Files
portal/tools/missed-activations.mjs
T

47 lines
1.6 KiB
JavaScript

#!/usr/bin/env node
/**
* Missed-activation matcher (Pravila §16.4 v1.36 conditional rule).
* Pure deterministic — read-only, no exec, no fs.
*
* An episode is "missed" iff:
* 1. schema_version === 2 (v1 lacks factor data)
* 2. NOT observer_error
* 3. primary_rationale.task_classification ∈ map AND map[c].length > 0
* 4. primary_rationale.node_chosen === 'direct' (no explicit node)
* 5. AT LEAST ONE recommended node is non-dormant
*
* Threshold: single episode (per Pravila §16.4 v1.36).
* DEFERRED-узлы filtered via dormancy registry (dormancy[id] === true means
* unavailable — covers both Tooling-marked dormant nodes and DEFERRED-in-
* boundaries nodes, normalized by tools/extract-node-dormancy.mjs).
*/
export function detectMissedActivations(episodes, classificationMap, dormancy) {
const byNode = {};
const byClassification = {};
let totalMissed = 0;
for (const e of episodes) {
if (!e || e.observer_error) continue;
if (e.schema_version !== 2) continue;
const pr = e.primary_rationale || {};
const cls = pr.task_classification;
const chosen = pr.node_chosen;
if (!cls || chosen !== 'direct') continue;
const recommended = classificationMap[cls];
if (!Array.isArray(recommended) || recommended.length === 0) continue;
const live = recommended.filter((id) => dormancy[id] === false);
if (live.length === 0) continue;
totalMissed += 1;
byClassification[cls] = (byClassification[cls] || 0) + 1;
for (const id of live) {
byNode[id] = (byNode[id] || 0) + 1;
}
}
return { totalMissed, byNode, byClassification };
}