feat: operational resource procurement facility

Implement RESOURCE-WP-0005: entity register, V0.1 terms parameters,
entity association on inventory and planning records, transfer-price
and credit-state arithmetic, monthly settlement, and entity views on
the portfolio report. Live close emits nothing until delivered cost
is known. Handoffs are FIN-WP-0006 and RAILIANCE-WP-0017.
This commit is contained in:
tegwick 2026-08-14 13:15:02 +02:00
parent 325a505980
commit f8d1c542d5
37 changed files with 1316 additions and 28 deletions

View file

@ -13,6 +13,7 @@ import sys
from datetime import date
from pathlib import Path
from entities import REQUIRED_ENTITY_IDS, load_register
from optimization import evaluate, validate_case
from portfolio import validate_record
@ -260,6 +261,47 @@ def optimization_section(cases: list[dict]) -> dict:
}
def entities_section(resources: list[dict], register: dict[str, dict]) -> dict:
by_id = {entity_id: {
"financial_entity_id": entity_id,
"display_name": register[entity_id]["display_name"],
"role": register[entity_id]["role"],
"dedicated_resources": [],
"shared_shares": [],
"priced_resources": 0,
"consumption_mode": None,
"consumption_mode_note": "no settlement close yet; mode is unknown, not open",
} for entity_id in REQUIRED_ENTITY_IDS}
unattributed = []
for resource in resources:
entity_id = resource.get("financial_entity_id")
entry = {
"resource_id": resource["id"],
"price_evidence": bool(resource["cost"]["price_evidence"]),
}
if entity_id:
by_id[entity_id]["dedicated_resources"].append(resource["id"])
if resource["cost"]["price_evidence"]:
by_id[entity_id]["priced_resources"] += 1
else:
unattributed.append({
"resource_id": resource["id"],
"entity_gap": resource.get("entity_gap"),
"allocation_mode": resource["ownership"]["allocation"]["mode"],
})
for share in resource["ownership"]["allocation"].get("entity_shares") or []:
share_id = share["financial_entity_id"]
by_id[share_id]["shared_shares"].append({
"resource_id": resource["id"],
"note": share["note"],
})
return {
"entities": [by_id[entity_id] for entity_id in REQUIRED_ENTITY_IDS],
"unattributed_resources": unattributed,
"known_monthly_spend_eur": None,
}
def next_actions(report: dict) -> list[str]:
"""The smallest set of evidence that would unblock the most decisions."""
actions = []
@ -281,6 +323,7 @@ def next_actions(report: dict) -> list[str]:
def build(root: Path, today: date | None = None) -> dict:
today = today or date.today()
data = load_portfolio(root)
_, register = load_register(root)
resources = data["resources"]
utilization = utilization_section(resources)
cost = cost_section(resources)
@ -292,6 +335,7 @@ def build(root: Path, today: date | None = None) -> dict:
"lifecycle": lifecycle_section(resources),
"utilization": utilization,
"cost": cost,
"entities": entities_section(resources, register),
"renewals": renewals_section(resources, today),
"optimization": optimization_section(data["cases"]),
}