@validation-os/core 0.15.5 → 0.16.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.
@@ -7,8 +7,11 @@ var derivation_exports = {};
7
7
  __export(derivation_exports, {
8
8
  COMMITMENT_FOUND: () => COMMITMENT_FOUND,
9
9
  COMPLETENESS_SLOTS: () => COMPLETENESS_SLOTS,
10
+ DEFAULT_QUESTION_TYPE: () => DEFAULT_QUESTION_TYPE,
11
+ INFERABLE_QUESTION_TYPES: () => INFERABLE_QUESTION_TYPES,
10
12
  KILL_LANE_THRESHOLD: () => KILL_LANE_THRESHOLD,
11
13
  MAX_STRENGTH: () => MAX_STRENGTH,
14
+ RISK_THRESHOLD_BY_STAGE: () => RISK_THRESHOLD_BY_STAGE,
12
15
  RUNG_ANCHOR: () => RUNG_ANCHOR,
13
16
  W0: () => W0,
14
17
  W0_BY_RUNG: () => W0_BY_RUNG,
@@ -17,6 +20,7 @@ __export(derivation_exports, {
17
20
  assumptionCompleteness: () => assumptionCompleteness,
18
21
  beliefRisk: () => beliefRisk,
19
22
  beliefTestMeters: () => beliefTestMeters,
23
+ ceilingAnchor: () => ceilingAnchor,
20
24
  classifyStage: () => classifyStage,
21
25
  commitmentFactor: () => commitmentFactor,
22
26
  completenessSlotPresence: () => completenessSlotPresence,
@@ -28,12 +32,18 @@ __export(derivation_exports, {
28
32
  emptyTestMeter: () => emptyTestMeter,
29
33
  experimentConfidence: () => experimentConfidence,
30
34
  experimentProgress: () => experimentProgress,
35
+ hasClearedThreshold: () => hasClearedThreshold,
36
+ inferQuestionType: () => inferQuestionType,
31
37
  isConcluded: () => isConcluded,
38
+ isNonEvidence: () => isNonEvidence,
39
+ migrateRegister: () => migrateRegister,
32
40
  missingCompletenessSlots: () => missingCompletenessSlots,
41
+ needsReview: () => needsReview,
33
42
  portfolioProgress: () => portfolioProgress,
34
43
  rankNextMoves: () => rankNextMoves,
35
44
  readingStrength: () => readingStrength,
36
45
  risk: () => risk,
46
+ riskThresholdForStage: () => riskThresholdForStage,
37
47
  round2: () => round2,
38
48
  scoreAndDedupe: () => scoreAndDedupe,
39
49
  sign: () => sign,
@@ -47,14 +57,95 @@ function round2(n) {
47
57
  }
48
58
 
49
59
  // src/derivation/rung.ts
60
+ var Z = { Low: 0, Typical: 0, High: 0 };
61
+ function band(low, typical, high) {
62
+ return { Low: low, Typical: typical, High: high };
63
+ }
50
64
  var RUNG_ANCHOR = {
51
- Talk: { Low: 3, Typical: 6, High: 10 },
52
- "Desk research": { Low: 15, Typical: 15, High: 15 },
53
- "Signed up": { Low: 30, Typical: 50, High: 70 },
54
- "Observed usage": { Low: 30, Typical: 50, High: 70 },
55
- "Signed intent": { Low: 30, Typical: 50, High: 70 },
56
- "Paying users": { Low: 30, Typical: 50, High: 70 }
65
+ Existence: {
66
+ Talk: band(10, 20, 30),
67
+ "Desk research": band(15, 15, 15),
68
+ "Signed up": Z,
69
+ "Observed usage": band(20, 35, 50),
70
+ "Signed intent": Z,
71
+ "Paying users": Z
72
+ },
73
+ Prevalence: {
74
+ Talk: Z,
75
+ "Desk research": band(15, 15, 15),
76
+ "Signed up": Z,
77
+ "Observed usage": band(25, 40, 50),
78
+ "Signed intent": Z,
79
+ "Paying users": Z
80
+ },
81
+ CausalEffect: {
82
+ Talk: Z,
83
+ "Desk research": Z,
84
+ "Signed up": Z,
85
+ "Observed usage": band(30, 50, 70),
86
+ "Signed intent": band(30, 50, 70),
87
+ "Paying users": band(50, 70, 90)
88
+ },
89
+ WillingnessToPay: {
90
+ Talk: Z,
91
+ "Desk research": Z,
92
+ "Signed up": band(30, 50, 70),
93
+ "Observed usage": Z,
94
+ "Signed intent": band(50, 70, 85),
95
+ "Paying users": band(75, 88, 99)
96
+ },
97
+ ValueUtility: {
98
+ Talk: band(10, 20, 30),
99
+ "Desk research": Z,
100
+ "Signed up": Z,
101
+ "Observed usage": band(30, 50, 70),
102
+ "Signed intent": Z,
103
+ "Paying users": Z
104
+ },
105
+ Regulatory: {
106
+ Talk: Z,
107
+ "Desk research": band(30, 50, 70),
108
+ "Signed up": Z,
109
+ "Observed usage": Z,
110
+ "Signed intent": Z,
111
+ "Paying users": Z
112
+ },
113
+ Feasibility: {
114
+ Talk: Z,
115
+ "Desk research": band(15, 15, 15),
116
+ "Signed up": Z,
117
+ "Observed usage": band(30, 50, 70),
118
+ "Signed intent": Z,
119
+ "Paying users": Z
120
+ }
121
+ };
122
+ function isNonEvidence(questionType, rung) {
123
+ const anchors = RUNG_ANCHOR[questionType]?.[rung];
124
+ if (!anchors) return false;
125
+ return anchors.Low === 0 && anchors.Typical === 0 && anchors.High === 0;
126
+ }
127
+ function ceilingAnchor(questionType, rung) {
128
+ return RUNG_ANCHOR[questionType]?.[rung]?.High ?? 0;
129
+ }
130
+ var RISK_THRESHOLD_BY_STAGE = {
131
+ Discovery: 30,
132
+ // two-way door — act on weak evidence
133
+ Validation: 15,
134
+ // becoming one-way — need more before committing
135
+ Scale: 10,
136
+ // one-way door — strong evidence before scaling
137
+ Maturity: 5
138
+ // defensive, often regulatory — strongest evidence
57
139
  };
140
+ function riskThresholdForStage(stage) {
141
+ if (stage && stage in RISK_THRESHOLD_BY_STAGE) {
142
+ return RISK_THRESHOLD_BY_STAGE[stage];
143
+ }
144
+ return RISK_THRESHOLD_BY_STAGE.Maturity;
145
+ }
146
+ function hasClearedThreshold(risk2, stage) {
147
+ return risk2 <= riskThresholdForStage(stage);
148
+ }
58
149
 
59
150
  // src/derivation/completeness.ts
60
151
  var COMPLETENESS_SLOTS = [
@@ -62,7 +153,8 @@ var COMPLETENESS_SLOTS = [
62
153
  "Lens",
63
154
  "Impact",
64
155
  "Scoring justification",
65
- "Dependencies traced"
156
+ "Dependencies traced",
157
+ "Question Type"
66
158
  ];
67
159
  function hasText(value) {
68
160
  return typeof value === "string" && value.trim().length > 0;
@@ -79,7 +171,8 @@ function completenessSlotPresence(record) {
79
171
  Lens: hasText(record.Lens),
80
172
  Impact: hasNumber(record.Impact),
81
173
  "Scoring justification": hasText(record["Scoring justification"]),
82
- "Dependencies traced": hasAny(record.dependsOnIds) || hasAny(record.enablesIds)
174
+ "Dependencies traced": hasAny(record.dependsOnIds) || hasAny(record.enablesIds),
175
+ "Question Type": hasText(record["Question Type"])
83
176
  };
84
177
  }
85
178
  function missingCompletenessSlots(record) {
@@ -107,8 +200,225 @@ function isConcluded(result) {
107
200
  function readingStrength(input) {
108
201
  const s = sign(input.result);
109
202
  if (s === 0) return 0;
110
- const band = input.magnitudeBand ?? "Typical";
111
- return (RUNG_ANCHOR[input.rung]?.[band] ?? 0) * s;
203
+ const band2 = input.magnitudeBand ?? "Typical";
204
+ return (RUNG_ANCHOR[input.questionType]?.[input.rung]?.[band2] ?? 0) * s;
205
+ }
206
+
207
+ // src/derivation/question-type.ts
208
+ var DEFAULT_QUESTION_TYPE = "Existence";
209
+ var RULES = [
210
+ {
211
+ type: "Regulatory",
212
+ patterns: [
213
+ /\bregulat(?:or|ion)\s+(?:prohibits|rul(?:es|ed)\s+against)\b/,
214
+ /\bregulator\b.*\bagainst\b/,
215
+ /\bcompliance\s+audit\s+fails?\b/,
216
+ /\bprohibits?\b.*\bscoring\b/
217
+ ]
218
+ },
219
+ {
220
+ type: "WillingnessToPay",
221
+ patterns: [
222
+ /\b(?:don't|do not|won't|will not)\s+pay\b/,
223
+ /\bfake[-\s]?door\s+(?:signup\s+)?rate\s+below\b/,
224
+ /\bfake[-\s]?door\b.*\brate\b/,
225
+ /\bdon't\s+sign\s+up\b/,
226
+ /\bno\s+(?:buyer|user)s?\s+(?:sign|pay|commit)\b/,
227
+ /\bsigns?\s+(?:an\s+)?loi\b/,
228
+ /\bput(?:s|ting)?\s+down\s+(?:a\s+)?deposit\b/,
229
+ /\bdon't\s+commit\b/,
230
+ /\boffered\s+users?\s+pay\b/,
231
+ /\bpay(?:ing)?\s+(?:rate|share)?\s*(?:is\s+)?below\b/,
232
+ /\bfewer\s+than\s+\d+\s+of\s+\d+\s+offered\s+users?\s+pay\b/
233
+ ]
234
+ },
235
+ {
236
+ type: "ValueUtility",
237
+ patterns: [
238
+ /\bstop\s+using\s+(?:it|the)\b/,
239
+ /\bdrop[-\s]?off\s+exceeds\b/,
240
+ /\bsustained\s+usage\s+falls?\s+below\b/,
241
+ /\bretention\s+(?:drops?|falls?)\s+below\b/,
242
+ /\bweek-\d+\s+retention\s+drops?\s+below\b/,
243
+ /\bdisuse\b/
244
+ ]
245
+ },
246
+ {
247
+ type: "CausalEffect",
248
+ patterns: [
249
+ /\btreatment\s+group(?:'s)?\s+doesn'?t\s+differ\s+from\s+control\b/,
250
+ /\bno\s+causal\s+effect\s+of\b/,
251
+ /\bvariant\s+doesn'?t\s+outperform\s+control\b/,
252
+ /\bdiffer\s+from\s+control\b/,
253
+ /\boutperform\s+control\b/,
254
+ /\btreatment\s+vs\.?\s+control\b/
255
+ ]
256
+ },
257
+ {
258
+ type: "Prevalence",
259
+ patterns: [
260
+ /\brate\s+is\s+below\b/,
261
+ /\bfewer\s+than\s+\d+\s+of\s+\d+\s+(?:interviewed|surveyed|of)\b/,
262
+ /\bfewer\s+than\s+\d+%\s+of\s+\w+\s+hit\b/,
263
+ /\bthe\s+(?:rate|share|proportion)\s+is\s+below\b/,
264
+ /\b(?:in\s+)?surveyed\s+teams\b/,
265
+ /\bshare\s+(?:of\s+\w+\s+)?(?:is\s+)?below\b/,
266
+ /\bproportion\s+is\s+below\b/
267
+ ]
268
+ },
269
+ {
270
+ type: "Feasibility",
271
+ patterns: [
272
+ /\bcan'?t\s+complete\s+the\s+flow\b/,
273
+ /\bsystem\s+can'?t\s+(?:do|ingest|handle|build)\b/,
274
+ /\bcan'?t\s+be\s+built\b/,
275
+ /\bcan'?t\s+ingest\b/,
276
+ /\bprototype\s+can'?t\s+be\s+built\b/,
277
+ /\bcan'?t\s+\w+\s+(?:unaided|without\s+(?:help|regressions))\b/
278
+ ]
279
+ },
280
+ {
281
+ type: "Existence",
282
+ patterns: [
283
+ /\bno\s+one\s+(?:we\s+interview\s+)?(?:describes|reports|mentions)\b/,
284
+ /\bno\s+interviewee(?:s)?\s+(?:describe|report|mention)s?\b/,
285
+ /\bcan'?t\s+describe\s+the\s+mechanism\b/,
286
+ /\bno\s+one\s+reports\s+caring\b/,
287
+ /\bno\s+one\s+describes\b/
288
+ ]
289
+ }
290
+ ];
291
+ function normalize(s) {
292
+ return s.toLowerCase().replace(/\s+/g, " ").trim();
293
+ }
294
+ function inferQuestionType(description, wrongIfBar) {
295
+ const bar = normalize(wrongIfBar ?? "");
296
+ const desc = normalize(description ?? "");
297
+ const hay = bar.length > 0 ? `${bar} ${desc}` : desc;
298
+ for (const rule of RULES) {
299
+ for (const re of rule.patterns) {
300
+ if (re.test(hay)) return rule.type;
301
+ }
302
+ }
303
+ return DEFAULT_QUESTION_TYPE;
304
+ }
305
+ var INFERABLE_QUESTION_TYPES = [
306
+ "Existence",
307
+ "Prevalence",
308
+ "CausalEffect",
309
+ "WillingnessToPay",
310
+ "ValueUtility",
311
+ "Regulatory",
312
+ "Feasibility"
313
+ ];
314
+ function needsReview(description, wrongIfBar, inferred) {
315
+ if (wrongIfBar == null || wrongIfBar.trim() === "") return true;
316
+ const bar = normalize(wrongIfBar);
317
+ for (const rule of RULES) {
318
+ if (rule.type === inferred) {
319
+ for (const re of rule.patterns) {
320
+ if (re.test(bar)) return false;
321
+ }
322
+ }
323
+ }
324
+ return true;
325
+ }
326
+
327
+ // src/derivation/migrate.ts
328
+ function migrateRegister(assumptions, readings) {
329
+ const questionTypeById = /* @__PURE__ */ new Map();
330
+ const reviewNeededById = /* @__PURE__ */ new Set();
331
+ for (const a of assumptions) {
332
+ const existing = a["Question Type"];
333
+ let qt;
334
+ let review = false;
335
+ if (existing && ["Existence", "Prevalence", "CausalEffect", "WillingnessToPay", "ValueUtility", "Regulatory", "Feasibility"].includes(existing)) {
336
+ qt = existing;
337
+ } else {
338
+ qt = inferQuestionType(a.Description ?? "", a.wrongIfBar ?? "");
339
+ review = needsReview(a.Description ?? "", a.wrongIfBar ?? "", qt);
340
+ if (review) reviewNeededById.add(a.id);
341
+ }
342
+ questionTypeById.set(a.id, qt);
343
+ }
344
+ const migratedReadings = [];
345
+ let nonEvidenceFlagCount = 0;
346
+ for (const r of readings) {
347
+ const questionType = questionTypeById.get(r.assumptionId) ?? "Existence";
348
+ const band2 = r.magnitudeBand ?? "Typical";
349
+ const newStrength = readingStrength({
350
+ questionType,
351
+ rung: r.Rung,
352
+ result: r.Result,
353
+ magnitudeBand: band2
354
+ });
355
+ const oldStrength = r.derived?.strength ?? 0;
356
+ const nonEvidence = isNonEvidence(questionType, r.Rung);
357
+ if (nonEvidence) nonEvidenceFlagCount += 1;
358
+ migratedReadings.push({
359
+ id: r.id,
360
+ assumptionId: r.assumptionId,
361
+ Rung: r.Rung,
362
+ newStrength,
363
+ oldStrength,
364
+ nonEvidence
365
+ });
366
+ }
367
+ const inputsByAssumption = /* @__PURE__ */ new Map();
368
+ for (const a of assumptions) inputsByAssumption.set(a.id, []);
369
+ for (const r of readings) {
370
+ const questionType = questionTypeById.get(r.assumptionId) ?? "Existence";
371
+ inputsByAssumption.get(r.assumptionId)?.push({
372
+ id: r.id,
373
+ source: r.Source,
374
+ rung: r.Rung,
375
+ result: r.Result,
376
+ questionType,
377
+ representativeness: r.Representativeness,
378
+ credibility: r.Credibility,
379
+ date: r.Date,
380
+ magnitudeBand: r.magnitudeBand ?? void 0,
381
+ experimentId: r.experimentId
382
+ });
383
+ }
384
+ const migratedAssumptions = assumptions.map((a) => {
385
+ const questionType = questionTypeById.get(a.id) ?? "Existence";
386
+ const oldConfidence = a.derived?.confidence ?? 0;
387
+ const newConfidence = confidence(inputsByAssumption.get(a.id) ?? []);
388
+ const stage = a.Stage ?? null;
389
+ return {
390
+ ...a,
391
+ "Question Type": questionType,
392
+ questionTypeReviewNeeded: reviewNeededById.has(a.id),
393
+ newConfidence,
394
+ oldConfidence,
395
+ confidenceDelta: Math.round((newConfidence - oldConfidence) * 100) / 100,
396
+ riskThreshold: riskThresholdForStage(stage)
397
+ };
398
+ });
399
+ const confidenceDeltas = migratedAssumptions.map((a) => ({
400
+ id: a.id,
401
+ delta: a.confidenceDelta,
402
+ oldConfidence: a.oldConfidence,
403
+ newConfidence: a.newConfidence
404
+ })).sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta));
405
+ const oldRanked = [...assumptions].map((a) => ({ id: a.id, c: a.derived?.confidence ?? 0 })).sort((a, b) => b.c - a.c || a.id.localeCompare(b.id));
406
+ const newRanked = [...migratedAssumptions].map((a) => ({ id: a.id, c: a.newConfidence })).sort((a, b) => b.c - a.c || a.id.localeCompare(b.id));
407
+ const oldRank = new Map(oldRanked.map((r, i) => [r.id, i + 1]));
408
+ const newRank = new Map(newRanked.map((r, i) => [r.id, i + 1]));
409
+ const rankingShifts = migratedAssumptions.map((a) => {
410
+ const o = oldRank.get(a.id) ?? 0;
411
+ const n = newRank.get(a.id) ?? 0;
412
+ return { id: a.id, oldRank: o, newRank: n, shift: n - o };
413
+ }).filter((r) => r.oldRank > 0 && r.newRank > 0).sort((a, b) => Math.abs(b.shift) - Math.abs(a.shift));
414
+ return {
415
+ assumptions: migratedAssumptions,
416
+ readings: migratedReadings,
417
+ nonEvidenceFlagCount,
418
+ reviewQueueCount: reviewNeededById.size,
419
+ confidenceDeltas,
420
+ rankingShifts
421
+ };
112
422
  }
113
423
 
114
424
  // src/derivation/source-quality.ts
@@ -131,6 +441,16 @@ var TESTING_RUNGS = [
131
441
  "Observed usage"
132
442
  ];
133
443
  var MARKET_RUNG_VALUES = ["Signed intent", "Paying users"];
444
+ var STAGES = ["Discovery", "Validation", "Scale", "Maturity"];
445
+ var QUESTION_TYPES = [
446
+ "Existence",
447
+ "Prevalence",
448
+ "CausalEffect",
449
+ "WillingnessToPay",
450
+ "ValueUtility",
451
+ "Regulatory",
452
+ "Feasibility"
453
+ ];
134
454
 
135
455
  // src/derivation/confidence.ts
136
456
  var MARKET_RUNG_SET = new Set(MARKET_RUNG_VALUES);
@@ -615,6 +935,8 @@ export {
615
935
  REGISTERS,
616
936
  TESTING_RUNGS,
617
937
  MARKET_RUNG_VALUES,
938
+ STAGES,
939
+ QUESTION_TYPES,
618
940
  COMPLETENESS_SLOTS,
619
941
  completenessSlotPresence,
620
942
  missingCompletenessSlots,
@@ -622,9 +944,19 @@ export {
622
944
  assumptionComplete,
623
945
  round2,
624
946
  RUNG_ANCHOR,
947
+ isNonEvidence,
948
+ ceilingAnchor,
949
+ RISK_THRESHOLD_BY_STAGE,
950
+ riskThresholdForStage,
951
+ hasClearedThreshold,
625
952
  sign,
626
953
  isConcluded,
627
954
  readingStrength,
955
+ DEFAULT_QUESTION_TYPE,
956
+ inferQuestionType,
957
+ INFERABLE_QUESTION_TYPES,
958
+ needsReview,
959
+ migrateRegister,
628
960
  sourceQuality,
629
961
  W0_BY_RUNG,
630
962
  w0ForRung,
@@ -651,4 +983,4 @@ export {
651
983
  assembleJourney,
652
984
  derivation_exports
653
985
  };
654
- //# sourceMappingURL=chunk-HMAHBGOJ.js.map
986
+ //# sourceMappingURL=chunk-OBAVYIST.js.map