generated from coulomb/repo-seed
156 lines
5.8 KiB
Python
156 lines
5.8 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from .deployment_overlay import normalize_deployment_overlay
|
|
from .financial import (
|
|
FINANCIAL_API_VERSION,
|
|
FINANCIAL_SCHEMA_VERSION,
|
|
financial_graph_errors,
|
|
materialize_financial_graph_export,
|
|
)
|
|
from .loader import load_yaml, repo_root
|
|
|
|
DEFAULT_BASELINE_PATH = repo_root() / "fabric" / "financial" / "railiance-netkingdom.yaml"
|
|
|
|
|
|
def load_financial_baseline(path: Path | None = None) -> dict[str, Any]:
|
|
baseline_path = path or DEFAULT_BASELINE_PATH
|
|
data = load_yaml(baseline_path)
|
|
if not isinstance(data, dict):
|
|
raise ValueError(f"financial baseline must be a mapping: {baseline_path}")
|
|
if data.get("kind") != "FinancialFabricBaseline":
|
|
raise ValueError(f"financial baseline kind must be FinancialFabricBaseline: {baseline_path}")
|
|
spec = data.get("spec")
|
|
if not isinstance(spec, dict):
|
|
raise ValueError(f"financial baseline spec must be a mapping: {baseline_path}")
|
|
return spec
|
|
|
|
|
|
def financial_export_from_legacy(
|
|
legacy_graph: dict[str, Any],
|
|
baseline: dict[str, Any] | None = None,
|
|
) -> dict[str, Any]:
|
|
baseline = json.loads(json.dumps(baseline or load_financial_baseline()))
|
|
defaults = baseline.get("defaults") if isinstance(baseline.get("defaults"), dict) else {}
|
|
containment_default = defaults.get("containment") if isinstance(defaults.get("containment"), dict) else {}
|
|
ownership_default = defaults.get("ownership") if isinstance(defaults.get("ownership"), dict) else {}
|
|
accounting_default = defaults.get("accounting") if isinstance(defaults.get("accounting"), dict) else {}
|
|
|
|
graph = {
|
|
"apiVersion": FINANCIAL_API_VERSION,
|
|
"kind": "FabricGraphExport",
|
|
"schema_version": FINANCIAL_SCHEMA_VERSION,
|
|
"source": {
|
|
**_object(legacy_graph.get("source")),
|
|
"producer": "railiance-fabric",
|
|
"generation_reason": "baseline_projection",
|
|
},
|
|
"compatibility": {
|
|
"legacy_v1alpha1_supported": True,
|
|
"breaking_reset": False,
|
|
"projected_from_apiVersion": legacy_graph.get("apiVersion"),
|
|
},
|
|
"netkingdom": baseline.get("netkingdom", {}),
|
|
"actors": baseline.get("actors", []),
|
|
"fabrics": baseline.get("fabrics", []),
|
|
"nodes": [
|
|
_financial_node_from_legacy(node, containment_default, ownership_default, accounting_default)
|
|
for node in legacy_graph.get("nodes", [])
|
|
if isinstance(node, dict)
|
|
],
|
|
"edges": [
|
|
_financial_edge_from_legacy(edge)
|
|
for edge in legacy_graph.get("edges", [])
|
|
if isinstance(edge, dict)
|
|
],
|
|
"unresolved": [],
|
|
}
|
|
graph["generated_at"] = legacy_graph.get("generated_at") or _utc_now()
|
|
materialized = materialize_financial_graph_export(graph)
|
|
errors = financial_graph_errors(materialized)
|
|
if errors:
|
|
raise ValueError(f"invalid financial baseline projection: {errors[0]}")
|
|
return materialized
|
|
|
|
|
|
def _financial_node_from_legacy(
|
|
node: dict[str, Any],
|
|
containment_default: dict[str, Any],
|
|
ownership_default: dict[str, Any],
|
|
accounting_default: dict[str, Any],
|
|
) -> dict[str, Any]:
|
|
result: dict[str, Any] = {
|
|
"id": node.get("id", ""),
|
|
"kind": _node_kind(node),
|
|
"name": node.get("name") or node.get("id", ""),
|
|
"repo": node.get("repo", ""),
|
|
"domain": node.get("domain", ""),
|
|
"lifecycle": node.get("lifecycle", ""),
|
|
"containment": json.loads(json.dumps(containment_default)),
|
|
"ownership": json.loads(json.dumps(ownership_default)),
|
|
"evidence_state": node.get("evidence_state", "declared"),
|
|
"attributes": _object(node.get("attributes")),
|
|
}
|
|
accounting = _object(result["attributes"].get("accounting")) or accounting_default
|
|
if _has_value(accounting):
|
|
result["accounting"] = json.loads(json.dumps(accounting))
|
|
overlay = normalize_deployment_overlay(
|
|
node.get("deployment_overlay") if isinstance(node.get("deployment_overlay"), dict) else {},
|
|
result["attributes"],
|
|
result["containment"],
|
|
)
|
|
if _has_value(overlay):
|
|
result["deployment_overlay"] = overlay
|
|
for key in ("canon_category", "canon_anchor", "mapping_fit"):
|
|
if node.get(key):
|
|
result[key] = node[key]
|
|
return result
|
|
|
|
|
|
def _financial_edge_from_legacy(edge: dict[str, Any]) -> dict[str, Any]:
|
|
result = {
|
|
"from": edge.get("from", ""),
|
|
"to": edge.get("to", ""),
|
|
"type": edge.get("type", ""),
|
|
"attributes": _object(edge.get("attributes")),
|
|
}
|
|
for key in ("canonical_type", "canon_anchor", "mapping_fit", "display_only", "evidence_state"):
|
|
if key in edge:
|
|
result[key] = edge[key]
|
|
overlay = normalize_deployment_overlay(
|
|
edge.get("deployment_overlay") if isinstance(edge.get("deployment_overlay"), dict) else {},
|
|
result["attributes"],
|
|
)
|
|
if _has_value(overlay):
|
|
result["deployment_overlay"] = overlay
|
|
return result
|
|
|
|
|
|
def _node_kind(node: dict[str, Any]) -> str:
|
|
kind = str(node.get("kind") or "")
|
|
if kind == "ServiceDeclaration":
|
|
return "Service"
|
|
if kind == "InterfaceDeclaration":
|
|
return "UtilityInterface"
|
|
return kind or "DiscoveredEntity"
|
|
|
|
|
|
def _object(value: Any) -> dict[str, Any]:
|
|
return value if isinstance(value, dict) else {}
|
|
|
|
|
|
def _has_value(value: Any) -> bool:
|
|
if isinstance(value, dict):
|
|
return any(_has_value(item) for item in value.values())
|
|
if isinstance(value, list):
|
|
return any(_has_value(item) for item in value)
|
|
return value not in (None, "")
|
|
|
|
|
|
def _utc_now() -> str:
|
|
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|