Add bid tracker

This commit is contained in:
2026-04-24 07:15:48 -04:00
parent 262c71e93b
commit 6ec439bafe
7 changed files with 2273 additions and 205 deletions
+192 -88
View File
@@ -1,21 +1,23 @@
# CLAUDE.md — Website Checker: Complete Technical Knowledge Base
This document captures the full architecture, design decisions, database schema, module inventory,
feature set, and operational notes for the **Website Checker** desktop application.
This document is the authoritative technical reference for the **Website Checker**
desktop application. Keep it current whenever code changes are made.
---
## 1. Application Overview
Website Checker is a Python/Tkinter desktop application for shift-based website monitoring.
Regular users log in, view websites assigned to their shift, open each site, and mark it checked.
Administrators manage users, websites, shifts, credentials, and run reports.
Website Checker is a Python/Tkinter desktop application for shift-based website
monitoring backed by a remote MySQL database. Regular users log in, view websites
assigned to their shift, open each site, and mark it checked. Administrators manage
users, websites, shifts, credentials, email reports, and AI-powered document analysis.
- **UI:** Python `tkinter` + `ttk`
- **Database:** Remote MySQL 5.7+ / MariaDB 10.3+ via `mysql-connector-python`
- **Entry point:** `app.py``class App(tk.Tk)`
- **Default window:** 1100×700, minimum 960×620
- **Default theme:** Light (toggleable to Dark)
- **Platform:** Windows desktop (primary); macOS/Linux supported with limitations
---
@@ -28,21 +30,23 @@ website_checker/
├── models.py All database access (data layer)
├── requirements.txt
├── CLAUDE.md This file
├── README.md Setup and deployment guide
├── USER_MANUAL.md End-user documentation
├── utils/
│ ├── config_crypto.py Windows DPAPI encryption for config.ini credentials
│ ├── crypto.py Fernet credential encryption (website passwords)
│ ├── export.py CSV + Excel export
│ ├── scheduler.py Daily email report background daemon
│ ├── scheduler.py Daily email report background daemon + SMTP helpers
│ └── ui_helpers.py ThemeManager, COLOURS proxy, DateEntry, widgets
└── views/
├── admin_dashboard_view.py Admin home — KPI cards + per-user progress table
├── admin_log_view.py Activity log treeview
├── admin_shifts_view.py Shift CRUD + PDF export
├── admin_users_view.py User CRUD
├── admin_shifts_view.py Shift CRUD + PDF export + inactive filter toggle
├── admin_users_view.py User CRUD + secure password reset dialog
├── admin_websites_view.py Website CRUD + visibility + credentials (collapsible)
├── ai_summary_view.py AI document analysis via Groq API (NEW)
├── ai_summary_view.py AI document analysis via Groq API
├── change_password_view.py Self-service password change (all roles)
├── email_settings_view.py SMTP / scheduled report configuration
├── email_settings_view.py SMTP / scheduled report config (3-mode security)
├── login_view.py Login form with rate-limiting countdown
├── reports_view.py Shift Detail / Unchecked / Summary / Chart tabs
├── settings_view.py DB connection settings dialog
@@ -71,9 +75,11 @@ smtp_host=smtp.example.com
smtp_port=587
smtp_user=sender@example.com
smtp_password=dpapi:<base64-blob> ; encrypted
use_tls=true
security=starttls ; starttls | ssl | none
use_tls=true ; legacy compat field, derived from security
recipients=admin@example.com
send_time=18:00
last_sent_date=2025-01-01 ; ISO date, prevents re-send after restart
[groq]
api_key=dpapi:<base64-blob> ; encrypted
@@ -92,6 +98,7 @@ UTF-8 log file in working directory. All INFO/WARNING/ERROR from every module.
All tables use InnoDB/utf8mb4. Created by `initialize_database()` on first launch.
Safe ALTER TABLE migrations run automatically for columns added post-deployment.
New tables (ai_criteria, ai_analysis_log) verified with information_schema check and logged.
### users
| Column | Type | Notes |
@@ -111,7 +118,7 @@ Safe ALTER TABLE migrations run automatically for columns added post-deployment.
|---|---|---|
| id | INT PK AUTO | |
| name | VARCHAR(200) | |
| url | TEXT | |
| url | TEXT | Validated by _validate_and_normalise_url (http/https only) |
| check_type | ENUM('daily','weekly') | weekly = once per ISO week |
| visibility | ENUM('all','assigned') | assigned = only users in website_users table |
| note | TEXT | Shown on user dashboard card |
@@ -130,9 +137,10 @@ Safe ALTER TABLE migrations run automatically for columns added post-deployment.
One row per user per site per day (upserted). `user_note` optional.
### activity_log
`action` codes: LOGIN, LOGOUT, SESSION_TIMEOUT, ACCOUNT_LOCKED, CREATE/UPDATE/DELETE_USER,
CHANGE_PASSWORD, CHANGE_PASSWORD_FAIL, RESET_PASSWORD, CREATE/UPDATE/DELETE_WEBSITE,
ADD/REMOVE_CREDENTIAL, CREATE/UPDATE/DELETE_SHIFT, CHECK_WEBSITE, UPDATE_NOTE,
`action` codes: LOGIN, LOGOUT, SESSION_TIMEOUT, ACCOUNT_LOCKED,
CREATE/UPDATE/DELETE_USER, CHANGE_PASSWORD, CHANGE_PASSWORD_FAIL, RESET_PASSWORD,
CREATE/UPDATE/DELETE_WEBSITE, ADD/REMOVE_CREDENTIAL,
CREATE/UPDATE/DELETE_SHIFT, CHECK_WEBSITE, UPDATE_NOTE,
EXPORT_CSV, EXPORT_EXCEL, EXPORT_SHIFT_PDF, UPDATE_EMAIL_SETTINGS,
CREATE/UPDATE/DELETE_AI_CRITERION.
@@ -146,15 +154,16 @@ E.g. "23456" = MonFri. Queried with `LOCATE(DAYOFWEEK(CURDATE()), days_of_wee
### website_users (junction): (website_id, user_id) PK — for visibility='assigned'
### ai_criteria
Stores evaluation criteria used by the AI to assess opportunity alignment.
Stores evaluation criteria used by the AI to assess solicitation alignment.
| Column | Type | Notes |
|---|---|---|
| id | INT PK AUTO | |
| title | VARCHAR(200) | Short label |
| description | TEXT | Full criterion text sent to the AI |
| title | VARCHAR(200) | Short label shown in treeview |
| description | TEXT | Full criterion text sent to the AI prompt |
| is_active | TINYINT(1) | Inactive criteria excluded from AI prompt |
| sort_order | INT | Controls display and prompt ordering |
| created_by | INT FK users SET NULL | |
| created_at / updated_at | DATETIME | |
### ai_analysis_log
Persists every AI analysis result for history and audit purposes.
@@ -173,16 +182,24 @@ Persists every AI analysis result for history and audit purposes.
## 5. Module Details
### app.py
- App(tk.Tk) — root window; manages login state, shell, sidebar, session timeout
- IDLE_TIMEOUT_MS = 1,800,000 (30 min); IDLE_WARNING_MS = 60,000 (1 min warning)
- _reset_idle_timer throttled: Motion events fire at most once per _IDLE_THROTTLE_S=5.0 seconds
- _idle_last_reset: monotonic timestamp used for throttle check
- Admin login → scheduler.start(); logout → scheduler.stop()
- Theme toggle destroys and rebuilds the entire shell (content panels re-instantiated)
### config.py
- DB_CONFIG loaded from config.ini at import; placeholders if file missing
- get_connection() → pool (pool_size=5)
- initialize_database() → idempotent DDL + ALTER TABLE migrations
- initialize_database() → idempotent DDL + ALTER TABLE migrations + table confirmation logs
- load_config() / save_config() → decrypt/encrypt DPAPI fields transparently
- migrate_plaintext_config() → one-time migration; encrypts any plain-text creds on startup
- config_exists() → True if host, database, user are set
- reload_db_config() → re-reads config.ini, resets pool
### utils/config_crypto.py *(NEW)*
### utils/config_crypto.py
Windows DPAPI-based encryption for config.ini sensitive values.
- encrypt_value(plaintext) → "dpapi:<base64>" string
- decrypt_value(stored) → plaintext; plain-text pass-through for legacy values
@@ -201,6 +218,14 @@ Password helpers:
Rate-limit constants: MAX_FAILED_ATTEMPTS=5, LOCKOUT_MINUTES=15
Password strength: PW_MIN_LENGTH=8, requires upper + digit + special char
Key functions:
- get_today_checks(user_id) — shift-aware; respects visibility and check_type
- get_unchecked_report(target_date, user_id) — shift-scoped; only sites user was expected to check
- get_summary_report(date_from, date_to) — shift-scoped total_sites per user per day
- get_all_criteria() / get_active_criteria() — no LEFT JOIN; simple SELECT from ai_criteria
- save_ai_analysis() / get_ai_analysis_history() / get_ai_analysis_detail()
- delete_user() — guards: self-delete prevention + last-admin prevention
### utils/crypto.py
- PBKDF2-HMAC-SHA256, 100,000 iterations, 32-byte salt from config.ini [crypto]
- encrypt(plaintext) → "enc:" + base64(Fernet token)
@@ -218,73 +243,97 @@ Password strength: PW_MIN_LENGTH=8, requires upper + digit + special char
### utils/scheduler.py
- Daemon thread; polls every 60 seconds
- Sends HTML email once per day when now >= send_time
- smtp_password is DPAPI-encrypted on save (encrypt_value) / decrypted on load (decrypt_value)
- last_sent_date in-memory (resets on restart)
- start() / stop() called from app.py on login/logout (admin only)
- smtp_password DPAPI-encrypted on save / decrypted on load
- last_sent_date persisted to config.ini [email] last_sent_date — survives restarts
- start() / stop() called from app.py on admin login/logout
- start() guards against double-start: joins existing thread before spawning new one
- _make_smtp_server(host, port, security) — centralised connection with explicit ehlo() calls
(required on Windows for reliable STARTTLS/SSL handshake)
- security field: "starttls" (port 587) | "ssl" (port 465) | "none" (port 25)
- test_smtp_connection() — 4-step diagnostic: DNS → TCP → TLS → Auth
- send_test_email() — sends real HTML test message through full pipeline
- Email report excludes users with total_sites=0 (no shift today); adds footnote count
### views/email_settings_view.py
- Security mode: 3 radio buttons (STARTTLS/SSL·TLS/None) replacing old Use STARTTLS checkbox
- Selecting a security mode auto-fills the default port
- Loads legacy use_tls bool from config.ini and maps to security field on first load
- 🔌 Test Connection — step-by-step diagnostic output in status label
- 📧 Send Test Email — real email send; all buttons disabled during operation
- Save preserves last_sent_date in config.ini
### views/ai_summary_view.py
AI-powered document analysis panel. Accessible from the sidebar for both admin and user roles.
AI-powered document analysis panel. Accessible from sidebar for both roles.
Key internals:
- Supported file types: .txt, .md, .csv, .pdf, .docx, .doc, .xlsx, .xls
- Groq API key and model selection are **visible only to admins** (user role sees neither)
- Regular users use the stored API key transparently
- Groq API key and model selection visible only to admins
- Regular users use stored API key transparently
- Background thread for AI calls (prevents UI freeze)
- Extraction prompt focused on procurement/solicitation fields:
Solicitation Number/Type, Set-Aside, Description, Work Site, Pre-Proposal Conference,
POC, Square Footage, Driving Distance from office (2815 Hartland Rd, Falls Church VA),
Last Day for Questions, Due Date
- Overall summary covers: Scope of Work, Contract Period, Proposal Submission Requirements,
Key Deadlines & Action Items
- Groq API key stored DPAPI-encrypted in config.ini [groq] section
- Notebook layout: ✨ Analyze tab + 🕑 History tab
- Extraction prompt: 12 labeled fields per document + 4-section Overall Summary
- Driving distance calculated from 2815 Hartland Rd, Falls Church VA (AI estimate)
- max_tokens=4096; temperature=0.2; MAX_CHARS_PER_FILE=14,000
- **Evaluation Criteria panel** (collapsible): admin can add/edit/delete criteria;
users see read-only list. Active criteria are appended to the AI prompt as an
alignment evaluation section. The AI outputs RECOMMENDATION: PURSUE/PASS/UNCLEAR
which is parsed and displayed as a colour-coded verdict banner.
- **History tab**: every analysis is saved to ai_analysis_log (with verdict + criteria
snapshot). History treeview is filterable by verdict. Double-click restores full
output in the Analyze tab. Admins see all users; regular users see only their own.
- CriterionDialog has a live character counter with 500-char soft limit guidance.
- URL open and health-check probe both guarded by _validate_and_normalise_url.
.doc reading (legacy binary Word format) — 3-tier fallback:
Evaluation Criteria panel (collapsible):
- Admin: Add/Edit/Delete controls; CriterionDialog with live character counter (soft limit 500)
- Users: read-only treeview
- Active criteria appended to AI prompt; AI outputs RECOMMENDATION: PURSUE/PASS/UNCLEAR
- Verdict parsed by _parse_verdict() and shown as colour-coded banner (green/red/amber)
History tab:
- Every analysis saved to ai_analysis_log with verdict + criteria snapshot
- save_ai_analysis() called in _on_success() — never blocks the result display
- Filterable by verdict radio buttons (All/PURSUE/PASS/UNCLEAR/none)
- Double-click or View Result restores full output in Analyze tab
- Admins see all users' history; regular users see only their own
- Auto-refreshes on tab switch via <<NotebookTabChanged>>
.doc reading (legacy binary Word) — 3-tier fallback:
1. win32com (Word COM automation — requires MS Word installed)
2. docx2txt (pure Python)
3. Raw ASCII scrape from binary
URL safety: _validate_and_normalise_url() rejects non-http/https schemes.
### views/admin_websites_view.py
- Website CRUD dialog now has a **collapsible credentials section** (hidden by default)
- "🔑 Show Credentials" / "🔒 Hide Credentials" toggle button
- Auto-expands when editing a site that already has saved credentials
- " Add Credential" auto-expands the section if collapsed
- **Bug fixed:** `_on_visibility_change` previously used `before=self.creds_container`
which crosses a widget parent boundary causing a TclError; corrected to `before=self._cred_body`
- Website CRUD dialog with collapsible credentials section (hidden by default)
- _on_visibility_change uses before=self._cred_body (not creds_container — same parent)
- _validate_and_normalise_url() called in _save() before DB write
- URL validation: prepends https:// if no scheme; rejects javascript:/data:/file: etc.
### views/admin_users_view.py
- 🔑 Reset Password button generates 16-char cryptographically random password
- _PasswordResetDialog: masked field, 👁 reveal, 📋 copy (2s flash), 120s auto-close,
30s clipboard wipe, countdown timer
- Logs RESET_PASSWORD to activity_log
### views/admin_shifts_view.py
- "Show inactive" checkbox toggles visibility of inactive shifts in treeview
- _load_shifts filters by _show_inactive BooleanVar; no extra DB call
- _export_pdf(): reportlab A4; one section per active shift
### views/user_dashboard_view.py
Key internals:
- _health_cache: {website_id: ("ok"|"slow"|"down", ms)} — daemon threads, HEAD request, 6s timeout
- Dot colours: green (<=3000ms), amber (>3000ms), red (error)
- Mousewheel: Enter/Leave scoped; unbind_all in destroy()
- Keyboard shortcuts: self.bind() stored in _shortcut_ids; unbound in destroy()
- Notifications: after(60_000) loop; plyer first, fallback to borderless Toplevel toast
- **Credentials popup no longer opens automatically on link click**
- "🔑 Credentials" button appears on each site card only if the site has saved credentials
- CredentialsPopup: 📋 copy button for username (2s flash); 📋 copy button for password
(2s flash + clipboard auto-cleared after 15s for security); 👁 toggle to reveal password
- _health_cache: {website_id: ("ok"|"slow"|"restricted"|"down", ms)}
- ok: 2xx/3xx ≤3000ms; slow: 2xx/3xx >3000ms; restricted: 4xx; down: 5xx/network error
- Notifications: single consolidated reminder per shift end-time group (not per site)
- Groups unchecked sites by end_time; fires one notification listing all sites
- _notified_sites key: (frozenset(names), end_t)
- Keyboard shortcuts: stored in _shortcut_ids; unbound in destroy()
- Credentials popup: 📋 copy (username 2s flash; password 2s flash + 15s clipboard wipe)
### views/reports_view.py
- Tabs: Shift Detail / Unchecked / Summary / Chart
- All date fields use DateEntry (no manual text entry)
- Summary report: shift-scoped total_sites per user per day (not global count)
- Unchecked report: shift-scoped; only sites user was expected to check that day
- Chart: matplotlib Agg + FigureCanvasTkAgg; bars green≥100% / amber≥50% / red<50%
### views/login_view.py
- check_login_allowed(username) called before authenticate()
- Locked: form disabled; 1-second countdown; re-enables when timer reaches 0
- Not-yet-locked failed attempt: shows "N attempt(s) remaining before lockout"
### views/admin_shifts_view.py
- _export_pdf(): reportlab A4 document; one section per active shift; user+website tables
### views/reports_view.py
- All date fields use DateEntry (no manual text entry)
- Chart: matplotlib Agg + FigureCanvasTkAgg; bars green>=100% / amber>=50% / red<50%
---
## 6. Authentication & Security
@@ -296,10 +345,14 @@ Key internals:
| Config credential protection | Windows DPAPI (CryptProtectData) — user-account-scoped |
| Encrypted fields | DB user, DB password, SMTP password, Groq API key |
| Login rate limiting | 5 attempts → 15-min lockout in DB |
| Session timeout | 30-min idle; 1-min warning; any mouse/key event resets |
| Session timeout | 30-min idle; 1-min warning; Motion events throttled to 1/5s |
| Password policy | 8+ chars, uppercase, digit, special character |
| Self-service change | Requires current password; strength meter; same-as-current guard |
| Clipboard security | Password copy auto-clears clipboard after 15 seconds |
| URL safety | _validate_and_normalise_url rejects non-http/https schemes |
| Admin self-delete | Blocked in delete_user() |
| Last-admin deletion | Blocked in delete_user() |
| Password reset display | _PasswordResetDialog: 120s auto-close, 30s clipboard wipe |
### DPAPI Migration
`migrate_plaintext_config()` is called automatically on every startup (in `_init_db`).
@@ -316,13 +369,21 @@ On first run after this feature was added, it encrypts any existing plain-text v
### visibility
- all: every user in shift sees it
- assigned: only users in website_users table see it
- assigned: only users in website_users table see it (enforced in both shift and legacy paths)
### get_today_checks resolution
1. Check if user has active shifts today (DAYOFWEEK match)
2. If yes: union of shift websites filtered by check_type + visibility
2. If yes: shift websites filtered by check_type + visibility (website_users respected)
3. If no: all active websites filtered by visibility + check_type (legacy fallback)
### Unchecked report scope
Uses shift membership to determine "expected" sites per user per day.
CROSS JOIN replaced with shift-aware subquery — no false positives for off-shift users.
### Summary report total_sites
Correlated subquery counts shift-assigned sites per user per check_date day-of-week.
A user in a 3-site shift shows 3 as total_sites, not the global count.
### Soft deletes
Websites and Shifts: is_active=0. Users: hard-delete (admin can deactivate first).
@@ -336,6 +397,7 @@ Websites and Shifts: is_active=0. Users: hard-delete (admin can deactivate first
| app.py | IDLE_WARNING_MS | 60,000 (1 min) |
| app.py | APP_VERSION | "1.0.0" |
| app.py | VERSION_CHECK_ENABLED | False |
| app.py | App._IDLE_THROTTLE_S | 5.0 (seconds between Motion-event timer resets) |
| models.py | MAX_FAILED_ATTEMPTS | 5 |
| models.py | LOCKOUT_MINUTES | 15 |
| models.py | PW_MIN_LENGTH | 8 |
@@ -345,9 +407,9 @@ Websites and Shifts: is_active=0. Users: hard-delete (admin can deactivate first
| ai_summary_view.py | MAX_CHARS_PER_FILE | 14,000 |
| ai_summary_view.py | _OFFICE_ADDRESS | "2815 Hartland Road, Falls Church, VA 22043, USA" |
| ai_summary_view.py | CriterionDialog._DESC_SOFT_LIMIT | 500 (chars, soft guidance only) |
| app.py | App._IDLE_THROTTLE_S | 5.0 (seconds between Motion-event timer resets) |
| admin_users_view.py | _PasswordResetDialog._AUTO_CLOSE_S | 120 (seconds before auto-close) |
| admin_users_view.py | _PasswordResetDialog._CLIP_CLEAR_S | 30 (seconds before clipboard wipe) |
| utils/scheduler.py | (timeout in _make_smtp_server) | 15 seconds |
---
@@ -370,9 +432,8 @@ Websites and Shifts: is_active=0. Users: hard-delete (admin can deactivate first
6. Weekly site logic uses YEARWEEK(..., 1) (ISO week, Monday start).
7. The email scheduler `last_sent_date` is now persisted to `config.ini`
`[email] last_sent_date` (ISO format). A restart after the configured
`send_time` will not re-send the report on the same day.
7. The email scheduler last_sent_date is persisted to config.ini [email] last_sent_date
(ISO format). A restart after the configured send_time will not re-send that day.
8. All log messages use ASCII only (hyphens not em-dashes/arrows) to prevent
cp1252 UnicodeEncodeError on Windows consoles.
@@ -381,8 +442,19 @@ Websites and Shifts: is_active=0. Users: hard-delete (admin can deactivate first
If config.ini is copied to a different machine or user account, credentials
cannot be decrypted. Users must re-enter credentials via the Settings dialog.
10. The AI summary view fetches credentials from the DB at card render time (not lazily).
Avoid having hundreds of sites with credentials as this adds DB round-trips per render.
10. Theme toggle destroys and rebuilds the entire shell. Any unsaved in-memory
state in the active panel is lost. AI summary output is not preserved across
theme toggles (it is in ai_analysis_log; retrieve from History tab).
11. _make_smtp_server calls ehlo() explicitly before and after starttls() — this is
required on Windows. Removing these calls breaks SMTP on many servers.
12. The security field in config.ini supersedes use_tls. On existing deployments
without a security field, load_email_config() derives it from use_tls for
backward compatibility.
13. get_all_criteria() does NOT join users table. The creator column is excluded
intentionally — the treeview doesn't show it and the join is wasted I/O.
---
@@ -403,8 +475,8 @@ pywin32>=306
docx2txt>=0.8
```
stdlib used: tkinter, csv, smtplib, urllib.request, configparser, threading, calendar,
datetime, base64, re, tempfile
stdlib used: tkinter, csv, smtplib, urllib.request, urllib.parse, configparser,
threading, calendar, datetime, base64, re, tempfile, socket
---
@@ -429,7 +501,28 @@ My Shift · 🤖 AI Summary · Change Password · Sign Out
---
## 13. Deployment Notes
## 13. SMTP Configuration Reference
| Security Mode | Protocol | Default Port | Use When |
|---|---|---|---|
| STARTTLS | Plain → TLS upgrade | 587 | Office 365, Exchange, most corporate |
| SSL/TLS | TLS from first byte | 465 | Gmail direct, some providers |
| None | Unencrypted | 25 | Internal relay servers only |
Connection flow in _make_smtp_server:
- STARTTLS: SMTP() → ehlo() → starttls() → ehlo()
- SSL: SMTP_SSL() → ehlo()
- None: SMTP() → ehlo()
test_smtp_connection() diagnostic steps:
1. DNS — getaddrinfo(); fails = hostname wrong or no network
2. TCP — create_connection(); fails = firewall blocking the port
3. TLS — _make_smtp_server(); fails = wrong security mode or port
4. Auth — server.login(); fails = wrong username/password or App Password needed
---
## 14. Deployment Notes
- Python 3.9+ required. 3.12 tested on Windows.
- tkinter bundled on Windows/macOS; Linux: apt install python3-tk
@@ -439,30 +532,41 @@ My Shift · 🤖 AI Summary · Change Password · Sign Out
- pywin32 required for DPAPI encryption (Windows only). Install: pip install pywin32
- Microsoft Word recommended for .doc file support in AI Summary (falls back to docx2txt)
- Groq API key required for AI Summary feature — free at https://console.groq.com
- Gmail users: enable 2FA, then create an App Password at myaccount.google.com/apppasswords
- config.ini is machine- and user-account-specific. Do not share or copy between machines.
---
## 14. AI Summary Feature — Setup & Prompt Details
## 15. AI Summary Feature — Setup & Prompt Details
### Setup (Admin)
### Setup (Admin only)
1. Obtain a free Groq API key at https://console.groq.com
2. Navigate to **🤖 AI Summary** in the sidebar
2. Navigate to 🤖 AI Summary in the sidebar
3. Enter the API key and select a model
4. Click **💾 Save Settings** — key is stored DPAPI-encrypted in config.ini
4. Click 💾 Save Settings — key stored DPAPI-encrypted in config.ini [groq]
### Supported Models
- llama-3.3-70b-versatile (default, recommended)
- llama-3.1-8b-instant
- llama-3.1-8b-instant (faster, smaller)
- gemma2-9b-it
- mixtral-8x7b-32768
### Prompt Strategy
The prompt instructs the AI to extract 12 labeled fields per document:
Solicitation Number, Type, Set-Aside, Description/Scope, Work Site, Pre-Proposal Conference,
POC, Square Footage, **Driving Distance from 2815 Hartland Rd Falls Church VA** (calculated by AI),
Extraction: 12 labeled fields per document:
Solicitation Number, Type, Set-Aside, Description/Scope, Work Site,
Pre-Proposal Conference, POC, Square Footage,
Driving Distance from 2815 Hartland Rd Falls Church VA (AI estimate),
Last Day for Questions, Due Date, Other requirements.
Then produces an Overall Summary with 4 sections:
Overall Summary: 4 sections:
A. Scope of Work · B. Contract Period · C. Proposal Submission Requirements · D. Key Deadlines
The driving distance/time is an AI estimate using major highways. Actual times may vary with traffic.
Alignment evaluation (when criteria active):
- Each criterion assessed as MEETS / DOES NOT MEET / PARTIALLY MEETS
- RECOMMENDATION: PURSUE | PASS | UNCLEAR on its own line (machine-read by _parse_verdict)
- Executive Summary: 2-3 sentences
### History & Audit
- Every analysis is auto-saved to ai_analysis_log (verdict + criteria snapshot at time of run)
- update_criterion() logs full description text in activity_log.detail for audit trail
- Admins see all users' analyses; users see only their own