rrt/crates/rrt-runtime/src/derived/finance/distress.rs

168 lines
6.6 KiB
Rust

use crate::derived::{
runtime_company_annual_finance_state, runtime_company_control_transfer_stat_value_f64,
runtime_company_stat_value_f64, runtime_company_support_adjusted_share_price_scalar,
runtime_round_f64_to_i64, runtime_world_annual_finance_mode_active,
runtime_world_bankruptcy_allowed,
};
use crate::event::metrics::{
RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
RuntimeCompanyStatSelector,
};
use crate::state::{
RuntimeCompanyAnnualCreditorPressureState, RuntimeCompanyAnnualDeepDistressState, RuntimeState,
};
pub fn runtime_company_annual_creditor_pressure_state(
state: &RuntimeState,
company_id: u32,
) -> Option<RuntimeCompanyAnnualCreditorPressureState> {
let annual_finance_state = runtime_company_annual_finance_state(state, company_id)?;
let current_cash_plus_slot_12_total =
runtime_company_control_transfer_stat_value_f64(state, company_id, 0x12)
.and_then(runtime_round_f64_to_i64)
.and_then(|slot_12| {
runtime_company_control_transfer_stat_value_f64(
state,
company_id,
RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
)
.and_then(runtime_round_f64_to_i64)
.map(|current_cash| current_cash + slot_12)
});
let support_adjusted_share_price_scalar =
runtime_company_support_adjusted_share_price_scalar(state, company_id)
.and_then(runtime_round_f64_to_i64);
let current_fuel_cost = runtime_company_stat_value_f64(
state,
company_id,
RuntimeCompanyStatSelector {
family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER,
slot_id: 0x09,
},
)
.and_then(runtime_round_f64_to_i64);
let recent_bad_net_profit_year_count = annual_finance_state
.trailing_full_year_net_profits
.iter()
.take(3)
.filter(|value| **value < -10_000)
.count() as u32;
let recent_peak_revenue = annual_finance_state
.trailing_full_year_revenues
.iter()
.take(3)
.copied()
.max();
let recent_three_year_net_profit_total =
if annual_finance_state.trailing_full_year_net_profits.len() >= 3 {
Some(
annual_finance_state
.trailing_full_year_net_profits
.iter()
.take(3)
.sum::<i64>(),
)
} else {
None
};
let pressure_ladder_cash_floor = recent_peak_revenue.map(|revenue| {
if revenue < 120_000 {
-600_000
} else if revenue < 230_000 {
-1_100_000
} else if revenue < 340_000 {
-1_600_000
} else {
-2_000_000
}
});
let support_adjusted_share_price_floor = Some(if recent_bad_net_profit_year_count == 3 {
20
} else {
15
});
let current_fuel_cost_floor = pressure_ladder_cash_floor.map(|floor| floor * 8 / 100);
let eligible_for_bankruptcy_branch = runtime_world_annual_finance_mode_active(state)
== Some(true)
&& runtime_world_bankruptcy_allowed(state) == Some(true)
&& annual_finance_state
.years_since_last_bankruptcy
.is_some_and(|years| years >= 13)
&& annual_finance_state
.years_since_founding
.is_some_and(|years| years >= 4)
&& recent_bad_net_profit_year_count >= 2
&& current_cash_plus_slot_12_total
.zip(pressure_ladder_cash_floor)
.is_some_and(|(value, floor)| value <= floor)
&& support_adjusted_share_price_scalar
.zip(support_adjusted_share_price_floor)
.is_some_and(|(value, floor)| value >= floor)
&& current_fuel_cost
.zip(current_fuel_cost_floor)
.is_some_and(|(value, floor)| value <= floor)
&& recent_three_year_net_profit_total.is_some_and(|value| value <= -60_000);
Some(RuntimeCompanyAnnualCreditorPressureState {
company_id,
annual_mode_active: runtime_world_annual_finance_mode_active(state),
bankruptcy_allowed: runtime_world_bankruptcy_allowed(state),
years_since_last_bankruptcy: annual_finance_state.years_since_last_bankruptcy,
years_since_founding: annual_finance_state.years_since_founding,
recent_bad_net_profit_year_count,
recent_peak_revenue,
recent_three_year_net_profit_total,
pressure_ladder_cash_floor,
current_cash_plus_slot_12_total,
support_adjusted_share_price_floor,
support_adjusted_share_price_scalar,
current_fuel_cost,
current_fuel_cost_floor,
eligible_for_bankruptcy_branch,
})
}
pub fn runtime_company_annual_deep_distress_state(
state: &RuntimeState,
company_id: u32,
) -> Option<RuntimeCompanyAnnualDeepDistressState> {
let annual_finance_state = runtime_company_annual_finance_state(state, company_id)?;
let current_cash = runtime_company_control_transfer_stat_value_f64(
state,
company_id,
RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
)
.and_then(runtime_round_f64_to_i64);
let recent_first_three_net_profit_years = annual_finance_state
.trailing_full_year_net_profits
.iter()
.take(3)
.copied()
.collect::<Vec<_>>();
let deep_distress_cash_floor = Some(-300_000);
let deep_distress_net_profit_floor = Some(-20_000);
let eligible_for_bankruptcy_fallback = runtime_world_bankruptcy_allowed(state) == Some(true)
&& current_cash
.zip(deep_distress_cash_floor)
.is_some_and(|(value, floor)| value <= floor)
&& annual_finance_state
.years_since_founding
.is_some_and(|years| years >= 3)
&& recent_first_three_net_profit_years.len() == 3
&& recent_first_three_net_profit_years
.iter()
.all(|value| *value <= deep_distress_net_profit_floor.unwrap())
&& annual_finance_state
.years_since_last_bankruptcy
.is_some_and(|years| years >= 5);
Some(RuntimeCompanyAnnualDeepDistressState {
company_id,
bankruptcy_allowed: runtime_world_bankruptcy_allowed(state),
years_since_founding: annual_finance_state.years_since_founding,
years_since_last_bankruptcy: annual_finance_state.years_since_last_bankruptcy,
current_cash,
recent_first_three_net_profit_years,
deep_distress_cash_floor,
deep_distress_net_profit_floor,
eligible_for_bankruptcy_fallback,
})
}