import { DEVELOPMENT_INPUT_CONTRACT_VERSION, DEFAULT_AVERAGES, type AnalysisReviewFinding, type NormalizedDevelopmentInput, type AnalysisCapability } from "@actuarial-ts/core"; import type { DataOperation } from "./dataCapabilities.js"; import * as contracts from "./nativeDevelopmentContracts.js"; const finding = (code: string, message: string, coordinates: AnalysisReviewFinding["coordinates"]): AnalysisReviewFinding => ({ code, path: "$", message, coordinates, remedies: ["Inspect the native coordinates, declared source convention and missingness before downstream use"] }); function cells(values: readonly (readonly (number | null)[])[], origins: readonly string[], ages: readonly number[], path: string): AnalysisReviewFinding[] { return values.flatMap((row, i) => row.flatMap((value, j) => value === null ? [finding("DEVELOPMENT_VALUE_UNAVAILABLE", "The native temporal value is unavailable; no missing amount is replaced by zero", { origin: origins[i]!, ageMonths: ages[j]!, nativeOutputPath: `${path}[${i}][${j}]` })] : [])); } function normalized(result: NormalizedDevelopmentInput, path = "$.values"): AnalysisReviewFinding[] { return [...cells(result.values, result.origins, result.ages, path), ...(result.ages.length ? [] : [finding("NO_POSITIVE_AGE_OBSERVATIONS", "Only the structural zero boundary exists; no fitting observations are supplied", { nativeOutputPath: path })])]; } const explicit = (path: string, choices: readonly string[], description: string) => ({ path, choices, description, default: null }); const common = { kind: "transformation" as const, contract: DEVELOPMENT_INPUT_CONTRACT_VERSION, limits: contracts.NATIVE_DEVELOPMENT_INPUT_LIMITS, work: "temporal-projection" as const, checked: ["safe-json-and-native-schema", "bounded-input-and-output-expansion", "native-temporal-calculation", "mandatory-structural-origin-zero", "native-value-availability"], deferred: ["source-authenticity", "unit-and-financial-basis-declaration", "population-compatibility", "historical-information-availability", "downstream-model-eligibility"], coordinates: ["origin", "ageMonths"], originKinds: ["caller-declared-origin-labels"], temporalStructures: ["ordered-month-ages-including-irregular-intervals"], source: "caller-declared-development-grid", replay: [], unitRule: "Amounts retain their supplied numerical scale; bare grids do not establish actual source units or convert them", populationRule: "Origin labels identify rows; they do not prove underlying population or financial-basis compatibility", financialRule: "No financial adjustment is inferred; use a bound review source for typed basis and population evidence", options: [] as AnalysisCapability["options"], }; const bare = "Native content identities do not authenticate original sources or provide an immutable review replay receipt"; const dated = { coordinates: ["origin", "ageMonths", "valuationDate"], originKinds: ["explicit-period-boundaries"], temporalStructures: ["calendar-period-end", "elapsed-months"] }; const factors = { ...common, contract: null, kind: "statistic" as const, work: "statistic-calculation" as const, limits: contracts.NATIVE_FACTOR_INPUT_LIMITS, checked: ["safe-json-and-native-schema", "positive-age-factor-geometry", "bounded-native-statistic-calculation", "finite-or-unavailable-output"], deferred: ["source-authenticity", "population-and-financial-basis-compatibility", "actual-cadence-and-origin-order", "predictive-uncertainty", "actuarial-suitability"], coordinates: ["originIndex", "fromAgeMonths", "toAgeMonths", "averageKey"], unitRule: "Factors and coefficient of variation are dimensionless; the caller must establish a consistent source amount basis", }; export function nativeDevelopmentCapabilities(): readonly DataOperation[] { return [ { ...common, id: "development.normalize", native: "normalizeDevelopmentInput", entry: "normalizeDevelopmentFromInput", parser: "parseDevelopmentNormalizationRequest", schemaName: "developmentNormalizationRequestSchema", schema: contracts.developmentNormalizationRequestSchema, input: "DevelopmentGridInput", output: "NormalizedDevelopmentInput", fields: ["kind", "origins", "ages", "values"], limitations: ["True origin age zero is always zero; explicit nonzero source values require a declared age remapping or correction", "Positive-age gaps remain missing, and a row labeled 0 is an ordinary origin", bare], evaluate(input) { return { findings: normalized(contracts.normalizeDevelopmentFromInput(input)) }; } }, { ...common, id: "development.age-remapping", native: "remapDevelopmentAges", entry: "remapDevelopmentAgesFromInput", parser: "parseDevelopmentRemappingRequest", schemaName: "developmentRemappingRequestSchema", schema: contracts.developmentRemappingRequestSchema, input: "{ source: DevelopmentGridInput; mapping: DevelopmentAgeMapping }", output: "DevelopmentAgeRemapping", fields: ["source", "mapping"], options: [explicit("mapping.sourceConvention", ["period-end-index", "different-time-anchor"], "Every source label needs an explicit target month age and rationale")], limitations: ["Maps declared source age labels before enforcing true origin zero; no cadence, interpolation or annualization is inferred", bare], evaluate(input) { return { findings: normalized(contracts.remapDevelopmentAgesFromInput(input).result, "$.result.values") }; } }, { ...common, id: "development.intervals", native: "developmentIntervalView", entry: "developmentIntervalsFromInput", parser: "parseDevelopmentIntervalsRequest", schemaName: "developmentIntervalsRequestSchema", schema: contracts.developmentIntervalsRequestSchema, input: "{ development: NormalizedDevelopmentInput; opening?: DevelopmentOpening }", output: "DevelopmentIntervalView", fields: ["development"], options: [{ path: "opening", choices: [], default: { kind: "structural-origin-zero" }, description: "The native default is the mandatory zero boundary; a later opening requires dated per-origin balances and rationale" }, explicit("opening.kind", ["structural-origin-zero", "declared-opening"], "Explicit opening objects require their declared kind")], limitations: ["Differencing resumes after a gap when two adjacent cumulative observations exist; negative increments remain negative", "A later opening does not change the mandatory cumulative zero at origin", bare], evaluate(input) { const value = contracts.developmentIntervalsFromInput(input); return { findings: cells(value.values, value.origins, value.intervals.map((interval) => interval.throughAgeMonths), "$.values") }; } }, { ...common, id: "development.interval-accumulation", native: "accumulateDevelopmentIntervals", entry: "accumulateDevelopmentIntervalsFromInput", parser: "parseDevelopmentIntervalAccumulationRequest", schemaName: "developmentIntervalAccumulationRequestSchema", schema: contracts.developmentIntervalAccumulationRequestSchema, input: "DevelopmentIntervalView", output: "NormalizedDevelopmentInput", fields: ["kind", "measure", "origins", "intervals", "values", "opening", "sourceIdentity"], options: [explicit("opening.kind", ["structural-origin-zero", "declared-opening"], "Contiguous intervals must begin at the declared opening")], limitations: ["Accumulation stops at the first missing increment or missing opening; later increments cannot invent a cumulative base", "sourceIdentity is retained input evidence, not proof of an original cumulative source or lossless round-trip", bare], evaluate(input) { return { findings: normalized(contracts.accumulateDevelopmentIntervalsFromInput(input)) }; } }, { ...common, ...dated, id: "development.valuation", native: "truncateDevelopmentAtValuation", entry: "truncateDevelopmentFromInput", parser: "parseDevelopmentValuationRequest", schemaName: "developmentValuationRequestSchema", schema: contracts.developmentValuationRequestSchema, input: "{ source: DevelopmentCalendarInput; cutoff: string }", output: "DevelopmentValuationTruncation", fields: ["source", "cutoff"], limitations: ["Masks values strictly after the explicit cutoff while retaining dated coordinates, exclusions and future-origin rows", "A valuation cutoff alone cannot reconstruct what was known historically; use the historical archive workflow", bare], evaluate(input) { const value = contracts.truncateDevelopmentFromInput(input); return { findings: [...normalized(value.result.development, "$.result.development.values"), ...value.originsNotStarted.map((origin) => finding("ORIGIN_NOT_STARTED", "Origin starts after the truncation cutoff; its structural zero is separate from future losses", { origin, valuationDate: value.cutoff })), ...(value.excludedCells.length ? [finding("DEVELOPMENT_CELLS_TRUNCATED", "Dated coordinates after cutoff are excluded; inspect the retained native exclusions", { excludedCells: value.excludedCells.length, valuationDate: value.cutoff })] : [])] }; } }, { ...common, ...dated, id: "development.calendar-view", native: "calendarDevelopmentView", entry: "calendarDevelopmentFromInput", parser: "parseDevelopmentCalendarViewRequest", schemaName: "developmentCalendarViewRequestSchema", schema: contracts.developmentCalendarViewRequestSchema, input: "DevelopmentCalendarInput", output: "readonly DevelopmentCalendarCell[]", fields: ["development", "origins", "ageConvention", "valuations"], options: [explicit("ageConvention", ["calendar-period-end", "elapsed-months"], "Each real supplied valuation must match its origin and age under the selected clock")], limitations: ["Separates dated missing observations from structurally unavailable cells; the native view lists positive-age coordinates", bare], evaluate(input) { return { findings: contracts.calendarDevelopmentFromInput(input).flatMap((cell, index) => cell.status === "observed" ? [] : [finding(cell.status === "missing" ? "DEVELOPMENT_VALUE_UNAVAILABLE" : "DEVELOPMENT_STRUCTURALLY_UNAVAILABLE", "Native calendar status remains explicit", { origin: cell.origin, ageMonths: cell.ageMonths, valuationDate: cell.valuationDate, nativeOutputPath: `$[${index}]` })]) }; } }, { ...common, id: "development.same-maturity", native: "sameMaturityDevelopment", entry: "sameMaturityDevelopmentFromInput", parser: "parseDevelopmentMaturityRequest", schemaName: "developmentMaturityRequestSchema", schema: contracts.developmentMaturityRequestSchema, input: "{ development: NormalizedDevelopmentInput; ageMonths: number }", output: "ReturnType", fields: ["development", "ageMonths"], limitations: ["Requires an exact observed age; no interpolation or latest-observation substitution. Age zero returns zero for every declared origin", bare], evaluate(input) { return { findings: contracts.sameMaturityDevelopmentFromInput(input).flatMap((cell, index) => cell.status === "available" ? [] : [finding(cell.reason!, "Requested native maturity is unavailable", { origin: cell.origin, ageMonths: cell.ageMonths, nativeOutputPath: `$[${index}]` })]) }; } }, { ...common, ...dated, id: "development.aggregate", native: "aggregateDevelopmentOrigins", entry: "aggregateDevelopmentFromInput", parser: "parseDevelopmentAggregationRequest", schemaName: "developmentAggregationRequestSchema", schema: contracts.developmentAggregationRequestSchema, input: "Parameters[0]", output: "DevelopmentOriginAggregation", fields: ["source", "quantity", "targets", "partialPeriodPolicy"], unitRule: "Requires declared additive cumulative quantity semantics; no currency/scale conversion is performed and the bare source does not prove its unit declaration", options: [explicit("partialPeriodPolicy", ["reject", "retain-partial"], "Retaining partial cohorts never invents missing members or annualizes their amounts")], limitations: ["Aggregates disjoint declared origin periods at common actual valuations; it does not sum mismatched age columns", "Population/basis compatibility within a bare source remains the caller's responsibility; use bound review sources for richer semantic evidence", bare], evaluate(input) { const value = contracts.aggregateDevelopmentFromInput(input); return { findings: [...value.findings, ...normalized(value.result.development, "$.result.development.values"), ...value.excludedOrigins.map((origin) => finding("DEVELOPMENT_ORIGIN_EXCLUDED", "Source origin is outside the explicit aggregation targets", { origin }))] }; } }, { ...factors, id: "development.factors", native: "computeDevelopmentFactors", entry: "computeDevelopmentFactorsFromInput", parser: "parseDevelopmentFactorsRequest", schemaName: "developmentFactorsRequestSchema", schema: contracts.developmentFactorsRequestSchema, input: "{ triangle: Triangle; averageSpecs?: AverageSpec[] }", output: "DevelopmentFactors", fields: ["triangle"], options: [{ path: "averageSpecs", choices: [], default: DEFAULT_AVERAGES.map((spec) => ({ ...spec })), description: "The native menu is used only when omitted; an empty array explicitly requests no averages" }, explicit("averageSpecs[].kind", ["straight", "weighted", "medial", "geometric"], "Each requested average declares its kernel"), explicit("averageSpecs[].years", [], "Optional positive integer counts origin ROWS ending at the last eligible row, not elapsed calendar years; omission uses all eligible rows")], limitations: ["Volume-weighted averages use paired sum/sum with positive denominators; structural zero must be removed before the native positive-age triangle boundary", "Window labels retain legacy wording: a five-row window can span five quarters or irregular periods. No calendar duration is inferred", "Medial falls back to straight with fewer than three factors; geometric requires all included factors positive; unavailable results remain null", bare], evaluate(input) { const value = contracts.computeDevelopmentFactorsFromInput(input); return { findings: [...value.individual.flatMap((row, i) => row.flatMap((factor, j) => factor === null ? [finding("FACTOR_UNAVAILABLE", "A factor requires both amounts and a positive denominator", { originIndex: i, fromAgeMonths: value.fromAges[j]!, toAgeMonths: value.toAges[j]!, nativeOutputPath: `$.individual[${i}][${j}]` })] : [])), ...value.averages.flatMap((row, i) => row.values.flatMap((factor, j) => factor === null ? [finding("FACTOR_AVERAGE_UNAVAILABLE", "The native average has no usable factors under its selected convention", { averageKey: row.spec.key, fromAgeMonths: value.fromAges[j]!, toAgeMonths: value.toAges[j]!, nativeOutputPath: `$.averages[${i}].values[${j}]` })] : [])), ...(value.fromAges.length ? [] : [finding("NO_FACTOR_INTERVALS", "At least two positive ages are needed for an age-to-age interval", {})])] }; } }, { ...factors, id: "development.factor-volatility", native: "factorVolatility", entry: "factorVolatilityFromInput", parser: "parseDevelopmentFactorVolatilityRequest", schemaName: "developmentFactorVolatilityRequestSchema", schema: contracts.developmentFactorVolatilityRequestSchema, input: "DevelopmentFactors", output: "(number | null)[]", fields: ["fromAges", "toAges", "individual", "averages"], limitations: ["Native sample standard deviation divided by absolute mean requires at least two finite individual factors and nonzero mean", "Supplied factors are checked for shape, not recalculated from underlying losses; this descriptive statistic is not predictive uncertainty", bare], evaluate(input) { return { findings: contracts.factorVolatilityFromInput(input).flatMap((value, index) => value === null ? [finding("FACTOR_VOLATILITY_UNAVAILABLE", "Native factor dispersion requires two factors and a nonzero mean", { columnIndex: index, nativeOutputPath: `$[${index}]` })] : []) }; } }, ]; }