From fd6046a85c6bbd24cc44c6e982b6acbf853bacfd Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sat, 18 Apr 2026 05:49:37 -0700 Subject: [PATCH] Persist annual finance news events in runtime state --- README.md | 4 ++- crates/rrt-runtime/src/import.rs | 1 + crates/rrt-runtime/src/lib.rs | 11 +++---- crates/rrt-runtime/src/runtime.rs | 21 +++++++++++++ crates/rrt-runtime/src/step.rs | 52 ++++++++++++++++++------------- crates/rrt-runtime/src/summary.rs | 33 ++++++++++++++++++++ docs/README.md | 2 ++ docs/runtime-rehost-plan.md | 3 ++ 8 files changed, 98 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index f0e8685..6f9fe13 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,9 @@ surface too: the runtime chooses one annual-finance action per active company an the shellless creditor-pressure-bankruptcy, deep-distress-bankruptcy, dividend-adjustment, stock-repurchase, stock-issue, and bond-issue branches by mutating owned company activity, dividend, company stat-post, outstanding-share, issue-calendar, and live bond-slot state instead -of stopping at reader-only diagnostics. +of stopping at reader-only diagnostics. That same service state now also persists the last emitted +annual-finance news events as structured runtime records carrying company id, exact selector label, +action label, and the grounded debt/share payload totals used by the shell news layer. Those bankruptcy branches now follow the grounded owner semantics too: they stamp the bankruptcy year and halve live bond principals in place instead of treating bankruptcy as a liquidation path. The same save-native live bond-slot surface now also carries per-slot maturity years all the way diff --git a/crates/rrt-runtime/src/import.rs b/crates/rrt-runtime/src/import.rs index f506bcc..86b6bdb 100644 --- a/crates/rrt-runtime/src/import.rs +++ b/crates/rrt-runtime/src/import.rs @@ -13998,6 +13998,7 @@ mod tests { annual_stock_repurchase_last_share_count: 0, annual_stock_issue_last_share_count: 0, annual_finance_last_news_family_candidates: BTreeMap::new(), + annual_finance_last_news_events: Vec::new(), chairman_issue_opinion_terms_raw_i32: BTreeMap::new(), chairman_personality_raw_u8: BTreeMap::new(), }, diff --git a/crates/rrt-runtime/src/lib.rs b/crates/rrt-runtime/src/lib.rs index 146bd83..772178e 100644 --- a/crates/rrt-runtime/src/lib.rs +++ b/crates/rrt-runtime/src/lib.rs @@ -49,9 +49,9 @@ pub use runtime::{ RUNTIME_COMPANY_STAT_SLOT_CREDIT_RATING, RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH, RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN, RUNTIME_WORLD_ISSUE_CREDIT_MARKET, RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE, RUNTIME_WORLD_ISSUE_MANAGEMENT_ATTITUDE, - RUNTIME_WORLD_ISSUE_PRIME_RATE, RuntimeCargoCatalogEntry, RuntimeCargoClass, - RuntimeCargoPriceTarget, RuntimeCargoProductionTarget, RuntimeChairmanMetric, - RuntimeChairmanProfile, RuntimeChairmanTarget, RuntimeCompany, + RUNTIME_WORLD_ISSUE_PRIME_RATE, RuntimeAnnualFinanceNewsEvent, RuntimeCargoCatalogEntry, + RuntimeCargoClass, RuntimeCargoPriceTarget, RuntimeCargoProductionTarget, + RuntimeChairmanMetric, RuntimeChairmanProfile, RuntimeChairmanTarget, RuntimeCompany, RuntimeCompanyAnnualBondPolicyState, RuntimeCompanyAnnualCreditorPressureState, RuntimeCompanyAnnualDeepDistressState, RuntimeCompanyAnnualDividendPolicyState, RuntimeCompanyAnnualFinancePolicyAction, RuntimeCompanyAnnualFinancePolicyState, @@ -124,10 +124,7 @@ pub use smp::{ inspect_save_company_and_chairman_analysis_file, inspect_smp_bytes, inspect_smp_file, load_save_slice_file, load_save_slice_from_report, }; -pub use step::{ - AnnualFinanceNewsEvent, BoundaryEvent, ServiceEvent, StepCommand, StepResult, - execute_step_command, -}; +pub use step::{BoundaryEvent, ServiceEvent, StepCommand, StepResult, execute_step_command}; pub use summary::RuntimeSummary; pub use win::{ WinAnonymousSelectorRecord, WinHeaderWord, WinInspectionReport, WinReferenceDeltaFrequency, diff --git a/crates/rrt-runtime/src/runtime.rs b/crates/rrt-runtime/src/runtime.rs index dcc3cf6..d694744 100644 --- a/crates/rrt-runtime/src/runtime.rs +++ b/crates/rrt-runtime/src/runtime.rs @@ -1214,6 +1214,17 @@ impl RuntimeEventRecordTemplate { } } +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct RuntimeAnnualFinanceNewsEvent { + pub company_id: u32, + pub selector_label: String, + pub action_label: String, + pub retired_principal_total: u64, + pub issued_principal_total: u64, + pub repurchased_share_count: u64, + pub issued_share_count: u64, +} + #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct RuntimeServiceState { #[serde(default)] @@ -1247,6 +1258,8 @@ pub struct RuntimeServiceState { #[serde(default)] pub annual_finance_last_news_family_candidates: BTreeMap, #[serde(default)] + pub annual_finance_last_news_events: Vec, + #[serde(default)] pub chairman_issue_opinion_terms_raw_i32: BTreeMap>, #[serde(default)] pub chairman_personality_raw_u8: BTreeMap, @@ -2169,6 +2182,14 @@ impl RuntimeState { )); } } + for news_event in &self.service_state.annual_finance_last_news_events { + if !seen_company_ids.contains(&news_event.company_id) { + return Err(format!( + "service_state.annual_finance_last_news_events references unknown company_id {}", + news_event.company_id + )); + } + } for chairman_profile_id in self .service_state .chairman_issue_opinion_terms_raw_i32 diff --git a/crates/rrt-runtime/src/step.rs b/crates/rrt-runtime/src/step.rs index 4608211..d1d663a 100644 --- a/crates/rrt-runtime/src/step.rs +++ b/crates/rrt-runtime/src/step.rs @@ -4,7 +4,7 @@ use std::collections::BTreeSet; use serde::{Deserialize, Serialize}; use crate::runtime::{ - runtime_company_bond_interest_rate_quote_f64, + RuntimeAnnualFinanceNewsEvent, runtime_company_bond_interest_rate_quote_f64, runtime_company_support_adjusted_share_price_scalar_with_pressure_f64, runtime_round_f64_to_i64, }; @@ -59,17 +59,6 @@ pub struct BoundaryEvent { pub calendar: crate::CalendarPoint, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct AnnualFinanceNewsEvent { - pub company_id: u32, - pub selector_label: String, - pub action_label: String, - pub retired_principal_total: u64, - pub issued_principal_total: u64, - pub repurchased_share_count: u64, - pub issued_share_count: u64, -} - #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ServiceEvent { pub kind: String, @@ -85,7 +74,7 @@ pub struct ServiceEvent { #[serde(default)] pub finance_news_family_candidates: BTreeMap, #[serde(default)] - pub annual_finance_news_events: Vec, + pub annual_finance_news_events: Vec, pub dirty_rerun: bool, } @@ -520,6 +509,7 @@ fn service_company_annual_finance_policy( .service_state .annual_finance_last_news_family_candidates .retain(|company_id, _| active_company_id_set.contains(company_id)); + state.service_state.annual_finance_last_news_events.clear(); state.service_state.annual_finance_service_calls += 1; state.service_state.annual_bond_last_retired_principal_total = 0; state.service_state.annual_bond_last_issued_principal_total = 0; @@ -593,7 +583,7 @@ fn service_company_annual_finance_policy( .annual_finance_last_news_family_candidates .insert(company_id, label.to_string()); finance_news_family_candidates.insert(company_id, label.to_string()); - annual_finance_news_events.push(AnnualFinanceNewsEvent { + let news_event = RuntimeAnnualFinanceNewsEvent { company_id, selector_label: label.to_string(), action_label: @@ -613,7 +603,12 @@ fn service_company_annual_finance_policy( issued_share_count: state .service_state .annual_stock_issue_last_share_count, - }); + }; + state + .service_state + .annual_finance_last_news_events + .push(news_event.clone()); + annual_finance_news_events.push(news_event); } applied_effect_count += 1; mutated_company_ids.insert(company_id); @@ -699,7 +694,7 @@ fn service_company_annual_finance_policy( .annual_finance_last_news_family_candidates .insert(company_id, label.to_string()); finance_news_family_candidates.insert(company_id, label.to_string()); - annual_finance_news_events.push(AnnualFinanceNewsEvent { + let news_event = RuntimeAnnualFinanceNewsEvent { company_id, selector_label: label.to_string(), action_label: @@ -719,7 +714,12 @@ fn service_company_annual_finance_policy( issued_share_count: state .service_state .annual_stock_issue_last_share_count, - }); + }; + state + .service_state + .annual_finance_last_news_events + .push(news_event.clone()); + annual_finance_news_events.push(news_event); } applied_effect_count += 1; mutated_company_ids.insert(company_id); @@ -880,7 +880,7 @@ fn service_company_annual_finance_policy( .annual_finance_last_news_family_candidates .insert(company_id, label.to_string()); finance_news_family_candidates.insert(company_id, label.to_string()); - annual_finance_news_events.push(AnnualFinanceNewsEvent { + let news_event = RuntimeAnnualFinanceNewsEvent { company_id, selector_label: label.to_string(), action_label: @@ -900,7 +900,12 @@ fn service_company_annual_finance_policy( issued_share_count: state .service_state .annual_stock_issue_last_share_count, - }); + }; + state + .service_state + .annual_finance_last_news_events + .push(news_event.clone()); + annual_finance_news_events.push(news_event); } applied_effect_count += 1; mutated_company_ids.insert(company_id); @@ -991,7 +996,7 @@ fn service_company_annual_finance_policy( .annual_finance_last_news_family_candidates .insert(company_id, label.to_string()); finance_news_family_candidates.insert(company_id, label.to_string()); - annual_finance_news_events.push(AnnualFinanceNewsEvent { + let news_event = RuntimeAnnualFinanceNewsEvent { company_id, selector_label: label.to_string(), action_label: @@ -1011,7 +1016,12 @@ fn service_company_annual_finance_policy( issued_share_count: state .service_state .annual_stock_issue_last_share_count, - }); + }; + state + .service_state + .annual_finance_last_news_events + .push(news_event.clone()); + annual_finance_news_events.push(news_event); } applied_effect_count += 1; mutated_company_ids.insert(company_id); diff --git a/crates/rrt-runtime/src/summary.rs b/crates/rrt-runtime/src/summary.rs index b30cc30..550b1bf 100644 --- a/crates/rrt-runtime/src/summary.rs +++ b/crates/rrt-runtime/src/summary.rs @@ -175,6 +175,8 @@ pub struct RuntimeSummary { pub selected_company_dividend_eligible_for_adjustment_branch: Option, pub selected_company_annual_finance_policy_action: Option, pub selected_company_annual_finance_news_family_candidate: Option, + pub selected_company_annual_finance_last_news_selector: Option, + pub annual_finance_last_news_event_count: usize, pub selected_company_annual_finance_policy_creditor_pressure_bankruptcy_eligible: Option, pub selected_company_annual_finance_policy_deep_distress_bankruptcy_fallback_eligible: Option, @@ -818,6 +820,21 @@ impl RuntimeSummary { .get(&company_id) }) .cloned(), + selected_company_annual_finance_last_news_selector: state + .selected_company_id + .and_then(|company_id| { + state + .service_state + .annual_finance_last_news_events + .iter() + .rev() + .find(|news| news.company_id == company_id) + }) + .map(|news| news.selector_label.clone()), + annual_finance_last_news_event_count: state + .service_state + .annual_finance_last_news_events + .len(), selected_company_annual_finance_policy_creditor_pressure_bankruptcy_eligible: selected_company_annual_finance_policy_state .as_ref() @@ -3392,6 +3409,15 @@ mod tests { 14, "4053".to_string(), )]), + annual_finance_last_news_events: vec![crate::RuntimeAnnualFinanceNewsEvent { + company_id: 14, + selector_label: "4053".to_string(), + action_label: "stock_issue".to_string(), + retired_principal_total: 0, + issued_principal_total: 0, + repurchased_share_count: 0, + issued_share_count: 4_000, + }], company_market_state: BTreeMap::from([( 14, crate::RuntimeCompanyMarketState { @@ -3517,6 +3543,13 @@ mod tests { .as_deref(), Some("4053") ); + assert_eq!( + summary + .selected_company_annual_finance_last_news_selector + .as_deref(), + Some("4053") + ); + assert_eq!(summary.annual_finance_last_news_event_count, 1); } #[test] diff --git a/docs/README.md b/docs/README.md index f5fbd15..e29695d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -161,6 +161,8 @@ The highest-value next passes are now: equity-offering and buyback news tails; runtime summaries now also expose the grounded retired-versus-issued relation directly, and annual finance service now maps that same relation onto the exact debt headline selectors `2882..2886` + while persisting the last emitted annual-finance news events as structured runtime-owned records + carrying company id, selector label, action label, and the grounded debt/share payload totals - the project rule on the remaining closure work is now explicit too: when one runtime-facing field is still ambiguous, prefer rehosting the owning source state or real reader/setter family first instead of guessing another derived leaf field from neighboring raw offsets diff --git a/docs/runtime-rehost-plan.md b/docs/runtime-rehost-plan.md index c8ebe75..89a1d4c 100644 --- a/docs/runtime-rehost-plan.md +++ b/docs/runtime-rehost-plan.md @@ -240,6 +240,9 @@ principal totals needed by the later debt-news tail, plus the issued-share and r counts needed by the later equity-offering and buyback news tails. Runtime summaries also expose the grounded retired-versus-issued relation directly, and annual finance service now maps that relation onto the exact debt headline selectors `2882..2886`. +The same service state now also persists the last emitted annual-finance news events as structured +runtime-owned records carrying company id, selector label, action label, and the grounded +debt/share payload totals. The annual dividend-adjustment lane now rides that same seam too: the runtime now rehosts the shared year-or-control-transfer metric reader, the board-approved dividend ceiling helper, and the full annual dividend branch over owned cash, public float, current dividend, and building-growth