@validation-os/core 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-22WJF5EZ.js → chunk-NIWHPSYD.js} +11 -2
- package/dist/chunk-NIWHPSYD.js.map +1 -0
- package/dist/{chunk-43RQBE2E.js → chunk-NTPWGMCT.js} +2 -2
- package/dist/chunk-NTPWGMCT.js.map +1 -0
- package/dist/derivation/index.d.ts +2 -2
- package/dist/derivation/index.js +5 -1
- package/dist/{index-BlXH_thK.d.ts → index-BL4ktiMU.d.ts} +29 -9
- package/dist/index.d.ts +28 -9
- package/dist/index.js +20 -18
- package/dist/index.js.map +1 -1
- package/dist/{provider-DGr5Xq5U.d.ts → provider-X5wgqIE_.d.ts} +3 -1
- package/dist/testing.d.ts +3 -2
- package/dist/testing.js +17 -7
- package/dist/testing.js.map +1 -1
- package/dist/{types-BAyl0w2E.d.ts → types-CX1gpi6l.d.ts} +36 -10
- package/package.json +1 -1
- package/dist/chunk-22WJF5EZ.js.map +0 -1
- package/dist/chunk-43RQBE2E.js.map +0 -1
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
// src/derivation/index.ts
|
|
6
6
|
var derivation_exports = {};
|
|
7
7
|
__export(derivation_exports, {
|
|
8
|
+
COMMITMENT_FOUND: () => COMMITMENT_FOUND,
|
|
8
9
|
COMPLETENESS_SLOTS: () => COMPLETENESS_SLOTS,
|
|
9
10
|
KILL_LANE_THRESHOLD: () => KILL_LANE_THRESHOLD,
|
|
10
11
|
MARKET_RUNG_ANCHOR: () => MARKET_RUNG_ANCHOR,
|
|
@@ -16,6 +17,7 @@ __export(derivation_exports, {
|
|
|
16
17
|
beliefRisk: () => beliefRisk,
|
|
17
18
|
beliefTestMeters: () => beliefTestMeters,
|
|
18
19
|
classifyStage: () => classifyStage,
|
|
20
|
+
commitmentFactor: () => commitmentFactor,
|
|
19
21
|
completenessSlotPresence: () => completenessSlotPresence,
|
|
20
22
|
confidence: () => confidence,
|
|
21
23
|
confidenceAttribution: () => confidenceAttribution,
|
|
@@ -143,11 +145,16 @@ function sourceQuality(representativeness, credibility) {
|
|
|
143
145
|
|
|
144
146
|
// src/derivation/confidence.ts
|
|
145
147
|
var W0 = 100;
|
|
148
|
+
var COMMITMENT_FOUND = 0.85;
|
|
149
|
+
function commitmentFactor(experimentId) {
|
|
150
|
+
return experimentId ? 1 : COMMITMENT_FOUND;
|
|
151
|
+
}
|
|
146
152
|
function scoreAndDedupe(readings) {
|
|
147
153
|
const scored = readings.filter((r) => isConcluded(r.result)).map((r) => {
|
|
148
154
|
const strength = readingStrength(r);
|
|
149
155
|
const sq = sourceQuality(r.representativeness, r.credibility);
|
|
150
|
-
|
|
156
|
+
const weight = Math.abs(strength) * sq * commitmentFactor(r.experimentId);
|
|
157
|
+
return { input: r, strength, sq, weight };
|
|
151
158
|
}).filter((x) => x.strength !== 0);
|
|
152
159
|
const best = /* @__PURE__ */ new Map();
|
|
153
160
|
for (const x of scored) {
|
|
@@ -551,6 +558,8 @@ export {
|
|
|
551
558
|
readingStrength,
|
|
552
559
|
sourceQuality,
|
|
553
560
|
W0,
|
|
561
|
+
COMMITMENT_FOUND,
|
|
562
|
+
commitmentFactor,
|
|
554
563
|
scoreAndDedupe,
|
|
555
564
|
confidence,
|
|
556
565
|
confidenceAttribution,
|
|
@@ -569,4 +578,4 @@ export {
|
|
|
569
578
|
assembleJourney,
|
|
570
579
|
derivation_exports
|
|
571
580
|
};
|
|
572
|
-
//# sourceMappingURL=chunk-
|
|
581
|
+
//# sourceMappingURL=chunk-NIWHPSYD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/derivation/index.ts","../src/derivation/round.ts","../src/types.ts","../src/derivation/rung.ts","../src/derivation/completeness.ts","../src/derivation/strength.ts","../src/derivation/source-quality.ts","../src/derivation/confidence.ts","../src/derivation/attribution.ts","../src/derivation/progress.ts","../src/derivation/trajectory.ts","../src/derivation/impact.ts","../src/derivation/risk.ts","../src/derivation/portfolio.ts","../src/derivation/next-move.ts","../src/derivation/stage.ts","../src/derivation/journey.ts"],"sourcesContent":["/**\n * The shared derivation module — pure functions, no I/O.\n *\n * The same module the dashboard, the API (derive-on-write), and Claude Code\n * audits all call, so every writer computes the four derived numbers\n * identically. Ported from `doshi-validation-os/migration/remodel.mjs` and\n * kept in lock-step with `skills/_shared/ontology.yaml`.\n */\nexport { round2 } from \"./round.js\";\nexport {\n RUNG_ANCHOR,\n MARKET_RUNG_ANCHOR,\n isMarketRung,\n} from \"./rung.js\";\nexport {\n COMPLETENESS_SLOTS,\n completenessSlotPresence,\n missingCompletenessSlots,\n assumptionCompleteness,\n assumptionComplete,\n} from \"./completeness.js\";\nexport type { CompletenessSlot, CompletenessInput } from \"./completeness.js\";\nexport { sign, isConcluded, readingStrength } from \"./strength.js\";\nexport type { StrengthInput } from \"./strength.js\";\nexport { sourceQuality } from \"./source-quality.js\";\nexport {\n confidence,\n scoreAndDedupe,\n commitmentFactor,\n COMMITMENT_FOUND,\n W0,\n} from \"./confidence.js\";\nexport type { ConfidenceReadingInput, Scored } from \"./confidence.js\";\nexport { confidenceAttribution } from \"./attribution.js\";\nexport type {\n Attribution,\n AttributionReadingInput,\n Mover,\n MoverKind,\n} from \"./attribution.js\";\nexport { experimentProgress } from \"./progress.js\";\nexport type { BarLineInput, Progress } from \"./progress.js\";\nexport { confidenceTrajectory } from \"./trajectory.js\";\nexport type { TrajectoryPoint } from \"./trajectory.js\";\nexport { derivedImpacts } from \"./impact.js\";\nexport type { ImpactAssumptionInput } from \"./impact.js\";\nexport { risk } from \"./risk.js\";\nexport { beliefRisk, portfolioProgress } from \"./portfolio.js\";\nexport type {\n BeliefRisk,\n PortfolioBeliefInput,\n PortfolioProgress,\n} from \"./portfolio.js\";\nexport { rankNextMoves, KILL_LANE_THRESHOLD } from \"./next-move.js\";\nexport type {\n MoveKind,\n NextMove,\n NextMoveAssumptionInput,\n NextMoveExperimentInput,\n NextMoveDecisionInput,\n NextMoveInput,\n} from \"./next-move.js\";\nexport {\n beliefTestMeters,\n classifyStage,\n deriveBeliefStage,\n emptyTestMeter,\n} from \"./stage.js\";\nexport type {\n BeliefStage,\n BeliefStageInput,\n ConfSign,\n StageExperimentInput,\n StageKey,\n TestMeter,\n} from \"./stage.js\";\nexport { assembleJourney } from \"./journey.js\";\nexport type {\n AssembleJourneyInput,\n JourneyBeliefInput,\n JourneyEvent,\n JourneyEventKind,\n JourneyExperimentInput,\n} from \"./journey.js\";\n","/**\n * Round to 2 decimals, matching the migration's `+(n).toFixed(2)`.\n * Derived values are stored/displayed rounded; full precision is only used\n * transiently inside a single computation.\n */\nexport function round2(n: number): number {\n return Number(n.toFixed(2));\n}\n","/**\n * Shared registry types — the field vocabulary the whole system speaks.\n *\n * These mirror `skills/_shared/registry-schema.md` and `ontology.yaml` (the\n * single source of truth). Field *meaning* lives there; this file is the\n * checkable TypeScript shape the packages import.\n */\n\n/**\n * The five registers. `goals` was unified into `experiments` and the `people`\n * reference collection retired (OPS-1305): Owner / Agreed by now reference a\n * dashboard user (the auth-sourced team list), not a register row.\n */\nexport const REGISTERS = [\n \"assumptions\",\n \"experiments\",\n \"readings\",\n \"decisions\",\n \"glossary\",\n] as const;\nexport type Register = (typeof REGISTERS)[number];\n\n/** There is no reference collection any more — a collection is a register. */\nexport type Collection = Register;\n\n/** Every stored record carries an id, a version, and timestamps. */\nexport interface BaseRecord {\n id: string;\n /** Optimistic-concurrency token; bumped on every write. */\n version: number;\n createdAt: string;\n updatedAt: string;\n}\n\n// ── Vocabularies (canonical select-option lists) ────────────────────────────\n\nexport type AssumptionStatus = \"Draft\" | \"Live\" | \"Invalidated\";\n/**\n * The unified evidence-plan lifecycle (OPS-1305). `Draft` is the new gate a\n * commit clears (Draft→Running); conclude+verdict stays the Running→Closed\n * gate. Absorbs what the retired Goal record's status used to carry.\n */\nexport type ExperimentStatus = \"Draft\" | \"Running\" | \"Closed\" | \"Archived\";\nexport type DecisionStatus =\n | \"Active\"\n | \"Provisional\"\n | \"Superseded\"\n | \"Reversed\";\nexport type GlossaryStatus = \"Active\" | \"Provisional\" | \"Superseded\";\n\nexport type Result = \"Validated\" | \"Invalidated\" | \"Inconclusive\";\nexport type MagnitudeBand = \"Low\" | \"Typical\" | \"High\";\nexport type Feasibility = \"High\" | \"Medium\" | \"Low\";\n\n/**\n * The 8-rung activity-and-strength ladder (order = strength, weakest first).\n * Two categories: Testing (recruited-sample instruments) and Market (open-world\n * targets — the category formerly called \"Goals\", renamed with the unification,\n * OPS-1305). The anchors and physics are unchanged by the rename.\n */\nexport const TESTING_RUNGS = [\n \"Opinion\",\n \"Pitch-deck reaction\",\n \"Anecdotal\",\n \"Desk research\",\n \"Survey at scale\",\n \"Prototype usage\",\n] as const;\nexport const MARKET_RUNG_VALUES = [\"Signed intent\", \"Paying users\"] as const;\nexport type TestingRung = (typeof TESTING_RUNGS)[number];\nexport type MarketRung = (typeof MARKET_RUNG_VALUES)[number];\nexport type Rung = TestingRung | MarketRung;\n\n/** Representativeness and Credibility are each picked from these. */\nexport type SourceQualityPick = 1.0 | 0.7 | 0.5;\n\n// ── Records ─────────────────────────────────────────────────────────────────\n\n/** The derived numbers stored on an assumption (never hand-typed). */\nexport interface AssumptionDerived {\n derivedImpact: number;\n risk: number;\n confidence: number;\n /** Structural readiness meter, 0–100 (see `derivation/completeness.ts`). */\n completeness: number;\n}\n\nexport interface AssumptionRecord extends BaseRecord {\n Title: string;\n Description: string;\n Lens: string | null;\n Theme: string[];\n /** The hand-scored seed (0–100), the only hand-scored number here. */\n Impact: number | null;\n Status: AssumptionStatus;\n /** Reference to a dashboard user (auth team list), not a `people` row. */\n Owner: string[];\n moot: boolean;\n /**\n * Kept, narrowed to the Impact-seed rationale (OPS-1305): why the seed\n * `Impact` was scored as it was, incl. dated moot lines. The `5 Whys`,\n * `Metric for truth`, and `Gaps` fields are gone — the why-trace lives in the\n * `Depends on / Enables` chain and the audit check-types are transient grill\n * stages, not stored tags. Readiness is the derived `completeness`.\n */\n \"Scoring justification\": string;\n dependsOnIds: string[];\n enablesIds: string[];\n contradictsIds: string[];\n readingIds: string[];\n derived: AssumptionDerived;\n}\n\n/**\n * One belief's scoring inside a reading — the per-assumption verdict an\n * artifact carries. A reading (one artifact ROW) may score several beliefs at\n * once, so the rung/result/justification that used to live on the row now live\n * here, one entry per assumption. Mirrors how {@link BarLine} embeds on an\n * experiment: no identity of its own, and its `strength` is derived (never\n * hand-typed). Source quality stays row-level — it is a property of the\n * artifact, not the belief.\n */\nexport interface BeliefScore {\n assumptionId: string;\n Rung: Rung;\n Result: Result;\n /** For Market-rung readings: the magnitude band from the absolute outcome. */\n magnitudeBand?: MagnitudeBand;\n /** The rationale for the rung / representativeness / credibility picks. */\n \"Grading justification\": string;\n /** Derived per belief: rung anchor × sign(Result) [× magnitude band]. */\n derived: { strength: number };\n /** Provenance: the original reading id this belief was migrated from. */\n sourceReadingId?: string;\n /** Optional review caveat, e.g. a second-hand-credibility note. */\n reviewNote?: string;\n}\n\nexport interface ReadingRecord extends BaseRecord {\n Title: string;\n /** The independence/dedupe key — the generator (person / dataset / cohort). */\n Source: string | null;\n /**\n * Provenance links (recording, dashboard, CRM row, user id) — 0..N, drives no\n * math and never keys dedupe (OPS-1305). Split out from `Source` so the\n * dedupe key stays narrow.\n */\n contextLinks: string[];\n /** The originating plan, or null for a bare/found reading (no Goal origin). */\n experimentId: string | null;\n Representativeness: SourceQualityPick;\n Credibility: SourceQualityPick;\n Date: string | null;\n Owner: string[];\n /** Free-text narrative of the reading. */\n body?: string;\n /** Per-belief scores — one artifact row can score several beliefs at once. */\n beliefs: BeliefScore[];\n /** Convenience projection of `beliefs[].assumptionId`; kept in sync on write. */\n assumptionIds: string[];\n derived: { sourceQuality: number };\n}\n\n/**\n * A pre-registered bar line — the per-belief pass/fail test on an experiment,\n * stored as an embedded array on the experiment (no identity of its own; see\n * `connectors/nosql-schema.md`). `barVerdict` is set once at closure and is a\n * report only — never folded into Confidence.\n */\nexport interface BarLine {\n assumptionId: string;\n rightIf: string;\n wrongIf?: string | null;\n plannedRung: Rung;\n barVerdict?: Result | null;\n}\n\nexport interface ExperimentRecord extends BaseRecord {\n Title: string;\n Instrument: string | null;\n Feasibility: Feasibility | null;\n Status: ExperimentStatus;\n /** Free-text narrative of the plan. */\n body?: string;\n closureReason: \"Completed\" | \"Early-stop\" | \"Kill\" | null;\n /** Optional deadline a committed plan carries (folded in from the Goal). */\n Deadline: string | null;\n /** Terminal closure outcome, null until Closed (folded in from the Goal). */\n Outcome: \"Achieved\" | \"Missed\" | \"Dropped\" | null;\n /** Reference to a dashboard user, not a `people` row. */\n Owner: string[];\n Date: string | null;\n /** The embedded pre-registered bar lines; drives progress-to-conclusion. */\n barLines: BarLine[];\n /** Convenience projection: the assumptions the bar lines test. */\n barLineAssumptionIds: string[];\n}\n\nexport interface DecisionRecord extends BaseRecord {\n Title: string;\n Status: DecisionStatus;\n /** The one-line statement of what was decided (promoted from the body). */\n Statement: string;\n /** Why the Unanimity score was scored as it was (promoted from the body). */\n \"Unanimity justification\": string;\n /** References to dashboard users (auth team list), not `people` rows. */\n Owner: string[];\n \"Agreed by\": string[];\n basedOnIds: string[];\n resolvesIds: string[];\n}\n\n/** One structured \"don't say\" entry on a glossary term. */\nexport interface GlossaryAvoid {\n audience: string;\n phrase: string;\n fix: string;\n}\n\nexport interface GlossaryRecord extends BaseRecord {\n Title: string;\n Status: GlossaryStatus;\n /** All properties, no body (OPS-1305): the terminology check parses these. */\n Definition: string;\n Avoid: GlossaryAvoid[];\n \"How it differs\": string;\n}\n\n/** A record of any register — the DataProvider's currency. */\nexport type AnyRecord = BaseRecord & Record<string, unknown>;\n\n// ── Relations (both-ends links) ─────────────────────────────────────────────\n\n/** A pointer to one record. */\nexport interface RecordRef {\n register: Collection;\n id: string;\n}\n\n/** The relations the dashboard can set (each writes both ends). */\nexport type Relation =\n | \"assumption-reading\"\n | \"assumption-depends-on\"\n | \"assumption-contradicts\"\n | \"reading-experiment\"\n | \"decision-based-on\"\n | \"decision-resolves\";\n","/**\n * The evidence ladder anchors that feed Strength.\n *\n * Source of truth: `skills/_shared/ontology.yaml` → `vocabularies.rung`.\n * Testing rungs carry a single anchor; Market rungs (the category formerly\n * called \"Goals\", OPS-1305) carry a magnitude band (Low/Typical/High) picked\n * from the absolute outcome. The anchors are unchanged by the rename.\n */\nimport type { MarketRung, MagnitudeBand, Rung, TestingRung } from \"../types.js\";\nimport { MARKET_RUNG_VALUES } from \"../types.js\";\n\nexport const RUNG_ANCHOR: Record<TestingRung, number> = {\n Opinion: 3,\n \"Pitch-deck reaction\": 6,\n Anecdotal: 10,\n \"Desk research\": 15,\n \"Survey at scale\": 25,\n \"Prototype usage\": 30,\n};\n\nexport const MARKET_RUNG_ANCHOR: Record<\n MarketRung,\n Record<MagnitudeBand, number>\n> = {\n \"Signed intent\": { Low: 55, Typical: 68, High: 80 },\n \"Paying users\": { Low: 75, Typical: 88, High: 99 },\n};\n\nconst MARKET_RUNG_SET = new Set<Rung>(MARKET_RUNG_VALUES);\n\nexport function isMarketRung(rung: Rung): rung is MarketRung {\n return MARKET_RUNG_SET.has(rung);\n}\n","/**\n * Assumption completeness — a derived readiness meter, never stored.\n *\n * Replaces the old Gaps / presence-field machinery (OPS-1305): an assumption's\n * Draft-vs-Live readiness is the structural presence of its slots, not a set of\n * hand-maintained tags. `Completeness %` is `filled slots / all slots`; a\n * fully-filled assumption reads 100 and is Live-ready, an empty draft reads low.\n *\n * Slots (each an equal fifth):\n * Description · Lens · Impact · Scoring justification · Dependencies traced\n *\n * Pure, no backend dependency — the same function the recompute pass stamps\n * into the derived tuple and the audit checks readiness against.\n */\n\n/** The structural slots whose presence makes an assumption Live-ready. */\nexport const COMPLETENESS_SLOTS = [\n \"Description\",\n \"Lens\",\n \"Impact\",\n \"Scoring justification\",\n \"Dependencies traced\",\n] as const;\n\nexport type CompletenessSlot = (typeof COMPLETENESS_SLOTS)[number];\n\n/** The record shape completeness reads — an assumption's structural fields. */\nexport interface CompletenessInput {\n Description?: unknown;\n Lens?: unknown;\n Impact?: unknown;\n \"Scoring justification\"?: unknown;\n dependsOnIds?: unknown;\n enablesIds?: unknown;\n}\n\n/** A text slot is present only when it is a non-blank string. */\nfunction hasText(value: unknown): boolean {\n return typeof value === \"string\" && value.trim().length > 0;\n}\n\n/** Impact is present when it is a real finite number (0 is a valid score). */\nfunction hasNumber(value: unknown): boolean {\n return typeof value === \"number\" && Number.isFinite(value);\n}\n\n/** Dependencies are traced once at least one Depends on / Enables link exists. */\nfunction hasAny(value: unknown): boolean {\n return Array.isArray(value) && value.length > 0;\n}\n\n/** Whether each slot is structurally present. */\nexport function completenessSlotPresence(\n record: CompletenessInput,\n): Record<CompletenessSlot, boolean> {\n return {\n Description: hasText(record.Description),\n Lens: hasText(record.Lens),\n Impact: hasNumber(record.Impact),\n \"Scoring justification\": hasText(record[\"Scoring justification\"]),\n \"Dependencies traced\":\n hasAny(record.dependsOnIds) || hasAny(record.enablesIds),\n };\n}\n\n/** The slots that are absent or blank on a record. */\nexport function missingCompletenessSlots(\n record: CompletenessInput,\n): CompletenessSlot[] {\n const present = completenessSlotPresence(record);\n return COMPLETENESS_SLOTS.filter((slot) => !present[slot]);\n}\n\n/** Completeness as a whole-number percentage (0, 20, 40, 60, 80, 100). */\nexport function assumptionCompleteness(record: CompletenessInput): number {\n const present = completenessSlotPresence(record);\n const filled = COMPLETENESS_SLOTS.filter((slot) => present[slot]).length;\n return Math.round((filled / COMPLETENESS_SLOTS.length) * 100);\n}\n\n/** True when every slot is present — the structural precondition to be Live. */\nexport function assumptionComplete(record: CompletenessInput): boolean {\n return missingCompletenessSlots(record).length === 0;\n}\n","/**\n * Strength — the signed reading value `s` the Confidence average reads.\n *\n * Formula (`ontology.yaml` → `derivations.strength`):\n * rung anchor (Market rungs: × magnitude band) × sign(Result)\n * — Validated positive, Invalidated negative; 0 unless Validated/Invalidated.\n */\nimport type { MagnitudeBand, Result, Rung } from \"../types.js\";\nimport { MARKET_RUNG_ANCHOR, RUNG_ANCHOR, isMarketRung } from \"./rung.js\";\n\nexport function sign(result: Result): -1 | 0 | 1 {\n if (result === \"Validated\") return 1;\n if (result === \"Invalidated\") return -1;\n return 0;\n}\n\n/** A reading counts toward Confidence only once concluded either way; an\n * Inconclusive reading carries no signal. The single definition the whole\n * derivation module (and its record→input mappers) share. */\nexport function isConcluded(result: Result): boolean {\n return result === \"Validated\" || result === \"Invalidated\";\n}\n\nexport interface StrengthInput {\n rung: Rung;\n result: Result;\n /** Only read for Market rungs; defaults to \"Typical\" when absent. */\n magnitudeBand?: MagnitudeBand;\n}\n\nexport function readingStrength(input: StrengthInput): number {\n const s = sign(input.result);\n if (s === 0) return 0; // Inconclusive contributes nothing.\n if (isMarketRung(input.rung)) {\n const band = input.magnitudeBand ?? \"Typical\";\n return MARKET_RUNG_ANCHOR[input.rung][band] * s;\n }\n return (RUNG_ANCHOR[input.rung] ?? 0) * s;\n}\n","/**\n * Source quality — Representativeness × Credibility.\n *\n * Scales a Reading's *weight* in the Confidence average, within its rung.\n * We keep the raw product as the weight (matching the migration's\n * `remodel.mjs`); the five display anchors {0.25, 0.35, 0.5, 0.7, 1.0} are a\n * storage/display concern, not the weight used in the average.\n */\nimport { round2 } from \"./round.js\";\n\nexport function sourceQuality(\n representativeness: number,\n credibility: number,\n): number {\n return round2(representativeness * credibility);\n}\n","/**\n * Confidence — signed −100…100, 0 = no evidence.\n *\n * Formula (`ontology.yaml` → `derivations.confidence`):\n * (w0·0 + Σ wi·si) / (w0 + Σ wi), w0 = 100,\n * wi = |si| × Source quality × commitment, si = the reading's signed Strength,\n * commitment = 1.0 for an experiment-linked reading, 0.85 for a found one.\n *\n * Only concluded Validated/Invalidated readings enter. Readings sharing a\n * Source against one belief dedupe to the strongest (largest |si|, most\n * recent on ties). Market-rung readings never dedupe (each closed commitment\n * is its own unit). No corroboration bump.\n */\nimport type { MagnitudeBand, Result, Rung } from \"../types.js\";\nimport { round2 } from \"./round.js\";\nimport { isMarketRung } from \"./rung.js\";\nimport { sourceQuality } from \"./source-quality.js\";\nimport { isConcluded, readingStrength } from \"./strength.js\";\n\n/** The neutral prior weight — a hard floor per the guardrails. */\nexport const W0 = 100;\n\n/**\n * Commitment factor for a *found* reading — one with no originating experiment.\n * A pre-registered (experiment-linked) reading weighs at full commitment (1.0);\n * a found reading is discounted to this. It is a SMALL tiebreaker: it scales the\n * weight only, never the Strength, so it can never reorder readings across rungs\n * (\"Rung dominates\").\n */\nexport const COMMITMENT_FOUND = 0.85;\n\nexport interface ConfidenceReadingInput {\n id: string;\n /** The independence-dedupe key. Null falls back to the reading's own id. */\n source: string | null;\n rung: Rung;\n result: Result;\n representativeness: number;\n credibility: number;\n /** ISO date; used only as the dedupe tie-break (most recent wins). */\n date?: string | null;\n magnitudeBand?: MagnitudeBand;\n /**\n * The originating experiment, or null/undefined for a *found* reading. Drives\n * the commitment factor in the weight (found → {@link COMMITMENT_FOUND}).\n */\n experimentId?: string | null;\n}\n\n/** The commitment weighting for a reading: full for committed, discounted for found. */\nexport function commitmentFactor(experimentId: string | null | undefined): number {\n return experimentId ? 1.0 : COMMITMENT_FOUND;\n}\n\nexport interface Scored {\n input: ConfidenceReadingInput;\n strength: number;\n sq: number;\n /** The reading's weight in the average: |strength| × Source quality × commitment. */\n weight: number;\n}\n\n/**\n * Score every concluded reading and resolve the Source dedupe — the shared\n * front half of the Confidence average. `confidence()` reduces the winners to\n * a number; `confidenceAttribution()` reuses the same winners so the movers it\n * reports always decompose the very number the drawer shows. Market rungs never\n * dedupe (each closed commitment is its own unit).\n */\nexport function scoreAndDedupe(readings: ConfidenceReadingInput[]): Scored[] {\n const scored: Scored[] = readings\n .filter((r) => isConcluded(r.result))\n .map((r) => {\n const strength = readingStrength(r);\n const sq = sourceQuality(r.representativeness, r.credibility);\n const weight = Math.abs(strength) * sq * commitmentFactor(r.experimentId);\n return { input: r, strength, sq, weight };\n })\n .filter((x) => x.strength !== 0);\n\n const best = new Map<string, Scored>();\n for (const x of scored) {\n if (isMarketRung(x.input.rung)) {\n best.set(x.input.id, x);\n continue;\n }\n const key = x.input.source || x.input.id;\n const cur = best.get(key);\n const better =\n !cur ||\n Math.abs(x.strength) > Math.abs(cur.strength) ||\n (Math.abs(x.strength) === Math.abs(cur.strength) &&\n (x.input.date || \"\") > (cur.input.date || \"\"));\n if (better) best.set(key, x);\n }\n return [...best.values()];\n}\n\nexport function confidence(readings: ConfidenceReadingInput[]): number {\n const winners = scoreAndDedupe(readings);\n let num = 0;\n let den = W0;\n for (const x of winners) {\n num += x.weight * x.strength;\n den += x.weight;\n }\n return den > 0 ? round2(num / den) : 0;\n}\n","/**\n * Confidence attribution — the \"what's moving the number\" half of the\n * understanding layer (OPS-1276). Decomposes an assumption's Confidence into\n * the experiments (and direct readings) contributing to it, ranked by how hard\n * each pushes the number up or down.\n *\n * A winner's contribution is its signed share of the average:\n * cᵢ = (weightᵢ · strengthᵢ) / den, den = w0 + Σ weight\n * so Σ contributions = Σ(wᵢ·sᵢ)/den = Confidence (the w0·0 prior term is 0).\n * The reveal therefore literally adds up to the hero number. Contributions are\n * grouped by the reading's experiment (experiment-less readings — bare/found —\n * fall into a \"direct\" bucket), then ranked by |contribution|. A reading's\n * origin is an experiment or nothing; the retired Goal container is gone\n * (OPS-1305).\n */\nimport { W0, scoreAndDedupe, type ConfidenceReadingInput } from \"./confidence.js\";\nimport { round2 } from \"./round.js\";\n\n/**\n * The reading shape the attribution reveal consumes. `experimentId` (the\n * grouping key, and the commitment-factor driver) lives on\n * {@link ConfidenceReadingInput}; this alias marks the attribution call sites.\n */\nexport type AttributionReadingInput = ConfidenceReadingInput;\n\n/** What a mover is anchored to — an experiment, or nothing (direct). */\nexport type MoverKind = \"experiment\" | \"direct\";\n\nexport interface Mover {\n /** Stable grouping key: the experiment id, or \"direct\". */\n key: string;\n kind: MoverKind;\n /** The experiment id when `kind === \"experiment\"`, else null. */\n experimentId: string | null;\n /** Signed push on Confidence; the whole set sums to `confidence`. */\n contribution: number;\n /** |contribution| — the rank key and the \"how hard\" magnitude. */\n magnitude: number;\n /** How many (deduped) readings back this mover. */\n readingCount: number;\n /** The winning readings' ids, for drill-through. */\n readingIds: string[];\n}\n\nexport interface Attribution {\n /** The same Confidence the derived box shows (from the same winners). */\n confidence: number;\n /** Movers ranked by |contribution|, strongest first. */\n movers: Mover[];\n}\n\nfunction bucketOf(r: AttributionReadingInput): {\n key: string;\n kind: MoverKind;\n experimentId: string | null;\n} {\n if (r.experimentId) {\n return {\n key: r.experimentId,\n kind: \"experiment\",\n experimentId: r.experimentId,\n };\n }\n return { key: \"direct\", kind: \"direct\", experimentId: null };\n}\n\nexport function confidenceAttribution(\n readings: AttributionReadingInput[],\n): Attribution {\n const winners = scoreAndDedupe(readings);\n const den = winners.reduce((acc, x) => acc + x.weight, W0);\n\n const byKey = new Map<string, Mover>();\n let num = 0;\n for (const x of winners) {\n num += x.weight * x.strength;\n const b = bucketOf(x.input as AttributionReadingInput);\n const contribution = (x.weight * x.strength) / den;\n const cur = byKey.get(b.key);\n if (cur) {\n cur.contribution += contribution;\n cur.readingCount += 1;\n cur.readingIds.push(x.input.id);\n } else {\n byKey.set(b.key, {\n key: b.key,\n kind: b.kind,\n experimentId: b.experimentId,\n contribution,\n magnitude: 0, // finalised below\n readingCount: 1,\n readingIds: [x.input.id],\n });\n }\n }\n\n const movers = [...byKey.values()].map((m) => ({\n ...m,\n contribution: round2(m.contribution),\n magnitude: round2(Math.abs(m.contribution)),\n }));\n movers.sort((a, b) => b.magnitude - a.magnitude);\n\n return {\n confidence: den > 0 ? round2(num / den) : 0,\n movers,\n };\n}\n","/**\n * Progress-to-conclusion — how close a running experiment is to concluding\n * (OPS-1276). An experiment concludes when every pre-registered bar line has\n * been given a Bar verdict; progress is the count of settled bars against the\n * total. Bar verdict is a report, never a Confidence input (nosql-schema.md),\n * so reading it here is exactly its intended use.\n */\nexport interface BarLineInput {\n /** null/\"\" until the bar is judged at closure. */\n barVerdict?: string | null;\n}\n\nexport interface Progress {\n /** Pre-registered bar lines on the experiment. */\n total: number;\n /** Bars that have a verdict. */\n settled: number;\n /** Bars still awaiting a verdict. */\n toGo: number;\n /** True once every bar is settled (and there is at least one). */\n concluded: boolean;\n}\n\nexport function experimentProgress(bars: BarLineInput[]): Progress {\n const total = bars.length;\n const settled = bars.filter(\n (b) => typeof b.barVerdict === \"string\" && b.barVerdict.trim() !== \"\",\n ).length;\n return {\n total,\n settled,\n toGo: total - settled,\n concluded: total > 0 && settled === total,\n };\n}\n","/**\n * Confidence over time — the story of how the number got where it is\n * (OPS-1276). At each date a concluded reading was dated, we recompute\n * Confidence over every concluded reading up to and including that date, using\n * the very same `confidence()` the derived box uses (so the last point equals\n * the hero number). Undated concluded readings have no place on the timeline\n * but still bear on the belief, so they are treated as always-present — folded\n * into every point — which keeps the final point equal to today's Confidence.\n */\nimport { confidence, type ConfidenceReadingInput } from \"./confidence.js\";\nimport { isConcluded } from \"./strength.js\";\n\nexport interface TrajectoryPoint {\n /** ISO date at which Confidence took this value. */\n date: string;\n confidence: number;\n}\n\nexport function confidenceTrajectory(\n readings: ConfidenceReadingInput[],\n): TrajectoryPoint[] {\n const concluded = readings.filter((r) => isConcluded(r.result));\n const undated = concluded.filter((r) => !r.date);\n const dated = concluded.filter((r) => r.date);\n if (dated.length === 0) return [];\n\n const dates = [...new Set(dated.map((r) => r.date as string))].sort();\n return dates.map((date) => ({\n date,\n // Everything dated on/before this point, plus the always-present undated.\n confidence: confidence([\n ...undated,\n ...dated.filter((r) => (r.date as string) <= date),\n ]),\n }));\n}\n","/**\n * Derived Impact — propagates dependents' pull into a belief's seed.\n *\n * Formula (`ontology.yaml` → `derivations.derived_impact`):\n * seed + (100 − seed) × S / (S + 100), where\n * S = Σ Derived Impact of assumptions whose `Depends on` names this row\n * + 100 per standing (Provisional/Active) decision naming it via\n * `Based on assumption`.\n * Goals never contribute. Moot rows pin to 0 and contribute nothing.\n *\n * One reverse-topological pass (dependents first) with memoization and a\n * cycle guard, matching `doshi-validation-os/migration/remodel.mjs`.\n */\nimport { round2 } from \"./round.js\";\n\nexport interface ImpactAssumptionInput {\n id: string;\n /** The hand-scored seed (0–100); null treated as 0. */\n impact: number | null;\n moot?: boolean;\n /** Ids this assumption depends on. */\n dependsOnIds: string[];\n}\n\n/**\n * @param assumptions the full register (never a filtered slice — a filter\n * silently drops dependents from the propagation).\n * @param basedOnCounts id → number of standing decisions with a `Based on`\n * link to that assumption. Each contributes +100 to S.\n */\nexport function derivedImpacts(\n assumptions: ImpactAssumptionInput[],\n basedOnCounts: Record<string, number> = {},\n): Map<string, number> {\n const byId = new Map(assumptions.map((a) => [a.id, a]));\n\n // dependents(X) = assumptions whose dependsOnIds includes X.\n const dependents = new Map<string, string[]>();\n for (const a of assumptions) dependents.set(a.id, []);\n for (const a of assumptions) {\n for (const dep of a.dependsOnIds) dependents.get(dep)?.push(a.id);\n }\n\n const memo = new Map<string, number>();\n const compute = (id: string, seen: Set<string>): number => {\n const cached = memo.get(id);\n if (cached !== undefined) return cached;\n if (seen.has(id)) return 0; // cycle guard\n seen.add(id);\n const a = byId.get(id);\n if (!a) return 0;\n if (a.moot) {\n memo.set(id, 0);\n return 0;\n }\n const seed = a.impact ?? 0;\n let S = 0;\n for (const depId of dependents.get(id) ?? []) {\n const d = byId.get(depId);\n if (d && !d.moot) S += compute(depId, seen);\n }\n S += 100 * (basedOnCounts[id] ?? 0);\n const value = seed + (100 - seed) * (S / (S + 100));\n const rounded = round2(value);\n memo.set(id, rounded);\n return rounded;\n };\n\n const out = new Map<string, number>();\n for (const a of assumptions) out.set(a.id, compute(a.id, new Set()));\n return out;\n}\n","/**\n * Risk — the belief's live standing.\n *\n * Formula (`ontology.yaml` → `derivations.risk`):\n * Derived Impact × (1 − max(0, Confidence) / 100)\n * Ranges 0 to Derived Impact. Negative confidence does not raise Risk above\n * Derived Impact (the max(0, …) clamp).\n */\nimport { round2 } from \"./round.js\";\n\nexport function risk(derivedImpact: number, confidence: number): number {\n return round2(derivedImpact * (1 - Math.max(0, confidence) / 100));\n}\n","/**\n * Portfolio progress — the one *cross-belief* reading (OPS-1293 / OPS-1300).\n *\n * Every other derived number here is per-record (risk/confidence/impact of one\n * belief). This is the whole-set roll-up behind the pipeline's headline: a\n * **burn-up**, \"% of identified risk bought down\" = Risk Retired ÷ Risk-ever-\n * identified across *all* beliefs, resolved ones included.\n *\n * Like `rankNextMoves`, it is a whole-set ordering computed **fresh on read**,\n * not stored — it stays out of the OPS-1251 on-write recompute (it only reads\n * numbers already kept current). Pure and numeric: the dashboard maps records\n * to these inputs (as `understanding.ts` maps readings), so the rule lives\n * once, here.\n *\n * The model (matches the OPS-1296 prototype's self-consistent maths):\n * - **ever-identified** for a belief = the risk it represented at Confidence\n * ≤ 0, i.e. its Derived Impact. A moot row's Derived Impact is pinned to 0,\n * which would erase it from *both* sides of the fraction, so we floor\n * ever-identified at the hand-scored seed Impact — mooting a belief must\n * *retire* its risk, never shrink the denominator (the burn-up's whole\n * point: resolved risk stays counted).\n * - **live** risk = the belief's current Risk, or 0 once resolved (a kill or\n * a moot resolves the uncertainty — that risk is bought down, not carried).\n * - **retired** = ever-identified − live.\n */\nimport { round2 } from \"./round.js\";\n\nexport interface PortfolioBeliefInput {\n id: string;\n /** Derived Impact — the belief's risk at Confidence ≤ 0 (0 when moot). */\n derivedImpact: number;\n /** The hand-scored seed Impact; the ever-identified floor when moot zeroes\n * Derived Impact. Null/absent treated as 0. */\n seedImpact: number | null;\n /** The belief's current live Risk (stored `derived.risk`). */\n risk: number;\n /** Resolved — Invalidated (killed) or moot. Its live risk reads 0 (fully\n * retired), whatever the stored Risk number says. */\n resolved: boolean;\n}\n\n/** One belief's contribution to the burn-up. */\nexport interface BeliefRisk {\n /** Risk-ever-identified — the denominator's per-belief share. */\n identified: number;\n /** Live risk still carried (0 once resolved). */\n live: number;\n /** Risk bought down — the numerator's per-belief share. */\n retired: number;\n}\n\nexport interface PortfolioProgress {\n /** Σ risk-ever-identified — the burn-up denominator. */\n identified: number;\n /** Σ risk bought down — the burn-up numerator. */\n retired: number;\n /** Σ risk still live. */\n live: number;\n /** Retired ÷ identified as a percentage (0 when nothing is identified). */\n percent: number;\n /** Beliefs still in play (not resolved). */\n liveCount: number;\n /** Beliefs resolved (killed or moot). */\n resolvedCount: number;\n}\n\n/** One belief's identified / live / retired risk — the rule in one place. */\nexport function beliefRisk(b: PortfolioBeliefInput): BeliefRisk {\n const seed = b.seedImpact ?? 0;\n const identified = Math.max(b.derivedImpact, seed, 0);\n // Live risk can never exceed what was ever identified — the clamp keeps a\n // stale/over-large stored Risk from making retired go negative.\n const live = b.resolved ? 0 : Math.min(identified, Math.max(0, b.risk));\n return {\n identified: round2(identified),\n live: round2(live),\n retired: round2(identified - live),\n };\n}\n\n/**\n * Roll every belief up into the portfolio burn-up. Pass the *whole* set\n * (resolved rows included) — a filtered slice understates the denominator and\n * makes fresh or retired risk read as backsliding.\n */\nexport function portfolioProgress(\n beliefs: PortfolioBeliefInput[],\n): PortfolioProgress {\n let identified = 0;\n let live = 0;\n let liveCount = 0;\n let resolvedCount = 0;\n for (const b of beliefs) {\n const r = beliefRisk(b);\n identified += r.identified;\n live += r.live;\n if (b.resolved) resolvedCount += 1;\n else liveCount += 1;\n }\n const retired = identified - live;\n return {\n identified: round2(identified),\n retired: round2(retired),\n live: round2(live),\n percent: identified > 0 ? round2((retired / identified) * 100) : 0,\n liveCount,\n resolvedCount,\n };\n}\n","/**\n * Next-move ranking — the front door's single source of truth for \"what should\n * I do next\" (build OPS-1304; placement OPS-1292; action vocabulary OPS-1291).\n *\n * Ranks *beliefs* — Model A: point at one belief, not a heterogeneous triage\n * queue (OPS-1291) — by the method's Feasibility × Risk rule (`docs/method.md`,\n * `ontology.yaml` → `derived_views.next_move`), and names the single act each\n * belief's stage demands. A belief at Confidence ≤ −50 jumps into a distinct\n * kill/re-test lane that sorts above the Feasibility × Risk order regardless of\n * rank — the one place act-urgency beats belief-risk (`derived_views.kill_lane`).\n *\n * Computed fresh on read: a whole-set ordering, so it stays OUT of the OPS-1251\n * on-write recompute — it reads the derived numbers (Risk, Confidence) those\n * writes already keep current. Pure: no I/O, no caching, no weights framework —\n * the enum→multiplier map below IS the formula (OPS-1292: \"no weights /\n * strategies / caching / framework\").\n */\nimport type { Feasibility } from \"../types.js\";\nimport { round2 } from \"./round.js\";\n\n/** The kill/re-test threshold — Confidence at or below this is the kill lane. */\nexport const KILL_LANE_THRESHOLD = -50;\n\n/**\n * The acts the front door can name, one per belief-stage. The front door\n * *names* all of them; only a subset are human step-in forms — the rest are\n * agent-run / off-dashboard (OPS-1294). Which is which is a presentation\n * concern the dashboard owns, not this ranking.\n */\nexport type MoveKind =\n | \"score-impact\" // Framed — the belief isn't weighted yet (Impact unscored)\n | \"design-experiment\" // Planned — no test plans this belief yet\n | \"record-reading\" // Tested — a test is running, evidence still landing\n | \"decide\" // Known — evidence has landed and no decision rests on it\n | \"retest\"; // kill lane — Confidence ≤ −50, jumps the ordering\n\n/**\n * One belief's next move. `move`/`score`/`reason` are the OPS-1292 output\n * contract; the rest is context the front door renders (the risk chip, the kill\n * banner, the step-in adaptation) — every field read from the inputs, nothing\n * new computed here.\n */\nexport interface NextMove {\n /** The act this belief's stage demands. */\n move: MoveKind;\n assumptionId: string;\n /** The belief statement — the hero headline. */\n title: string;\n /** Feasibility × Risk. Kill-lane rows carry their Risk and sort first. */\n score: number;\n /** Plain-language \"why this\" — explains from the inputs, no jargon. */\n reason: string;\n /** The belief's live derived Risk (0–100). */\n risk: number;\n /** The belief's live derived Confidence (signed −100…100). */\n confidence: number;\n /** The feasibility that fed the score; null when no test plans it yet. */\n feasibility: Feasibility | null;\n /** Confidence ≤ −50 — the override lane. */\n killLane: boolean;\n}\n\n/** A belief and its live derived numbers (the ranking's primary input). */\nexport interface NextMoveAssumptionInput {\n id: string;\n title: string;\n /** AssumptionStatus; `Invalidated` rows are already killed and drop out. */\n status: string;\n /** The hand-scored seed (0–100); null means unscored → \"score impact\". */\n impact: number | null;\n /** Mooted beliefs (Impact pinned to 0 by a decision) drop out of ranking. */\n moot: boolean;\n /** Derived Risk (already recomputed on write — read, never recomputed). */\n risk: number;\n /** Derived Confidence (already recomputed on write). */\n confidence: number;\n /** How many concluded (Validated/Invalidated) readings back this belief. */\n concludedReadings: number;\n}\n\n/** An experiment, reduced to what stage + feasibility resolution needs. */\nexport interface NextMoveExperimentInput {\n /** ExperimentStatus — \"Running\" | \"Closed\". */\n status: string;\n feasibility: Feasibility | null;\n /** The assumption ids this experiment's bar lines name. */\n assumptionIds: string[];\n}\n\n/** A decision, reduced to which beliefs it rests on or resolves. */\nexport interface NextMoveDecisionInput {\n /** DecisionStatus; only standing (Active/Provisional) decisions count. */\n status: string;\n /** The assumption ids this decision rests on (`based on`) or resolves. */\n assumptionIds: string[];\n}\n\nexport interface NextMoveInput {\n assumptions: NextMoveAssumptionInput[];\n experiments: NextMoveExperimentInput[];\n decisions: NextMoveDecisionInput[];\n}\n\n/**\n * Feasibility → the score multiplier. Cheaper (more feasible) tests rank\n * higher, so the cheapest honest test of the riskiest belief is on top\n * (`method.md`). A belief with no planned test yet has unknown feasibility →\n * the neutral middle, so Risk decides its place honestly.\n */\nconst FEASIBILITY_WEIGHT: Record<Feasibility, number> = {\n High: 1,\n Medium: 0.6,\n Low: 0.3,\n};\nconst NEUTRAL_FEASIBILITY_WEIGHT = FEASIBILITY_WEIGHT.Medium;\n\nfunction isStanding(decisionStatus: string): boolean {\n return decisionStatus === \"Active\" || decisionStatus === \"Provisional\";\n}\n\n/** Pick the cheapest (highest-weight) feasibility among a belief's tests. */\nfunction bestFeasibility(\n feasibilities: (Feasibility | null)[],\n): Feasibility | null {\n let best: Feasibility | null = null;\n let bestWeight = -1;\n for (const f of feasibilities) {\n if (!f) continue;\n const w = FEASIBILITY_WEIGHT[f];\n if (w > bestWeight) {\n bestWeight = w;\n best = f;\n }\n }\n return best;\n}\n\n/**\n * The act a belief's stage demands (before the kill-lane override).\n *\n * Deliberately NOT the `stage.ts` structural classifier: that answers \"how far\n * along the Framed→Planned→Tested→Known artifacts are\" (framing completeness,\n * bar-line settlement); this answers \"what should the founder do next\" from the\n * evidence state (is it weighted, has evidence concluded, does a test run, does\n * a decision rest on it). Same belief can be structurally `tested` yet owe a\n * `decide` here — the journey rail (stage) and next-move card (act) are two\n * readings of one belief, so they share the test-plan *meter* (`beliefTestMeters`)\n * but not the classifier.\n */\nfunction stageMove(\n a: NextMoveAssumptionInput,\n hasRunningTest: boolean,\n hasAnyTest: boolean,\n hasStandingDecision: boolean,\n): MoveKind | null {\n if (a.impact === null) return \"score-impact\"; // Framed → weight it\n if (a.concludedReadings === 0) {\n // No evidence yet: if a test is running, wait for readings; else plan one.\n return hasRunningTest ? \"record-reading\" : \"design-experiment\";\n }\n // Evidence has landed. If nothing rests on it yet, it's time to decide;\n // once a standing decision does, the belief is resolved and drops out.\n if (!hasStandingDecision) return \"decide\";\n return null;\n}\n\n/** Plain-language \"why this\" — explains the move from the inputs. */\nfunction reasonFor(move: MoveKind, a: NextMoveAssumptionInput): string {\n switch (move) {\n case \"retest\":\n return `Confidence has fallen to ${Math.round(a.confidence)} — the evidence is turning against this belief. Kill it or test it again.`;\n case \"score-impact\":\n return \"This belief isn't weighted yet — score its impact so its risk can rank against the rest.\";\n case \"design-experiment\":\n return `Your riskiest untested belief. Design the cheapest honest test that could move it.`;\n case \"record-reading\":\n return \"A test is running against this belief — evidence is still landing.\";\n case \"decide\":\n return \"The evidence is in and nothing rests on it yet — time to make the call.\";\n }\n}\n\n/**\n * Rank every unresolved belief into its next move (Model A). Returns a plain\n * sorted list, most-pressing first: the kill lane on top (by Risk), then the\n * rest by Feasibility × Risk, tie-broken by the most-negative Confidence\n * (`derived_views.test_next_surface`), then by id for a stable order. The front\n * door takes the head as the hero and the tail as \"On deck\" / manual override.\n */\nexport function rankNextMoves(input: NextMoveInput): NextMove[] {\n const { assumptions, experiments, decisions } = input;\n\n // Which experiments (and their feasibilities/statuses) name each belief.\n const testsByAssumption = new Map<\n string,\n { running: boolean; any: boolean; feasibilities: (Feasibility | null)[] }\n >();\n for (const exp of experiments) {\n const running = exp.status === \"Running\";\n for (const id of exp.assumptionIds) {\n const entry = testsByAssumption.get(id) ?? {\n running: false,\n any: false,\n feasibilities: [],\n };\n entry.any = true;\n entry.running = entry.running || running;\n entry.feasibilities.push(exp.feasibility);\n testsByAssumption.set(id, entry);\n }\n }\n\n // Which beliefs a standing decision rests on or resolves.\n const decidedAssumptions = new Set<string>();\n for (const d of decisions) {\n if (!isStanding(d.status)) continue;\n for (const id of d.assumptionIds) decidedAssumptions.add(id);\n }\n\n const moves: NextMove[] = [];\n for (const a of assumptions) {\n if (a.moot || a.status === \"Invalidated\") continue;\n\n const killLane = a.confidence <= KILL_LANE_THRESHOLD;\n const tests = testsByAssumption.get(a.id);\n const feasibility = bestFeasibility(tests?.feasibilities ?? []);\n\n const move: MoveKind | null = killLane\n ? \"retest\"\n : stageMove(\n a,\n tests?.running ?? false,\n tests?.any ?? false,\n decidedAssumptions.has(a.id),\n );\n if (move === null) continue; // resolved — no move\n\n const weight = feasibility\n ? FEASIBILITY_WEIGHT[feasibility]\n : NEUTRAL_FEASIBILITY_WEIGHT;\n // Kill-lane rows carry their raw Risk so they order among themselves by\n // Risk; the killLane flag (not the score) floats them above everyone else.\n const score = round2(killLane ? a.risk : weight * a.risk);\n\n moves.push({\n move,\n assumptionId: a.id,\n title: a.title,\n score,\n reason: reasonFor(move, a),\n risk: a.risk,\n confidence: a.confidence,\n feasibility,\n killLane,\n });\n }\n\n moves.sort((x, y) => {\n if (x.killLane !== y.killLane) return x.killLane ? -1 : 1;\n if (y.score !== x.score) return y.score - x.score;\n if (x.confidence !== y.confidence) return x.confidence - y.confidence;\n return x.assumptionId < y.assumptionId ? -1 : 1;\n });\n return moves;\n}\n","/**\n * Per-belief stage — where one belief sits on the loop's four-stage spine\n * (Framed → Planned → Tested → Known) and its four meters (OPS-1329).\n *\n * This is the *single-belief* analogue of the pipeline's cross-belief roll-up:\n * the pipeline row-builder used to derive framing / test-plan / test-progress\n * inline, and the front-door move ladder (OPS-1304) re-walked the same stages a\n * second way. The shared classification lives here now, so the pipeline board,\n * the front door, and the per-belief journey rail all read one rule rather than\n * three that can drift.\n *\n * Pure and computed fresh on read (like `portfolio.ts` / `next-move.ts`): it\n * only reads numbers already kept current, so it stays out of the OPS-1251\n * on-write recompute. The record → meter mapping (bar lines, presence fields)\n * lives in the dashboard, as elsewhere; this module takes the reduced inputs.\n */\nimport { KILL_LANE_THRESHOLD } from \"./next-move.js\";\n\n/** The four loop stages a belief travels, in order (OPS-1293). */\nexport type StageKey = \"framed\" | \"planned\" | \"tested\" | \"known\";\n\n/** Sign of a belief's Confidence — the Known meter's direction. */\nexport type ConfSign = \"pos\" | \"neg\" | \"zero\";\n\n/** One belief's test state, aggregated across every experiment aimed at it. */\nexport interface TestMeter {\n /** A bar line (or the convenience projection) names this belief. */\n planned: boolean;\n /** Pre-registered bars that have a verdict. */\n settled: number;\n /** Pre-registered bars in total. */\n total: number;\n}\n\n/** A test's bar lines, reduced to what the meter needs (register-agnostic). */\nexport interface StageExperimentInput {\n /** Each bar line naming a belief, and whether it has settled (has a verdict). */\n bars: { assumptionId: string; settled: boolean }[];\n /** Beliefs this test plans via the convenience projection (bars may be unexpanded). */\n plannedAssumptionIds: string[];\n}\n\n/** The reduced inputs one belief's stage is derived from. */\nexport interface BeliefStageInput {\n /** Framing completeness, 0–100 (`assumptionCompleteness`). */\n framed: number;\n /** Live derived Confidence (signed −100…100). */\n confidence: number;\n /** This belief's aggregated test state. */\n test: TestMeter;\n}\n\n/** One belief's position on the spine plus its four meters. */\nexport interface BeliefStage {\n /** Where the belief sits: framed → planned → tested → known. */\n stage: StageKey;\n /** Meter 1 — framing completeness, 0–100. */\n framed: number;\n /** Meter 2 — a test has been designed against this belief. */\n planned: boolean;\n /** Meter 3 — pre-registered bars settled / total. */\n tested: { settled: number; total: number };\n /** Meter 4 — signed Known: the belief's Confidence. */\n confidence: number;\n /** Sign bucket for the Known gauge direction. */\n confSign: ConfSign;\n /** Confidence ≤ −50 — the kill/re-test overlay (the same lane as the front door). */\n killZone: boolean;\n}\n\n/** An empty test state — a belief no experiment has named yet. */\nexport function emptyTestMeter(): TestMeter {\n return { planned: false, settled: 0, total: 0 };\n}\n\n/**\n * For every belief, the state of the tests aimed at it — whether one is designed\n * and how many of its pre-registered bars have settled, aggregated across all\n * experiments. Factored out of the pipeline row-builder so the board and a\n * single belief's rail agree by construction.\n */\nexport function beliefTestMeters(\n experiments: StageExperimentInput[],\n): Map<string, TestMeter> {\n const byAssumption = new Map<string, TestMeter>();\n const ensure = (id: string): TestMeter => {\n let s = byAssumption.get(id);\n if (!s) {\n s = emptyTestMeter();\n byAssumption.set(id, s);\n }\n return s;\n };\n for (const exp of experiments) {\n for (const b of exp.bars) {\n if (!b.assumptionId) continue;\n const s = ensure(b.assumptionId);\n s.planned = true;\n s.total += 1;\n if (b.settled) s.settled += 1;\n }\n // A designed test may name a belief via the convenience projection even when\n // its bar lines aren't expanded — still counts as Planned.\n for (const id of exp.plannedAssumptionIds) ensure(id).planned = true;\n }\n return byAssumption;\n}\n\n/**\n * Classify a belief on the spine from its meters. The kill-zone overlay is\n * *not* a stage — a belief whose evidence has turned is still structurally\n * wherever its framing/tests put it (a re-test moves it backward via the Known\n * meter, OPS-1300), so this stays pure status.\n */\nexport function classifyStage(framed: number, test: TestMeter): StageKey {\n if (framed < 100) return \"framed\";\n if (!test.planned) return \"planned\";\n if (test.total === 0 || test.settled < test.total) return \"tested\";\n return \"known\";\n}\n\n/** One belief's stage + four meters — the rail's data. */\nexport function deriveBeliefStage(input: BeliefStageInput): BeliefStage {\n const { framed, confidence, test } = input;\n return {\n stage: classifyStage(framed, test),\n framed,\n planned: test.planned,\n tested: { settled: test.settled, total: test.total },\n confidence,\n confSign: confidence > 0 ? \"pos\" : confidence < 0 ? \"neg\" : \"zero\",\n killZone: confidence <= KILL_LANE_THRESHOLD,\n };\n}\n","/**\n * The per-belief journey event log (OPS-1329) — the belief's life ordered into\n * dated events: bet → score → experiment → readings → confidence-cross → now.\n * It is the *story* half of the journey drill-in (the *rail* half is `stage.ts`).\n *\n * No new maths: Confidence at each point is read off `confidenceTrajectory`\n * (the very numbers the understanding layer already shows), and the current\n * number off `confidence()`. Nothing is invented — an event whose underlying\n * datum is absent (no impact score, no experiment, no concluded reading, no\n * kill-zone cross) is simply omitted, and no date is ever faked (an event with\n * no real date carries `date: null` and takes its place by structural order).\n *\n * Pure and label-free: the dashboard journey view-model adds the copy. Computed\n * fresh on read, out of the OPS-1251 on-write recompute.\n */\nimport type { Result } from \"../types.js\";\nimport type { AttributionReadingInput } from \"./attribution.js\";\nimport { confidence } from \"./confidence.js\";\nimport { KILL_LANE_THRESHOLD } from \"./next-move.js\";\nimport { isConcluded } from \"./strength.js\";\nimport { confidenceTrajectory } from \"./trajectory.js\";\n\n/** The kinds of event a belief's life produces, in structural order. */\nexport type JourneyEventKind =\n | \"bet\" // the belief was written\n | \"score\" // its impact was scored\n | \"experiment\" // a test was designed against it\n | \"reading\" // evidence landed\n | \"confidence-cross\" // Confidence fell into the kill zone\n | \"now\"; // the belief's state today\n\n/** The structural order the story walks — the tie-break for undated events. */\nconst KIND_ORDER: Record<JourneyEventKind, number> = {\n bet: 0,\n score: 1,\n experiment: 2,\n reading: 3,\n \"confidence-cross\": 4,\n now: 5,\n};\n\n/** One event in a belief's life. Label-free — the view-model adds the copy. */\nexport interface JourneyEvent {\n kind: JourneyEventKind;\n /** ISO date, or null when no real date exists (never faked). */\n date: string | null;\n /** Confidence known at this event (reading / cross / now); null otherwise. */\n confidence: number | null;\n /** The reading's verdict, for `reading` events; null otherwise. */\n result: Result | null;\n /** The source record id (reading id, experiment id) when the event has one. */\n refId: string | null;\n}\n\n/** The belief itself, reduced to what the log needs. */\nexport interface JourneyBeliefInput {\n /** When the bet was written — the `bet` event's date. */\n createdAt: string | null;\n /** Impact has been scored (a non-null seed) — emits the `score` event. */\n impactScored: boolean;\n}\n\n/** A test aimed at this belief, reduced to what the log needs. */\nexport interface JourneyExperimentInput {\n id: string;\n /** When the test was designed; null when unknown (event still emitted, undated). */\n date: string | null;\n}\n\nexport interface AssembleJourneyInput {\n belief: JourneyBeliefInput;\n /** The belief's own readings (already filtered to this assumption). */\n readings: AttributionReadingInput[];\n /** The experiments testing this belief. */\n experiments: JourneyExperimentInput[];\n /** \"Now\" as an ISO date — passed in so the log stays pure. */\n now: string;\n}\n\n/**\n * Assemble one belief's chronological event log. Events sort by date, undated\n * events anchored to the bet's date and ordered structurally; `now` is always\n * last.\n */\nexport function assembleJourney(input: AssembleJourneyInput): JourneyEvent[] {\n const { belief, readings, experiments, now } = input;\n const events: JourneyEvent[] = [];\n\n if (belief.createdAt) {\n events.push(event(\"bet\", belief.createdAt));\n }\n if (belief.impactScored) {\n // No scored-on date is stored, so the score rides structurally after the\n // bet rather than carrying an invented date.\n events.push(event(\"score\", null));\n }\n for (const e of experiments) {\n events.push(event(\"experiment\", e.date, { refId: e.id }));\n }\n\n const trajectory = confidenceTrajectory(readings);\n const confByDate = new Map(trajectory.map((p) => [p.date, p.confidence]));\n for (const r of readings) {\n const dated = r.date ?? null;\n // Confidence is known only for a concluded reading that lands on a\n // trajectory date (undated / inconclusive readings carry no number).\n const conf =\n dated && isConcluded(r.result) ? (confByDate.get(dated) ?? null) : null;\n events.push(\n event(\"reading\", dated, { confidence: conf, result: r.result, refId: r.id }),\n );\n }\n\n // The first point at which the evidence dragged Confidence into the kill zone.\n const cross = trajectory.find((p) => p.confidence <= KILL_LANE_THRESHOLD);\n if (cross) {\n events.push(event(\"confidence-cross\", cross.date, { confidence: cross.confidence }));\n }\n\n // Undated events anchor to the bet so they sit at the start in structural\n // order; `now` is appended after sorting so it is unconditionally last.\n const anchor = belief.createdAt ?? \"\";\n events.sort((a, b) => {\n const ka = a.date ?? anchor;\n const kb = b.date ?? anchor;\n if (ka !== kb) return ka < kb ? -1 : 1;\n return KIND_ORDER[a.kind] - KIND_ORDER[b.kind];\n });\n events.push(event(\"now\", now, { confidence: confidence(readings) }));\n\n return events;\n}\n\nfunction event(\n kind: JourneyEventKind,\n date: string | null,\n extra: Partial<Pick<JourneyEvent, \"confidence\" | \"result\" | \"refId\">> = {},\n): JourneyEvent {\n return {\n kind,\n date,\n confidence: extra.confidence ?? null,\n result: extra.result ?? null,\n refId: extra.refId ?? null,\n };\n}\n"],"mappings":"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","names":["confidence","confidence"]}
|
|
@@ -36,7 +36,7 @@ function isNotFoundError(e) {
|
|
|
36
36
|
var RELATIONS = {
|
|
37
37
|
"assumption-reading": {
|
|
38
38
|
from: { register: "assumptions", field: "readingIds", cardinality: "many" },
|
|
39
|
-
to: { register: "readings", field: "
|
|
39
|
+
to: { register: "readings", field: "assumptionIds", cardinality: "many" },
|
|
40
40
|
targetRegister: "readings"
|
|
41
41
|
},
|
|
42
42
|
"assumption-depends-on": {
|
|
@@ -88,4 +88,4 @@ export {
|
|
|
88
88
|
isNotFoundError,
|
|
89
89
|
RELATIONS
|
|
90
90
|
};
|
|
91
|
-
//# sourceMappingURL=chunk-
|
|
91
|
+
//# sourceMappingURL=chunk-NTPWGMCT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/provider.ts","../src/relations.ts"],"sourcesContent":["/**\n * The `DataProvider` — the single integration seam between the dashboard/API\n * and a concrete backend. Hand-rolled (patterns borrowed from\n * Refine/React-Admin's dataProvider, not forked). One adapter per backend;\n * Firestore is the first. Writing a new adapter is a bounded task: implement\n * these five methods.\n */\nimport type { AnyRecord, Collection, RecordRef, Relation } from \"./types.js\";\n\nexport interface DataProvider {\n /** Every row of a register — never a filtered view (a filter drops rows). */\n list(register: Collection): Promise<AnyRecord[]>;\n get(register: Collection, id: string): Promise<AnyRecord>;\n create(register: Collection, data: Partial<AnyRecord>): Promise<AnyRecord>;\n /**\n * Version-guarded update: `version` is the value the caller loaded. If the\n * stored version has moved on, the write is rejected with\n * {@link StaleVersionError} (surfaced to the user as a 409).\n */\n update(\n register: Collection,\n id: string,\n patch: Partial<AnyRecord>,\n version: number,\n ): Promise<AnyRecord>;\n /** Set a relation on both ends in one logical write. */\n link(relation: Relation, from: RecordRef, to: RecordRef): Promise<void>;\n /** Remove a relation from both ends in one logical write (the inverse of {@link link}). */\n unlink(relation: Relation, from: RecordRef, to: RecordRef): Promise<void>;\n}\n\n/**\n * Thrown when an update carries a version older than what is stored — a\n * concurrent edit landed first. The API turns this into a 409 with\n * plain-language copy; it is never surfaced as version jargon.\n */\nexport class StaleVersionError extends Error {\n override readonly name = \"StaleVersionError\";\n constructor(\n readonly register: Collection,\n readonly id: string,\n readonly expected: number,\n readonly actual: number,\n ) {\n super(\n `Stale write to ${register}/${id}: caller had version ${expected}, ` +\n `stored is ${actual}.`,\n );\n }\n}\n\nexport function isStaleVersionError(e: unknown): e is StaleVersionError {\n return e instanceof StaleVersionError;\n}\n\n/** Thrown when a get/update targets a record that does not exist (→ 404). */\nexport class NotFoundError extends Error {\n override readonly name = \"NotFoundError\";\n constructor(\n readonly register: Collection,\n readonly id: string,\n ) {\n super(`No ${register} record with id ${id}.`);\n }\n}\n\nexport function isNotFoundError(e: unknown): e is NotFoundError {\n return e instanceof NotFoundError;\n}\n","/**\n * Relation config — the single table describing, for each linkable relation,\n * which field on which register holds each end. `link()` sets both ends from\n * this table, so relations stay consistent no matter which side initiated.\n *\n * A `null` `to` end means the inverse is a *derived view*, not a stored field\n * (e.g. \"experiments testing me\" is computed over bar-lines; a decision's\n * `Based on` never touches the assumption). Those relations write one end.\n */\nimport type { Collection, Relation } from \"./types.js\";\n\nexport interface RelationEnd {\n register: Collection;\n field: string;\n cardinality: \"one\" | \"many\";\n}\n\nexport interface RelationSpec {\n /** The end named by `from` in `link(relation, from, to)`. */\n from: RelationEnd;\n /** The end named by `to`; null when the inverse is derived, not stored. */\n to: RelationEnd | null;\n /**\n * The register the `to` end points at — always present, even when `to` is\n * null (the inverse is a derived view). The dashboard reads this to know\n * which register to pick a link target from; the API validates the target\n * register against it.\n */\n targetRegister: Collection;\n}\n\nexport const RELATIONS: Record<Relation, RelationSpec> = {\n \"assumption-reading\": {\n from: { register: \"assumptions\", field: \"readingIds\", cardinality: \"many\" },\n to: { register: \"readings\", field: \"assumptionIds\", cardinality: \"many\" },\n targetRegister: \"readings\",\n },\n \"assumption-depends-on\": {\n from: {\n register: \"assumptions\",\n field: \"dependsOnIds\",\n cardinality: \"many\",\n },\n to: { register: \"assumptions\", field: \"enablesIds\", cardinality: \"many\" },\n targetRegister: \"assumptions\",\n },\n \"assumption-contradicts\": {\n from: {\n register: \"assumptions\",\n field: \"contradictsIds\",\n cardinality: \"many\",\n },\n to: {\n register: \"assumptions\",\n field: \"contradictsIds\",\n cardinality: \"many\",\n },\n targetRegister: \"assumptions\",\n },\n \"reading-experiment\": {\n from: { register: \"readings\", field: \"experimentId\", cardinality: \"one\" },\n to: null, // experiment→readings is derived\n targetRegister: \"experiments\",\n },\n \"decision-based-on\": {\n from: { register: \"decisions\", field: \"basedOnIds\", cardinality: \"many\" },\n to: null, // never touches the assumption\n targetRegister: \"assumptions\",\n },\n \"decision-resolves\": {\n from: { register: \"decisions\", field: \"resolvesIds\", cardinality: \"many\" },\n to: null, // mooting the assumption is a gated business action, not a link\n targetRegister: \"assumptions\",\n },\n};\n"],"mappings":";AAoCO,IAAM,oBAAN,cAAgC,MAAM;AAAA,EAE3C,YACW,UACA,IACA,UACA,QACT;AACA;AAAA,MACE,kBAAkB,QAAQ,IAAI,EAAE,wBAAwB,QAAQ,eACjD,MAAM;AAAA,IACvB;AARS;AACA;AACA;AACA;AAAA,EAMX;AAAA,EATW;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EALO,OAAO;AAY3B;AAEO,SAAS,oBAAoB,GAAoC;AACtE,SAAO,aAAa;AACtB;AAGO,IAAM,gBAAN,cAA4B,MAAM;AAAA,EAEvC,YACW,UACA,IACT;AACA,UAAM,MAAM,QAAQ,mBAAmB,EAAE,GAAG;AAHnC;AACA;AAAA,EAGX;AAAA,EAJW;AAAA,EACA;AAAA,EAHO,OAAO;AAO3B;AAEO,SAAS,gBAAgB,GAAgC;AAC9D,SAAO,aAAa;AACtB;;;ACrCO,IAAM,YAA4C;AAAA,EACvD,sBAAsB;AAAA,IACpB,MAAM,EAAE,UAAU,eAAe,OAAO,cAAc,aAAa,OAAO;AAAA,IAC1E,IAAI,EAAE,UAAU,YAAY,OAAO,iBAAiB,aAAa,OAAO;AAAA,IACxE,gBAAgB;AAAA,EAClB;AAAA,EACA,yBAAyB;AAAA,IACvB,MAAM;AAAA,MACJ,UAAU;AAAA,MACV,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,IAAI,EAAE,UAAU,eAAe,OAAO,cAAc,aAAa,OAAO;AAAA,IACxE,gBAAgB;AAAA,EAClB;AAAA,EACA,0BAA0B;AAAA,IACxB,MAAM;AAAA,MACJ,UAAU;AAAA,MACV,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,IAAI;AAAA,MACF,UAAU;AAAA,MACV,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,gBAAgB;AAAA,EAClB;AAAA,EACA,sBAAsB;AAAA,IACpB,MAAM,EAAE,UAAU,YAAY,OAAO,gBAAgB,aAAa,MAAM;AAAA,IACxE,IAAI;AAAA;AAAA,IACJ,gBAAgB;AAAA,EAClB;AAAA,EACA,qBAAqB;AAAA,IACnB,MAAM,EAAE,UAAU,aAAa,OAAO,cAAc,aAAa,OAAO;AAAA,IACxE,IAAI;AAAA;AAAA,IACJ,gBAAgB;AAAA,EAClB;AAAA,EACA,qBAAqB;AAAA,IACnB,MAAM,EAAE,UAAU,aAAa,OAAO,eAAe,aAAa,OAAO;AAAA,IACzE,IAAI;AAAA;AAAA,IACJ,gBAAgB;AAAA,EAClB;AACF;","names":[]}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { e as AssembleJourneyInput, f as Attribution, A as AttributionReadingInput, B as BarLineInput, g as BeliefRisk, h as BeliefStage, j as BeliefStageInput, C as COMPLETENESS_SLOTS, b as CompletenessInput, a as CompletenessSlot,
|
|
2
|
-
import '../types-
|
|
1
|
+
export { e as AssembleJourneyInput, f as Attribution, A as AttributionReadingInput, B as BarLineInput, g as BeliefRisk, h as BeliefStage, j as BeliefStageInput, k as COMMITMENT_FOUND, C as COMPLETENESS_SLOTS, b as CompletenessInput, a as CompletenessSlot, l as ConfSign, n as ConfidenceReadingInput, I as ImpactAssumptionInput, J as JourneyBeliefInput, o as JourneyEvent, p as JourneyEventKind, q as JourneyExperimentInput, K as KILL_LANE_THRESHOLD, M as MARKET_RUNG_ANCHOR, t as MoveKind, u as Mover, v as MoverKind, N as NextMove, w as NextMoveAssumptionInput, x as NextMoveDecisionInput, y as NextMoveExperimentInput, z as NextMoveInput, P as PortfolioBeliefInput, D as PortfolioProgress, E as Progress, R as RUNG_ANCHOR, S as Scored, F as StageExperimentInput, G as StageKey, H as StrengthInput, T as TestMeter, L as TrajectoryPoint, W as W0, O as assembleJourney, d as assumptionComplete, c as assumptionCompleteness, Q as beliefRisk, U as beliefTestMeters, V as classifyStage, X as commitmentFactor, Y as completenessSlotPresence, Z as confidence, _ as confidenceAttribution, $ as confidenceTrajectory, a0 as deriveBeliefStage, a1 as derivedImpacts, a2 as emptyTestMeter, a3 as experimentProgress, a4 as isConcluded, a5 as isMarketRung, m as missingCompletenessSlots, a6 as portfolioProgress, a7 as rankNextMoves, r as readingStrength, a8 as risk, a9 as round2, aa as scoreAndDedupe, ab as sign, s as sourceQuality } from '../index-BL4ktiMU.js';
|
|
2
|
+
import '../types-CX1gpi6l.js';
|
package/dist/derivation/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
COMMITMENT_FOUND,
|
|
2
3
|
COMPLETENESS_SLOTS,
|
|
3
4
|
KILL_LANE_THRESHOLD,
|
|
4
5
|
MARKET_RUNG_ANCHOR,
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
beliefRisk,
|
|
11
12
|
beliefTestMeters,
|
|
12
13
|
classifyStage,
|
|
14
|
+
commitmentFactor,
|
|
13
15
|
completenessSlotPresence,
|
|
14
16
|
confidence,
|
|
15
17
|
confidenceAttribution,
|
|
@@ -29,9 +31,10 @@ import {
|
|
|
29
31
|
scoreAndDedupe,
|
|
30
32
|
sign,
|
|
31
33
|
sourceQuality
|
|
32
|
-
} from "../chunk-
|
|
34
|
+
} from "../chunk-NIWHPSYD.js";
|
|
33
35
|
import "../chunk-PZ5AY32C.js";
|
|
34
36
|
export {
|
|
37
|
+
COMMITMENT_FOUND,
|
|
35
38
|
COMPLETENESS_SLOTS,
|
|
36
39
|
KILL_LANE_THRESHOLD,
|
|
37
40
|
MARKET_RUNG_ANCHOR,
|
|
@@ -43,6 +46,7 @@ export {
|
|
|
43
46
|
beliefRisk,
|
|
44
47
|
beliefTestMeters,
|
|
45
48
|
classifyStage,
|
|
49
|
+
commitmentFactor,
|
|
46
50
|
completenessSlotPresence,
|
|
47
51
|
confidence,
|
|
48
52
|
confidenceAttribution,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { l as MarketRung, k as MagnitudeBand, r as TestingRung, q as Rung, p as Result, F as Feasibility } from './types-CX1gpi6l.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Round to 2 decimals, matching the migration's `+(n).toFixed(2)`.
|
|
@@ -83,7 +83,8 @@ declare function sourceQuality(representativeness: number, credibility: number):
|
|
|
83
83
|
*
|
|
84
84
|
* Formula (`ontology.yaml` → `derivations.confidence`):
|
|
85
85
|
* (w0·0 + Σ wi·si) / (w0 + Σ wi), w0 = 100,
|
|
86
|
-
* wi = |si| × Source quality, si = the reading's signed Strength
|
|
86
|
+
* wi = |si| × Source quality × commitment, si = the reading's signed Strength,
|
|
87
|
+
* commitment = 1.0 for an experiment-linked reading, 0.85 for a found one.
|
|
87
88
|
*
|
|
88
89
|
* Only concluded Validated/Invalidated readings enter. Readings sharing a
|
|
89
90
|
* Source against one belief dedupe to the strongest (largest |si|, most
|
|
@@ -93,6 +94,14 @@ declare function sourceQuality(representativeness: number, credibility: number):
|
|
|
93
94
|
|
|
94
95
|
/** The neutral prior weight — a hard floor per the guardrails. */
|
|
95
96
|
declare const W0 = 100;
|
|
97
|
+
/**
|
|
98
|
+
* Commitment factor for a *found* reading — one with no originating experiment.
|
|
99
|
+
* A pre-registered (experiment-linked) reading weighs at full commitment (1.0);
|
|
100
|
+
* a found reading is discounted to this. It is a SMALL tiebreaker: it scales the
|
|
101
|
+
* weight only, never the Strength, so it can never reorder readings across rungs
|
|
102
|
+
* ("Rung dominates").
|
|
103
|
+
*/
|
|
104
|
+
declare const COMMITMENT_FOUND = 0.85;
|
|
96
105
|
interface ConfidenceReadingInput {
|
|
97
106
|
id: string;
|
|
98
107
|
/** The independence-dedupe key. Null falls back to the reading's own id. */
|
|
@@ -104,12 +113,19 @@ interface ConfidenceReadingInput {
|
|
|
104
113
|
/** ISO date; used only as the dedupe tie-break (most recent wins). */
|
|
105
114
|
date?: string | null;
|
|
106
115
|
magnitudeBand?: MagnitudeBand;
|
|
116
|
+
/**
|
|
117
|
+
* The originating experiment, or null/undefined for a *found* reading. Drives
|
|
118
|
+
* the commitment factor in the weight (found → {@link COMMITMENT_FOUND}).
|
|
119
|
+
*/
|
|
120
|
+
experimentId?: string | null;
|
|
107
121
|
}
|
|
122
|
+
/** The commitment weighting for a reading: full for committed, discounted for found. */
|
|
123
|
+
declare function commitmentFactor(experimentId: string | null | undefined): number;
|
|
108
124
|
interface Scored {
|
|
109
125
|
input: ConfidenceReadingInput;
|
|
110
126
|
strength: number;
|
|
111
127
|
sq: number;
|
|
112
|
-
/** The reading's weight in the average: |strength| × Source quality. */
|
|
128
|
+
/** The reading's weight in the average: |strength| × Source quality × commitment. */
|
|
113
129
|
weight: number;
|
|
114
130
|
}
|
|
115
131
|
/**
|
|
@@ -138,10 +154,12 @@ declare function confidence(readings: ConfidenceReadingInput[]): number;
|
|
|
138
154
|
* (OPS-1305).
|
|
139
155
|
*/
|
|
140
156
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
157
|
+
/**
|
|
158
|
+
* The reading shape the attribution reveal consumes. `experimentId` (the
|
|
159
|
+
* grouping key, and the commitment-factor driver) lives on
|
|
160
|
+
* {@link ConfidenceReadingInput}; this alias marks the attribution call sites.
|
|
161
|
+
*/
|
|
162
|
+
type AttributionReadingInput = ConfidenceReadingInput;
|
|
145
163
|
/** What a mover is anchored to — an experiment, or nothing (direct). */
|
|
146
164
|
type MoverKind = "experiment" | "direct";
|
|
147
165
|
interface Mover {
|
|
@@ -514,6 +532,7 @@ type index_BarLineInput = BarLineInput;
|
|
|
514
532
|
type index_BeliefRisk = BeliefRisk;
|
|
515
533
|
type index_BeliefStage = BeliefStage;
|
|
516
534
|
type index_BeliefStageInput = BeliefStageInput;
|
|
535
|
+
declare const index_COMMITMENT_FOUND: typeof COMMITMENT_FOUND;
|
|
517
536
|
declare const index_COMPLETENESS_SLOTS: typeof COMPLETENESS_SLOTS;
|
|
518
537
|
type index_CompletenessInput = CompletenessInput;
|
|
519
538
|
type index_CompletenessSlot = CompletenessSlot;
|
|
@@ -551,6 +570,7 @@ declare const index_assumptionCompleteness: typeof assumptionCompleteness;
|
|
|
551
570
|
declare const index_beliefRisk: typeof beliefRisk;
|
|
552
571
|
declare const index_beliefTestMeters: typeof beliefTestMeters;
|
|
553
572
|
declare const index_classifyStage: typeof classifyStage;
|
|
573
|
+
declare const index_commitmentFactor: typeof commitmentFactor;
|
|
554
574
|
declare const index_completenessSlotPresence: typeof completenessSlotPresence;
|
|
555
575
|
declare const index_confidence: typeof confidence;
|
|
556
576
|
declare const index_confidenceAttribution: typeof confidenceAttribution;
|
|
@@ -571,7 +591,7 @@ declare const index_scoreAndDedupe: typeof scoreAndDedupe;
|
|
|
571
591
|
declare const index_sign: typeof sign;
|
|
572
592
|
declare const index_sourceQuality: typeof sourceQuality;
|
|
573
593
|
declare namespace index {
|
|
574
|
-
export { type index_AssembleJourneyInput as AssembleJourneyInput, type index_Attribution as Attribution, type index_AttributionReadingInput as AttributionReadingInput, type index_BarLineInput as BarLineInput, type index_BeliefRisk as BeliefRisk, type index_BeliefStage as BeliefStage, type index_BeliefStageInput as BeliefStageInput, index_COMPLETENESS_SLOTS as COMPLETENESS_SLOTS, type index_CompletenessInput as CompletenessInput, type index_CompletenessSlot as CompletenessSlot, type index_ConfSign as ConfSign, type index_ConfidenceReadingInput as ConfidenceReadingInput, type index_ImpactAssumptionInput as ImpactAssumptionInput, type index_JourneyBeliefInput as JourneyBeliefInput, type index_JourneyEvent as JourneyEvent, type index_JourneyEventKind as JourneyEventKind, type index_JourneyExperimentInput as JourneyExperimentInput, index_KILL_LANE_THRESHOLD as KILL_LANE_THRESHOLD, index_MARKET_RUNG_ANCHOR as MARKET_RUNG_ANCHOR, type index_MoveKind as MoveKind, type index_Mover as Mover, type index_MoverKind as MoverKind, type index_NextMove as NextMove, type index_NextMoveAssumptionInput as NextMoveAssumptionInput, type index_NextMoveDecisionInput as NextMoveDecisionInput, type index_NextMoveExperimentInput as NextMoveExperimentInput, type index_NextMoveInput as NextMoveInput, type index_PortfolioBeliefInput as PortfolioBeliefInput, type index_PortfolioProgress as PortfolioProgress, type index_Progress as Progress, index_RUNG_ANCHOR as RUNG_ANCHOR, type index_Scored as Scored, type index_StageExperimentInput as StageExperimentInput, type index_StageKey as StageKey, type index_StrengthInput as StrengthInput, type index_TestMeter as TestMeter, type index_TrajectoryPoint as TrajectoryPoint, index_W0 as W0, index_assembleJourney as assembleJourney, index_assumptionComplete as assumptionComplete, index_assumptionCompleteness as assumptionCompleteness, index_beliefRisk as beliefRisk, index_beliefTestMeters as beliefTestMeters, index_classifyStage as classifyStage, index_completenessSlotPresence as completenessSlotPresence, index_confidence as confidence, index_confidenceAttribution as confidenceAttribution, index_confidenceTrajectory as confidenceTrajectory, index_deriveBeliefStage as deriveBeliefStage, index_derivedImpacts as derivedImpacts, index_emptyTestMeter as emptyTestMeter, index_experimentProgress as experimentProgress, index_isConcluded as isConcluded, index_isMarketRung as isMarketRung, index_missingCompletenessSlots as missingCompletenessSlots, index_portfolioProgress as portfolioProgress, index_rankNextMoves as rankNextMoves, index_readingStrength as readingStrength, index_risk as risk, index_round2 as round2, index_scoreAndDedupe as scoreAndDedupe, index_sign as sign, index_sourceQuality as sourceQuality };
|
|
594
|
+
export { type index_AssembleJourneyInput as AssembleJourneyInput, type index_Attribution as Attribution, type index_AttributionReadingInput as AttributionReadingInput, type index_BarLineInput as BarLineInput, type index_BeliefRisk as BeliefRisk, type index_BeliefStage as BeliefStage, type index_BeliefStageInput as BeliefStageInput, index_COMMITMENT_FOUND as COMMITMENT_FOUND, index_COMPLETENESS_SLOTS as COMPLETENESS_SLOTS, type index_CompletenessInput as CompletenessInput, type index_CompletenessSlot as CompletenessSlot, type index_ConfSign as ConfSign, type index_ConfidenceReadingInput as ConfidenceReadingInput, type index_ImpactAssumptionInput as ImpactAssumptionInput, type index_JourneyBeliefInput as JourneyBeliefInput, type index_JourneyEvent as JourneyEvent, type index_JourneyEventKind as JourneyEventKind, type index_JourneyExperimentInput as JourneyExperimentInput, index_KILL_LANE_THRESHOLD as KILL_LANE_THRESHOLD, index_MARKET_RUNG_ANCHOR as MARKET_RUNG_ANCHOR, type index_MoveKind as MoveKind, type index_Mover as Mover, type index_MoverKind as MoverKind, type index_NextMove as NextMove, type index_NextMoveAssumptionInput as NextMoveAssumptionInput, type index_NextMoveDecisionInput as NextMoveDecisionInput, type index_NextMoveExperimentInput as NextMoveExperimentInput, type index_NextMoveInput as NextMoveInput, type index_PortfolioBeliefInput as PortfolioBeliefInput, type index_PortfolioProgress as PortfolioProgress, type index_Progress as Progress, index_RUNG_ANCHOR as RUNG_ANCHOR, type index_Scored as Scored, type index_StageExperimentInput as StageExperimentInput, type index_StageKey as StageKey, type index_StrengthInput as StrengthInput, type index_TestMeter as TestMeter, type index_TrajectoryPoint as TrajectoryPoint, index_W0 as W0, index_assembleJourney as assembleJourney, index_assumptionComplete as assumptionComplete, index_assumptionCompleteness as assumptionCompleteness, index_beliefRisk as beliefRisk, index_beliefTestMeters as beliefTestMeters, index_classifyStage as classifyStage, index_commitmentFactor as commitmentFactor, index_completenessSlotPresence as completenessSlotPresence, index_confidence as confidence, index_confidenceAttribution as confidenceAttribution, index_confidenceTrajectory as confidenceTrajectory, index_deriveBeliefStage as deriveBeliefStage, index_derivedImpacts as derivedImpacts, index_emptyTestMeter as emptyTestMeter, index_experimentProgress as experimentProgress, index_isConcluded as isConcluded, index_isMarketRung as isMarketRung, index_missingCompletenessSlots as missingCompletenessSlots, index_portfolioProgress as portfolioProgress, index_rankNextMoves as rankNextMoves, index_readingStrength as readingStrength, index_risk as risk, index_round2 as round2, index_scoreAndDedupe as scoreAndDedupe, index_sign as sign, index_sourceQuality as sourceQuality };
|
|
575
595
|
}
|
|
576
596
|
|
|
577
|
-
export {
|
|
597
|
+
export { confidenceTrajectory as $, type AttributionReadingInput as A, type BarLineInput as B, COMPLETENESS_SLOTS as C, type PortfolioProgress as D, type Progress as E, type StageExperimentInput as F, type StageKey as G, type StrengthInput as H, type ImpactAssumptionInput as I, type JourneyBeliefInput as J, KILL_LANE_THRESHOLD as K, type TrajectoryPoint as L, MARKET_RUNG_ANCHOR as M, type NextMove as N, assembleJourney as O, type PortfolioBeliefInput as P, beliefRisk as Q, RUNG_ANCHOR as R, type Scored as S, type TestMeter as T, beliefTestMeters as U, classifyStage as V, W0 as W, commitmentFactor as X, completenessSlotPresence as Y, confidence as Z, confidenceAttribution as _, type CompletenessSlot as a, deriveBeliefStage as a0, derivedImpacts as a1, emptyTestMeter as a2, experimentProgress as a3, isConcluded as a4, isMarketRung as a5, portfolioProgress as a6, rankNextMoves as a7, risk as a8, round2 as a9, scoreAndDedupe as aa, sign as ab, type CompletenessInput as b, assumptionCompleteness as c, assumptionComplete as d, type AssembleJourneyInput as e, type Attribution as f, type BeliefRisk as g, type BeliefStage as h, index as i, type BeliefStageInput as j, COMMITMENT_FOUND as k, type ConfSign as l, missingCompletenessSlots as m, type ConfidenceReadingInput as n, type JourneyEvent as o, type JourneyEventKind as p, type JourneyExperimentInput as q, readingStrength as r, sourceQuality as s, type MoveKind as t, type Mover as u, type MoverKind as v, type NextMoveAssumptionInput as w, type NextMoveDecisionInput as x, type NextMoveExperimentInput as y, type NextMoveInput as z };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { R as Relation, C as Collection, A as AssumptionRecord, a as ReadingRecord, D as DecisionRecord, b as AssumptionDerived, c as AnyRecord } from './types-
|
|
2
|
-
export { d as AssumptionStatus, B as BarLine, e as BaseRecord, f as DecisionStatus, E as ExperimentRecord,
|
|
3
|
-
export { D as DataProvider, N as NotFoundError, S as StaleVersionError, i as isNotFoundError, a as isStaleVersionError } from './provider-
|
|
4
|
-
import { A as AttributionReadingInput } from './index-
|
|
5
|
-
export { C as ASSUMPTION_PRESENCE_SLOTS, a as AssumptionPresenceSlot, b as CompletenessInput, c as assumptionCompleteness, d as assumptionPresenceComplete, i as derivation, m as missingPresenceSlots, s as recomputeSourceQuality, r as recomputeStrength } from './index-
|
|
1
|
+
import { R as Relation, C as Collection, A as AssumptionRecord, a as ReadingRecord, D as DecisionRecord, b as AssumptionDerived, c as AnyRecord } from './types-CX1gpi6l.js';
|
|
2
|
+
export { d as AssumptionStatus, B as BarLine, e as BaseRecord, f as BeliefScore, g as DecisionStatus, E as ExperimentRecord, h as ExperimentStatus, F as Feasibility, G as GlossaryAvoid, i as GlossaryRecord, j as GlossaryStatus, M as MARKET_RUNG_VALUES, k as MagnitudeBand, l as MarketRung, m as REGISTERS, n as RecordRef, o as Register, p as Result, q as Rung, S as SourceQualityPick, T as TESTING_RUNGS, r as TestingRung } from './types-CX1gpi6l.js';
|
|
3
|
+
export { D as DataProvider, N as NotFoundError, S as StaleVersionError, i as isNotFoundError, a as isStaleVersionError } from './provider-X5wgqIE_.js';
|
|
4
|
+
import { A as AttributionReadingInput } from './index-BL4ktiMU.js';
|
|
5
|
+
export { C as ASSUMPTION_PRESENCE_SLOTS, a as AssumptionPresenceSlot, b as CompletenessInput, c as assumptionCompleteness, d as assumptionPresenceComplete, i as derivation, m as missingPresenceSlots, s as recomputeSourceQuality, r as recomputeStrength } from './index-BL4ktiMU.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Relation config — the single table describing, for each linkable relation,
|
|
@@ -44,13 +44,32 @@ declare function recomputeDerived(input: RecomputeInput): Map<string, Assumption
|
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* The one place that maps a stored reading record → the derivation module's
|
|
47
|
-
* typed
|
|
47
|
+
* typed inputs, so the pure functions stay decoupled from field names. Both the
|
|
48
48
|
* server-side recompute pass and the dashboard's understanding layer map
|
|
49
49
|
* through here, so a reading is read identically wherever Confidence is derived
|
|
50
50
|
* or explained. Coercion is defensive because a record's fields are `unknown`.
|
|
51
|
+
*
|
|
52
|
+
* A reading is one artifact ROW carrying a `beliefs[]` array: each belief scores
|
|
53
|
+
* one assumption (its own Rung / Result / magnitude band), while Source, source
|
|
54
|
+
* quality inputs, date, and the originating experiment are row-level and shared
|
|
55
|
+
* by every belief. `readingBeliefInputs` fans a row out into one derivation
|
|
56
|
+
* input per belief, stamping the row-level fields onto each.
|
|
51
57
|
*/
|
|
52
58
|
|
|
53
|
-
/**
|
|
54
|
-
|
|
59
|
+
/**
|
|
60
|
+
* A per-belief derivation input tagged with the assumption it scores, so the
|
|
61
|
+
* recompute pass can group a row's beliefs to the right assumptions.
|
|
62
|
+
*/
|
|
63
|
+
type BeliefReadingInput = AttributionReadingInput & {
|
|
64
|
+
assumptionId: string;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Fan a reading row out into one derivation input per belief. Each input carries
|
|
68
|
+
* the belief's Rung/Result/magnitude band plus the row's Source, source-quality
|
|
69
|
+
* picks, date, and experiment (which drives the commitment factor). The input's
|
|
70
|
+
* `id` is the row id — within a single assumption's group each row contributes
|
|
71
|
+
* one belief, so the id stays a stable dedupe fallback and drill-through key.
|
|
72
|
+
*/
|
|
73
|
+
declare function readingBeliefInputs(r: AnyRecord): BeliefReadingInput[];
|
|
55
74
|
|
|
56
|
-
export { AnyRecord, AssumptionDerived, AssumptionRecord, Collection, DecisionRecord, RELATIONS, ReadingRecord, type RecomputeInput, Relation, type RelationEnd, type RelationSpec,
|
|
75
|
+
export { AnyRecord, AssumptionDerived, AssumptionRecord, type BeliefReadingInput, Collection, DecisionRecord, RELATIONS, ReadingRecord, type RecomputeInput, Relation, type RelationEnd, type RelationSpec, readingBeliefInputs, recomputeDerived };
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
StaleVersionError,
|
|
5
5
|
isNotFoundError,
|
|
6
6
|
isStaleVersionError
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-NTPWGMCT.js";
|
|
8
8
|
import {
|
|
9
9
|
COMPLETENESS_SLOTS,
|
|
10
10
|
MARKET_RUNG_VALUES,
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
readingStrength,
|
|
20
20
|
risk,
|
|
21
21
|
sourceQuality
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-NIWHPSYD.js";
|
|
23
23
|
import "./chunk-PZ5AY32C.js";
|
|
24
24
|
|
|
25
25
|
// src/reading-input.ts
|
|
@@ -30,34 +30,36 @@ function num(v) {
|
|
|
30
30
|
const n = Number(v);
|
|
31
31
|
return Number.isFinite(n) ? n : 0;
|
|
32
32
|
}
|
|
33
|
-
function
|
|
34
|
-
|
|
33
|
+
function readingBeliefInputs(r) {
|
|
34
|
+
const beliefs = Array.isArray(r.beliefs) ? r.beliefs : [];
|
|
35
|
+
return beliefs.map((b) => ({
|
|
35
36
|
id: r.id,
|
|
37
|
+
assumptionId: str(b.assumptionId) ?? "",
|
|
36
38
|
source: str(r.Source),
|
|
37
|
-
rung:
|
|
38
|
-
result:
|
|
39
|
+
rung: b.Rung,
|
|
40
|
+
result: b.Result,
|
|
39
41
|
representativeness: num(r.Representativeness),
|
|
40
42
|
credibility: num(r.Credibility),
|
|
41
43
|
date: str(r.Date),
|
|
42
|
-
magnitudeBand:
|
|
44
|
+
magnitudeBand: b.magnitudeBand,
|
|
43
45
|
experimentId: str(r.experimentId)
|
|
44
|
-
};
|
|
46
|
+
}));
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
// src/recompute.ts
|
|
48
50
|
var STANDING_DECISION = /* @__PURE__ */ new Set(["Active", "Provisional"]);
|
|
49
51
|
function recomputeDerived(input) {
|
|
50
52
|
const { assumptions, readings, decisions } = input;
|
|
51
|
-
const
|
|
52
|
-
for (const a of assumptions)
|
|
53
|
-
for (const r of readings)
|
|
53
|
+
const inputsByAssumption = /* @__PURE__ */ new Map();
|
|
54
|
+
for (const a of assumptions) inputsByAssumption.set(a.id, []);
|
|
55
|
+
for (const r of readings) {
|
|
56
|
+
for (const input2 of readingBeliefInputs(r)) {
|
|
57
|
+
inputsByAssumption.get(input2.assumptionId)?.push(input2);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
54
60
|
const confidenceById = /* @__PURE__ */ new Map();
|
|
55
61
|
for (const a of assumptions) {
|
|
56
|
-
|
|
57
|
-
confidenceById.set(
|
|
58
|
-
a.id,
|
|
59
|
-
confidence(rs.map((r) => toReadingInput(r)))
|
|
60
|
-
);
|
|
62
|
+
confidenceById.set(a.id, confidence(inputsByAssumption.get(a.id) ?? []));
|
|
61
63
|
}
|
|
62
64
|
const basedOnCounts = {};
|
|
63
65
|
for (const d of decisions) {
|
|
@@ -105,9 +107,9 @@ export {
|
|
|
105
107
|
isNotFoundError,
|
|
106
108
|
isStaleVersionError,
|
|
107
109
|
missingCompletenessSlots as missingPresenceSlots,
|
|
110
|
+
readingBeliefInputs,
|
|
108
111
|
recomputeDerived,
|
|
109
112
|
sourceQuality as recomputeSourceQuality,
|
|
110
|
-
readingStrength as recomputeStrength
|
|
111
|
-
toReadingInput
|
|
113
|
+
readingStrength as recomputeStrength
|
|
112
114
|
};
|
|
113
115
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/reading-input.ts","../src/recompute.ts"],"sourcesContent":["/**\n * The one place that maps a stored reading record → the derivation module's\n * typed
|
|
1
|
+
{"version":3,"sources":["../src/reading-input.ts","../src/recompute.ts"],"sourcesContent":["/**\n * The one place that maps a stored reading record → the derivation module's\n * typed inputs, so the pure functions stay decoupled from field names. Both the\n * server-side recompute pass and the dashboard's understanding layer map\n * through here, so a reading is read identically wherever Confidence is derived\n * or explained. Coercion is defensive because a record's fields are `unknown`.\n *\n * A reading is one artifact ROW carrying a `beliefs[]` array: each belief scores\n * one assumption (its own Rung / Result / magnitude band), while Source, source\n * quality inputs, date, and the originating experiment are row-level and shared\n * by every belief. `readingBeliefInputs` fans a row out into one derivation\n * input per belief, stamping the row-level fields onto each.\n */\nimport type { AnyRecord } from \"./types.js\";\nimport type { AttributionReadingInput } from \"./derivation/index.js\";\n\nfunction str(v: unknown): string | null {\n return typeof v === \"string\" && v !== \"\" ? v : null;\n}\n\nfunction num(v: unknown): number {\n const n = Number(v);\n return Number.isFinite(n) ? n : 0;\n}\n\n/** The stored per-belief scoring shape, read defensively off `unknown`. */\ninterface RawBelief {\n assumptionId?: unknown;\n Rung?: unknown;\n Result?: unknown;\n magnitudeBand?: unknown;\n}\n\n/**\n * A per-belief derivation input tagged with the assumption it scores, so the\n * recompute pass can group a row's beliefs to the right assumptions.\n */\nexport type BeliefReadingInput = AttributionReadingInput & {\n assumptionId: string;\n};\n\n/**\n * Fan a reading row out into one derivation input per belief. Each input carries\n * the belief's Rung/Result/magnitude band plus the row's Source, source-quality\n * picks, date, and experiment (which drives the commitment factor). The input's\n * `id` is the row id — within a single assumption's group each row contributes\n * one belief, so the id stays a stable dedupe fallback and drill-through key.\n */\nexport function readingBeliefInputs(r: AnyRecord): BeliefReadingInput[] {\n const beliefs = Array.isArray(r.beliefs) ? (r.beliefs as RawBelief[]) : [];\n return beliefs.map((b) => ({\n id: r.id,\n assumptionId: str(b.assumptionId) ?? \"\",\n source: str(r.Source),\n rung: b.Rung as AttributionReadingInput[\"rung\"],\n result: b.Result as AttributionReadingInput[\"result\"],\n representativeness: num(r.Representativeness),\n credibility: num(r.Credibility),\n date: str(r.Date),\n magnitudeBand: b.magnitudeBand as AttributionReadingInput[\"magnitudeBand\"],\n experimentId: str(r.experimentId),\n }));\n}\n","/**\n * Derive-on-write glue: recompute the four derived numbers for a whole\n * assumptions register from its readings and standing decisions, using the\n * pure derivation module. The API calls this server-side on every touching\n * write and writes the results back; a batch pass is the backstop for\n * non-dashboard writes.\n *\n * The stored-record → derivation-input mapping lives in `reading-input.ts`\n * (`readingBeliefInputs`, which fans a reading's beliefs[] out into one input\n * per belief), shared with the dashboard's understanding layer so a reading is\n * read identically wherever Confidence is derived or explained.\n */\nimport {\n assumptionCompleteness,\n confidence,\n derivedImpacts,\n risk,\n} from \"./derivation/index.js\";\nimport { readingBeliefInputs } from \"./reading-input.js\";\nimport type { ConfidenceReadingInput } from \"./derivation/index.js\";\nimport type {\n AnyRecord,\n AssumptionDerived,\n AssumptionRecord,\n DecisionRecord,\n ReadingRecord,\n} from \"./types.js\";\n\n/** Standing decisions (Provisional/Active) contribute to Derived Impact. */\nconst STANDING_DECISION = new Set([\"Active\", \"Provisional\"]);\n\nexport interface RecomputeInput {\n assumptions: AssumptionRecord[];\n readings: ReadingRecord[];\n decisions: DecisionRecord[];\n}\n\n/** id → recomputed derived tuple for every assumption in the register. */\nexport function recomputeDerived(\n input: RecomputeInput,\n): Map<string, AssumptionDerived> {\n const { assumptions, readings, decisions } = input;\n\n // Confidence: fan each reading row out into its per-belief inputs and group\n // those by the assumption each belief scores. A row that scores several\n // beliefs contributes one input to each of their assumptions; every input\n // carries the row-level Source, source quality, and experiment (commitment).\n const inputsByAssumption = new Map<string, ConfidenceReadingInput[]>();\n for (const a of assumptions) inputsByAssumption.set(a.id, []);\n for (const r of readings) {\n for (const input of readingBeliefInputs(r as unknown as AnyRecord)) {\n inputsByAssumption.get(input.assumptionId)?.push(input);\n }\n }\n\n const confidenceById = new Map<string, number>();\n for (const a of assumptions) {\n confidenceById.set(a.id, confidence(inputsByAssumption.get(a.id) ?? []));\n }\n\n // Derived Impact: standing-decision `Based on` links count +100 each.\n const basedOnCounts: Record<string, number> = {};\n for (const d of decisions) {\n if (!STANDING_DECISION.has(d.Status)) continue;\n for (const aid of d.basedOnIds ?? []) {\n basedOnCounts[aid] = (basedOnCounts[aid] ?? 0) + 1;\n }\n }\n const impactById = derivedImpacts(\n assumptions.map((a) => ({\n id: a.id,\n impact: a.moot ? 0 : a.Impact,\n moot: a.moot,\n dependsOnIds: a.dependsOnIds,\n })),\n basedOnCounts,\n );\n\n const out = new Map<string, AssumptionDerived>();\n for (const a of assumptions) {\n const c = confidenceById.get(a.id) ?? 0;\n const di = impactById.get(a.id) ?? 0;\n out.set(a.id, {\n confidence: c,\n derivedImpact: di,\n risk: risk(di, c),\n // Completeness is a *structural* readiness meter: it reads a.Impact as\n // present/absent, not its value, so a moot assumption (whose Impact the\n // Derived Impact pass zeroes) still counts its scored Impact slot.\n completeness: assumptionCompleteness(a),\n });\n }\n return out;\n}\n\n/** Recompute Source quality + Strength for a single reading. */\nexport {\n sourceQuality as recomputeSourceQuality,\n readingStrength as recomputeStrength,\n} from \"./derivation/index.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAgBA,SAAS,IAAI,GAA2B;AACtC,SAAO,OAAO,MAAM,YAAY,MAAM,KAAK,IAAI;AACjD;AAEA,SAAS,IAAI,GAAoB;AAC/B,QAAM,IAAI,OAAO,CAAC;AAClB,SAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAClC;AAyBO,SAAS,oBAAoB,GAAoC;AACtE,QAAM,UAAU,MAAM,QAAQ,EAAE,OAAO,IAAK,EAAE,UAA0B,CAAC;AACzE,SAAO,QAAQ,IAAI,CAAC,OAAO;AAAA,IACzB,IAAI,EAAE;AAAA,IACN,cAAc,IAAI,EAAE,YAAY,KAAK;AAAA,IACrC,QAAQ,IAAI,EAAE,MAAM;AAAA,IACpB,MAAM,EAAE;AAAA,IACR,QAAQ,EAAE;AAAA,IACV,oBAAoB,IAAI,EAAE,kBAAkB;AAAA,IAC5C,aAAa,IAAI,EAAE,WAAW;AAAA,IAC9B,MAAM,IAAI,EAAE,IAAI;AAAA,IAChB,eAAe,EAAE;AAAA,IACjB,cAAc,IAAI,EAAE,YAAY;AAAA,EAClC,EAAE;AACJ;;;ACjCA,IAAM,oBAAoB,oBAAI,IAAI,CAAC,UAAU,aAAa,CAAC;AASpD,SAAS,iBACd,OACgC;AAChC,QAAM,EAAE,aAAa,UAAU,UAAU,IAAI;AAM7C,QAAM,qBAAqB,oBAAI,IAAsC;AACrE,aAAW,KAAK,YAAa,oBAAmB,IAAI,EAAE,IAAI,CAAC,CAAC;AAC5D,aAAW,KAAK,UAAU;AACxB,eAAWA,UAAS,oBAAoB,CAAyB,GAAG;AAClE,yBAAmB,IAAIA,OAAM,YAAY,GAAG,KAAKA,MAAK;AAAA,IACxD;AAAA,EACF;AAEA,QAAM,iBAAiB,oBAAI,IAAoB;AAC/C,aAAW,KAAK,aAAa;AAC3B,mBAAe,IAAI,EAAE,IAAI,WAAW,mBAAmB,IAAI,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC;AAAA,EACzE;AAGA,QAAM,gBAAwC,CAAC;AAC/C,aAAW,KAAK,WAAW;AACzB,QAAI,CAAC,kBAAkB,IAAI,EAAE,MAAM,EAAG;AACtC,eAAW,OAAO,EAAE,cAAc,CAAC,GAAG;AACpC,oBAAc,GAAG,KAAK,cAAc,GAAG,KAAK,KAAK;AAAA,IACnD;AAAA,EACF;AACA,QAAM,aAAa;AAAA,IACjB,YAAY,IAAI,CAAC,OAAO;AAAA,MACtB,IAAI,EAAE;AAAA,MACN,QAAQ,EAAE,OAAO,IAAI,EAAE;AAAA,MACvB,MAAM,EAAE;AAAA,MACR,cAAc,EAAE;AAAA,IAClB,EAAE;AAAA,IACF;AAAA,EACF;AAEA,QAAM,MAAM,oBAAI,IAA+B;AAC/C,aAAW,KAAK,aAAa;AAC3B,UAAM,IAAI,eAAe,IAAI,EAAE,EAAE,KAAK;AACtC,UAAM,KAAK,WAAW,IAAI,EAAE,EAAE,KAAK;AACnC,QAAI,IAAI,EAAE,IAAI;AAAA,MACZ,YAAY;AAAA,MACZ,eAAe;AAAA,MACf,MAAM,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA,MAIhB,cAAc,uBAAuB,CAAC;AAAA,IACxC,CAAC;AAAA,EACH;AACA,SAAO;AACT;","names":["input"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as Collection, c as AnyRecord, R as Relation,
|
|
1
|
+
import { C as Collection, c as AnyRecord, R as Relation, n as RecordRef } from './types-CX1gpi6l.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* The `DataProvider` — the single integration seam between the dashboard/API
|
|
@@ -21,6 +21,8 @@ interface DataProvider {
|
|
|
21
21
|
update(register: Collection, id: string, patch: Partial<AnyRecord>, version: number): Promise<AnyRecord>;
|
|
22
22
|
/** Set a relation on both ends in one logical write. */
|
|
23
23
|
link(relation: Relation, from: RecordRef, to: RecordRef): Promise<void>;
|
|
24
|
+
/** Remove a relation from both ends in one logical write (the inverse of {@link link}). */
|
|
25
|
+
unlink(relation: Relation, from: RecordRef, to: RecordRef): Promise<void>;
|
|
24
26
|
}
|
|
25
27
|
/**
|
|
26
28
|
* Thrown when an update carries a version older than what is stored — a
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DataProvider } from './provider-
|
|
2
|
-
import { C as Collection, c as AnyRecord, R as Relation,
|
|
1
|
+
import { D as DataProvider } from './provider-X5wgqIE_.js';
|
|
2
|
+
import { C as Collection, c as AnyRecord, R as Relation, n as RecordRef } from './types-CX1gpi6l.js';
|
|
3
3
|
|
|
4
4
|
interface InMemoryProviderOptions {
|
|
5
5
|
now?: () => string;
|
|
@@ -17,6 +17,7 @@ declare class InMemoryProvider implements DataProvider {
|
|
|
17
17
|
create(register: Collection, data: Partial<AnyRecord>): Promise<AnyRecord>;
|
|
18
18
|
update(register: Collection, id: string, patch: Partial<AnyRecord>, version: number): Promise<AnyRecord>;
|
|
19
19
|
link(relation: Relation, from: RecordRef, to: RecordRef): Promise<void>;
|
|
20
|
+
unlink(relation: Relation, from: RecordRef, to: RecordRef): Promise<void>;
|
|
20
21
|
private applyEnd;
|
|
21
22
|
}
|
|
22
23
|
declare function createInMemoryProvider(options?: InMemoryProviderOptions): InMemoryProvider;
|
package/dist/testing.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
NotFoundError,
|
|
3
3
|
RELATIONS,
|
|
4
4
|
StaleVersionError
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-NTPWGMCT.js";
|
|
6
6
|
import "./chunk-PZ5AY32C.js";
|
|
7
7
|
|
|
8
8
|
// src/testing.ts
|
|
@@ -69,18 +69,28 @@ var InMemoryProvider = class {
|
|
|
69
69
|
}
|
|
70
70
|
async link(relation, from, to) {
|
|
71
71
|
const spec = RELATIONS[relation];
|
|
72
|
-
this.applyEnd(spec.from, from.register, from.id, to.id);
|
|
73
|
-
if (spec.to) this.applyEnd(spec.to, to.register, to.id, from.id);
|
|
72
|
+
this.applyEnd(spec.from, from.register, from.id, to.id, "add");
|
|
73
|
+
if (spec.to) this.applyEnd(spec.to, to.register, to.id, from.id, "add");
|
|
74
74
|
}
|
|
75
|
-
|
|
75
|
+
async unlink(relation, from, to) {
|
|
76
|
+
const spec = RELATIONS[relation];
|
|
77
|
+
this.applyEnd(spec.from, from.register, from.id, to.id, "remove");
|
|
78
|
+
if (spec.to) this.applyEnd(spec.to, to.register, to.id, from.id, "remove");
|
|
79
|
+
}
|
|
80
|
+
applyEnd(end, register, ownerId, otherId, op) {
|
|
76
81
|
const rec = this.col(register).get(ownerId);
|
|
77
82
|
if (!rec) throw new NotFoundError(register, ownerId);
|
|
78
83
|
if (end.cardinality === "many") {
|
|
79
84
|
const arr = Array.isArray(rec[end.field]) ? rec[end.field] : [];
|
|
80
|
-
if (
|
|
81
|
-
|
|
85
|
+
if (op === "add") {
|
|
86
|
+
if (!arr.includes(otherId)) arr.push(otherId);
|
|
87
|
+
rec[end.field] = arr;
|
|
88
|
+
} else {
|
|
89
|
+
rec[end.field] = arr.filter((x) => x !== otherId);
|
|
90
|
+
}
|
|
82
91
|
} else {
|
|
83
|
-
rec[end.field] = otherId;
|
|
92
|
+
if (op === "add") rec[end.field] = otherId;
|
|
93
|
+
else if (rec[end.field] === otherId) rec[end.field] = null;
|
|
84
94
|
}
|
|
85
95
|
rec.version += 1;
|
|
86
96
|
rec.updatedAt = this.now();
|
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/testing.ts"],"sourcesContent":["/**\n * An in-memory `DataProvider` — the fast, deterministic fake that backs API\n * and dashboard tests (the spec's \"Seam 1\" in-memory adapter). It honours the\n * full contract, including version-guarded writes (409 on stale) and\n * both-ends linking, so tests exercise real behaviour without a database.\n */\nimport { NotFoundError, StaleVersionError } from \"./provider.js\";\nimport type { DataProvider } from \"./provider.js\";\nimport { RELATIONS } from \"./relations.js\";\nimport type { RelationEnd } from \"./relations.js\";\nimport type { AnyRecord, Collection, RecordRef, Relation } from \"./types.js\";\n\nexport interface InMemoryProviderOptions {\n now?: () => string;\n /** Seed data keyed by register. */\n seed?: Partial<Record<Collection, AnyRecord[]>>;\n}\n\nexport class InMemoryProvider implements DataProvider {\n private readonly store = new Map<Collection, Map<string, AnyRecord>>();\n private readonly now: () => string;\n private counter = 0;\n\n constructor(options: InMemoryProviderOptions = {}) {\n this.now = options.now ?? (() => new Date().toISOString());\n for (const [register, rows] of Object.entries(options.seed ?? {})) {\n const col = this.col(register as Collection);\n for (const r of rows ?? []) col.set(r.id, structuredClone(r));\n }\n }\n\n private col(register: Collection): Map<string, AnyRecord> {\n let c = this.store.get(register);\n if (!c) {\n c = new Map();\n this.store.set(register, c);\n }\n return c;\n }\n\n async list(register: Collection): Promise<AnyRecord[]> {\n return [...this.col(register).values()].map((r) => structuredClone(r));\n }\n\n async get(register: Collection, id: string): Promise<AnyRecord> {\n const r = this.col(register).get(id);\n if (!r) throw new NotFoundError(register, id);\n return structuredClone(r);\n }\n\n async create(\n register: Collection,\n data: Partial<AnyRecord>,\n ): Promise<AnyRecord> {\n const id = data.id ? String(data.id) : `${register}-${++this.counter}`;\n if (this.col(register).has(id)) {\n throw new Error(`${register}/${id} already exists`);\n }\n const ts = this.now();\n const { id: _i, ...rest } = data;\n const record = {\n ...rest,\n id,\n version: 0,\n createdAt: ts,\n updatedAt: ts,\n } as AnyRecord;\n this.col(register).set(id, record);\n return structuredClone(record);\n }\n\n async update(\n register: Collection,\n id: string,\n patch: Partial<AnyRecord>,\n version: number,\n ): Promise<AnyRecord> {\n const current = this.col(register).get(id);\n if (!current) throw new NotFoundError(register, id);\n if (current.version !== version) {\n throw new StaleVersionError(register, id, version, current.version);\n }\n const { id: _i, version: _v, createdAt: _c, ...safe } = patch;\n const next = {\n ...current,\n ...safe,\n id,\n version: current.version + 1,\n updatedAt: this.now(),\n } as AnyRecord;\n this.col(register).set(id, next);\n return structuredClone(next);\n }\n\n async link(relation: Relation, from: RecordRef, to: RecordRef): Promise<void> {\n const spec = RELATIONS[relation];\n this.applyEnd(spec.from, from.register, from.id, to.id);\n if (spec.to) this.applyEnd(spec.to, to.register, to.id, from.id);\n }\n\n private applyEnd(\n end: RelationEnd,\n register: Collection,\n ownerId: string,\n otherId: string,\n ): void {\n const rec = this.col(register).get(ownerId);\n if (!rec) throw new NotFoundError(register, ownerId);\n if (end.cardinality === \"many\") {\n const arr = Array.isArray(rec[end.field])\n ? (rec[end.field] as string[])\n : [];\n if (!arr.includes(otherId)) arr.push(otherId);\n rec[end.field] = arr;\n } else {\n rec[end.field] = otherId;\n }\n rec.version += 1;\n rec.updatedAt = this.now();\n }\n}\n\nexport function createInMemoryProvider(\n options?: InMemoryProviderOptions,\n): InMemoryProvider {\n return new InMemoryProvider(options);\n}\n"],"mappings":";;;;;;;;AAkBO,IAAM,mBAAN,MAA+C;AAAA,EACnC,QAAQ,oBAAI,IAAwC;AAAA,EACpD;AAAA,EACT,UAAU;AAAA,EAElB,YAAY,UAAmC,CAAC,GAAG;AACjD,SAAK,MAAM,QAAQ,QAAQ,OAAM,oBAAI,KAAK,GAAE,YAAY;AACxD,eAAW,CAAC,UAAU,IAAI,KAAK,OAAO,QAAQ,QAAQ,QAAQ,CAAC,CAAC,GAAG;AACjE,YAAM,MAAM,KAAK,IAAI,QAAsB;AAC3C,iBAAW,KAAK,QAAQ,CAAC,EAAG,KAAI,IAAI,EAAE,IAAI,gBAAgB,CAAC,CAAC;AAAA,IAC9D;AAAA,EACF;AAAA,EAEQ,IAAI,UAA8C;AACxD,QAAI,IAAI,KAAK,MAAM,IAAI,QAAQ;AAC/B,QAAI,CAAC,GAAG;AACN,UAAI,oBAAI,IAAI;AACZ,WAAK,MAAM,IAAI,UAAU,CAAC;AAAA,IAC5B;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,KAAK,UAA4C;AACrD,WAAO,CAAC,GAAG,KAAK,IAAI,QAAQ,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,MAAM,gBAAgB,CAAC,CAAC;AAAA,EACvE;AAAA,EAEA,MAAM,IAAI,UAAsB,IAAgC;AAC9D,UAAM,IAAI,KAAK,IAAI,QAAQ,EAAE,IAAI,EAAE;AACnC,QAAI,CAAC,EAAG,OAAM,IAAI,cAAc,UAAU,EAAE;AAC5C,WAAO,gBAAgB,CAAC;AAAA,EAC1B;AAAA,EAEA,MAAM,OACJ,UACA,MACoB;AACpB,UAAM,KAAK,KAAK,KAAK,OAAO,KAAK,EAAE,IAAI,GAAG,QAAQ,IAAI,EAAE,KAAK,OAAO;AACpE,QAAI,KAAK,IAAI,QAAQ,EAAE,IAAI,EAAE,GAAG;AAC9B,YAAM,IAAI,MAAM,GAAG,QAAQ,IAAI,EAAE,iBAAiB;AAAA,IACpD;AACA,UAAM,KAAK,KAAK,IAAI;AACpB,UAAM,EAAE,IAAI,IAAI,GAAG,KAAK,IAAI;AAC5B,UAAM,SAAS;AAAA,MACb,GAAG;AAAA,MACH;AAAA,MACA,SAAS;AAAA,MACT,WAAW;AAAA,MACX,WAAW;AAAA,IACb;AACA,SAAK,IAAI,QAAQ,EAAE,IAAI,IAAI,MAAM;AACjC,WAAO,gBAAgB,MAAM;AAAA,EAC/B;AAAA,EAEA,MAAM,OACJ,UACA,IACA,OACA,SACoB;AACpB,UAAM,UAAU,KAAK,IAAI,QAAQ,EAAE,IAAI,EAAE;AACzC,QAAI,CAAC,QAAS,OAAM,IAAI,cAAc,UAAU,EAAE;AAClD,QAAI,QAAQ,YAAY,SAAS;AAC/B,YAAM,IAAI,kBAAkB,UAAU,IAAI,SAAS,QAAQ,OAAO;AAAA,IACpE;AACA,UAAM,EAAE,IAAI,IAAI,SAAS,IAAI,WAAW,IAAI,GAAG,KAAK,IAAI;AACxD,UAAM,OAAO;AAAA,MACX,GAAG;AAAA,MACH,GAAG;AAAA,MACH;AAAA,MACA,SAAS,QAAQ,UAAU;AAAA,MAC3B,WAAW,KAAK,IAAI;AAAA,IACtB;AACA,SAAK,IAAI,QAAQ,EAAE,IAAI,IAAI,IAAI;AAC/B,WAAO,gBAAgB,IAAI;AAAA,EAC7B;AAAA,EAEA,MAAM,KAAK,UAAoB,MAAiB,IAA8B;AAC5E,UAAM,OAAO,UAAU,QAAQ;AAC/B,SAAK,SAAS,KAAK,MAAM,KAAK,UAAU,KAAK,IAAI,GAAG,
|
|
1
|
+
{"version":3,"sources":["../src/testing.ts"],"sourcesContent":["/**\n * An in-memory `DataProvider` — the fast, deterministic fake that backs API\n * and dashboard tests (the spec's \"Seam 1\" in-memory adapter). It honours the\n * full contract, including version-guarded writes (409 on stale) and\n * both-ends linking, so tests exercise real behaviour without a database.\n */\nimport { NotFoundError, StaleVersionError } from \"./provider.js\";\nimport type { DataProvider } from \"./provider.js\";\nimport { RELATIONS } from \"./relations.js\";\nimport type { RelationEnd } from \"./relations.js\";\nimport type { AnyRecord, Collection, RecordRef, Relation } from \"./types.js\";\n\nexport interface InMemoryProviderOptions {\n now?: () => string;\n /** Seed data keyed by register. */\n seed?: Partial<Record<Collection, AnyRecord[]>>;\n}\n\nexport class InMemoryProvider implements DataProvider {\n private readonly store = new Map<Collection, Map<string, AnyRecord>>();\n private readonly now: () => string;\n private counter = 0;\n\n constructor(options: InMemoryProviderOptions = {}) {\n this.now = options.now ?? (() => new Date().toISOString());\n for (const [register, rows] of Object.entries(options.seed ?? {})) {\n const col = this.col(register as Collection);\n for (const r of rows ?? []) col.set(r.id, structuredClone(r));\n }\n }\n\n private col(register: Collection): Map<string, AnyRecord> {\n let c = this.store.get(register);\n if (!c) {\n c = new Map();\n this.store.set(register, c);\n }\n return c;\n }\n\n async list(register: Collection): Promise<AnyRecord[]> {\n return [...this.col(register).values()].map((r) => structuredClone(r));\n }\n\n async get(register: Collection, id: string): Promise<AnyRecord> {\n const r = this.col(register).get(id);\n if (!r) throw new NotFoundError(register, id);\n return structuredClone(r);\n }\n\n async create(\n register: Collection,\n data: Partial<AnyRecord>,\n ): Promise<AnyRecord> {\n const id = data.id ? String(data.id) : `${register}-${++this.counter}`;\n if (this.col(register).has(id)) {\n throw new Error(`${register}/${id} already exists`);\n }\n const ts = this.now();\n const { id: _i, ...rest } = data;\n const record = {\n ...rest,\n id,\n version: 0,\n createdAt: ts,\n updatedAt: ts,\n } as AnyRecord;\n this.col(register).set(id, record);\n return structuredClone(record);\n }\n\n async update(\n register: Collection,\n id: string,\n patch: Partial<AnyRecord>,\n version: number,\n ): Promise<AnyRecord> {\n const current = this.col(register).get(id);\n if (!current) throw new NotFoundError(register, id);\n if (current.version !== version) {\n throw new StaleVersionError(register, id, version, current.version);\n }\n const { id: _i, version: _v, createdAt: _c, ...safe } = patch;\n const next = {\n ...current,\n ...safe,\n id,\n version: current.version + 1,\n updatedAt: this.now(),\n } as AnyRecord;\n this.col(register).set(id, next);\n return structuredClone(next);\n }\n\n async link(relation: Relation, from: RecordRef, to: RecordRef): Promise<void> {\n const spec = RELATIONS[relation];\n this.applyEnd(spec.from, from.register, from.id, to.id, \"add\");\n if (spec.to) this.applyEnd(spec.to, to.register, to.id, from.id, \"add\");\n }\n\n async unlink(\n relation: Relation,\n from: RecordRef,\n to: RecordRef,\n ): Promise<void> {\n const spec = RELATIONS[relation];\n this.applyEnd(spec.from, from.register, from.id, to.id, \"remove\");\n if (spec.to) this.applyEnd(spec.to, to.register, to.id, from.id, \"remove\");\n }\n\n private applyEnd(\n end: RelationEnd,\n register: Collection,\n ownerId: string,\n otherId: string,\n op: \"add\" | \"remove\",\n ): void {\n const rec = this.col(register).get(ownerId);\n if (!rec) throw new NotFoundError(register, ownerId);\n if (end.cardinality === \"many\") {\n const arr = Array.isArray(rec[end.field])\n ? (rec[end.field] as string[])\n : [];\n if (op === \"add\") {\n if (!arr.includes(otherId)) arr.push(otherId);\n rec[end.field] = arr;\n } else {\n rec[end.field] = arr.filter((x) => x !== otherId);\n }\n } else {\n // A single-valued end: set it on add, clear it on remove (when it matches).\n if (op === \"add\") rec[end.field] = otherId;\n else if (rec[end.field] === otherId) rec[end.field] = null;\n }\n rec.version += 1;\n rec.updatedAt = this.now();\n }\n}\n\nexport function createInMemoryProvider(\n options?: InMemoryProviderOptions,\n): InMemoryProvider {\n return new InMemoryProvider(options);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -28,7 +28,7 @@ type AssumptionStatus = "Draft" | "Live" | "Invalidated";
|
|
|
28
28
|
* commit clears (Draft→Running); conclude+verdict stays the Running→Closed
|
|
29
29
|
* gate. Absorbs what the retired Goal record's status used to carry.
|
|
30
30
|
*/
|
|
31
|
-
type ExperimentStatus = "Draft" | "Running" | "Closed";
|
|
31
|
+
type ExperimentStatus = "Draft" | "Running" | "Closed" | "Archived";
|
|
32
32
|
type DecisionStatus = "Active" | "Provisional" | "Superseded" | "Reversed";
|
|
33
33
|
type GlossaryStatus = "Active" | "Provisional" | "Superseded";
|
|
34
34
|
type Result = "Validated" | "Invalidated" | "Inconclusive";
|
|
@@ -80,6 +80,32 @@ interface AssumptionRecord extends BaseRecord {
|
|
|
80
80
|
readingIds: string[];
|
|
81
81
|
derived: AssumptionDerived;
|
|
82
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* One belief's scoring inside a reading — the per-assumption verdict an
|
|
85
|
+
* artifact carries. A reading (one artifact ROW) may score several beliefs at
|
|
86
|
+
* once, so the rung/result/justification that used to live on the row now live
|
|
87
|
+
* here, one entry per assumption. Mirrors how {@link BarLine} embeds on an
|
|
88
|
+
* experiment: no identity of its own, and its `strength` is derived (never
|
|
89
|
+
* hand-typed). Source quality stays row-level — it is a property of the
|
|
90
|
+
* artifact, not the belief.
|
|
91
|
+
*/
|
|
92
|
+
interface BeliefScore {
|
|
93
|
+
assumptionId: string;
|
|
94
|
+
Rung: Rung;
|
|
95
|
+
Result: Result;
|
|
96
|
+
/** For Market-rung readings: the magnitude band from the absolute outcome. */
|
|
97
|
+
magnitudeBand?: MagnitudeBand;
|
|
98
|
+
/** The rationale for the rung / representativeness / credibility picks. */
|
|
99
|
+
"Grading justification": string;
|
|
100
|
+
/** Derived per belief: rung anchor × sign(Result) [× magnitude band]. */
|
|
101
|
+
derived: {
|
|
102
|
+
strength: number;
|
|
103
|
+
};
|
|
104
|
+
/** Provenance: the original reading id this belief was migrated from. */
|
|
105
|
+
sourceReadingId?: string;
|
|
106
|
+
/** Optional review caveat, e.g. a second-hand-credibility note. */
|
|
107
|
+
reviewNote?: string;
|
|
108
|
+
}
|
|
83
109
|
interface ReadingRecord extends BaseRecord {
|
|
84
110
|
Title: string;
|
|
85
111
|
/** The independence/dedupe key — the generator (person / dataset / cohort). */
|
|
@@ -90,22 +116,20 @@ interface ReadingRecord extends BaseRecord {
|
|
|
90
116
|
* dedupe key stays narrow.
|
|
91
117
|
*/
|
|
92
118
|
contextLinks: string[];
|
|
93
|
-
assumptionId: string;
|
|
94
119
|
/** The originating plan, or null for a bare/found reading (no Goal origin). */
|
|
95
120
|
experimentId: string | null;
|
|
96
|
-
Rung: Rung;
|
|
97
121
|
Representativeness: SourceQualityPick;
|
|
98
122
|
Credibility: SourceQualityPick;
|
|
99
|
-
/** For Market-rung readings: the magnitude band from the absolute outcome. */
|
|
100
|
-
magnitudeBand?: MagnitudeBand;
|
|
101
|
-
Result: Result;
|
|
102
|
-
/** The rationale for the rung / representativeness / credibility picks. */
|
|
103
|
-
"Grading justification": string;
|
|
104
123
|
Date: string | null;
|
|
105
124
|
Owner: string[];
|
|
125
|
+
/** Free-text narrative of the reading. */
|
|
126
|
+
body?: string;
|
|
127
|
+
/** Per-belief scores — one artifact row can score several beliefs at once. */
|
|
128
|
+
beliefs: BeliefScore[];
|
|
129
|
+
/** Convenience projection of `beliefs[].assumptionId`; kept in sync on write. */
|
|
130
|
+
assumptionIds: string[];
|
|
106
131
|
derived: {
|
|
107
132
|
sourceQuality: number;
|
|
108
|
-
strength: number;
|
|
109
133
|
};
|
|
110
134
|
}
|
|
111
135
|
/**
|
|
@@ -126,6 +150,8 @@ interface ExperimentRecord extends BaseRecord {
|
|
|
126
150
|
Instrument: string | null;
|
|
127
151
|
Feasibility: Feasibility | null;
|
|
128
152
|
Status: ExperimentStatus;
|
|
153
|
+
/** Free-text narrative of the plan. */
|
|
154
|
+
body?: string;
|
|
129
155
|
closureReason: "Completed" | "Early-stop" | "Kill" | null;
|
|
130
156
|
/** Optional deadline a committed plan carries (folded in from the Goal). */
|
|
131
157
|
Deadline: string | null;
|
|
@@ -176,4 +202,4 @@ interface RecordRef {
|
|
|
176
202
|
/** The relations the dashboard can set (each writes both ends). */
|
|
177
203
|
type Relation = "assumption-reading" | "assumption-depends-on" | "assumption-contradicts" | "reading-experiment" | "decision-based-on" | "decision-resolves";
|
|
178
204
|
|
|
179
|
-
export { type AssumptionRecord as A, type BarLine as B, type Collection as C, type DecisionRecord as D, type ExperimentRecord as E, type Feasibility as F, type GlossaryAvoid as G, MARKET_RUNG_VALUES as M, type Relation as R, type SourceQualityPick as S, TESTING_RUNGS as T, type ReadingRecord as a, type AssumptionDerived as b, type AnyRecord as c, type AssumptionStatus as d, type BaseRecord as e, type
|
|
205
|
+
export { type AssumptionRecord as A, type BarLine as B, type Collection as C, type DecisionRecord as D, type ExperimentRecord as E, type Feasibility as F, type GlossaryAvoid as G, MARKET_RUNG_VALUES as M, type Relation as R, type SourceQualityPick as S, TESTING_RUNGS as T, type ReadingRecord as a, type AssumptionDerived as b, type AnyRecord as c, type AssumptionStatus as d, type BaseRecord as e, type BeliefScore as f, type DecisionStatus as g, type ExperimentStatus as h, type GlossaryRecord as i, type GlossaryStatus as j, type MagnitudeBand as k, type MarketRung as l, REGISTERS as m, type RecordRef as n, type Register as o, type Result as p, type Rung as q, type TestingRung as r };
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/derivation/index.ts","../src/derivation/round.ts","../src/types.ts","../src/derivation/rung.ts","../src/derivation/completeness.ts","../src/derivation/strength.ts","../src/derivation/source-quality.ts","../src/derivation/confidence.ts","../src/derivation/attribution.ts","../src/derivation/progress.ts","../src/derivation/trajectory.ts","../src/derivation/impact.ts","../src/derivation/risk.ts","../src/derivation/portfolio.ts","../src/derivation/next-move.ts","../src/derivation/stage.ts","../src/derivation/journey.ts"],"sourcesContent":["/**\n * The shared derivation module — pure functions, no I/O.\n *\n * The same module the dashboard, the API (derive-on-write), and Claude Code\n * audits all call, so every writer computes the four derived numbers\n * identically. Ported from `doshi-validation-os/migration/remodel.mjs` and\n * kept in lock-step with `skills/_shared/ontology.yaml`.\n */\nexport { round2 } from \"./round.js\";\nexport {\n RUNG_ANCHOR,\n MARKET_RUNG_ANCHOR,\n isMarketRung,\n} from \"./rung.js\";\nexport {\n COMPLETENESS_SLOTS,\n completenessSlotPresence,\n missingCompletenessSlots,\n assumptionCompleteness,\n assumptionComplete,\n} from \"./completeness.js\";\nexport type { CompletenessSlot, CompletenessInput } from \"./completeness.js\";\nexport { sign, isConcluded, readingStrength } from \"./strength.js\";\nexport type { StrengthInput } from \"./strength.js\";\nexport { sourceQuality } from \"./source-quality.js\";\nexport { confidence, scoreAndDedupe, W0 } from \"./confidence.js\";\nexport type { ConfidenceReadingInput, Scored } from \"./confidence.js\";\nexport { confidenceAttribution } from \"./attribution.js\";\nexport type {\n Attribution,\n AttributionReadingInput,\n Mover,\n MoverKind,\n} from \"./attribution.js\";\nexport { experimentProgress } from \"./progress.js\";\nexport type { BarLineInput, Progress } from \"./progress.js\";\nexport { confidenceTrajectory } from \"./trajectory.js\";\nexport type { TrajectoryPoint } from \"./trajectory.js\";\nexport { derivedImpacts } from \"./impact.js\";\nexport type { ImpactAssumptionInput } from \"./impact.js\";\nexport { risk } from \"./risk.js\";\nexport { beliefRisk, portfolioProgress } from \"./portfolio.js\";\nexport type {\n BeliefRisk,\n PortfolioBeliefInput,\n PortfolioProgress,\n} from \"./portfolio.js\";\nexport { rankNextMoves, KILL_LANE_THRESHOLD } from \"./next-move.js\";\nexport type {\n MoveKind,\n NextMove,\n NextMoveAssumptionInput,\n NextMoveExperimentInput,\n NextMoveDecisionInput,\n NextMoveInput,\n} from \"./next-move.js\";\nexport {\n beliefTestMeters,\n classifyStage,\n deriveBeliefStage,\n emptyTestMeter,\n} from \"./stage.js\";\nexport type {\n BeliefStage,\n BeliefStageInput,\n ConfSign,\n StageExperimentInput,\n StageKey,\n TestMeter,\n} from \"./stage.js\";\nexport { assembleJourney } from \"./journey.js\";\nexport type {\n AssembleJourneyInput,\n JourneyBeliefInput,\n JourneyEvent,\n JourneyEventKind,\n JourneyExperimentInput,\n} from \"./journey.js\";\n","/**\n * Round to 2 decimals, matching the migration's `+(n).toFixed(2)`.\n * Derived values are stored/displayed rounded; full precision is only used\n * transiently inside a single computation.\n */\nexport function round2(n: number): number {\n return Number(n.toFixed(2));\n}\n","/**\n * Shared registry types — the field vocabulary the whole system speaks.\n *\n * These mirror `skills/_shared/registry-schema.md` and `ontology.yaml` (the\n * single source of truth). Field *meaning* lives there; this file is the\n * checkable TypeScript shape the packages import.\n */\n\n/**\n * The five registers. `goals` was unified into `experiments` and the `people`\n * reference collection retired (OPS-1305): Owner / Agreed by now reference a\n * dashboard user (the auth-sourced team list), not a register row.\n */\nexport const REGISTERS = [\n \"assumptions\",\n \"experiments\",\n \"readings\",\n \"decisions\",\n \"glossary\",\n] as const;\nexport type Register = (typeof REGISTERS)[number];\n\n/** There is no reference collection any more — a collection is a register. */\nexport type Collection = Register;\n\n/** Every stored record carries an id, a version, and timestamps. */\nexport interface BaseRecord {\n id: string;\n /** Optimistic-concurrency token; bumped on every write. */\n version: number;\n createdAt: string;\n updatedAt: string;\n}\n\n// ── Vocabularies (canonical select-option lists) ────────────────────────────\n\nexport type AssumptionStatus = \"Draft\" | \"Live\" | \"Invalidated\";\n/**\n * The unified evidence-plan lifecycle (OPS-1305). `Draft` is the new gate a\n * commit clears (Draft→Running); conclude+verdict stays the Running→Closed\n * gate. Absorbs what the retired Goal record's status used to carry.\n */\nexport type ExperimentStatus = \"Draft\" | \"Running\" | \"Closed\";\nexport type DecisionStatus =\n | \"Active\"\n | \"Provisional\"\n | \"Superseded\"\n | \"Reversed\";\nexport type GlossaryStatus = \"Active\" | \"Provisional\" | \"Superseded\";\n\nexport type Result = \"Validated\" | \"Invalidated\" | \"Inconclusive\";\nexport type MagnitudeBand = \"Low\" | \"Typical\" | \"High\";\nexport type Feasibility = \"High\" | \"Medium\" | \"Low\";\n\n/**\n * The 8-rung activity-and-strength ladder (order = strength, weakest first).\n * Two categories: Testing (recruited-sample instruments) and Market (open-world\n * targets — the category formerly called \"Goals\", renamed with the unification,\n * OPS-1305). The anchors and physics are unchanged by the rename.\n */\nexport const TESTING_RUNGS = [\n \"Opinion\",\n \"Pitch-deck reaction\",\n \"Anecdotal\",\n \"Desk research\",\n \"Survey at scale\",\n \"Prototype usage\",\n] as const;\nexport const MARKET_RUNG_VALUES = [\"Signed intent\", \"Paying users\"] as const;\nexport type TestingRung = (typeof TESTING_RUNGS)[number];\nexport type MarketRung = (typeof MARKET_RUNG_VALUES)[number];\nexport type Rung = TestingRung | MarketRung;\n\n/** Representativeness and Credibility are each picked from these. */\nexport type SourceQualityPick = 1.0 | 0.7 | 0.5;\n\n// ── Records ─────────────────────────────────────────────────────────────────\n\n/** The derived numbers stored on an assumption (never hand-typed). */\nexport interface AssumptionDerived {\n derivedImpact: number;\n risk: number;\n confidence: number;\n /** Structural readiness meter, 0–100 (see `derivation/completeness.ts`). */\n completeness: number;\n}\n\nexport interface AssumptionRecord extends BaseRecord {\n Title: string;\n Description: string;\n Lens: string | null;\n Theme: string[];\n /** The hand-scored seed (0–100), the only hand-scored number here. */\n Impact: number | null;\n Status: AssumptionStatus;\n /** Reference to a dashboard user (auth team list), not a `people` row. */\n Owner: string[];\n moot: boolean;\n /**\n * Kept, narrowed to the Impact-seed rationale (OPS-1305): why the seed\n * `Impact` was scored as it was, incl. dated moot lines. The `5 Whys`,\n * `Metric for truth`, and `Gaps` fields are gone — the why-trace lives in the\n * `Depends on / Enables` chain and the audit check-types are transient grill\n * stages, not stored tags. Readiness is the derived `completeness`.\n */\n \"Scoring justification\": string;\n dependsOnIds: string[];\n enablesIds: string[];\n contradictsIds: string[];\n readingIds: string[];\n derived: AssumptionDerived;\n}\n\nexport interface ReadingRecord extends BaseRecord {\n Title: string;\n /** The independence/dedupe key — the generator (person / dataset / cohort). */\n Source: string | null;\n /**\n * Provenance links (recording, dashboard, CRM row, user id) — 0..N, drives no\n * math and never keys dedupe (OPS-1305). Split out from `Source` so the\n * dedupe key stays narrow.\n */\n contextLinks: string[];\n assumptionId: string;\n /** The originating plan, or null for a bare/found reading (no Goal origin). */\n experimentId: string | null;\n Rung: Rung;\n Representativeness: SourceQualityPick;\n Credibility: SourceQualityPick;\n /** For Market-rung readings: the magnitude band from the absolute outcome. */\n magnitudeBand?: MagnitudeBand;\n Result: Result;\n /** The rationale for the rung / representativeness / credibility picks. */\n \"Grading justification\": string;\n Date: string | null;\n Owner: string[];\n derived: { sourceQuality: number; strength: number };\n}\n\n/**\n * A pre-registered bar line — the per-belief pass/fail test on an experiment,\n * stored as an embedded array on the experiment (no identity of its own; see\n * `connectors/nosql-schema.md`). `barVerdict` is set once at closure and is a\n * report only — never folded into Confidence.\n */\nexport interface BarLine {\n assumptionId: string;\n rightIf: string;\n wrongIf?: string | null;\n plannedRung: Rung;\n barVerdict?: Result | null;\n}\n\nexport interface ExperimentRecord extends BaseRecord {\n Title: string;\n Instrument: string | null;\n Feasibility: Feasibility | null;\n Status: ExperimentStatus;\n closureReason: \"Completed\" | \"Early-stop\" | \"Kill\" | null;\n /** Optional deadline a committed plan carries (folded in from the Goal). */\n Deadline: string | null;\n /** Terminal closure outcome, null until Closed (folded in from the Goal). */\n Outcome: \"Achieved\" | \"Missed\" | \"Dropped\" | null;\n /** Reference to a dashboard user, not a `people` row. */\n Owner: string[];\n Date: string | null;\n /** The embedded pre-registered bar lines; drives progress-to-conclusion. */\n barLines: BarLine[];\n /** Convenience projection: the assumptions the bar lines test. */\n barLineAssumptionIds: string[];\n}\n\nexport interface DecisionRecord extends BaseRecord {\n Title: string;\n Status: DecisionStatus;\n /** The one-line statement of what was decided (promoted from the body). */\n Statement: string;\n /** Why the Unanimity score was scored as it was (promoted from the body). */\n \"Unanimity justification\": string;\n /** References to dashboard users (auth team list), not `people` rows. */\n Owner: string[];\n \"Agreed by\": string[];\n basedOnIds: string[];\n resolvesIds: string[];\n}\n\n/** One structured \"don't say\" entry on a glossary term. */\nexport interface GlossaryAvoid {\n audience: string;\n phrase: string;\n fix: string;\n}\n\nexport interface GlossaryRecord extends BaseRecord {\n Title: string;\n Status: GlossaryStatus;\n /** All properties, no body (OPS-1305): the terminology check parses these. */\n Definition: string;\n Avoid: GlossaryAvoid[];\n \"How it differs\": string;\n}\n\n/** A record of any register — the DataProvider's currency. */\nexport type AnyRecord = BaseRecord & Record<string, unknown>;\n\n// ── Relations (both-ends links) ─────────────────────────────────────────────\n\n/** A pointer to one record. */\nexport interface RecordRef {\n register: Collection;\n id: string;\n}\n\n/** The relations the dashboard can set (each writes both ends). */\nexport type Relation =\n | \"assumption-reading\"\n | \"assumption-depends-on\"\n | \"assumption-contradicts\"\n | \"reading-experiment\"\n | \"decision-based-on\"\n | \"decision-resolves\";\n","/**\n * The evidence ladder anchors that feed Strength.\n *\n * Source of truth: `skills/_shared/ontology.yaml` → `vocabularies.rung`.\n * Testing rungs carry a single anchor; Market rungs (the category formerly\n * called \"Goals\", OPS-1305) carry a magnitude band (Low/Typical/High) picked\n * from the absolute outcome. The anchors are unchanged by the rename.\n */\nimport type { MarketRung, MagnitudeBand, Rung, TestingRung } from \"../types.js\";\nimport { MARKET_RUNG_VALUES } from \"../types.js\";\n\nexport const RUNG_ANCHOR: Record<TestingRung, number> = {\n Opinion: 3,\n \"Pitch-deck reaction\": 6,\n Anecdotal: 10,\n \"Desk research\": 15,\n \"Survey at scale\": 25,\n \"Prototype usage\": 30,\n};\n\nexport const MARKET_RUNG_ANCHOR: Record<\n MarketRung,\n Record<MagnitudeBand, number>\n> = {\n \"Signed intent\": { Low: 55, Typical: 68, High: 80 },\n \"Paying users\": { Low: 75, Typical: 88, High: 99 },\n};\n\nconst MARKET_RUNG_SET = new Set<Rung>(MARKET_RUNG_VALUES);\n\nexport function isMarketRung(rung: Rung): rung is MarketRung {\n return MARKET_RUNG_SET.has(rung);\n}\n","/**\n * Assumption completeness — a derived readiness meter, never stored.\n *\n * Replaces the old Gaps / presence-field machinery (OPS-1305): an assumption's\n * Draft-vs-Live readiness is the structural presence of its slots, not a set of\n * hand-maintained tags. `Completeness %` is `filled slots / all slots`; a\n * fully-filled assumption reads 100 and is Live-ready, an empty draft reads low.\n *\n * Slots (each an equal fifth):\n * Description · Lens · Impact · Scoring justification · Dependencies traced\n *\n * Pure, no backend dependency — the same function the recompute pass stamps\n * into the derived tuple and the audit checks readiness against.\n */\n\n/** The structural slots whose presence makes an assumption Live-ready. */\nexport const COMPLETENESS_SLOTS = [\n \"Description\",\n \"Lens\",\n \"Impact\",\n \"Scoring justification\",\n \"Dependencies traced\",\n] as const;\n\nexport type CompletenessSlot = (typeof COMPLETENESS_SLOTS)[number];\n\n/** The record shape completeness reads — an assumption's structural fields. */\nexport interface CompletenessInput {\n Description?: unknown;\n Lens?: unknown;\n Impact?: unknown;\n \"Scoring justification\"?: unknown;\n dependsOnIds?: unknown;\n enablesIds?: unknown;\n}\n\n/** A text slot is present only when it is a non-blank string. */\nfunction hasText(value: unknown): boolean {\n return typeof value === \"string\" && value.trim().length > 0;\n}\n\n/** Impact is present when it is a real finite number (0 is a valid score). */\nfunction hasNumber(value: unknown): boolean {\n return typeof value === \"number\" && Number.isFinite(value);\n}\n\n/** Dependencies are traced once at least one Depends on / Enables link exists. */\nfunction hasAny(value: unknown): boolean {\n return Array.isArray(value) && value.length > 0;\n}\n\n/** Whether each slot is structurally present. */\nexport function completenessSlotPresence(\n record: CompletenessInput,\n): Record<CompletenessSlot, boolean> {\n return {\n Description: hasText(record.Description),\n Lens: hasText(record.Lens),\n Impact: hasNumber(record.Impact),\n \"Scoring justification\": hasText(record[\"Scoring justification\"]),\n \"Dependencies traced\":\n hasAny(record.dependsOnIds) || hasAny(record.enablesIds),\n };\n}\n\n/** The slots that are absent or blank on a record. */\nexport function missingCompletenessSlots(\n record: CompletenessInput,\n): CompletenessSlot[] {\n const present = completenessSlotPresence(record);\n return COMPLETENESS_SLOTS.filter((slot) => !present[slot]);\n}\n\n/** Completeness as a whole-number percentage (0, 20, 40, 60, 80, 100). */\nexport function assumptionCompleteness(record: CompletenessInput): number {\n const present = completenessSlotPresence(record);\n const filled = COMPLETENESS_SLOTS.filter((slot) => present[slot]).length;\n return Math.round((filled / COMPLETENESS_SLOTS.length) * 100);\n}\n\n/** True when every slot is present — the structural precondition to be Live. */\nexport function assumptionComplete(record: CompletenessInput): boolean {\n return missingCompletenessSlots(record).length === 0;\n}\n","/**\n * Strength — the signed reading value `s` the Confidence average reads.\n *\n * Formula (`ontology.yaml` → `derivations.strength`):\n * rung anchor (Market rungs: × magnitude band) × sign(Result)\n * — Validated positive, Invalidated negative; 0 unless Validated/Invalidated.\n */\nimport type { MagnitudeBand, Result, Rung } from \"../types.js\";\nimport { MARKET_RUNG_ANCHOR, RUNG_ANCHOR, isMarketRung } from \"./rung.js\";\n\nexport function sign(result: Result): -1 | 0 | 1 {\n if (result === \"Validated\") return 1;\n if (result === \"Invalidated\") return -1;\n return 0;\n}\n\n/** A reading counts toward Confidence only once concluded either way; an\n * Inconclusive reading carries no signal. The single definition the whole\n * derivation module (and its record→input mappers) share. */\nexport function isConcluded(result: Result): boolean {\n return result === \"Validated\" || result === \"Invalidated\";\n}\n\nexport interface StrengthInput {\n rung: Rung;\n result: Result;\n /** Only read for Market rungs; defaults to \"Typical\" when absent. */\n magnitudeBand?: MagnitudeBand;\n}\n\nexport function readingStrength(input: StrengthInput): number {\n const s = sign(input.result);\n if (s === 0) return 0; // Inconclusive contributes nothing.\n if (isMarketRung(input.rung)) {\n const band = input.magnitudeBand ?? \"Typical\";\n return MARKET_RUNG_ANCHOR[input.rung][band] * s;\n }\n return (RUNG_ANCHOR[input.rung] ?? 0) * s;\n}\n","/**\n * Source quality — Representativeness × Credibility.\n *\n * Scales a Reading's *weight* in the Confidence average, within its rung.\n * We keep the raw product as the weight (matching the migration's\n * `remodel.mjs`); the five display anchors {0.25, 0.35, 0.5, 0.7, 1.0} are a\n * storage/display concern, not the weight used in the average.\n */\nimport { round2 } from \"./round.js\";\n\nexport function sourceQuality(\n representativeness: number,\n credibility: number,\n): number {\n return round2(representativeness * credibility);\n}\n","/**\n * Confidence — signed −100…100, 0 = no evidence.\n *\n * Formula (`ontology.yaml` → `derivations.confidence`):\n * (w0·0 + Σ wi·si) / (w0 + Σ wi), w0 = 100,\n * wi = |si| × Source quality, si = the reading's signed Strength.\n *\n * Only concluded Validated/Invalidated readings enter. Readings sharing a\n * Source against one belief dedupe to the strongest (largest |si|, most\n * recent on ties). Market-rung readings never dedupe (each closed commitment\n * is its own unit). No corroboration bump.\n */\nimport type { MagnitudeBand, Result, Rung } from \"../types.js\";\nimport { round2 } from \"./round.js\";\nimport { isMarketRung } from \"./rung.js\";\nimport { sourceQuality } from \"./source-quality.js\";\nimport { isConcluded, readingStrength } from \"./strength.js\";\n\n/** The neutral prior weight — a hard floor per the guardrails. */\nexport const W0 = 100;\n\nexport interface ConfidenceReadingInput {\n id: string;\n /** The independence-dedupe key. Null falls back to the reading's own id. */\n source: string | null;\n rung: Rung;\n result: Result;\n representativeness: number;\n credibility: number;\n /** ISO date; used only as the dedupe tie-break (most recent wins). */\n date?: string | null;\n magnitudeBand?: MagnitudeBand;\n}\n\nexport interface Scored {\n input: ConfidenceReadingInput;\n strength: number;\n sq: number;\n /** The reading's weight in the average: |strength| × Source quality. */\n weight: number;\n}\n\n/**\n * Score every concluded reading and resolve the Source dedupe — the shared\n * front half of the Confidence average. `confidence()` reduces the winners to\n * a number; `confidenceAttribution()` reuses the same winners so the movers it\n * reports always decompose the very number the drawer shows. Market rungs never\n * dedupe (each closed commitment is its own unit).\n */\nexport function scoreAndDedupe(readings: ConfidenceReadingInput[]): Scored[] {\n const scored: Scored[] = readings\n .filter((r) => isConcluded(r.result))\n .map((r) => {\n const strength = readingStrength(r);\n const sq = sourceQuality(r.representativeness, r.credibility);\n return { input: r, strength, sq, weight: Math.abs(strength) * sq };\n })\n .filter((x) => x.strength !== 0);\n\n const best = new Map<string, Scored>();\n for (const x of scored) {\n if (isMarketRung(x.input.rung)) {\n best.set(x.input.id, x);\n continue;\n }\n const key = x.input.source || x.input.id;\n const cur = best.get(key);\n const better =\n !cur ||\n Math.abs(x.strength) > Math.abs(cur.strength) ||\n (Math.abs(x.strength) === Math.abs(cur.strength) &&\n (x.input.date || \"\") > (cur.input.date || \"\"));\n if (better) best.set(key, x);\n }\n return [...best.values()];\n}\n\nexport function confidence(readings: ConfidenceReadingInput[]): number {\n const winners = scoreAndDedupe(readings);\n let num = 0;\n let den = W0;\n for (const x of winners) {\n num += x.weight * x.strength;\n den += x.weight;\n }\n return den > 0 ? round2(num / den) : 0;\n}\n","/**\n * Confidence attribution — the \"what's moving the number\" half of the\n * understanding layer (OPS-1276). Decomposes an assumption's Confidence into\n * the experiments (and direct readings) contributing to it, ranked by how hard\n * each pushes the number up or down.\n *\n * A winner's contribution is its signed share of the average:\n * cᵢ = (weightᵢ · strengthᵢ) / den, den = w0 + Σ weight\n * so Σ contributions = Σ(wᵢ·sᵢ)/den = Confidence (the w0·0 prior term is 0).\n * The reveal therefore literally adds up to the hero number. Contributions are\n * grouped by the reading's experiment (experiment-less readings — bare/found —\n * fall into a \"direct\" bucket), then ranked by |contribution|. A reading's\n * origin is an experiment or nothing; the retired Goal container is gone\n * (OPS-1305).\n */\nimport { W0, scoreAndDedupe, type ConfidenceReadingInput } from \"./confidence.js\";\nimport { round2 } from \"./round.js\";\n\nexport interface AttributionReadingInput extends ConfidenceReadingInput {\n /** The experiment that produced the reading, if any. */\n experimentId?: string | null;\n}\n\n/** What a mover is anchored to — an experiment, or nothing (direct). */\nexport type MoverKind = \"experiment\" | \"direct\";\n\nexport interface Mover {\n /** Stable grouping key: the experiment id, or \"direct\". */\n key: string;\n kind: MoverKind;\n /** The experiment id when `kind === \"experiment\"`, else null. */\n experimentId: string | null;\n /** Signed push on Confidence; the whole set sums to `confidence`. */\n contribution: number;\n /** |contribution| — the rank key and the \"how hard\" magnitude. */\n magnitude: number;\n /** How many (deduped) readings back this mover. */\n readingCount: number;\n /** The winning readings' ids, for drill-through. */\n readingIds: string[];\n}\n\nexport interface Attribution {\n /** The same Confidence the derived box shows (from the same winners). */\n confidence: number;\n /** Movers ranked by |contribution|, strongest first. */\n movers: Mover[];\n}\n\nfunction bucketOf(r: AttributionReadingInput): {\n key: string;\n kind: MoverKind;\n experimentId: string | null;\n} {\n if (r.experimentId) {\n return {\n key: r.experimentId,\n kind: \"experiment\",\n experimentId: r.experimentId,\n };\n }\n return { key: \"direct\", kind: \"direct\", experimentId: null };\n}\n\nexport function confidenceAttribution(\n readings: AttributionReadingInput[],\n): Attribution {\n const winners = scoreAndDedupe(readings);\n const den = winners.reduce((acc, x) => acc + x.weight, W0);\n\n const byKey = new Map<string, Mover>();\n let num = 0;\n for (const x of winners) {\n num += x.weight * x.strength;\n const b = bucketOf(x.input as AttributionReadingInput);\n const contribution = (x.weight * x.strength) / den;\n const cur = byKey.get(b.key);\n if (cur) {\n cur.contribution += contribution;\n cur.readingCount += 1;\n cur.readingIds.push(x.input.id);\n } else {\n byKey.set(b.key, {\n key: b.key,\n kind: b.kind,\n experimentId: b.experimentId,\n contribution,\n magnitude: 0, // finalised below\n readingCount: 1,\n readingIds: [x.input.id],\n });\n }\n }\n\n const movers = [...byKey.values()].map((m) => ({\n ...m,\n contribution: round2(m.contribution),\n magnitude: round2(Math.abs(m.contribution)),\n }));\n movers.sort((a, b) => b.magnitude - a.magnitude);\n\n return {\n confidence: den > 0 ? round2(num / den) : 0,\n movers,\n };\n}\n","/**\n * Progress-to-conclusion — how close a running experiment is to concluding\n * (OPS-1276). An experiment concludes when every pre-registered bar line has\n * been given a Bar verdict; progress is the count of settled bars against the\n * total. Bar verdict is a report, never a Confidence input (nosql-schema.md),\n * so reading it here is exactly its intended use.\n */\nexport interface BarLineInput {\n /** null/\"\" until the bar is judged at closure. */\n barVerdict?: string | null;\n}\n\nexport interface Progress {\n /** Pre-registered bar lines on the experiment. */\n total: number;\n /** Bars that have a verdict. */\n settled: number;\n /** Bars still awaiting a verdict. */\n toGo: number;\n /** True once every bar is settled (and there is at least one). */\n concluded: boolean;\n}\n\nexport function experimentProgress(bars: BarLineInput[]): Progress {\n const total = bars.length;\n const settled = bars.filter(\n (b) => typeof b.barVerdict === \"string\" && b.barVerdict.trim() !== \"\",\n ).length;\n return {\n total,\n settled,\n toGo: total - settled,\n concluded: total > 0 && settled === total,\n };\n}\n","/**\n * Confidence over time — the story of how the number got where it is\n * (OPS-1276). At each date a concluded reading was dated, we recompute\n * Confidence over every concluded reading up to and including that date, using\n * the very same `confidence()` the derived box uses (so the last point equals\n * the hero number). Undated concluded readings have no place on the timeline\n * but still bear on the belief, so they are treated as always-present — folded\n * into every point — which keeps the final point equal to today's Confidence.\n */\nimport { confidence, type ConfidenceReadingInput } from \"./confidence.js\";\nimport { isConcluded } from \"./strength.js\";\n\nexport interface TrajectoryPoint {\n /** ISO date at which Confidence took this value. */\n date: string;\n confidence: number;\n}\n\nexport function confidenceTrajectory(\n readings: ConfidenceReadingInput[],\n): TrajectoryPoint[] {\n const concluded = readings.filter((r) => isConcluded(r.result));\n const undated = concluded.filter((r) => !r.date);\n const dated = concluded.filter((r) => r.date);\n if (dated.length === 0) return [];\n\n const dates = [...new Set(dated.map((r) => r.date as string))].sort();\n return dates.map((date) => ({\n date,\n // Everything dated on/before this point, plus the always-present undated.\n confidence: confidence([\n ...undated,\n ...dated.filter((r) => (r.date as string) <= date),\n ]),\n }));\n}\n","/**\n * Derived Impact — propagates dependents' pull into a belief's seed.\n *\n * Formula (`ontology.yaml` → `derivations.derived_impact`):\n * seed + (100 − seed) × S / (S + 100), where\n * S = Σ Derived Impact of assumptions whose `Depends on` names this row\n * + 100 per standing (Provisional/Active) decision naming it via\n * `Based on assumption`.\n * Goals never contribute. Moot rows pin to 0 and contribute nothing.\n *\n * One reverse-topological pass (dependents first) with memoization and a\n * cycle guard, matching `doshi-validation-os/migration/remodel.mjs`.\n */\nimport { round2 } from \"./round.js\";\n\nexport interface ImpactAssumptionInput {\n id: string;\n /** The hand-scored seed (0–100); null treated as 0. */\n impact: number | null;\n moot?: boolean;\n /** Ids this assumption depends on. */\n dependsOnIds: string[];\n}\n\n/**\n * @param assumptions the full register (never a filtered slice — a filter\n * silently drops dependents from the propagation).\n * @param basedOnCounts id → number of standing decisions with a `Based on`\n * link to that assumption. Each contributes +100 to S.\n */\nexport function derivedImpacts(\n assumptions: ImpactAssumptionInput[],\n basedOnCounts: Record<string, number> = {},\n): Map<string, number> {\n const byId = new Map(assumptions.map((a) => [a.id, a]));\n\n // dependents(X) = assumptions whose dependsOnIds includes X.\n const dependents = new Map<string, string[]>();\n for (const a of assumptions) dependents.set(a.id, []);\n for (const a of assumptions) {\n for (const dep of a.dependsOnIds) dependents.get(dep)?.push(a.id);\n }\n\n const memo = new Map<string, number>();\n const compute = (id: string, seen: Set<string>): number => {\n const cached = memo.get(id);\n if (cached !== undefined) return cached;\n if (seen.has(id)) return 0; // cycle guard\n seen.add(id);\n const a = byId.get(id);\n if (!a) return 0;\n if (a.moot) {\n memo.set(id, 0);\n return 0;\n }\n const seed = a.impact ?? 0;\n let S = 0;\n for (const depId of dependents.get(id) ?? []) {\n const d = byId.get(depId);\n if (d && !d.moot) S += compute(depId, seen);\n }\n S += 100 * (basedOnCounts[id] ?? 0);\n const value = seed + (100 - seed) * (S / (S + 100));\n const rounded = round2(value);\n memo.set(id, rounded);\n return rounded;\n };\n\n const out = new Map<string, number>();\n for (const a of assumptions) out.set(a.id, compute(a.id, new Set()));\n return out;\n}\n","/**\n * Risk — the belief's live standing.\n *\n * Formula (`ontology.yaml` → `derivations.risk`):\n * Derived Impact × (1 − max(0, Confidence) / 100)\n * Ranges 0 to Derived Impact. Negative confidence does not raise Risk above\n * Derived Impact (the max(0, …) clamp).\n */\nimport { round2 } from \"./round.js\";\n\nexport function risk(derivedImpact: number, confidence: number): number {\n return round2(derivedImpact * (1 - Math.max(0, confidence) / 100));\n}\n","/**\n * Portfolio progress — the one *cross-belief* reading (OPS-1293 / OPS-1300).\n *\n * Every other derived number here is per-record (risk/confidence/impact of one\n * belief). This is the whole-set roll-up behind the pipeline's headline: a\n * **burn-up**, \"% of identified risk bought down\" = Risk Retired ÷ Risk-ever-\n * identified across *all* beliefs, resolved ones included.\n *\n * Like `rankNextMoves`, it is a whole-set ordering computed **fresh on read**,\n * not stored — it stays out of the OPS-1251 on-write recompute (it only reads\n * numbers already kept current). Pure and numeric: the dashboard maps records\n * to these inputs (as `understanding.ts` maps readings), so the rule lives\n * once, here.\n *\n * The model (matches the OPS-1296 prototype's self-consistent maths):\n * - **ever-identified** for a belief = the risk it represented at Confidence\n * ≤ 0, i.e. its Derived Impact. A moot row's Derived Impact is pinned to 0,\n * which would erase it from *both* sides of the fraction, so we floor\n * ever-identified at the hand-scored seed Impact — mooting a belief must\n * *retire* its risk, never shrink the denominator (the burn-up's whole\n * point: resolved risk stays counted).\n * - **live** risk = the belief's current Risk, or 0 once resolved (a kill or\n * a moot resolves the uncertainty — that risk is bought down, not carried).\n * - **retired** = ever-identified − live.\n */\nimport { round2 } from \"./round.js\";\n\nexport interface PortfolioBeliefInput {\n id: string;\n /** Derived Impact — the belief's risk at Confidence ≤ 0 (0 when moot). */\n derivedImpact: number;\n /** The hand-scored seed Impact; the ever-identified floor when moot zeroes\n * Derived Impact. Null/absent treated as 0. */\n seedImpact: number | null;\n /** The belief's current live Risk (stored `derived.risk`). */\n risk: number;\n /** Resolved — Invalidated (killed) or moot. Its live risk reads 0 (fully\n * retired), whatever the stored Risk number says. */\n resolved: boolean;\n}\n\n/** One belief's contribution to the burn-up. */\nexport interface BeliefRisk {\n /** Risk-ever-identified — the denominator's per-belief share. */\n identified: number;\n /** Live risk still carried (0 once resolved). */\n live: number;\n /** Risk bought down — the numerator's per-belief share. */\n retired: number;\n}\n\nexport interface PortfolioProgress {\n /** Σ risk-ever-identified — the burn-up denominator. */\n identified: number;\n /** Σ risk bought down — the burn-up numerator. */\n retired: number;\n /** Σ risk still live. */\n live: number;\n /** Retired ÷ identified as a percentage (0 when nothing is identified). */\n percent: number;\n /** Beliefs still in play (not resolved). */\n liveCount: number;\n /** Beliefs resolved (killed or moot). */\n resolvedCount: number;\n}\n\n/** One belief's identified / live / retired risk — the rule in one place. */\nexport function beliefRisk(b: PortfolioBeliefInput): BeliefRisk {\n const seed = b.seedImpact ?? 0;\n const identified = Math.max(b.derivedImpact, seed, 0);\n // Live risk can never exceed what was ever identified — the clamp keeps a\n // stale/over-large stored Risk from making retired go negative.\n const live = b.resolved ? 0 : Math.min(identified, Math.max(0, b.risk));\n return {\n identified: round2(identified),\n live: round2(live),\n retired: round2(identified - live),\n };\n}\n\n/**\n * Roll every belief up into the portfolio burn-up. Pass the *whole* set\n * (resolved rows included) — a filtered slice understates the denominator and\n * makes fresh or retired risk read as backsliding.\n */\nexport function portfolioProgress(\n beliefs: PortfolioBeliefInput[],\n): PortfolioProgress {\n let identified = 0;\n let live = 0;\n let liveCount = 0;\n let resolvedCount = 0;\n for (const b of beliefs) {\n const r = beliefRisk(b);\n identified += r.identified;\n live += r.live;\n if (b.resolved) resolvedCount += 1;\n else liveCount += 1;\n }\n const retired = identified - live;\n return {\n identified: round2(identified),\n retired: round2(retired),\n live: round2(live),\n percent: identified > 0 ? round2((retired / identified) * 100) : 0,\n liveCount,\n resolvedCount,\n };\n}\n","/**\n * Next-move ranking — the front door's single source of truth for \"what should\n * I do next\" (build OPS-1304; placement OPS-1292; action vocabulary OPS-1291).\n *\n * Ranks *beliefs* — Model A: point at one belief, not a heterogeneous triage\n * queue (OPS-1291) — by the method's Feasibility × Risk rule (`docs/method.md`,\n * `ontology.yaml` → `derived_views.next_move`), and names the single act each\n * belief's stage demands. A belief at Confidence ≤ −50 jumps into a distinct\n * kill/re-test lane that sorts above the Feasibility × Risk order regardless of\n * rank — the one place act-urgency beats belief-risk (`derived_views.kill_lane`).\n *\n * Computed fresh on read: a whole-set ordering, so it stays OUT of the OPS-1251\n * on-write recompute — it reads the derived numbers (Risk, Confidence) those\n * writes already keep current. Pure: no I/O, no caching, no weights framework —\n * the enum→multiplier map below IS the formula (OPS-1292: \"no weights /\n * strategies / caching / framework\").\n */\nimport type { Feasibility } from \"../types.js\";\nimport { round2 } from \"./round.js\";\n\n/** The kill/re-test threshold — Confidence at or below this is the kill lane. */\nexport const KILL_LANE_THRESHOLD = -50;\n\n/**\n * The acts the front door can name, one per belief-stage. The front door\n * *names* all of them; only a subset are human step-in forms — the rest are\n * agent-run / off-dashboard (OPS-1294). Which is which is a presentation\n * concern the dashboard owns, not this ranking.\n */\nexport type MoveKind =\n | \"score-impact\" // Framed — the belief isn't weighted yet (Impact unscored)\n | \"design-experiment\" // Planned — no test plans this belief yet\n | \"record-reading\" // Tested — a test is running, evidence still landing\n | \"decide\" // Known — evidence has landed and no decision rests on it\n | \"retest\"; // kill lane — Confidence ≤ −50, jumps the ordering\n\n/**\n * One belief's next move. `move`/`score`/`reason` are the OPS-1292 output\n * contract; the rest is context the front door renders (the risk chip, the kill\n * banner, the step-in adaptation) — every field read from the inputs, nothing\n * new computed here.\n */\nexport interface NextMove {\n /** The act this belief's stage demands. */\n move: MoveKind;\n assumptionId: string;\n /** The belief statement — the hero headline. */\n title: string;\n /** Feasibility × Risk. Kill-lane rows carry their Risk and sort first. */\n score: number;\n /** Plain-language \"why this\" — explains from the inputs, no jargon. */\n reason: string;\n /** The belief's live derived Risk (0–100). */\n risk: number;\n /** The belief's live derived Confidence (signed −100…100). */\n confidence: number;\n /** The feasibility that fed the score; null when no test plans it yet. */\n feasibility: Feasibility | null;\n /** Confidence ≤ −50 — the override lane. */\n killLane: boolean;\n}\n\n/** A belief and its live derived numbers (the ranking's primary input). */\nexport interface NextMoveAssumptionInput {\n id: string;\n title: string;\n /** AssumptionStatus; `Invalidated` rows are already killed and drop out. */\n status: string;\n /** The hand-scored seed (0–100); null means unscored → \"score impact\". */\n impact: number | null;\n /** Mooted beliefs (Impact pinned to 0 by a decision) drop out of ranking. */\n moot: boolean;\n /** Derived Risk (already recomputed on write — read, never recomputed). */\n risk: number;\n /** Derived Confidence (already recomputed on write). */\n confidence: number;\n /** How many concluded (Validated/Invalidated) readings back this belief. */\n concludedReadings: number;\n}\n\n/** An experiment, reduced to what stage + feasibility resolution needs. */\nexport interface NextMoveExperimentInput {\n /** ExperimentStatus — \"Running\" | \"Closed\". */\n status: string;\n feasibility: Feasibility | null;\n /** The assumption ids this experiment's bar lines name. */\n assumptionIds: string[];\n}\n\n/** A decision, reduced to which beliefs it rests on or resolves. */\nexport interface NextMoveDecisionInput {\n /** DecisionStatus; only standing (Active/Provisional) decisions count. */\n status: string;\n /** The assumption ids this decision rests on (`based on`) or resolves. */\n assumptionIds: string[];\n}\n\nexport interface NextMoveInput {\n assumptions: NextMoveAssumptionInput[];\n experiments: NextMoveExperimentInput[];\n decisions: NextMoveDecisionInput[];\n}\n\n/**\n * Feasibility → the score multiplier. Cheaper (more feasible) tests rank\n * higher, so the cheapest honest test of the riskiest belief is on top\n * (`method.md`). A belief with no planned test yet has unknown feasibility →\n * the neutral middle, so Risk decides its place honestly.\n */\nconst FEASIBILITY_WEIGHT: Record<Feasibility, number> = {\n High: 1,\n Medium: 0.6,\n Low: 0.3,\n};\nconst NEUTRAL_FEASIBILITY_WEIGHT = FEASIBILITY_WEIGHT.Medium;\n\nfunction isStanding(decisionStatus: string): boolean {\n return decisionStatus === \"Active\" || decisionStatus === \"Provisional\";\n}\n\n/** Pick the cheapest (highest-weight) feasibility among a belief's tests. */\nfunction bestFeasibility(\n feasibilities: (Feasibility | null)[],\n): Feasibility | null {\n let best: Feasibility | null = null;\n let bestWeight = -1;\n for (const f of feasibilities) {\n if (!f) continue;\n const w = FEASIBILITY_WEIGHT[f];\n if (w > bestWeight) {\n bestWeight = w;\n best = f;\n }\n }\n return best;\n}\n\n/**\n * The act a belief's stage demands (before the kill-lane override).\n *\n * Deliberately NOT the `stage.ts` structural classifier: that answers \"how far\n * along the Framed→Planned→Tested→Known artifacts are\" (framing completeness,\n * bar-line settlement); this answers \"what should the founder do next\" from the\n * evidence state (is it weighted, has evidence concluded, does a test run, does\n * a decision rest on it). Same belief can be structurally `tested` yet owe a\n * `decide` here — the journey rail (stage) and next-move card (act) are two\n * readings of one belief, so they share the test-plan *meter* (`beliefTestMeters`)\n * but not the classifier.\n */\nfunction stageMove(\n a: NextMoveAssumptionInput,\n hasRunningTest: boolean,\n hasAnyTest: boolean,\n hasStandingDecision: boolean,\n): MoveKind | null {\n if (a.impact === null) return \"score-impact\"; // Framed → weight it\n if (a.concludedReadings === 0) {\n // No evidence yet: if a test is running, wait for readings; else plan one.\n return hasRunningTest ? \"record-reading\" : \"design-experiment\";\n }\n // Evidence has landed. If nothing rests on it yet, it's time to decide;\n // once a standing decision does, the belief is resolved and drops out.\n if (!hasStandingDecision) return \"decide\";\n return null;\n}\n\n/** Plain-language \"why this\" — explains the move from the inputs. */\nfunction reasonFor(move: MoveKind, a: NextMoveAssumptionInput): string {\n switch (move) {\n case \"retest\":\n return `Confidence has fallen to ${Math.round(a.confidence)} — the evidence is turning against this belief. Kill it or test it again.`;\n case \"score-impact\":\n return \"This belief isn't weighted yet — score its impact so its risk can rank against the rest.\";\n case \"design-experiment\":\n return `Your riskiest untested belief. Design the cheapest honest test that could move it.`;\n case \"record-reading\":\n return \"A test is running against this belief — evidence is still landing.\";\n case \"decide\":\n return \"The evidence is in and nothing rests on it yet — time to make the call.\";\n }\n}\n\n/**\n * Rank every unresolved belief into its next move (Model A). Returns a plain\n * sorted list, most-pressing first: the kill lane on top (by Risk), then the\n * rest by Feasibility × Risk, tie-broken by the most-negative Confidence\n * (`derived_views.test_next_surface`), then by id for a stable order. The front\n * door takes the head as the hero and the tail as \"On deck\" / manual override.\n */\nexport function rankNextMoves(input: NextMoveInput): NextMove[] {\n const { assumptions, experiments, decisions } = input;\n\n // Which experiments (and their feasibilities/statuses) name each belief.\n const testsByAssumption = new Map<\n string,\n { running: boolean; any: boolean; feasibilities: (Feasibility | null)[] }\n >();\n for (const exp of experiments) {\n const running = exp.status === \"Running\";\n for (const id of exp.assumptionIds) {\n const entry = testsByAssumption.get(id) ?? {\n running: false,\n any: false,\n feasibilities: [],\n };\n entry.any = true;\n entry.running = entry.running || running;\n entry.feasibilities.push(exp.feasibility);\n testsByAssumption.set(id, entry);\n }\n }\n\n // Which beliefs a standing decision rests on or resolves.\n const decidedAssumptions = new Set<string>();\n for (const d of decisions) {\n if (!isStanding(d.status)) continue;\n for (const id of d.assumptionIds) decidedAssumptions.add(id);\n }\n\n const moves: NextMove[] = [];\n for (const a of assumptions) {\n if (a.moot || a.status === \"Invalidated\") continue;\n\n const killLane = a.confidence <= KILL_LANE_THRESHOLD;\n const tests = testsByAssumption.get(a.id);\n const feasibility = bestFeasibility(tests?.feasibilities ?? []);\n\n const move: MoveKind | null = killLane\n ? \"retest\"\n : stageMove(\n a,\n tests?.running ?? false,\n tests?.any ?? false,\n decidedAssumptions.has(a.id),\n );\n if (move === null) continue; // resolved — no move\n\n const weight = feasibility\n ? FEASIBILITY_WEIGHT[feasibility]\n : NEUTRAL_FEASIBILITY_WEIGHT;\n // Kill-lane rows carry their raw Risk so they order among themselves by\n // Risk; the killLane flag (not the score) floats them above everyone else.\n const score = round2(killLane ? a.risk : weight * a.risk);\n\n moves.push({\n move,\n assumptionId: a.id,\n title: a.title,\n score,\n reason: reasonFor(move, a),\n risk: a.risk,\n confidence: a.confidence,\n feasibility,\n killLane,\n });\n }\n\n moves.sort((x, y) => {\n if (x.killLane !== y.killLane) return x.killLane ? -1 : 1;\n if (y.score !== x.score) return y.score - x.score;\n if (x.confidence !== y.confidence) return x.confidence - y.confidence;\n return x.assumptionId < y.assumptionId ? -1 : 1;\n });\n return moves;\n}\n","/**\n * Per-belief stage — where one belief sits on the loop's four-stage spine\n * (Framed → Planned → Tested → Known) and its four meters (OPS-1329).\n *\n * This is the *single-belief* analogue of the pipeline's cross-belief roll-up:\n * the pipeline row-builder used to derive framing / test-plan / test-progress\n * inline, and the front-door move ladder (OPS-1304) re-walked the same stages a\n * second way. The shared classification lives here now, so the pipeline board,\n * the front door, and the per-belief journey rail all read one rule rather than\n * three that can drift.\n *\n * Pure and computed fresh on read (like `portfolio.ts` / `next-move.ts`): it\n * only reads numbers already kept current, so it stays out of the OPS-1251\n * on-write recompute. The record → meter mapping (bar lines, presence fields)\n * lives in the dashboard, as elsewhere; this module takes the reduced inputs.\n */\nimport { KILL_LANE_THRESHOLD } from \"./next-move.js\";\n\n/** The four loop stages a belief travels, in order (OPS-1293). */\nexport type StageKey = \"framed\" | \"planned\" | \"tested\" | \"known\";\n\n/** Sign of a belief's Confidence — the Known meter's direction. */\nexport type ConfSign = \"pos\" | \"neg\" | \"zero\";\n\n/** One belief's test state, aggregated across every experiment aimed at it. */\nexport interface TestMeter {\n /** A bar line (or the convenience projection) names this belief. */\n planned: boolean;\n /** Pre-registered bars that have a verdict. */\n settled: number;\n /** Pre-registered bars in total. */\n total: number;\n}\n\n/** A test's bar lines, reduced to what the meter needs (register-agnostic). */\nexport interface StageExperimentInput {\n /** Each bar line naming a belief, and whether it has settled (has a verdict). */\n bars: { assumptionId: string; settled: boolean }[];\n /** Beliefs this test plans via the convenience projection (bars may be unexpanded). */\n plannedAssumptionIds: string[];\n}\n\n/** The reduced inputs one belief's stage is derived from. */\nexport interface BeliefStageInput {\n /** Framing completeness, 0–100 (`assumptionCompleteness`). */\n framed: number;\n /** Live derived Confidence (signed −100…100). */\n confidence: number;\n /** This belief's aggregated test state. */\n test: TestMeter;\n}\n\n/** One belief's position on the spine plus its four meters. */\nexport interface BeliefStage {\n /** Where the belief sits: framed → planned → tested → known. */\n stage: StageKey;\n /** Meter 1 — framing completeness, 0–100. */\n framed: number;\n /** Meter 2 — a test has been designed against this belief. */\n planned: boolean;\n /** Meter 3 — pre-registered bars settled / total. */\n tested: { settled: number; total: number };\n /** Meter 4 — signed Known: the belief's Confidence. */\n confidence: number;\n /** Sign bucket for the Known gauge direction. */\n confSign: ConfSign;\n /** Confidence ≤ −50 — the kill/re-test overlay (the same lane as the front door). */\n killZone: boolean;\n}\n\n/** An empty test state — a belief no experiment has named yet. */\nexport function emptyTestMeter(): TestMeter {\n return { planned: false, settled: 0, total: 0 };\n}\n\n/**\n * For every belief, the state of the tests aimed at it — whether one is designed\n * and how many of its pre-registered bars have settled, aggregated across all\n * experiments. Factored out of the pipeline row-builder so the board and a\n * single belief's rail agree by construction.\n */\nexport function beliefTestMeters(\n experiments: StageExperimentInput[],\n): Map<string, TestMeter> {\n const byAssumption = new Map<string, TestMeter>();\n const ensure = (id: string): TestMeter => {\n let s = byAssumption.get(id);\n if (!s) {\n s = emptyTestMeter();\n byAssumption.set(id, s);\n }\n return s;\n };\n for (const exp of experiments) {\n for (const b of exp.bars) {\n if (!b.assumptionId) continue;\n const s = ensure(b.assumptionId);\n s.planned = true;\n s.total += 1;\n if (b.settled) s.settled += 1;\n }\n // A designed test may name a belief via the convenience projection even when\n // its bar lines aren't expanded — still counts as Planned.\n for (const id of exp.plannedAssumptionIds) ensure(id).planned = true;\n }\n return byAssumption;\n}\n\n/**\n * Classify a belief on the spine from its meters. The kill-zone overlay is\n * *not* a stage — a belief whose evidence has turned is still structurally\n * wherever its framing/tests put it (a re-test moves it backward via the Known\n * meter, OPS-1300), so this stays pure status.\n */\nexport function classifyStage(framed: number, test: TestMeter): StageKey {\n if (framed < 100) return \"framed\";\n if (!test.planned) return \"planned\";\n if (test.total === 0 || test.settled < test.total) return \"tested\";\n return \"known\";\n}\n\n/** One belief's stage + four meters — the rail's data. */\nexport function deriveBeliefStage(input: BeliefStageInput): BeliefStage {\n const { framed, confidence, test } = input;\n return {\n stage: classifyStage(framed, test),\n framed,\n planned: test.planned,\n tested: { settled: test.settled, total: test.total },\n confidence,\n confSign: confidence > 0 ? \"pos\" : confidence < 0 ? \"neg\" : \"zero\",\n killZone: confidence <= KILL_LANE_THRESHOLD,\n };\n}\n","/**\n * The per-belief journey event log (OPS-1329) — the belief's life ordered into\n * dated events: bet → score → experiment → readings → confidence-cross → now.\n * It is the *story* half of the journey drill-in (the *rail* half is `stage.ts`).\n *\n * No new maths: Confidence at each point is read off `confidenceTrajectory`\n * (the very numbers the understanding layer already shows), and the current\n * number off `confidence()`. Nothing is invented — an event whose underlying\n * datum is absent (no impact score, no experiment, no concluded reading, no\n * kill-zone cross) is simply omitted, and no date is ever faked (an event with\n * no real date carries `date: null` and takes its place by structural order).\n *\n * Pure and label-free: the dashboard journey view-model adds the copy. Computed\n * fresh on read, out of the OPS-1251 on-write recompute.\n */\nimport type { Result } from \"../types.js\";\nimport type { AttributionReadingInput } from \"./attribution.js\";\nimport { confidence } from \"./confidence.js\";\nimport { KILL_LANE_THRESHOLD } from \"./next-move.js\";\nimport { isConcluded } from \"./strength.js\";\nimport { confidenceTrajectory } from \"./trajectory.js\";\n\n/** The kinds of event a belief's life produces, in structural order. */\nexport type JourneyEventKind =\n | \"bet\" // the belief was written\n | \"score\" // its impact was scored\n | \"experiment\" // a test was designed against it\n | \"reading\" // evidence landed\n | \"confidence-cross\" // Confidence fell into the kill zone\n | \"now\"; // the belief's state today\n\n/** The structural order the story walks — the tie-break for undated events. */\nconst KIND_ORDER: Record<JourneyEventKind, number> = {\n bet: 0,\n score: 1,\n experiment: 2,\n reading: 3,\n \"confidence-cross\": 4,\n now: 5,\n};\n\n/** One event in a belief's life. Label-free — the view-model adds the copy. */\nexport interface JourneyEvent {\n kind: JourneyEventKind;\n /** ISO date, or null when no real date exists (never faked). */\n date: string | null;\n /** Confidence known at this event (reading / cross / now); null otherwise. */\n confidence: number | null;\n /** The reading's verdict, for `reading` events; null otherwise. */\n result: Result | null;\n /** The source record id (reading id, experiment id) when the event has one. */\n refId: string | null;\n}\n\n/** The belief itself, reduced to what the log needs. */\nexport interface JourneyBeliefInput {\n /** When the bet was written — the `bet` event's date. */\n createdAt: string | null;\n /** Impact has been scored (a non-null seed) — emits the `score` event. */\n impactScored: boolean;\n}\n\n/** A test aimed at this belief, reduced to what the log needs. */\nexport interface JourneyExperimentInput {\n id: string;\n /** When the test was designed; null when unknown (event still emitted, undated). */\n date: string | null;\n}\n\nexport interface AssembleJourneyInput {\n belief: JourneyBeliefInput;\n /** The belief's own readings (already filtered to this assumption). */\n readings: AttributionReadingInput[];\n /** The experiments testing this belief. */\n experiments: JourneyExperimentInput[];\n /** \"Now\" as an ISO date — passed in so the log stays pure. */\n now: string;\n}\n\n/**\n * Assemble one belief's chronological event log. Events sort by date, undated\n * events anchored to the bet's date and ordered structurally; `now` is always\n * last.\n */\nexport function assembleJourney(input: AssembleJourneyInput): JourneyEvent[] {\n const { belief, readings, experiments, now } = input;\n const events: JourneyEvent[] = [];\n\n if (belief.createdAt) {\n events.push(event(\"bet\", belief.createdAt));\n }\n if (belief.impactScored) {\n // No scored-on date is stored, so the score rides structurally after the\n // bet rather than carrying an invented date.\n events.push(event(\"score\", null));\n }\n for (const e of experiments) {\n events.push(event(\"experiment\", e.date, { refId: e.id }));\n }\n\n const trajectory = confidenceTrajectory(readings);\n const confByDate = new Map(trajectory.map((p) => [p.date, p.confidence]));\n for (const r of readings) {\n const dated = r.date ?? null;\n // Confidence is known only for a concluded reading that lands on a\n // trajectory date (undated / inconclusive readings carry no number).\n const conf =\n dated && isConcluded(r.result) ? (confByDate.get(dated) ?? null) : null;\n events.push(\n event(\"reading\", dated, { confidence: conf, result: r.result, refId: r.id }),\n );\n }\n\n // The first point at which the evidence dragged Confidence into the kill zone.\n const cross = trajectory.find((p) => p.confidence <= KILL_LANE_THRESHOLD);\n if (cross) {\n events.push(event(\"confidence-cross\", cross.date, { confidence: cross.confidence }));\n }\n\n // Undated events anchor to the bet so they sit at the start in structural\n // order; `now` is appended after sorting so it is unconditionally last.\n const anchor = belief.createdAt ?? \"\";\n events.sort((a, b) => {\n const ka = a.date ?? anchor;\n const kb = b.date ?? anchor;\n if (ka !== kb) return ka < kb ? -1 : 1;\n return KIND_ORDER[a.kind] - KIND_ORDER[b.kind];\n });\n events.push(event(\"now\", now, { confidence: confidence(readings) }));\n\n return events;\n}\n\nfunction event(\n kind: JourneyEventKind,\n date: string | null,\n extra: Partial<Pick<JourneyEvent, \"confidence\" | \"result\" | \"refId\">> = {},\n): JourneyEvent {\n return {\n kind,\n date,\n confidence: extra.confidence ?? null,\n result: extra.result ?? null,\n refId: extra.refId ?? null,\n };\n}\n"],"mappings":"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","names":["confidence","confidence"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provider.ts","../src/relations.ts"],"sourcesContent":["/**\n * The `DataProvider` — the single integration seam between the dashboard/API\n * and a concrete backend. Hand-rolled (patterns borrowed from\n * Refine/React-Admin's dataProvider, not forked). One adapter per backend;\n * Firestore is the first. Writing a new adapter is a bounded task: implement\n * these five methods.\n */\nimport type { AnyRecord, Collection, RecordRef, Relation } from \"./types.js\";\n\nexport interface DataProvider {\n /** Every row of a register — never a filtered view (a filter drops rows). */\n list(register: Collection): Promise<AnyRecord[]>;\n get(register: Collection, id: string): Promise<AnyRecord>;\n create(register: Collection, data: Partial<AnyRecord>): Promise<AnyRecord>;\n /**\n * Version-guarded update: `version` is the value the caller loaded. If the\n * stored version has moved on, the write is rejected with\n * {@link StaleVersionError} (surfaced to the user as a 409).\n */\n update(\n register: Collection,\n id: string,\n patch: Partial<AnyRecord>,\n version: number,\n ): Promise<AnyRecord>;\n /** Set a relation on both ends in one logical write. */\n link(relation: Relation, from: RecordRef, to: RecordRef): Promise<void>;\n}\n\n/**\n * Thrown when an update carries a version older than what is stored — a\n * concurrent edit landed first. The API turns this into a 409 with\n * plain-language copy; it is never surfaced as version jargon.\n */\nexport class StaleVersionError extends Error {\n override readonly name = \"StaleVersionError\";\n constructor(\n readonly register: Collection,\n readonly id: string,\n readonly expected: number,\n readonly actual: number,\n ) {\n super(\n `Stale write to ${register}/${id}: caller had version ${expected}, ` +\n `stored is ${actual}.`,\n );\n }\n}\n\nexport function isStaleVersionError(e: unknown): e is StaleVersionError {\n return e instanceof StaleVersionError;\n}\n\n/** Thrown when a get/update targets a record that does not exist (→ 404). */\nexport class NotFoundError extends Error {\n override readonly name = \"NotFoundError\";\n constructor(\n readonly register: Collection,\n readonly id: string,\n ) {\n super(`No ${register} record with id ${id}.`);\n }\n}\n\nexport function isNotFoundError(e: unknown): e is NotFoundError {\n return e instanceof NotFoundError;\n}\n","/**\n * Relation config — the single table describing, for each linkable relation,\n * which field on which register holds each end. `link()` sets both ends from\n * this table, so relations stay consistent no matter which side initiated.\n *\n * A `null` `to` end means the inverse is a *derived view*, not a stored field\n * (e.g. \"experiments testing me\" is computed over bar-lines; a decision's\n * `Based on` never touches the assumption). Those relations write one end.\n */\nimport type { Collection, Relation } from \"./types.js\";\n\nexport interface RelationEnd {\n register: Collection;\n field: string;\n cardinality: \"one\" | \"many\";\n}\n\nexport interface RelationSpec {\n /** The end named by `from` in `link(relation, from, to)`. */\n from: RelationEnd;\n /** The end named by `to`; null when the inverse is derived, not stored. */\n to: RelationEnd | null;\n /**\n * The register the `to` end points at — always present, even when `to` is\n * null (the inverse is a derived view). The dashboard reads this to know\n * which register to pick a link target from; the API validates the target\n * register against it.\n */\n targetRegister: Collection;\n}\n\nexport const RELATIONS: Record<Relation, RelationSpec> = {\n \"assumption-reading\": {\n from: { register: \"assumptions\", field: \"readingIds\", cardinality: \"many\" },\n to: { register: \"readings\", field: \"assumptionId\", cardinality: \"one\" },\n targetRegister: \"readings\",\n },\n \"assumption-depends-on\": {\n from: {\n register: \"assumptions\",\n field: \"dependsOnIds\",\n cardinality: \"many\",\n },\n to: { register: \"assumptions\", field: \"enablesIds\", cardinality: \"many\" },\n targetRegister: \"assumptions\",\n },\n \"assumption-contradicts\": {\n from: {\n register: \"assumptions\",\n field: \"contradictsIds\",\n cardinality: \"many\",\n },\n to: {\n register: \"assumptions\",\n field: \"contradictsIds\",\n cardinality: \"many\",\n },\n targetRegister: \"assumptions\",\n },\n \"reading-experiment\": {\n from: { register: \"readings\", field: \"experimentId\", cardinality: \"one\" },\n to: null, // experiment→readings is derived\n targetRegister: \"experiments\",\n },\n \"decision-based-on\": {\n from: { register: \"decisions\", field: \"basedOnIds\", cardinality: \"many\" },\n to: null, // never touches the assumption\n targetRegister: \"assumptions\",\n },\n \"decision-resolves\": {\n from: { register: \"decisions\", field: \"resolvesIds\", cardinality: \"many\" },\n to: null, // mooting the assumption is a gated business action, not a link\n targetRegister: \"assumptions\",\n },\n};\n"],"mappings":";AAkCO,IAAM,oBAAN,cAAgC,MAAM;AAAA,EAE3C,YACW,UACA,IACA,UACA,QACT;AACA;AAAA,MACE,kBAAkB,QAAQ,IAAI,EAAE,wBAAwB,QAAQ,eACjD,MAAM;AAAA,IACvB;AARS;AACA;AACA;AACA;AAAA,EAMX;AAAA,EATW;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EALO,OAAO;AAY3B;AAEO,SAAS,oBAAoB,GAAoC;AACtE,SAAO,aAAa;AACtB;AAGO,IAAM,gBAAN,cAA4B,MAAM;AAAA,EAEvC,YACW,UACA,IACT;AACA,UAAM,MAAM,QAAQ,mBAAmB,EAAE,GAAG;AAHnC;AACA;AAAA,EAGX;AAAA,EAJW;AAAA,EACA;AAAA,EAHO,OAAO;AAO3B;AAEO,SAAS,gBAAgB,GAAgC;AAC9D,SAAO,aAAa;AACtB;;;ACnCO,IAAM,YAA4C;AAAA,EACvD,sBAAsB;AAAA,IACpB,MAAM,EAAE,UAAU,eAAe,OAAO,cAAc,aAAa,OAAO;AAAA,IAC1E,IAAI,EAAE,UAAU,YAAY,OAAO,gBAAgB,aAAa,MAAM;AAAA,IACtE,gBAAgB;AAAA,EAClB;AAAA,EACA,yBAAyB;AAAA,IACvB,MAAM;AAAA,MACJ,UAAU;AAAA,MACV,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,IAAI,EAAE,UAAU,eAAe,OAAO,cAAc,aAAa,OAAO;AAAA,IACxE,gBAAgB;AAAA,EAClB;AAAA,EACA,0BAA0B;AAAA,IACxB,MAAM;AAAA,MACJ,UAAU;AAAA,MACV,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,IAAI;AAAA,MACF,UAAU;AAAA,MACV,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,gBAAgB;AAAA,EAClB;AAAA,EACA,sBAAsB;AAAA,IACpB,MAAM,EAAE,UAAU,YAAY,OAAO,gBAAgB,aAAa,MAAM;AAAA,IACxE,IAAI;AAAA;AAAA,IACJ,gBAAgB;AAAA,EAClB;AAAA,EACA,qBAAqB;AAAA,IACnB,MAAM,EAAE,UAAU,aAAa,OAAO,cAAc,aAAa,OAAO;AAAA,IACxE,IAAI;AAAA;AAAA,IACJ,gBAAgB;AAAA,EAClB;AAAA,EACA,qBAAqB;AAAA,IACnB,MAAM,EAAE,UAAU,aAAa,OAAO,eAAe,aAAa,OAAO;AAAA,IACzE,IAAI;AAAA;AAAA,IACJ,gBAAgB;AAAA,EAClB;AACF;","names":[]}
|