1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
use std::collections::BTreeMap;
use serde::{Deserialize, Deserializer, Serialize};
use super::Dollars;
use super::dates::PlanDate;
use super::triggers::Trigger;
use crate::tax;
/// Federal filing status. These two are the supported statuses; they cover a
/// one- or two-person household and the survivor transition between them.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum FilingStatus {
/// One filer.
Single,
/// Married filing jointly.
MarriedJoint,
}
impl FilingStatus {
/// Every status, in the order the schema declares them.
pub const ALL: &'static [Self] = &[Self::Single, Self::MarriedJoint];
/// The status as a plan file spells it.
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Single => "single",
Self::MarriedJoint => "married-joint",
}
}
}
/// A member of the household.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Person {
/// Unique id other plan items reference as `owner`.
pub id: String,
/// The person's name as it is shown; the id where there is none.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
/// Birth date.
pub birth: PlanDate,
/// Covered (FICA) earnings by calendar year, nominal, as the Social
/// Security statement records them; what a `social-security` income
/// without an `amount` is computed from.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub earnings: BTreeMap<i16, Dollars>,
}
impl Person {
/// The age this person reaches during the given calendar year.
#[must_use]
pub fn age_in_year(&self, year: i16) -> i16 {
year - self.birth.year()
}
/// The age this person reaches during `year`, held between 0 and 255.
#[must_use]
pub fn age_in(&self, year: i16) -> u8 {
self.age_in_year(year).clamp(0, 255) as u8
}
/// The year this person turns the earliest claim age, whose wage index
/// and bend points their benefit is figured on.
#[must_use]
pub fn eligibility_year(&self) -> i16 {
self.birth.year() + tax::EARLIEST_CLAIM_AGE
}
}
/// The household: filing status and its one or two people.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Household {
/// Federal filing status.
pub filing: FilingStatus,
/// The people; one for `single`, two for `married-joint`.
pub people: Vec<Person>,
}
/// Where the household lives from `from` until the next residency begins.
/// Validated but not yet priced: the engine models federal tax only.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Residency {
/// ISO 3166-1 two-letter country code, one of
/// [`COUNTRIES`](super::COUNTRIES); read in any case, kept lowercase.
#[serde(deserialize_with = "lowercased")]
pub country: String,
/// One of [`US_STATES`](super::US_STATES), given exactly when `country`
/// is `us`; read in any case, kept lowercase.
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "lowercased_option"
)]
pub state: Option<String>,
/// When this residency begins; absent means from plan start.
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<Trigger>,
}
fn lowercased<'de, D: Deserializer<'de>>(deserializer: D) -> Result<String, D::Error> {
String::deserialize(deserializer).map(|code| code.to_ascii_lowercase())
}
fn lowercased_option<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Option<String>, D::Error> {
let code = Option::<String>::deserialize(deserializer)?;
Ok(code.map(|code| code.to_ascii_lowercase()))
}