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:
@@ -42,6 +42,7 @@ class ConstraintResolver:
|
||||
self._check_mutual_exclusion(all_deps, result)
|
||||
self._check_range_incompatibility(all_deps, result)
|
||||
self._check_force_scale(combination, result)
|
||||
self._check_energy_density(combination, result)
|
||||
self._check_unmet_requirements(all_deps, result)
|
||||
|
||||
if result.violations:
|
||||
@@ -155,6 +156,40 @@ class ConstraintResolver:
|
||||
f"(under-powered)"
|
||||
)
|
||||
|
||||
def _check_energy_density(
|
||||
self, combination: Combination, result: ConstraintResult
|
||||
) -> None:
|
||||
"""Rule 6: If power source energy density << platform minimum → warn/block.
|
||||
|
||||
Uses a 25% threshold: below 25% of required → hard block (> 4x deficit).
|
||||
"""
|
||||
density_provided: list[tuple[str, float]] = []
|
||||
density_required: list[tuple[str, float]] = []
|
||||
|
||||
for entity in combination.entities:
|
||||
for dep in entity.dependencies:
|
||||
if dep.key == "energy_density_wh_kg" and dep.constraint_type == "provides":
|
||||
density_provided.append((entity.name, float(dep.value)))
|
||||
elif dep.key == "energy_density_wh_kg" and dep.constraint_type == "range_min":
|
||||
density_required.append((entity.name, float(dep.value)))
|
||||
|
||||
for req_name, req_density in density_required:
|
||||
if not density_provided:
|
||||
continue # No stored energy source in this combo — skip check
|
||||
for prov_name, prov_density in density_provided:
|
||||
if prov_density < req_density * 0.25:
|
||||
result.violations.append(
|
||||
f"{prov_name} provides {prov_density:.0f} Wh/kg but "
|
||||
f"{req_name} requires {req_density:.0f} Wh/kg "
|
||||
f"(energy density deficit > 4x)"
|
||||
)
|
||||
elif prov_density < req_density:
|
||||
result.warnings.append(
|
||||
f"{prov_name} provides {prov_density:.0f} Wh/kg but "
|
||||
f"{req_name} requires {req_density:.0f} Wh/kg "
|
||||
f"(under-density)"
|
||||
)
|
||||
|
||||
def _check_unmet_requirements(
|
||||
self, all_deps: list[tuple[str, Dependency]], result: ConstraintResult
|
||||
) -> None:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
|
||||
@@ -293,22 +294,36 @@ class Pipeline:
|
||||
for s in db_scores
|
||||
if s["normalized_score"] is not None
|
||||
}
|
||||
review = self.llm.review_plausibility(
|
||||
description, score_dict
|
||||
)
|
||||
self.repo.save_result(
|
||||
combo.id,
|
||||
domain.id,
|
||||
cur_result["composite_score"],
|
||||
pass_reached=4,
|
||||
novelty_flag=cur_result.get("novelty_flag"),
|
||||
llm_review=review,
|
||||
human_notes=cur_result.get("human_notes"),
|
||||
)
|
||||
result.pass4_reviewed += 1
|
||||
self._update_run_counters(
|
||||
run_id, result, current_pass=4
|
||||
)
|
||||
review: str | None = None
|
||||
try:
|
||||
review = self.llm.review_plausibility(
|
||||
description, score_dict
|
||||
)
|
||||
except LLMRateLimitError as exc:
|
||||
self._wait_for_rate_limit(run_id, exc.retry_after)
|
||||
try:
|
||||
review = self.llm.review_plausibility(
|
||||
description, score_dict
|
||||
)
|
||||
except LLMRateLimitError:
|
||||
pass # still limited; skip, retry next run
|
||||
if review is not None:
|
||||
self.repo.save_result(
|
||||
combo.id,
|
||||
domain.id,
|
||||
cur_result["composite_score"],
|
||||
pass_reached=4,
|
||||
novelty_flag=cur_result.get("novelty_flag"),
|
||||
llm_review=review,
|
||||
human_notes=cur_result.get("human_notes"),
|
||||
)
|
||||
self.repo.update_combination_status(
|
||||
combo.id, "llm_reviewed"
|
||||
)
|
||||
result.pass4_reviewed += 1
|
||||
self._update_run_counters(
|
||||
run_id, result, current_pass=4
|
||||
)
|
||||
|
||||
except CancelledError:
|
||||
if run_id is not None:
|
||||
@@ -319,17 +334,6 @@ class Pipeline:
|
||||
)
|
||||
result.top_results = self.repo.get_top_results(domain.name, limit=20)
|
||||
return result
|
||||
except LLMRateLimitError:
|
||||
# Rate limit hit — save progress and let the user re-run to continue.
|
||||
# Already-reviewed combos are persisted; resumability skips them next time.
|
||||
if run_id is not None:
|
||||
self.repo.update_pipeline_run(
|
||||
run_id,
|
||||
status="completed",
|
||||
completed_at=datetime.now(timezone.utc).isoformat(),
|
||||
)
|
||||
result.top_results = self.repo.get_top_results(domain.name, limit=20)
|
||||
return result
|
||||
|
||||
# Mark run as completed
|
||||
if run_id is not None:
|
||||
@@ -342,6 +346,18 @@ class Pipeline:
|
||||
result.top_results = self.repo.get_top_results(domain.name, limit=20)
|
||||
return result
|
||||
|
||||
def _wait_for_rate_limit(self, run_id: int | None, retry_after: int) -> None:
|
||||
"""Mark run rate_limited, sleep with cancel checks, then resume."""
|
||||
if run_id is not None:
|
||||
self.repo.update_pipeline_run(run_id, status="rate_limited")
|
||||
waited = 0
|
||||
while waited < retry_after:
|
||||
time.sleep(5)
|
||||
waited += 5
|
||||
self._check_cancelled(run_id)
|
||||
if run_id is not None:
|
||||
self.repo.update_pipeline_run(run_id, status="running")
|
||||
|
||||
def _stub_estimate(
|
||||
self, combo: Combination, metric_names: list[str]
|
||||
) -> dict[str, float]:
|
||||
|
||||
Reference in New Issue
Block a user