Add domain CRUD, energy density constraint, LLM status, reset results, score display fixes
Domain management: - Add domain list/detail/form templates and full CRUD routes (domains.py) - Add metric bound add/edit/delete via HTMX partials (_metrics_table.html) Energy density constraint (Rule 6 in ConstraintResolver): - Hard-block combos where power source provides <25% of platform's required Wh/kg - Warn (conditional) when under-density but within 4x - Solar Sail exempt (no stored energy); Airplane requires 400 Wh/kg, Spaceship 2000 Wh/kg - Add energy_density_wh_kg provides to all 8 stored-energy power sources in seed data - 3 new constraint resolver tests LLM-complete status: - Pipeline Pass 4 now sets combo status to llm_reviewed after successful LLM review - update_combination_status guards against downgrading: scored won't overwrite llm_reviewed or reviewed; llm_reviewed won't overwrite reviewed - Add badge-llm_reviewed CSS style (light blue) Reset results: - Repository.reset_domain_results() deletes combination_results, combination_scores, and pipeline_runs for a domain; pipeline re-evaluates on next run - POST /results/<domain>/reset route with flash confirmation - "Reset results" danger button with JS confirm dialog in results list Fix composite score 0 displaying as --- (Jinja2 falsy 0.0 bug): - Change `if r.composite_score` to `if r.composite_score is not none` Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,10 +8,14 @@ from abc import ABC, abstractmethod
|
||||
class LLMRateLimitError(Exception):
|
||||
"""Raised by a provider when the API rate limit is exceeded.
|
||||
|
||||
The pipeline catches this to stop gracefully and let the user re-run
|
||||
to continue from where it left off.
|
||||
The pipeline catches this, waits retry_after seconds (checking for
|
||||
cancellation), then retries. retry_after defaults to 65s if unknown.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, retry_after: int = 65) -> None:
|
||||
super().__init__(message)
|
||||
self.retry_after = retry_after
|
||||
|
||||
|
||||
class LLMProvider(ABC):
|
||||
"""Abstract LLM interface for physics estimation and plausibility review."""
|
||||
|
||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import math
|
||||
|
||||
from physcom.llm.base import LLMProvider, LLMRateLimitError
|
||||
from physcom.llm.prompts import PHYSICS_ESTIMATION_PROMPT, PLAUSIBILITY_REVIEW_PROMPT
|
||||
@@ -35,7 +36,7 @@ class GeminiLLMProvider(LLMProvider):
|
||||
)
|
||||
except Exception as exc:
|
||||
if "429" in str(exc) or "RESOURCE_EXHAUSTED" in str(exc):
|
||||
raise LLMRateLimitError(str(exc)) from exc
|
||||
raise LLMRateLimitError(str(exc), self._parse_retry_after(exc)) from exc
|
||||
raise
|
||||
return self._parse_json(response.text, metrics)
|
||||
|
||||
@@ -53,10 +54,15 @@ class GeminiLLMProvider(LLMProvider):
|
||||
)
|
||||
except Exception as exc:
|
||||
if "429" in str(exc) or "RESOURCE_EXHAUSTED" in str(exc):
|
||||
raise LLMRateLimitError(str(exc)) from exc
|
||||
raise LLMRateLimitError(str(exc), self._parse_retry_after(exc)) from exc
|
||||
raise
|
||||
return response.text.strip()
|
||||
|
||||
def _parse_retry_after(self, exc: Exception) -> int:
|
||||
"""Extract retry delay from the error message, with a safe default."""
|
||||
m = re.search(r"retry in (\d+(?:\.\d+)?)", str(exc))
|
||||
return math.ceil(float(m.group(1))) + 5 if m else 65
|
||||
|
||||
def _parse_json(self, text: str, metrics: list[str]) -> dict[str, float]:
|
||||
"""Strip markdown fences and parse JSON; fall back to 0.5 per metric on error."""
|
||||
text = re.sub(r"```(?:json)?\s*", "", text).strip().rstrip("`").strip()
|
||||
|
||||
Reference in New Issue
Block a user