@actuarial-ts/core 0.4.0 → 0.6.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.
Files changed (87) hide show
  1. package/README.md +65 -215
  2. package/dist/casualtyDiagnostics.d.ts +19 -49
  3. package/dist/casualtyDiagnostics.d.ts.map +1 -1
  4. package/dist/casualtyDiagnostics.js +84 -114
  5. package/dist/casualtyDiagnostics.js.map +1 -1
  6. package/dist/diagnosticAggregation.d.ts +29 -0
  7. package/dist/diagnosticAggregation.d.ts.map +1 -0
  8. package/dist/diagnosticAggregation.js +50 -0
  9. package/dist/diagnosticAggregation.js.map +1 -0
  10. package/dist/diagnosticDefinitions.d.ts +286 -0
  11. package/dist/diagnosticDefinitions.d.ts.map +1 -0
  12. package/dist/diagnosticDefinitions.js +1026 -0
  13. package/dist/diagnosticDefinitions.js.map +1 -0
  14. package/dist/diagnosticDerivations.d.ts +12 -0
  15. package/dist/diagnosticDerivations.d.ts.map +1 -0
  16. package/dist/diagnosticDerivations.js +80 -0
  17. package/dist/diagnosticDerivations.js.map +1 -0
  18. package/dist/diagnosticExposure.d.ts +51 -0
  19. package/dist/diagnosticExposure.d.ts.map +1 -0
  20. package/dist/diagnosticExposure.js +65 -0
  21. package/dist/diagnosticExposure.js.map +1 -0
  22. package/dist/diagnosticExpressions.d.ts +56 -0
  23. package/dist/diagnosticExpressions.d.ts.map +1 -0
  24. package/dist/diagnosticExpressions.js +104 -0
  25. package/dist/diagnosticExpressions.js.map +1 -0
  26. package/dist/diagnosticFormulas.d.ts +195 -0
  27. package/dist/diagnosticFormulas.d.ts.map +1 -0
  28. package/dist/diagnosticFormulas.js +85 -0
  29. package/dist/diagnosticFormulas.js.map +1 -0
  30. package/dist/diagnosticIdentity.d.ts +237 -0
  31. package/dist/diagnosticIdentity.d.ts.map +1 -0
  32. package/dist/diagnosticIdentity.js +454 -0
  33. package/dist/diagnosticIdentity.js.map +1 -0
  34. package/dist/diagnosticPeriods.d.ts +16 -0
  35. package/dist/diagnosticPeriods.d.ts.map +1 -0
  36. package/dist/diagnosticPeriods.js +98 -0
  37. package/dist/diagnosticPeriods.js.map +1 -0
  38. package/dist/diagnosticPreparation.d.ts +98 -0
  39. package/dist/diagnosticPreparation.d.ts.map +1 -0
  40. package/dist/diagnosticPreparation.js +169 -0
  41. package/dist/diagnosticPreparation.js.map +1 -0
  42. package/dist/diagnosticReview.d.ts +44 -0
  43. package/dist/diagnosticReview.d.ts.map +1 -0
  44. package/dist/diagnosticReview.js +84 -0
  45. package/dist/diagnosticReview.js.map +1 -0
  46. package/dist/diagnosticRules.d.ts +32 -0
  47. package/dist/diagnosticRules.d.ts.map +1 -0
  48. package/dist/diagnosticRules.js +32 -0
  49. package/dist/diagnosticRules.js.map +1 -0
  50. package/dist/diagnosticRunner.d.ts +85 -0
  51. package/dist/diagnosticRunner.d.ts.map +1 -0
  52. package/dist/diagnosticRunner.js +117 -0
  53. package/dist/diagnosticRunner.js.map +1 -0
  54. package/dist/index.d.ts +24 -1
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +12 -1
  57. package/dist/index.js.map +1 -1
  58. package/dist/types.d.ts +17 -0
  59. package/dist/types.d.ts.map +1 -1
  60. package/dist/types.js +114 -0
  61. package/dist/types.js.map +1 -1
  62. package/dist/version.d.ts +3 -0
  63. package/dist/version.d.ts.map +1 -0
  64. package/dist/version.js +3 -0
  65. package/dist/version.js.map +1 -0
  66. package/package.json +2 -2
  67. package/src/casualtyDiagnostics.ts +79 -187
  68. package/src/diagnosticAggregation.ts +79 -0
  69. package/src/diagnosticDefinitions.ts +1375 -0
  70. package/src/diagnosticDerivations.ts +89 -0
  71. package/src/diagnosticExposure.ts +91 -0
  72. package/src/diagnosticExpressions.ts +173 -0
  73. package/src/diagnosticFormulas.ts +150 -0
  74. package/src/diagnosticIdentity.ts +767 -0
  75. package/src/diagnosticPeriods.ts +117 -0
  76. package/src/diagnosticPreparation.ts +233 -0
  77. package/src/diagnosticReview.ts +91 -0
  78. package/src/diagnosticRules.ts +69 -0
  79. package/src/diagnosticRunner.ts +110 -0
  80. package/src/index.ts +156 -1
  81. package/src/types.ts +161 -0
  82. package/src/version.ts +2 -0
  83. package/dist/metricDiagnostics.d.ts +0 -212
  84. package/dist/metricDiagnostics.d.ts.map +0 -1
  85. package/dist/metricDiagnostics.js +0 -627
  86. package/dist/metricDiagnostics.js.map +0 -1
  87. package/src/metricDiagnostics.ts +0 -876
