@actuarial-ts/compliance 0.5.0 → 0.6.1

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/src/errors.ts ADDED
@@ -0,0 +1,12 @@
1
+ export const COMPLIANCE_ERROR_CODES = [
2
+ "BAD_BUNDLE", "BAD_CDF", "MISSING_RATIONALE", "BAD_DIAGNOSTIC_RUN",
3
+ "DIAGNOSTIC_MISMATCH", "CRYPTO_UNAVAILABLE",
4
+ ] as const;
5
+ export type ComplianceErrorCode = (typeof COMPLIANCE_ERROR_CODES)[number];
6
+ export class ComplianceError extends Error {
7
+ readonly code: ComplianceErrorCode;
8
+ readonly path?: string;
9
+ constructor(code: ComplianceErrorCode, message: string, path?: string) {
10
+ super(message);this.name="ComplianceError";this.code=code;if(path!==undefined)this.path=path;
11
+ }
12
+ }
package/src/index.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  export * from "./metadata.js";
2
+ export * from "./errors.js";
3
+ export * from "./version.js";
2
4
  export * from "./ledger.js";
3
5
  export * from "./modelCards.js";
4
6
  export * from "./disclosure.js";
5
- export * from "./diagnostics.js";
7
+ export * from "./diagnosticRun.js";
6
8
  export * from "./bundle.js";
7
9
  export * from "./ave.js";
package/src/ledger.ts CHANGED
@@ -30,7 +30,13 @@ import { canonicalJson, ComplianceError } from "./bundle.js";
30
30
  export type AssumptionActor = "default" | "actuary" | "agent";
31
31
 
32
32
  /** JSON-representable assumption value (the ledger stores data, not behavior). */
33
- export type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
33
+ export type JsonValue =
34
+ | string
35
+ | number
36
+ | boolean
37
+ | null
38
+ | JsonValue[]
39
+ | { [key: string]: JsonValue };
34
40
 
