close guardrail gaps and fix the scoring pipeline top to bottom

Constraint resolver: aggregate mass/footprint across a combo instead of
pairwise-only checks, treat medium/atmosphere as agreement not supply/demand,
reduce multi-provider checks by best/sum instead of AND-ing every provider,
fail closed on unrecognized mutex values, add a propulsion-viability
(thrust-to-weight) rule. Seed data updated to match (nuclear/solar-sail
footprint floors, water-medium exclusions, explicit ground/gravity providers).

Domain metric units were stored globally per metric name instead of
per-domain, silently corrupting cost_efficiency for every domain but the
first one seeded — fixed with a schema migration.

Stub estimator's cost_efficiency/safety/availability/reliability were a
backwards formula and flat constants; replaced with heuristics grounded in
each entity's thrust_profile/energy_form/infrastructure.

LLM estimate_physics() now receives each metric's unit and expected range
instead of a bare name, fixing wildly miscalibrated estimates traced back to
the prompt's own hardcoded example anchoring the model to the wrong order of
magnitude. Sharpened the safety-estimation and plausibility-review prompts.
Deduped provider parsing logic into llm/parsing.py.

Web pipeline form can now pick an LLM provider per run instead of only via
server env var.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-26 00:13:16 -05:00
parent 63295ab80e
commit 434df718d7
18 changed files with 836 additions and 175 deletions

View File