@@ -0,0 +1,110 @@
1
+ import type { DiagnosticDeepReadonly, DiagnosticMeasureDefinition, DiagnosticMetricPresentation, DiagnosticsFilter, JsonValue } from "./diagnosticDefinitions.js";
2
+ import { getCompiledDiagnosticDefinitionInternals } from "./diagnosticDefinitions.js";
3
+ import { applyDiagnosticPresentation, diagnosticRawRatio, evaluateDiagnosticMeasureExpression, evaluateDiagnosticRoleExpression, type DiagnosticMetricFinding, type DiagnosticQuantity, type FinalizedDiagnosticMeasure } from "./diagnosticFormulas.js";
4
+ import { classifyDiagnosticComparison, diagnosticPredicateMatches, type DiagnosticRuleEvaluation } from "./diagnosticRules.js";
5
+ import { assertPreparedDiagnosticData, type PreparedDiagnosticData } from "./diagnosticPreparation.js";
6
+ import type { DiagnosticMeasureStats } from "./diagnosticDefinitions.js";
7
+ import { DiagnosticValidationError } from "./types.js";
8
+
9
+ export interface RunMetricDiagnosticsInput { readonly prepared: PreparedDiagnosticData; readonly groupMap?: Readonly<Record<string, string>>; readonly groupDimensions?: Readonly<Record<string, JsonValue>> }
10
+ export interface DiagnosticMetricEvaluation {
11
+ readonly instanceId: string; readonly instanceVersion: string; readonly formulaId: string; readonly formulaVersion: string;
12
+ readonly semanticReferences: { readonly amountBasisIds: readonly string[]; readonly countPopulationIds: readonly string[]; readonly exposureBasisIds: readonly string[] };
13
+ readonly formulaFingerprint: string; readonly calculationFingerprint: string; readonly definitionIntegrity: string;
14
+ readonly calculation: { readonly numerator: DiagnosticQuantity; readonly denominator: DiagnosticQuantity; readonly value: number | null };
15
+ readonly presentation: DiagnosticMetricPresentation & { readonly value: number | null };
16
+ readonly components: Readonly<Record<string, DiagnosticMeasureStats>>;
17
+ readonly rules: readonly DiagnosticRuleEvaluation[];
18
+ readonly findings: readonly DiagnosticMetricFinding[];
19
+ }
20
+ export interface DiagnosticEmergencePoint { readonly group: string; readonly sourceGroups: readonly string[]; readonly dimensions?: JsonValue; readonly origin: string; readonly valuation: string; readonly developmentAge: number; readonly ageUnit: string; readonly components: Readonly<Record<string, DiagnosticMeasureStats>>; readonly metrics: Readonly<Record<string, DiagnosticMetricEvaluation>>; readonly findings: readonly DiagnosticMetricFinding[] }
21
+ export interface DiagnosticMetricTriangleCell { readonly origin: string; readonly valuation: string; readonly developmentAge: number; readonly ageUnit: string; readonly evaluation: DiagnosticMetricEvaluation }
22
+ export interface DiagnosticMetricTriangle { readonly group: string; readonly instanceId: string; readonly origins: readonly string[]; readonly developmentAges: readonly number[]; readonly ageUnit: string; readonly calculationValues: readonly (readonly (number | null)[])[]; readonly presentationValues: readonly (readonly (number | null)[])[]; readonly cells: readonly (readonly (DiagnosticMetricTriangleCell | null)[])[] }
23
+ export interface MetricDiagnosticsResult { readonly definitionIntegrity: string; readonly preparationFingerprint: string; readonly ageUnit: string; readonly emergence: readonly DiagnosticEmergencePoint[]; readonly triangles: readonly DiagnosticMetricTriangle[]; readonly latestDiagonal: readonly DiagnosticEmergencePoint[]; readonly findings: readonly DiagnosticMetricFinding[] }
24
+ export interface CommonMaturityResult { readonly developmentAge: number | null; readonly ageUnit: string; readonly points: readonly DiagnosticDeepReadonly<DiagnosticEmergencePoint>[] }
25
+ export type NormalizedDiagnosticResultIdentity = DiagnosticDeepReadonly<MetricDiagnosticsResult>;
26
+
27
+ function codeUnit(a: string, b: string): number { return a < b ? -1 : a > b ? 1 : 0 }
28
+ function deepFreeze<T>(value: T, seen = new WeakSet<object>()): DiagnosticDeepReadonly<T> { if (value === null || typeof value !== "object" || seen.has(value)) return value as DiagnosticDeepReadonly<T>; seen.add(value); for (const child of Object.values(value as Record<string, unknown>)) deepFreeze(child, seen); return Object.freeze(value) as DiagnosticDeepReadonly<T> }
29
+ function quantity(measure: DiagnosticMeasureDefinition, value: number | null): DiagnosticQuantity { return { kind: measure.kind, unit: measure.unit, ...(measure.basisId ? { basisId: measure.basisId } : {}), ...(measure.countPopulationId ? { countPopulationId: measure.countPopulationId } : {}), ...(measure.exposureBasisId ? { exposureBasisId: measure.exposureBasisId } : {}), value } }
30
+
31
+ export function validateDiagnosticGroupingConfiguration(input: RunMetricDiagnosticsInput): void {
32
+ assertPreparedDiagnosticData(input.prepared);
33
+ const sourceGroups = new Set(input.prepared.cells.map((cell) => cell.sourceGroup));
34
+ for (const [source, target] of Object.entries(input.groupMap ?? {})) {
35
+ if (!sourceGroups.has(source)) throw new DiagnosticValidationError([{ domain: "configuration", code: "invalid-configuration", path: `$.groupMap[${JSON.stringify(source)}]`, message: "Group map contains an unused source group" }]);
36
+ if (target.length === 0 || /^[\t-\r ]|[\t-\r ]$/.test(target)) throw new DiagnosticValidationError([{ domain: "configuration", code: "invalid-string", path: `$.groupMap[${JSON.stringify(source)}]`, message: "Output group must be a nonempty token" }]);
37
+ }
38
+ const outputGroups = new Set([...sourceGroups].map((source) => input.groupMap?.[source] ?? source));
39
+ for (const group of Object.keys(input.groupDimensions ?? {})) if (!outputGroups.has(group)) throw new DiagnosticValidationError([{ domain: "configuration", code: "invalid-configuration", path: `$.groupDimensions[${JSON.stringify(group)}]`, message: "Dimensions contain an unused output group" }]);
40
+ for (const group of input.prepared.filter?.outputGroups ?? []) if (!outputGroups.has(group)) throw new DiagnosticValidationError([{ domain: "configuration", code: "unknown-reference", path: "$.prepared.filter.outputGroups", message: `Unknown output group ${group}` }]);
41
+ }
42
+
43
+ function mergeStats(stats: readonly DiagnosticMeasureStats[]): DiagnosticMeasureStats {
44
+ if (stats.length === 0) return { value: null, sum: 0, observed: 0, missing: 0, nonFinite: 0, imputedZero: 0, deduplicated: 0, structural: 0 };
45
+ const count = (key: "observed" | "missing" | "nonFinite" | "imputedZero" | "deduplicated" | "structural") => stats.reduce((sum, item) => sum + item[key], 0);
46
+ const structural = count("structural"); const nonFinite = count("nonFinite");
47
+ const sums = stats.map((item) => item.sum);
48
+ const sum = structural > 0 || nonFinite > 0 || sums.some((value) => value === null) ? null : sums.reduce<number>((total, value) => total + value!, 0);
49
+ return { value: sum === null || stats.some((item) => item.value === null) ? null : sum, sum, observed: count("observed"), missing: count("missing"), nonFinite, imputedZero: count("imputedZero"), deduplicated: count("deduplicated"), structural };
50
+ }
51
+
52
+ function inferExpressionMeasure(expression: import("./diagnosticExpressions.js").DiagnosticMeasureExpression, measures: ReadonlyMap<string, DiagnosticMeasureDefinition>): DiagnosticMeasureDefinition {
53
+ const id = expression.op === "measure" ? expression.measureId : expression.op === "add" ? inferExpressionMeasure(expression.terms[0]!, measures).id : inferExpressionMeasure(expression.left, measures).id;
54
+ return measures.get(id)!;
55
+ }
56
+
57
+ function evaluatePoint(prepared: PreparedDiagnosticData, components: Readonly<Record<string, DiagnosticMeasureStats>>, instanceId: string): DiagnosticMetricEvaluation {
58
+ const definition = prepared.definition;
59
+ const internals = getCompiledDiagnosticDefinitionInternals(definition);
60
+ const instance = definition.definition.instances.find((item) => item.id === instanceId)!;
61
+ const formula = definition.definition.formulas.find((item) => item.id === instance.formulaId)!;
62
+ const measureStates: Record<string, FinalizedDiagnosticMeasure> = Object.create(null);
63
+ for (const [measureId, stats] of Object.entries(components)) {
64
+ const measure = internals.measuresById.get(measureId)!;
65
+ const readiness = [...(stats.missing > 0 ? [stats.imputedZero > 0 ? "imputed" as const : "missing" as const] : []), ...(stats.nonFinite > 0 ? ["non-finite" as const] : []), ...(stats.structural > 0 ? ["structural-ambiguity" as const] : []), ...(stats.sum === null && stats.nonFinite === 0 && stats.structural === 0 ? ["aggregation-overflow" as const] : [])];
66
+ measureStates[measureId] = { quantity: quantity(measure, stats.value), stats, readiness };
67
+ }
68
+ const bindings = Object.fromEntries(Object.entries(instance.bindings).map(([role, expression]) => [role, evaluateDiagnosticMeasureExpression(expression, measureStates, `$.instances[${JSON.stringify(instance.id)}].bindings[${JSON.stringify(role)}]`)]));
69
+ const numeratorResult = evaluateDiagnosticRoleExpression(formula.numerator, bindings, `$.formulas[${JSON.stringify(formula.id)}].numerator`);
70
+ const denominatorResult = evaluateDiagnosticRoleExpression(formula.denominator, bindings, `$.formulas[${JSON.stringify(formula.id)}].denominator`);
71
+ const raw = diagnosticRawRatio(numeratorResult.value, denominatorResult.value);
72
+ const presented = applyDiagnosticPresentation(raw, instance.presentation);
73
+ const numeratorMeasure = inferExpressionMeasure(instance.bindings[(formula.numerator.op === "role" ? formula.numerator.role : formula.numerator.op === "subtract" && formula.numerator.left.op === "role" ? formula.numerator.left.role : Object.keys(instance.bindings)[0])!]!, internals.measuresById);
74
+ const denominatorMeasure = inferExpressionMeasure(instance.bindings[(formula.denominator.op === "role" ? formula.denominator.role : Object.keys(instance.bindings)[0])!]!, internals.measuresById);
75
+ const ruleEvaluations: DiagnosticRuleEvaluation[] = [];
76
+ const findings: DiagnosticMetricFinding[] = presented.finding ? [{ ...presented.finding, instanceId: instance.id }] : [];
77
+ const operand = (item: typeof instance.rules[number]["when"]["left"]): number | null => item.source === "constant" ? item.value : item.source === "calculation" ? (item.field === "numerator" ? numeratorResult.value : denominatorResult.value) : evaluateDiagnosticMeasureExpression(item.expression, measureStates, "$.rule").value;
78
+ for (const rule of instance.rules) {
79
+ const left = operand(rule.when.left); const right = operand(rule.when.right); const classified = classifyDiagnosticComparison(left, right, rule.when.tolerance);
80
+ if (classified.status === "not-evaluated") ruleEvaluations.push({ ruleId: rule.id, status: "not-evaluated", severity: rule.severity, left: Number.isFinite(left) ? left : null, right: Number.isFinite(right) ? right : null, relation: null, notEvaluatedReasons: [classified.reason], expressionOverflows: [], code: "diagnostic-rule-not-evaluated", message: "Diagnostic metric rule was not evaluated" });
81
+ else {
82
+ const triggered = diagnosticPredicateMatches(rule.when.operator, classified.relation);
83
+ ruleEvaluations.push({ ruleId: rule.id, status: triggered ? "triggered" : "pass", severity: rule.severity, left, right, relation: classified.relation, notEvaluatedReasons: [], expressionOverflows: [], code: triggered ? rule.code : null, message: triggered ? rule.message : null });
84
+ if (triggered) findings.push({ code: rule.code, message: rule.message, severity: rule.severity, category: "rule", ruleId: rule.id, instanceId: instance.id, sources: [] });
85
+ }
86
+ }
87
+ const deps = internals.evaluationDependenciesByInstanceId.get(instance.id) ?? [];
88
+ const refs = { amountBasisIds: [...new Set(deps.flatMap((id) => internals.measuresById.get(id)?.basisId ? [internals.measuresById.get(id)!.basisId!] : []))].sort(codeUnit), countPopulationIds: [...new Set(deps.flatMap((id) => internals.measuresById.get(id)?.countPopulationId ? [internals.measuresById.get(id)!.countPopulationId!] : []))].sort(codeUnit), exposureBasisIds: [...new Set(deps.flatMap((id) => internals.measuresById.get(id)?.exposureBasisId ? [internals.measuresById.get(id)!.exposureBasisId!] : []))].sort(codeUnit) };
89
+ return deepFreeze({ instanceId: instance.id, instanceVersion: instance.version, formulaId: formula.id, formulaVersion: formula.version, semanticReferences: refs, formulaFingerprint: definition.formulaFingerprints[formula.id]!, calculationFingerprint: definition.calculationFingerprints[instance.id]!, definitionIntegrity: definition.definitionIntegrity, calculation: { numerator: quantity(numeratorMeasure, numeratorResult.value), denominator: quantity(denominatorMeasure, denominatorResult.value), value: raw }, presentation: { ...instance.presentation, value: presented.value }, components: Object.fromEntries(deps.map((id) => [id, components[id]!])), rules: ruleEvaluations, findings });
90
+ }
91
+
92
+ export function runMetricDiagnostics(input: RunMetricDiagnosticsInput): DiagnosticDeepReadonly<MetricDiagnosticsResult> {
93
+ validateDiagnosticGroupingConfiguration(input);
94
+ const prepared = input.prepared;
95
+ const buckets = new Map<string, typeof prepared.cells>();
96
+ for (const cell of prepared.cells) { const group = input.groupMap?.[cell.sourceGroup] ?? cell.sourceGroup; if (prepared.filter?.outputGroups !== undefined && !prepared.filter.outputGroups.includes(group)) continue; const key = `${group}\u0000${cell.origin}\u0000${cell.valuation}`; buckets.set(key, [...(buckets.get(key) ?? []), cell]); }
97
+ const selectedInstances = prepared.definition.definition.instances.filter((instance) => prepared.filter?.instanceIds === undefined || prepared.filter.instanceIds.includes(instance.id));
98
+ const emergence: DiagnosticEmergencePoint[] = [];
99
+ for (const [key, cells] of buckets) { const [group, origin, valuation] = key.split("\u0000") as [string,string,string]; const components = Object.fromEntries(prepared.definition.definition.measures.map((measure) => [measure.id, mergeStats(cells.map((cell) => cell.components[measure.id]!))])); const metrics = Object.fromEntries(selectedInstances.map((instance) => [instance.id, evaluatePoint(prepared, components, instance.id)])); emergence.push({ group, sourceGroups: [...new Set(cells.map((cell) => cell.sourceGroup))].sort(codeUnit), ...(input.groupDimensions && Object.prototype.hasOwnProperty.call(input.groupDimensions, group) ? { dimensions: input.groupDimensions[group] } : {}), origin, valuation, developmentAge: cells[0]!.developmentAge, ageUnit: cells[0]!.ageUnit, components, metrics, findings: Object.values(metrics).flatMap((metric) => metric.findings) }); }
100
+ emergence.sort((a,b) => codeUnit(a.group,b.group)||codeUnit(a.origin,b.origin)||a.developmentAge-b.developmentAge||codeUnit(a.valuation,b.valuation));
101
+ const groups = [...new Set(emergence.map((point) => point.group))].sort(codeUnit);
102
+ const triangles: DiagnosticMetricTriangle[] = [];
103
+ for (const group of groups) for (const instance of selectedInstances) { const points = emergence.filter((point) => point.group===group); const origins=[...new Set(points.map((point)=>point.origin))].sort(codeUnit); const ages=[...new Set(points.map((point)=>point.developmentAge))].sort((a,b)=>a-b); const cells=origins.map((origin)=>ages.map((age)=>{const point=points.find((item)=>item.origin===origin&&item.developmentAge===age); return point?{origin:point.origin,valuation:point.valuation,developmentAge:point.developmentAge,ageUnit:point.ageUnit,evaluation:point.metrics[instance.id]!}:null;})); triangles.push({group,instanceId:instance.id,origins,developmentAges:ages,ageUnit:prepared.definition.definition.periodAxis.ageUnit,calculationValues:cells.map((row)=>row.map((cell)=>cell?.evaluation.calculation.value??null)),presentationValues:cells.map((row)=>row.map((cell)=>cell?.evaluation.presentation.value??null)),cells}); }
104
+ const latestDiagonal = groups.flatMap((group)=>{const groupPoints=emergence.filter((point)=>point.group===group); return [...new Set(groupPoints.map((point)=>point.origin))].flatMap((origin)=>{const points=groupPoints.filter((point)=>point.origin===origin); return points.length?[points.reduce((latest,point)=>point.developmentAge>latest.developmentAge?point:latest)]:[];});});
105
+ return deepFreeze({ definitionIntegrity: prepared.definition.definitionIntegrity, preparationFingerprint: prepared.preparationFingerprint, ageUnit: prepared.definition.definition.periodAxis.ageUnit, emergence, triangles, latestDiagonal, findings: [...prepared.findings, ...emergence.flatMap((point)=>point.findings)] });
106
+ }
107
+
108
+ export function sameMaturity(result: DiagnosticDeepReadonly<MetricDiagnosticsResult>, developmentAge: number, outputGroups?: readonly string[]): readonly DiagnosticDeepReadonly<DiagnosticEmergencePoint>[] { if (!Number.isSafeInteger(developmentAge) || developmentAge < 0) throw new DiagnosticValidationError([{domain:"view",code:"invalid-number",path:"$.developmentAge",message:"Development age must be a nonnegative safe integer"}]); const groups=outputGroups===undefined?null:new Set(outputGroups); return result.emergence.filter((point)=>point.developmentAge===developmentAge&&(groups===null||groups.has(point.group))); }
109
+ export function commonMaturity(result: DiagnosticDeepReadonly<MetricDiagnosticsResult>, outputGroups: readonly string[]): DiagnosticDeepReadonly<CommonMaturityResult> { const groups=[...new Set(outputGroups)].sort(codeUnit); const common=[...new Set(result.emergence.filter((p)=>p.group===groups[0]).map((p)=>p.developmentAge))].filter((age)=>groups.every((group)=>result.emergence.some((point)=>point.group===group&&point.developmentAge===age))).sort((a,b)=>b-a); const developmentAge=common[0]??null; return deepFreeze({developmentAge,ageUnit:result.ageUnit,points:developmentAge===null?[]:sameMaturity(result,developmentAge,groups)}); }
110
+ export function getMetricDiagnosticsResultIdentity(result: DiagnosticDeepReadonly<MetricDiagnosticsResult>): NormalizedDiagnosticResultIdentity { return deepFreeze(result); }
package/src/index.ts CHANGED
@@ -1,6 +1,162 @@
1
1
  export * from "./types.js";
