Library/Fundamental Analysis and Valuation/Sector-Specific Equity Scoring/Unsupported-Scope and Coverage Decision

D18-F10-A09 / Complete engineering topic

Unsupported-Scope and Coverage Decision

Create a deterministic coverage gate that checks sector support, framework identity, required fields, knowledge dates, and staleness before selecting a stable model ID.

Unsupported-Scope and Coverage Decision routes sector evidence into an auditable component and decision ledgerD18 / D18-F10

Create a deterministic coverage gate that checks sector support, framework identity, required fields, knowledge dates, and staleness before selecting a stable model ID.

Unsupported-Scope and Coverage Decision route, calculation, and decision map

The decision this tutorial makes visible

A mathematically valid score can still be unusable when the issuer is out of scope, facts are missing, frameworks conflict, or evidence was unavailable at the decision time.

The precise question is: How should the system decide supported, partial-review, or abstain before a sector score enters the integrated scoring router?

A practitioner needs to know what the diagnostic does and does not justify. A builder needs a contract that can be reproduced from the same point-in-time inputs in Python, TypeScript, a visual, and a browser lab.

Intuition before notation

This is the circuit breaker for the family: it decides whether a model may run before any headline score can create false confidence.

The result depends on the declared algorithm scope, input clocks, units, equality and rounding policies, and unsupported-state treatment. Change one of those and the output represents a different decision even when its field name is unchanged.

Scope and nearby methods

The router supports seven declared sector contracts. Future evidence always abstains; complete current evidence is supported; incomplete current evidence may be partial-review only when coverage meets the declared threshold.

VariantDefinitionBest useMain limitation
Selected hard gate with partial reviewExplicit supported/partial/abstain statesAuditable integrated routingCoverage is not data quality in full
Always-score imputationFill missing values and continueHigh-throughput experimentationCan create unjustified precision
Binary supported/unsupportedNo partial stateStrict production controlsLoses useful diagnostic middle ground

What is sourced, selected, synthetic, and derived

RoleMaterial claimEvidenceBoundary
sourced contextSector frameworks define different measures, scopes, and reporting clocks.Basel, EIOPA, Nareit, FERC, SEC, and IFRS sourcesNone prescribe this cross-sector router.
package choiceSector map, required fields, age limit, coverage threshold, partial state, and precedence order are versioned repository policy.Repository contractThey require governance for production.
synthetic inputThe canonical fact ledger represents no issuer.canonical-input.jsonDates and presence flags are fabricated.
derived outputThe downstream handoff envelope contains state, selected_model, coverage_ratio, missing_fields, stale_fields, future_fields, and reasons; the model ID follows deterministic precedence rules.Calculation ledger and D18-F09-A04 handoffSelection means contract eligibility, not investment suitability, and D18-F09-A04 must not reconstruct or override this family decision silently.

The authoritative sources support only the exact facts named in the claim ledger. They do not certify the synthetic numbers in this tutorial. The repository fixture is deliberately invented for auditability, and the displayed output is author-derived under the selected implementation choice.

Formula, symbols, and numerical policy

Plain text
coverage = count(required fields available, nonfuture, and nonstale) / count(required fields).
SymbolMeaningUnitPolicy
Kusable required-field countcountPresent, known by cutoff, and not stale
Nrequired-field countcountFrozen by sector model version
Ccoverage ratio0-1K/N; never infer availability from value alone
  • Use IEEE-754 binary64 arithmetic without intermediate rounding.
  • Treat percentages as decimal fractions and label percentage-point changes explicitly.
  • Clamp only declared component transforms to [0,100]; never clamp raw regulatory or accounting inputs silently.
  • Round only for presentation after the full component, penalty, and coverage ledger is stored.

Read the formula in the same order as the algorithm. Validate identity, ordering, units, and supported state first. Apply the selected equality and window rules second. Calculate with unrounded numeric values. Round only at the declared presentation boundary, and preserve null as a diagnostic rather than coercing it to zero.

Build the algorithm

  1. Resolve the declared sector and candidate framework before reading values.
  2. Load the versioned required-field list and one unique fact-ledger row per field.
  3. Classify missing, future, stale, and current evidence at the cutoff.
  4. Compute coverage from current usable required fields only.
  5. Apply precedence: unsupported/framework mismatch, future evidence, complete support, partial threshold, then abstain.
  6. Pass state, stable model ID, coverage, and reasons to D18-F09-A04.

Production-minded operational checklist

  1. Resolve sector and framework before calculating.
  2. Freeze scoring and knowledge-cutoff timestamps.
  3. Reconcile each input to its filing, regulator, provider, and reporting perimeter.
  4. Inspect component scores, penalties, and coverage before the headline.
  5. Retain abstention and unsupported-scope reasons for the integrated router.

The checklist is intentionally strict: an explicit rejection is safer than a plausible output built from stale, malformed, or unsupported state.

Worked synthetic example

The canonical fixture is synthetic teaching data, not an observed control event, customer order, or broker execution. Its primary author-derived output, coverage_ratio, is 1.0. The complete input and output are in datasets/canonical-input.json and datasets/expected-output.json.

All eight canonical bank facts were known before the cutoff and fall within the age policy, so coverage is complete and the router selects D18-F10-A01 with a supported state and an explicit reason ledger.

Counterfactual checkpoint

Move one knowledge date beyond the cutoff. Hold every fact present and current-period, but move one knowledge date after as_of. The output changes because Coverage arithmetic may remain visually high, yet temporal integrity forces abstention and clears the selected model.

The structured result retains state and diagnostics in addition to the primary number. That makes the calculation independently reviewable and prevents a partial, null, rejected, or venue-bounded outcome from being mistaken for an unqualified value.

Boundary and counterexample workbook

The playground computes every scenario at 61 deterministic parameter states. The table uses the declared focus step and states whether that focus reproduces the canonical fixture. The full state ledger and compressed transition segments are in datasets/scenario-results.json.

ScenarioReview focusPurposeStatePrimary outputDiagnosticDecision segments
Canonical driver sweepStep 30 · canonical fixtureMove one primary causal driver through the canonical midpoint.supportedcoverage 100%supported · complete-current-coverage1
Adverse operating stressStep 30 · comparison focusApply a controlled operating or earnings stress while retaining the framework.abstaincoverage 50%abstain · missing-required-fields3
Balance-sheet resilienceStep 30 · canonical fixtureVary a funding, leverage, capital, or liquidity channel.supportedcoverage 100%supported · complete-current-coverage2
Boundary crossingStep 30 · canonical fixtureCross a declared review boundary and inspect equality behavior.supportedcoverage 100%supported · complete-current-coverage2
Evidence-quality stressStep 30 · canonical fixtureChange evidence usability or freshness without hiding the diagnostic.supportedcoverage 100%supported · complete-current-coverage2
Concentration or mixStep 30 · canonical fixtureVary concentration, mix, or component balance.supportedcoverage 100%supported · complete-current-coverage2
Recovery pathStep 30 · canonical fixtureTrace a recovery from an adverse state toward a resilient state.supportedcoverage 100%supported · complete-current-coverage2

These rows are not backtest observations. They are controlled counterexamples that expose how one driver changes the state, output, or reason code while the rest of the contract stays fixed.

Visualize the boundary

Unsupported-Scope and Coverage Decision annotated teaching map

Open this SVG at full size, or use the guided playground to compare the seven topic-specific canonical, boundary, policy, and failure scenarios.

The Mermaid flow answers where the selected calculation sits in the processing sequence. The SVG keeps the formula, output, decision boundary, and invariant visible together. The lab lets the reader step through the same structured states without changing the underlying definition.

Implementation walkthrough

The Python and TypeScript references begin with the same validation contract, reject malformed and unsupported state before calculation, preserve declared ordering and rounding policies, and return structured diagnostics rather than one context-free number.

The main implementation branches are:

  • Sector is unsupported — Abstain with no model ID, because No family contract matches the issuer.
  • Framework mismatches the route — Abstain with no model ID, because Ratios cannot be silently translated.
  • Any fact is future-dated — Abstain, because Point-in-time integrity dominates coverage.
  • Coverage equals 1 and facts are current — Supported with stable model ID, because The declared minimum contract is complete.
  • Coverage is incomplete but at least the threshold — Partial-review with model ID, because A human can inspect the named gaps.
  • Coverage is below threshold — Abstain, because Too little evidence remains.

Neither reference silently fetches data, mutates caller-owned inputs outside the declared engine behavior, guesses hidden state, or substitutes a provider default. Shared JSON fixtures make value, null, state, and reason-code drift visible across languages.

Testing and validation

Definition tests compare every canonical field, reject malformed state, and exercise the material boundary. Family validation recomputes every playground state from the reference function. Independent arithmetic is recorded beside the fixture rather than inferred only from implementation output.

The audit must preserve these invariants:

  • framework and policy version
  • point-in-time normalized input ledger
  • component transform thresholds and scores
  • weight vector and penalties
  • coverage, state, reason, and final output

Passing the suite proves the selected package contract and Python/TypeScript parity; it does not prove empirical usefulness for a live issuer universe.

Failure modes and misuse

  • The 0-100 teaching bands and default weights are package choices, not regulations, ratings, or market standards.
  • Definition fidelity and code parity do not establish predictive validity, calibration, causality, fair value, or investment performance.
  • Accounting frameworks, prudential regimes, business mixes, and reporting perimeters can make apparently identical ratios incomparable.
  • A complete component ledger can still omit material qualitative, governance, legal, catastrophe, operational, or market risks.

Debugging order

When a result looks surprising, inspect the state in this order:

  1. Confirm framework and reporting perimeter
  2. Check units and knowledge dates
  3. Recalculate raw intermediates
  4. Inspect bounded component transforms
  5. Reconcile weights, penalties, coverage, and state

Evidence and historical boundary

Historical decision: not useful. A named Unsupported-Scope and Coverage Decision case would introduce issuer identity, filing and regulator scope, restatement, data-license, and scoring-policy questions without teaching the deterministic mechanism better than a controlled synthetic record.

The primary sources are Basel Framework, Solvency II Article 101, Nareit FFO 2018, FERC electric forms, SEC FRM Topic 9, IFRS 12. They support the source roles listed in the research ledger, not a redistributable historical observation, a private participant decision, production conformance certification, execution-quality result, profitability claim, or prediction claim.

Summary and next topic

You can now compute and audit Unsupported-Scope and Coverage Decision before passing its versioned output to the integrated scoring router. The learning flow is: Sector-Specific Weight Calibration → Unsupported-Scope and Coverage Decision → D18-F09-A04 Model Applicability and Variant Router. Carry the result forward only with its scope, clock, state, and evidence label.

Verified model contract — Level 1

  • Selected calculation: coverage = count(required fields available, nonfuture, and nonstale) / count(required fields).
  • Reproducibility boundary: the canonical fixture is synthetic; the stored input, intermediate ledger, output, and cross-language implementations define the executable example.
  • Interpretation ceiling: Definition fidelity and code parity do not establish predictive validity, calibration, causality, fair value, or investment performance.
  • Router handoff: send state, selected_model, coverage_ratio, missing_fields, stale_fields, future_fields, reasons to D18-F09-A04. Precedence is unsupported sector or framework mismatch → future evidence → complete current support → partial-review threshold → abstain below threshold. Preserve abstain and null selected_model; never reconstruct eligibility from the headline coverage ratio alone.

Level 2 learning layer

How to choose this model or a nearby method

MethodBest useAssumption that must holdMain limitation
Selected hard gate with partial reviewAuditable integrated routingRequired fields and dates are governedCoverage is not data quality in full
Always-score imputationHigh-throughput experimentationImputation is valid and labeledCan create unjustified precision
Binary supported/unsupportedStrict production controlsAll-or-nothing policy is acceptableLoses useful diagnostic middle ground

The selected package is appropriate only when its framework tag, reporting perimeter, unit policy, and point-in-time evidence contract are all satisfied. If a stop condition in the topic decision matrix fires, use the named review, reroute, or abstention state instead of forcing a score.

Compact glossary

SymbolMeaningUnitPolicy
Kusable required-field countcountPresent, known by cutoff, and not stale
Nrequired-field countcountFrozen by sector model version
Ccoverage ratio0-1K/N; never infer availability from value alone

Visual asset map

AssetLearning jobStatus
Article heroSee route → normalize → calculate → decide at a glanceReady
System mapConnect formula, boundary, invariant, counterfactual, and outputReady
Model anatomyFollow the four-stage calculation pipelineReady
Component ledgerReconstruct the headline from named dimensionsReady
Decision boundarySeparate a computable result from a permissible interpretationReady
Evidence clockPrevent point-in-time leakageReady
Mermaid flowInspect the text-native algorithm flowReady
Guided playgroundPredict, reveal, sweep 427 calculations, and explain state changesReady

Related concepts and continuation

  • Previous family topic: D18-F10-A08
  • Downstream integrated router: D18-F09-A04 — catalog-level continuation outside this family.

Deep visual atlas

Sector model anatomy

Canonical component ledger

Decision boundary and interpretation ceiling

Point-in-time evidence clock

The teaching sequence is route → normalize → calculate → decide. Use the component ledger to reconstruct the headline, the boundary map to stop unsupported interpretation, and the evidence clock to block hindsight.

Guided playground protocol

  1. Orient: identify the framework, active driver, and invariant.
  2. Predict: commit to decrease, stay flat, or increase before revealing the adjacent numeric result.
  3. Experiment: sweep all 61 computed states in each of seven scenarios and inspect discontinuities.
  4. Explain: reconcile missing/stale/future ledgers and model-selection precedence to the headline and apply this boundary: Unsupported sectors, framework mismatch, future evidence, or sub-threshold coverage must abstain.

Unsupported-Scope and Coverage Decision calculation flow

This flow identifies the selected calculation stages and the structured output.

Rendering system map…

Takeaway: A trustworthy router is valuable partly because it knows when not to score.

ReferencesPrimary sources and evidence notes

Expand the source trail, evidence role, and limitations behind the engineering choices.

S1 — Basel Framework

  • Organization or authors: Basel Committee on Banking Supervision
  • Source type: Official prudential standard
  • Publication or effective date: Current framework accessed 2026-08-08
  • Version: Current web framework
  • URL or DOI: https://www.bis.org/basel_framework/
  • Accessed: 2026-08-09
  • Jurisdiction: International Basel standard; national implementation may differ
  • Supports: CET1, leverage, LCR, NSFR, disclosure, and risk-based capital are distinct prudential measures.
  • Limitations: Does not define this package's 0-100 composite or teaching bands.

S2 — Calculation of the Solvency Capital Requirement

  • Organization or authors: European Parliament and Council via EIOPA Single Rulebook
  • Source type: Official prudential law
  • Publication or effective date: Directive 2009/138/EC; accessed 2026-08-08
  • Version: Article 101
  • URL or DOI: https://www.eiopa.europa.eu/rulebook/solvency-ii-single-rulebook/article-2188_en
  • Accessed: 2026-08-09
  • Jurisdiction: European Union Solvency II
  • Supports: SCR covers specified risks and is calibrated to a 99.5% one-year confidence level under the directive.
  • Limitations: Does not make a Solvency II ratio comparable with another regime or define this composite.

S3 — Nareit Funds From Operations White Paper — 2018 Restatement

  • Organization or authors: Nareit
  • Source type: Official industry methodology
  • Publication or effective date: December 2018; accessed 2026-08-08
  • Version: 2018 Restatement
  • URL or DOI: https://www.reit.com/sites/default/files/2018-FFO-white-paper-%2811-27-18%29.pdf
  • Accessed: 2026-08-09
  • Jurisdiction: U.S. equity REIT reporting
  • Supports: Nareit FFO adjusts GAAP net income for real-estate depreciation, specified sales gains/losses, and related impairments.
  • Limitations: AFFO is not standardized by this paper; the package labels its AFFO proxy explicitly.

S4 — Electric Industry Forms

  • Organization or authors: Federal Energy Regulatory Commission
  • Source type: Official reporting portal
  • Publication or effective date: Accessed 2026-08-08
  • Version: Current forms page
  • URL or DOI: https://www.ferc.gov/general-information-0/electric-industry-forms
  • Accessed: 2026-08-09
  • Jurisdiction: U.S. FERC-jurisdictional electric utilities
  • Supports: FERC Form 1 and 1-F are financial and operating reports used for regulation, oversight, and audits.
  • Limitations: Form applicability and filing status do not validate this package's score bands.

S5 — Financial Reporting Manual — Management's Discussion and Analysis

  • Organization or authors: U.S. Securities and Exchange Commission
  • Source type: Official staff manual
  • Publication or effective date: Updated through 2020; accessed 2026-08-08
  • Version: Topic 9
  • URL or DOI: https://www.sec.gov/about/divisions-offices/division-corporation-finance/financial-reporting-manual/frm-topic-9
  • Accessed: 2026-08-09
  • Jurisdiction: United States SEC reporting
  • Supports: Liquidity analysis addresses cash generation, cash requirements, commitments, uncertainties, and capital resources.
  • Limitations: The manual does not prescribe a runway score or forecast future financing.

S6 — IFRS 12 Disclosure of Interests in Other Entities

  • Organization or authors: International Accounting Standards Board
  • Source type: Official accounting standard overview
  • Publication or effective date: Accessed 2026-08-08
  • Version: Current standard page
  • URL or DOI: https://www.ifrs.org/issued-standards/list-of-standards/ifrs-12-disclosure-of-interests-in-other-entities/
  • Accessed: 2026-08-09
  • Jurisdiction: IFRS
  • Supports: IFRS 12 requires disclosures about interests in subsidiaries, joint arrangements, associates, and unconsolidated structured entities.
  • Limitations: Disclosures do not supply market values or remove look-through judgment.

Evidence boundary

Sources define sector measures, disclosure regimes, and methodological cautions. They do not endorse the repository's synthetic fixtures, weights, score bands, or investment use.

sector-scoring.ts
/** Deterministic TypeScript reference for D18-F10 sector-specific scoring. */

export type RecordValue = Record<string, any>;

const numberValue = (data: RecordValue, name: string): number => {
  const value = data[name];
  if (typeof value !== "number" || !Number.isFinite(value)) throw new TypeError(`${name} must be a finite number`);
  return value;
};
const nonnegative = (data: RecordValue, name: string): number => {
  const value = numberValue(data, name); if (value < 0) throw new RangeError(`${name} must be nonnegative`); return value;
};
const positive = (data: RecordValue, name: string): number => {
  const value = numberValue(data, name); if (value <= 0) throw new RangeError(`${name} must be positive`); return value;
};
const textValue = (data: RecordValue, name: string): string => {
  const value = data[name]; if (typeof value !== "string" || !value.trim()) throw new TypeError(`${name} must be a nonempty string`); return value.trim();
};
const dateValue = (value: unknown, name: string): Date => {
  if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value)) throw new TypeError(`${name} must be YYYY-MM-DD`);
  const result = new Date(`${value}T00:00:00Z`); if (!Number.isFinite(result.getTime()) || result.toISOString().slice(0, 10) !== value) throw new RangeError(`${name} must be YYYY-MM-DD`); return result;
};
const requireFramework = (data: RecordValue, expected: string): void => { if (textValue(data, "framework") !== expected) throw new RangeError(`framework must be ${expected}`); };
const clamp = (value: number, low = 0, high = 100): number => Math.min(high, Math.max(low, value));
const higher = (value: number, weak: number, strong: number): number => { if (strong <= weak) throw new RangeError("strong threshold must exceed weak threshold"); return clamp(100 * (value - weak) / (strong - weak)); };
const lower = (value: number, strong: number, weak: number): number => { if (weak <= strong) throw new RangeError("weak threshold must exceed strong threshold"); return clamp(100 * (weak - value) / (weak - strong)); };
const centered = (value: number, center: number, fullDistance: number, zeroDistance: number): number => {
  if (zeroDistance <= fullDistance) throw new RangeError("zero-score distance must exceed full-score distance");
  return clamp(100 * (zeroDistance - Math.abs(value - center)) / (zeroDistance - fullDistance));
};
const weightsFor = (data: RecordValue, names: string[]): Record<string, number> => {
  const raw = data.weights;
  if (!raw || typeof raw !== "object" || Array.isArray(raw) || Object.keys(raw).sort().join("|") !== [...names].sort().join("|")) throw new RangeError("weights must contain exactly the declared component names");
  const result = Object.fromEntries(names.map(name => [name, nonnegative(raw, name)]));
  const total = Object.values(result).reduce((sum, value) => sum + value, 0);
  if (Math.abs(total - 1) > 1e-9) throw new RangeError("weights must sum to 1");
  return result;
};
const weightedScore = (components: Record<string, number>, weights: Record<string, number>): number => Object.entries(components).reduce((sum, [name, value]) => sum + value * weights[name], 0);
const scoreState = (score: number): string => score >= 75 ? "strong-review-band" : score >= 50 ? "mixed-review-band" : "weak-review-band";

function bankScore(data: RecordValue): RecordValue {
  requireFramework(data, "basel-iii-teaching-v1");
  const names = ["capital", "leverage", "short_liquidity", "stable_funding", "asset_quality", "coverage", "margin", "efficiency"];
  const weights = weightsFor(data, names);
  const values = {cet1_ratio: nonnegative(data,"cet1_ratio"), leverage_ratio: nonnegative(data,"leverage_ratio"), lcr: nonnegative(data,"lcr"), nsfr: nonnegative(data,"nsfr"), npl_ratio: nonnegative(data,"npl_ratio"), provision_coverage_ratio: nonnegative(data,"provision_coverage_ratio"), net_interest_margin: numberValue(data,"net_interest_margin"), cost_income_ratio: nonnegative(data,"cost_income_ratio")};
  const components = {capital:higher(values.cet1_ratio,.07,.14), leverage:higher(values.leverage_ratio,.03,.06), short_liquidity:higher(values.lcr,1,1.4), stable_funding:higher(values.nsfr,1,1.3), asset_quality:lower(values.npl_ratio,.02,.08), coverage:higher(values.provision_coverage_ratio,.6,1.2), margin:higher(values.net_interest_margin,.01,.04), efficiency:lower(values.cost_income_ratio,.4,.7)};
  const minimums = data.minimums; if (!minimums || typeof minimums !== "object" || Array.isArray(minimums)) throw new TypeError("minimums must be an object");
  const checks: Record<string,number> = {cet1_ratio:nonnegative(minimums,"cet1_ratio"), leverage_ratio:nonnegative(minimums,"leverage_ratio"), lcr:nonnegative(minimums,"lcr"), nsfr:nonnegative(minimums,"nsfr")};
  const breaches = Object.entries(checks).filter(([name, minimum]) => values[name as keyof typeof values] < minimum).map(([name])=>name);
  const base = weightedScore(components, weights), penalty = 12.5 * breaches.length, score = clamp(base - penalty);
  return {state:breaches.length?"prudential-floor-review":scoreState(score),method:"bank-sector-score-v1",component_scores:components,weights,base_score:base,floor_breaches:breaches,penalty,fundamental_score:score,coverage_ratio:1,reason:"package-bands-with-declared-prudential-minimums"};
}

function insuranceScore(data: RecordValue): RecordValue {
  requireFramework(data,"solvency-ii-nonlife-teaching-v1");
  const names=["solvency","minimum_capital","underwriting","reserve_quality","own_fund_quality","concentration","profitability"], weights=weightsFor(data,names);
  const values={scr_coverage_ratio:nonnegative(data,"scr_coverage_ratio"),mcr_coverage_ratio:nonnegative(data,"mcr_coverage_ratio"),combined_ratio:nonnegative(data,"combined_ratio"),adverse_reserve_development_ratio:numberValue(data,"adverse_reserve_development_ratio"),tier1_own_funds_share:nonnegative(data,"tier1_own_funds_share"),investment_concentration_ratio:nonnegative(data,"investment_concentration_ratio"),return_on_equity:numberValue(data,"return_on_equity")};
  const components={solvency:higher(values.scr_coverage_ratio,1,2),minimum_capital:higher(values.mcr_coverage_ratio,1,3),underwriting:lower(values.combined_ratio,.88,1.08),reserve_quality:lower(values.adverse_reserve_development_ratio,-.02,.08),own_fund_quality:higher(values.tier1_own_funds_share,.5,.9),concentration:lower(values.investment_concentration_ratio,.1,.4),profitability:higher(values.return_on_equity,.02,.15)};
  const breaches=["scr_coverage_ratio","mcr_coverage_ratio"].filter(name=>values[name as keyof typeof values]<1),base=weightedScore(components,weights),penalty=20*breaches.length,score=clamp(base-penalty);
  return {state:breaches.length?"capital-requirement-review":scoreState(score),method:"insurance-sector-score-v1",component_scores:components,weights,base_score:base,floor_breaches:breaches,penalty,fundamental_score:score,coverage_ratio:1,reason:"solvency-ii-tagged-nonlife-package-bands"};
}

function reitScore(data: RecordValue): RecordValue {
  requireFramework(data,"nareit-equity-reit-teaching-v1");
  const names=["distribution","leverage","coverage","occupancy","same_store_growth","liquidity"],weights=weightsFor(data,names);
  const ffo=numberValue(data,"nareit_ffo"),capex=nonnegative(data,"recurring_capex"),rent=numberValue(data,"straight_line_rent_adjustment"),dividends=positive(data,"common_dividends"),ebitda=positive(data,"ebitda_re"),interest=positive(data,"interest_expense"),nearDebt=positive(data,"near_term_debt_maturities");
  const affo=ffo-capex-rent,distribution=affo/dividends,leverage=nonnegative(data,"net_debt")/ebitda,interestCoverage=ebitda/interest,liquidity=nonnegative(data,"available_liquidity")/nearDebt;
  const components={distribution:higher(distribution,.8,1.4),leverage:lower(leverage,3.5,8),coverage:higher(interestCoverage,1.5,5),occupancy:higher(nonnegative(data,"occupancy_ratio"),.8,.97),same_store_growth:higher(numberValue(data,"same_store_noi_growth"),-.05,.08),liquidity:higher(liquidity,.75,2)};
  const score=weightedScore(components,weights);
  return {state:affo<=0?"affo-proxy-deficit-review":scoreState(score),method:"reit-sector-score-v1",nareit_ffo:ffo,affo_proxy:affo,distribution_coverage:distribution,net_debt_to_ebitda_re:leverage,interest_coverage:interestCoverage,liquidity_coverage:liquidity,component_scores:components,weights,fundamental_score:score,coverage_ratio:1,reason:"nareit-ffo-plus-explicit-package-affo-proxy"};
}

function utilityScore(data: RecordValue): RecordValue {
  requireFramework(data,"ferc-regulated-electric-teaching-v1");
  const names=["earned_return","cash_debt","capital_structure","interest_coverage","capex_funding","regulatory_lag","rate_base_growth"],weights=weightsFor(data,names);
  const allowed=positive(data,"allowed_roe"),earned=numberValue(data,"earned_roe"),debt=positive(data,"total_debt"),ffoDebt=numberValue(data,"funds_from_operations")/debt,interestCoverage=numberValue(data,"ebit")/positive(data,"interest_expense"),capexFunding=numberValue(data,"cash_from_operations")/positive(data,"capital_expenditure");
  const components={earned_return:centered(earned-allowed,0,.005,.04),cash_debt:higher(ffoDebt,.08,.22),capital_structure:lower(nonnegative(data,"debt_to_capital"),.4,.65),interest_coverage:higher(interestCoverage,1.5,5),capex_funding:higher(capexFunding,.4,1),regulatory_lag:lower(nonnegative(data,"regulatory_lag_months"),3,18),rate_base_growth:centered(numberValue(data,"rate_base_growth"),.05,.01,.08)};
  const score=weightedScore(components,weights);
  return {state:scoreState(score),method:"utility-sector-score-v1",earned_allowed_roe_gap:earned-allowed,ffo_to_debt:ffoDebt,interest_coverage:interestCoverage,capex_funding_ratio:capexFunding,component_scores:components,weights,fundamental_score:score,coverage_ratio:1,reason:"ferc-tagged-package-bands"};
}

function earlyStageScore(data: RecordValue): RecordValue {
  requireFramework(data,"early-stage-liquidity-teaching-v1");
  const names=["runway","burn_trend","obligation_cover","revenue_cover"],weights=weightsFor(data,names),cashFlows=data.monthly_net_cash_flows,revenues=data.monthly_revenue;
  if(!Array.isArray(cashFlows)||cashFlows.length<6)throw new RangeError("monthly_net_cash_flows must contain at least 6 observations");
  if(!Array.isArray(revenues)||revenues.length!==cashFlows.length)throw new RangeError("monthly_revenue must match cash-flow history");
  const flows=cashFlows.map((v:any)=>numberValue({v},"v")),revs=revenues.map((v:any)=>nonnegative({v},"v"));
  const unrestricted=nonnegative(data,"unrestricted_cash"),investments=nonnegative(data,"liquid_investments"),facility=nonnegative(data,"unconditionally_committed_facility"),obligations=nonnegative(data,"near_term_obligations"),minimumCash=nonnegative(data,"minimum_operating_cash");
  const available=unrestricted+investments+facility-obligations-minimumCash,latest=flows.slice(-6),burn=Math.max(0,-latest.reduce((a:number,b:number)=>a+b,0)/latest.length),prior=Math.max(0,-latest.slice(0,3).reduce((a:number,b:number)=>a+b,0)/3),current=Math.max(0,-latest.slice(3).reduce((a:number,b:number)=>a+b,0)/3),burnRatio=prior>0?current/prior:(current===0?0:2),gross=unrestricted+investments+facility,obligationCover=obligations>0?gross/obligations:10,latestRevenue=revs.slice(-3).reduce((a:number,b:number)=>a+b,0)/3,revenueCover=current>0?latestRevenue/current:10,runway=burn>0?available/burn:null;
  const components={runway:runway===null?100:higher(runway,6,24),burn_trend:lower(burnRatio,.75,1.25),obligation_cover:higher(obligationCover,.75,2),revenue_cover:higher(revenueCover,0,1)},score=weightedScore(components,weights);
  const state=available<0?"negative-available-liquidity":runway===null?"nonburning-observation":runway<12?"funding-window-under-12-months":scoreState(score);
  return {state,method:"early-stage-runway-score-v1",available_liquidity:available,monthly_burn:burn,prior_three_month_burn:prior,latest_three_month_burn:current,burn_ratio:burnRatio,runway_months:runway,obligation_coverage:obligationCover,revenue_to_burn:revenueCover,component_scores:components,weights,runway_score:score,coverage_ratio:1,reason:"historical-burn-sensitivity-not-management-forecast"};
}

const medianValue=(values:number[]):number=>{const sorted=[...values].sort((a,b)=>a-b),middle=Math.floor(sorted.length/2);return sorted.length%2?sorted[middle]:(sorted[middle-1]+sorted[middle])/2;};
function cyclicalScore(data:RecordValue):RecordValue{
  requireFramework(data,"cyclical-midcycle-teaching-v1");
  const names=["normalized_margin","normalized_leverage","cash_consistency","cycle_balance"],weights=weightsFor(data,names),arrays:Record<string,number[]>={};
  for(const name of ["realized_price_history","unit_cost_history","volume_history","free_cash_flow_history"]){const raw=data[name];if(!Array.isArray(raw)||raw.length<7)throw new RangeError(`${name} must contain at least 7 observations`);arrays[name]=raw.map((v:any)=>numberValue({v},"v"));}
  if(new Set(Object.values(arrays).map(v=>v.length)).size!==1)throw new RangeError("cycle histories must have equal length");
  const prices=arrays.realized_price_history,costs=arrays.unit_cost_history,volumes=arrays.volume_history;if([...prices,...costs,...volumes].some(v=>v<=0))throw new RangeError("price, cost, and volume histories must be positive");
  const normalizedPrice=medianValue(prices),normalizedCost=medianValue(costs),normalizedVolume=medianValue(volumes),revenue=normalizedPrice*normalizedVolume,ebitda=(normalizedPrice-normalizedCost)*normalizedVolume-nonnegative(data,"fixed_costs"),margin=ebitda/revenue,netDebt=nonnegative(data,"net_debt"),leverage=ebitda>0?netDebt/ebitda:null,positiveFcf=arrays.free_cash_flow_history.filter(v=>v>0).length/arrays.free_cash_flow_history.length,currentPrice=positive(data,"current_realized_price"),percentile=prices.filter(v=>v<=currentPrice).length/prices.length;
  const components={normalized_margin:higher(margin,0,.3),normalized_leverage:leverage===null?0:lower(leverage,.5,4),cash_consistency:higher(positiveFcf,.3,.9),cycle_balance:centered(percentile,.5,.1,.5)},score=weightedScore(components,weights);
  return {state:ebitda<=0?"nonpositive-midcycle-ebitda-review":scoreState(score),method:"cyclical-midcycle-normalization-v1",normalized_price:normalizedPrice,normalized_unit_cost:normalizedCost,normalized_volume:normalizedVolume,normalized_revenue:revenue,normalized_ebitda:ebitda,normalized_margin:margin,normalized_net_leverage:leverage,positive_fcf_ratio:positiveFcf,cycle_percentile:percentile,component_scores:components,weights,normalized_score:score,coverage_ratio:1,reason:"point-in-time-median-cycle-window"};
}

function holdingCompanyScore(data:RecordValue):RecordValue{
  requireFramework(data,"holding-company-lookthrough-teaching-v1");
  const names=["nav_buffer","lookthrough_leverage","parent_coverage","diversification","freshness","listed_coverage"],weights=weightsFor(data,names),holdings=data.holdings;
  if(!Array.isArray(holdings)||holdings.length<2)throw new RangeError("holdings must contain at least two records");
  const ids=new Set<string>(),rows=holdings.map((raw:any)=>{if(!raw||typeof raw!=="object"||Array.isArray(raw))throw new TypeError("each holding must be an object");const id=textValue(raw,"id");if(ids.has(id))throw new RangeError("holding IDs must be unique");ids.add(id);const ownership=numberValue(raw,"ownership_ratio");if(!(ownership>0&&ownership<=1))throw new RangeError("ownership_ratio must be in (0, 1]");if(typeof raw.listed!=="boolean"||typeof raw.stale!=="boolean")throw new TypeError("listed and stale must be booleans");return{id,ownership_ratio:ownership,attributable_equity_value:ownership*nonnegative(raw,"equity_value"),attributable_debt:ownership*nonnegative(raw,"debt"),attributable_dividends:ownership*nonnegative(raw,"dividends_to_parent"),listed:raw.listed,stale:raw.stale};});
  const parentCash=nonnegative(data,"parent_cash"),parentDebt=nonnegative(data,"parent_debt"),other=nonnegative(data,"other_parent_liabilities"),parentInterest=positive(data,"parent_interest_expense"),stake=rows.reduce((s,r)=>s+r.attributable_equity_value,0),gav=stake+parentCash;if(gav<=0)throw new RangeError("gross asset value must be positive");
  const nav=gav-parentDebt-other,attributableDebt=rows.reduce((s,r)=>s+r.attributable_debt,0),enterprise=stake+attributableDebt+parentCash,leverage=enterprise>0?(parentDebt+attributableDebt)/enterprise:1,parentCoverage=rows.reduce((s,r)=>s+r.attributable_dividends,0)/parentInterest,shares=stake>0?rows.map(r=>r.attributable_equity_value/stake):[1],concentration=shares.reduce((s,v)=>s+v*v,0),freshness=stake>0?rows.filter(r=>!r.stale).reduce((s,r)=>s+r.attributable_equity_value,0)/stake:0,listed=stake>0?rows.filter(r=>r.listed).reduce((s,r)=>s+r.attributable_equity_value,0)/stake:0,navRatio=nav/gav;
  const components={nav_buffer:higher(navRatio,.2,.8),lookthrough_leverage:lower(leverage,.2,.65),parent_coverage:higher(parentCoverage,1,4),diversification:lower(concentration,.25,.7),freshness:higher(freshness,.6,1),listed_coverage:higher(listed,.3,1)},score=weightedScore(components,weights),marketCap=data.parent_market_cap===undefined?null:nonnegative(data,"parent_market_cap"),discount=marketCap===null||nav<=0?null:1-marketCap/nav;
  return {state:nav<=0?"nonpositive-nav-review":scoreState(score),method:"holding-company-lookthrough-score-v1",holding_ledger:rows,gross_asset_value:gav,net_asset_value:nav,attributable_subsidiary_debt:attributableDebt,lookthrough_leverage:leverage,parent_interest_coverage:parentCoverage,concentration_hhi:concentration,fresh_value_coverage:freshness,listed_value_coverage:listed,discount_to_nav:discount,component_scores:components,weights,look_through_score:score,coverage_ratio:1,reason:"attributable-stakes-with-parent-bridge"};
}

const sigmoid=(value:number):number=>value>=0?1/(1+Math.exp(-value)):Math.exp(value)/(1+Math.exp(value));
const projectSimplex=(values:number[]):number[]=>{const ordered=[...values].sort((a,b)=>b-a);let cumulative=0,rho=0;ordered.forEach((value,index)=>{cumulative+=value;if(value-(cumulative-1)/(index+1)>0)rho=index+1;});const theta=(ordered.slice(0,rho).reduce((a,b)=>a+b,0)-1)/rho,projected=values.map(v=>Math.max(v-theta,0)),total=projected.reduce((a,b)=>a+b,0);return projected.map(v=>v/total);};
const brier=(rows:number[][],labels:number[],weights:number[],intercept:number,slope:number):number=>rows.reduce((sum,row,index)=>{const score=row.reduce((s,v,j)=>s+weights[j]*v,0)/100,p=sigmoid(intercept+slope*(score-.5));return sum+(p-labels[index])**2;},0)/rows.length;
function calibrateWeights(data:RecordValue):RecordValue{
  requireFramework(data,"sector-weight-calibration-teaching-v1");
  const names=data.component_names,rowsRaw=data.feature_rows,labelsRaw=data.labels,datesRaw=data.observation_dates;
  if(!Array.isArray(names)||names.length<2||new Set(names).size!==names.length||!names.every((n:any)=>typeof n==="string"&&n))throw new RangeError("component_names must be unique nonempty strings");
  if(!Array.isArray(rowsRaw)||rowsRaw.length<12)throw new RangeError("feature_rows must contain at least 12 observations");if(!Array.isArray(labelsRaw)||labelsRaw.length!==rowsRaw.length)throw new RangeError("labels must match feature_rows");if(!Array.isArray(datesRaw)||datesRaw.length!==rowsRaw.length)throw new RangeError("observation_dates must match feature_rows");
  const rows=rowsRaw.map((row:any)=>{if(!Array.isArray(row)||row.length!==names.length)throw new RangeError("each feature row must match component_names");const values=row.map((v:any)=>numberValue({v},"v"));if(values.some((v:number)=>v<0||v>100))throw new RangeError("feature scores must be within [0, 100]");return values;});
  const labels=labelsRaw.map((v:any)=>{if(v!==0&&v!==1)throw new RangeError("labels must be binary 0/1 integers");return v;});const dates=datesRaw.map((v:any)=>dateValue(v,"observation_dates"));if(dates.some((v:Date,i:number)=>i<dates.length-1&&v.getTime()>=dates[i+1].getTime()))throw new RangeError("observation_dates must be strictly increasing");
  const split=Math.trunc(numberValue(data,"train_end_index"));if(split<8||split>rows.length-4)throw new RangeError("train_end_index must leave at least 8 training and 4 validation rows");const baseMap=data.base_weights;if(!baseMap||typeof baseMap!=="object"||Array.isArray(baseMap)||Object.keys(baseMap).sort().join("|")!==[...names].sort().join("|"))throw new RangeError("base_weights must match component_names");const base=names.map((n:string)=>nonnegative(baseMap,n));if(Math.abs(base.reduce((a:number,b:number)=>a+b,0)-1)>1e-9)throw new RangeError("base_weights must sum to 1");
  const learningRate=positive(data,"learning_rate"),ridge=nonnegative(data,"ridge_penalty"),slope=positive(data,"logit_slope"),iterations=Math.trunc(positive(data,"iterations"));if(iterations>5000)throw new RangeError("iterations must not exceed 5000");let weights=[...base],intercept=0;const trainRows=rows.slice(0,split),validationRows=rows.slice(split),trainLabels=labels.slice(0,split),validationLabels=labels.slice(split);
  for(let iteration=0;iteration<iterations;iteration++){const probabilities=trainRows.map((row:number[])=>sigmoid(intercept+slope*(row.reduce((s:number,v:number,j:number)=>s+weights[j]*v,0)/100-.5))),residuals=probabilities.map((p:number,i:number)=>p-trainLabels[i]),gradIntercept=residuals.reduce((a:number,b:number)=>a+b,0)/residuals.length,gradWeights=names.map((_:string,column:number)=>residuals.reduce((sum:number,residual:number,i:number)=>sum+residual*slope*trainRows[i][column]/100,0)/trainRows.length+ridge*(weights[column]-base[column]));intercept-=learningRate*gradIntercept;weights=projectSimplex(weights.map((v:number,i:number)=>v-learningRate*gradWeights[i]));}
  const baseTrain=brier(trainRows,trainLabels,base,0,slope),baseValidation=brier(validationRows,validationLabels,base,0,slope),candidateTrain=brier(trainRows,trainLabels,weights,intercept,slope),candidateValidation=brier(validationRows,validationLabels,weights,intercept,slope),improved=candidateValidation<=baseValidation,selected=improved?weights:base,selectedIntercept=improved?intercept:0,selectedValidation=improved?candidateValidation:baseValidation;
  return {state:improved?"calibrated-improved":"retain-base-weights",method:"nonnegative-simplex-logloss-v1",component_names:names,base_weights:Object.fromEntries(names.map((n:string,i:number)=>[n,base[i]])),candidate_weights:Object.fromEntries(names.map((n:string,i:number)=>[n,weights[i]])),selected_weights:Object.fromEntries(names.map((n:string,i:number)=>[n,selected[i]])),candidate_intercept:intercept,selected_intercept:selectedIntercept,base_train_brier:baseTrain,base_validation_brier:baseValidation,candidate_train_brier:candidateTrain,candidate_validation_brier:candidateValidation,selected_validation_brier:selectedValidation,weight_shift_l1:weights.reduce((s:number,v:number,i:number)=>s+Math.abs(v-base[i]),0),train_rows:trainRows.length,validation_rows:validationRows.length,coverage_ratio:1,reason:"temporal-holdout-governs-selection"};
}