35
41
  export interface AssumptionEntry {
36
42
  /** 1-based position in the ledger; assigned by recordAssumption, never by the caller. */
@@ -58,7 +64,31 @@ export interface AssumptionLedger {
58
64
 
59
65
  /** An empty, frozen ledger. */
60
66
  export function createLedger(): AssumptionLedger {
61
- return Object.freeze({ entries: Object.freeze([]) as readonly AssumptionEntry[] });
67
+ return Object.freeze({
68
+ entries: Object.freeze([]) as readonly AssumptionEntry[],
69
+ });
70
+ }
71
+
72
+ function snapshotJson(value: JsonValue): JsonValue {
73
+ let cloned: JsonValue;
74
+ try {
75
+ cloned = JSON.parse(canonicalJson(value)) as JsonValue;
76
+ } catch {
77
+ throw new ComplianceError(
78
+ "BAD_BUNDLE",
79
+ "Assumption values must be finite, acyclic JSON data",
80
+ );
81
+ }
82
+ const freeze = (item: JsonValue): JsonValue => {
83
+ if (item === null || typeof item !== "object") return item;
84
+ if (Array.isArray(item)) {
85
+ for (const child of item) freeze(child);
86
+ } else {
87
+ for (const child of Object.values(item)) freeze(child);
88
+ }
89
+ return Object.freeze(item) as JsonValue;
90
+ };
91
+ return freeze(cloned);
62
92
  }
63
93
 
64
94
  /**
@@ -67,15 +97,30 @@ export function createLedger(): AssumptionLedger {
67
97
  * ComplianceError("MISSING_RATIONALE") when actor !== "default" and rationale
68
98
  * is missing or blank.
69
99
  */
70
- export function recordAssumption(ledger: AssumptionLedger, entry: NewAssumptionEntry): AssumptionLedger {
71
- if (entry.actor !== "default" && (entry.rationale === undefined || entry.rationale.trim() === "")) {
100
+ export function recordAssumption(
101
+ ledger: AssumptionLedger,
102
+ entry: NewAssumptionEntry,
103
+ ): AssumptionLedger {
104
+ if (
105
+ entry.actor !== "default" &&
106
+ (entry.rationale === undefined || entry.rationale.trim() === "")
107
+ ) {
72
108
  throw new ComplianceError(
73
109
  "MISSING_RATIONALE",
74
110
  `assumption "${entry.field}" set by actor "${entry.actor}" requires a rationale; only actor "default" may omit one`,
75
111
  );
76
112
  }
77
- const recorded: AssumptionEntry = Object.freeze({ ...entry, seq: ledger.entries.length + 1 });
78
- return Object.freeze({ entries: Object.freeze([...ledger.entries, recorded]) });
113
+ const recorded: AssumptionEntry = Object.freeze({
114
+ ...entry,
115
+ value: snapshotJson(entry.value),
116
+ ...(entry.previousValue === undefined
117
+ ? {}
118
+ : { previousValue: snapshotJson(entry.previousValue) }),
119
+ seq: ledger.entries.length + 1,
120
+ });
121
+ return Object.freeze({
122
+ entries: Object.freeze([...ledger.entries, recorded]),
123
+ });
79
124
  }
80
125
 
81
126
  /** Entries that represent judgment (actor !== "default"), in ledger order. */
@@ -120,7 +165,9 @@ export function changedAssumptions(
120
165
  const added: string[] = [];
121
166
  const removed: string[] = [];
122
167
  const changed: AssumptionValueChange[] = [];
123
- const fields = [...new Set([...priorLatest.keys(), ...currentLatest.keys()])].sort();
168
+ const fields = [
169
+ ...new Set([...priorLatest.keys(), ...currentLatest.keys()]),
170
+ ].sort();
124
171
  for (const field of fields) {
125
172
  const before = priorLatest.get(field);
126
173
  const after = currentLatest.get(field);
@@ -133,7 +180,11 @@ export function changedAssumptions(
133
180
  continue;
134
181
  }
135
182
  if (canonicalJson(before.value) !== canonicalJson(after.value)) {
136
- changed.push({ field, priorValue: before.value, currentValue: after.value });
183
+ changed.push({
184
+ field,
185
+ priorValue: before.value,
186
+ currentValue: after.value,
187
+ });
137
188
  }
138
189
  }
139
190
  return { added, removed, changed };
package/src/metadata.ts CHANGED
@@ -92,7 +92,20 @@ function isIsoDate(value: string): boolean {
92
92
  const day = Number(match[3]);
93
93
  if (month < 1 || month > 12) return false;
94
94
  const leap = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
95
- const daysInMonth = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1]!;
95
+ const daysInMonth = [
96
+ 31,
97
+ leap ? 29 : 28,
98
+ 31,
99
+ 30,
100
+ 31,
101
+ 30,
102
+ 31,
103
+ 31,
104
+ 30,
105
+ 31,
106
+ 30,
107
+ 31,
108
+ ][month - 1]!;
96
109
  return day >= 1 && day <= daysInMonth;
97
110
  }
98
111
 
@@ -100,13 +113,20 @@ function isNonEmptyString(value: unknown): value is string {
100
113
  return typeof value === "string" && value.trim() !== "";
101
114
  }
102
115
 
103
- function checkDate(problems: string[], name: string, value: unknown, required: boolean): void {
116
+ function checkDate(
117
+ problems: string[],
118
+ name: string,
119
+ value: unknown,
120
+ required: boolean,
121
+ ): void {
104
122
  if (value === undefined) {
105
123
  if (required) problems.push(`${name} is required (ISO yyyy-mm-dd)`);
106
124
  return;
107
125
  }
108
126
  if (typeof value !== "string" || !isIsoDate(value)) {
109
- problems.push(`${name} must be a valid ISO date (yyyy-mm-dd); got ${JSON.stringify(value)}`);
127
+ problems.push(
128
+ `${name} must be a valid ISO date (yyyy-mm-dd); got ${JSON.stringify(value)}`,
129
+ );
110
130
  }
111
131
  }
112
132
 
@@ -123,7 +143,9 @@ export function validateMetadata(metadata: EstimateMetadata): string[] {
123
143
 
124
144
  if (metadata.intendedUsers !== undefined) {
125
145
  if (!Array.isArray(metadata.intendedUsers)) {
126
- problems.push("intendedUsers, when provided, must be an array of non-empty strings");
146
+ problems.push(
147
+ "intendedUsers, when provided, must be an array of non-empty strings",
148
+ );
127
149
  } else {
128
150
  metadata.intendedUsers.forEach((user, index) => {
129
151
  if (!isNonEmptyString(user)) {
@@ -145,8 +167,15 @@ export function validateMetadata(metadata: EstimateMetadata): string[] {
145
167
  if (measure.kind === "specified-percentile") {
146
168
  const p = measure.percentile;
147
169
  if (p === undefined) {
148
- problems.push('intendedMeasure.percentile is required when kind is "specified-percentile"');
149
- } else if (typeof p !== "number" || !Number.isFinite(p) || p <= 0 || p >= 1) {
170
+ problems.push(
171
+ 'intendedMeasure.percentile is required when kind is "specified-percentile"',
172
+ );
173
+ } else if (
174
+ typeof p !== "number" ||
175
+ !Number.isFinite(p) ||
176
+ p <= 0 ||
177
+ p >= 1
178
+ ) {
150
179
  problems.push(
151
180
  `intendedMeasure.percentile must be a fraction strictly between 0 and 1 (e.g. 0.75); got ${JSON.stringify(p)}`,
152
181
  );
@@ -177,8 +206,20 @@ export function validateMetadata(metadata: EstimateMetadata): string[] {
177
206
  checkDate(problems, "accountingDate", metadata.accountingDate, true);
178
207
  checkDate(problems, "valuationDate", metadata.valuationDate, true);
179
208
  checkDate(problems, "reviewDate", metadata.reviewDate, false);
209
+ if (
210
+ typeof metadata.reviewDate === "string" &&
211
+ isIsoDate(metadata.reviewDate) &&
212
+ typeof metadata.valuationDate === "string" &&
213
+ isIsoDate(metadata.valuationDate) &&
214
+ metadata.reviewDate < metadata.valuationDate
215
+ ) {
216
+ problems.push("reviewDate cannot precede valuationDate");
217
+ }
180
218
 
181
- if (metadata.scopeNotes !== undefined && !isNonEmptyString(metadata.scopeNotes)) {
219
+ if (
220
+ metadata.scopeNotes !== undefined &&
221
+ !isNonEmptyString(metadata.scopeNotes)
222
+ ) {
182
223
  problems.push("scopeNotes, when provided, must be a non-empty string");
183
224
  }
184
225
  if (metadata.currency !== undefined && !isNonEmptyString(metadata.currency)) {
package/src/version.ts ADDED
@@ -0,0 +1 @@
1
+ export const COMPLIANCE_PACKAGE_VERSION = "0.6.1";
@@ -1,75 +0,0 @@
1
- import type { AmountLayerDefinition, MeasureExpression, MetricDefinition, SparseValuePolicy } from "@actuarial-ts/core";
2
- /** Serializable metric metadata: executable warning callbacks are deliberately excluded. */
3
- export interface DiagnosticMetricProvenance {
4
- id: string;
5
- definitionVersion: string;
6
- displayName: string;
7
- unit: string;
8
- scale: number;
9
- numerator: MeasureExpression;
10
- denominator: MeasureExpression;
11
- numeratorLabel: string;
12
- denominatorLabel: string;
13
- basis: string;
14
- requiredComponents: string[];
15
- }
16
- export interface DiagnosticLayerProvenance {
17
- id: string;
18
- displayName: string;
19
- paidMeasure: string;
20
- incurredMeasure: string;
21
- paid: AmountLayerDefinition["paid"];
22
- incurred: AmountLayerDefinition["incurred"];
23
- basis: AmountLayerDefinition["basis"];
24
- }
25
- export interface CreateDiagnosticsProvenanceInput {
26
- packageVersions: Readonly<Record<string, string>>;
27
- formulaPack: {
28
- id: string;
29
- version: string;
30
- };
31
- metrics: readonly MetricDefinition[];
32
- layers?: readonly AmountLayerDefinition[];
33
- exposure: {
34
- basis: string;
35
- frequencyScale: number;
36
- };
37
- sparsePolicy: SparseValuePolicy;
38
- ageConvention: string;
39
- completePeriodCutoffs: Readonly<Record<string, string | number | boolean | null>>;
40
- appliedFilters?: Readonly<Record<string, unknown>>;
41
- groupingSelections?: Readonly<Record<string, unknown>>;
42
- inputReferences: readonly {
43
- id: string;
44
- hash?: string;
45
- }[];
46
- }
47
- /**
48
- * Audit metadata for a diagnostic run. This helper lives in compliance so
49
- * core numeric results stay deterministic and free of application filter or
50
- * persistence state. The returned record can be placed directly in
51
- * `createBundle(...).parameters` or an interchange `extensions` object.
52
- */
53
- export declare function createDiagnosticsProvenance(input: CreateDiagnosticsProvenanceInput): {
54
- packageVersions: Record<string, string>;
55
- formulaPack: {
56
- id: string;
57
- version: string;
58
- };
59
- metrics: DiagnosticMetricProvenance[];
60
- layers: DiagnosticLayerProvenance[];
61
- exposure: {
62
- basis: string;
63
- frequencyScale: number;
64
- };
65
- sparsePolicy: SparseValuePolicy;
66
- ageConvention: string;
67
- completePeriodCutoffs: Record<string, string | number | boolean | null>;
68
- appliedFilters?: Record<string, unknown>;
69
- groupingSelections?: Record<string, unknown>;
70
- inputReferences: {
71
- id: string;
72
- hash?: string;
73
- }[];
74
- };
75
- //# sourceMappingURL=diagnostics.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"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"}
@@ -1,44 +0,0 @@
1
- /**
2
- * Audit metadata for a diagnostic run. This helper lives in compliance so
3
- * core numeric results stay deterministic and free of application filter or
4
- * persistence state. The returned record can be placed directly in
5
- * `createBundle(...).parameters` or an interchange `extensions` object.
6
- */
7
- export function createDiagnosticsProvenance(input) {
8
- const metrics = input.metrics.map((metric) => ({
9
- id: metric.id,
10
- definitionVersion: metric.version,
11
- displayName: metric.displayName,
12
- unit: metric.unit,
13
- scale: metric.scale,
14
- numerator: metric.numerator,
15
- denominator: metric.denominator,
16
- numeratorLabel: metric.numeratorLabel,
17
- denominatorLabel: metric.denominatorLabel,
18
- basis: metric.basis,
19
- requiredComponents: [...metric.requiredComponents],
20
- }));
21
- const layers = (input.layers ?? []).map((layer) => ({
22
- id: layer.id,
23
- displayName: layer.displayName,
24
- paidMeasure: layer.paidMeasure,
25
- incurredMeasure: layer.incurredMeasure,
26
- paid: layer.paid,
27
- incurred: layer.incurred,
28
- basis: layer.basis,
29
- }));
30
- return {
31
- packageVersions: { ...input.packageVersions },
32
- formulaPack: { ...input.formulaPack },
33
- metrics,
34
- layers,
35
- exposure: { ...input.exposure },
36
- sparsePolicy: input.sparsePolicy,
37
- ageConvention: input.ageConvention,
38
- completePeriodCutoffs: { ...input.completePeriodCutoffs },
39
- ...(input.appliedFilters ? { appliedFilters: { ...input.appliedFilters } } : {}),
40
- ...(input.groupingSelections ? { groupingSelections: { ...input.groupingSelections } } : {}),
41
- inputReferences: input.inputReferences.map((reference) => ({ ...reference })),
42
- };
43
- }
44
- //# sourceMappingURL=diagnostics.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"AA8CA;;;;;GAKG;AACH,MAAM,UAAU,2BAA2B,CACzC,KAAuC;IAcvC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC7C,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,iBAAiB,EAAE,MAAM,CAAC,OAAO;QACjC,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,cAAc,EAAE,MAAM,CAAC,cAAc;QACrC,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;QACzC,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,kBAAkB,EAAE,CAAC,GAAG,MAAM,CAAC,kBAAkB,CAAC;KACnD,CAAC,CAAC,CAAC;IACJ,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAClD,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,KAAK,EAAE,KAAK,CAAC,KAAK;KACnB,CAAC,CAAC,CAAC;IACJ,OAAO;QACL,eAAe,EAAE,EAAE,GAAG,KAAK,CAAC,eAAe,EAAE;QAC7C,WAAW,EAAE,EAAE,GAAG,KAAK,CAAC,WAAW,EAAE;QACrC,OAAO;QACP,MAAM;QACN,QAAQ,EAAE,EAAE,GAAG,KAAK,CAAC,QAAQ,EAAE;QAC/B,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,qBAAqB,EAAE,EAAE,GAAG,KAAK,CAAC,qBAAqB,EAAE;QACzD,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,EAAE,GAAG,KAAK,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChF,GAAG,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,kBAAkB,EAAE,EAAE,GAAG,KAAK,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5F,eAAe,EAAE,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,SAAS,EAAE,CAAC,CAAC;KAC9E,CAAC;AACJ,CAAC"}
@@ -1,104 +0,0 @@
1
- import type {
2
- AmountLayerDefinition,
3
- MeasureExpression,
4
- MetricDefinition,
5
- SparseValuePolicy,
6
- } from "@actuarial-ts/core";
7
-
8
- /** Serializable metric metadata: executable warning callbacks are deliberately excluded. */
9
- export interface DiagnosticMetricProvenance {
10
- id: string;
11
- definitionVersion: string;
12
- displayName: string;
13
- unit: string;
14
- scale: number;
15
- numerator: MeasureExpression;
16
- denominator: MeasureExpression;
17
- numeratorLabel: string;
18
- denominatorLabel: string;
19
- basis: string;
20
- requiredComponents: string[];
21
- }
22
-
23
- export interface DiagnosticLayerProvenance {
24
- id: string;
25
- displayName: string;
26
- paidMeasure: string;
27
- incurredMeasure: string;
28
- paid: AmountLayerDefinition["paid"];
29
- incurred: AmountLayerDefinition["incurred"];
30
- basis: AmountLayerDefinition["basis"];
31
- }
32
-
33
- export interface CreateDiagnosticsProvenanceInput {
34
- packageVersions: Readonly<Record<string, string>>;
35
- formulaPack: { id: string; version: string };
36
- metrics: readonly MetricDefinition[];
37
- layers?: readonly AmountLayerDefinition[];
38
- exposure: { basis: string; frequencyScale: number };
39
- sparsePolicy: SparseValuePolicy;
40
- ageConvention: string;
41
- completePeriodCutoffs: Readonly<Record<string, string | number | boolean | null>>;
42
- appliedFilters?: Readonly<Record<string, unknown>>;
43
- groupingSelections?: Readonly<Record<string, unknown>>;
44
- inputReferences: readonly { id: string; hash?: string }[];
45
- }
46
-
47
- /**
48
- * Audit metadata for a diagnostic run. This helper lives in compliance so
49
- * core numeric results stay deterministic and free of application filter or
50
- * persistence state. The returned record can be placed directly in
51
- * `createBundle(...).parameters` or an interchange `extensions` object.
52
- */
53
- export function createDiagnosticsProvenance(
54
- input: CreateDiagnosticsProvenanceInput,
55
- ): {
56
- packageVersions: Record<string, string>;
57
- formulaPack: { id: string; version: string };
58
- metrics: DiagnosticMetricProvenance[];
59
- layers: DiagnosticLayerProvenance[];
60
- exposure: { basis: string; frequencyScale: number };
61
- sparsePolicy: SparseValuePolicy;
62
- ageConvention: string;
63
- completePeriodCutoffs: Record<string, string | number | boolean | null>;
64
- appliedFilters?: Record<string, unknown>;
65
- groupingSelections?: Record<string, unknown>;
66
- inputReferences: { id: string; hash?: string }[];
67
- } {
68
- const metrics = input.metrics.map((metric) => ({
69
- id: metric.id,
70
- definitionVersion: metric.version,
71
- displayName: metric.displayName,
72
- unit: metric.unit,
73
- scale: metric.scale,
74
- numerator: metric.numerator,
75
- denominator: metric.denominator,
76
- numeratorLabel: metric.numeratorLabel,
77
- denominatorLabel: metric.denominatorLabel,
78
- basis: metric.basis,
79
- requiredComponents: [...metric.requiredComponents],
80
- }));
81
- const layers = (input.layers ?? []).map((layer) => ({
82
- id: layer.id,
83
- displayName: layer.displayName,
84
- paidMeasure: layer.paidMeasure,
85
- incurredMeasure: layer.incurredMeasure,
86
- paid: layer.paid,
87
- incurred: layer.incurred,
88
- basis: layer.basis,
89
- }));
90
- return {
91
- packageVersions: { ...input.packageVersions },
92
- formulaPack: { ...input.formulaPack },
93
- metrics,
94
- layers,
95
- exposure: { ...input.exposure },
96
- sparsePolicy: input.sparsePolicy,
97
- ageConvention: input.ageConvention,
98
- completePeriodCutoffs: { ...input.completePeriodCutoffs },
99
- ...(input.appliedFilters ? { appliedFilters: { ...input.appliedFilters } } : {}),
100
- ...(input.groupingSelections ? { groupingSelections: { ...input.groupingSelections } } : {}),
101
- inputReferences: input.inputReferences.map((reference) => ({ ...reference })),
102
- };
103
- }
104
-