diff --git a/src/physcom/db/repository.py b/src/physcom/db/repository.py index 1a33af0..c1f937c 100644 --- a/src/physcom/db/repository.py +++ b/src/physcom/db/repository.py @@ -153,6 +153,35 @@ class Repository: self.conn.commit() return dep + def replace_entity_dependencies(self, entity_id: int, deps: list[Dependency]) -> None: + """Delete all existing dependencies for an entity and insert new ones.""" + self.conn.execute("DELETE FROM dependencies WHERE entity_id = ?", (entity_id,)) + for dep in deps: + cur = self.conn.execute( + """INSERT INTO dependencies + (entity_id, category, key, value, unit, constraint_type) + VALUES (?, ?, ?, ?, ?, ?)""", + (entity_id, dep.category, dep.key, dep.value, dep.unit, dep.constraint_type), + ) + dep.id = cur.lastrowid + self.conn.commit() + + def get_entity_by_name(self, dimension: str, name: str) -> Entity | None: + row = self.conn.execute( + """SELECT e.id, e.name, e.description, d.name as dimension, e.dimension_id + FROM entities e JOIN dimensions d ON e.dimension_id = d.id + WHERE d.name = ? AND e.name = ?""", + (dimension, name), + ).fetchone() + if not row: + return None + deps = self._load_dependencies(row["id"]) + return Entity( + id=row["id"], name=row["name"], description=row["description"] or "", + dimension=row["dimension"], dimension_id=row["dimension_id"], + dependencies=deps, + ) + def update_dependency(self, dep_id: int, dep: Dependency) -> None: self.conn.execute( """UPDATE dependencies @@ -772,3 +801,20 @@ class Repository: (combo_id, domain_id), ).fetchone() return dict(row) if row else None + + # ── Admin ──────────────────────────────────────────────────── + + def clear_all(self) -> None: + """Delete all data from every table in FK-safe order.""" + self.conn.execute("DELETE FROM pipeline_runs") + self.conn.execute("DELETE FROM combination_results") + self.conn.execute("DELETE FROM combination_scores") + self.conn.execute("DELETE FROM combination_entities") + self.conn.execute("DELETE FROM combinations") + self.conn.execute("DELETE FROM dependencies") + self.conn.execute("DELETE FROM entities") + self.conn.execute("DELETE FROM domain_metric_weights") + self.conn.execute("DELETE FROM domains") + self.conn.execute("DELETE FROM metrics") + self.conn.execute("DELETE FROM dimensions") + self.conn.commit() diff --git a/src/physcom/engine/constraint_resolver.py b/src/physcom/engine/constraint_resolver.py index 6eaef07..fc7a110 100644 --- a/src/physcom/engine/constraint_resolver.py +++ b/src/physcom/engine/constraint_resolver.py @@ -41,7 +41,7 @@ class ConstraintResolver: self._check_requires_vs_excludes(all_deps, result) self._check_mutual_exclusion(all_deps, result) self._check_range_incompatibility(all_deps, result) - self._check_force_scale(combination, result) + self._check_power_density(combination, result) self._check_energy_density(combination, result) self._check_unmet_requirements(all_deps, result) @@ -125,34 +125,32 @@ class ConstraintResolver: f"but {max_name} limits {key} <= {max_val}" ) - def _check_force_scale( + def _check_power_density( self, combination: Combination, result: ConstraintResult ) -> None: - """Rule 4: If power source output << platform requirement → warn/block.""" - force_provided: list[tuple[str, float]] = [] - force_required: list[tuple[str, float]] = [] + """Rule 4: If power source W/kg << platform required W/kg → warn/block.""" + 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 == "force_output_watts" and dep.constraint_type == "provides": - force_provided.append((entity.name, float(dep.value))) - elif dep.key == "force_required_watts" and dep.constraint_type == "range_min": - force_required.append((entity.name, float(dep.value))) + if dep.key == "power_density_w_kg" and dep.constraint_type == "provides": + density_provided.append((entity.name, float(dep.value))) + elif dep.key == "power_density_required_w_kg" and dep.constraint_type == "range_min": + density_required.append((entity.name, float(dep.value))) - for req_name, req_watts in force_required: - for prov_name, prov_watts in force_provided: - if prov_watts < req_watts * 0.01: - # Off by more than 100x — hard block + for req_name, req_density in density_required: + for prov_name, prov_density in density_provided: + if prov_density < req_density * 0.01: result.violations.append( - f"{prov_name} provides {prov_watts}W but " - f"{req_name} requires {req_watts}W " - f"(force deficit > 100x)" + f"{prov_name} provides {prov_density} W/kg but " + f"{req_name} requires {req_density} W/kg " + f"(power density deficit > 100x)" ) - elif prov_watts < req_watts: - # Under-powered but not impossibly so — warn + elif prov_density < req_density: result.warnings.append( - f"{prov_name} provides {prov_watts}W but " - f"{req_name} requires {req_watts}W " + f"{prov_name} provides {prov_density} W/kg but " + f"{req_name} requires {req_density} W/kg " f"(under-powered)" ) diff --git a/src/physcom/engine/pipeline.py b/src/physcom/engine/pipeline.py index 296a73e..a201dde 100644 --- a/src/physcom/engine/pipeline.py +++ b/src/physcom/engine/pipeline.py @@ -417,22 +417,25 @@ class Pipeline: """Simple heuristic estimation from dependency data.""" raw: dict[str, float] = {m: 0.0 for m in metric_names} - # Extract force output from power source - force_watts = 0.0 + # Extract intrinsic properties from entities + power_density = 0.0 # W/kg + energy_density = 0.0 # Wh/kg mass_kg = 100.0 # default for entity in combo.entities: for dep in entity.dependencies: - if dep.key == "force_output_watts" and dep.constraint_type == "provides": - force_watts = max(force_watts, float(dep.value)) - if dep.key == "min_mass_kg" and dep.constraint_type == "range_min": + if dep.key == "power_density_w_kg" and dep.constraint_type == "provides": + power_density = max(power_density, float(dep.value)) + if dep.key == "energy_density_wh_kg" and dep.constraint_type == "provides": + energy_density = max(energy_density, float(dep.value)) + if dep.key == "mass_kg" and dep.constraint_type == "range_min": mass_kg = max(mass_kg, float(dep.value)) - # Rough speed estimate: F=ma -> v proportional to power/mass - if "speed" in raw and mass_kg > 0: - raw["speed"] = min(force_watts / mass_kg * 0.5, 300000) + # Rough speed estimate: higher power density → faster + if "speed" in raw: + raw["speed"] = min(power_density * 0.5, 300000) if "cost_efficiency" in raw: - raw["cost_efficiency"] = max(0.01, 2.0 - force_watts / 100000) + raw["cost_efficiency"] = max(0.01, 2.0 - power_density / 1000) if "safety" in raw: raw["safety"] = 0.5 @@ -441,9 +444,21 @@ class Pipeline: raw["availability"] = 0.5 if "range_fuel" in raw: - raw["range_fuel"] = min(force_watts * 0.01, 1e10) + raw["range_fuel"] = min(energy_density * 10, 1e10) if "range_degradation" in raw: raw["range_degradation"] = 365 + if "cargo_capacity" in raw: + raw["cargo_capacity"] = mass_kg * 0.5 + + if "cargo_capacity_kg" in raw: + raw["cargo_capacity_kg"] = mass_kg * 0.3 + + if "environmental_impact" in raw: + raw["environmental_impact"] = max(0.0, power_density * 0.2) + + if "reliability" in raw: + raw["reliability"] = 0.5 + return raw diff --git a/src/physcom/seed/transport_example.py b/src/physcom/seed/transport_example.py index 339c29f..70d118f 100644 --- a/src/physcom/seed/transport_example.py +++ b/src/physcom/seed/transport_example.py @@ -6,9 +6,9 @@ from physcom.models.entity import Entity, Dependency from physcom.models.domain import Domain, MetricBound -# ── Platforms ─────────────────────────────────────────────────── +# ── Platforms — Ground ────────────────────────────────────────── -PLATFORMS: list[Entity] = [ +GROUND_PLATFORMS: list[Entity] = [ Entity( name="Car", dimension="platform", @@ -18,25 +18,11 @@ PLATFORMS: list[Entity] = [ Dependency("environment", "gravity", "true", None, "requires"), Dependency("physical", "mass_kg", "3000", "kg", "range_max"), Dependency("physical", "mass_kg", "800", "kg", "range_min"), - Dependency("force", "force_required_watts", "15000", "watts", "range_min"), + Dependency("force", "power_density_required_w_kg", "30", "W/kg", "range_min"), Dependency("infrastructure", "road_network", "true", None, "requires"), Dependency("environment", "medium", "ground", None, "requires"), ], ), - Entity( - name="Airplane", - dimension="platform", - description="Fixed-wing aircraft for atmospheric flight", - dependencies=[ - Dependency("environment", "atmosphere", "standard", None, "requires"), - Dependency("environment", "gravity", "true", None, "requires"), - Dependency("physical", "mass_kg", "500", "kg", "range_min"), - Dependency("force", "force_required_watts", "100000", "watts", "range_min"), - Dependency("infrastructure", "runway", "true", None, "requires"), - Dependency("environment", "medium", "air", None, "requires"), - Dependency("physical", "energy_density_wh_kg", "400", "Wh/kg", "range_min"), - ], - ), Entity( name="Train", dimension="platform", @@ -45,7 +31,7 @@ PLATFORMS: list[Entity] = [ Dependency("environment", "ground_surface", "true", None, "requires"), Dependency("environment", "gravity", "true", None, "requires"), Dependency("physical", "mass_kg", "10000", "kg", "range_min"), - Dependency("force", "force_required_watts", "500000", "watts", "range_min"), + Dependency("force", "power_density_required_w_kg", "10", "W/kg", "range_min"), Dependency("infrastructure", "rail_network", "true", None, "requires"), Dependency("environment", "medium", "ground", None, "requires"), ], @@ -58,7 +44,7 @@ PLATFORMS: list[Entity] = [ Dependency("environment", "ground_surface", "true", None, "requires"), Dependency("environment", "gravity", "true", None, "requires"), Dependency("physical", "mass_kg", "30", "kg", "range_max"), - Dependency("force", "force_required_watts", "75", "watts", "range_min"), + Dependency("force", "power_density_required_w_kg", "1", "W/kg", "range_min"), Dependency("infrastructure", "road_network", "true", None, "requires"), Dependency("environment", "medium", "ground", None, "requires"), ], @@ -71,7 +57,7 @@ PLATFORMS: list[Entity] = [ Dependency("environment", "ground_surface", "true", None, "requires"), Dependency("environment", "gravity", "true", None, "requires"), Dependency("physical", "mass_kg", "150", "kg", "range_max"), - Dependency("force", "force_required_watts", "75", "watts", "range_min"), + Dependency("force", "power_density_required_w_kg", "1", "W/kg", "range_min"), Dependency("infrastructure", "fuel_infrastructure", "none", None, "requires"), Dependency("environment", "medium", "ground", None, "requires"), ], @@ -84,7 +70,7 @@ PLATFORMS: list[Entity] = [ Dependency("environment", "ground_surface", "true", None, "requires"), Dependency("environment", "gravity", "true", None, "requires"), Dependency("physical", "mass_kg", "200", "kg", "range_max"), - Dependency("force", "force_required_watts", "50", "watts", "range_min"), + Dependency("force", "power_density_required_w_kg", "1", "W/kg", "range_min"), Dependency("infrastructure", "road_network", "true", None, "requires"), Dependency("environment", "medium", "ground", None, "requires"), ], @@ -97,11 +83,398 @@ PLATFORMS: list[Entity] = [ Dependency("environment", "ground_surface", "true", None, "requires"), Dependency("environment", "gravity", "true", None, "requires"), Dependency("physical", "mass_kg", "50", "kg", "range_max"), - Dependency("force", "force_required_watts", "200", "watts", "range_min"), + Dependency("force", "power_density_required_w_kg", "1", "W/kg", "range_min"), Dependency("infrastructure", "road_network", "true", None, "requires"), Dependency("environment", "medium", "ground", None, "requires"), ], ), + Entity( + name="Motorcycle", + dimension="platform", + description="Two-wheeled motorized road vehicle", + dependencies=[ + Dependency("environment", "ground_surface", "true", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "300", "kg", "range_max"), + Dependency("physical", "mass_kg", "150", "kg", "range_min"), + Dependency("force", "power_density_required_w_kg", "10", "W/kg", "range_min"), + Dependency("infrastructure", "road_network", "true", None, "requires"), + Dependency("environment", "medium", "ground", None, "requires"), + ], + ), + Entity( + name="Bus", + dimension="platform", + description="Large enclosed multi-passenger road vehicle", + dependencies=[ + Dependency("environment", "ground_surface", "true", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "18000", "kg", "range_max"), + Dependency("physical", "mass_kg", "8000", "kg", "range_min"), + Dependency("force", "power_density_required_w_kg", "15", "W/kg", "range_min"), + Dependency("infrastructure", "road_network", "true", None, "requires"), + Dependency("environment", "medium", "ground", None, "requires"), + ], + ), + Entity( + name="Truck", + dimension="platform", + description="Heavy-duty cargo road vehicle", + dependencies=[ + Dependency("environment", "ground_surface", "true", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "36000", "kg", "range_max"), + Dependency("physical", "mass_kg", "5000", "kg", "range_min"), + Dependency("force", "power_density_required_w_kg", "20", "W/kg", "range_min"), + Dependency("infrastructure", "road_network", "true", None, "requires"), + Dependency("environment", "medium", "ground", None, "requires"), + ], + ), + Entity( + name="Horse", + dimension="platform", + description="Equine animal used for riding or pulling loads", + dependencies=[ + Dependency("environment", "ground_surface", "true", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "700", "kg", "range_max"), + Dependency("physical", "mass_kg", "400", "kg", "range_min"), + Dependency("force", "power_density_required_w_kg", "1", "W/kg", "range_min"), + Dependency("environment", "medium", "ground", None, "requires"), + ], + ), + Entity( + name="Skateboard", + dimension="platform", + description="Small board on four wheels for rolling locomotion", + dependencies=[ + Dependency("environment", "ground_surface", "true", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "10", "kg", "range_max"), + Dependency("force", "power_density_required_w_kg", "1", "W/kg", "range_min"), + Dependency("infrastructure", "road_network", "true", None, "requires"), + Dependency("environment", "medium", "ground", None, "requires"), + ], + ), + Entity( + name="Snowmobile", + dimension="platform", + description="Tracked vehicle for travel over snow and ice", + dependencies=[ + Dependency("environment", "ground_surface", "true", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("environment", "terrain", "snow", None, "requires"), + Dependency("physical", "mass_kg", "400", "kg", "range_max"), + Dependency("physical", "mass_kg", "200", "kg", "range_min"), + Dependency("force", "power_density_required_w_kg", "40", "W/kg", "range_min"), + Dependency("environment", "medium", "ground", None, "requires"), + ], + ), + Entity( + name="Tram", + dimension="platform", + description="Rail-guided urban streetcar", + dependencies=[ + Dependency("environment", "ground_surface", "true", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "40000", "kg", "range_max"), + Dependency("physical", "mass_kg", "20000", "kg", "range_min"), + Dependency("force", "power_density_required_w_kg", "10", "W/kg", "range_min"), + Dependency("infrastructure", "rail_network", "true", None, "requires"), + Dependency("infrastructure", "overhead_power", "true", None, "requires"), + Dependency("environment", "medium", "ground", None, "requires"), + ], + ), + Entity( + name="ATV", + dimension="platform", + description="All-terrain vehicle (quad bike) for off-road use", + dependencies=[ + Dependency("environment", "ground_surface", "true", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "500", "kg", "range_max"), + Dependency("physical", "mass_kg", "200", "kg", "range_min"), + Dependency("force", "power_density_required_w_kg", "20", "W/kg", "range_min"), + Dependency("environment", "medium", "ground", None, "requires"), + ], + ), + Entity( + name="Rickshaw", + dimension="platform", + description="Small passenger cart pulled by human or bicycle", + dependencies=[ + Dependency("environment", "ground_surface", "true", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "150", "kg", "range_max"), + Dependency("force", "power_density_required_w_kg", "2", "W/kg", "range_min"), + Dependency("infrastructure", "road_network", "true", None, "requires"), + Dependency("environment", "medium", "ground", None, "requires"), + ], + ), + Entity( + name="Segway", + dimension="platform", + description="Self-balancing two-wheeled personal transporter", + dependencies=[ + Dependency("environment", "ground_surface", "true", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "60", "kg", "range_max"), + Dependency("force", "power_density_required_w_kg", "5", "W/kg", "range_min"), + Dependency("infrastructure", "road_network", "true", None, "requires"), + Dependency("environment", "medium", "ground", None, "requires"), + ], + ), + Entity( + name="Golf Cart", + dimension="platform", + description="Small low-speed utility vehicle", + dependencies=[ + Dependency("environment", "ground_surface", "true", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "500", "kg", "range_max"), + Dependency("physical", "mass_kg", "300", "kg", "range_min"), + Dependency("force", "power_density_required_w_kg", "3", "W/kg", "range_min"), + Dependency("infrastructure", "road_network", "true", None, "requires"), + Dependency("environment", "medium", "ground", None, "requires"), + ], + ), + Entity( + name="Dog Sled", + dimension="platform", + description="Sled pulled by a team of dogs over snow", + dependencies=[ + Dependency("environment", "ground_surface", "true", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("environment", "terrain", "snow", None, "requires"), + Dependency("physical", "mass_kg", "300", "kg", "range_max"), + Dependency("force", "power_density_required_w_kg", "2", "W/kg", "range_min"), + Dependency("environment", "medium", "ground", None, "requires"), + ], + ), + Entity( + name="Maglev Train", + dimension="platform", + description="Magnetically levitated high-speed rail vehicle", + dependencies=[ + Dependency("environment", "ground_surface", "true", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "30000", "kg", "range_min"), + Dependency("force", "power_density_required_w_kg", "50", "W/kg", "range_min"), + Dependency("infrastructure", "maglev_guideway", "true", None, "requires"), + Dependency("environment", "medium", "ground", None, "requires"), + ], + ), +] + + +# ── Platforms — Water ─────────────────────────────────────────── + +WATER_PLATFORMS: list[Entity] = [ + Entity( + name="Sailboat", + dimension="platform", + description="Wind-propelled watercraft with sails", + dependencies=[ + Dependency("environment", "water_surface", "true", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "5000", "kg", "range_max"), + Dependency("physical", "mass_kg", "500", "kg", "range_min"), + Dependency("force", "power_density_required_w_kg", "2", "W/kg", "range_min"), + Dependency("environment", "medium", "water", None, "requires"), + ], + ), + Entity( + name="Motorboat", + dimension="platform", + description="Small powered watercraft with outboard or inboard motor", + dependencies=[ + Dependency("environment", "water_surface", "true", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "3000", "kg", "range_max"), + Dependency("physical", "mass_kg", "500", "kg", "range_min"), + Dependency("force", "power_density_required_w_kg", "30", "W/kg", "range_min"), + Dependency("environment", "medium", "water", None, "requires"), + ], + ), + Entity( + name="Submarine", + dimension="platform", + description="Submersible vessel for underwater travel", + dependencies=[ + Dependency("environment", "water_surface", "true", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "10000", "kg", "range_min"), + Dependency("force", "power_density_required_w_kg", "20", "W/kg", "range_min"), + Dependency("environment", "medium", "water", None, "requires"), + Dependency("physical", "energy_density_wh_kg", "200", "Wh/kg", "range_min"), + ], + ), + Entity( + name="Kayak", + dimension="platform", + description="Small narrow watercraft propelled by a double-bladed paddle", + dependencies=[ + Dependency("environment", "water_surface", "true", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "35", "kg", "range_max"), + Dependency("force", "power_density_required_w_kg", "1", "W/kg", "range_min"), + Dependency("environment", "medium", "water", None, "requires"), + ], + ), + Entity( + name="Ferry", + dimension="platform", + description="Large passenger and vehicle watercraft for short sea crossings", + dependencies=[ + Dependency("environment", "water_surface", "true", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "50000", "kg", "range_min"), + Dependency("force", "power_density_required_w_kg", "10", "W/kg", "range_min"), + Dependency("infrastructure", "port_facility", "true", None, "requires"), + Dependency("environment", "medium", "water", None, "requires"), + ], + ), + Entity( + name="Container Ship", + dimension="platform", + description="Massive ocean-going cargo vessel carrying shipping containers", + dependencies=[ + Dependency("environment", "water_surface", "true", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "100000", "kg", "range_min"), + Dependency("force", "power_density_required_w_kg", "5", "W/kg", "range_min"), + Dependency("infrastructure", "port_facility", "true", None, "requires"), + Dependency("environment", "medium", "water", None, "requires"), + ], + ), + Entity( + name="Canoe", + dimension="platform", + description="Lightweight open watercraft propelled by single-bladed paddles", + dependencies=[ + Dependency("environment", "water_surface", "true", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "40", "kg", "range_max"), + Dependency("force", "power_density_required_w_kg", "1", "W/kg", "range_min"), + Dependency("environment", "medium", "water", None, "requires"), + ], + ), +] + + +# ── Platforms — Air ───────────────────────────────────────────── + +AIR_PLATFORMS: list[Entity] = [ + Entity( + name="Airplane", + dimension="platform", + description="Fixed-wing aircraft for atmospheric flight", + dependencies=[ + Dependency("environment", "atmosphere", "standard", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "500", "kg", "range_min"), + Dependency("force", "power_density_required_w_kg", "100", "W/kg", "range_min"), + Dependency("infrastructure", "runway", "true", None, "requires"), + Dependency("environment", "medium", "air", None, "requires"), + Dependency("physical", "energy_density_wh_kg", "400", "Wh/kg", "range_min"), + ], + ), + Entity( + name="Helicopter", + dimension="platform", + description="Rotary-wing aircraft capable of vertical takeoff and hover", + dependencies=[ + Dependency("environment", "atmosphere", "standard", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "5000", "kg", "range_max"), + Dependency("physical", "mass_kg", "1000", "kg", "range_min"), + Dependency("force", "power_density_required_w_kg", "100", "W/kg", "range_min"), + Dependency("environment", "medium", "air", None, "requires"), + Dependency("physical", "energy_density_wh_kg", "300", "Wh/kg", "range_min"), + ], + ), + Entity( + name="Hot Air Balloon", + dimension="platform", + description="Buoyancy-based aircraft using heated air in a fabric envelope", + dependencies=[ + Dependency("environment", "atmosphere", "standard", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "500", "kg", "range_max"), + Dependency("physical", "mass_kg", "200", "kg", "range_min"), + Dependency("force", "power_density_required_w_kg", "5", "W/kg", "range_min"), + Dependency("environment", "medium", "air", None, "requires"), + ], + ), + Entity( + name="Glider", + dimension="platform", + description="Unpowered fixed-wing sailplane relying on thermals and gravity", + dependencies=[ + Dependency("environment", "atmosphere", "standard", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "600", "kg", "range_max"), + Dependency("physical", "mass_kg", "200", "kg", "range_min"), + Dependency("force", "power_density_required_w_kg", "1", "W/kg", "range_min"), + Dependency("infrastructure", "tow_or_winch", "true", None, "requires"), + Dependency("environment", "medium", "air", None, "requires"), + ], + ), + Entity( + name="Drone", + dimension="platform", + description="Small unmanned rotary-wing aircraft (quadcopter)", + dependencies=[ + Dependency("environment", "atmosphere", "standard", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "25", "kg", "range_max"), + Dependency("force", "power_density_required_w_kg", "80", "W/kg", "range_min"), + Dependency("environment", "medium", "air", None, "requires"), + ], + ), + Entity( + name="Airship", + dimension="platform", + description="Lighter-than-air powered craft (blimp or zeppelin)", + dependencies=[ + Dependency("environment", "atmosphere", "standard", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "20000", "kg", "range_max"), + Dependency("physical", "mass_kg", "5000", "kg", "range_min"), + Dependency("force", "power_density_required_w_kg", "5", "W/kg", "range_min"), + Dependency("environment", "medium", "air", None, "requires"), + ], + ), + Entity( + name="Hang Glider", + dimension="platform", + description="Lightweight unpowered glider launched from elevation", + dependencies=[ + Dependency("environment", "atmosphere", "standard", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "40", "kg", "range_max"), + Dependency("force", "power_density_required_w_kg", "1", "W/kg", "range_min"), + Dependency("environment", "medium", "air", None, "requires"), + ], + ), + Entity( + name="Paraglider", + dimension="platform", + description="Lightweight fabric wing for foot-launched free flight", + dependencies=[ + Dependency("environment", "atmosphere", "standard", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "30", "kg", "range_max"), + Dependency("force", "power_density_required_w_kg", "1", "W/kg", "range_min"), + Dependency("environment", "medium", "air", None, "requires"), + ], + ), +] + + +# ── Platforms — Space ─────────────────────────────────────────── + +SPACE_PLATFORMS: list[Entity] = [ Entity( name="Spaceship", dimension="platform", @@ -109,51 +482,112 @@ PLATFORMS: list[Entity] = [ dependencies=[ Dependency("environment", "atmosphere", "vacuum_or_thin", None, "requires"), Dependency("physical", "mass_kg", "5000", "kg", "range_min"), - Dependency("force", "force_required_watts", "1000000", "watts", "range_min"), + Dependency("force", "power_density_required_w_kg", "300", "W/kg", "range_min"), Dependency("infrastructure", "launch_facility", "true", None, "requires"), Dependency("environment", "medium", "space", None, "requires"), Dependency("physical", "energy_density_wh_kg", "2000", "Wh/kg", "range_min"), ], ), +] + + +# ── Platforms — Multi-medium ──────────────────────────────────── + +MULTI_PLATFORMS: list[Entity] = [ + Entity( + name="Hovercraft", + dimension="platform", + description="Air-cushion vehicle that travels over land, water, or ice", + dependencies=[ + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "10000", "kg", "range_max"), + Dependency("physical", "mass_kg", "2000", "kg", "range_min"), + Dependency("force", "power_density_required_w_kg", "30", "W/kg", "range_min"), + ], + ), + Entity( + name="Amphibious Vehicle", + dimension="platform", + description="Vehicle capable of operation on both land and water", + dependencies=[ + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "5000", "kg", "range_max"), + Dependency("physical", "mass_kg", "1500", "kg", "range_min"), + Dependency("force", "power_density_required_w_kg", "20", "W/kg", "range_min"), + ], + ), +] + + +# ── Platforms — Fictional / Speculative ───────────────────────── + +FICTIONAL_PLATFORMS: list[Entity] = [ Entity( name="Teleporter", dimension="platform", description="Hypothetical matter transmission device", dependencies=[ Dependency("physical", "mass_kg", "0", "kg", "range_min"), - Dependency("force", "force_required_watts", "1000000000", "watts", "range_min"), + Dependency("force", "power_density_required_w_kg", "1000", "W/kg", "range_min"), Dependency("infrastructure", "teleport_network", "true", None, "requires"), ], ), + Entity( + name="Hyperloop", + dimension="platform", + description="Sealed low-pressure tube with passenger pods at near-sonic speed", + dependencies=[ + Dependency("environment", "ground_surface", "true", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("physical", "mass_kg", "20000", "kg", "range_max"), + Dependency("physical", "mass_kg", "5000", "kg", "range_min"), + Dependency("force", "power_density_required_w_kg", "80", "W/kg", "range_min"), + Dependency("infrastructure", "hyperloop_tube", "true", None, "requires"), + Dependency("environment", "medium", "ground", None, "requires"), + ], + ), ] -# ── Power Sources ─────────────────────────────────────────────── +# ── All Platforms ─────────────────────────────────────────────── -POWER_SOURCES: list[Entity] = [ +PLATFORMS: list[Entity] = ( + GROUND_PLATFORMS + + WATER_PLATFORMS + + AIR_PLATFORMS + + SPACE_PLATFORMS + + MULTI_PLATFORMS + + FICTIONAL_PLATFORMS +) + + +# ── Power Sources — Combustion ────────────────────────────────── + +COMBUSTION_SOURCES: list[Entity] = [ Entity( name="Internal Combustion Engine", dimension="power_source", description="Gas/petrol-powered engine", dependencies=[ - Dependency("force", "force_output_watts", "100000", "watts", "provides"), Dependency("infrastructure", "fuel_infrastructure", "gas_station", None, "requires"), Dependency("environment", "atmosphere", "standard", None, "requires"), Dependency("physical", "mass_kg", "50", "kg", "range_min"), Dependency("force", "thrust_profile", "high_continuous", None, "provides"), + Dependency("force", "power_density_w_kg", "1000", "W/kg", "provides"), Dependency("physical", "energy_density_wh_kg", "1500", "Wh/kg", "provides"), ], ), Entity( - name="Lithium Ion Battery", + name="Diesel Engine", dimension="power_source", - description="Rechargeable electric battery pack", + description="Compression-ignition engine running on diesel fuel", dependencies=[ - Dependency("force", "force_output_watts", "50000", "watts", "provides"), - Dependency("infrastructure", "fuel_infrastructure", "charging_station", None, "requires"), - Dependency("physical", "mass_kg", "10", "kg", "range_min"), - Dependency("force", "thrust_profile", "moderate_continuous", None, "provides"), - Dependency("physical", "energy_density_wh_kg", "200", "Wh/kg", "provides"), + Dependency("infrastructure", "fuel_infrastructure", "diesel_station", None, "requires"), + Dependency("environment", "atmosphere", "standard", None, "requires"), + Dependency("physical", "mass_kg", "60", "kg", "range_min"), + Dependency("force", "thrust_profile", "high_continuous", None, "provides"), + Dependency("force", "power_density_w_kg", "700", "W/kg", "provides"), + Dependency("physical", "energy_density_wh_kg", "1300", "Wh/kg", "provides"), ], ), Entity( @@ -161,36 +595,37 @@ POWER_SOURCES: list[Entity] = [ dimension="power_source", description="Hydrogen fuel cell or combustion engine", dependencies=[ - Dependency("force", "force_output_watts", "80000", "watts", "provides"), Dependency("infrastructure", "fuel_infrastructure", "hydrogen_station", None, "requires"), Dependency("physical", "mass_kg", "30", "kg", "range_min"), Dependency("force", "thrust_profile", "high_continuous", None, "provides"), + Dependency("force", "power_density_w_kg", "1500", "W/kg", "provides"), Dependency("physical", "energy_density_wh_kg", "600", "Wh/kg", "provides"), ], ), Entity( - name="Human Pedalling", + name="Compressed Natural Gas Engine", dimension="power_source", - description="Human-powered via pedalling mechanism", + description="Engine running on compressed natural gas (CNG)", dependencies=[ - Dependency("force", "force_output_watts", "75", "watts", "provides"), - Dependency("infrastructure", "fuel_infrastructure", "none", None, "requires"), - Dependency("physical", "mass_kg", "0", "kg", "range_min"), - Dependency("force", "thrust_profile", "low_continuous", None, "provides"), - Dependency("physical", "energy_density_wh_kg", "200", "Wh/kg", "provides"), + Dependency("infrastructure", "fuel_infrastructure", "cng_station", None, "requires"), + Dependency("environment", "atmosphere", "standard", None, "requires"), + Dependency("physical", "mass_kg", "55", "kg", "range_min"), + Dependency("force", "thrust_profile", "high_continuous", None, "provides"), + Dependency("force", "power_density_w_kg", "500", "W/kg", "provides"), + Dependency("physical", "energy_density_wh_kg", "800", "Wh/kg", "provides"), ], ), Entity( - name="Modular Nuclear Reactor", + name="Biofuel Engine", dimension="power_source", - description="Small modular nuclear fission reactor", + description="Internal combustion engine running on biodiesel or ethanol", dependencies=[ - Dependency("force", "force_output_watts", "50000000", "watts", "provides"), - Dependency("infrastructure", "fuel_infrastructure", "nuclear_fuel", None, "requires"), - Dependency("physical", "mass_kg", "2000", "kg", "range_min"), - Dependency("force", "thrust_profile", "extreme_continuous", None, "provides"), - Dependency("material", "radiation_shielding", "true", None, "requires"), - Dependency("physical", "energy_density_wh_kg", "500000", "Wh/kg", "provides"), + Dependency("infrastructure", "fuel_infrastructure", "biofuel_supply", None, "requires"), + Dependency("environment", "atmosphere", "standard", None, "requires"), + Dependency("physical", "mass_kg", "50", "kg", "range_min"), + Dependency("force", "thrust_profile", "high_continuous", None, "provides"), + Dependency("force", "power_density_w_kg", "800", "W/kg", "provides"), + Dependency("physical", "energy_density_wh_kg", "1000", "Wh/kg", "provides"), ], ), Entity( @@ -198,14 +633,133 @@ POWER_SOURCES: list[Entity] = [ dimension="power_source", description="Coal-fired boiler with steam locomotion", dependencies=[ - Dependency("force", "force_output_watts", "200000", "watts", "provides"), Dependency("infrastructure", "fuel_infrastructure", "coal_supply", None, "requires"), Dependency("environment", "atmosphere", "standard", None, "requires"), Dependency("physical", "mass_kg", "500", "kg", "range_min"), Dependency("force", "thrust_profile", "high_continuous", None, "provides"), + Dependency("force", "power_density_w_kg", "30", "W/kg", "provides"), Dependency("physical", "energy_density_wh_kg", "400", "Wh/kg", "provides"), ], ), +] + + +# ── Power Sources — Turbine ───────────────────────────────────── + +TURBINE_SOURCES: list[Entity] = [ + Entity( + name="Jet Turbine", + dimension="power_source", + description="Turbofan jet engine for high-speed atmospheric flight", + dependencies=[ + Dependency("infrastructure", "fuel_infrastructure", "jet_fuel", None, "requires"), + Dependency("environment", "atmosphere", "standard", None, "requires"), + Dependency("physical", "mass_kg", "500", "kg", "range_min"), + Dependency("force", "thrust_profile", "extreme_continuous", None, "provides"), + Dependency("force", "power_density_w_kg", "7000", "W/kg", "provides"), + Dependency("physical", "energy_density_wh_kg", "1200", "Wh/kg", "provides"), + Dependency("environment", "medium", "air", None, "requires"), + ], + ), + Entity( + name="Turboshaft Engine", + dimension="power_source", + description="Gas turbine driving a shaft, used in helicopters and ships", + dependencies=[ + Dependency("infrastructure", "fuel_infrastructure", "jet_fuel", None, "requires"), + Dependency("environment", "atmosphere", "standard", None, "requires"), + Dependency("physical", "mass_kg", "200", "kg", "range_min"), + Dependency("force", "thrust_profile", "high_continuous", None, "provides"), + Dependency("force", "power_density_w_kg", "5000", "W/kg", "provides"), + Dependency("physical", "energy_density_wh_kg", "1200", "Wh/kg", "provides"), + ], + ), + Entity( + name="Turboprop Engine", + dimension="power_source", + description="Gas turbine driving a propeller for moderate-speed flight", + dependencies=[ + Dependency("infrastructure", "fuel_infrastructure", "jet_fuel", None, "requires"), + Dependency("environment", "atmosphere", "standard", None, "requires"), + Dependency("physical", "mass_kg", "300", "kg", "range_min"), + Dependency("force", "thrust_profile", "high_continuous", None, "provides"), + Dependency("force", "power_density_w_kg", "4000", "W/kg", "provides"), + Dependency("physical", "energy_density_wh_kg", "1200", "Wh/kg", "provides"), + Dependency("environment", "medium", "air", None, "requires"), + ], + ), +] + + +# ── Power Sources — Electric ──────────────────────────────────── + +ELECTRIC_SOURCES: list[Entity] = [ + Entity( + name="Lithium Ion Battery", + dimension="power_source", + description="Rechargeable electric battery pack", + dependencies=[ + Dependency("infrastructure", "fuel_infrastructure", "charging_station", None, "requires"), + Dependency("physical", "mass_kg", "10", "kg", "range_min"), + Dependency("force", "thrust_profile", "moderate_continuous", None, "provides"), + Dependency("force", "power_density_w_kg", "500", "W/kg", "provides"), + Dependency("physical", "energy_density_wh_kg", "200", "Wh/kg", "provides"), + ], + ), + Entity( + name="Solid State Battery", + dimension="power_source", + description="Next-generation rechargeable battery with solid electrolyte", + dependencies=[ + Dependency("infrastructure", "fuel_infrastructure", "charging_station", None, "requires"), + Dependency("physical", "mass_kg", "8", "kg", "range_min"), + Dependency("force", "thrust_profile", "moderate_continuous", None, "provides"), + Dependency("force", "power_density_w_kg", "800", "W/kg", "provides"), + Dependency("physical", "energy_density_wh_kg", "400", "Wh/kg", "provides"), + ], + ), + Entity( + name="Supercapacitor", + dimension="power_source", + description="High-power energy storage for rapid charge/discharge cycles", + dependencies=[ + Dependency("infrastructure", "fuel_infrastructure", "charging_station", None, "requires"), + Dependency("physical", "mass_kg", "15", "kg", "range_min"), + Dependency("force", "thrust_profile", "high_burst", None, "provides"), + Dependency("force", "power_density_w_kg", "10000", "W/kg", "provides"), + Dependency("physical", "energy_density_wh_kg", "10", "Wh/kg", "provides"), + ], + ), +] + + +# ── Power Sources — Renewable ─────────────────────────────────── + +RENEWABLE_SOURCES: list[Entity] = [ + Entity( + name="Wind Sail", + dimension="power_source", + description="Fabric or rigid sail harnessing wind for propulsion", + dependencies=[ + Dependency("infrastructure", "fuel_infrastructure", "none", None, "requires"), + Dependency("environment", "atmosphere", "standard", None, "requires"), + Dependency("physical", "mass_kg", "20", "kg", "range_min"), + Dependency("force", "thrust_profile", "low_continuous", None, "provides"), + Dependency("force", "power_density_w_kg", "10", "W/kg", "provides"), + ], + ), + Entity( + name="Solar Photovoltaic Panel", + dimension="power_source", + description="Photovoltaic cells converting sunlight to electricity", + dependencies=[ + Dependency("infrastructure", "fuel_infrastructure", "none", None, "requires"), + Dependency("environment", "star_proximity", "true", None, "requires"), + Dependency("physical", "mass_kg", "10", "kg", "range_min"), + Dependency("force", "thrust_profile", "continuous_low", None, "provides"), + Dependency("force", "power_density_w_kg", "20", "W/kg", "provides"), + ], + ), Entity( name="Solar Sail", dimension="power_source", @@ -214,20 +768,39 @@ POWER_SOURCES: list[Entity] = [ Dependency("environment", "atmosphere", "vacuum_or_thin", None, "requires"), Dependency("environment", "star_proximity", "true", None, "requires"), Dependency("physical", "surface_area_m2", "100", "m^2", "range_min"), - Dependency("force", "force_output_watts", "1", "watts", "provides"), Dependency("force", "thrust_profile", "continuous_low", None, "provides"), + Dependency("force", "power_density_w_kg", "0.01", "W/kg", "provides"), Dependency("environment", "medium", "space", None, "requires"), ], ), +] + + +# ── Power Sources — Biological / Human ────────────────────────── + +BIOLOGICAL_SOURCES: list[Entity] = [ Entity( - name="Cannonfire Recoil", + name="Human Pedalling", dimension="power_source", - description="Propulsion via sequential cannon blasts", + description="Human-powered via pedalling mechanism", dependencies=[ - Dependency("force", "force_output_watts", "500000", "watts", "provides"), - Dependency("infrastructure", "fuel_infrastructure", "ammunition", None, "requires"), - Dependency("physical", "mass_kg", "100", "kg", "range_min"), - Dependency("force", "thrust_profile", "high_burst", None, "provides"), + Dependency("infrastructure", "fuel_infrastructure", "none", None, "requires"), + Dependency("physical", "mass_kg", "0", "kg", "range_min"), + Dependency("force", "thrust_profile", "low_continuous", None, "provides"), + Dependency("force", "power_density_w_kg", "1.5", "W/kg", "provides"), + Dependency("physical", "energy_density_wh_kg", "200", "Wh/kg", "provides"), + ], + ), + Entity( + name="Animal Muscle", + dimension="power_source", + description="Animal traction power (horse, ox, or dog team)", + dependencies=[ + Dependency("infrastructure", "fuel_infrastructure", "feed_supply", None, "requires"), + Dependency("environment", "ground_surface", "true", None, "requires"), + Dependency("physical", "mass_kg", "0", "kg", "range_min"), + Dependency("force", "thrust_profile", "low_continuous", None, "provides"), + Dependency("force", "power_density_w_kg", "2", "W/kg", "provides"), Dependency("physical", "energy_density_wh_kg", "200", "Wh/kg", "provides"), ], ), @@ -236,17 +809,130 @@ POWER_SOURCES: list[Entity] = [ dimension="power_source", description="External human pushing the vehicle", dependencies=[ - Dependency("force", "force_output_watts", "50", "watts", "provides"), Dependency("infrastructure", "fuel_infrastructure", "none", None, "requires"), Dependency("physical", "mass_kg", "0", "kg", "range_min"), Dependency("environment", "ground_surface", "true", None, "requires"), Dependency("force", "thrust_profile", "low_continuous", None, "provides"), + Dependency("force", "power_density_w_kg", "1", "W/kg", "provides"), Dependency("physical", "energy_density_wh_kg", "200", "Wh/kg", "provides"), ], ), ] +# ── Power Sources — Rocket / Space ────────────────────────────── + +ROCKET_SOURCES: list[Entity] = [ + Entity( + name="Solid Rocket Motor", + dimension="power_source", + description="Solid propellant rocket providing massive thrust in bursts", + dependencies=[ + Dependency("infrastructure", "fuel_infrastructure", "solid_propellant", None, "requires"), + Dependency("physical", "mass_kg", "200", "kg", "range_min"), + Dependency("force", "thrust_profile", "extreme_burst", None, "provides"), + Dependency("force", "power_density_w_kg", "10000", "W/kg", "provides"), + Dependency("physical", "energy_density_wh_kg", "500", "Wh/kg", "provides"), + ], + ), + Entity( + name="Ion Thruster", + dimension="power_source", + description="Electric propulsion using ionized xenon gas for deep space", + dependencies=[ + Dependency("infrastructure", "fuel_infrastructure", "xenon_propellant", None, "requires"), + Dependency("environment", "atmosphere", "vacuum_or_thin", None, "requires"), + Dependency("physical", "mass_kg", "10", "kg", "range_min"), + Dependency("force", "thrust_profile", "continuous_low", None, "provides"), + Dependency("force", "power_density_w_kg", "30", "W/kg", "provides"), + Dependency("physical", "energy_density_wh_kg", "3000", "Wh/kg", "provides"), + Dependency("environment", "medium", "space", None, "requires"), + ], + ), + Entity( + name="Modular Nuclear Reactor", + dimension="power_source", + description="Small modular nuclear fission reactor", + dependencies=[ + Dependency("infrastructure", "fuel_infrastructure", "nuclear_fuel", None, "requires"), + Dependency("physical", "mass_kg", "2000", "kg", "range_min"), + Dependency("force", "thrust_profile", "extreme_continuous", None, "provides"), + Dependency("force", "power_density_w_kg", "50", "W/kg", "provides"), + Dependency("material", "radiation_shielding", "true", None, "requires"), + Dependency("physical", "energy_density_wh_kg", "500000", "Wh/kg", "provides"), + ], + ), +] + + +# ── Power Sources — Exotic / Fun ──────────────────────────────── + +EXOTIC_SOURCES: list[Entity] = [ + Entity( + name="Cannonfire Recoil", + dimension="power_source", + description="Propulsion via sequential cannon blasts", + dependencies=[ + Dependency("infrastructure", "fuel_infrastructure", "ammunition", None, "requires"), + Dependency("physical", "mass_kg", "100", "kg", "range_min"), + Dependency("force", "thrust_profile", "high_burst", None, "provides"), + Dependency("force", "power_density_w_kg", "3000", "W/kg", "provides"), + Dependency("physical", "energy_density_wh_kg", "200", "Wh/kg", "provides"), + ], + ), + Entity( + name="Compressed Air Engine", + dimension="power_source", + description="Engine powered by compressed air stored in high-pressure tanks", + dependencies=[ + Dependency("infrastructure", "fuel_infrastructure", "compressed_air_station", None, "requires"), + Dependency("physical", "mass_kg", "20", "kg", "range_min"), + Dependency("force", "thrust_profile", "moderate_continuous", None, "provides"), + Dependency("force", "power_density_w_kg", "100", "W/kg", "provides"), + Dependency("physical", "energy_density_wh_kg", "30", "Wh/kg", "provides"), + ], + ), + Entity( + name="Flywheel", + dimension="power_source", + description="Kinetic energy stored in a spinning rotor", + dependencies=[ + Dependency("infrastructure", "fuel_infrastructure", "charging_station", None, "requires"), + Dependency("physical", "mass_kg", "50", "kg", "range_min"), + Dependency("force", "thrust_profile", "high_burst", None, "provides"), + Dependency("force", "power_density_w_kg", "2000", "W/kg", "provides"), + Dependency("physical", "energy_density_wh_kg", "50", "Wh/kg", "provides"), + ], + ), + Entity( + name="Gravity Downhill", + dimension="power_source", + description="Gravitational potential energy on slopes and descents", + dependencies=[ + Dependency("infrastructure", "fuel_infrastructure", "none", None, "requires"), + Dependency("environment", "gravity", "true", None, "requires"), + Dependency("environment", "ground_surface", "true", None, "requires"), + Dependency("physical", "mass_kg", "0", "kg", "range_min"), + Dependency("force", "thrust_profile", "low_continuous", None, "provides"), + Dependency("force", "power_density_w_kg", "10", "W/kg", "provides"), + ], + ), +] + + +# ── All Power Sources ─────────────────────────────────────────── + +POWER_SOURCES: list[Entity] = ( + COMBUSTION_SOURCES + + TURBINE_SOURCES + + ELECTRIC_SOURCES + + RENEWABLE_SOURCES + + BIOLOGICAL_SOURCES + + ROCKET_SOURCES + + EXOTIC_SOURCES +) + + # ── Domains ───────────────────────────────────────────────────── URBAN_COMMUTING = Domain( @@ -273,6 +959,50 @@ INTERPLANETARY = Domain( ], ) +MARITIME_SHIPPING = Domain( + name="maritime_shipping", + description="Ocean cargo transport between ports, 100-40000km range", + metric_bounds=[ + MetricBound("speed", weight=0.15, norm_min=5, norm_max=60, unit="km/h"), + MetricBound("cargo_capacity", weight=0.25, norm_min=1, norm_max=200000, unit="tons"), + MetricBound("cost_efficiency", weight=0.25, norm_min=0.001, norm_max=1.0, unit="$/ton-km", lower_is_better=True), + MetricBound("safety", weight=0.20, norm_min=0.0, norm_max=1.0, unit="0-1"), + MetricBound("range_fuel", weight=0.15, norm_min=100, norm_max=40000, unit="km"), + ], +) + +LAST_MILE_DELIVERY = Domain( + name="last_mile_delivery", + description="Short-range package delivery within neighborhoods, 0.5-15km", + metric_bounds=[ + MetricBound("speed", weight=0.25, norm_min=2, norm_max=60, unit="km/h"), + MetricBound("cost_efficiency", weight=0.30, norm_min=0.01, norm_max=5.0, unit="$/km", lower_is_better=True), + MetricBound("cargo_capacity_kg", weight=0.20, norm_min=1, norm_max=500, unit="kg"), + MetricBound("safety", weight=0.15, norm_min=0.0, norm_max=1.0, unit="0-1"), + MetricBound("environmental_impact", weight=0.10, norm_min=0, norm_max=500, unit="g CO2/km", lower_is_better=True), + ], +) + +CROSS_COUNTRY_FREIGHT = Domain( + name="cross_country_freight", + description="Long-distance overland cargo transport, 200-5000km", + metric_bounds=[ + MetricBound("speed", weight=0.20, norm_min=20, norm_max=200, unit="km/h"), + MetricBound("cargo_capacity", weight=0.25, norm_min=0.5, norm_max=100, unit="tons"), + MetricBound("cost_efficiency", weight=0.25, norm_min=0.01, norm_max=5.0, unit="$/ton-km", lower_is_better=True), + MetricBound("range_fuel", weight=0.20, norm_min=100, norm_max=5000, unit="km"), + MetricBound("reliability", weight=0.10, norm_min=0.0, norm_max=1.0, unit="0-1"), + ], +) + +ALL_DOMAINS = [ + URBAN_COMMUTING, + INTERPLANETARY, + MARITIME_SHIPPING, + LAST_MILE_DELIVERY, + CROSS_COUNTRY_FREIGHT, +] + def load_transport_seed(repo) -> dict: """Load all transport seed data into the repository. Idempotent — safe to re-run.""" @@ -287,16 +1017,20 @@ def load_transport_seed(repo) -> dict: repo.add_entity(entity) counts["platforms"] += 1 except sqlite3.IntegrityError: - pass + existing = repo.get_entity_by_name(entity.dimension, entity.name) + if existing: + repo.replace_entity_dependencies(existing.id, entity.dependencies) for entity in POWER_SOURCES: try: repo.add_entity(entity) counts["power_sources"] += 1 except sqlite3.IntegrityError: - pass + existing = repo.get_entity_by_name(entity.dimension, entity.name) + if existing: + repo.replace_entity_dependencies(existing.id, entity.dependencies) - for domain in (URBAN_COMMUTING, INTERPLANETARY): + for domain in ALL_DOMAINS: try: repo.add_domain(domain) counts["domains"] += 1 diff --git a/src/physcom_web/app.py b/src/physcom_web/app.py index 67e890d..c14e3bb 100644 --- a/src/physcom_web/app.py +++ b/src/physcom_web/app.py @@ -109,11 +109,13 @@ def create_app() -> Flask: from physcom_web.routes.domains import bp as domains_bp from physcom_web.routes.pipeline import bp as pipeline_bp from physcom_web.routes.results import bp as results_bp + from physcom_web.routes.admin import bp as admin_bp app.register_blueprint(entities_bp) app.register_blueprint(domains_bp) app.register_blueprint(pipeline_bp) app.register_blueprint(results_bp) + app.register_blueprint(admin_bp) @app.route("/") def index(): diff --git a/src/physcom_web/routes/admin.py b/src/physcom_web/routes/admin.py new file mode 100644 index 0000000..8f622cc --- /dev/null +++ b/src/physcom_web/routes/admin.py @@ -0,0 +1,51 @@ +"""Admin panel routes.""" + +from __future__ import annotations + +from flask import Blueprint, flash, redirect, render_template, url_for + +from physcom.seed.transport_example import load_transport_seed +from physcom_web.app import get_repo + +bp = Blueprint("admin", __name__, url_prefix="/admin") + + +@bp.route("/") +def admin_index(): + repo = get_repo() + entities = repo.list_entities() + domains = repo.list_domains() + status_counts = repo.count_combinations_by_status() + runs = repo.list_pipeline_runs() + stats = { + "entities": len(entities), + "domains": len(domains), + "combinations": sum(status_counts.values()), + "pipeline_runs": len(runs), + } + return render_template("admin/index.html", stats=stats) + + +@bp.route("/reseed", methods=["POST"]) +def reseed(): + repo = get_repo() + counts = load_transport_seed(repo) + total = counts["platforms"] + counts["power_sources"] + flash( + f"Reseed complete — added {total} entities, {counts['domains']} domains.", + "success", + ) + return redirect(url_for("admin.admin_index")) + + +@bp.route("/wipe-and-reseed", methods=["POST"]) +def wipe_and_reseed(): + repo = get_repo() + repo.clear_all() + counts = load_transport_seed(repo) + total = counts["platforms"] + counts["power_sources"] + flash( + f"Wiped all data and reseeded — {total} entities, {counts['domains']} domains.", + "success", + ) + return redirect(url_for("admin.admin_index")) diff --git a/src/physcom_web/static/style.css b/src/physcom_web/static/style.css index 6234021..6580802 100644 --- a/src/physcom_web/static/style.css +++ b/src/physcom_web/static/style.css @@ -647,5 +647,11 @@ select option { margin-top: 0.25rem; } +/* ── Admin warning box ──────────────────────────────────── */ +.warning-box { + border-color: rgba(184,147,92,0.4); + background: rgba(184,147,92,0.06); +} + /* ── Google Fonts import ─────────────────────────────────── */ @import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;500;600&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap'); diff --git a/src/physcom_web/templates/admin/index.html b/src/physcom_web/templates/admin/index.html new file mode 100644 index 0000000..8c7f1c8 --- /dev/null +++ b/src/physcom_web/templates/admin/index.html @@ -0,0 +1,51 @@ +{% extends "base.html" %} +{% block title %}Admin — PhysCom{% endblock %} + +{% block content %} +

Admin Panel

+ +
+
+
{{ stats.entities }}
+
Entities
+
+
+
{{ stats.domains }}
+
Domains
+
+
+
{{ stats.combinations }}
+
Combinations
+
+
+
{{ stats.pipeline_runs }}
+
Pipeline Runs
+
+
+ +

Seed Data

+
+
+

Reseed (Additive)

+

+ Add any missing seed entities and domains. Existing user-created data is + preserved — only entries that don't already exist are inserted. +

+
+ +
+
+ +
+

Wipe & Reseed

+

+ Delete all data — entities, domains, combinations, + pipeline runs — then reload seed data from scratch. +

+
+ +
+
+
+{% endblock %} diff --git a/src/physcom_web/templates/base.html b/src/physcom_web/templates/base.html index bc9bd0a..88bddf5 100644 --- a/src/physcom_web/templates/base.html +++ b/src/physcom_web/templates/base.html @@ -19,6 +19,7 @@
  • Domains
  • Pipeline
  • Results
  • +
  • Admin
  • diff --git a/tests/conftest.py b/tests/conftest.py index 6ad09da..61dae27 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -37,7 +37,7 @@ def walking(): Dependency("environment", "ground_surface", "true", None, "requires"), Dependency("environment", "gravity", "true", None, "requires"), Dependency("physical", "mass_kg", "150", "kg", "range_max"), - Dependency("force", "force_required_watts", "75", "watts", "range_min"), + Dependency("force", "power_density_required_w_kg", "1", "W/kg", "range_min"), Dependency("environment", "medium", "ground", None, "requires"), ], ) @@ -53,7 +53,7 @@ def bicycle(): Dependency("environment", "ground_surface", "true", None, "requires"), Dependency("environment", "gravity", "true", None, "requires"), Dependency("physical", "mass_kg", "30", "kg", "range_max"), - Dependency("force", "force_required_watts", "75", "watts", "range_min"), + Dependency("force", "power_density_required_w_kg", "1", "W/kg", "range_min"), Dependency("environment", "medium", "ground", None, "requires"), ], ) @@ -68,7 +68,7 @@ def spaceship(): dependencies=[ Dependency("environment", "atmosphere", "vacuum_or_thin", None, "requires"), Dependency("physical", "mass_kg", "5000", "kg", "range_min"), - Dependency("force", "force_required_watts", "1000000", "watts", "range_min"), + Dependency("force", "power_density_required_w_kg", "500", "W/kg", "range_min"), Dependency("environment", "medium", "space", None, "requires"), ], ) @@ -82,7 +82,7 @@ def solar_sail(): description="Propulsion via radiation pressure", dependencies=[ Dependency("environment", "atmosphere", "vacuum_or_thin", None, "requires"), - Dependency("force", "force_output_watts", "1", "watts", "provides"), + Dependency("force", "power_density_w_kg", "0.01", "W/kg", "provides"), Dependency("environment", "medium", "space", None, "requires"), ], ) @@ -95,7 +95,7 @@ def human_pedalling(): dimension="power_source", description="Human-powered via pedalling", dependencies=[ - Dependency("force", "force_output_watts", "75", "watts", "provides"), + Dependency("force", "power_density_w_kg", "1.5", "W/kg", "provides"), Dependency("physical", "mass_kg", "0", "kg", "range_min"), ], ) @@ -108,7 +108,7 @@ def nuclear_reactor(): dimension="power_source", description="Small modular nuclear fission reactor", dependencies=[ - Dependency("force", "force_output_watts", "50000000", "watts", "provides"), + Dependency("force", "power_density_w_kg", "50", "W/kg", "provides"), Dependency("physical", "mass_kg", "2000", "kg", "range_min"), ], ) @@ -121,7 +121,7 @@ def hydrogen_engine(): dimension="power_source", description="Hydrogen fuel cell", dependencies=[ - Dependency("force", "force_output_watts", "80000", "watts", "provides"), + Dependency("force", "power_density_w_kg", "1500", "W/kg", "provides"), Dependency("physical", "mass_kg", "30", "kg", "range_min"), ], ) @@ -134,7 +134,7 @@ def ice_engine(): dimension="power_source", description="Gas-powered engine", dependencies=[ - Dependency("force", "force_output_watts", "100000", "watts", "provides"), + Dependency("force", "power_density_w_kg", "1000", "W/kg", "provides"), Dependency("environment", "atmosphere", "standard", None, "requires"), Dependency("physical", "mass_kg", "50", "kg", "range_min"), ], diff --git a/tests/test_admin.py b/tests/test_admin.py new file mode 100644 index 0000000..c309afc --- /dev/null +++ b/tests/test_admin.py @@ -0,0 +1,48 @@ +"""Tests for admin panel — clear_all, reseed, and additive reseed.""" + +from __future__ import annotations + +from physcom.seed.transport_example import load_transport_seed + + +def test_clear_all_wipes_data(seeded_repo): + """clear_all() should remove all rows from every table.""" + repo = seeded_repo + assert len(repo.list_entities()) > 0 + assert len(repo.list_domains()) > 0 + + repo.clear_all() + + assert len(repo.list_entities()) == 0 + assert len(repo.list_domains()) == 0 + assert len(repo.list_dimensions()) == 0 + assert sum(repo.count_combinations_by_status().values()) == 0 + assert len(repo.list_pipeline_runs()) == 0 + + +def test_reseed_after_clear_restores_data(seeded_repo): + """Wipe then reseed should restore entity/domain counts.""" + repo = seeded_repo + original_entities = len(repo.list_entities()) + original_domains = len(repo.list_domains()) + + repo.clear_all() + assert len(repo.list_entities()) == 0 + + counts = load_transport_seed(repo) + assert len(repo.list_entities()) == original_entities + assert len(repo.list_domains()) == original_domains + assert counts["platforms"] + counts["power_sources"] == original_entities + + +def test_additive_reseed_no_duplicates(seeded_repo): + """Running reseed on an already-seeded DB should not create duplicates.""" + repo = seeded_repo + before = len(repo.list_entities()) + + counts = load_transport_seed(repo) + + assert len(repo.list_entities()) == before + assert counts["platforms"] == 0 + assert counts["power_sources"] == 0 + assert counts["domains"] == 0 diff --git a/tests/test_combinator.py b/tests/test_combinator.py index 660532a..61354dd 100644 --- a/tests/test_combinator.py +++ b/tests/test_combinator.py @@ -7,9 +7,11 @@ from physcom.models.entity import Entity def test_generates_cartesian_product(seeded_repo): + from physcom.seed.transport_example import PLATFORMS, POWER_SOURCES + combos = generate_combinations(seeded_repo, ["platform", "power_source"]) - # 9 platforms x 9 power sources = 81 - assert len(combos) == 81 + expected = len(PLATFORMS) * len(POWER_SOURCES) + assert len(combos) == expected def test_each_combo_has_one_per_dimension(seeded_repo): diff --git a/tests/test_constraint_resolver.py b/tests/test_constraint_resolver.py index f3d0cfa..ff51a98 100644 --- a/tests/test_constraint_resolver.py +++ b/tests/test_constraint_resolver.py @@ -41,39 +41,39 @@ def test_nuclear_reactor_blocks_with_bicycle(bicycle, nuclear_reactor): assert any("mass" in v.lower() for v in result.violations) -def test_force_scale_mismatch_blocks(): - """A platform needing 1MW and a power source providing 1W → force deficit block.""" +def test_power_density_mismatch_blocks(): + """A platform needing 500 W/kg and a source providing 0.01 W/kg → block.""" platform = Entity( name="HeavyPlatform", dimension="platform", dependencies=[ - Dependency("force", "force_required_watts", "1000000", "watts", "range_min"), + Dependency("force", "power_density_required_w_kg", "500", "W/kg", "range_min"), ], ) power = Entity( name="TinyPower", dimension="power_source", dependencies=[ - Dependency("force", "force_output_watts", "1", "watts", "provides"), + Dependency("force", "power_density_w_kg", "0.01", "W/kg", "provides"), ], ) resolver = ConstraintResolver() combo = Combination(entities=[platform, power]) result = resolver.resolve(combo) assert result.status == "p1_fail" - assert any("force deficit" in v for v in result.violations) + assert any("power density deficit" in v for v in result.violations) -def test_force_under_powered_warning(): +def test_power_density_under_powered_warning(): """Power source slightly below requirement → warning, not block.""" platform = Entity( name="MedPlatform", dimension="platform", dependencies=[ - Dependency("force", "force_required_watts", "1000", "watts", "range_min"), + Dependency("force", "power_density_required_w_kg", "100", "W/kg", "range_min"), ], ) power = Entity( name="WeakPower", dimension="power_source", dependencies=[ - Dependency("force", "force_output_watts", "500", "watts", "provides"), + Dependency("force", "power_density_w_kg", "50", "W/kg", "provides"), ], ) resolver = ConstraintResolver() @@ -175,7 +175,7 @@ def test_energy_density_no_constraint_if_no_provider(): power = Entity( name="Solar Sail", dimension="power_source", dependencies=[ - Dependency("force", "force_output_watts", "1", "watts", "provides"), + Dependency("force", "power_density_w_kg", "0.01", "W/kg", "provides"), ], ) resolver = ConstraintResolver() diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py index 50cd22d..088a94a 100644 --- a/tests/test_pipeline.py +++ b/tests/test_pipeline.py @@ -15,9 +15,11 @@ def test_pass1_filters_impossible_combos(seeded_repo): result = pipeline.run(domain, ["platform", "power_source"], passes=[1]) - assert result.total_generated == 81 + from physcom.seed.transport_example import PLATFORMS, POWER_SOURCES + expected = len(PLATFORMS) * len(POWER_SOURCES) + assert result.total_generated == expected assert result.pass1_failed > 0 - assert result.pass1_valid + result.pass1_conditional + result.pass1_failed == 81 + assert result.pass1_valid + result.pass1_conditional + result.pass1_failed == expected def test_pass123_produces_scored_results(seeded_repo): diff --git a/tests/test_pipeline_async.py b/tests/test_pipeline_async.py index cd58144..b0cf05e 100644 --- a/tests/test_pipeline_async.py +++ b/tests/test_pipeline_async.py @@ -21,9 +21,12 @@ def test_pipeline_run_lifecycle(seeded_repo): pipeline.run(domain, ["platform", "power_source"], passes=[1, 2, 3], run_id=run_id) + from physcom.seed.transport_example import PLATFORMS, POWER_SOURCES + expected = len(PLATFORMS) * len(POWER_SOURCES) + run = repo.get_pipeline_run(run_id) assert run["status"] == "completed" - assert run["total_combos"] == 81 + assert run["total_combos"] == expected assert run["started_at"] is not None assert run["completed_at"] is not None diff --git a/tests/test_repository.py b/tests/test_repository.py index c85b950..5b6d37f 100644 --- a/tests/test_repository.py +++ b/tests/test_repository.py @@ -79,10 +79,12 @@ def test_combination_save_and_dedup(repo): def test_seed_loads(seeded_repo): + from physcom.seed.transport_example import PLATFORMS, POWER_SOURCES, ALL_DOMAINS + platforms = seeded_repo.list_entities(dimension="platform") power_sources = seeded_repo.list_entities(dimension="power_source") - assert len(platforms) == 9 - assert len(power_sources) == 9 + assert len(platforms) == len(PLATFORMS) + assert len(power_sources) == len(POWER_SOURCES) domains = seeded_repo.list_domains() - assert len(domains) == 2 + assert len(domains) == len(ALL_DOMAINS)