code wiki / (root) / nx_ledes_lib.nx

nx_ledes_lib.nx source

↩ module page · 124 lines · 5364 B

1// nx_ledes_lib.nx -- LEGAL SPEND: UTBMS coding + LEDES 1998B validation + outside-counsel guidelines. 2// The legal-ops category we had at zero. An invoice is not a document to read, it is a contract to 3// enforce: outside counsel guidelines say what may be billed, at what rate, in what increments, by whom. 4// 5// u2605BLOCK BILLING is the single largest source of disputed legal spend: several distinct tasks lumped 6// into one narrative with one time figure, so no line can be assessed on its own. It is detectable 7// structurally -- more than one UTBMS task code implicated by a single entry -- and it is rejected here 8// rather than merely flagged, because a flagged-but-payable line gets paid. 9// 10// u2605THE INCREMENT TOOTH: guidelines almost universally require tenth-of-an-hour billing. An entry in 11// quarter hours (0.25) silently inflates every short task by up to 50%. Encoded as an exact divisibility 12// check in HUNDREDTHS of an hour, so 0.1 is 10 and anything not a multiple of 10 fails. 13// 14// u2605FAIL-CLOSED: an entry with an UNRECOGNISED task or activity code is INVALID, never "assume billable". 15// A code nobody recognises is a line nobody reviewed. 16// 17// UTBMS ranges encoded as data-driven bounds (rule 11): litigation task codes L100-L700, counselling 18// C100-C500, activity codes A101-A114, expense codes E101-E124. 19// STRUCTURE: pure decision core, zero I/O. license_tier: ORIGINAL LIB. 20 21import "nx_matter_lib.nx" 22 23const LED_OK: i64 = 0 24const LED_BAD_TASK: i64 = 1 25const LED_BAD_ACTIVITY: i64 = 2 26const LED_BLOCK_BILLED: i64 = 3 27const LED_BAD_INCREMENT: i64 = 4 28const LED_RATE_EXCEEDED: i64 = 5 29const LED_NOT_BILLABLE: i64 = 6 30 31const LED_TASK_LIT_LO: i64 = 100 32const LED_TASK_LIT_HI: i64 = 700 33const LED_ACT_LO: i64 = 101 34const LED_ACT_HI: i64 = 114 35const LED_EXP_LO: i64 = 101 36const LED_EXP_HI: i64 = 124 37const LED_INCREMENT_HUNDREDTHS: i64 = 10 38 39func led_is1(v: i64) -> i64 { 40 if v == 1 { return 1 } 41 return 0 42} 43 44// a UTBMS litigation/counselling task code must fall in a recognised band 45func led_task_ok_pure(task: i64) -> i64 { 46 if task < LED_TASK_LIT_LO { return 0 } 47 if task > LED_TASK_LIT_HI { return 0 } 48 return 1 49} 50 51func led_activity_ok_pure(act: i64) -> i64 { 52 if act < LED_ACT_LO { return 0 } 53 if act > LED_ACT_HI { return 0 } 54 return 1 55} 56 57func led_expense_ok_pure(exp: i64) -> i64 { 58 if exp < LED_EXP_LO { return 0 } 59 if exp > LED_EXP_HI { return 0 } 60 return 1 61} 62 63// u2605more than ONE task code implicated by a single narrative = block billing 64func led_block_billed_pure(task_count: i64) -> i64 { 65 if task_count > 1 { return 1 } 66 return 0 67} 68 69// hours expressed in HUNDREDTHS (0.1h = 10). Must be a positive multiple of the increment. 70func led_increment_ok_pure(hours_hundredths: i64) -> i64 { 71 if hours_hundredths <= 0 { return 0 } 72 if (hours_hundredths % LED_INCREMENT_HUNDREDTHS) != 0 { return 0 } 73 return 1 74} 75 76// rate in cents/hour against the guideline cap; a cap of 0 means "no cap agreed" and is NOT a free pass, 77// it is an unenforceable guideline -- so it fails closed. 78func led_rate_ok_pure(rate_cents: i64, cap_cents: i64) -> i64 { 79 if rate_cents <= 0 { return 0 } 80 if cap_cents <= 0 { return 0 } 81 if rate_cents > cap_cents { return 0 } 82 return 1 83} 84 85// u2605THE ENTRY VERDICT, first failure wins so the reason is actionable rather than a boolean. 86func led_entry_check_pure(task: i64, act: i64, task_count: i64, hours_hundredths: i64, rate_cents: i64, cap_cents: i64, billable: i64) -> i64 { 87 if led_is1(billable) == 0 { return LED_NOT_BILLABLE } 88 if led_task_ok_pure(task) == 0 { return LED_BAD_TASK } 89 if led_activity_ok_pure(act) == 0 { return LED_BAD_ACTIVITY } 90 if led_block_billed_pure(task_count) == 1 { return LED_BLOCK_BILLED } 91 if led_increment_ok_pure(hours_hundredths) == 0 { return LED_BAD_INCREMENT } 92 if led_rate_ok_pure(rate_cents, cap_cents) == 0 { return LED_RATE_EXCEEDED } 93 return LED_OK 94} 95 96func led_entry_payable_pure(verdict: i64) -> i64 { 97 if verdict == LED_OK { return 1 } 98 return 0 99} 100 101// line amount in cents = hours(hundredths) * rate(cents/hr) / 100, only for a payable line. 102func led_line_amount_pure(verdict: i64, hours_hundredths: i64, rate_cents: i64) -> i64 { 103 if led_entry_payable_pure(verdict) == 0 { return 0 } 104 return (hours_hundredths * rate_cents) / 100 105} 106 107// an invoice is payable only when EVERY line is; one bad line does not silently reduce the total. 108func led_invoice_clean_pure(total_lines: i64, rejected_lines: i64) -> i64 { 109 if total_lines <= 0 { return 0 } 110 if rejected_lines != 0 { return 0 } 111 return 1 112} 113 114func led_verdict_label(v: i64, out: *u8) -> i64 { 115 if v == LED_BAD_TASK { mt_catcopy(out, 0, "BAD-TASK-CODE" as *u8); out[13] = 0 as u8; return 13 } 116 if v == LED_BAD_ACTIVITY { mt_catcopy(out, 0, "BAD-ACTIVITY-CODE" as *u8); out[17] = 0 as u8; return 17 } 117 if v == LED_BLOCK_BILLED { mt_catcopy(out, 0, "BLOCK-BILLED" as *u8); out[12] = 0 as u8; return 12 } 118 if v == LED_BAD_INCREMENT { mt_catcopy(out, 0, "BAD-INCREMENT" as *u8); out[13] = 0 as u8; return 13 } 119 if v == LED_RATE_EXCEEDED { mt_catcopy(out, 0, "RATE-EXCEEDED" as *u8); out[13] = 0 as u8; return 13 } 120 if v == LED_NOT_BILLABLE { mt_catcopy(out, 0, "NOT-BILLABLE" as *u8); out[12] = 0 as u8; return 12 } 121 mt_catcopy(out, 0, "OK" as *u8) 122 out[2] = 0 as u8 123 return 2 124}