2
2
  export * from "./util.js";
3
3
  export * from "./canonical.js";
4
+ export * from "./version.js";
5
+ export {
6
+ MAX_DIAGNOSTIC_DEFINITION_EXPRESSION_NODES,
7
+ MAX_DIAGNOSTIC_EXPRESSION_DEPTH,
8
+ MAX_DIAGNOSTIC_EXPRESSION_NODES,
9
+ } from "./diagnosticExpressions.js";
10
+ export type {
11
+ DiagnosticClaimExpression,
12
+ DiagnosticMeasureExpression,
13
+ DiagnosticRoleExpression,
14
+ } from "./diagnosticExpressions.js";
15
+ export {
16
+ assertCompiledDiagnosticDefinition,
17
+ compileDiagnosticDefinition,
18
+ } from "./diagnosticDefinitions.js";
19
+ export type {
20
+ AmountBasisComponent,
21
+ AmountBasisDefinition,
22
+ AmountLimitation,
23
+ AmountPerspective,
24
+ CompiledDiagnosticDefinition,
25
+ DiagnosticAggregation,
26
+ DiagnosticComparisonPredicate,
27
+ DiagnosticComparisonRule,
28
+ DiagnosticControlTotalProjection,
29
+ DiagnosticCountPopulationDefinition,
30
+ DiagnosticDeepReadonly,
31
+ DiagnosticDefinition,
32
+ DiagnosticDerivedMeasureDefinition,
33
+ DiagnosticDevelopmentSemantics,
34
+ DiagnosticExposureBasisDefinition,
35
+ DiagnosticExposureTiming,
36
+ DiagnosticFormulaRole,
37
+ DiagnosticFormulaTemplate,
38
+ DiagnosticMeasureDefinition,
39
+ DiagnosticMeasureKind,
40
+ DiagnosticMeasureSource,
41
+ DiagnosticMeasureStats,
42
+ DiagnosticMetricInstance,
43
+ DiagnosticMetricPresentation,
44
+ DiagnosticsFilter,
45
+ DiagnosticMissingPolicy,
46
+ DiagnosticPeriodAxis,
47
+ DiagnosticPeriodCoordinate,
48
+ DiagnosticReviewFilter,
49
+ DiagnosticReviewOperand,
50
+ DiagnosticReviewPredicate,
51
+ DiagnosticReviewRule,
52
+ DiagnosticRuleOperand,
53
+ DiagnosticSourceLocation,
54
+ JsonValue,
55
+ } from "./diagnosticDefinitions.js";
56
+ export type {
57
+ NormalizedAmountLimitationIdentity,
58
+ NormalizedDiagnosticCalculationScope,
59
+ NormalizedDiagnosticDefinitionIdentity,
60
+ NormalizedDiagnosticFormulaIdentity,
61
+ NormalizedDiagnosticPeriodAxisIdentity,
62
+ NormalizedDiagnosticReviewFilterIdentity,
63
+ NormalizedDiagnosticReviewRuleIdentity,
64
+ NormalizedDiagnosticSourceLocationIdentity,
65
+ NormalizedDiagnosticToleranceIdentity,
66
+ } from "./diagnosticIdentity.js";
67
+ export {
68
+ CASUALTY_FORMULA_TEMPLATES,
69
+ applyDiagnosticPresentation,
70
+ diagnosticRawRatio,
71
+ } from "./diagnosticFormulas.js";
72
+ export type {
73
+ DiagnosticMetricFinding,
74
+ DiagnosticQuantity,
75
+ DiagnosticQuantitySemantics,
76
+ FinalizedDiagnosticMeasure,
77
+ } from "./diagnosticFormulas.js";
78
+ export {
79
+ classifyDiagnosticComparison,
80
+ } from "./diagnosticRules.js";
81
+ export { deriveDiagnosticClaimMeasures } from "./diagnosticDerivations.js";
82
+ export type {
83
+ DiagnosticMeasureValues,
84
+ DiagnosticRowWithDerivedMeasures,
85
+ } from "./diagnosticDerivations.js";
86
+ export {
87
+ compareDiagnosticPeriods,
88
+ diagnosticDevelopmentAge,
89
+ normalizeDiagnosticPeriod,
90
+ } from "./diagnosticPeriods.js";
91
+ export type { DiagnosticNormalizedPeriod } from "./diagnosticPeriods.js";
92
+ export {
93
+ auditedDiagnosticContribution,
94
+ finalizeDiagnosticContributions,
95
+ } from "./diagnosticAggregation.js";
96
+ export type {
97
+ DiagnosticMeasureContribution,
98
+ DiagnosticMeasureContributionBase,
99
+ DiagnosticStructuralBlocker,
100
+ } from "./diagnosticAggregation.js";
101
+ export {
102
+ auditDiagnosticNumber,
103
+ reconcileDiagnosticExposures,
104
+ } from "./diagnosticExposure.js";
105
+ export {
106
+ assertPreparedDiagnosticData,
107
+ getPreparedDiagnosticDataIdentity,
108
+ prepareDiagnosticData,
109
+ verifyPreparedDiagnosticDataIntegrity,
110
+ } from "./diagnosticPreparation.js";
111
+ export type {
112
+ DiagnosticClaimObservation,
113
+ DiagnosticCompletePeriodCutoff,
114
+ DiagnosticExpectedCell,
115
+ DiagnosticInputAuditRecord,
116
+ DiagnosticInputDisposition,
117
+ DiagnosticLossInput,
118
+ DiagnosticLossRecordBase,
119
+ DiagnosticLossSnapshot,
120
+ NormalizedDiagnosticPreparationIdentity,
121
+ PrepareDiagnosticDataInput,
122
+ PreparedDiagnosticData,
123
+ PreparedDiagnosticSourceCell,
124
+ } from "./diagnosticPreparation.js";
125
+ export {
126
+ commonMaturity,
127
+ getMetricDiagnosticsResultIdentity,
128
+ runMetricDiagnostics,
129
+ sameMaturity,
130
+ validateDiagnosticGroupingConfiguration,
131
+ } from "./diagnosticRunner.js";
132
+ export { evaluateDiagnosticReviewRules } from "./diagnosticReview.js";
133
+ export type {
134
+ DiagnosticReviewCoordinate,
135
+ DiagnosticReviewEvaluationScope,
136
+ DiagnosticReviewRuleEvaluation,
137
+ } from "./diagnosticReview.js";
138
+ export type {
139
+ CommonMaturityResult,
140
+ DiagnosticEmergencePoint,
141
+ DiagnosticMetricEvaluation,
142
+ DiagnosticMetricTriangle,
143
+ DiagnosticMetricTriangleCell,
144
+ MetricDiagnosticsResult,
145
+ NormalizedDiagnosticResultIdentity,
146
+ RunMetricDiagnosticsInput,
147
+ } from "./diagnosticRunner.js";
148
+ export type {
149
+ DiagnosticAuditedNumericValue,
150
+ DiagnosticExposureAuditObservation,
151
+ DiagnosticExposureObservation,
152
+ ReconciledDiagnosticExposure,
153
+ } from "./diagnosticExposure.js";
154
+ export type {
155
+ DiagnosticComparisonClassification,
156
+ DiagnosticExpressionOverflow,
157
+ DiagnosticRuleEvaluation,
158
+ DiagnosticRuleNotEvaluatedReason,
159
+ } from "./diagnosticRules.js";
4
160
  export * from "./triangle.js";
