1use rust_decimal::Decimal;
13use serde::{Deserialize, Serialize};
14use uuid::Uuid;
15
16use crate::framework::AccountingFramework;
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct FairValueMeasurement {
21 pub measurement_id: Uuid,
23
24 pub item_id: String,
26
27 pub item_description: String,
29
30 pub item_category: FairValueCategory,
32
33 pub hierarchy_level: FairValueHierarchyLevel,
35
36 pub valuation_technique: ValuationTechnique,
38
39 #[serde(with = "rust_decimal::serde::str")]
41 pub fair_value: Decimal,
42
43 #[serde(with = "rust_decimal::serde::str")]
45 pub carrying_amount: Decimal,
46
47 pub measurement_date: chrono::NaiveDate,
49
50 pub currency: String,
52
53 pub valuation_inputs: Vec<ValuationInput>,
55
56 pub measurement_type: MeasurementType,
58
59 pub framework: AccountingFramework,
61
62 pub sensitivity_analysis: Option<SensitivityAnalysis>,
64}
65
66impl FairValueMeasurement {
67 #[allow(clippy::too_many_arguments)]
69 pub fn new(
70 item_id: impl Into<String>,
71 item_description: impl Into<String>,
72 item_category: FairValueCategory,
73 hierarchy_level: FairValueHierarchyLevel,
74 fair_value: Decimal,
75 measurement_date: chrono::NaiveDate,
76 currency: impl Into<String>,
77 framework: AccountingFramework,
78 ) -> Self {
79 Self {
80 measurement_id: Uuid::now_v7(),
81 item_id: item_id.into(),
82 item_description: item_description.into(),
83 item_category,
84 hierarchy_level,
85 valuation_technique: ValuationTechnique::default(),
86 fair_value,
87 carrying_amount: fair_value,
88 measurement_date,
89 currency: currency.into(),
90 valuation_inputs: Vec::new(),
91 measurement_type: MeasurementType::Recurring,
92 framework,
93 sensitivity_analysis: None,
94 }
95 }
96
97 pub fn add_input(&mut self, input: ValuationInput) {
99 self.valuation_inputs.push(input);
100 }
101
102 pub fn unrealized_gain_loss(&self) -> Decimal {
104 self.fair_value - self.carrying_amount
105 }
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
115#[serde(rename_all = "snake_case")]
116pub enum FairValueHierarchyLevel {
117 #[default]
121 Level1,
122
123 Level2,
127
128 Level3,
133}
134
135impl std::fmt::Display for FairValueHierarchyLevel {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 match self {
138 Self::Level1 => write!(f, "Level 1"),
139 Self::Level2 => write!(f, "Level 2"),
140 Self::Level3 => write!(f, "Level 3"),
141 }
142 }
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
147#[serde(rename_all = "snake_case")]
148pub enum FairValueCategory {
149 #[default]
151 TradingSecurities,
152 AvailableForSale,
154 Derivatives,
156 InvestmentProperty,
158 BiologicalAssets,
160 PensionAssets,
162 ContingentConsideration,
164 ImpairedAssets,
166 Other,
168}
169
170impl std::fmt::Display for FairValueCategory {
171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 match self {
173 Self::TradingSecurities => write!(f, "Trading Securities"),
174 Self::AvailableForSale => write!(f, "Available-for-Sale Securities"),
175 Self::Derivatives => write!(f, "Derivatives"),
176 Self::InvestmentProperty => write!(f, "Investment Property"),
177 Self::BiologicalAssets => write!(f, "Biological Assets"),
178 Self::PensionAssets => write!(f, "Pension Plan Assets"),
179 Self::ContingentConsideration => write!(f, "Contingent Consideration"),
180 Self::ImpairedAssets => write!(f, "Impaired Assets"),
181 Self::Other => write!(f, "Other"),
182 }
183 }
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
188#[serde(rename_all = "snake_case")]
189pub enum ValuationTechnique {
190 #[default]
192 MarketApproach,
193 IncomeApproach,
195 CostApproach,
197 MultipleApproaches,
199}
200
201impl std::fmt::Display for ValuationTechnique {
202 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203 match self {
204 Self::MarketApproach => write!(f, "Market Approach"),
205 Self::IncomeApproach => write!(f, "Income Approach"),
206 Self::CostApproach => write!(f, "Cost Approach"),
207 Self::MultipleApproaches => write!(f, "Multiple Approaches"),
208 }
209 }
210}
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
214#[serde(rename_all = "snake_case")]
215pub enum MeasurementType {
216 #[default]
218 Recurring,
219 NonRecurring,
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct ValuationInput {
226 pub name: String,
228
229 #[serde(with = "rust_decimal::serde::str")]
231 pub value: Decimal,
232
233 pub unit: String,
235
236 pub observable: bool,
238
239 pub source: String,
241}
242
243impl ValuationInput {
244 pub fn new(
246 name: impl Into<String>,
247 value: Decimal,
248 unit: impl Into<String>,
249 observable: bool,
250 source: impl Into<String>,
251 ) -> Self {
252 Self {
253 name: name.into(),
254 value,
255 unit: unit.into(),
256 observable,
257 source: source.into(),
258 }
259 }
260
261 pub fn discount_rate(rate: Decimal, source: impl Into<String>) -> Self {
263 Self::new("Discount Rate", rate, "%", true, source)
264 }
265
266 pub fn growth_rate(rate: Decimal, source: impl Into<String>) -> Self {
268 Self::new("Expected Growth Rate", rate, "%", false, source)
269 }
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct SensitivityAnalysis {
275 pub input_name: String,
277
278 #[serde(with = "decimal_tuple")]
280 pub input_range: (Decimal, Decimal),
281
282 #[serde(with = "rust_decimal::serde::str")]
284 pub fair_value_low: Decimal,
285
286 #[serde(with = "rust_decimal::serde::str")]
288 pub fair_value_high: Decimal,
289
290 pub correlated_inputs: Vec<String>,
292}
293
294mod decimal_tuple {
295 use rust_decimal::Decimal;
296 use serde::{Deserialize, Deserializer, Serialize, Serializer};
297
298 pub fn serialize<S>(value: &(Decimal, Decimal), serializer: S) -> Result<S::Ok, S::Error>
299 where
300 S: Serializer,
301 {
302 let tuple = (value.0.to_string(), value.1.to_string());
303 tuple.serialize(serializer)
304 }
305
306 pub fn deserialize<'de, D>(deserializer: D) -> Result<(Decimal, Decimal), D::Error>
307 where
308 D: Deserializer<'de>,
309 {
310 let tuple: (String, String) = Deserialize::deserialize(deserializer)?;
311 let low = tuple
312 .0
313 .parse()
314 .map_err(|_| serde::de::Error::custom("invalid decimal"))?;
315 let high = tuple
316 .1
317 .parse()
318 .map_err(|_| serde::de::Error::custom("invalid decimal"))?;
319 Ok((low, high))
320 }
321}
322
323#[derive(Debug, Clone, Serialize, Deserialize)]
325pub struct FairValueHierarchySummary {
326 pub period_date: chrono::NaiveDate,
328
329 pub company_code: String,
331
332 #[serde(with = "rust_decimal::serde::str")]
334 pub level1_assets: Decimal,
335
336 #[serde(with = "rust_decimal::serde::str")]
338 pub level2_assets: Decimal,
339
340 #[serde(with = "rust_decimal::serde::str")]
342 pub level3_assets: Decimal,
343
344 #[serde(with = "rust_decimal::serde::str")]
346 pub level1_liabilities: Decimal,
347
348 #[serde(with = "rust_decimal::serde::str")]
350 pub level2_liabilities: Decimal,
351
352 #[serde(with = "rust_decimal::serde::str")]
354 pub level3_liabilities: Decimal,
355
356 pub framework: AccountingFramework,
358}
359
360impl FairValueHierarchySummary {
361 pub fn new(
363 period_date: chrono::NaiveDate,
364 company_code: impl Into<String>,
365 framework: AccountingFramework,
366 ) -> Self {
367 Self {
368 period_date,
369 company_code: company_code.into(),
370 level1_assets: Decimal::ZERO,
371 level2_assets: Decimal::ZERO,
372 level3_assets: Decimal::ZERO,
373 level1_liabilities: Decimal::ZERO,
374 level2_liabilities: Decimal::ZERO,
375 level3_liabilities: Decimal::ZERO,
376 framework,
377 }
378 }
379
380 pub fn total_assets(&self) -> Decimal {
382 self.level1_assets + self.level2_assets + self.level3_assets
383 }
384
385 pub fn total_liabilities(&self) -> Decimal {
387 self.level1_liabilities + self.level2_liabilities + self.level3_liabilities
388 }
389}
390
391#[cfg(test)]
392mod tests {
393 use super::*;
394 use rust_decimal_macros::dec;
395
396 #[test]
397 fn test_fair_value_measurement() {
398 let measurement = FairValueMeasurement::new(
399 "SEC001",
400 "ABC Corp Common Stock",
401 FairValueCategory::TradingSecurities,
402 FairValueHierarchyLevel::Level1,
403 dec!(50000),
404 chrono::NaiveDate::from_ymd_opt(2024, 3, 31).unwrap(),
405 "USD",
406 AccountingFramework::UsGaap,
407 );
408
409 assert_eq!(measurement.fair_value, dec!(50000));
410 assert_eq!(measurement.hierarchy_level, FairValueHierarchyLevel::Level1);
411 }
412
413 #[test]
414 fn test_unrealized_gain_loss() {
415 let mut measurement = FairValueMeasurement::new(
416 "SEC001",
417 "XYZ Corp Stock",
418 FairValueCategory::TradingSecurities,
419 FairValueHierarchyLevel::Level1,
420 dec!(55000), chrono::NaiveDate::from_ymd_opt(2024, 3, 31).unwrap(),
422 "USD",
423 AccountingFramework::UsGaap,
424 );
425 measurement.carrying_amount = dec!(50000); assert_eq!(measurement.unrealized_gain_loss(), dec!(5000));
428 }
429
430 #[test]
431 fn test_hierarchy_summary() {
432 let mut summary = FairValueHierarchySummary::new(
433 chrono::NaiveDate::from_ymd_opt(2024, 3, 31).unwrap(),
434 "1000",
435 AccountingFramework::UsGaap,
436 );
437
438 summary.level1_assets = dec!(100000);
439 summary.level2_assets = dec!(50000);
440 summary.level3_assets = dec!(25000);
441
442 assert_eq!(summary.total_assets(), dec!(175000));
443 }
444}