Skip to content

Metrics Engine

Layer: backend

Status — Wave 0 (2026-08-30): catalog, engine and the two endpoints are live. No existing caller has moved yet: /info, /admin/model-stats, /admin/export/full-model/info, the activity heatmap and /metrics/batch still compute (or shim) their own numbers until Wave 1; the detail panel and tree chips until Wave 3; EIDOS Dash until Wave 4.

Why an engine

Before Wave 0, metrics were computed per feature, on the fly, with divergent definitions: connections meant one thing in the tree chip and another in the detail panel; "changes" had two audit folds (chip vs heatmap); sub-items, descendants and parts were four counters over the same hierarchy; and EIDOS Dash re-implemented ~25 numbers from the export archive. Users could see numbers that disagreed, and nobody could look up what a number meant.

The Metrics Engine makes the definition the unit of ownership. A metric exists only if it is in the catalog; the engine computes only what the catalog declares; this page's table is generated from the catalog at build time.

Facts, not scores

The engine owns facts: counts and ratios of structural things that can be verified against disk. Weighted or composite scores (completeness, readiness, health, maturity) are dashboard policy and stay in EIDOS Dash — but must be defined purely over catalog ids. Instance-administration figures (users, snapshots, tier) are not metrics and remain in /admin/model-stats.

Scope, source, merge

  • scope node — computed for a list of node ids (compute_node); model — computed for one domain and merged across domains.
  • source — the only thing a compute function may read: graph (effective graph via the loader), audit (committed events via audit_service, read at most once per window — contract 13), iis (iis.storage). No direct disk or SQL (contract 01.01).
  • merge — how per-domain values become the model value: sum, max, mean (weighted by the declared inputs), dict_sum, or none (primary only). An unreachable secondary yields domains.<name>: null and partial: true; a merged number is never presented as complete when a contribution is missing.

Freshness

Model-scope results are memoised per (graph_version, date, days, ids). graph_version is the mutation store's cross-worker sentinel counter — every commit bumps it, so the cache dies by itself. There is no separate invalidation mechanism to keep in sync.

IIS-sourced metrics (interfaces_*) are the one exception: the IIS store has its own write path outside the graph sentinel, so they are computed fresh on every request rather than served from the memo cache.

Response shape

GET /metrics/model returns {scope, graph_version, computed_at, days, partial, metrics: {id: {value, unit, inputs?, findings?, domains}}, errors}. inputs carries denominators so a consumer can show the arithmetic; findings (capped at 200) lists the offending ids for quality facts. Formula text is in the catalog, not in the response.

Catalog

Relation-based node metrics (connections, type, location, unresolved and the *_label / *_uuid companions) read a node's effective outgoing relations — own plus inherited — from the canonical resolver domain/effective_relations.py, the same engine the detail panel renders from. Inheritance is decided there by the rulebook (relationInheritance, most-specific-wins, tier-gated) or a relation's inherit_source flag; the metric catalog itself never walks ancestors. GET /metrics/batch injects the live rulebook and tier gate into the engine (rulebook=, tier_can=), so a tree chip and the panel header always show the same location and type.

id scope unit source merge definition
subitems node count graph Direct children of the node (children_of[id]).
connections node count graph Effective (own + rulebook-inherited) outgoing internal relations deduped by (predicate, target) plus incoming relations; cross-domain references are excluded (see type/location).
type node count graph Effective outgoing cross-domain Type references (% aspect → 'type' domain): own, plus inherited where the rulebook (or the relation's inherit_source flag) says so; deduped on external_ref.
location node count graph Effective outgoing Location references (+ aspect): own, plus inherited where the rulebook (or the relation's inherit_source flag) says so — the detail panel's number; deduped on external_ref.
documents node count graph Engineering entries whose value matches ^(https?|file):// (case-insensitive).
specifications node count graph Engineering entries whose value is NOT such a URL.
unresolved node count graph Effective outgoing relations that cannot bind: an internal unresolved_ref that key_derivation.resolve_ref cannot bind, or a cross-domain ref whose domain has no public URL.
changes node count audit Committed audit events for the node in the last days days; Create/DeleteRelation are attributed to payload.sourceId.
location_label node label graph external_ref of the first effective location, '' if none (BL-FE-129).
location_uuid node label graph UUID ('to') of the first effective location node, '' if none (BL-FE-129).
type_label node label graph external_ref of the first effective type relation, '' if none (BL-FE-162).
type_uuid node label graph UUID ('to') of the first effective type node, '' if none (BL-FE-162).
descendants node count graph Size of the recursive subtree below the node, excluding the node itself.
changes_subtree node count audit changes summed over the node and every descendant (the activity heatmap number).
nodes_total model count graph sum Number of effective nodes (base + overlay − tombstoned) in the domain.
nodes_by_class model dict graph dict_sum Effective nodes grouped by class.
nodes_by_product_type model dict graph dict_sum Nodes grouped by their productType field (Functional System / Technical System / Component); nodes without one are excluded, never fabricated.
roots model count graph sum Nodes with no parent.
tree_edges model count graph sum Sum of direct children over all nodes (= Σ subitems).
relations_explicit model count graph sum Effective explicit relations (base + overlay − tombstoned).
relations_inherited model count graph sum Relations materialised by inheritance: each inherit_source relation counts once per descendant of its source node; tombstoned sources excluded.
relations_by_predicate model dict graph dict_sum relations_total grouped by predicate.
relations_cross_domain model count graph sum Relations whose target lies in another domain (external_domain set).
relations_total model count graph sum relations_explicit + relations_inherited.
overlay_nodes model count graph sum Nodes added by the mutation overlay.
overlay_relations model count graph sum Relations added by the mutation overlay.
tombstones_nodes model count graph sum Soft-deleted nodes (node tombstones).
tombstones_relations model count graph sum Soft-deleted relations (relation tombstones).
hierarchy_depth_max model count graph max Maximum root-to-node depth (root = 0).
hierarchy_depth_avg model ratio graph mean Mean root-to-leaf depth over all leaves (nodes without children).
max_container model id_count graph max Node with the most direct children.
max_relations_node model id_count graph max Node with the most effective relations (in + out).
orphan_nodes model id_count graph sum Isolated nodes: no parent, no children and no effective relations.
unresolved_refs model id_count graph sum Nodes with ≥1 unresolved outgoing relation (node unresolved > 0).
broken_relations model id_count graph sum Relations whose from is not an effective node, or whose to is set, internal, and not an effective node.
duplicate_designations model id_count graph sum Designations (external_ref) shared by ≥2 effective nodes in the domain.
predicate_coverage model dict graph mean {predicate: share of product nodes with ≥1 outgoing relation of that predicate} for every predicate in the relation catalog; a product node is a node whose productType is not null.
changes_total model count audit sum Committed audit events in the requested days window.
changes_7d model count audit sum Committed audit events in the last 7 days.
changes_30d model count audit sum Committed audit events in the last 30 days.
changes_by_day model dict audit dict_sum Committed audit events per calendar day, last 30 days.
active_users_30d model count audit sum Distinct actors with ≥1 committed audit event in the last 30 days.
interfaces_total model count iis none Detected interfaces (IIS store total).
interfaces_by_status model dict iis none Detected interfaces grouped by status.
interfaces_by_criticality model dict iis none Detected interfaces grouped by criticality class.