Persist annual finance news events in runtime state

This commit is contained in:
Jan Petykiewicz 2026-04-18 05:49:37 -07:00
commit fd6046a85c
8 changed files with 98 additions and 29 deletions

View file

@ -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

View file

@ -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(),
},

View file

@ -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,

View file

@ -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<u32, String>,
#[serde(default)]
pub annual_finance_last_news_events: Vec<RuntimeAnnualFinanceNewsEvent>,
#[serde(default)]
pub chairman_issue_opinion_terms_raw_i32: BTreeMap<u32, Vec<i32>>,
#[serde(default)]
pub chairman_personality_raw_u8: BTreeMap<u32, u8>,
@ -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

View file

@ -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<u32, String>,
#[serde(default)]
pub annual_finance_news_events: Vec<AnnualFinanceNewsEvent>,
pub annual_finance_news_events: Vec<RuntimeAnnualFinanceNewsEvent>,
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);

View file

@ -175,6 +175,8 @@ pub struct RuntimeSummary {
pub selected_company_dividend_eligible_for_adjustment_branch: Option<bool>,
pub selected_company_annual_finance_policy_action: Option<String>,
pub selected_company_annual_finance_news_family_candidate: Option<String>,
pub selected_company_annual_finance_last_news_selector: Option<String>,
pub annual_finance_last_news_event_count: usize,
pub selected_company_annual_finance_policy_creditor_pressure_bankruptcy_eligible: Option<bool>,
pub selected_company_annual_finance_policy_deep_distress_bankruptcy_fallback_eligible:
Option<bool>,
@ -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]

View file

@ -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

View file

@ -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