Rename: _stub_estimate -> _estimate_physics (it's a real deterministic physics engine now, not a stub) and estimation_method "stub" -> "physics_calc" to match the value pass 3 already used, for consistency between the raw-estimate and scored-metric tables. Also skip the cargo_capacity/cargo_capacity_kg arithmetic entirely in _raw_physics_from_masses for domains that score neither and don't need it as cost_efficiency's $/(kg·m) denominator either -- real but modest savings on the ~11,000-eval-per-combo optimizer hot path (a separate log1p-caching attempt was tried and reverted: it measured SLOWER, not faster -- the extra dict lookup cost more than the two math.log1p calls it avoided). Six bugs found by a full-codebase review agent, verified individually: - pipeline.py: LLM rate-limit retry called review_plausibility() with domain.metric_bounds instead of domain, crashing the whole pipeline run on any retry (every provider immediately accesses domain.name/ .metric_bounds on that arg). - _explore_result.html: mass-bar width divided by total_mass with no zero guard; biological/ambient actuators can legitimately have 0 mass floors, so an all-zero slider combination 500'd the explore endpoint. - routes/pipeline.py: if init_db/Repository(conn) raised before repo/conn were assigned, the except/finally handlers referencing them raised UnboundLocalError, silently swallowed by bare except/pass -- a bad PHYSCOM_DB path left a run stuck at status=pending forever with no diagnostic. conn/repo now init to None and are guarded before use; the truly-unreachable-DB case at least logs server-side now. - repository.py: update_combination_status's downgrade guard protected scored/llm_reviewed/*_fail but not a write of "valid" -- pass 1 re-running for a different domain against an already-reviewed combo silently reverted its status back to "valid", erasing the review signal. Verified directly: marked a combo reviewed, re-ran pass 1, status held. - pipeline.py: cost_efficiency's operating-cost term fell back to ground rolling-resistance physics (effective_k_med or ...["ground"]) for media with no resistance model (space), instead of skipping the term the way range_fuel explicitly does two lines above. Every scored interplanetary_travel combo got a cost_efficiency computed from ground physics applied to a spacecraft. Now reports amortized/upfront cost only for such media -- an honest partial answer. - pipeline.py: `if min_accel and specific_thrust:` used truthiness instead of `is not None` -- dep_value() legitimately returns 0.0 for a declared floor of zero (Spaceship declares min_effective_accel=0), masking a real requirement as "undeclared." Three seed-data guardrail holes, matching LOGIC DOCS/002's "missing floor is a silent hole" pattern: - constraint_resolver.py: CATEGORY_SEVERITY had no entry for the "material" category, so Nuclear Thermal Drive/Nuclear Fuel's radiation_shielding requirement defaulted to a non-blocking "warn" nothing in the catalog ever satisfies. Added material -> block. Consequence, verified: every nuclear combo across all domains now correctly fails pass 1, since nothing currently provides shielding -- the accurate state given the catalog gap, not a regression. - transport_example.py: Submarine had a mass range_min but no range_max, unlike its sibling water platform -- _decide_masses skips its entire structural-feasibility search when p_max is None. Added a 20,000,000kg ceiling (small submersible to large ballistic-missile class). - transport_example.py: Amphibious Vehicle declared no medium requires at all, so it vacuously satisfied every domain's medium constraint including space-only interplanetary_travel. Added medium=ground (the current requires model has no OR semantics for "ground or water," so this is a real tradeoff -- it can no longer participate in maritime_shipping either, losing the water half of "amphibious"). Verified: interplanetary_travel's pass-2-estimated count dropped from 33 to 3, and all 3 remaining are genuinely Spaceship-based; the ~30 removed were confirmed to be Amphibious Vehicle's vacuous passes. Logged the GPU-batching-for-the-optimizer discussion (why it doesn't fit at current scale, what threshold would change that, what it would actually require) as LOGIC DOCS/003 for future reference. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6.1 KiB
6.1 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this project is
PhysCom (Physical Combinatorics) — innovation discovery engine that generates entity combinations across dimensions (e.g. platform × power_source), filters by physical constraints, scores against domain-specific metrics, and ranks results. Includes a CLI, a Flask/HTMX web UI, and a 5-pass pipeline (constraints → estimation → scoring → LLM review → human review).
Commands
- Install:
pip install -e ".[dev,web]"(editable install with test and web deps) - Tests (all):
python -m pytest tests/ -q(48 tests, ~5s). Run after every change. - Single test file:
python -m pytest tests/test_scorer.py -q - Single test:
python -m pytest tests/test_scorer.py::test_score_combination -q - Web dev server:
python -m physcom_web - CLI:
python -m physcom(orphyscomif installed) - Docker:
docker compose up web/docker compose run cli physcom seed - Seed data: loaded automatically on first DB init (SQLite,
physcom.dbor$PHYSCOM_DB)
Architecture
src/physcom/ # Core library (no web dependency)
models/ # Dataclasses: Entity, Dependency, Combination, Domain, MetricBound
db/schema.py # DDL (all CREATE TABLE statements)
db/repository.py # All DB access — single Repository class, sqlite3 row_factory=Row
engine/combinator.py # Cartesian product of entities across dimensions
engine/constraint_resolver.py # Pass 1: requires/excludes/mutex/range/force checks
engine/scorer.py # Pass 3: log-normalize raw→0-1, weighted geometric mean composite
engine/pipeline.py # Orchestrator: combo-first loop, incremental saves, resume, cancel
llm/base.py # LLMProvider ABC (estimate_physics, review_plausibility)
llm/providers/mock.py # MockLLMProvider for tests
seed/transport_example.py # 9 platforms + 9 power sources, 2 domains
src/physcom_web/ # Flask web UI
app.py # App factory, get_repo(), DB path resolution
routes/pipeline.py # Background thread pipeline execution, HTMX status/cancel endpoints
routes/results.py # Results browse, detail view, human review submission
routes/entities.py # Entity CRUD
routes/domains.py # Domain listing
templates/ # Jinja2, extends base.html, uses HTMX for polling
static/style.css # Single stylesheet
tests/ # pytest, uses seeded_repo fixture from conftest.py
Key patterns
- Guardrails are emergent, not authored. Entities never reference each other by name. Each declares only its own intrinsic physical envelope (
requires/provides/excludesfor categorical fit,range_min/range_maxfor numeric floors/ceilings on shared keys likemass,footprint,energy_density).ConstraintResolvercross-checks these generically across every pair in a combo — a nuclear reactor is blocked from a bicycle becausemass range_mincollides withmass range_max, not because any code says "nuclear + bicycle = blocked." When adding physical realism to an entity, add a property to the entity — never a pairwise special case inConstraintResolver. A category missing a floor it should have is a silent guardrail hole (blocks nothing, looks fine). SeeLOGIC DOCS/002-guardrails-via-intrinsic-properties.md. - Repository is the only DB interface. No raw SQL outside
repository.py. - Pipeline is combo-first: each combo goes through all requested passes before the next combo starts. Progress is persisted per-combo (crash-safe, resumable).
pipeline_runstable tracks run lifecycle: pending → running → completed/failed/cancelled. The web route creates the record, then starts a background thread with its ownsqlite3.Connection.combination_resultshas rows for ALL combos including blocked ones (pass_reached=1, composite_score=0.0). Scored combos get pass_reached=3+.- Status guard:
update_combination_statusrefuses to downgradereviewed→scored. save_combinationloads existing status/block_reason on dedup (important for resume).ensure_metricbackfills unit if the row already exists with an empty unit.- MetricBound carries
unit— flows through seed → ensure_metric → metrics table → get_combination_scores → template display. - HTMX polling:
_run_status.htmlpartial polls every 2s while run is pending/running; stops polling when terminal.
Data flow (pipeline passes)
- Pass 1 — Constraints:
ConstraintResolver.resolve()→ blocked/conditional/valid. Blocked combos get a result row andcontinue. - Pass 2 — Estimation:
_estimate_physics()(deterministic physics engine; estimator-only, no LLM) → raw metric values. Saved immediately viasave_raw_estimates()(normalized_score=NULL). - Pass 3 — Scoring:
Scorer.score_combination()→ log-normalized scores + weighted geometric mean composite. Saves viasave_scores()+save_result(). - Pass 4 — LLM Review: Only for above-threshold combos with an LLM provider. No real provider yet (only
MockLLMProvider). - Pass 5 — Human Review: Manual via web UI results page.
Testing
- Tests use
seeded_repofixture (in-memory SQLite with transport seed data: 9 platforms, 9 power sources, 2 domains). There's also a barerepofixture for tests that seed their own data. - Individual entity fixtures (walking, bicycle, spaceship, solar_sail, etc.) are defined in
conftest.py.
Conventions
- Python 3.11+,
from __future__ import annotationseverywhere. - Dataclasses for models, no ORM.
- Don't use
cdin Bash commands — run from the working directory so pre-approved permission patterns match. - Don't add docstrings/comments/type annotations to code you didn't change.
INSERT OR IGNOREwon't update existing rows — if adding a new column/field to seed data, also add an UPDATE for backfill.- Jinja2
0.0is falsy — useis not nonenotif valuewhen displaying scores that can legitimately be zero.