nx_damages_lib.nx source
↩ module page · 58 lines · 2772 B
1// nx_damages_lib.nx -- PREJUDGMENT INTEREST / money damages over time. Integer-exact, composes nx_sol.
2//
3// A money judgment is rarely just the principal: statutes award prejudgment interest from the date of loss
4// (accrual) to the date of judgment. Getting it right means an EXACT day count (not an approximation) and the
5// correct convention -- simple vs annual compounding, at a declared statutory rate. This organ REUSES the
6// exact proleptic-Gregorian day engine from nx_sol (composition, not a re-implementation) for the day count,
7// then does the interest in exact integer minor units.
8//
9// FAIL-CLOSED: a judgment date before the accrual date, a negative principal, or a negative rate returns
10// DMG_BAD -- you never compute interest on an impossible interval. The year basis is a DECLARED constant
11// (statutes differ: 365 vs 360); it is not buried (Rule 11).
12//
13// SCALE ENVELOPE (declared): principal * annual_bp * days fits i64 for principal up to ~9e10 minor units at
14// bp<=1e4, days<=1e4. license_tier: ORIGINAL No hw writes (Rule 26). LIB.
15
16import "nx_sol_lib.nx"
17
18const DMG_YEAR_DAYS: i64 = 365 // declared day-count basis (statute-configurable; 360 is the alternative)
19const DMG_BP_FULL: i64 = 10000
20const DMG_BAD: i64 = 0 - 2000000002
21
22// exact day count between two civil dates, via nx_sol's day-number engine. FAIL-CLOSED if end precedes start.
23func dmg_days_between(y1: i64, m1: i64, d1: i64, y2: i64, m2: i64, d2: i64) -> i64 {
24 let start: i64 = sol_days_from_civil(y1, m1, d1)
25 let end: i64 = sol_days_from_civil(y2, m2, d2)
26 if end < start { return DMG_BAD }
27 return end - start
28}
29
30// SIMPLE prejudgment interest: principal * rate * days / year. Truncated down. Fail-closed on bad input.
31func dmg_simple_interest(principal: i64, annual_bp: i64, days: i64) -> i64 {
32 if principal < 0 { return DMG_BAD }
33 if annual_bp < 0 { return DMG_BAD }
34 if days < 0 { return DMG_BAD }
35 return principal * annual_bp * days / (DMG_BP_FULL * DMG_YEAR_DAYS)
36}
37
38// ANNUALLY COMPOUNDED interest over whole years: returns the interest (balance minus principal).
39func dmg_compound_interest(principal: i64, annual_bp: i64, years: i64) -> i64 {
40 if principal < 0 { return DMG_BAD }
41 if annual_bp < 0 { return DMG_BAD }
42 if years < 0 { return DMG_BAD }
43 var bal: i64 = principal
44 var i: i64 = 0
45 while i < years {
46 bal = bal + bal * annual_bp / DMG_BP_FULL
47 i = i + 1
48 }
49 return bal - principal
50}
51
52// total award = principal + interest, propagating the fail-closed sentinel.
53func dmg_total_award(principal: i64, interest: i64) -> i64 {
54 if principal < 0 { return DMG_BAD }
55 if interest == DMG_BAD { return DMG_BAD }
56 if interest < 0 { return DMG_BAD }
57 return principal + interest
58}