Add detailed cash flow statement line items

This commit is contained in:
2026-04-24 21:45:38 -04:00
parent 38205f62fe
commit 4d133f1964
6 changed files with 1114 additions and 40 deletions

View File

@@ -1,4 +1,4 @@
use serde_json::{json, Value};
use serde_json::{json, Map, Value};
use rig::completion::Message;
@@ -304,21 +304,142 @@ fn compact_statement_period(period: &StatementPeriod) -> Value {
}
fn compact_cash_flow_period(period: &CashFlowPeriod) -> Value {
json!({
"label": truncate_text(&period.label),
"fiscalYear": period.fiscal_year,
"fiscalPeriod": period.fiscal_period,
"periodStart": period.period_start,
"periodEnd": period.period_end,
"filedDate": period.filed_date,
"form": period.form,
"operatingCashFlow": period.operating_cash_flow,
"investingCashFlow": period.investing_cash_flow,
"financingCashFlow": period.financing_cash_flow,
"capex": period.capex,
"freeCashFlow": period.free_cash_flow,
"endingCash": period.ending_cash,
})
let mut value = Map::new();
value.insert("label".to_string(), json!(truncate_text(&period.label)));
value.insert("fiscalYear".to_string(), json!(&period.fiscal_year));
value.insert("fiscalPeriod".to_string(), json!(&period.fiscal_period));
value.insert("periodStart".to_string(), json!(&period.period_start));
value.insert("periodEnd".to_string(), json!(&period.period_end));
value.insert("filedDate".to_string(), json!(&period.filed_date));
value.insert("form".to_string(), json!(&period.form));
value.insert("netIncome".to_string(), json!(period.net_income));
value.insert(
"depreciationAndAmortization".to_string(),
json!(period.depreciation_and_amortization),
);
value.insert(
"stockBasedCompensation".to_string(),
json!(period.stock_based_compensation),
);
value.insert(
"deferredIncomeTaxes".to_string(),
json!(period.deferred_income_taxes),
);
value.insert(
"impairmentCharges".to_string(),
json!(period.impairment_charges),
);
value.insert(
"lossGainOnSaleOfAssets".to_string(),
json!(period.loss_gain_on_sale_of_assets),
);
value.insert(
"otherNonCashItems".to_string(),
json!(period.other_non_cash_items),
);
value.insert(
"accountsReceivable".to_string(),
json!(period.accounts_receivable),
);
value.insert("inventory".to_string(), json!(period.inventory));
value.insert(
"prepaidExpensesAndOtherCurrentAssets".to_string(),
json!(period.prepaid_expenses_and_other_current_assets),
);
value.insert(
"accountsPayable".to_string(),
json!(period.accounts_payable),
);
value.insert(
"accruedExpenses".to_string(),
json!(period.accrued_expenses),
);
value.insert(
"deferredRevenue".to_string(),
json!(period.deferred_revenue),
);
value.insert(
"otherWorkingCapitalChanges".to_string(),
json!(period.other_working_capital_changes),
);
value.insert(
"changeInOperatingAssets".to_string(),
json!(period.change_in_operating_assets),
);
value.insert(
"changeInOperatingLiabilities".to_string(),
json!(period.change_in_operating_liabilities),
);
value.insert(
"otherOperatingAdjustments".to_string(),
json!(period.other_operating_adjustments),
);
value.insert(
"operatingCashFlow".to_string(),
json!(period.operating_cash_flow),
);
value.insert("capex".to_string(), json!(period.capex));
value.insert(
"proceedsFromSaleOfPpe".to_string(),
json!(period.proceeds_from_sale_of_ppe),
);
value.insert(
"purchasesOfInvestments".to_string(),
json!(period.purchases_of_investments),
);
value.insert(
"salesOfInvestments".to_string(),
json!(period.sales_of_investments),
);
value.insert("acquisitions".to_string(), json!(period.acquisitions));
value.insert(
"otherInvestingActivities".to_string(),
json!(period.other_investing_activities),
);
value.insert(
"investingCashFlow".to_string(),
json!(period.investing_cash_flow),
);
value.insert("debtIssuance".to_string(), json!(period.debt_issuance));
value.insert("debtRepayment".to_string(), json!(period.debt_repayment));
value.insert("equityIssuance".to_string(), json!(period.equity_issuance));
value.insert(
"stockRepurchases".to_string(),
json!(period.stock_repurchases),
);
value.insert("dividendsPaid".to_string(), json!(period.dividends_paid));
value.insert(
"financeLeaseObligations".to_string(),
json!(period.finance_lease_obligations),
);
value.insert(
"otherFinancingActivities".to_string(),
json!(period.other_financing_activities),
);
value.insert(
"financingCashFlow".to_string(),
json!(period.financing_cash_flow),
);
value.insert("freeCashFlow".to_string(), json!(period.free_cash_flow));
value.insert(
"exchangeRateEffects".to_string(),
json!(period.exchange_rate_effects),
);
value.insert(
"netIncreaseDecreaseCash".to_string(),
json!(period.net_increase_decrease_cash),
);
value.insert("beginningCash".to_string(), json!(period.beginning_cash));
value.insert("endingCash".to_string(), json!(period.ending_cash));
value.insert(
"cashPaidInterest".to_string(),
json!(period.cash_paid_interest),
);
value.insert(
"cashPaidIncomeTaxes".to_string(),
json!(period.cash_paid_income_taxes),
);
Value::Object(value)
}
fn compact_dividend_event(event: &DividendEvent) -> Value {
@@ -794,12 +915,46 @@ mod tests {
period_end: "2025-12-31".to_string(),
filed_date: "2026-01-31".to_string(),
form: "10-K".to_string(),
net_income: Some(30.0),
depreciation_and_amortization: Some(15.0),
stock_based_compensation: Some(5.0),
deferred_income_taxes: Some(2.0),
impairment_charges: None,
loss_gain_on_sale_of_assets: None,
other_non_cash_items: Some(1.0),
accounts_receivable: Some(-4.0),
inventory: Some(-2.0),
prepaid_expenses_and_other_current_assets: Some(-1.0),
accounts_payable: Some(3.0),
accrued_expenses: Some(2.0),
deferred_revenue: Some(1.0),
other_working_capital_changes: None,
change_in_operating_assets: Some(-8.0),
change_in_operating_liabilities: Some(3.0),
other_operating_adjustments: None,
operating_cash_flow: Some(100.0),
investing_cash_flow: Some(-50.0),
financing_cash_flow: Some(-25.0),
capex: Some(-10.0),
proceeds_from_sale_of_ppe: Some(1.0),
purchases_of_investments: Some(-40.0),
sales_of_investments: Some(20.0),
acquisitions: None,
other_investing_activities: None,
investing_cash_flow: Some(-50.0),
debt_issuance: Some(50.0),
debt_repayment: Some(-30.0),
equity_issuance: Some(10.0),
stock_repurchases: Some(-15.0),
dividends_paid: Some(-20.0),
finance_lease_obligations: Some(-5.0),
other_financing_activities: None,
financing_cash_flow: Some(-25.0),
free_cash_flow: Some(90.0),
exchange_rate_effects: None,
net_increase_decrease_cash: Some(5.0),
beginning_cash: Some(15.0),
ending_cash: Some(20.0),
cash_paid_interest: Some(4.0),
cash_paid_income_taxes: Some(6.0),
}
}

View File

@@ -150,6 +150,375 @@ pub(crate) const CAPEX_CONCEPTS: &[ConceptCandidate] = &[
UnitFamily::Currency,
),
];
pub(crate) const NET_INCOME_CF_CONCEPTS: &[ConceptCandidate] = &[
candidate("us-gaap", "NetIncomeLoss", UnitFamily::Currency),
candidate("ifrs-full", "ProfitLoss", UnitFamily::Currency),
];
pub(crate) const DEPRECIATION_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"DepreciationDepletionAndAmortization",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"DepreciationAndAmortization",
UnitFamily::Currency,
),
candidate(
"ifrs-full",
"DepreciationAndAmortisation",
UnitFamily::Currency,
),
];
pub(crate) const STOCK_BASED_COMPENSATION_CONCEPTS: &[ConceptCandidate] = &[
candidate("us-gaap", "ShareBasedCompensation", UnitFamily::Currency),
candidate("us-gaap", "StockCompensationExpense", UnitFamily::Currency),
];
pub(crate) const DEFERRED_INCOME_TAXES_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"DeferredIncomeTaxExpenseBenefit",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"DeferredIncomeTaxesAndTaxCredits",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"IncreaseDecreaseInDeferredIncomeTaxes",
UnitFamily::Currency,
),
];
pub(crate) const IMPAIRMENT_CHARGES_CONCEPTS: &[ConceptCandidate] = &[
candidate("us-gaap", "AssetImpairmentCharges", UnitFamily::Currency),
candidate("us-gaap", "GoodwillImpairmentLosses", UnitFamily::Currency),
candidate(
"us-gaap",
"ImpairmentOfGoodwillAndIndefiniteLivedIntangibleAssets",
UnitFamily::Currency,
),
];
pub(crate) const LOSS_GAIN_ON_SALE_OF_ASSETS_CONCEPTS: &[ConceptCandidate] = &[
candidate("us-gaap", "LossGainOnSaleOfAssets", UnitFamily::Currency),
candidate(
"us-gaap",
"GainLossOnSaleOfPropertyPlantEquipment",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"GainLossOnDispositionOfAssets",
UnitFamily::Currency,
),
];
pub(crate) const OTHER_NON_CASH_ITEMS_CONCEPTS: &[ConceptCandidate] = &[
candidate("us-gaap", "OtherNoncashIncomeExpense", UnitFamily::Currency),
candidate("us-gaap", "OtherNoncashExpense", UnitFamily::Currency),
candidate(
"us-gaap",
"OtherNoncashItemsIncludedInNetIncomeLoss",
UnitFamily::Currency,
),
];
pub(crate) const ACCOUNTS_RECEIVABLE_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"IncreaseDecreaseInAccountsReceivable",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"ChangesInAccountReceivables",
UnitFamily::Currency,
),
];
pub(crate) const INVENTORY_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"IncreaseDecreaseInInventories",
UnitFamily::Currency,
),
candidate("us-gaap", "ChangesInInventories", UnitFamily::Currency),
];
pub(crate) const PREPAIDS_AND_OTHER_CURRENT_ASSETS_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"IncreaseDecreaseInPrepaidDeferredExpenseAndOtherAssets",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"IncreaseDecreaseInPrepaidExpenseAndOtherAssets",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"IncreaseDecreaseInOtherCurrentAssets",
UnitFamily::Currency,
),
];
pub(crate) const ACCOUNTS_PAYABLE_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"IncreaseDecreaseInAccountsPayable",
UnitFamily::Currency,
),
candidate("us-gaap", "ChangesInAccountPayables", UnitFamily::Currency),
];
pub(crate) const ACCRUED_EXPENSES_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"IncreaseDecreaseInAccruedLiabilities",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"IncreaseDecreaseInAccruedExpenses",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"IncreaseDecreaseInAccruedIncomeTaxesPayable",
UnitFamily::Currency,
),
];
pub(crate) const DEFERRED_REVENUE_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"IncreaseDecreaseInContractWithCustomerLiability",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"IncreaseDecreaseInDeferredRevenue",
UnitFamily::Currency,
),
];
pub(crate) const OTHER_WORKING_CAPITAL_CHANGES_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"IncreaseDecreaseInOperatingCapital",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"IncreaseDecreaseInOperatingAssetsAndLiabilitiesNet",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"OtherAssetsAndLiabilitiesNet",
UnitFamily::Currency,
),
];
pub(crate) const CHANGE_IN_OPERATING_ASSETS_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"IncreaseDecreaseInAccountsReceivable",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"ChangesInAccountReceivables",
UnitFamily::Currency,
),
];
pub(crate) const CHANGE_IN_OPERATING_LIABILITIES_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"IncreaseDecreaseInAccountsPayable",
UnitFamily::Currency,
),
candidate("us-gaap", "ChangesInAccountPayables", UnitFamily::Currency),
];
pub(crate) const OTHER_OPERATING_ADJUSTMENTS_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"OtherAdjustmentsToReconcileNetIncomeLossToCashProvidedByUsedInOperatingActivities",
UnitFamily::Currency,
),
candidate("us-gaap", "OtherNoncashIncomeExpense", UnitFamily::Currency),
];
pub(crate) const PROCEEDS_FROM_SALE_OF_PPE_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"ProceedsFromSaleOfPropertyPlantAndEquipment",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"ProceedsFromSaleOfPropertyPlantAndEquipmentAndIntangibleAssets",
UnitFamily::Currency,
),
];
pub(crate) const PURCHASES_OF_INVESTMENTS_CONCEPTS: &[ConceptCandidate] = &[
candidate("us-gaap", "PaymentsForInvestments", UnitFamily::Currency),
candidate("us-gaap", "PurchaseOfInvestments", UnitFamily::Currency),
candidate(
"us-gaap",
"PaymentsToAcquireInvestments",
UnitFamily::Currency,
),
];
pub(crate) const SALES_OF_INVESTMENTS_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"ProceedsFromSaleOfInvestments",
UnitFamily::Currency,
),
candidate("us-gaap", "MaturitiesOfInvestments", UnitFamily::Currency),
];
pub(crate) const ACQUISITIONS_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"PaymentsToAcquireBusinessesNetOfCashAcquired",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"PaymentsToAcquireBusinesses",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"PaymentsMadeForAcquisitionOfBusiness",
UnitFamily::Currency,
),
];
pub(crate) const OTHER_INVESTING_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"OtherCashPaymentsForInvestingActivities",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"OtherInvestingActivitiesNetCashFlow",
UnitFamily::Currency,
),
];
pub(crate) const DEBT_ISSUANCE_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"ProceedsFromIssuanceOfDebt",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"ProceedsFromIssuanceOfLongTermDebt",
UnitFamily::Currency,
),
];
pub(crate) const DEBT_REPAYMENT_CONCEPTS: &[ConceptCandidate] = &[
candidate("us-gaap", "PaymentsOfLongTermDebt", UnitFamily::Currency),
candidate("us-gaap", "RepaymentsOfLongTermDebt", UnitFamily::Currency),
candidate("us-gaap", "RepaymentsOfDebt", UnitFamily::Currency),
];
pub(crate) const EQUITY_ISSUANCE_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"ProceedsFromIssuanceOfCommonStock",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"ProceedsFromStockOptionsExercised",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"ProceedsFromIssuanceOfSharesUnderIncentiveAndShareBasedCompensationPlansIncludingStockOptions",
UnitFamily::Currency,
),
];
pub(crate) const STOCK_REPURCHASES_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"PaymentsForRepurchaseOfCommonStock",
UnitFamily::Currency,
),
candidate("us-gaap", "RepurchaseOfCommonStock", UnitFamily::Currency),
candidate(
"us-gaap",
"PaymentsForRepurchaseOfTreasuryStock",
UnitFamily::Currency,
),
];
pub(crate) const DIVIDENDS_PAID_CONCEPTS: &[ConceptCandidate] = &[
candidate("us-gaap", "PaymentsOfDividends", UnitFamily::Currency),
candidate(
"us-gaap",
"PaymentsOfDividendsCommonStock",
UnitFamily::Currency,
),
];
pub(crate) const FINANCE_LEASE_OBLIGATIONS_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"PaymentsOfFinanceLeaseObligations",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"PaymentsOfCapitalLeaseObligations",
UnitFamily::Currency,
),
];
pub(crate) const OTHER_FINANCING_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"OtherCashPaymentsForFinancingActivities",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"OtherFinancingActivitiesNetCashFlow",
UnitFamily::Currency,
),
];
pub(crate) const EXCHANGE_RATE_EFFECTS_CONCEPTS: &[ConceptCandidate] = &[candidate(
"us-gaap",
"EffectOfExchangeRateOnCashAndCashEquivalents",
UnitFamily::Currency,
)];
pub(crate) const NET_INCREASE_DECREASE_CASH_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"CashCashEquivalentsRestrictedCashAndRestrictedCashEquivalentsPeriodIncreaseDecreaseIncludingExchangeRateEffect",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"CashAndCashEquivalentsPeriodIncreaseDecrease",
UnitFamily::Currency,
),
candidate(
"ifrs-full",
"IncreaseDecreaseInCashAndCashEquivalents",
UnitFamily::Currency,
),
];
pub(crate) const BEGINNING_CASH_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
"CashCashEquivalentsRestrictedCashAndRestrictedCashEquivalentsAtBeginningOfPeriod",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"CashAndCashEquivalentsAtBeginningOfPeriod",
UnitFamily::Currency,
),
candidate(
"us-gaap",
"CashAndCashEquivalentsAtCarryingValue",
UnitFamily::Currency,
),
candidate("ifrs-full", "CashAndCashEquivalents", UnitFamily::Currency),
];
pub(crate) const ENDING_CASH_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
@@ -163,6 +532,18 @@ pub(crate) const ENDING_CASH_CONCEPTS: &[ConceptCandidate] = &[
),
candidate("ifrs-full", "CashAndCashEquivalents", UnitFamily::Currency),
];
pub(crate) const CASH_PAID_INTEREST_CONCEPTS: &[ConceptCandidate] = &[
candidate("us-gaap", "InterestPaidNet", UnitFamily::Currency),
candidate("us-gaap", "InterestPaid", UnitFamily::Currency),
candidate("us-gaap", "CashPaidForInterest", UnitFamily::Currency),
candidate("ifrs-full", "InterestPaid", UnitFamily::Currency),
];
pub(crate) const CASH_PAID_INCOME_TAXES_CONCEPTS: &[ConceptCandidate] = &[
candidate("us-gaap", "IncomeTaxesPaidNet", UnitFamily::Currency),
candidate("us-gaap", "IncomeTaxesPaid", UnitFamily::Currency),
candidate("us-gaap", "CashPaidForIncomeTaxes", UnitFamily::Currency),
candidate("ifrs-full", "IncomeTaxesPaid", UnitFamily::Currency),
];
pub(crate) const DIVIDEND_PER_SHARE_CONCEPTS: &[ConceptCandidate] = &[
candidate(
"us-gaap",
@@ -309,15 +690,184 @@ pub(crate) fn build_cash_flow_periods(
period_end: row.period_end.clone(),
filed_date: row.filed_date.clone(),
form: row.form.clone(),
net_income: value_for_period(facts, &row, NET_INCOME_CF_CONCEPTS, latest_xbrl),
depreciation_and_amortization: value_for_period(
facts,
&row,
DEPRECIATION_CONCEPTS,
latest_xbrl,
),
stock_based_compensation: value_for_period(
facts,
&row,
STOCK_BASED_COMPENSATION_CONCEPTS,
latest_xbrl,
),
deferred_income_taxes: value_for_period(
facts,
&row,
DEFERRED_INCOME_TAXES_CONCEPTS,
latest_xbrl,
),
impairment_charges: value_for_period(
facts,
&row,
IMPAIRMENT_CHARGES_CONCEPTS,
latest_xbrl,
),
loss_gain_on_sale_of_assets: value_for_period(
facts,
&row,
LOSS_GAIN_ON_SALE_OF_ASSETS_CONCEPTS,
latest_xbrl,
),
other_non_cash_items: value_for_period(
facts,
&row,
OTHER_NON_CASH_ITEMS_CONCEPTS,
latest_xbrl,
),
accounts_receivable: value_for_period(
facts,
&row,
ACCOUNTS_RECEIVABLE_CONCEPTS,
latest_xbrl,
),
inventory: value_for_period(facts, &row, INVENTORY_CONCEPTS, latest_xbrl),
prepaid_expenses_and_other_current_assets: value_for_period(
facts,
&row,
PREPAIDS_AND_OTHER_CURRENT_ASSETS_CONCEPTS,
latest_xbrl,
),
accounts_payable: value_for_period(
facts,
&row,
ACCOUNTS_PAYABLE_CONCEPTS,
latest_xbrl,
),
accrued_expenses: value_for_period(
facts,
&row,
ACCRUED_EXPENSES_CONCEPTS,
latest_xbrl,
),
deferred_revenue: value_for_period(
facts,
&row,
DEFERRED_REVENUE_CONCEPTS,
latest_xbrl,
),
other_working_capital_changes: value_for_period(
facts,
&row,
OTHER_WORKING_CAPITAL_CHANGES_CONCEPTS,
latest_xbrl,
),
change_in_operating_assets: value_for_period(
facts,
&row,
CHANGE_IN_OPERATING_ASSETS_CONCEPTS,
latest_xbrl,
),
change_in_operating_liabilities: value_for_period(
facts,
&row,
CHANGE_IN_OPERATING_LIABILITIES_CONCEPTS,
latest_xbrl,
),
other_operating_adjustments: value_for_period(
facts,
&row,
OTHER_OPERATING_ADJUSTMENTS_CONCEPTS,
latest_xbrl,
),
operating_cash_flow,
investing_cash_flow: value_for_period(facts, &row, CFI_CONCEPTS, latest_xbrl),
financing_cash_flow: value_for_period(facts, &row, CFF_CONCEPTS, latest_xbrl),
capex,
proceeds_from_sale_of_ppe: value_for_period(
facts,
&row,
PROCEEDS_FROM_SALE_OF_PPE_CONCEPTS,
latest_xbrl,
),
purchases_of_investments: value_for_period(
facts,
&row,
PURCHASES_OF_INVESTMENTS_CONCEPTS,
latest_xbrl,
),
sales_of_investments: value_for_period(
facts,
&row,
SALES_OF_INVESTMENTS_CONCEPTS,
latest_xbrl,
),
acquisitions: value_for_period(facts, &row, ACQUISITIONS_CONCEPTS, latest_xbrl),
other_investing_activities: value_for_period(
facts,
&row,
OTHER_INVESTING_CONCEPTS,
latest_xbrl,
),
investing_cash_flow: value_for_period(facts, &row, CFI_CONCEPTS, latest_xbrl),
debt_issuance: value_for_period(facts, &row, DEBT_ISSUANCE_CONCEPTS, latest_xbrl),
debt_repayment: value_for_period(facts, &row, DEBT_REPAYMENT_CONCEPTS, latest_xbrl),
equity_issuance: value_for_period(
facts,
&row,
EQUITY_ISSUANCE_CONCEPTS,
latest_xbrl,
),
stock_repurchases: value_for_period(
facts,
&row,
STOCK_REPURCHASES_CONCEPTS,
latest_xbrl,
),
dividends_paid: value_for_period(facts, &row, DIVIDENDS_PAID_CONCEPTS, latest_xbrl),
finance_lease_obligations: value_for_period(
facts,
&row,
FINANCE_LEASE_OBLIGATIONS_CONCEPTS,
latest_xbrl,
),
other_financing_activities: value_for_period(
facts,
&row,
OTHER_FINANCING_CONCEPTS,
latest_xbrl,
),
financing_cash_flow: value_for_period(facts, &row, CFF_CONCEPTS, latest_xbrl),
free_cash_flow: match (operating_cash_flow, capex) {
(Some(cfo), Some(capex)) => Some(cfo - capex.abs()),
_ => None,
},
exchange_rate_effects: value_for_period(
facts,
&row,
EXCHANGE_RATE_EFFECTS_CONCEPTS,
latest_xbrl,
),
net_increase_decrease_cash: value_for_period(
facts,
&row,
NET_INCREASE_DECREASE_CASH_CONCEPTS,
latest_xbrl,
),
beginning_cash: value_for_period(facts, &row, BEGINNING_CASH_CONCEPTS, latest_xbrl),
ending_cash: value_for_period(facts, &row, ENDING_CASH_CONCEPTS, latest_xbrl),
cash_paid_interest: value_for_period(
facts,
&row,
CASH_PAID_INTEREST_CONCEPTS,
latest_xbrl,
),
cash_paid_income_taxes: value_for_period(
facts,
&row,
CASH_PAID_INCOME_TAXES_CONCEPTS,
latest_xbrl,
),
}
})
.collect()

View File

@@ -297,12 +297,50 @@ pub struct CashFlowPeriod {
pub period_end: String,
pub filed_date: String,
pub form: String,
// Operating Activities
pub net_income: Option<f64>,
pub depreciation_and_amortization: Option<f64>,
pub stock_based_compensation: Option<f64>,
pub deferred_income_taxes: Option<f64>,
pub impairment_charges: Option<f64>,
pub loss_gain_on_sale_of_assets: Option<f64>,
pub other_non_cash_items: Option<f64>,
pub accounts_receivable: Option<f64>,
pub inventory: Option<f64>,
pub prepaid_expenses_and_other_current_assets: Option<f64>,
pub accounts_payable: Option<f64>,
pub accrued_expenses: Option<f64>,
pub deferred_revenue: Option<f64>,
pub other_working_capital_changes: Option<f64>,
pub change_in_operating_assets: Option<f64>,
pub change_in_operating_liabilities: Option<f64>,
pub other_operating_adjustments: Option<f64>,
pub operating_cash_flow: Option<f64>,
pub investing_cash_flow: Option<f64>,
pub financing_cash_flow: Option<f64>,
// Investing Activities
pub capex: Option<f64>,
pub proceeds_from_sale_of_ppe: Option<f64>,
pub purchases_of_investments: Option<f64>,
pub sales_of_investments: Option<f64>,
pub acquisitions: Option<f64>,
pub other_investing_activities: Option<f64>,
pub investing_cash_flow: Option<f64>,
// Financing Activities
pub debt_issuance: Option<f64>,
pub debt_repayment: Option<f64>,
pub equity_issuance: Option<f64>,
pub stock_repurchases: Option<f64>,
pub dividends_paid: Option<f64>,
pub finance_lease_obligations: Option<f64>,
pub other_financing_activities: Option<f64>,
pub financing_cash_flow: Option<f64>,
// Summary
pub free_cash_flow: Option<f64>,
pub exchange_rate_effects: Option<f64>,
pub net_increase_decrease_cash: Option<f64>,
pub beginning_cash: Option<f64>,
pub ending_cash: Option<f64>,
pub cash_paid_interest: Option<f64>,
pub cash_paid_income_taxes: Option<f64>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]

View File

@@ -1,11 +1,29 @@
import React from 'react';
import { CashFlowPanelData } from '../../types/financial';
import { StatementTableMinimal, formatMoney } from '../ui/StatementTableMinimal';
import { formatMoney } from '../ui/StatementTableMinimal';
interface CashFlowPanelProps {
data: CashFlowPanelData;
}
type CashFlowRow = CashFlowPanelData['periods'][number];
interface SectionHeader {
key: string;
label: string;
isSectionHeader: true;
}
interface MetricRow {
key: string;
label: string;
indent?: boolean;
isSectionHeader?: false;
render: (period: CashFlowRow) => React.ReactNode;
}
type CashFlowMetric = SectionHeader | MetricRow;
const SourceAttribution: React.FC<{ status: CashFlowPanelData['sourceStatus'] }> = ({ status }) => {
return (
<div className="text-[11px] font-mono text-term-text-muted">
@@ -17,10 +35,226 @@ const SourceAttribution: React.FC<{ status: CashFlowPanelData['sourceStatus'] }>
);
};
const metrics: CashFlowMetric[] = [
{ key: 'section-operating', label: 'Operating Activities', isSectionHeader: true },
{
key: 'netIncome',
label: 'Net Income / Net Earnings',
render: (period: CashFlowRow) => formatMoney(period.netIncome),
},
{
key: 'depreciationAndAmortization',
label: 'Depreciation & Amortization',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.depreciationAndAmortization),
},
{
key: 'stockBasedCompensation',
label: 'Stock-Based Compensation',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.stockBasedCompensation),
},
{
key: 'deferredIncomeTaxes',
label: 'Deferred Income Taxes',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.deferredIncomeTaxes),
},
{
key: 'impairmentCharges',
label: 'Impairment Charges',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.impairmentCharges),
},
{
key: 'lossGainOnSaleOfAssets',
label: 'Loss / Gain on Sale of Assets',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.lossGainOnSaleOfAssets),
},
{
key: 'otherNonCashItems',
label: 'Other Non-Cash Items',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.otherNonCashItems),
},
{
key: 'accountsReceivable',
label: 'Accounts Receivable',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.accountsReceivable),
},
{
key: 'inventory',
label: 'Inventory',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.inventory),
},
{
key: 'prepaidExpensesAndOtherCurrentAssets',
label: 'Prepaid Expenses and Other Current Assets',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.prepaidExpensesAndOtherCurrentAssets),
},
{
key: 'accountsPayable',
label: 'Accounts Payable',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.accountsPayable),
},
{
key: 'accruedExpenses',
label: 'Accrued Expenses',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.accruedExpenses),
},
{
key: 'deferredRevenue',
label: 'Deferred Revenue',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.deferredRevenue),
},
{
key: 'otherWorkingCapitalChanges',
label: 'Other Working Capital Changes',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.otherWorkingCapitalChanges),
},
{
key: 'operatingCashFlow',
label: 'Net Cash Provided by Operating Activities',
render: (period: CashFlowRow) => formatMoney(period.operatingCashFlow),
},
{ key: 'section-investing', label: 'Investing Activities', isSectionHeader: true },
{
key: 'capex',
label: 'Capital Expenditures / Purchases of Property and Equipment',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.capex),
},
{
key: 'proceedsFromSaleOfPpe',
label: 'Proceeds from Sale of Property and Equipment',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.proceedsFromSaleOfPpe),
},
{
key: 'acquisitions',
label: 'Acquisitions, Net of Cash Acquired',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.acquisitions),
},
{
key: 'purchasesOfInvestments',
label: 'Purchases of Investments',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.purchasesOfInvestments),
},
{
key: 'salesOfInvestments',
label: 'Proceeds from Sale or Maturity of Investments',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.salesOfInvestments),
},
{
key: 'otherInvestingActivities',
label: 'Other Investing Activities',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.otherInvestingActivities),
},
{
key: 'investingCashFlow',
label: 'Net Cash Used in Investing Activities',
render: (period: CashFlowRow) => formatMoney(period.investingCashFlow),
},
{ key: 'section-financing', label: 'Financing Activities', isSectionHeader: true },
{
key: 'debtIssuance',
label: 'Proceeds from Debt Issuance',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.debtIssuance),
},
{
key: 'debtRepayment',
label: 'Repayments of Debt',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.debtRepayment),
},
{
key: 'equityIssuance',
label: 'Proceeds from Equity Issuance',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.equityIssuance),
},
{
key: 'stockRepurchases',
label: 'Repurchases of Common Stock',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.stockRepurchases),
},
{
key: 'dividendsPaid',
label: 'Dividends Paid',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.dividendsPaid),
},
{
key: 'financeLeaseObligations',
label: 'Payment of Finance Lease Obligations',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.financeLeaseObligations),
},
{
key: 'otherFinancingActivities',
label: 'Other Financing Activities',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.otherFinancingActivities),
},
{
key: 'financingCashFlow',
label: 'Net Cash Provided by / Used in Financing Activities',
render: (period: CashFlowRow) => formatMoney(period.financingCashFlow),
},
{ key: 'section-summary', label: 'Supplemental / Other Cash Flow', isSectionHeader: true },
{
key: 'exchangeRateEffects',
label: 'Effect of Exchange Rate Changes on Cash',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.exchangeRateEffects),
},
{
key: 'netIncreaseDecreaseCash',
label: 'Net Increase / Decrease in Cash and Cash Equivalents',
render: (period: CashFlowRow) => formatMoney(period.netIncreaseDecreaseCash),
},
{
key: 'beginningCash',
label: 'Cash and Cash Equivalents at Beginning of Period',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.beginningCash),
},
{
key: 'endingCash',
label: 'Cash and Cash Equivalents at End of Period',
render: (period: CashFlowRow) => formatMoney(period.endingCash),
},
{
key: 'cashPaidInterest',
label: 'Cash Paid for Interest',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.cashPaidInterest),
},
{
key: 'cashPaidIncomeTaxes',
label: 'Cash Paid for Income Taxes',
indent: true,
render: (period: CashFlowRow) => formatMoney(period.cashPaidIncomeTaxes),
},
];
export const CashFlowPanel: React.FC<CashFlowPanelProps> = ({ data }) => {
return (
<div className="cashflow-panel py-4 overflow-auto">
{/* Header - Minimal */}
<header className="mb-4">
<div className="flex items-start justify-between">
<div>
@@ -40,22 +274,61 @@ export const CashFlowPanel: React.FC<CashFlowPanelProps> = ({ data }) => {
</div>
</header>
{/* Table - Minimal styling */}
<section>
<StatementTableMinimal
periods={data.periods}
metrics={[
{ key: 'cfo', label: 'Operating Cash Flow', render: (period) => formatMoney(period.operatingCashFlow) },
{ key: 'cfi', label: 'Investing Cash Flow', render: (period) => formatMoney(period.investingCashFlow) },
{ key: 'cff', label: 'Financing Cash Flow', render: (period) => formatMoney(period.financingCashFlow) },
{ key: 'capex', label: 'Capex', render: (period) => formatMoney(period.capex) },
{ key: 'fcf', label: 'Free Cash Flow', render: (period) => formatMoney(period.freeCashFlow) },
{ key: 'endingCash', label: 'Ending Cash', render: (period) => formatMoney(period.endingCash) },
]}
/>
<div className="overflow-x-auto">
<table className="min-w-full border-collapse font-mono text-sm">
<thead className="text-term-text-muted">
<tr className="border-b border-term-border-subtle">
<th className="sticky left-0 border-r border-term-border-subtle bg-term-bg px-3 py-2 text-left text-[10px] uppercase tracking-[0.18em]">
Item
</th>
{data.periods.map((period) => (
<th
key={period.label}
className="border-b border-term-border-subtle px-3 py-2 text-right text-[10px] uppercase tracking-[0.18em]"
>
{period.label}
</th>
))}
</tr>
</thead>
<tbody>
{metrics.map((metric) => {
if (metric.isSectionHeader) {
return (
<tr key={metric.key} className="border-b border-term-border-subtle">
<th
colSpan={data.periods.length + 1}
className="bg-term-bg-secondary px-3 py-2 text-left text-[10px] uppercase tracking-[0.18em] font-semibold text-term-text-muted"
>
{metric.label}
</th>
</tr>
);
}
const labelClass = metric.indent
? 'pl-6 font-normal text-term-text-muted'
: 'font-medium text-term-text';
return (
<tr key={metric.key} className="border-b border-term-border-subtle last:border-b-0">
<th className={`sticky left-0 border-r border-term-border-subtle bg-term-bg px-3 py-2 text-left text-term-text ${labelClass}`}>
{metric.label}
</th>
{data.periods.map((period) => (
<td key={`${metric.key}-${period.label}`} className="px-3 py-2 text-right text-term-text">
<span className="inline-block">{metric.render(period)}</span>
</td>
))}
</tr>
);
})}
</tbody>
</table>
</div>
</section>
{/* Footer - Minimal attribution */}
<footer className="mt-4 border-t border-term-border-subtle pt-4">
<SourceAttribution status={data.sourceStatus} />
</footer>

View File

@@ -118,15 +118,35 @@ const summarizeCashFlow = (panel: CashFlowPanelData, sourceCommand?: string) =>
const rows = latest
? [
`Latest period: ${latest.label}.`,
latest.netIncome != null ? `Net income: ${latest.netIncome.toLocaleString()}.` : null,
latest.depreciationAndAmortization != null
? `Depreciation & amortization: ${latest.depreciationAndAmortization.toLocaleString()}.`
: null,
latest.operatingCashFlow != null
? `Operating cash flow: ${latest.operatingCashFlow.toLocaleString()}.`
: null,
latest.capex != null ? `Capital expenditures: ${latest.capex.toLocaleString()}.` : null,
latest.freeCashFlow != null
? `Free cash flow: ${latest.freeCashFlow.toLocaleString()}.`
: null,
latest.investingCashFlow != null
? `Investing cash flow: ${latest.investingCashFlow.toLocaleString()}.`
: null,
latest.financingCashFlow != null
? `Financing cash flow: ${latest.financingCashFlow.toLocaleString()}.`
: null,
latest.dividendsPaid != null
? `Dividends paid: ${latest.dividendsPaid.toLocaleString()}.`
: null,
latest.stockRepurchases != null
? `Stock repurchases: ${latest.stockRepurchases.toLocaleString()}.`
: null,
latest.endingCash != null
? `Ending cash: ${latest.endingCash.toLocaleString()}.`
: null,
].filter(Boolean) as string[]
: ['No cash flow rows loaded.'];
return summarizeStatementPanel('cash flow summary', panel.symbol, panel.latestFiling, rows, sourceCommand);
return summarizeStatementPanel('cash flow statement', panel.symbol, panel.latestFiling, rows, sourceCommand);
};
const summarizeDividends = (panel: DividendsPanelData, sourceCommand?: string) =>

View File

@@ -175,12 +175,50 @@ export interface CashFlowPeriod {
periodEnd: string;
filedDate: string;
form: string;
// Operating Activities
netIncome?: number;
depreciationAndAmortization?: number;
stockBasedCompensation?: number;
deferredIncomeTaxes?: number;
impairmentCharges?: number;
lossGainOnSaleOfAssets?: number;
otherNonCashItems?: number;
accountsReceivable?: number;
inventory?: number;
prepaidExpensesAndOtherCurrentAssets?: number;
accountsPayable?: number;
accruedExpenses?: number;
deferredRevenue?: number;
otherWorkingCapitalChanges?: number;
changeInOperatingAssets?: number;
changeInOperatingLiabilities?: number;
otherOperatingAdjustments?: number;
operatingCashFlow?: number;
investingCashFlow?: number;
financingCashFlow?: number;
// Investing Activities
capex?: number;
proceedsFromSaleOfPpe?: number;
purchasesOfInvestments?: number;
salesOfInvestments?: number;
acquisitions?: number;
otherInvestingActivities?: number;
investingCashFlow?: number;
// Financing Activities
debtIssuance?: number;
debtRepayment?: number;
equityIssuance?: number;
stockRepurchases?: number;
dividendsPaid?: number;
financeLeaseObligations?: number;
otherFinancingActivities?: number;
financingCashFlow?: number;
// Summary
freeCashFlow?: number;
exchangeRateEffects?: number;
netIncreaseDecreaseCash?: number;
beginningCash?: number;
endingCash?: number;
cashPaidInterest?: number;
cashPaidIncomeTaxes?: number;
}
export interface DividendEvent {