use std::path::{Path, PathBuf};
use clap::Args;
use retiretui_engine::optimize::{Claim, ClaimSearch, claims_overlay, optimize_claims};
use retiretui_engine::project::Summary;
use schemars::JsonSchema;
use serde::Serialize;
use crate::commands::project::OutputFormat;
use crate::commands::table::summary_table;
#[derive(Args)]
pub struct ClaimArgs {
pub plan: PathBuf,
#[arg(long)]
pub income: Vec<String>,
#[arg(long)]
pub write: Option<PathBuf>,
#[arg(long)]
pub nominal: bool,
#[arg(long, value_enum, default_value_t = OutputFormat::Table)]
pub format: OutputFormat,
#[arg(long)]
pub tax_dir: Vec<PathBuf>,
}
pub fn run(args: &ClaimArgs) -> anyhow::Result<()> {
let tables = crate::commands::load_tables(&args.tax_dir)?;
let plan = crate::commands::load_validated_plan(&args.plan, &tables)?;
let search = optimize_claims(&plan, &tables, &args.income, &[])
.map_err(|issues| anyhow::Error::msg(crate::commands::issue_listing(&issues)))?;
let deflated = !args.nominal;
match args.format {
OutputFormat::Json => println!(
"{}",
serde_json::to_string_pretty(&ClaimsReply::new(&search, deflated))?
),
OutputFormat::Table => print!("{}", claims_table(&search, deflated)),
}
if let Some(out) = &args.write {
write_best(out, &args.plan, &search)?;
println!("wrote {}", out.display());
}
Ok(())
}
#[derive(Serialize, JsonSchema)]
pub struct ClaimsReply {
pub baseline: Summary,
pub incomes: Vec<String>,
pub candidates: Vec<ClaimEntry>,
}
#[derive(Serialize, JsonSchema)]
pub struct ClaimEntry {
pub claims: Vec<Claim>,
pub summary: Summary,
}
impl ClaimsReply {
pub fn new(search: &ClaimSearch, deflated: bool) -> Self {
Self {
baseline: search.baseline.summary(deflated),
incomes: search.incomes.clone(),
candidates: search
.candidates
.iter()
.map(|candidate| ClaimEntry {
claims: candidate.claims.clone(),
summary: candidate.projection.summary(deflated),
})
.collect(),
}
}
}
fn claims_table(search: &ClaimSearch, deflated: bool) -> String {
let mut leading = vec!["rank"];
leading.extend(search.incomes.iter().map(String::as_str));
let mut baseline = vec!["baseline".to_owned()];
baseline.extend(search.incomes.iter().map(|_| "-".to_owned()));
let mut rows = vec![(baseline, search.baseline.summary(deflated))];
rows.extend(
search
.candidates
.iter()
.enumerate()
.map(|(rank, candidate)| {
let mut cells = vec![(rank + 1).to_string()];
cells.extend(candidate.claims.iter().map(|claim| claim.age.to_string()));
(cells, candidate.projection.summary(deflated))
}),
);
summary_table(&leading, rows)
}
fn write_best(out: &Path, plan_path: &Path, search: &ClaimSearch) -> anyhow::Result<()> {
let base = crate::commands::overlay_base(out, plan_path)?;
let overlay = claims_overlay(&base, &search.added, &search.best().claims)?;
crate::commands::write_atomic(out, &overlay)?;
Ok(())
}