Jun 29 - Improve AI analysis function

This commit is contained in:
2026-06-29 17:35:53 -04:00
parent 1a88eb0251
commit 262e85634c
+209 -75
View File
@@ -182,97 +182,199 @@ def _extract_text(file_obj, ext: str) -> str:
# Office address used as origin for distance/travel-time estimates.
_OFFICE_ADDRESS = "2815 Hartland Road, Falls Church, VA 22043, USA"
# Text character limit sent to the API. llama-3.3-70b supports 128K tokens (~500K chars);
# 60K chars is a safe ceiling that leaves room for the prompt and response.
_TEXT_LIMIT = 60_000
# Analyst persona injected as the system message in every API call.
_SYSTEM_PROMPT = (
"You are a senior government procurement analyst supporting a small business BD team. "
"Your role is to extract, organize, and evaluate information from solicitation documents "
"with precision and clarity. Format all output in well-structured Markdown using tables "
"and headers. Never invent or assume information not present in the source documents — "
"write \"Not specified\" for any missing field."
)
# Stage 1 — extraction prompt (always sent)
_EXTRACTION_PROMPT = """You are an expert government procurement analyst.
The user has provided {n} document(s). Treat all provided content as a single combined source — do not analyze each file individually. Extract the following information once, consolidating details from all documents.
_EXTRACTION_PROMPT = """\
Analyze the {n} attached government solicitation document(s) as one combined package.
Complete every section below exactly as structured. Write **"Not specified"** for any field
not found in the documents. Do not guess or invent details.
IMPORTANT — Our office is located at:
{office}
Use this as the ORIGIN address for all driving distance and travel time calculations in field #9 below.
Our office is located at: **{office}** — use this as the origin for all travel estimates.
Extract and clearly label the following fields (write "N/A" if not found):
---
1. Solicitation Number
2. Solicitation Type (e.g. RFP, RFQ, IFB, etc.)
3. Set-Aside (e.g. Small Business, 8(a), N/A)
4. Description / Scope of Work
5. Work Site / Location(s)
6. Pre-Proposal Conference / Site-Visit (date, time, full address)
7. Point of Contact (POC) (name, phone, email)
8. Total Square Footage (if applicable)
9. Driving Distance & Travel Time
- Origin: {office}
- Destination: Pre-Proposal Conference or primary Work Site address
- Provide your best estimate of driving distance (miles) and typical driving time using major highways
- Note that these are AI estimates; actual times may vary with traffic
10. Last Day to Submit Questions
11. Due Date & Time
12. Any other notable requirements or deadlines
## Solicitation Overview
Then provide a detailed OVERALL SUMMARY covering:
| Field | Value |
|---|---|
| **Solicitation Number** | |
| **Solicitation Type** | (RFP / RFQ / IFB / IDIQ / BPA / etc.) |
| **Issuing Agency / Office** | |
| **Set-Aside** | (Small Business / 8(a) / SDVOSB / HUBZone / WOSB / Unrestricted / etc.) |
| **Contract Type** | (Firm-Fixed-Price / T&M / Cost-Plus / IDIQ / etc.) |
| **Contract Period** | |
| **NAICS Code** | |
| **Estimated Value** | |
A. Scope of Work
B. Contract Period
C. Proposal Submission Requirements
D. Key Deadlines & Action Items
---
Be precise, detailed, and use bullet points throughout.
If information is not explicitly stated in the documents, note it as "Not specified in the document."
## Scope of Work
*Describe what is required in full detail: services, deliverables, performance standards, and any technical requirements. Use bullet points.*
---
## Performance Location(s)
*List all work sites. Note whether remote or on-site work is permitted.*
---
## Key Dates & Deadlines
| Milestone | Date & Time (with timezone) |
|---|---|
| Pre-Proposal Conference / Site Visit | |
| Questions Due | |
| **Proposal Due** | |
| Award Date (if stated) | |
| Period of Performance Start | |
| Other Deadlines | |
---
## Pre-Proposal Conference / Site Visit
| | |
|---|---|
| **Date & Time** | |
| **Full Address** | |
| **Attendance** | (Mandatory / Optional / Not applicable) |
| **RSVP Required** | (Yes — deadline & method / No) |
---
## Point of Contact (POC)
| | |
|---|---|
| **Name / Title** | |
| **Phone** | |
| **Email** | |
| **Questions Submission Method** | |
---
## Proposal Requirements
*Summarize format, page limits, required sections/volumes, submission method (portal/email/mail), and number of copies. Use bullet points.*
---
## Evaluation Factors
*List evaluation criteria and their weights or order of priority as stated in the solicitation.*
---
## Travel & Logistics
| | |
|---|---|
| **Origin** | {office} |
| **Destination** | (Pre-Proposal / Primary Work Site address) |
| **Estimated Driving Distance** | |
| **Estimated Drive Time** | (normal traffic conditions) |
*(AI estimate — verify with a mapping service before scheduling travel.)*
---
## Notable Requirements & Red Flags
*List anything that affects bid/no-bid decisions: unusual insurance or bonding levels, required security clearances, certifications, teaming or subcontracting restrictions, incumbent advantage indicators, aggressive timelines, or any other risk factors.*
---
## Total Square Footage
*(If applicable to the scope of work; write "N/A" if not.)*
---
## Overall Summary
Provide a concise but thorough summary covering:
- **What is Being Procured** — Scope and key deliverables.
- **Contract Period & Estimated Value** — Duration and any stated ceiling or estimate.
- **Key Constraints & Requirements** — Timeline, location, special certifications, etc.
- **Immediate Action Items** — What the team must do and by when.
---
DOCUMENTS:
{documents}"""
# Stage 2 — criteria evaluation suffix (appended only when active criteria exist)
_CRITERIA_PROMPT_SUFFIX = """
# Stage 2 — criteria evaluation (sent as a separate API call when active criteria exist).
# Receives the clean Stage 1 extraction output as {summary}, not the raw documents,
# so the model can focus entirely on the evaluation without re-parsing document noise.
_CRITERIA_PROMPT = """\
Below is an extracted and summarized government solicitation opportunity.
Evaluate whether our company should pursue it based on our evaluation criteria.
================================================================================
OPPORTUNITY ALIGNMENT EVALUATION
================================================================================
---
After completing the extraction and summary above, evaluate whether this
opportunity aligns with our company's interests based on the following criteria.
## OPPORTUNITY SUMMARY
{summary}
---
## ALIGNMENT EVALUATION
**Our evaluation criteria:**
OUR EVALUATION CRITERIA:
{criteria_list}
For EACH criterion above:
- State whether the opportunity MEETS, DOES NOT MEET, or PARTIALLY MEETS it.
- Provide a brief, specific explanation citing details from the document(s).
For **each criterion** listed above, provide:
- A status: ✅ **MEETS** / ⚠️ **PARTIALLY MEETS** / ❌ **DOES NOT MEET** / ❓ **CANNOT DETERMINE**
- A 12 sentence explanation citing specific details from the summary above.
Then provide an OVERALL RECOMMENDATION using EXACTLY one of these three labels
on its own line (this label is machine-read — do not alter it):
---
## Overall Recommendation
State your recommendation on its own line in **exactly** this format (required for system parsing — do not alter the label):
```
RECOMMENDATION: PURSUE
```
or
```
RECOMMENDATION: PASS
```
or
```
RECOMMENDATION: UNCLEAR
```
Use PURSUE if the opportunity clearly meets most criteria and presents strong
alignment. Use PASS if it clearly fails key criteria. Use UNCLEAR if the
documents lack sufficient information to make a confident determination.
**PURSUE** — opportunity clearly aligns with most criteria and is competitive.
**PASS** — fails one or more critical criteria or presents unacceptable risk.
**UNCLEAR** — insufficient information to make a confident determination.
End with a 2-3 sentence EXECUTIVE SUMMARY explaining your recommendation
in plain business language."""
---
## Executive Summary
Write 34 sentences in plain business language explaining your recommendation:
the strongest reasons to pursue or pass, and the single biggest risk or opportunity."""
def _call_groq(api_key: str, model: str, text: str, criteria: list) -> dict:
"""Call the Groq chat completions REST API directly (no SDK required)."""
import re
n = text.count("=== ") or 1 # count file separators for the prompt header
truncated = len(text) > 14000
# Build the two-stage prompt matching the desktop app exactly
prompt = _EXTRACTION_PROMPT.format(
n=n, office=_OFFICE_ADDRESS, documents=text[:14000]
)
if criteria:
criteria_list = "\n".join(
f" {i+1}. {c['title']}: {c['description']}"
for i, c in enumerate(criteria)
)
prompt += _CRITERIA_PROMPT_SUFFIX.format(criteria_list=criteria_list)
def _call_groq_api(api_key: str, model: str, user_message: str) -> str:
"""Single Groq chat completions call. Raises on HTTP error."""
response = http_requests.post(
"https://api.groq.com/openai/v1/chat/completions",
headers={
@@ -281,29 +383,61 @@ def _call_groq(api_key: str, model: str, text: str, criteria: list) -> dict:
},
json={
"model": model,
"messages": [{"role": "user", "content": prompt}],
"max_tokens": 4096,
"temperature": 0.2,
"messages": [
{"role": "system", "content": _SYSTEM_PROMPT},
{"role": "user", "content": user_message},
],
"max_tokens": 8192,
"temperature": 0.1,
},
timeout=90,
timeout=120,
)
if not response.ok:
logger.error(f"Groq API {response.status_code}: {response.text[:300]}")
response.raise_for_status()
return response.json()["choices"][0]["message"]["content"] or ""
content = response.json()["choices"][0]["message"]["content"] or ""
# Parse the machine-readable RECOMMENDATION label (only present when criteria used)
def _call_groq(api_key: str, model: str, text: str, criteria: list) -> dict:
"""
Two-stage Groq analysis:
Stage 1 (always): extract structured fields + overall summary from documents.
Stage 2 (optional): evaluate criteria against the Stage 1 output (not raw docs),
producing a focused RECOMMENDATION with per-criterion scoring.
"""
truncated = len(text) > _TEXT_LIMIT
doc_text = text[:_TEXT_LIMIT]
n = doc_text.count("=== ") or 1
# ── Stage 1: extraction ────────────────────────────────────────────────────
stage1_prompt = _EXTRACTION_PROMPT.format(
n=n, office=_OFFICE_ADDRESS, documents=doc_text
)
summary = _call_groq_api(api_key, model, stage1_prompt)
if not criteria:
return {"verdict": None, "summary": summary, "truncated": truncated}
# ── Stage 2: criteria evaluation (uses clean summary, not raw docs) ────────
criteria_list = "\n".join(
f"{i+1}. **{c['title']}**: {c['description']}"
for i, c in enumerate(criteria)
)
stage2_prompt = _CRITERIA_PROMPT.format(
summary=summary, criteria_list=criteria_list
)
evaluation = _call_groq_api(api_key, model, stage2_prompt)
verdict = None
if criteria:
match = re.search(
r"RECOMMENDATION\s*:\s*(PURSUE|PASS|UNCLEAR)",
content, re.IGNORECASE,
evaluation, re.IGNORECASE,
)
if match:
verdict = match.group(1).upper()
return {"verdict": verdict, "summary": content, "truncated": truncated}
combined = summary + "\n\n---\n\n" + evaluation
return {"verdict": verdict, "summary": combined, "truncated": truncated}
# ─── Criteria Management (admin only) ─────────────────────────────────────────