use crate::params::{BenefitParams, TaxTables};
use crate::plan::{Dollars, Income, IncomeKind, Issue, Plan, TriggerForm, push_issue};
use crate::tax;
use super::resolve::Resolver;
use super::year::Simulation;
use super::{plan_inflation, scale};
impl Simulation<'_> {
pub(super) fn record_covered(&mut self, owner: &str, year: i16, nominal: Dollars) {
let Some(person) = self.plan.person(owner) else {
return;
};
*self
.covered
.entry(person.id.as_str())
.or_default()
.entry(year)
.or_default() += nominal;
}
pub(super) fn social_security_paid(
&self,
income: &Income,
year: i16,
nominal: Dollars,
) -> Dollars {
if income.kind != IncomeKind::SocialSecurity {
return nominal;
}
let start = income.start.as_ref().and_then(|start| start.form().ok());
let Some(TriggerForm::Age { owner, years }) = start else {
return nominal;
};
if owner != income.owner {
return nominal;
}
let claim_age = i16::from(years);
match self.plan.person(owner) {
Some(person) if person.age_in_year(year) == claim_age => {
scale(nominal, tax::claim_year_share(person.birth, claim_age))
}
_ => nominal,
}
}
pub(super) fn derived_benefit(&mut self, index: usize, claim_year: i16) -> Dollars {
if let Some(benefit) = self.benefits[index] {
return benefit;
}
let benefit = self.compute_benefit(index, claim_year);
self.benefits[index] = Some(benefit);
benefit
}
fn compute_benefit(&self, index: usize, claim_year: i16) -> Dollars {
let income = &self.plan.income[index];
let owner = self.plan.person(&income.owner);
let (Some(owner), Some(params)) = (owner, benefit_params(self.plan, self.tables)) else {
return 0;
};
let mut earnings = owner.earnings.clone();
let covered = self.covered.get(owner.id.as_str()).into_iter().flatten();
for (&year, &amount) in covered.filter(|&(&year, _)| year < claim_year) {
earnings.entry(year).or_insert(amount);
}
let at_eligibility = tax::social_security_benefit(
¶ms,
owner.birth.year(),
owner.age_in_year(claim_year),
&earnings,
);
let eligibility_year = owner.birth.year() + tax::EARLIEST_CLAIM_AGE;
scale(
at_eligibility,
1.0 / self.cola_factor(income.cola, eligibility_year),
)
}
}
pub(crate) fn benefit_params(plan: &Plan, tables: &TaxTables) -> Option<BenefitParams> {
let mut params = tables
.params_for(plan.plan.start_year, &plan_inflation(plan))
.social_security
.benefit?;
if let Some(rate) = plan.plan.wage_growth {
params.wage_growth = rate;
}
Some(params)
}
pub(super) fn check_derived_claims(plan: &Plan, tables: &TaxTables) -> Vec<Issue> {
let mut issues = Vec::new();
if !plan.income.iter().any(Income::is_derived) {
return issues;
}
let resolver = Resolver::new(plan);
let has_params = tables
.params_for(plan.plan.start_year, &plan_inflation(plan))
.social_security
.benefit
.is_some();
for (i, income) in plan
.income
.iter()
.enumerate()
.filter(|(_, income)| income.is_derived())
{
let claim_year = income
.start
.as_ref()
.and_then(|start| resolver.trigger_year(plan, start));
let Some((owner, claim_year)) = plan.person(&income.owner).zip(claim_year) else {
continue;
};
let age = owner.age_in_year(claim_year);
if age < tax::EARLIEST_CLAIM_AGE {
push_issue(
&mut issues,
format!("income[{i}].start"),
format!(
"a computed benefit needs a claim at {} or later; this claims at {age}",
tax::EARLIEST_CLAIM_AGE
),
);
}
if !has_params {
push_issue(
&mut issues,
format!("income[{i}].amount"),
format!(
"the tax parameters for {} carry no [social-security.benefit] table to compute it from",
plan.plan.start_year
),
);
}
}
issues
}