Three things. 1. CANON RESTATEMENT (info-tech-canon's ask after accepting our demand) data/capability/platform-audit-storage.json restates the backup case against ITC-CAP 0.2.0: requirement with profile, targets and the failure-domain constraint that decided the procurement; two provisions (data.object and data.backup); all four data.backup evidence hooks satisfied and measured; and consumption in native units — GB, hours, tokens — with unknown never zero. tools/capability.py reads their capabilities.yaml directly rather than copying it, so drift in either repo fails here. The requirement asks D5, the provision is D4, and the review reports below_requirement rather than inflating maturity. 2. EVIDENCE BASIS (tools/basis.py, docs/evidence-basis.md) Every value declares how it was obtained on an ordered scale: invoiced, measured, quoted, derived, projected, estimated, assumed, unknown. A derived value resolves to the weakest basis among its inputs, so precise arithmetic cannot launder weak assumptions. First application is a finding about our own biggest decision: the Scaleway vs Hetzner comparison, EUR 29.14/month stated to the cent, grades "indicative" — 1 of 4 load-bearing values evidenced, weakest "assumed". The direction is robust; the magnitude is a model output. The cheapest fix is recording real operator hours, not better arithmetic. 3. CONSUMPTION-MODE SIGNAL (railiance-platform RAILIANCE-WP-0017) settlement.py gains a consumption-mode command projecting statements into the signal they consume; make consumption-mode PERIOD=YYYY-MM publishes data/consumption-mode/current.json. Currently an empty list: no live charges for 2026-09, so no entity is restricted. Publishing the empty list makes that an assertion rather than an absence, which their contract distinguishes. The validator fails if the published signal is stale. 185 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
377 lines
16 KiB
Python
377 lines
16 KiB
Python
#!/usr/bin/env python3
|
|
"""Delivered-cost transfer prices, credit state, and monthly settlement."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from calendar import monthrange
|
|
from datetime import date, timedelta
|
|
from decimal import Decimal
|
|
from pathlib import Path
|
|
|
|
from entities import RAILIANCE, association_ok, load_register, load_terms, require_entity
|
|
from financial_exchange import money, money_text
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def transfer_price(delivered_cost: Decimal | None, consuming_entity: str, terms: dict) -> Decimal | None:
|
|
if delivered_cost is None:
|
|
return None
|
|
require_entity(consuming_entity)
|
|
rate = terms["railiance_self_markup_rate"] if consuming_entity == RAILIANCE else terms["markup_rate"]
|
|
return money(delivered_cost * (Decimal("1") + rate))
|
|
|
|
|
|
def max_new_delivered_cost(allowance: Decimal | None, consuming_entity: str, terms: dict) -> Decimal | None:
|
|
if allowance is None:
|
|
return None
|
|
if consuming_entity == RAILIANCE or allowance == Decimal("0.00"):
|
|
return money(allowance) if consuming_entity == RAILIANCE else money(0)
|
|
divisor = Decimal("1") + terms["markup_rate"]
|
|
return money(allowance / divisor)
|
|
|
|
|
|
def due_date(statement_date: date, terms: dict) -> date:
|
|
return statement_date + timedelta(days=terms["payment_term_days"])
|
|
|
|
|
|
def monthly_interest(overdue: Decimal, terms: dict) -> Decimal:
|
|
if overdue <= 0:
|
|
return money(0)
|
|
return money(overdue * terms["interest_rate_per_year"] / Decimal(12))
|
|
|
|
|
|
def apply_payment(interest_due: Decimal, principal: Decimal, payment: Decimal) -> dict:
|
|
"""Interest first, then principal (Terms OQ-7)."""
|
|
interest_due = money(interest_due)
|
|
principal = money(principal)
|
|
remaining = money(payment)
|
|
interest_paid = money(min(interest_due, remaining))
|
|
remaining = money(remaining - interest_paid)
|
|
principal_paid = money(min(principal, remaining))
|
|
remaining = money(remaining - principal_paid)
|
|
return {
|
|
"interest_paid": interest_paid,
|
|
"principal_paid": principal_paid,
|
|
"interest_remaining": money(interest_due - interest_paid),
|
|
"principal_remaining": money(principal - principal_paid),
|
|
"unapplied": remaining,
|
|
}
|
|
|
|
|
|
def credit_limit_for(entity_id: str, terms: dict, register: dict[str, dict]) -> Decimal | None:
|
|
require_entity(entity_id, register)
|
|
if entity_id == RAILIANCE:
|
|
return None
|
|
raw = register[entity_id].get("credit_limit_eur")
|
|
if raw is None:
|
|
return terms["default_credit_limit_eur"]
|
|
return money(raw)
|
|
|
|
|
|
def evaluate_credit(outstanding: Decimal, terms: dict, *, entity_id: str, register: dict[str, dict], overdue: bool) -> dict:
|
|
outstanding = money(outstanding)
|
|
limit = credit_limit_for(entity_id, terms, register)
|
|
interest = monthly_interest(outstanding, terms) if overdue else money(0)
|
|
if limit is None:
|
|
return {
|
|
"consumption_mode": "open",
|
|
"credit_limit_eur": None,
|
|
"credit_headroom_eur": None,
|
|
"interest_this_month_eur": money_text(interest),
|
|
"new_transfer_charges_allowed_eur": None,
|
|
"max_new_delivered_cost_eur": None,
|
|
}
|
|
restricted = outstanding >= limit
|
|
if restricted:
|
|
allowance = money(max(Decimal("0.00"), terms["restricted_monthly_consumption_eur"] - interest))
|
|
else:
|
|
allowance = None
|
|
headroom = money(max(Decimal("0.00"), limit - outstanding))
|
|
return {
|
|
"consumption_mode": "restricted" if restricted else "open",
|
|
"credit_limit_eur": money_text(limit),
|
|
"credit_headroom_eur": money_text(headroom),
|
|
"interest_this_month_eur": money_text(interest),
|
|
"new_transfer_charges_allowed_eur": None if allowance is None else money_text(allowance),
|
|
"max_new_delivered_cost_eur": (
|
|
None if allowance is None else money_text(max_new_delivered_cost(allowance, entity_id, terms))
|
|
),
|
|
}
|
|
|
|
|
|
def statement_date_for(period: str) -> date:
|
|
year, month = (int(part) for part in period.split("-"))
|
|
if month == 12:
|
|
return date(year + 1, 1, 1)
|
|
return date(year, month + 1, 1)
|
|
|
|
|
|
def _line_item(charge: dict, terms: dict) -> dict:
|
|
entity_id = charge["financial_entity_id"]
|
|
delivered = None if charge.get("delivered_cost") is None else money(charge["delivered_cost"])
|
|
price = transfer_price(delivered, entity_id, terms)
|
|
markup = None
|
|
if delivered is not None and price is not None:
|
|
markup = money(price - delivered)
|
|
components = charge.get("components") or {}
|
|
return {
|
|
"resource_id": charge["resource_id"],
|
|
"usage_summary": charge.get("usage_summary") or "",
|
|
"delivered_cost_eur": None if delivered is None else money_text(delivered),
|
|
"components": {
|
|
"infrastructure": components.get("infrastructure"),
|
|
"internal_labor": components.get("internal_labor"),
|
|
"external_services": components.get("external_services"),
|
|
"setup": components.get("setup"),
|
|
"other": components.get("other"),
|
|
},
|
|
"markup_eur": None if markup is None else money_text(markup),
|
|
"transfer_price_eur": None if price is None else money_text(price),
|
|
"unknown_remainder": charge.get("unknown_remainder"),
|
|
}
|
|
|
|
|
|
def build_statement(
|
|
*,
|
|
entity_id: str,
|
|
period: str,
|
|
terms: dict,
|
|
register: dict[str, dict],
|
|
statement_date: date,
|
|
lines: list[dict],
|
|
prior_outstanding: Decimal,
|
|
recognized_payments: list[dict],
|
|
as_of: date | None = None,
|
|
payment_status: str = "unknown",
|
|
) -> dict | None:
|
|
require_entity(entity_id, register)
|
|
as_of = as_of or statement_date
|
|
due = due_date(statement_date, terms)
|
|
prior = money(prior_outstanding)
|
|
paid = money(0)
|
|
for payment in recognized_payments:
|
|
if payment.get("financial_entity_id") == entity_id:
|
|
paid = money(paid + money(payment["amount"]))
|
|
payment_status = "recognized"
|
|
known_cost = money(0)
|
|
known_transfer = money(0)
|
|
unknown = False
|
|
for line in lines:
|
|
if line["delivered_cost_eur"] is None or line["transfer_price_eur"] is None:
|
|
unknown = True
|
|
else:
|
|
known_cost = money(known_cost + money(line["delivered_cost_eur"]))
|
|
known_transfer = money(known_transfer + money(line["transfer_price_eur"]))
|
|
prior_due = due_date(statement_date_for(_previous_period(period)), terms) if prior > 0 else due
|
|
# Interest is quantified when prior outstanding is past its due date at as_of.
|
|
overdue = prior > 0 and as_of > prior_due
|
|
if prior > 0 and as_of == statement_date and statement_date <= due:
|
|
# Brand-new statement of this period: prior from last period is overdue
|
|
# only if its own due date has passed.
|
|
overdue = as_of > prior_due
|
|
interest = monthly_interest(prior, terms) if overdue else money(0)
|
|
applied = apply_payment(interest, prior, paid)
|
|
balance_before_interest = money(applied["principal_remaining"] + known_transfer)
|
|
outstanding = money(balance_before_interest + applied["interest_remaining"])
|
|
credit = evaluate_credit(
|
|
balance_before_interest, terms, entity_id=entity_id, register=register, overdue=overdue,
|
|
)
|
|
has_activity = bool(lines) or prior > 0 or paid > 0 or interest > 0
|
|
if not has_activity:
|
|
return None
|
|
entity = register[entity_id]
|
|
provider = register[RAILIANCE]
|
|
payment_instruction = None
|
|
if entity_id != RAILIANCE and known_transfer > 0:
|
|
payment_instruction = {
|
|
"pay_from_account_ref": entity.get("account_ref"),
|
|
"pay_to_account_ref": provider.get("account_ref"),
|
|
}
|
|
return {
|
|
"schema_version": "0.1",
|
|
"record_type": "settlement_statement",
|
|
"terms_version": terms["terms_version"],
|
|
"financial_entity_id": entity_id,
|
|
"procuring_entity_id": RAILIANCE,
|
|
"period": period,
|
|
"statement_date": statement_date.isoformat(),
|
|
"due_date": due.isoformat(),
|
|
"currency": terms["currency"],
|
|
"line_items": lines,
|
|
"known_delivered_cost_eur": money_text(known_cost),
|
|
"known_transfer_price_eur": money_text(known_transfer),
|
|
"unknown_cost_remainder": unknown,
|
|
"prior_outstanding_eur": money_text(prior),
|
|
"recognized_payments_eur": None if payment_status == "unknown" and paid == 0 else money_text(paid),
|
|
"payment_recognition": payment_status if paid > 0 else "unknown",
|
|
"interest_eur": money_text(interest),
|
|
"new_transfer_charges_eur": money_text(known_transfer),
|
|
"outstanding_eur": money_text(outstanding),
|
|
"credit_limit_eur": credit["credit_limit_eur"],
|
|
"credit_headroom_eur": credit["credit_headroom_eur"],
|
|
"consumption_mode": credit["consumption_mode"],
|
|
"next_month_allowance_eur": credit["new_transfer_charges_allowed_eur"],
|
|
"payment_instruction": payment_instruction,
|
|
}
|
|
|
|
|
|
def _previous_period(period: str) -> str:
|
|
year, month = (int(part) for part in period.split("-"))
|
|
if month == 1:
|
|
return f"{year - 1}-12"
|
|
return f"{year}-{month - 1:02d}"
|
|
|
|
|
|
def close_fixture(payload: dict, terms: dict, register: dict[str, dict]) -> list[dict]:
|
|
statement_date = date.fromisoformat(payload["statement_date"])
|
|
as_of = date.fromisoformat(payload["as_of"]) if payload.get("as_of") else statement_date
|
|
by_entity: dict[str, list[dict]] = {}
|
|
for charge in payload.get("charges", []):
|
|
association_ok({
|
|
"financial_entity_id": charge["financial_entity_id"],
|
|
"procuring_entity_id": charge.get("procuring_entity_id", RAILIANCE),
|
|
"entity_gap": None,
|
|
})
|
|
by_entity.setdefault(charge["financial_entity_id"], []).append(_line_item(charge, terms))
|
|
prior = {key: money(value) for key, value in (payload.get("prior_outstanding") or {}).items()}
|
|
entities = sorted(set(by_entity) | set(prior) | {
|
|
payment["financial_entity_id"] for payment in payload.get("recognized_payments") or []
|
|
})
|
|
statements = []
|
|
for entity_id in entities:
|
|
statement = build_statement(
|
|
entity_id=entity_id,
|
|
period=payload["period"],
|
|
terms=terms,
|
|
register=register,
|
|
statement_date=statement_date,
|
|
lines=by_entity.get(entity_id, []),
|
|
prior_outstanding=prior.get(entity_id, money(0)),
|
|
recognized_payments=payload.get("recognized_payments") or [],
|
|
as_of=as_of,
|
|
payment_status=payload.get("payment_recognition", "unknown"),
|
|
)
|
|
if statement:
|
|
statements.append(statement)
|
|
return statements
|
|
|
|
|
|
def collect_live_charges(root: Path, period: str) -> list[dict]:
|
|
"""Live close only emits a charge when delivered cost is known. Unknown is not zero."""
|
|
charges = []
|
|
year, month = (int(part) for part in period.split("-"))
|
|
period_end = date(year, month, monthrange(year, month)[1])
|
|
for path in sorted((root / "data" / "resources").glob("*.json")):
|
|
resource = json.loads(path.read_text())
|
|
if resource.get("status") in {"rejected", "retired"}:
|
|
continue
|
|
commissioned = resource.get("lifecycle", {}).get("commissioned_on")
|
|
if commissioned and date.fromisoformat(commissioned) > period_end:
|
|
continue
|
|
proposed = resource.get("lifecycle", {}).get("proposed_on")
|
|
if resource.get("status") == "proposed" and proposed and date.fromisoformat(proposed) > period_end:
|
|
continue
|
|
# No booked delivered cost lives on inventory records today.
|
|
# A control-cycle actual with known infrastructure would be the source;
|
|
# until then there are no live charges.
|
|
del path
|
|
control_dir = root / "data" / "control-cycle"
|
|
if control_dir.exists():
|
|
for path in sorted(control_dir.glob("*.json")):
|
|
record = json.loads(path.read_text())
|
|
if record.get("period") != period:
|
|
continue
|
|
costs = record.get("costs") or {}
|
|
parts = [costs.get("infrastructure"), costs.get("internal_labor"), costs.get("external_labor")]
|
|
if any(part is None for part in parts):
|
|
continue
|
|
entity_id = record.get("financial_entity_id")
|
|
if not entity_id:
|
|
continue
|
|
charges.append({
|
|
"financial_entity_id": entity_id,
|
|
"procuring_entity_id": record.get("procuring_entity_id", RAILIANCE),
|
|
"resource_id": record["resource_id"],
|
|
"delivered_cost": money_text(sum(parts)),
|
|
"usage_summary": f"control-cycle {record['record_type']} {record['record_id']}",
|
|
"components": {
|
|
"infrastructure": None if costs.get("infrastructure") is None else money_text(costs["infrastructure"]),
|
|
"internal_labor": None if costs.get("internal_labor") is None else money_text(costs["internal_labor"]),
|
|
"external_services": None if costs.get("external_labor") is None else money_text(costs["external_labor"]),
|
|
"setup": None,
|
|
"other": None,
|
|
},
|
|
})
|
|
return charges
|
|
|
|
|
|
def close_live(root: Path, period: str) -> list[dict]:
|
|
terms = load_terms(root)
|
|
_, register = load_register(root)
|
|
payload = {
|
|
"period": period,
|
|
"statement_date": statement_date_for(period).isoformat(),
|
|
"charges": collect_live_charges(root, period),
|
|
"prior_outstanding": {},
|
|
"recognized_payments": [],
|
|
"payment_recognition": "unknown",
|
|
}
|
|
return close_fixture(payload, terms, register)
|
|
|
|
|
|
def consumption_mode_signal(statements: list[dict], period: str) -> list[dict]:
|
|
"""Project settlement statements into the signal railiance-platform consumes.
|
|
|
|
An empty list is a real answer, not a missing one: it means no entity is
|
|
restricted. Per the consumption-mode contract a *missing* signal is neither
|
|
open nor restricted and does not refuse, so publishing the empty list is
|
|
what makes "nobody is restricted" an assertion rather than an absence.
|
|
"""
|
|
return [
|
|
{
|
|
"schema_version": "0.1",
|
|
"record_type": "consumption_mode",
|
|
"financial_entity_id": statement["financial_entity_id"],
|
|
"period": period,
|
|
"consumption_mode": statement["consumption_mode"],
|
|
"new_transfer_charges_allowed_eur": statement["next_month_allowance_eur"],
|
|
"terms_version": statement["terms_version"],
|
|
}
|
|
for statement in statements
|
|
]
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Monthly settlement close")
|
|
parser.add_argument("command", choices=["close", "consumption-mode"])
|
|
parser.add_argument("--period", help="YYYY-MM")
|
|
parser.add_argument("--fixture", help="path to a settlement fixture")
|
|
parser.add_argument("--root", default=str(ROOT))
|
|
args = parser.parse_args()
|
|
root = Path(args.root)
|
|
terms = load_terms(root)
|
|
_, register = load_register(root)
|
|
if args.fixture:
|
|
payload = json.loads(Path(args.fixture).read_text())
|
|
statements = close_fixture(payload, terms, register)
|
|
else:
|
|
if not args.period:
|
|
print("PERIOD=YYYY-MM is required without --fixture", file=sys.stderr)
|
|
return 2
|
|
statements = close_live(root, args.period)
|
|
if args.command == "consumption-mode":
|
|
if not args.period:
|
|
print("--period YYYY-MM is required for consumption-mode", file=sys.stderr)
|
|
return 2
|
|
print(json.dumps(consumption_mode_signal(statements, args.period), indent=2))
|
|
return 0
|
|
print(json.dumps(statements, indent=2))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|