nx_sol_lib.nx source
↩ module page · 97 lines · 5090 B
1// nx_sol_lib.nx -- STATUTE OF LIMITATIONS deadline engine. Exact calendar arithmetic, fail-closed.
2//
3// Missing a limitations deadline is the single most common legal-malpractice claim. The hard part is not the
4// subtraction -- it is CORRECT CALENDAR ARITHMETIC: 'two years from the accrual date' means the same civil
5// date two years later, and naive day-counting (period * 365) drifts by a day per leap year and mishandles a
6// Feb-29 accrual. This organ computes deadlines on exact proleptic-Gregorian day numbers (Howard Hinnant's
7// algorithm: days since 1970-01-01, valid for any year), so leap years and the Feb-29 -> Feb-28 clamp on year
8// addition are handled by construction.
9//
10// *FAIL-CLOSED ON MISSING DATA (the flagship): if the accrual date or the period is unknown, the deadline is
11// SOL_UNKNOWN and sol_status returns UNDETERMINED-FOR-REVIEW -- it will NEVER tell a client a claim is still
12// open or already time-barred on data it does not have. A confident wrong answer here ends a career.
13//
14// Models the two doctrines that move the clock: TOLLING (the clock pauses -- defendant absent, plaintiff a
15// minor -- so tolled days push the deadline OUT) and the DISCOVERY RULE (for a latent harm the clock starts
16// at discovery, not the wrongful act). Day numbers are signed integers; no float, no wall-clock read.
17// license_tier: ORIGINAL No hw writes (Rule 26). LIB.
18
19const SOL_UNKNOWN: i64 = 0 - 2000000002 // deadline uncomputable (missing accrual or period)
20const SOL_OPEN: i64 = 1 // as-of date is on or before the deadline
21const SOL_EXPIRED: i64 = 0 // as-of date is past the deadline (time-barred)
22const SOL_UNDETERMINED: i64 = 0 - 1 // fail-closed: cannot determine (missing data)
23
24// proleptic-Gregorian leap year: divisible by 4, except centuries unless divisible by 400.
25func sol_is_leap(y: i64) -> i64 {
26 if y % 400 == 0 { return 1 }
27 if y % 100 == 0 { return 0 }
28 if y % 4 == 0 { return 1 }
29 return 0
30}
31
32// *civil (y,m,d) -> day number, days since 1970-01-01 (negative before it). Hinnant's exact algorithm.
33// The era branch uses floor division for negative years (truncation-toward-zero would round the wrong way).
34func sol_days_from_civil(y0: i64, m: i64, d: i64) -> i64 {
35 var y: i64 = y0
36 if m <= 2 { y = y - 1 }
37 var era: i64 = y / 400
38 if y < 0 { era = (y - 399) / 400 }
39 let yoe: i64 = y - era * 400
40 var mm: i64 = m + 9
41 if m > 2 { mm = m - 3 }
42 let doy: i64 = (153 * mm + 2) / 5 + d - 1
43 let doe: i64 = yoe * 365 + yoe / 4 - yoe / 100 + doy
44 return era * 146097 + doe - 719468
45}
46
47// *add whole years to a civil date and return the deadline day number, clamping a Feb-29 start to Feb-28
48// when the target year is not a leap year (the standard legal convention for an anniversary date).
49func sol_add_years_civil(y: i64, m: i64, d: i64, add_years: i64) -> i64 {
50 let ny: i64 = y + add_years
51 var nd: i64 = d
52 if m == 2 {
53 if d == 29 {
54 if sol_is_leap(ny) == 0 { nd = 28 }
55 }
56 }
57 return sol_days_from_civil(ny, m, nd)
58}
59
60// deadline for a period stated in YEARS from an accrual date, plus any tolled days. FAIL-CLOSED: returns
61// SOL_UNKNOWN if the accrual is not established (has_accrual==0), the period is non-positive, or tolling is
62// negative -- an uncomputable deadline, never a guessed one.
63func sol_deadline_years(has_accrual: i64, y: i64, m: i64, d: i64, years: i64, tolled_days: i64) -> i64 {
64 if has_accrual == 0 { return SOL_UNKNOWN }
65 if years <= 0 { return SOL_UNKNOWN }
66 if tolled_days < 0 { return SOL_UNKNOWN }
67 return sol_add_years_civil(y, m, d, years) + tolled_days
68}
69
70// discovery rule: the effective accrual day number. For a standard claim (uses_discovery==0) it is the act
71// day. Under the discovery rule it is the later of act and discovery (you cannot discover a harm before it
72// occurs, so an earlier 'discovery' is floored to the act -- fail-safe against a bad input).
73func sol_effective_accrual(act_daynum: i64, discovery_daynum: i64, uses_discovery: i64) -> i64 {
74 if uses_discovery == 0 { return act_daynum }
75 if discovery_daynum < act_daynum { return act_daynum }
76 return discovery_daynum
77}
78
79// *status of the claim as of a day. FAIL-CLOSED: an unknown deadline is UNDETERMINED, never OPEN/EXPIRED.
80// The deadline day itself is still OPEN (a claim may be filed on the last day).
81func sol_status(deadline_daynum: i64, as_of_daynum: i64) -> i64 {
82 if deadline_daynum == SOL_UNKNOWN { return SOL_UNDETERMINED }
83 if as_of_daynum <= deadline_daynum { return SOL_OPEN }
84 return SOL_EXPIRED
85}
86
87// days remaining (positive) or overdue (negative). SOL_UNKNOWN if the deadline is uncomputable.
88func sol_days_remaining(deadline_daynum: i64, as_of_daynum: i64) -> i64 {
89 if deadline_daynum == SOL_UNKNOWN { return SOL_UNKNOWN }
90 return deadline_daynum - as_of_daynum
91}
92
93func sol_status_str(s: i64) -> *u8 {
94 if s == SOL_OPEN { return "OPEN" as *u8 }
95 if s == SOL_EXPIRED { return "TIME-BARRED" as *u8 }
96 return "UNDETERMINED-FOR-REVIEW" as *u8
97}