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:
@@ -102,6 +102,50 @@ pub struct KpiDefinition {
|
||||
pub unit: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct ComputedPackFile {
|
||||
pub version: String,
|
||||
pub pack: String,
|
||||
pub computed: Vec<ComputedDefinition>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct ComputedDefinition {
|
||||
pub key: String,
|
||||
pub label: String,
|
||||
pub category: String,
|
||||
pub order: i64,
|
||||
pub unit: String,
|
||||
pub computation: ComputationSpec,
|
||||
#[serde(default)]
|
||||
pub supported_cadences: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub requires_external_data: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ComputationSpec {
|
||||
Ratio {
|
||||
numerator: String,
|
||||
denominator: String,
|
||||
},
|
||||
YoyGrowth {
|
||||
source: String,
|
||||
},
|
||||
Cagr {
|
||||
source: String,
|
||||
years: i64,
|
||||
},
|
||||
PerShare {
|
||||
source: String,
|
||||
shares_key: String,
|
||||
},
|
||||
Simple {
|
||||
formula: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct UniversalIncomeFile {
|
||||
pub version: String,
|
||||
@@ -222,7 +266,9 @@ pub fn load_surface_pack(pack: FiscalPack) -> Result<SurfacePackFile> {
|
||||
core_file
|
||||
.surfaces
|
||||
.into_iter()
|
||||
.filter(|surface| surface.statement == "balance" || surface.statement == "cash_flow")
|
||||
.filter(|surface| {
|
||||
surface.statement == "balance" || surface.statement == "cash_flow"
|
||||
})
|
||||
.filter(|surface| {
|
||||
!pack_inherited_keys
|
||||
.contains(&(surface.statement.clone(), surface.surface_key.clone()))
|
||||
@@ -297,6 +343,28 @@ pub fn load_kpi_pack(pack: FiscalPack) -> Result<KpiPackFile> {
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
pub fn load_computed_pack(pack: FiscalPack) -> Result<ComputedPackFile> {
|
||||
let taxonomy_dir = resolve_taxonomy_dir()?;
|
||||
let path = taxonomy_dir
|
||||
.join("fiscal")
|
||||
.join("v1")
|
||||
.join(format!("{}.computed.json", pack.as_str()));
|
||||
let raw = fs::read_to_string(&path).with_context(|| {
|
||||
format!(
|
||||
"taxonomy resolution failed: unable to read {}",
|
||||
path.display()
|
||||
)
|
||||
})?;
|
||||
let file = serde_json::from_str::<ComputedPackFile>(&raw).with_context(|| {
|
||||
format!(
|
||||
"taxonomy resolution failed: unable to parse {}",
|
||||
path.display()
|
||||
)
|
||||
})?;
|
||||
let _ = (&file.version, &file.pack);
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
pub fn load_universal_income_definitions() -> Result<UniversalIncomeFile> {
|
||||
let taxonomy_dir = resolve_taxonomy_dir()?;
|
||||
let path = taxonomy_dir
|
||||
@@ -359,6 +427,10 @@ mod tests {
|
||||
let kpi_pack = load_kpi_pack(FiscalPack::Core).expect("core kpi pack should load");
|
||||
assert_eq!(kpi_pack.pack, "core");
|
||||
|
||||
let computed_pack =
|
||||
load_computed_pack(FiscalPack::Core).expect("core computed pack should load");
|
||||
assert_eq!(computed_pack.pack, "core");
|
||||
|
||||
let universal_income =
|
||||
load_universal_income_definitions().expect("universal income config should load");
|
||||
assert!(!universal_income.rows.is_empty());
|
||||
|
||||
Reference in New Issue
Block a user