seeding expansion
also: replace energy output with energy output density
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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)"
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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():
|
||||
|
||||
51
src/physcom_web/routes/admin.py
Normal file
51
src/physcom_web/routes/admin.py
Normal file
@@ -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"))
|
||||
@@ -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');
|
||||
|
||||
51
src/physcom_web/templates/admin/index.html
Normal file
51
src/physcom_web/templates/admin/index.html
Normal file
@@ -0,0 +1,51 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Admin — PhysCom{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Admin Panel</h1>
|
||||
|
||||
<div class="stats-row">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{{ stats.entities }}</div>
|
||||
<div class="stat-label">Entities</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{{ stats.domains }}</div>
|
||||
<div class="stat-label">Domains</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{{ stats.combinations }}</div>
|
||||
<div class="stat-label">Combinations</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{{ stats.pipeline_runs }}</div>
|
||||
<div class="stat-label">Pipeline Runs</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Seed Data</h2>
|
||||
<div class="card-grid">
|
||||
<div class="card">
|
||||
<h3>Reseed (Additive)</h3>
|
||||
<p class="subtitle">
|
||||
Add any missing seed entities and domains. Existing user-created data is
|
||||
preserved — only entries that don't already exist are inserted.
|
||||
</p>
|
||||
<form method="post" action="{{ url_for('admin.reseed') }}" style="margin-top: 0.75rem">
|
||||
<button type="submit" class="btn btn-primary">Reseed</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card warning-box">
|
||||
<h3>Wipe & Reseed</h3>
|
||||
<p class="subtitle">
|
||||
Delete <strong>all</strong> data — entities, domains, combinations,
|
||||
pipeline runs — then reload seed data from scratch.
|
||||
</p>
|
||||
<form method="post" action="{{ url_for('admin.wipe_and_reseed') }}" style="margin-top: 0.75rem"
|
||||
onsubmit="return confirm('This will permanently delete ALL data and reseed from scratch. Continue?')">
|
||||
<button type="submit" class="btn btn-danger">Wipe & Reseed</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -19,6 +19,7 @@
|
||||
<li><a href="{{ url_for('domains.domain_list') }}">Domains</a></li>
|
||||
<li><a href="{{ url_for('pipeline.pipeline_form') }}">Pipeline</a></li>
|
||||
<li><a href="{{ url_for('results.results_index') }}">Results</a></li>
|
||||
<li><a href="{{ url_for('admin.admin_index') }}">Admin</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
|
||||
@@ -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"),
|
||||
],
|
||||
|
||||
48
tests/test_admin.py
Normal file
48
tests/test_admin.py
Normal file
@@ -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
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user