@@ -35,7 +35,9 @@ def road_vehicle():
description="Generic wheeled road vehicle",
dependencies=[
Dependency("environment", "ground_surface", "true", None, "requires"),
Dependency("environment", "ground_surface", "true", None, "provides"),
Dependency("environment", "gravity", "true", None, "requires"),
Dependency("environment", "gravity", "true", None, "provides"),
Dependency("physical", "mass", "36000", "kg", "range_max"),
Dependency("physical", "mass", "50", "kg", "range_min"),
Dependency("environment", "medium", "ground", None, "requires"),
@@ -51,7 +53,9 @@ def bicycle():
description="Two-wheeled human-scale vehicle",
dependencies=[
Dependency("environment", "ground_surface", "true", None, "requires"),
Dependency("environment", "ground_surface", "true", None, "provides"),
Dependency("environment", "gravity", "true", None, "requires"),
Dependency("environment", "gravity", "true", None, "provides"),
Dependency("physical", "mass", "30", "kg", "range_max"),
Dependency("environment", "medium", "ground", None, "requires"),
],

View File

@@ -160,3 +160,231 @@ def test_domain_constraint_allows_matching_medium(bicycle, human_pedalling, food
constraints = [DomainConstraint("medium", ["ground", "air"])]
result = resolver.check_domain_constraints(combo, constraints)
assert result.status == "valid"
def _rotorcraft():
return Entity(
name="Rotorcraft", dimension="platform",
dependencies=[
Dependency("physical", "footprint", "20", "", "range_max"),
Dependency("physical", "footprint", "0.5", "", "range_min"),
Dependency("physical", "mass", "5000", "kg", "range_max"),
Dependency("physical", "mass", "1", "kg", "range_min"),
],
)
def _spaceship_with_footprint():
return Entity(
name="Spaceship", dimension="platform",
dependencies=[
Dependency("physical", "footprint", "500", "", "range_max"),
Dependency("physical", "footprint", "10", "", "range_min"),
Dependency("physical", "mass", "5000", "kg", "range_min"),
],
)
def _nuclear_thermal_drive_with_footprint():
return Entity(
name="Nuclear Thermal Drive", dimension="actuator",
dependencies=[
Dependency("physical", "footprint", "20", "", "range_min"),
Dependency("physical", "mass", "1500", "kg", "range_min"),
],
)
def _nuclear_fuel_with_footprint():
return Entity(
name="Nuclear Fuel", dimension="energy_storage",
dependencies=[
Dependency("physical", "footprint", "5", "", "range_min"),
Dependency("physical", "mass", "500", "kg", "range_min"),
],
)
def test_footprint_aggregation_blocks_reactor_on_rotorcraft():
"""P1: individual footprint floors each fit under the ceiling (20, 5 <= 20)
but their sum (25.5) doesn't — must block even though no single component
exceeds the ceiling on its own."""
resolver = ConstraintResolver()
combo = Combination(entities=[
_rotorcraft(), _nuclear_thermal_drive_with_footprint(), _nuclear_fuel_with_footprint(),
])
result = resolver.resolve(combo)
assert result.status == "p1_fail"
assert any("combined footprint" in v for v in result.violations)
def test_footprint_aggregation_still_passes_spaceship():
"""Same reactor + fuel, but a platform with enough footprint budget (500 m²)
must still pass — aggregation shouldn't over-block combos with real headroom."""
resolver = ConstraintResolver()
combo = Combination(entities=[
_spaceship_with_footprint(), _nuclear_thermal_drive_with_footprint(), _nuclear_fuel_with_footprint(),
])
result = resolver.resolve(combo)
assert result.status != "p1_fail"
assert not any("footprint" in v for v in result.violations)
def test_mass_aggregation_within_tolerance_warns_not_blocks():
"""Sum only slightly over the ceiling (65 vs 60, +8.3%) is a data-calibration
signal, not a hard physical impossibility — should warn, not block."""
platform = Entity(
name="Light Personal Vehicle", dimension="platform",
dependencies=[
Dependency("physical", "mass", "60", "kg", "range_max"),
Dependency("physical", "mass", "5", "kg", "range_min"),
],
)
actuator = Entity(
name="Piston Engine", dimension="actuator",
dependencies=[Dependency("physical", "mass", "45", "kg", "range_min")],
)
storage = Entity(
name="Compressed Natural Gas", dimension="energy_storage",
dependencies=[Dependency("physical", "mass", "15", "kg", "range_min")],
)
resolver = ConstraintResolver()
result = resolver.resolve(Combination(entities=[platform, actuator, storage]))
assert result.status == "conditional"
assert any("combined mass" in w for w in result.warnings)
def test_weak_secondary_provider_does_not_block_satisfied_requirement():
"""P3: a strong provider (nuclear fuel) already satisfies the requirement;
a weak secondary provider (solar panel) in the same combo must not
retroactively block it — a real backup power source shouldn't break a
vehicle that already has enough primary power."""
platform = Entity(
name="Spaceship", dimension="platform",
dependencies=[Dependency("physical", "energy_density", "7200000", "J/kg", "range_min")],
)
nuclear_fuel = Entity(
name="Nuclear Fuel", dimension="energy_storage",
dependencies=[Dependency("physical", "energy_density", "1800000000", "J/kg", "provides")],
)
solar_panel = Entity(
name="Solar Photovoltaic Panel", dimension="energy_storage",
dependencies=[Dependency("physical", "energy_density", "180000", "J/kg", "provides")],
)
resolver = ConstraintResolver()
result = resolver.resolve(Combination(entities=[platform, nuclear_fuel, solar_panel]))
assert result.status != "p1_fail"
assert not any("energy_density" in v for v in result.violations)
def test_unrecognized_mutex_value_fails_closed():
"""P4: a value not in any registered mutex set (e.g. a new 'medium' typed
into the admin UI) must conflict with a recognized value on the same key,
not silently pass."""
a = Entity(
name="A", dimension="platform",
dependencies=[Dependency("environment", "medium", "underground", None, "requires")],
)
b = Entity(
name="B", dimension="actuator",
dependencies=[Dependency("environment", "medium", "space", None, "requires")],
)
resolver = ConstraintResolver()
result = resolver.resolve(Combination(entities=[a, b]))
assert result.status == "p1_fail"
assert any("mutually exclusive" in v for v in result.violations)
def test_propulsion_viability_blocks_weak_actuator_regardless_of_scale():
"""G4: specific_thrust below min_effective_accel can never be fixed by
adding more actuator mass — must block unconditionally (Case 1)."""
platform = Entity(
name="Rotorcraft", dimension="platform",
dependencies=[
Dependency("physical", "mass", "5000", "kg", "range_max"),
Dependency("physical", "min_effective_accel", "10", "m/s²", "range_min"),
],
)
actuator = Entity(
name="Ion Drive", dimension="actuator",
dependencies=[
Dependency("physical", "mass", "8", "kg", "range_min"),
Dependency("force", "specific_thrust", "0.01", "N/kg", "provides"),
],
)
resolver = ConstraintResolver()
result = resolver.resolve(Combination(entities=[platform, actuator]))
assert result.status == "p1_fail"
assert any("regardless of scale" in v for v in result.violations)
def test_propulsion_viability_blocks_when_required_mass_exceeds_ceiling():
"""G4 Case 2: specific_thrust clears min_effective_accel, but the mass
needed to hit that thrust doesn't fit the vehicle's mass budget."""
platform = Entity(
name="Test Platform", dimension="platform",
dependencies=[
Dependency("physical", "mass", "50", "kg", "range_max"),
Dependency("physical", "mass", "10", "kg", "range_min"),
Dependency("physical", "min_effective_accel", "5", "m/s²", "range_min"),
],
)
actuator = Entity(
name="Weak Reaction Drive", dimension="actuator",
dependencies=[
Dependency("physical", "mass", "1", "kg", "range_min"),
Dependency("force", "specific_thrust", "6", "N/kg", "provides"),
],
)
storage = Entity(
name="Fuel", dimension="energy_storage",
dependencies=[Dependency("physical", "mass", "1", "kg", "range_min")],
)
resolver = ConstraintResolver()
result = resolver.resolve(Combination(entities=[platform, actuator, storage]))
assert result.status == "p1_fail"
assert any("would need >=" in v for v in result.violations)
def test_propulsion_viability_passes_with_enough_budget():
"""Same shape as above but with a generous mass ceiling — must pass."""
platform = Entity(
name="Test Platform", dimension="platform",
dependencies=[
Dependency("physical", "mass", "5000", "kg", "range_max"),
Dependency("physical", "mass", "10", "kg", "range_min"),
Dependency("physical", "min_effective_accel", "5", "m/s²", "range_min"),
],
)
actuator = Entity(
name="Weak Reaction Drive", dimension="actuator",
dependencies=[
Dependency("physical", "mass", "1", "kg", "range_min"),
Dependency("force", "specific_thrust", "6", "N/kg", "provides"),
],
)
storage = Entity(
name="Fuel", dimension="energy_storage",
dependencies=[Dependency("physical", "mass", "1", "kg", "range_min")],
)
resolver = ConstraintResolver()
result = resolver.resolve(Combination(entities=[platform, actuator, storage]))
assert not any("specific thrust" in v or "would need" in v for v in result.violations)
def test_propulsion_viability_skips_when_undeclared(bicycle, human_pedalling, food_calories):
"""Platforms/actuators that never declare min_effective_accel or
specific_thrust (most of the catalog, for now) must be unaffected."""
resolver = ConstraintResolver()
result = resolver.resolve(Combination(entities=[bicycle, human_pedalling, food_calories]))
assert not any("specific thrust" in v or "would need" in v for v in result.violations)
def test_agreement_key_reaches_valid_status(bicycle, human_pedalling, food_calories):
"""P2: medium/atmosphere are agreement keys, not supply/demand — a combo
with no other issues should reach 'valid', not get stuck at 'conditional'
forever because nothing 'provides' medium=ground."""
resolver = ConstraintResolver()
result = resolver.resolve(Combination(entities=[bicycle, human_pedalling, food_calories]))
assert result.status == "valid"
assert not any("medium" in w for w in result.warnings)

32
tests/test_llm_parsing.py Normal file
View File

@@ -0,0 +1,32 @@
"""Tests for shared LLM response-parsing logic."""
from __future__ import annotations
from physcom.llm.parsing import parse_metric_json, parse_verdict
from physcom.models.domain import MetricBound
def _bounds():
return [
MetricBound("power_density", weight=0.5, norm_min=1, norm_max=2000, unit="W/kg"),
MetricBound("safety", weight=0.5, norm_min=0.0, norm_max=1.0, unit="0-1"),
]
def test_parse_metric_json_strips_fences():
text = '```json\n{"power_density": 500.0, "safety": 0.7}\n```'
result = parse_metric_json(text, _bounds())
assert result == {"power_density": 500.0, "safety": 0.7}
def test_parse_metric_json_falls_back_to_range_midpoint_on_invalid():
result = parse_metric_json("not json", _bounds())
assert result == {"power_density": 1000.5, "safety": 0.5}
def test_parse_verdict_plausible():
assert parse_verdict("blah blah\nVERDICT: PLAUSIBLE") is True
def test_parse_verdict_implausible():
assert parse_verdict("blah blah\nVERDICT: IMPLAUSIBLE") is False

View File

@@ -1,36 +1,10 @@
"""Tests for the Ollama provider's parsing logic and registry wiring."""
"""Tests for the Ollama provider's registry wiring."""
from __future__ import annotations
import pytest
from physcom.llm.providers.ollama import OllamaLLMProvider
@pytest.fixture
def provider():
return OllamaLLMProvider()
def test_parse_json_strips_fences(provider):
text = '```json\n{"power_density": 500.0, "safety": 0.7}\n```'
result = provider._parse_json(text, ["power_density", "safety"])
assert result == {"power_density": 500.0, "safety": 0.7}
def test_parse_json_falls_back_on_invalid(provider):
result = provider._parse_json("not json", ["power_density", "safety"])
assert result == {"power_density": 0.5, "safety": 0.5}
def test_parse_verdict_plausible(provider):
assert provider._parse_verdict("blah blah\nVERDICT: PLAUSIBLE") is True
def test_parse_verdict_implausible(provider):
assert provider._parse_verdict("blah blah\nVERDICT: IMPLAUSIBLE") is False
def test_registry_builds_ollama_provider(monkeypatch):
from physcom.llm.registry import build_llm_provider