use std::collections::BTreeMap;
use super::claims::{Claim, computed_income};
use crate::params::TaxTables;
use crate::plan::{Dollars, Income, Person, Plan};
use crate::project::benefit_params;
use crate::project::{deflate, horizon_year, project};
use crate::tax::{self, EARLIEST_CLAIM_AGE, LATEST_CREDIT_AGE, MONTHS_PER_YEAR};
#[must_use]
pub fn benefit_estimates(plan: &Plan, tables: &TaxTables, person: &str) -> [Option<Dollars>; 3] {
let Some(owner) = plan
.person(person)
.filter(|owner| !owner.earnings.is_empty())
else {
return [None; 3];
};
let months = tax::full_retirement_months(owner.birth.year());
let full = (months + MONTHS_PER_YEAR - 1) / MONTHS_PER_YEAR;
let mut estimating = plan.clone();
let held = estimating
.income
.iter()
.position(|income| income.is_benefit_of(&owner.id));
let index = held.unwrap_or_else(|| {
estimating.income.push(computed_income(&owner.id));
estimating.income.len() - 1
});
estimating.income[index].amount = None;
[EARLIEST_CLAIM_AGE, full as i16, LATEST_CREDIT_AGE]
.map(|age| estimate_at(&mut estimating, tables, index, owner, age))
}
fn estimate_at(
estimating: &mut Plan,
tables: &TaxTables,
index: usize,
owner: &Person,
age: i16,
) -> Option<Dollars> {
let first_whole_year = owner.birth.year() + age + 1;
if age < owner.age_in_year(estimating.plan.start_year)
|| first_whole_year > horizon_year(estimating)
{
return None;
}
let income = &mut estimating.income[index];
let claim = Claim {
income: income.id.clone(),
owner: owner.id.clone(),
age: u8::try_from(age).ok()?,
};
income.start = Some(claim.trigger());
let projection = project(estimating, tables);
let row = projection.row(first_whole_year)?;
let paid = *row.income.get(&claim.income)?;
Some(deflate(paid, row.deflator * f64::from(MONTHS_PER_YEAR)))
}
pub fn career_at_salary(
plan: &Plan,
tables: &TaxTables,
person: &str,
) -> Result<BTreeMap<i16, Dollars>, String> {
let owner = plan
.person(person)
.ok_or_else(|| format!("no person `{person}`"))?;
let start_year = plan.plan.start_year;
let params = benefit_params(plan, tables)
.ok_or_else(|| format!("the {start_year} table carries no benefit formula"))?;
let salary = salary_in_start_year(plan, tables, person);
if salary <= 0 {
let is_uncovered = |income: &Income| income.owner == person && !income.social_security;
let why = if plan.income.iter().any(is_uncovered) {
format!("{person}'s salary pays into no Social Security")
} else {
format!("{person} is paid no salary in {start_year}")
};
return Err(format!("{why} to fill a career from; import a statement"));
}
let career = tax::career_before(¶ms, owner.birth.year(), salary, start_year);
if career.is_empty() {
return Err(format!("{person} has no working year before {start_year}"));
}
Ok(career)
}
fn salary_in_start_year(plan: &Plan, tables: &TaxTables, person: &str) -> Dollars {
let projection = project(plan, tables);
let Some(first) = projection.years.first() else {
return 0;
};
let nominal = first.covered.get(person).copied().unwrap_or(0);
deflate(nominal, first.deflator)
}