feat(fase3): config-driven docs_dir + verdict_input on Project loader
This commit is contained in:
parent
8b64c7f8de
commit
c7c42eeb75
6 changed files with 44 additions and 2 deletions
|
|
@ -0,0 +1,5 @@
|
|||
SYNTHETIC fixture (D4) — AI-authored, not verified domain content.
|
||||
|
||||
Cost saving measure candidate for Bru over Lakselva (rehabilitering):
|
||||
Reducing the scope on the concrete-repair area lowers the cost. Scope reduction on the
|
||||
rehabilitation works is the candidate cost-saving measure to validate.
|
||||
5
src/portfolio_optimiser/data/docs/FV42-GSV-E1/notes.txt
Normal file
5
src/portfolio_optimiser/data/docs/FV42-GSV-E1/notes.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
SYNTHETIC fixture (D4) — AI-authored, not verified domain content.
|
||||
|
||||
Cost saving measure candidate for Fv. 42 gang- og sykkelveg (etappe 1):
|
||||
A unit-rate renegotiation on code 05.2 (Asfalt Ab11) reduces the paving cost on this
|
||||
stretch. Reducing the asphalt scope is the candidate cost-saving measure to validate.
|
||||
5
src/portfolio_optimiser/data/docs/RV13-RAS-TP/notes.txt
Normal file
5
src/portfolio_optimiser/data/docs/RV13-RAS-TP/notes.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
SYNTHETIC fixture (D4) — AI-authored, not verified domain content.
|
||||
|
||||
Cost saving measure candidate for Rv. 13 rassikring tunnelportal:
|
||||
A material substitution on the rock-support scope reduces the cost. Substituting the
|
||||
specified material is the candidate cost-saving measure to validate on the portal works.
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"_note": "SYNTHETIC fixture (D4). Fictional 'anleggskostnad' projects with dummy data — not real estimates. Used as the framework's bundled reference portfolio.",
|
||||
"_note": "SYNTHETIC fixture (D4). Fictional 'anleggskostnad' projects with dummy data — not real estimates. Used as the framework's bundled reference portfolio. The per-project docs_dir contents and the verdict_input (decision/rationale) values are SYNTHETIC / AI-authored placeholders — NOT verified domain judgments; a production deployer replaces this reference domain with a real data source and supplies Layer-2 verdicts via real HITL (not static config).",
|
||||
"currency": "NOK",
|
||||
"projects": [
|
||||
{
|
||||
|
|
@ -7,6 +7,8 @@
|
|||
"name": "Fv. 42 gang- og sykkelveg, etappe 1",
|
||||
"description": "Ny gang- og sykkelveg langs Fv. 42, 1,2 km. Fiktivt referanseprosjekt.",
|
||||
"currency": "NOK",
|
||||
"docs_dir": "docs/FV42-GSV-E1",
|
||||
"verdict_input": {"decision": "approved", "rationale": "Asphalt scope reduction is feasible within the estimate range (SYNTHETIC / not verified)."},
|
||||
"cost_items": [
|
||||
{"code": "01.1", "description": "Rigg og drift", "quantity": 1, "unit": "rs", "unit_cost": 850000},
|
||||
{"code": "02.3", "description": "Masseutskifting bløt grunn", "quantity": 4200, "unit": "m3", "unit_cost": 420},
|
||||
|
|
@ -21,6 +23,8 @@
|
|||
"name": "Rv. 13 rassikring tunnelportal",
|
||||
"description": "Rassikring og forlenget portal, Rv. 13. Fiktivt referanseprosjekt.",
|
||||
"currency": "NOK",
|
||||
"docs_dir": "docs/RV13-RAS-TP",
|
||||
"verdict_input": {"decision": "approved", "rationale": "Rock-support material substitution stays within the validated savings range (SYNTHETIC / not verified)."},
|
||||
"cost_items": [
|
||||
{"code": "01.1", "description": "Rigg og drift", "quantity": 1, "unit": "rs", "unit_cost": 1450000},
|
||||
{"code": "21.2", "description": "Sprengning fjell", "quantity": 3600, "unit": "m3", "unit_cost": 540},
|
||||
|
|
@ -34,6 +38,8 @@
|
|||
"name": "Bru over Lakselva — rehabilitering",
|
||||
"description": "Betongrehabilitering og ny fuktisolering, eksisterende bru. Fiktivt referanseprosjekt.",
|
||||
"currency": "NOK",
|
||||
"docs_dir": "docs/BRU-LAKS-REHAB",
|
||||
"verdict_input": {"decision": "approved", "rationale": "Concrete-repair scope reduction is feasible within the estimate range (SYNTHETIC / not verified)."},
|
||||
"cost_items": [
|
||||
{"code": "01.1", "description": "Rigg og drift", "quantity": 1, "unit": "rs", "unit_cost": 620000},
|
||||
{"code": "84.1", "description": "Stillas og tilkomst", "quantity": 1, "unit": "rs", "unit_cost": 540000},
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ class Project:
|
|||
description: str
|
||||
currency: str
|
||||
cost_items: tuple[CostItem, ...]
|
||||
docs_dir: str # absolute path to this project's bundled cost-docs folder (config-driven)
|
||||
verdict_input: dict[str, str] # SYNTHETIC Layer-2 expert decision/rationale (config-driven)
|
||||
|
||||
@property
|
||||
def total_cost(self) -> float:
|
||||
|
|
@ -51,7 +53,12 @@ class Project:
|
|||
|
||||
|
||||
def load_reference_projects() -> tuple[Project, ...]:
|
||||
"""Load the bundled synthetic reference projects (D4)."""
|
||||
"""Load the bundled synthetic reference projects (D4).
|
||||
|
||||
Each project's ``docs_dir`` is stored in the JSON relative to the package ``data/`` root
|
||||
and resolved here to an absolute filesystem path; ``verdict_input`` carries the SYNTHETIC
|
||||
Layer-2 expert decision/rationale. Missing keys raise ``KeyError`` (fail-fast, matching the
|
||||
existing loader contract)."""
|
||||
resource = files("portfolio_optimiser").joinpath(_DATA_RESOURCE)
|
||||
raw = json.loads(resource.read_text(encoding="utf-8"))
|
||||
return tuple(
|
||||
|
|
@ -70,6 +77,8 @@ def load_reference_projects() -> tuple[Project, ...]:
|
|||
)
|
||||
for c in p["cost_items"]
|
||||
),
|
||||
docs_dir=str(files("portfolio_optimiser").joinpath("data", p["docs_dir"])),
|
||||
verdict_input=p["verdict_input"],
|
||||
)
|
||||
for p in raw["projects"]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,18 @@ def test_every_project_well_formed(projects: tuple[Project, ...]) -> None:
|
|||
assert len(p.cost_items) >= 1
|
||||
|
||||
|
||||
def test_every_project_has_docs_dir_and_verdict_input(projects: tuple[Project, ...]) -> None:
|
||||
"""Step 2 (SC1 prep): docs_dir + verdict_input are config-driven per project. Each loaded
|
||||
Project exposes a docs_dir resolved to an existing bundled directory, and a verdict_input
|
||||
dict with exactly the decision + rationale keys. Removing the loader fields reddens this."""
|
||||
import os
|
||||
|
||||
for p in projects:
|
||||
assert p.docs_dir and os.path.isdir(p.docs_dir)
|
||||
assert set(p.verdict_input) == {"decision", "rationale"}
|
||||
assert p.verdict_input["decision"] and p.verdict_input["rationale"]
|
||||
|
||||
|
||||
def test_cost_item_total_is_quantity_times_unit_cost() -> None:
|
||||
item = CostItem(code="X", description="d", quantity=4.0, unit="m2", unit_cost=215.0)
|
||||
assert item.total_cost == pytest.approx(860.0)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue