Consolidate metric definitions with Rust JSON as single source of truth
- Add core.computed.json with 32 ratio definitions (filing + market derived) - Add Rust types for ComputedDefinition and ComputationSpec - Create generate-taxonomy.ts to generate TypeScript from Rust JSON - Generate lib/generated/ (gitignored) with surfaces, computed, kpis - Update financial-metrics.ts to use generated definitions - Add build-time generation via 'bun run generate' - Add taxonomy architecture documentation Two-phase ratio computation: - Filing-derived: margins, returns, per-share, growth (Rust computes) - Market-derived: valuation ratios (TypeScript computes with price data) All 32 ratios defined in core.computed.json: - Margins: gross, operating, ebitda, net, fcf - Returns: roa, roe, roic, roce - Financial health: debt_to_equity, net_debt_to_ebitda, cash_to_debt, current_ratio - Per-share: revenue, fcf, book_value - Growth: yoy metrics + 3y/5y cagr - Valuation: market_cap, ev, p/e, p/fcf, p/b, ev/sales, ev/ebitda, ev/fcf
This commit is contained in:
@@ -12,6 +12,8 @@ mod surface_mapper;
|
||||
mod taxonomy_loader;
|
||||
mod universal_income;
|
||||
|
||||
use taxonomy_loader::{ComputationSpec, ComputedDefinition};
|
||||
|
||||
#[cfg(feature = "with-crabrl")]
|
||||
use crabrl as _;
|
||||
|
||||
@@ -112,6 +114,7 @@ pub struct HydrateFilingResponse {
|
||||
pub surface_rows: SurfaceRowMap,
|
||||
pub detail_rows: DetailRowStatementMap,
|
||||
pub kpi_rows: Vec<KpiRowOutput>,
|
||||
pub computed_definitions: Vec<ComputedDefinitionOutput>,
|
||||
pub contexts: Vec<ContextOutput>,
|
||||
pub derived_metrics: FilingMetrics,
|
||||
pub validation_result: ValidationResultOutput,
|
||||
@@ -257,6 +260,86 @@ pub struct KpiRowOutput {
|
||||
pub has_dimensions: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct ComputedDefinitionOutput {
|
||||
pub key: String,
|
||||
pub label: String,
|
||||
pub category: String,
|
||||
pub order: i64,
|
||||
pub unit: String,
|
||||
pub computation: ComputationSpecOutput,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub supported_cadences: Vec<String>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub requires_external_data: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ComputationSpecOutput {
|
||||
Ratio {
|
||||
numerator: String,
|
||||
denominator: String,
|
||||
},
|
||||
YoyGrowth {
|
||||
source: String,
|
||||
},
|
||||
Cagr {
|
||||
source: String,
|
||||
years: i64,
|
||||
},
|
||||
PerShare {
|
||||
source: String,
|
||||
shares_key: String,
|
||||
},
|
||||
Simple {
|
||||
formula: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<&ComputationSpec> for ComputationSpecOutput {
|
||||
fn from(spec: &ComputationSpec) -> Self {
|
||||
match spec {
|
||||
ComputationSpec::Ratio {
|
||||
numerator,
|
||||
denominator,
|
||||
} => ComputationSpecOutput::Ratio {
|
||||
numerator: numerator.clone(),
|
||||
denominator: denominator.clone(),
|
||||
},
|
||||
ComputationSpec::YoyGrowth { source } => ComputationSpecOutput::YoyGrowth {
|
||||
source: source.clone(),
|
||||
},
|
||||
ComputationSpec::Cagr { source, years } => ComputationSpecOutput::Cagr {
|
||||
source: source.clone(),
|
||||
years: *years,
|
||||
},
|
||||
ComputationSpec::PerShare { source, shares_key } => ComputationSpecOutput::PerShare {
|
||||
source: source.clone(),
|
||||
shares_key: shares_key.clone(),
|
||||
},
|
||||
ComputationSpec::Simple { formula } => ComputationSpecOutput::Simple {
|
||||
formula: formula.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ComputedDefinition> for ComputedDefinitionOutput {
|
||||
fn from(def: &ComputedDefinition) -> Self {
|
||||
ComputedDefinitionOutput {
|
||||
key: def.key.clone(),
|
||||
label: def.label.clone(),
|
||||
category: def.category.clone(),
|
||||
order: def.order,
|
||||
unit: def.unit.clone(),
|
||||
computation: ComputationSpecOutput::from(&def.computation),
|
||||
supported_cadences: def.supported_cadences.clone(),
|
||||
requires_external_data: def.requires_external_data.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct ConceptOutput {
|
||||
pub concept_key: String,
|
||||
@@ -435,6 +518,7 @@ pub fn hydrate_filing(input: HydrateFilingRequest) -> Result<HydrateFilingRespon
|
||||
surface_rows: empty_surface_rows,
|
||||
detail_rows: empty_detail_rows,
|
||||
kpi_rows: vec![],
|
||||
computed_definitions: vec![],
|
||||
contexts: vec![],
|
||||
derived_metrics: FilingMetrics::default(),
|
||||
validation_result,
|
||||
@@ -541,6 +625,18 @@ pub fn hydrate_filing(input: HydrateFilingRequest) -> Result<HydrateFilingRespon
|
||||
&compact_model.concept_mappings,
|
||||
);
|
||||
|
||||
let computed_pack = taxonomy_loader::load_computed_pack(pack_selection.pack)
|
||||
.ok()
|
||||
.or_else(|| taxonomy_loader::load_computed_pack(pack_selector::FiscalPack::Core).ok());
|
||||
let computed_definitions: Vec<ComputedDefinitionOutput> = computed_pack
|
||||
.map(|pack| {
|
||||
pack.computed
|
||||
.iter()
|
||||
.map(ComputedDefinitionOutput::from)
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let has_rows = materialized
|
||||
.statement_rows
|
||||
.values()
|
||||
@@ -578,6 +674,7 @@ pub fn hydrate_filing(input: HydrateFilingRequest) -> Result<HydrateFilingRespon
|
||||
surface_rows: compact_model.surface_rows,
|
||||
detail_rows: compact_model.detail_rows,
|
||||
kpi_rows: kpi_result.rows,
|
||||
computed_definitions,
|
||||
contexts: parsed_instance.contexts,
|
||||
derived_metrics: metrics::derive_metrics(&facts),
|
||||
validation_result,
|
||||
|
||||
Reference in New Issue
Block a user