code wiki / (root) / nx_sol_lib.nx

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}