const ROUTES:Record<string,[string,string,string[]]>={bank:["D18-F10-A01","basel-iii-teaching-v1",["cet1_ratio","leverage_ratio","lcr","nsfr","npl_ratio","provision_coverage_ratio","net_interest_margin","cost_income_ratio"]],insurance:["D18-F10-A02","solvency-ii-nonlife-teaching-v1",["scr_coverage_ratio","mcr_coverage_ratio","combined_ratio","adverse_reserve_development_ratio","tier1_own_funds_share","investment_concentration_ratio","return_on_equity"]],reit:["D18-F10-A03","nareit-equity-reit-teaching-v1",["nareit_ffo","recurring_capex","common_dividends","net_debt","ebitda_re","interest_expense","occupancy_ratio","same_store_noi_growth"]],utility:["D18-F10-A04","ferc-regulated-electric-teaching-v1",["allowed_roe","earned_roe","funds_from_operations","total_debt","debt_to_capital","ebit","interest_expense","capital_expenditure"]],"early-stage":["D18-F10-A05","early-stage-liquidity-teaching-v1",["unrestricted_cash","liquid_investments","monthly_net_cash_flows","near_term_obligations"]],cyclical:["D18-F10-A06","cyclical-midcycle-teaching-v1",["realized_price_history","unit_cost_history","volume_history","free_cash_flow_history","net_debt"]],"holding-company":["D18-F10-A07","holding-company-lookthrough-teaching-v1",["holdings","parent_cash","parent_debt","parent_interest_expense"]]};
function coverageDecision(data:RecordValue):RecordValue{
  requireFramework(data,"coverage-router-teaching-v1");const sector=textValue(data,"sector"),candidate=textValue(data,"candidate_framework"),asOf=dateValue(data.as_of,"as_of"),minimum=numberValue(data,"minimum_coverage"),maxAge=Math.trunc(positive(data,"max_age_days"));if(!(minimum>0&&minimum<=1))throw new RangeError("minimum_coverage must be in (0, 1]");if(!ROUTES[sector])return{state:"abstain",method:"sector-coverage-router-v1",selected_model:null,coverage_ratio:0,available_fields:[],missing_fields:[],stale_fields:[],future_fields:[],reasons:["unsupported-sector"],reason:"unsupported-sector"};
  const [model,expected,required]=ROUTES[sector];if(candidate!==expected)return{state:"abstain",method:"sector-coverage-router-v1",selected_model:null,coverage_ratio:0,available_fields:[],missing_fields:[...required],stale_fields:[],future_fields:[],reasons:["framework-mismatch"],reason:"framework-mismatch"};const facts=data.facts;if(!Array.isArray(facts))throw new TypeError("facts must be an array");const ledger:Record<string,any>={};for(const raw of facts){if(!raw||typeof raw!=="object"||Array.isArray(raw))throw new TypeError("each fact must be an object");const name=textValue(raw,"name");if(ledger[name])throw new RangeError("fact names must be unique");if(typeof raw.value_present!=="boolean")throw new TypeError("value_present must be boolean");ledger[name]=raw;}
  const available:string[]=[],missing:string[]=[],stale:string[]=[],future:string[]=[];for(const name of required){const fact=ledger[name];if(!fact||!fact.value_present){missing.push(name);continue;}const knowledge=dateValue(fact.knowledge_date,`${name}.knowledge_date`),periodEnd=dateValue(fact.period_end,`${name}.period_end`);if(knowledge.getTime()>asOf.getTime())future.push(name);else if((asOf.getTime()-periodEnd.getTime())/86400000>maxAge)stale.push(name);else available.push(name);}const coverage=available.length/required.length,reasons:string[]=[];if(future.length)reasons.push("future-evidence");if(stale.length)reasons.push("stale-evidence");if(missing.length)reasons.push("missing-required-fields");let state:string,selected:string|null;if(future.length){state="abstain";selected=null;}else if(coverage===1){state="supported";selected=model;}else if(coverage>=minimum){state="partial-review";selected=model;}else{state="abstain";selected=null;reasons.push("coverage-below-minimum");}if(!reasons.length)reasons.push("complete-current-coverage");return{state,method:"sector-coverage-router-v1",selected_model:selected,coverage_ratio:coverage,available_fields:available,missing_fields:missing,stale_fields:stale,future_fields:future,required_field_count:required.length,reasons,reason:reasons[0]};
}

export function calculate(topicId:string,data:RecordValue):RecordValue{
  if(!data||typeof data!=="object"||Array.isArray(data))throw new TypeError("input must be an object");
  const functions:Record<string,(value:RecordValue)=>RecordValue>={"D18-F10-A01":bankScore,"D18-F10-A02":insuranceScore,"D18-F10-A03":reitScore,"D18-F10-A04":utilityScore,"D18-F10-A05":earlyStageScore,"D18-F10-A06":cyclicalScore,"D18-F10-A07":holdingCompanyScore,"D18-F10-A08":calibrateWeights,"D18-F10-A09":coverageDecision};
  if(!functions[topicId])throw new RangeError(`unsupported topic ID: ${topicId}`);return functions[topicId](data);
}
Full-height labplaygroundOpen full screen