47 lines
1.6 KiB
JavaScript
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 };
|
|
}
|