5
161
  export * from "./factors.js";
6
162
  export * from "./chainladder.js";
@@ -15,7 +171,6 @@ export * from "./berquist.js";
15
171
  export * from "./clark.js";
16
172
  export * from "./diagnostics.js";
17
173
  export * from "./periods.js";
18
- export * from "./metricDiagnostics.js";
19
174
  export * from "./casualtyDiagnostics.js";
20
175
  export * from "./mack.js";
21
176
  export * from "./capping.js";
package/src/types.ts CHANGED
@@ -295,6 +295,167 @@ export interface DiagnosticFinding {
295
295
  message: string;
296
296
  }
297
297
 
298
+ export type DiagnosticValidationErrorCode =
299
+ | "INVALID_DIAGNOSTIC_DEFINITION"
300
+ | "INVALID_DIAGNOSTIC_INPUT"
301
+ | "INVALID_DIAGNOSTIC_CONFIGURATION"
302
+ | "INVALID_DIAGNOSTIC_VIEW";
303
+
304
+ export type DiagnosticValidationIssueCode =
305
+ | "missing-required"
306
+ | "invalid-type"
307
+ | "unknown-key"
308
+ | "invalid-string"
309
+ | "invalid-number"
310
+ | "invalid-json-value"
311
+ | "duplicate-id"
312
+ | "unknown-reference"
313
+ | "incompatible-semantics"
314
+ | "invalid-period"
315
+ | "invalid-input-relationship"
316
+ | "invalid-configuration"
317
+ | "expression-limit"
318
+ | "cycle";
319
+
320
+ export type DiagnosticValidationDomain =
321
+ | "definition"
322
+ | "input"
323
+ | "configuration"
324
+ | "view";
325
+
326
+ export interface DiagnosticValidationIssue {
327
+ readonly domain: DiagnosticValidationDomain;
328
+ readonly code: DiagnosticValidationIssueCode;
329
+ /** Canonical singular JSONPath, rooted at `$`. */
330
+ readonly path: string;
331
+ readonly message: string;
332
+ }
333
+
334
+ const DIAGNOSTIC_DOMAIN_ORDER: readonly DiagnosticValidationDomain[] = [
335
+ "definition",
336
+ "input",
337
+ "configuration",
338
+ "view",
339
+ ];
340
+
341
+ const DIAGNOSTIC_ISSUE_ORDER: readonly DiagnosticValidationIssueCode[] = [
342
+ "missing-required",
343
+ "invalid-type",
344
+ "unknown-key",
345
+ "invalid-string",
346
+ "invalid-number",
347
+ "invalid-json-value",
348
+ "duplicate-id",
349
+ "unknown-reference",
350
+ "incompatible-semantics",
351
+ "invalid-period",
352
+ "invalid-input-relationship",
353
+ "invalid-configuration",
354
+ "expression-limit",
355
+ "cycle",
356
+ ];
357
+
358
+ function compareCodeUnits(left: string, right: string): number {
359
+ return left === right ? 0 : left < right ? -1 : 1;
360
+ }
361
+
362
+ type DiagnosticPathSegment =
363
+ | { readonly kind: "property"; readonly value: string }
364
+ | { readonly kind: "index"; readonly value: number };
365
+
366
+ function diagnosticPathSegments(path: string): readonly DiagnosticPathSegment[] {
367
+ const segments: DiagnosticPathSegment[] = [];
368
+ let cursor = 1;
369
+ while (cursor < path.length) {
370
+ if (path[cursor] === ".") {
371
+ const start = ++cursor;
372
+ while (cursor < path.length && path[cursor] !== "." && path[cursor] !== "[") cursor++;
373
+ segments.push({ kind: "property", value: path.slice(start, cursor) });
374
+ continue;
375
+ }
376
+ if (path[cursor] === "[") {
377
+ const close = path.indexOf("]", cursor);
378
+ if (close < 0) return [{ kind: "property", value: path }];
379
+ const token = path.slice(cursor + 1, close);
380
+ if (/^\d+$/.test(token)) segments.push({ kind: "index", value: Number(token) });
381
+ else {
382
+ try {
383
+ segments.push({ kind: "property", value: JSON.parse(token) as string });
384
+ } catch {
385
+ return [{ kind: "property", value: path }];
386
+ }
387
+ }
388
+ cursor = close + 1;
389
+ continue;
390
+ }
391
+ return [{ kind: "property", value: path }];
392
+ }
393
+ return segments;
394
+ }
395
+
396
+ function compareDiagnosticPaths(left: string, right: string): number {
397
+ if (left === right) return 0;
398
+ const a = diagnosticPathSegments(left);
399
+ const b = diagnosticPathSegments(right);
400
+ const length = Math.min(a.length, b.length);
401
+ for (let index = 0; index < length; index++) {
402
+ const x = a[index]!;
403
+ const y = b[index]!;
404
+ if (x.kind !== y.kind) return x.kind === "property" ? -1 : 1;
405
+ const compared = x.kind === "index"
406
+ ? x.value - (y as Extract<DiagnosticPathSegment, { kind: "index" }>).value
407
+ : compareCodeUnits(
408
+ x.value,
409
+ (y as Extract<DiagnosticPathSegment, { kind: "property" }>).value,
410
+ );
411
+ if (compared !== 0) return compared;
412
+ }
413
+ return a.length - b.length;
414
+ }
415
+
416
+ function normalizeDiagnosticIssues(
417
+ issues: readonly DiagnosticValidationIssue[],
418
+ ): readonly DiagnosticValidationIssue[] {
419
+ if (issues.length === 0) {
420
+ throw new Error("DiagnosticValidationError requires at least one issue");
421
+ }
422
+ const unique = new Map<string, DiagnosticValidationIssue>();
423
+ for (const issue of issues) {
424
+ const normalized = Object.freeze({ ...issue });
425
+ const key = `${issue.domain}\u0000${issue.code}\u0000${issue.path}\u0000${issue.message}`;
426
+ if (!unique.has(key)) unique.set(key, normalized);
427
+ }
428
+ return Object.freeze([...unique.values()].sort((left, right) =>
429
+ DIAGNOSTIC_DOMAIN_ORDER.indexOf(left.domain) - DIAGNOSTIC_DOMAIN_ORDER.indexOf(right.domain) ||
430
+ DIAGNOSTIC_ISSUE_ORDER.indexOf(left.code) - DIAGNOSTIC_ISSUE_ORDER.indexOf(right.code) ||
431
+ compareDiagnosticPaths(left.path, right.path) ||
432
+ compareCodeUnits(left.code, right.code) ||
433
+ compareCodeUnits(left.message, right.message),
434
+ ));
435
+ }
436
+
437
+ /** Public, deterministic validation failure for every generalized diagnostic boundary. */
438
+ export class DiagnosticValidationError extends Error {
439
+ readonly code: DiagnosticValidationErrorCode;
440
+ readonly path: string;
441
+ readonly issues: readonly DiagnosticValidationIssue[];
442
+
443
+ constructor(issues: readonly DiagnosticValidationIssue[]) {
444
+ const normalized = normalizeDiagnosticIssues(issues);
445
+ super(normalized[0]!.message);
446
+ this.name = "DiagnosticValidationError";
447
+ this.issues = normalized;
448
+ this.path = normalized[0]!.path;
449
+ this.code = {
450
+ definition: "INVALID_DIAGNOSTIC_DEFINITION",
451
+ input: "INVALID_DIAGNOSTIC_INPUT",
452
+ configuration: "INVALID_DIAGNOSTIC_CONFIGURATION",
453
+ view: "INVALID_DIAGNOSTIC_VIEW",
454
+ }[normalized[0]!.domain] as DiagnosticValidationErrorCode;
455
+ Object.freeze(this);
456
+ }
457
+ }
458
+
298
459
  /**
299
460
  * Every machine-readable code a ReservingError can carry. This registry is
300
461
  * public contract: consumers may switch exhaustively on ReservingErrorCode,
package/src/version.ts ADDED
@@ -0,0 +1,2 @@
1
+ /** This package's runtime version. A test pins it to package.json. */
2
+ export const CORE_PACKAGE_VERSION = "0.6.0";
@@ -1,212 +0,0 @@
1
- import { type DiagnosticFinding } from "./types.js";
2
- export type SparseValuePolicy = "preserve-null" | "zero-fill";
3
- export type DiagnosticMeasureMap = Readonly<Record<string, number | null | undefined>>;
4
- export type MeasureExpression = {
5
- op: "measure";
6
- measure: string;
7
- } | {
8
- op: "add";
9
- terms: readonly MeasureExpression[];
10
- } | {
11
- op: "subtract";
12
- left: MeasureExpression;
13
- right: MeasureExpression;
14
- };
15
- export interface DiagnosticWarning {
16
- code: "MISSING_COMPONENT" | "NON_FINITE_COMPONENT" | "NON_FINITE_NUMERATOR" | "NON_FINITE_RESULT" | "INVALID_DENOMINATOR" | "INCOMPLETE_EXPOSURE" | "CONFLICTING_EXPOSURE" | "DUPLICATE_EXPOSURE_KEY" | "PAID_EXCEEDS_INCURRED" | (string & {});
17
- message: string;
18
- component?: string;
19
- exposureKey?: string;
20
- }
21
- /** Adapts structured metric warnings to the existing core diagnostic finding vocabulary. */
22
- export declare function diagnosticWarningToFinding(warning: DiagnosticWarning): DiagnosticFinding;
23
- export interface MetricWarningRule {
24
- code: "PAID_EXCEEDS_INCURRED";
25
- when: "numerator-greater-than-denominator";
26
- message: string;
27
- tolerance?: number;
28
- }
29
- export interface MetricDefinition {
30
- id: string;
31
- version: string;
32
- displayName: string;
33
- description: string;
34
- unit: "count-per-million" | "ratio" | "currency-per-exposure" | "currency-per-claim" | (string & {});
35
- scale: number;
36
- numerator: MeasureExpression;
37
- denominator: MeasureExpression;
38
- numeratorLabel: string;
39
- denominatorLabel: string;
40
- basis: string;
41
- requiredComponents: readonly string[];
42
- warningRules?: readonly MetricWarningRule[];
43
- /** Optional pure caller rule for domain warnings beyond the built-in comparisons. */
44
- evaluateWarnings?: (context: MetricWarningContext) => readonly DiagnosticWarning[];
45
- }
46
- export interface MetricWarningContext {
47
- definition: MetricDefinition;
48
- components: Readonly<Record<string, number | null>>;
49
- rawNumerator: number | null;
50
- rawDenominator: number | null;
51
- value: number | null;
52
- }
53
- export interface MetricEvaluation {
54
- metricId: string;
55
- metricVersion: string;
56
- value: number | null;
57
- rawNumerator: number | null;
58
- rawDenominator: number | null;
59
- numeratorLabel: string;
60
- denominatorLabel: string;
61
- unit: MetricDefinition["unit"];
62
- scale: number;
63
- basis: string;
64
- rawComponents: Record<string, number | null>;
65
- warnings: DiagnosticWarning[];
66
- }
67
- export interface MeasureAggregateCell {
68
- sum: number;
69
- observed: number;
70
- missing: number;
71
- nonFinite: number;
72
- }
73
- /** Mergeable sufficient statistics: grouping in stages gives the same result. */
74
- export interface MeasureAggregate {
75
- components: Record<string, MeasureAggregateCell>;
76
- }
77
- export declare function aggregateMeasures(rows: readonly DiagnosticMeasureMap[]): MeasureAggregate;
78
- export declare function mergeMeasureAggregates(aggregates: readonly MeasureAggregate[]): MeasureAggregate;
79
- export interface FinalizedMeasures {
80
- measures: Record<string, number | null>;
81
- warnings: DiagnosticWarning[];
82
- }
83
- export declare function finalizeMeasureAggregate(aggregate: MeasureAggregate, sparsePolicy?: SparseValuePolicy): FinalizedMeasures;
84
- export declare function measureExpressionComponents(expression: MeasureExpression): string[];
85
- export declare function evaluateMetric(definition: MetricDefinition, components: DiagnosticMeasureMap, inheritedWarnings?: readonly DiagnosticWarning[]): MetricEvaluation;
86
- export type LayerExpression = {
87
- op: "measure";
88
- measure: string;
89
- } | {
90
- op: "add";
91
- terms: readonly LayerExpression[];
92
- } | {
93
- op: "claim-cap";
94
- measure: string;
95
- limit: number;
96
- };
97
- export interface AmountLayerDefinition {
98
- id: string;
99
- displayName: string;
100
- paidMeasure: string;
101
- incurredMeasure: string;
102
- paid: LayerExpression;
103
- incurred: LayerExpression;
104
- /** Documents whether inputs are already limited or this layer caps claim rows. */
105
- basis: "pre-capped-additive" | "claim-level-cap" | "unlimited-additive";
106
- }
107
- export interface DiagnosticClaimRow<T = unknown> {
108
- dimensions: T;
109
- measures: DiagnosticMeasureMap;
110
- }
111
- /** Applies claim caps before aggregation; this function intentionally accepts claim rows, not aggregates. */
112
- export declare function deriveAmountLayers<T>(rows: readonly DiagnosticClaimRow<T>[], layers: readonly AmountLayerDefinition[]): DiagnosticClaimRow<T>[];
113
- export interface DiagnosticLossRow<TDimensions = unknown> {
114
- id: string;
115
- group: string;
116
- origin: string;
117
- valuation: string;
118
- ageMonths: number;
119
- /** Optional caller-mapped policy/fiscal period (for example a Q3-Q2 year). */
120
- policyPeriod?: string;
121
- /** Optional caller-owned grouping metadata; `group` remains the stable key. */
122
- dimensions?: TDimensions;
123
- measures: DiagnosticMeasureMap;
124
- }
125
- export interface DiagnosticExposureRow<TDimensions = unknown> {
126
- /** Stable exposure identity. Repeated snapshots with the same key count once. */
127
- key: string;
128
- group: string;
129
- origin: string;
130
- valuation?: string;
131
- measures: DiagnosticMeasureMap;
132
- complete?: boolean;
133
- dimensions?: TDimensions;
134
- }
135
- export interface DiagnosticsFilter {
136
- groups?: readonly string[];
137
- origins?: readonly string[];
138
- originFrom?: string;
139
- originThrough?: string;
140
- valuations?: readonly string[];
141
- valuationFrom?: string;
142
- valuationThrough?: string;
143
- policyPeriods?: readonly string[];
144
- minAgeMonths?: number;
145
- maxAgeMonths?: number;
146
- }
147
- export interface DiagnosticEmergencePoint<TDimensions = unknown> {
148
- group: string;
149
- dimensions?: TDimensions;
150
- origin: string;
151
- valuation: string;
152
- ageMonths: number;
153
- components: Record<string, number | null>;
154
- componentWarnings: DiagnosticWarning[];
155
- metrics: Record<string, MetricEvaluation>;
156
- }
157
- export interface DiagnosticMetricTriangle {
158
- group: string;
159
- metricId: string;
160
- origins: string[];
161
- ages: number[];
162
- values: (number | null)[][];
163
- cells: (MetricEvaluation | null)[][];
164
- }
165
- export interface MetricDiagnosticsResult<TDimensions = unknown> {
166
- emergence: DiagnosticEmergencePoint<TDimensions>[];
167
- triangles: DiagnosticMetricTriangle[];
168
- latestDiagonal: DiagnosticEmergencePoint<TDimensions>[];
169
- }
170
- export interface RunMetricDiagnosticsInput<TDimensions = unknown> {
171
- losses: readonly DiagnosticLossRow<TDimensions>[];
172
- exposures?: readonly DiagnosticExposureRow<TDimensions>[];
173
- metrics: readonly MetricDefinition[];
174
- sparsePolicy?: SparseValuePolicy;
175
- filter?: DiagnosticsFilter;
176
- /** Maps source group keys to requested aggregate keys before summation. */
177
- groupMap?: Readonly<Record<string, string>>;
178
- /** Metadata for mapped/combined output groups. */
179
- groupDimensions?: Readonly<Record<string, TDimensions>>;
180
- }
181
- export interface DiagnosticExposureReconciliationFinding {
182
- code: "DUPLICATE_EXPOSURE_KEY" | "CONFLICTING_EXPOSURE";
183
- exposureKey: string;
184
- group: string;
185
- origin: string;
186
- message: string;
187
- }
188
- export interface ReconciledDiagnosticExposures<TDimensions = unknown> {
189
- exposures: DiagnosticExposureRow<TDimensions>[];
190
- findings: DiagnosticExposureReconciliationFinding[];
191
- }
192
- /**
193
- * Reconciles stable exposure keys before aggregation. Identical valuation
194
- * copies collapse to one row. Conflicting copies become one incomplete/null
195
- * row so exposure-based metrics fail closed rather than using a partial base.
196
- */
197
- export declare function reconcileDiagnosticExposureKeys<TDimensions = unknown>(rows: readonly DiagnosticExposureRow<TDimensions>[]): ReconciledDiagnosticExposures<TDimensions>;
198
- /**
199
- * Generic quarterly diagnostics engine. Loss rows aggregate by
200
- * group/origin/valuation; exposure rows aggregate once by stable exposure key;
201
- * every metric then divides the aggregated expressions exactly once.
202
- */
203
- export declare function runMetricDiagnostics<TDimensions = unknown>(input: RunMetricDiagnosticsInput<TDimensions>): MetricDiagnosticsResult<TDimensions>;
204
- export declare function metricTriangleFromEmergence(points: readonly DiagnosticEmergencePoint<unknown>[], metricId: string): DiagnosticMetricTriangle;
205
- export declare function sameMaturity<TDimensions>(result: MetricDiagnosticsResult<TDimensions>, ageMonths: number, groups?: readonly string[]): DiagnosticEmergencePoint<TDimensions>[];
206
- export interface CommonMaturityResult<TDimensions = unknown> {
207
- ageMonths: number | null;
208
- points: DiagnosticEmergencePoint<TDimensions>[];
209
- }
210
- /** Latest maturity present in every selected group; selection is caller-defined. */
211
- export declare function commonMaturity<TDimensions>(result: MetricDiagnosticsResult<TDimensions>, groups: readonly string[]): CommonMaturityResult<TDimensions>;
212
- //# sourceMappingURL=metricDiagnostics.d.ts.map