Spaces:
Sleeping
Sleeping
| """Optional AI assistance for Calgary SWMR drafting and consistency review. | |
| This module is intentionally separate from the general model-analysis agent. | |
| The deterministic report engine remains the source of all engineering values, | |
| criteria statuses, checklist statuses, and permitted conclusions. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| from pathlib import Path | |
| from typing import Any, Mapping, Sequence | |
| import requests | |
| PROMPT_DIR = Path(__file__).resolve().parent / "prompts" | |
| PROVIDERS: dict[str, dict[str, Any]] = { | |
| "Claude (Anthropic)": { | |
| "models": ["claude-opus-4-5", "claude-sonnet-4-5", "claude-haiku-4-5"], | |
| "format": "anthropic", | |
| "url": "https://api.anthropic.com/v1/messages", | |
| }, | |
| "GPT (OpenAI)": { | |
| "models": ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo"], | |
| "format": "openai", | |
| "url": "https://api.openai.com/v1/chat/completions", | |
| }, | |
| "Gemini (Google)": { | |
| "models": ["gemini-2.0-flash", "gemini-1.5-pro", "gemini-1.5-flash"], | |
| "format": "gemini", | |
| "url": "https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent", | |
| }, | |
| "Groq (Free tier)": { | |
| "models": ["llama-3.3-70b-versatile", "llama-3.1-8b-instant", "mixtral-8x7b-32768"], | |
| "format": "openai", | |
| "url": "https://api.groq.com/openai/v1/chat/completions", | |
| }, | |
| "Mistral": { | |
| "models": ["mistral-large-latest", "mistral-small-latest", "open-mistral-7b"], | |
| "format": "openai", | |
| "url": "https://api.mistral.ai/v1/chat/completions", | |
| }, | |
| } | |
| def load_prompt(name: str) -> str: | |
| path = PROMPT_DIR / name | |
| if not path.exists(): | |
| raise FileNotFoundError(f"Prompt file not found: {path}") | |
| return path.read_text(encoding="utf-8").strip() | |
| DRAFT_SYSTEM_PROMPT = load_prompt("calgary_report_drafting_system.txt") | |
| REVIEW_SYSTEM_PROMPT = load_prompt("calgary_report_review_system.txt") | |
| def _call_provider( | |
| provider_name: str, | |
| api_key: str, | |
| model: str, | |
| messages: Sequence[Mapping[str, str]], | |
| system_prompt: str, | |
| max_tokens: int = 3200, | |
| timeout: int = 90, | |
| ) -> str: | |
| if provider_name not in PROVIDERS: | |
| raise ValueError(f"Unsupported provider: {provider_name}") | |
| if not api_key.strip(): | |
| raise ValueError("An API key is required for optional AI assistance.") | |
| provider = PROVIDERS[provider_name] | |
| fmt = provider["format"] | |
| if fmt == "anthropic": | |
| response = requests.post( | |
| provider["url"], | |
| headers={ | |
| "Content-Type": "application/json", | |
| "x-api-key": api_key, | |
| "anthropic-version": "2023-06-01", | |
| }, | |
| json={ | |
| "model": model, | |
| "max_tokens": max_tokens, | |
| "system": system_prompt, | |
| "messages": list(messages), | |
| }, | |
| timeout=timeout, | |
| ) | |
| response.raise_for_status() | |
| return response.json()["content"][0]["text"].strip() | |
| if fmt == "openai": | |
| response = requests.post( | |
| provider["url"], | |
| headers={ | |
| "Content-Type": "application/json", | |
| "Authorization": f"Bearer {api_key}", | |
| }, | |
| json={ | |
| "model": model, | |
| "max_tokens": max_tokens, | |
| "messages": [{"role": "system", "content": system_prompt}] + list(messages), | |
| }, | |
| timeout=timeout, | |
| ) | |
| response.raise_for_status() | |
| return response.json()["choices"][0]["message"]["content"].strip() | |
| if fmt == "gemini": | |
| url = provider["url"].replace("{model}", model) + f"?key={api_key}" | |
| contents = [] | |
| for message in messages: | |
| role = "model" if message["role"] == "assistant" else "user" | |
| contents.append({"role": role, "parts": [{"text": message["content"]}]}) | |
| response = requests.post( | |
| url, | |
| json={ | |
| "system_instruction": {"parts": [{"text": system_prompt}]}, | |
| "contents": contents, | |
| "generationConfig": {"maxOutputTokens": max_tokens}, | |
| }, | |
| timeout=timeout, | |
| ) | |
| response.raise_for_status() | |
| return response.json()["candidates"][0]["content"]["parts"][0]["text"].strip() | |
| raise ValueError(f"Provider format is not implemented: {fmt}") | |
| def _safe_json(data: Mapping[str, Any]) -> str: | |
| return json.dumps(data, ensure_ascii=False, default=str, separators=(",", ":")) | |
| def draft_report_section( | |
| *, | |
| provider_name: str, | |
| api_key: str, | |
| model: str, | |
| section: str, | |
| report_context: Mapping[str, Any], | |
| additional_instruction: str = "", | |
| ) -> str: | |
| """Draft narrative from deterministic report context only.""" | |
| request = f"""Prepare this Calgary SWMR draft component: {section}. | |
| Use only the VERIFIED_REPORT_CONTEXT JSON below. Follow every conclusion-control, | |
| checklist, storage, outfall, minor-system, major-system, and model-input/output rule | |
| in the system prompt. Preserve all values and units exactly. | |
| VERIFIED_REPORT_CONTEXT: | |
| {_safe_json(report_context)} | |
| ADDITIONAL_USER_INSTRUCTION: | |
| {additional_instruction.strip() or 'None'} | |
| Return report-ready prose with clear headings. Do not include a preamble about being an AI. | |
| """ | |
| return _call_provider( | |
| provider_name, | |
| api_key, | |
| model, | |
| [{"role": "user", "content": request}], | |
| DRAFT_SYSTEM_PROMPT, | |
| ) | |
| def review_report_narrative( | |
| *, | |
| provider_name: str, | |
| api_key: str, | |
| model: str, | |
| narrative: str, | |
| report_context: Mapping[str, Any], | |
| ) -> str: | |
| """Review narrative against deterministic facts without changing model results.""" | |
| request = f"""Review the DRAFT_NARRATIVE against VERIFIED_REPORT_CONTEXT. | |
| Identify unsupported claims, value or unit discrepancies, checklist omissions, | |
| misuse of criteria, overstatements, and contradictions. Then provide corrected | |
| replacement wording for each material issue. | |
| VERIFIED_REPORT_CONTEXT: | |
| {_safe_json(report_context)} | |
| DRAFT_NARRATIVE: | |
| {narrative} | |
| """ | |
| return _call_provider( | |
| provider_name, | |
| api_key, | |
| model, | |
| [{"role": "user", "content": request}], | |
| REVIEW_SYSTEM_PROMPT, | |
| ) | |
| def draft_multiple_report_sections(*, provider_name: str, api_key: str, model: str, sections: Sequence[str], report_context: Mapping[str, Any], additional_instruction: str = "") -> dict[str, str]: | |
| """Generate independent section drafts so each can be reviewed and approved.""" | |
| drafts: dict[str, str] = {} | |
| for section in sections: | |
| drafts[section] = draft_report_section( | |
| provider_name=provider_name, api_key=api_key, model=model, section=section, | |
| report_context=report_context, additional_instruction=additional_instruction, | |
| ) | |
| return drafts | |