From 5903f7634d2c73fe4f58961cade2358e26d05353 Mon Sep 17 00:00:00 2001 From: orenodinner Date: Sun, 31 May 2026 17:54:49 +0900 Subject: [PATCH 1/2] Add revenue tax exemption hold guard --- revenue-tax-exemption-hold-guard/README.md | 27 +++ revenue-tax-exemption-hold-guard/demo.js | 68 +++++++ .../reports/demo.mp4 | Bin 0 -> 25977 bytes .../reports/summary.svg | 19 ++ .../reports/tax-exemption-packet.json | 114 +++++++++++ .../reports/tax-exemption-report.md | 24 +++ .../sampleInvoices.js | 119 +++++++++++ .../taxExemptionHoldGuard.js | 188 ++++++++++++++++++ revenue-tax-exemption-hold-guard/test.js | 72 +++++++ 9 files changed, 631 insertions(+) create mode 100644 revenue-tax-exemption-hold-guard/README.md create mode 100644 revenue-tax-exemption-hold-guard/demo.js create mode 100644 revenue-tax-exemption-hold-guard/reports/demo.mp4 create mode 100644 revenue-tax-exemption-hold-guard/reports/summary.svg create mode 100644 revenue-tax-exemption-hold-guard/reports/tax-exemption-packet.json create mode 100644 revenue-tax-exemption-hold-guard/reports/tax-exemption-report.md create mode 100644 revenue-tax-exemption-hold-guard/sampleInvoices.js create mode 100644 revenue-tax-exemption-hold-guard/taxExemptionHoldGuard.js create mode 100644 revenue-tax-exemption-hold-guard/test.js diff --git a/revenue-tax-exemption-hold-guard/README.md b/revenue-tax-exemption-hold-guard/README.md new file mode 100644 index 00000000..0757091e --- /dev/null +++ b/revenue-tax-exemption-hold-guard/README.md @@ -0,0 +1,27 @@ +# Revenue Tax Exemption Hold Guard + +This is a self-contained Revenue Infrastructure slice for issue #20. + +The guard checks whether institutional invoices that request tax-exempt or reverse-charge treatment can be released before billing. It validates exemption certificate freshness, VAT or jurisdiction evidence, PO tax terms, mixed taxable line items, and remediation actions for subscription, compute, and analytics-license revenue. + +## Scope + +- Synthetic data only. +- No network calls, credentials, payment processor integration, tax filing, bank data, or SCIBASE production service integration. +- Focused on institutional tax/VAT exemption release safety, not broad billing ledgers, usage metering, receipt privacy, refunds, cancellation, dunning, sanctions screening, support entitlement, collections, or analytics seat rosters. + +## Validation + +```sh +node revenue-tax-exemption-hold-guard/test.js +node revenue-tax-exemption-hold-guard/demo.js +``` + +The demo writes deterministic reviewer artifacts under `revenue-tax-exemption-hold-guard/reports/`. + +Reviewer artifacts: + +- `reports/tax-exemption-packet.json` +- `reports/tax-exemption-report.md` +- `reports/summary.svg` +- `reports/demo.mp4` diff --git a/revenue-tax-exemption-hold-guard/demo.js b/revenue-tax-exemption-hold-guard/demo.js new file mode 100644 index 00000000..a08a2512 --- /dev/null +++ b/revenue-tax-exemption-hold-guard/demo.js @@ -0,0 +1,68 @@ +"use strict"; + +const fs = require("fs"); +const path = require("path"); +const { analyzeTaxExemptionHolds, formatUsd } = require("./taxExemptionHoldGuard"); +const { sampleInvoices } = require("./sampleInvoices"); + +const reportsDir = path.join(__dirname, "reports"); +fs.mkdirSync(reportsDir, { recursive: true }); + +const packet = analyzeTaxExemptionHolds(sampleInvoices); +fs.writeFileSync( + path.join(reportsDir, "tax-exemption-packet.json"), + JSON.stringify(packet, null, 2) + "\n", +); + +const report = [ + "# Revenue Tax Exemption Hold Guard", + "", + `Status: **${packet.status}**`, + "", + "## Summary", + "", + `- Invoices checked: ${packet.summary.invoices}`, + `- Release: ${packet.summary.release}`, + `- Review: ${packet.summary.review}`, + `- Hold: ${packet.summary.hold}`, + `- Invoice amount reviewed: ${formatUsd(packet.summary.totalAmountCents)}`, + `- Taxable line amount: ${formatUsd(packet.summary.taxableAmountCents)}`, + "", + "## Decisions", + "", + ...packet.decisions.map((item) => { + const codes = item.findings.map((finding) => finding.code).join(", ") || "none"; + return `- ${item.invoiceId}: ${item.decision} (${codes})`; + }), + "", + "## Scope", + "", + "This guard supports SCIBASE revenue operations by holding or routing institutional tax-exempt and reverse-charge invoices before billing release when exemption evidence, VAT evidence, PO terms, certificate freshness, or mixed taxable lines need attention.", + "", +].join("\n"); + +fs.writeFileSync(path.join(reportsDir, "tax-exemption-report.md"), report); + +const svg = ` + + Revenue tax exemption hold guard + Status: ${packet.status.toUpperCase()} + + ${packet.summary.invoices} + invoices checked + + ${packet.summary.release} + release + + ${packet.summary.review} + review + + ${packet.summary.hold} + hold + Checks exemption certificates, VAT evidence, PO tax terms, certificate expiry, and mixed taxable lines. + Reviewed invoice amount: ${formatUsd(packet.summary.totalAmountCents)} + +`; +fs.writeFileSync(path.join(reportsDir, "summary.svg"), svg); + +console.log(report); diff --git a/revenue-tax-exemption-hold-guard/reports/demo.mp4 b/revenue-tax-exemption-hold-guard/reports/demo.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..d7fe746de21162ed5d202f5fba7d449ebff59b12 GIT binary patch literal 25977 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 literal 0 HcmV?d00001 diff --git a/revenue-tax-exemption-hold-guard/reports/summary.svg b/revenue-tax-exemption-hold-guard/reports/summary.svg new file mode 100644 index 00000000..ce47c25d --- /dev/null +++ b/revenue-tax-exemption-hold-guard/reports/summary.svg @@ -0,0 +1,19 @@ + + + Revenue tax exemption hold guard + Status: HOLD + + 5 + invoices checked + + 1 + release + + 2 + review + + 2 + hold + Checks exemption certificates, VAT evidence, PO tax terms, certificate expiry, and mixed taxable lines. + Reviewed invoice amount: $38650.00 + diff --git a/revenue-tax-exemption-hold-guard/reports/tax-exemption-packet.json b/revenue-tax-exemption-hold-guard/reports/tax-exemption-packet.json new file mode 100644 index 00000000..231cb401 --- /dev/null +++ b/revenue-tax-exemption-hold-guard/reports/tax-exemption-packet.json @@ -0,0 +1,114 @@ +{ + "status": "hold", + "checkedAt": "2026-05-31T08:55:00.000Z", + "policy": { + "expiryWarningDays": 30, + "taxableLineReviewThresholdCents": 25000 + }, + "summary": { + "invoices": 5, + "release": 1, + "review": 2, + "hold": 2, + "totalAmountCents": 3865000, + "taxableAmountCents": 405000 + }, + "decisions": [ + { + "invoiceId": "inv-clean-us-lab", + "customer": "Northlake Genomics Lab", + "billingModel": "institutional_invoice", + "requestedTaxTreatment": "exempt", + "amountCents": 1380000, + "taxableCents": 0, + "decision": "release", + "findings": [], + "remediation": [ + "Release invoice with current exemption evidence packet." + ] + }, + { + "invoiceId": "inv-expired-certificate", + "customer": "Bay Ridge Materials Center", + "billingModel": "institutional_invoice", + "requestedTaxTreatment": "exempt", + "amountCents": 720000, + "taxableCents": 0, + "decision": "hold", + "findings": [ + { + "code": "CERTIFICATE_EXPIRED", + "severity": "hold", + "message": "inv-expired-certificate certificate cert-us-2024-144 expired 46 days before billing review." + } + ], + "remediation": [ + "Request renewed exemption certificate before invoice release." + ] + }, + { + "invoiceId": "inv-eu-missing-vat", + "customer": "Alpine Neuroimaging Institute", + "billingModel": "institutional_invoice", + "requestedTaxTreatment": "reverse_charge", + "amountCents": 460000, + "taxableCents": 0, + "decision": "hold", + "findings": [ + { + "code": "MISSING_VAT_EVIDENCE", + "severity": "hold", + "message": "inv-eu-missing-vat is missing VAT or reverse-charge evidence for institutional billing." + } + ], + "remediation": [ + "Collect validated VAT ID or reverse-charge evidence before release." + ] + }, + { + "invoiceId": "inv-mixed-taxable-lines", + "customer": "East Harbor Quant Lab", + "billingModel": "institutional_invoice", + "requestedTaxTreatment": "exempt", + "amountCents": 985000, + "taxableCents": 85000, + "decision": "review", + "findings": [ + { + "code": "CERTIFICATE_EXPIRING_SOON", + "severity": "review", + "message": "inv-mixed-taxable-lines certificate cert-us-2026-212 expires in 18 days." + }, + { + "code": "MIXED_TAXABLE_LINES", + "severity": "review", + "message": "inv-mixed-taxable-lines contains $850.00 in taxable lines under exempt treatment." + } + ], + "remediation": [ + "Confirm certificate remains valid for service period and archive renewal task.", + "Route taxable line items to finance review before final tax treatment." + ] + }, + { + "invoiceId": "inv-po-tax-conflict", + "customer": "Cedar Clinical Trials Office", + "billingModel": "institutional_invoice", + "requestedTaxTreatment": "standard", + "amountCents": 320000, + "taxableCents": 320000, + "decision": "review", + "findings": [ + { + "code": "PO_TAX_TREATMENT_CONFLICT", + "severity": "review", + "message": "inv-po-tax-conflict PO tax treatment is exempt, but invoice requests standard." + } + ], + "remediation": [ + "Resolve PO and invoice tax-treatment mismatch with finance operations." + ] + } + ], + "warnings": [] +} diff --git a/revenue-tax-exemption-hold-guard/reports/tax-exemption-report.md b/revenue-tax-exemption-hold-guard/reports/tax-exemption-report.md new file mode 100644 index 00000000..67d8c2a8 --- /dev/null +++ b/revenue-tax-exemption-hold-guard/reports/tax-exemption-report.md @@ -0,0 +1,24 @@ +# Revenue Tax Exemption Hold Guard + +Status: **hold** + +## Summary + +- Invoices checked: 5 +- Release: 1 +- Review: 2 +- Hold: 2 +- Invoice amount reviewed: $38650.00 +- Taxable line amount: $4050.00 + +## Decisions + +- inv-clean-us-lab: release (none) +- inv-expired-certificate: hold (CERTIFICATE_EXPIRED) +- inv-eu-missing-vat: hold (MISSING_VAT_EVIDENCE) +- inv-mixed-taxable-lines: review (CERTIFICATE_EXPIRING_SOON, MIXED_TAXABLE_LINES) +- inv-po-tax-conflict: review (PO_TAX_TREATMENT_CONFLICT) + +## Scope + +This guard supports SCIBASE revenue operations by holding or routing institutional tax-exempt and reverse-charge invoices before billing release when exemption evidence, VAT evidence, PO terms, certificate freshness, or mixed taxable lines need attention. diff --git a/revenue-tax-exemption-hold-guard/sampleInvoices.js b/revenue-tax-exemption-hold-guard/sampleInvoices.js new file mode 100644 index 00000000..99340f92 --- /dev/null +++ b/revenue-tax-exemption-hold-guard/sampleInvoices.js @@ -0,0 +1,119 @@ +"use strict"; + +const sampleInvoices = { + checkedAt: "2026-05-31T08:55:00.000Z", + policy: { + expiryWarningDays: 30, + taxableLineReviewThresholdCents: 25000, + }, + invoices: [ + { + id: "inv-clean-us-lab", + customer: "Northlake Genomics Lab", + country: "US", + billingModel: "institutional_invoice", + requestedTaxTreatment: "exempt", + certificate: { + id: "cert-us-2026-001", + type: "state_exemption", + status: "verified", + expiresAt: "2027-04-30", + jurisdiction: "US-MA", + }, + purchaseOrder: { + id: "PO-8842", + taxTreatment: "exempt", + jurisdiction: "US-MA", + }, + lines: [ + { id: "line-sub", category: "subscription", description: "Institutional annual plan", taxable: false, amountCents: 1200000 }, + { id: "line-compute", category: "compute", description: "Reproducibility compute pack", taxable: false, amountCents: 180000 }, + ], + }, + { + id: "inv-expired-certificate", + customer: "Bay Ridge Materials Center", + country: "US", + billingModel: "institutional_invoice", + requestedTaxTreatment: "exempt", + certificate: { + id: "cert-us-2024-144", + type: "state_exemption", + status: "verified", + expiresAt: "2026-04-15", + jurisdiction: "US-CA", + }, + purchaseOrder: { + id: "PO-8120", + taxTreatment: "exempt", + jurisdiction: "US-CA", + }, + lines: [ + { id: "line-sub", category: "subscription", description: "Lab annual plan", taxable: false, amountCents: 720000 }, + ], + }, + { + id: "inv-eu-missing-vat", + customer: "Alpine Neuroimaging Institute", + country: "DE", + billingModel: "institutional_invoice", + requestedTaxTreatment: "reverse_charge", + certificate: { + id: "cert-eu-2026-087", + type: "vat_reverse_charge", + status: "verified", + expiresAt: "2026-12-31", + jurisdiction: "EU", + }, + purchaseOrder: { + id: "PO-2239", + taxTreatment: "reverse_charge", + jurisdiction: "DE", + }, + lines: [ + { id: "line-license", category: "analytics_license", description: "Research trend analytics API", taxable: false, amountCents: 460000 }, + ], + }, + { + id: "inv-mixed-taxable-lines", + customer: "East Harbor Quant Lab", + country: "US", + billingModel: "institutional_invoice", + requestedTaxTreatment: "exempt", + certificate: { + id: "cert-us-2026-212", + type: "state_exemption", + status: "verified", + expiresAt: "2026-06-18", + jurisdiction: "US-NY", + }, + purchaseOrder: { + id: "PO-3371", + taxTreatment: "exempt", + jurisdiction: "US-NY", + }, + lines: [ + { id: "line-sub", category: "subscription", description: "Lab annual plan", taxable: false, amountCents: 900000 }, + { id: "line-training", category: "professional_services", description: "Onsite onboarding workshop", taxable: true, amountCents: 85000 }, + ], + }, + { + id: "inv-po-tax-conflict", + customer: "Cedar Clinical Trials Office", + country: "US", + billingModel: "institutional_invoice", + requestedTaxTreatment: "standard", + certificate: null, + purchaseOrder: { + id: "PO-5581", + taxTreatment: "exempt", + jurisdiction: "US-TX", + }, + lines: [ + { id: "line-compute", category: "compute", description: "Clinical reproducibility compute runs", taxable: true, amountCents: 320000 }, + ], + }, + ], +}; + +module.exports = { sampleInvoices }; diff --git a/revenue-tax-exemption-hold-guard/taxExemptionHoldGuard.js b/revenue-tax-exemption-hold-guard/taxExemptionHoldGuard.js new file mode 100644 index 00000000..b0114393 --- /dev/null +++ b/revenue-tax-exemption-hold-guard/taxExemptionHoldGuard.js @@ -0,0 +1,188 @@ +"use strict"; + +const EU_COUNTRIES = new Set([ + "AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", + "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", + "SI", "ES", "SE", +]); + +function normalizeText(value) { + return String(value || "").trim().toLowerCase().replace(/\s+/g, " "); +} + +function parseDate(value) { + const date = new Date(`${value}T00:00:00.000Z`); + return Number.isNaN(date.getTime()) ? null : date; +} + +function daysUntil(dateValue, nowValue) { + const date = parseDate(dateValue); + const now = new Date(nowValue); + if (!date || Number.isNaN(now.getTime())) { + return null; + } + return Math.ceil((date.getTime() - now.getTime()) / 86400000); +} + +function lineTotalCents(invoice) { + return (invoice.lines || []).reduce((sum, line) => sum + Number(line.amountCents || 0), 0); +} + +function taxableLineTotalCents(invoice) { + return (invoice.lines || []) + .filter((line) => line.taxable) + .reduce((sum, line) => sum + Number(line.amountCents || 0), 0); +} + +function hasVatEvidence(invoice) { + return Boolean(invoice.vatId || invoice.taxRegistrationId || invoice.reverseChargeEvidenceId); +} + +function evaluateInvoice(invoice, context) { + const policy = context.policy; + const findings = []; + const taxTreatment = normalizeText(invoice.requestedTaxTreatment); + const poTaxTreatment = normalizeText(invoice.purchaseOrder?.taxTreatment); + const certificate = invoice.certificate; + const country = String(invoice.country || "").toUpperCase(); + const taxableCents = taxableLineTotalCents(invoice); + + if ((taxTreatment === "exempt" || taxTreatment === "reverse_charge") && !certificate) { + findings.push({ + code: "MISSING_EXEMPTION_EVIDENCE", + severity: "hold", + message: `${invoice.id} requests ${taxTreatment} treatment without a certificate or evidence record.`, + }); + } + + if (certificate) { + const days = daysUntil(certificate.expiresAt, context.checkedAt); + if (normalizeText(certificate.status) !== "verified") { + findings.push({ + code: "CERTIFICATE_NOT_VERIFIED", + severity: "hold", + message: `${invoice.id} has certificate ${certificate.id} but it is not verified.`, + }); + } + if (days !== null && days < 0) { + findings.push({ + code: "CERTIFICATE_EXPIRED", + severity: "hold", + message: `${invoice.id} certificate ${certificate.id} expired ${Math.abs(days)} days before billing review.`, + }); + } else if (days !== null && days <= policy.expiryWarningDays) { + findings.push({ + code: "CERTIFICATE_EXPIRING_SOON", + severity: "review", + message: `${invoice.id} certificate ${certificate.id} expires in ${days} days.`, + }); + } + } + + if (taxTreatment === "reverse_charge") { + if (!EU_COUNTRIES.has(country)) { + findings.push({ + code: "REVERSE_CHARGE_JURISDICTION_MISMATCH", + severity: "hold", + message: `${invoice.id} requests reverse-charge treatment outside a supported EU jurisdiction.`, + }); + } + if (!hasVatEvidence(invoice)) { + findings.push({ + code: "MISSING_VAT_EVIDENCE", + severity: "hold", + message: `${invoice.id} is missing VAT or reverse-charge evidence for institutional billing.`, + }); + } + } + + if (poTaxTreatment && poTaxTreatment !== taxTreatment) { + findings.push({ + code: "PO_TAX_TREATMENT_CONFLICT", + severity: taxTreatment === "standard" ? "review" : "hold", + message: `${invoice.id} PO tax treatment is ${poTaxTreatment}, but invoice requests ${taxTreatment}.`, + }); + } + + if (taxableCents > 0 && (taxTreatment === "exempt" || taxTreatment === "reverse_charge")) { + findings.push({ + code: "MIXED_TAXABLE_LINES", + severity: taxableCents >= policy.taxableLineReviewThresholdCents ? "review" : "note", + message: `${invoice.id} contains ${formatUsd(taxableCents)} in taxable lines under ${taxTreatment} treatment.`, + }); + } + + const blockers = findings.filter((finding) => finding.severity === "hold"); + const reviews = findings.filter((finding) => finding.severity === "review"); + const decision = blockers.length > 0 ? "hold" : reviews.length > 0 ? "review" : "release"; + + return { + invoiceId: invoice.id, + customer: invoice.customer, + billingModel: invoice.billingModel, + requestedTaxTreatment: invoice.requestedTaxTreatment, + amountCents: lineTotalCents(invoice), + taxableCents, + decision, + findings, + remediation: remediationFor(decision, findings), + }; +} + +function remediationFor(decision, findings) { + if (decision === "release") { + return ["Release invoice with current exemption evidence packet."]; + } + return findings + .filter((finding) => finding.severity === "hold" || finding.severity === "review") + .map((finding) => { + if (finding.code === "CERTIFICATE_EXPIRED") return "Request renewed exemption certificate before invoice release."; + if (finding.code === "MISSING_VAT_EVIDENCE") return "Collect validated VAT ID or reverse-charge evidence before release."; + if (finding.code === "MIXED_TAXABLE_LINES") return "Route taxable line items to finance review before final tax treatment."; + if (finding.code === "PO_TAX_TREATMENT_CONFLICT") return "Resolve PO and invoice tax-treatment mismatch with finance operations."; + if (finding.code === "CERTIFICATE_EXPIRING_SOON") return "Confirm certificate remains valid for service period and archive renewal task."; + return "Attach missing tax evidence and rerun the hold guard."; + }); +} + +function analyzeTaxExemptionHolds(input) { + const policy = { + expiryWarningDays: input.policy?.expiryWarningDays ?? 30, + taxableLineReviewThresholdCents: input.policy?.taxableLineReviewThresholdCents ?? 25000, + }; + const checkedAt = input.checkedAt || new Date().toISOString(); + const invoices = input.invoices || []; + const decisions = invoices.map((invoice) => evaluateInvoice(invoice, { checkedAt, policy })); + const summary = { + invoices: decisions.length, + release: decisions.filter((item) => item.decision === "release").length, + review: decisions.filter((item) => item.decision === "review").length, + hold: decisions.filter((item) => item.decision === "hold").length, + totalAmountCents: decisions.reduce((sum, item) => sum + item.amountCents, 0), + taxableAmountCents: decisions.reduce((sum, item) => sum + item.taxableCents, 0), + }; + + return { + status: summary.hold > 0 ? "hold" : summary.review > 0 ? "review" : "release", + checkedAt, + policy, + summary, + decisions, + warnings: invoices.length === 0 ? [{ code: "NO_INVOICES", message: "No invoices were available for tax exemption review." }] : [], + }; +} + +function formatUsd(cents) { + return `$${(Number(cents || 0) / 100).toFixed(2)}`; +} + +module.exports = { + analyzeTaxExemptionHolds, + daysUntil, + evaluateInvoice, + formatUsd, + hasVatEvidence, + lineTotalCents, + normalizeText, + taxableLineTotalCents, +}; diff --git a/revenue-tax-exemption-hold-guard/test.js b/revenue-tax-exemption-hold-guard/test.js new file mode 100644 index 00000000..be436016 --- /dev/null +++ b/revenue-tax-exemption-hold-guard/test.js @@ -0,0 +1,72 @@ +"use strict"; + +const assert = require("assert"); +const { + analyzeTaxExemptionHolds, + daysUntil, + formatUsd, + hasVatEvidence, + lineTotalCents, + taxableLineTotalCents, +} = require("./taxExemptionHoldGuard"); +const { sampleInvoices } = require("./sampleInvoices"); + +assert.strictEqual(daysUntil("2026-06-30", "2026-05-31T00:00:00.000Z"), 30); +assert.strictEqual(formatUsd(123456), "$1234.56"); +assert.strictEqual(lineTotalCents(sampleInvoices.invoices[0]), 1380000); +assert.strictEqual(taxableLineTotalCents(sampleInvoices.invoices[3]), 85000); +assert.strictEqual(hasVatEvidence({ vatId: "DE123456789" }), true); +assert.strictEqual(hasVatEvidence({}), false); + +const packet = analyzeTaxExemptionHolds(sampleInvoices); +assert.strictEqual(packet.status, "hold"); +assert.strictEqual(packet.summary.invoices, 5); +assert.strictEqual(packet.summary.release, 1); +assert.strictEqual(packet.summary.review, 2); +assert.strictEqual(packet.summary.hold, 2); + +const expired = packet.decisions.find((item) => item.invoiceId === "inv-expired-certificate"); +assert(expired); +assert.strictEqual(expired.decision, "hold"); +assert(expired.findings.some((finding) => finding.code === "CERTIFICATE_EXPIRED")); + +const missingVat = packet.decisions.find((item) => item.invoiceId === "inv-eu-missing-vat"); +assert(missingVat); +assert.strictEqual(missingVat.decision, "hold"); +assert(missingVat.findings.some((finding) => finding.code === "MISSING_VAT_EVIDENCE")); + +const mixed = packet.decisions.find((item) => item.invoiceId === "inv-mixed-taxable-lines"); +assert(mixed); +assert.strictEqual(mixed.decision, "review"); +assert(mixed.findings.some((finding) => finding.code === "CERTIFICATE_EXPIRING_SOON")); +assert(mixed.findings.some((finding) => finding.code === "MIXED_TAXABLE_LINES")); + +const clean = analyzeTaxExemptionHolds({ + checkedAt: "2026-05-31T08:55:00.000Z", + invoices: [ + { + id: "inv-clean", + customer: "Clean Lab", + country: "US", + billingModel: "institutional_invoice", + requestedTaxTreatment: "exempt", + certificate: { + id: "cert-clean", + type: "state_exemption", + status: "verified", + expiresAt: "2027-01-01", + jurisdiction: "US-WA", + }, + purchaseOrder: { id: "PO-clean", taxTreatment: "exempt", jurisdiction: "US-WA" }, + lines: [{ id: "line-sub", category: "subscription", taxable: false, amountCents: 100000 }], + }, + ], +}); +assert.strictEqual(clean.status, "release"); +assert.strictEqual(clean.summary.release, 1); + +const empty = analyzeTaxExemptionHolds({ invoices: [] }); +assert.strictEqual(empty.status, "release"); +assert.strictEqual(empty.warnings[0].code, "NO_INVOICES"); + +console.log("revenue-tax-exemption-hold-guard tests passed"); From 15288dc296ffeb1921d830a402c6955cf2bf7c09 Mon Sep 17 00:00:00 2001 From: orenodinner Date: Sun, 31 May 2026 17:59:05 +0900 Subject: [PATCH 2/2] Replace tax guard with price escalation cap guard --- revenue-price-escalation-cap-guard/README.md | 27 +++ .../demo.js | 42 ++-- .../priceEscalationCapGuard.js | 195 ++++++++++++++++++ .../reports/demo.mp4 | Bin 0 -> 26441 bytes .../reports/price-escalation-packet.json | 127 ++++++++++++ .../reports/price-escalation-report.md | 24 +++ .../reports/summary.svg | 22 +- .../sampleRenewals.js | 100 +++++++++ revenue-price-escalation-cap-guard/test.js | 67 ++++++ revenue-tax-exemption-hold-guard/README.md | 27 --- .../reports/demo.mp4 | Bin 25977 -> 0 bytes .../reports/tax-exemption-packet.json | 114 ---------- .../reports/tax-exemption-report.md | 24 --- .../sampleInvoices.js | 119 ----------- .../taxExemptionHoldGuard.js | 188 ----------------- revenue-tax-exemption-hold-guard/test.js | 72 ------- 16 files changed, 572 insertions(+), 576 deletions(-) create mode 100644 revenue-price-escalation-cap-guard/README.md rename {revenue-tax-exemption-hold-guard => revenue-price-escalation-cap-guard}/demo.js (54%) create mode 100644 revenue-price-escalation-cap-guard/priceEscalationCapGuard.js create mode 100644 revenue-price-escalation-cap-guard/reports/demo.mp4 create mode 100644 revenue-price-escalation-cap-guard/reports/price-escalation-packet.json create mode 100644 revenue-price-escalation-cap-guard/reports/price-escalation-report.md rename {revenue-tax-exemption-hold-guard => revenue-price-escalation-cap-guard}/reports/summary.svg (57%) create mode 100644 revenue-price-escalation-cap-guard/sampleRenewals.js create mode 100644 revenue-price-escalation-cap-guard/test.js delete mode 100644 revenue-tax-exemption-hold-guard/README.md delete mode 100644 revenue-tax-exemption-hold-guard/reports/demo.mp4 delete mode 100644 revenue-tax-exemption-hold-guard/reports/tax-exemption-packet.json delete mode 100644 revenue-tax-exemption-hold-guard/reports/tax-exemption-report.md delete mode 100644 revenue-tax-exemption-hold-guard/sampleInvoices.js delete mode 100644 revenue-tax-exemption-hold-guard/taxExemptionHoldGuard.js delete mode 100644 revenue-tax-exemption-hold-guard/test.js diff --git a/revenue-price-escalation-cap-guard/README.md b/revenue-price-escalation-cap-guard/README.md new file mode 100644 index 00000000..329ddcc2 --- /dev/null +++ b/revenue-price-escalation-cap-guard/README.md @@ -0,0 +1,27 @@ +# Revenue Price Escalation Cap Guard + +This is a self-contained Revenue Infrastructure slice for issue #20. + +The guard checks whether annual institutional renewal price increases can be released before renewal invoices or entitlement changes are issued. It validates contract uplift caps, CPI/source evidence, customer notice windows, excluded account types, multi-year price locks, exception approvals, and deterministic finance remediation actions. + +## Scope + +- Synthetic data only. +- No network calls, credentials, payment processor integration, CPQ/CRM calls, tax filing, bank data, or SCIBASE production service integration. +- Focused on contract price escalation release safety, not broad billing ledgers, renewal notice delivery, pricing experiments, tax exemption, quote approval, dunning, refunds, support entitlement, collections, or analytics seat rosters. + +## Validation + +```sh +node revenue-price-escalation-cap-guard/test.js +node revenue-price-escalation-cap-guard/demo.js +``` + +The demo writes deterministic reviewer artifacts under `revenue-price-escalation-cap-guard/reports/`. + +Reviewer artifacts: + +- `reports/price-escalation-packet.json` +- `reports/price-escalation-report.md` +- `reports/summary.svg` +- `reports/demo.mp4` diff --git a/revenue-tax-exemption-hold-guard/demo.js b/revenue-price-escalation-cap-guard/demo.js similarity index 54% rename from revenue-tax-exemption-hold-guard/demo.js rename to revenue-price-escalation-cap-guard/demo.js index a08a2512..9deb0836 100644 --- a/revenue-tax-exemption-hold-guard/demo.js +++ b/revenue-price-escalation-cap-guard/demo.js @@ -2,65 +2,65 @@ const fs = require("fs"); const path = require("path"); -const { analyzeTaxExemptionHolds, formatUsd } = require("./taxExemptionHoldGuard"); -const { sampleInvoices } = require("./sampleInvoices"); +const { analyzePriceEscalations, formatUsd } = require("./priceEscalationCapGuard"); +const { sampleRenewals } = require("./sampleRenewals"); const reportsDir = path.join(__dirname, "reports"); fs.mkdirSync(reportsDir, { recursive: true }); -const packet = analyzeTaxExemptionHolds(sampleInvoices); +const packet = analyzePriceEscalations(sampleRenewals); fs.writeFileSync( - path.join(reportsDir, "tax-exemption-packet.json"), + path.join(reportsDir, "price-escalation-packet.json"), JSON.stringify(packet, null, 2) + "\n", ); const report = [ - "# Revenue Tax Exemption Hold Guard", + "# Revenue Price Escalation Cap Guard", "", `Status: **${packet.status}**`, "", "## Summary", "", - `- Invoices checked: ${packet.summary.invoices}`, + `- Renewals checked: ${packet.summary.renewals}`, `- Release: ${packet.summary.release}`, `- Review: ${packet.summary.review}`, `- Hold: ${packet.summary.hold}`, - `- Invoice amount reviewed: ${formatUsd(packet.summary.totalAmountCents)}`, - `- Taxable line amount: ${formatUsd(packet.summary.taxableAmountCents)}`, + `- Current ARR reviewed: ${formatUsd(packet.summary.currentAnnualCents)}`, + `- Proposed ARR reviewed: ${formatUsd(packet.summary.proposedAnnualCents)}`, "", "## Decisions", "", ...packet.decisions.map((item) => { const codes = item.findings.map((finding) => finding.code).join(", ") || "none"; - return `- ${item.invoiceId}: ${item.decision} (${codes})`; + return `- ${item.renewalId}: ${item.decision} (${codes})`; }), "", "## Scope", "", - "This guard supports SCIBASE revenue operations by holding or routing institutional tax-exempt and reverse-charge invoices before billing release when exemption evidence, VAT evidence, PO terms, certificate freshness, or mixed taxable lines need attention.", + "This guard supports SCIBASE revenue operations by holding or routing institutional renewal price escalations before invoice release when contract caps, CPI evidence, notice windows, price locks, or exception approvals need attention.", "", ].join("\n"); -fs.writeFileSync(path.join(reportsDir, "tax-exemption-report.md"), report); +fs.writeFileSync(path.join(reportsDir, "price-escalation-report.md"), report); const svg = ` - - Revenue tax exemption hold guard - Status: ${packet.status.toUpperCase()} - - ${packet.summary.invoices} - invoices checked - + + Revenue price escalation cap guard + Status: ${packet.status.toUpperCase()} + + ${packet.summary.renewals} + renewals checked + ${packet.summary.release} - release + release ${packet.summary.review} review ${packet.summary.hold} hold - Checks exemption certificates, VAT evidence, PO tax terms, certificate expiry, and mixed taxable lines. - Reviewed invoice amount: ${formatUsd(packet.summary.totalAmountCents)} + Checks contract uplift caps, CPI evidence, notice windows, price locks, and exception approvals. + Proposed ARR reviewed: ${formatUsd(packet.summary.proposedAnnualCents)} `; fs.writeFileSync(path.join(reportsDir, "summary.svg"), svg); diff --git a/revenue-price-escalation-cap-guard/priceEscalationCapGuard.js b/revenue-price-escalation-cap-guard/priceEscalationCapGuard.js new file mode 100644 index 00000000..4b80288b --- /dev/null +++ b/revenue-price-escalation-cap-guard/priceEscalationCapGuard.js @@ -0,0 +1,195 @@ +"use strict"; + +function normalizeText(value) { + return String(value || "").trim().toLowerCase().replace(/\s+/g, " "); +} + +function parseDate(value) { + const raw = String(value || ""); + const date = new Date(raw.includes("T") ? raw : `${raw}T00:00:00.000Z`); + return Number.isNaN(date.getTime()) ? null : date; +} + +function daysBetween(startValue, endValue) { + const start = parseDate(startValue); + const end = parseDate(endValue); + if (!start || !end) return null; + return Math.ceil((end.getTime() - start.getTime()) / 86400000); +} + +function basisPointsIncrease(currentAnnualCents, proposedAnnualCents) { + const current = Number(currentAnnualCents || 0); + const proposed = Number(proposedAnnualCents || 0); + if (current <= 0) return null; + return Math.round(((proposed - current) / current) * 10000); +} + +function isBeforeOrSame(leftValue, rightValue) { + const left = parseDate(leftValue); + const right = parseDate(rightValue); + if (!left || !right) return false; + return left.getTime() <= right.getTime(); +} + +function evaluateRenewal(renewal, context) { + const policy = context.policy; + const findings = []; + const increaseBps = basisPointsIncrease(renewal.currentAnnualCents, renewal.proposedAnnualCents); + const contractCap = renewal.contract?.upliftCapBps ?? policy.defaultMaxIncreaseBps; + const noticeDays = daysBetween(renewal.notice?.sentAt, renewal.renewalDate); + const cpiAgeDays = daysBetween(renewal.cpiEvidence?.observedAt, context.checkedAt); + const priceLocked = isBeforeOrSame(renewal.renewalDate, renewal.contract?.priceLockUntil); + + if (increaseBps === null) { + findings.push({ + code: "MISSING_BASE_PRICE", + severity: "hold", + message: `${renewal.id} is missing a positive current annual price for uplift validation.`, + }); + } else if (increaseBps < policy.maxUnsupportedIncreaseBps) { + findings.push({ + code: "NEGATIVE_UPLIFT_REVIEW", + severity: "review", + message: `${renewal.id} lowers annual price by ${Math.abs(increaseBps)} bps and needs revenue approval.`, + }); + } else if (increaseBps > contractCap) { + findings.push({ + code: "CONTRACT_CAP_EXCEEDED", + severity: "hold", + message: `${renewal.id} proposes ${increaseBps} bps uplift over the ${contractCap} bps contract cap.`, + }); + } + + if (priceLocked && increaseBps > 0) { + findings.push({ + code: "PRICE_LOCK_ACTIVE", + severity: "hold", + message: `${renewal.id} proposes an uplift while the contract price lock is still active.`, + }); + } + + if (renewal.contract?.excludedFromAutoUplift && increaseBps > 0 && !renewal.approval) { + findings.push({ + code: "EXCLUDED_ACCOUNT_NO_APPROVAL", + severity: "hold", + message: `${renewal.id} is excluded from automatic uplift and has no exception approval.`, + }); + } else if (renewal.contract?.excludedFromAutoUplift && increaseBps > 0 && renewal.approval) { + findings.push({ + code: "EXCLUDED_ACCOUNT_APPROVAL_REVIEW", + severity: "review", + message: `${renewal.id} has an exception approval that finance should verify before release.`, + }); + } + + if (!renewal.cpiEvidence?.source || renewal.cpiEvidence.valueBps === undefined) { + findings.push({ + code: "MISSING_CPI_EVIDENCE", + severity: "hold", + message: `${renewal.id} lacks CPI/source evidence for the price uplift.`, + }); + } else { + if (cpiAgeDays !== null && cpiAgeDays > policy.maxCpiStalenessDays) { + findings.push({ + code: "STALE_CPI_EVIDENCE", + severity: "review", + message: `${renewal.id} CPI evidence is ${cpiAgeDays} days old.`, + }); + } + if (increaseBps !== null && increaseBps > renewal.cpiEvidence.valueBps && increaseBps > contractCap) { + findings.push({ + code: "UPLIFT_ABOVE_CPI_AND_CAP", + severity: "hold", + message: `${renewal.id} uplift exceeds both CPI evidence and contract cap.`, + }); + } + } + + const requiredNoticeDays = renewal.contract?.requiresNoticeDays ?? policy.minimumNoticeDays; + if (noticeDays === null) { + findings.push({ + code: "MISSING_NOTICE_DATE", + severity: "hold", + message: `${renewal.id} is missing customer notice evidence.`, + }); + } else if (noticeDays < requiredNoticeDays) { + findings.push({ + code: "NOTICE_WINDOW_SHORT", + severity: "hold", + message: `${renewal.id} has ${noticeDays} notice days, below required ${requiredNoticeDays}.`, + }); + } + + const blockers = findings.filter((finding) => finding.severity === "hold"); + const reviews = findings.filter((finding) => finding.severity === "review"); + const decision = blockers.length > 0 ? "hold" : reviews.length > 0 ? "review" : "release"; + + return { + renewalId: renewal.id, + customer: renewal.customer, + segment: renewal.segment, + currentAnnualCents: renewal.currentAnnualCents, + proposedAnnualCents: renewal.proposedAnnualCents, + increaseBps, + contractCapBps: contractCap, + decision, + findings, + remediation: remediationFor(decision, findings), + }; +} + +function remediationFor(decision, findings) { + if (decision === "release") return ["Release renewal uplift to invoice staging."]; + return findings + .filter((finding) => finding.severity === "hold" || finding.severity === "review") + .map((finding) => { + if (finding.code === "CONTRACT_CAP_EXCEEDED") return "Reduce uplift to contract cap or collect signed exception approval."; + if (finding.code === "PRICE_LOCK_ACTIVE") return "Keep current price until lock expires or attach signed amendment."; + if (finding.code === "NOTICE_WINDOW_SHORT") return "Delay renewal invoice or restart customer notice window."; + if (finding.code === "STALE_CPI_EVIDENCE") return "Refresh CPI/source evidence before releasing uplift."; + if (finding.code === "EXCLUDED_ACCOUNT_APPROVAL_REVIEW") return "Verify exception approval scope before invoice release."; + return "Resolve uplift evidence and rerun the escalation cap guard."; + }); +} + +function analyzePriceEscalations(input) { + const policy = { + defaultMaxIncreaseBps: input.policy?.defaultMaxIncreaseBps ?? 800, + minimumNoticeDays: input.policy?.minimumNoticeDays ?? 45, + maxCpiStalenessDays: input.policy?.maxCpiStalenessDays ?? 120, + maxUnsupportedIncreaseBps: input.policy?.maxUnsupportedIncreaseBps ?? 0, + }; + const checkedAt = input.checkedAt || new Date().toISOString(); + const renewals = input.renewals || []; + const decisions = renewals.map((renewal) => evaluateRenewal(renewal, { checkedAt, policy })); + const summary = { + renewals: decisions.length, + release: decisions.filter((item) => item.decision === "release").length, + review: decisions.filter((item) => item.decision === "review").length, + hold: decisions.filter((item) => item.decision === "hold").length, + currentAnnualCents: decisions.reduce((sum, item) => sum + Number(item.currentAnnualCents || 0), 0), + proposedAnnualCents: decisions.reduce((sum, item) => sum + Number(item.proposedAnnualCents || 0), 0), + }; + + return { + status: summary.hold > 0 ? "hold" : summary.review > 0 ? "review" : "release", + checkedAt, + policy, + summary, + decisions, + warnings: renewals.length === 0 ? [{ code: "NO_RENEWALS", message: "No renewals were available for price escalation review." }] : [], + }; +} + +function formatUsd(cents) { + return `$${(Number(cents || 0) / 100).toFixed(2)}`; +} + +module.exports = { + analyzePriceEscalations, + basisPointsIncrease, + daysBetween, + evaluateRenewal, + formatUsd, + normalizeText, +}; diff --git a/revenue-price-escalation-cap-guard/reports/demo.mp4 b/revenue-price-escalation-cap-guard/reports/demo.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..e90fe1e056eb0c3ccdbeb4856b10b1011507b9bc GIT binary patch literal 26441 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 lXg!3rbPe% literal 0 HcmV?d00001 diff --git a/revenue-price-escalation-cap-guard/reports/price-escalation-packet.json b/revenue-price-escalation-cap-guard/reports/price-escalation-packet.json new file mode 100644 index 00000000..764e4346 --- /dev/null +++ b/revenue-price-escalation-cap-guard/reports/price-escalation-packet.json @@ -0,0 +1,127 @@ +{ + "status": "hold", + "checkedAt": "2026-05-31T09:15:00.000Z", + "policy": { + "defaultMaxIncreaseBps": 800, + "minimumNoticeDays": 45, + "maxCpiStalenessDays": 120, + "maxUnsupportedIncreaseBps": 0 + }, + "summary": { + "renewals": 5, + "release": 1, + "review": 1, + "hold": 3, + "currentAnnualCents": 5080000, + "proposedAnnualCents": 5406000 + }, + "decisions": [ + { + "renewalId": "ren-clean-cpi", + "customer": "Northlake Genomics Lab", + "segment": "institutional", + "currentAnnualCents": 1200000, + "proposedAnnualCents": 1260000, + "increaseBps": 500, + "contractCapBps": 600, + "decision": "release", + "findings": [], + "remediation": [ + "Release renewal uplift to invoice staging." + ] + }, + { + "renewalId": "ren-over-cap", + "customer": "East Harbor Quant Lab", + "segment": "institutional", + "currentAnnualCents": 900000, + "proposedAnnualCents": 1017000, + "increaseBps": 1300, + "contractCapBps": 800, + "decision": "hold", + "findings": [ + { + "code": "CONTRACT_CAP_EXCEEDED", + "severity": "hold", + "message": "ren-over-cap proposes 1300 bps uplift over the 800 bps contract cap." + }, + { + "code": "UPLIFT_ABOVE_CPI_AND_CAP", + "severity": "hold", + "message": "ren-over-cap uplift exceeds both CPI evidence and contract cap." + } + ], + "remediation": [ + "Reduce uplift to contract cap or collect signed exception approval.", + "Resolve uplift evidence and rerun the escalation cap guard." + ] + }, + { + "renewalId": "ren-stale-cpi", + "customer": "Alpine Neuroimaging Institute", + "segment": "institutional", + "currentAnnualCents": 460000, + "proposedAnnualCents": 483000, + "increaseBps": 500, + "contractCapBps": 700, + "decision": "review", + "findings": [ + { + "code": "STALE_CPI_EVIDENCE", + "severity": "review", + "message": "ren-stale-cpi CPI evidence is 152 days old." + } + ], + "remediation": [ + "Refresh CPI/source evidence before releasing uplift." + ] + }, + { + "renewalId": "ren-late-notice", + "customer": "Cedar Clinical Trials Office", + "segment": "institutional", + "currentAnnualCents": 320000, + "proposedAnnualCents": 336000, + "increaseBps": 500, + "contractCapBps": 600, + "decision": "hold", + "findings": [ + { + "code": "NOTICE_WINDOW_SHORT", + "severity": "hold", + "message": "ren-late-notice has 31 notice days, below required 45." + } + ], + "remediation": [ + "Delay renewal invoice or restart customer notice window." + ] + }, + { + "renewalId": "ren-price-locked", + "customer": "Riverbend Consortium", + "segment": "consortium", + "currentAnnualCents": 2200000, + "proposedAnnualCents": 2310000, + "increaseBps": 500, + "contractCapBps": 500, + "decision": "hold", + "findings": [ + { + "code": "PRICE_LOCK_ACTIVE", + "severity": "hold", + "message": "ren-price-locked proposes an uplift while the contract price lock is still active." + }, + { + "code": "EXCLUDED_ACCOUNT_APPROVAL_REVIEW", + "severity": "review", + "message": "ren-price-locked has an exception approval that finance should verify before release." + } + ], + "remediation": [ + "Keep current price until lock expires or attach signed amendment.", + "Verify exception approval scope before invoice release." + ] + } + ], + "warnings": [] +} diff --git a/revenue-price-escalation-cap-guard/reports/price-escalation-report.md b/revenue-price-escalation-cap-guard/reports/price-escalation-report.md new file mode 100644 index 00000000..0b3c2005 --- /dev/null +++ b/revenue-price-escalation-cap-guard/reports/price-escalation-report.md @@ -0,0 +1,24 @@ +# Revenue Price Escalation Cap Guard + +Status: **hold** + +## Summary + +- Renewals checked: 5 +- Release: 1 +- Review: 1 +- Hold: 3 +- Current ARR reviewed: $50800.00 +- Proposed ARR reviewed: $54060.00 + +## Decisions + +- ren-clean-cpi: release (none) +- ren-over-cap: hold (CONTRACT_CAP_EXCEEDED, UPLIFT_ABOVE_CPI_AND_CAP) +- ren-stale-cpi: review (STALE_CPI_EVIDENCE) +- ren-late-notice: hold (NOTICE_WINDOW_SHORT) +- ren-price-locked: hold (PRICE_LOCK_ACTIVE, EXCLUDED_ACCOUNT_APPROVAL_REVIEW) + +## Scope + +This guard supports SCIBASE revenue operations by holding or routing institutional renewal price escalations before invoice release when contract caps, CPI evidence, notice windows, price locks, or exception approvals need attention. diff --git a/revenue-tax-exemption-hold-guard/reports/summary.svg b/revenue-price-escalation-cap-guard/reports/summary.svg similarity index 57% rename from revenue-tax-exemption-hold-guard/reports/summary.svg rename to revenue-price-escalation-cap-guard/reports/summary.svg index ce47c25d..6a5eeacf 100644 --- a/revenue-tax-exemption-hold-guard/reports/summary.svg +++ b/revenue-price-escalation-cap-guard/reports/summary.svg @@ -1,19 +1,19 @@ - - Revenue tax exemption hold guard - Status: HOLD - + + Revenue price escalation cap guard + Status: HOLD + 5 - invoices checked - + renewals checked + 1 - release + release - 2 + 1 review - 2 + 3 hold - Checks exemption certificates, VAT evidence, PO tax terms, certificate expiry, and mixed taxable lines. - Reviewed invoice amount: $38650.00 + Checks contract uplift caps, CPI evidence, notice windows, price locks, and exception approvals. + Proposed ARR reviewed: $54060.00 diff --git a/revenue-price-escalation-cap-guard/sampleRenewals.js b/revenue-price-escalation-cap-guard/sampleRenewals.js new file mode 100644 index 00000000..eb3849ce --- /dev/null +++ b/revenue-price-escalation-cap-guard/sampleRenewals.js @@ -0,0 +1,100 @@ +"use strict"; + +const sampleRenewals = { + checkedAt: "2026-05-31T09:15:00.000Z", + policy: { + defaultMaxIncreaseBps: 800, + minimumNoticeDays: 45, + maxCpiStalenessDays: 120, + maxUnsupportedIncreaseBps: 0, + }, + renewals: [ + { + id: "ren-clean-cpi", + customer: "Northlake Genomics Lab", + segment: "institutional", + renewalDate: "2026-08-01", + currentAnnualCents: 1200000, + proposedAnnualCents: 1260000, + contract: { + upliftCapBps: 600, + priceLockUntil: "2026-07-31", + requiresNoticeDays: 45, + excludedFromAutoUplift: false, + }, + cpiEvidence: { source: "national-statistics-office", valueBps: 490, observedAt: "2026-04-30" }, + notice: { sentAt: "2026-05-31", channel: "billing-admin-email" }, + approval: null, + }, + { + id: "ren-over-cap", + customer: "East Harbor Quant Lab", + segment: "institutional", + renewalDate: "2026-07-15", + currentAnnualCents: 900000, + proposedAnnualCents: 1017000, + contract: { + upliftCapBps: 800, + priceLockUntil: "2026-06-30", + requiresNoticeDays: 45, + excludedFromAutoUplift: false, + }, + cpiEvidence: { source: "national-statistics-office", valueBps: 520, observedAt: "2026-04-30" }, + notice: { sentAt: "2026-05-31", channel: "billing-admin-email" }, + approval: null, + }, + { + id: "ren-stale-cpi", + customer: "Alpine Neuroimaging Institute", + segment: "institutional", + renewalDate: "2026-09-01", + currentAnnualCents: 460000, + proposedAnnualCents: 483000, + contract: { + upliftCapBps: 700, + priceLockUntil: "2026-08-31", + requiresNoticeDays: 45, + excludedFromAutoUplift: false, + }, + cpiEvidence: { source: "national-statistics-office", valueBps: 500, observedAt: "2025-12-31" }, + notice: { sentAt: "2026-06-20", channel: "billing-admin-email" }, + approval: null, + }, + { + id: "ren-late-notice", + customer: "Cedar Clinical Trials Office", + segment: "institutional", + renewalDate: "2026-07-01", + currentAnnualCents: 320000, + proposedAnnualCents: 336000, + contract: { + upliftCapBps: 600, + priceLockUntil: "2026-06-30", + requiresNoticeDays: 45, + excludedFromAutoUplift: false, + }, + cpiEvidence: { source: "national-statistics-office", valueBps: 510, observedAt: "2026-04-30" }, + notice: { sentAt: "2026-05-31", channel: "billing-admin-email" }, + approval: null, + }, + { + id: "ren-price-locked", + customer: "Riverbend Consortium", + segment: "consortium", + renewalDate: "2026-08-15", + currentAnnualCents: 2200000, + proposedAnnualCents: 2310000, + contract: { + upliftCapBps: 500, + priceLockUntil: "2026-12-31", + requiresNoticeDays: 60, + excludedFromAutoUplift: true, + }, + cpiEvidence: { source: "national-statistics-office", valueBps: 500, observedAt: "2026-04-30" }, + notice: { sentAt: "2026-05-31", channel: "billing-admin-email" }, + approval: { id: "APP-77", approver: "finance-director", reason: "manual consortium renewal exception" }, + }, + ], +}; + +module.exports = { sampleRenewals }; diff --git a/revenue-price-escalation-cap-guard/test.js b/revenue-price-escalation-cap-guard/test.js new file mode 100644 index 00000000..bb2577cf --- /dev/null +++ b/revenue-price-escalation-cap-guard/test.js @@ -0,0 +1,67 @@ +"use strict"; + +const assert = require("assert"); +const { + analyzePriceEscalations, + basisPointsIncrease, + daysBetween, + formatUsd, +} = require("./priceEscalationCapGuard"); +const { sampleRenewals } = require("./sampleRenewals"); + +assert.strictEqual(daysBetween("2026-05-31", "2026-07-15"), 45); +assert.strictEqual(basisPointsIncrease(100000, 108000), 800); +assert.strictEqual(formatUsd(123456), "$1234.56"); + +const packet = analyzePriceEscalations(sampleRenewals); +assert.strictEqual(packet.status, "hold"); +assert.strictEqual(packet.summary.renewals, 5); +assert.strictEqual(packet.summary.release, 1); +assert.strictEqual(packet.summary.review, 1); +assert.strictEqual(packet.summary.hold, 3); + +const overCap = packet.decisions.find((item) => item.renewalId === "ren-over-cap"); +assert(overCap); +assert.strictEqual(overCap.decision, "hold"); +assert(overCap.findings.some((finding) => finding.code === "CONTRACT_CAP_EXCEEDED")); + +const stale = packet.decisions.find((item) => item.renewalId === "ren-stale-cpi"); +assert(stale); +assert.strictEqual(stale.decision, "review"); +assert(stale.findings.some((finding) => finding.code === "STALE_CPI_EVIDENCE")); + +const lateNotice = packet.decisions.find((item) => item.renewalId === "ren-late-notice"); +assert(lateNotice); +assert.strictEqual(lateNotice.decision, "hold"); +assert(lateNotice.findings.some((finding) => finding.code === "NOTICE_WINDOW_SHORT")); + +const priceLocked = packet.decisions.find((item) => item.renewalId === "ren-price-locked"); +assert(priceLocked); +assert.strictEqual(priceLocked.decision, "hold"); +assert(priceLocked.findings.some((finding) => finding.code === "PRICE_LOCK_ACTIVE")); +assert(priceLocked.findings.some((finding) => finding.code === "EXCLUDED_ACCOUNT_APPROVAL_REVIEW")); + +const clean = analyzePriceEscalations({ + checkedAt: "2026-05-31T09:15:00.000Z", + renewals: [ + { + id: "ren-clean", + customer: "Clean Lab", + segment: "institutional", + renewalDate: "2026-08-01", + currentAnnualCents: 100000, + proposedAnnualCents: 105000, + contract: { upliftCapBps: 600, priceLockUntil: "2026-07-31", requiresNoticeDays: 45 }, + cpiEvidence: { source: "national-statistics-office", valueBps: 500, observedAt: "2026-04-30" }, + notice: { sentAt: "2026-05-31", channel: "billing-admin-email" }, + }, + ], +}); +assert.strictEqual(clean.status, "release"); +assert.strictEqual(clean.summary.release, 1); + +const empty = analyzePriceEscalations({ renewals: [] }); +assert.strictEqual(empty.status, "release"); +assert.strictEqual(empty.warnings[0].code, "NO_RENEWALS"); + +console.log("revenue-price-escalation-cap-guard tests passed"); diff --git a/revenue-tax-exemption-hold-guard/README.md b/revenue-tax-exemption-hold-guard/README.md deleted file mode 100644 index 0757091e..00000000 --- a/revenue-tax-exemption-hold-guard/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# Revenue Tax Exemption Hold Guard - -This is a self-contained Revenue Infrastructure slice for issue #20. - -The guard checks whether institutional invoices that request tax-exempt or reverse-charge treatment can be released before billing. It validates exemption certificate freshness, VAT or jurisdiction evidence, PO tax terms, mixed taxable line items, and remediation actions for subscription, compute, and analytics-license revenue. - -## Scope - -- Synthetic data only. -- No network calls, credentials, payment processor integration, tax filing, bank data, or SCIBASE production service integration. -- Focused on institutional tax/VAT exemption release safety, not broad billing ledgers, usage metering, receipt privacy, refunds, cancellation, dunning, sanctions screening, support entitlement, collections, or analytics seat rosters. - -## Validation - -```sh -node revenue-tax-exemption-hold-guard/test.js -node revenue-tax-exemption-hold-guard/demo.js -``` - -The demo writes deterministic reviewer artifacts under `revenue-tax-exemption-hold-guard/reports/`. - -Reviewer artifacts: - -- `reports/tax-exemption-packet.json` -- `reports/tax-exemption-report.md` -- `reports/summary.svg` -- `reports/demo.mp4` diff --git a/revenue-tax-exemption-hold-guard/reports/demo.mp4 b/revenue-tax-exemption-hold-guard/reports/demo.mp4 deleted file mode 100644 index d7fe746de21162ed5d202f5fba7d449ebff59b12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25977 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 diff --git a/revenue-tax-exemption-hold-guard/reports/tax-exemption-packet.json b/revenue-tax-exemption-hold-guard/reports/tax-exemption-packet.json deleted file mode 100644 index 231cb401..00000000 --- a/revenue-tax-exemption-hold-guard/reports/tax-exemption-packet.json +++ /dev/null @@ -1,114 +0,0 @@ -{ - "status": "hold", - "checkedAt": "2026-05-31T08:55:00.000Z", - "policy": { - "expiryWarningDays": 30, - "taxableLineReviewThresholdCents": 25000 - }, - "summary": { - "invoices": 5, - "release": 1, - "review": 2, - "hold": 2, - "totalAmountCents": 3865000, - "taxableAmountCents": 405000 - }, - "decisions": [ - { - "invoiceId": "inv-clean-us-lab", - "customer": "Northlake Genomics Lab", - "billingModel": "institutional_invoice", - "requestedTaxTreatment": "exempt", - "amountCents": 1380000, - "taxableCents": 0, - "decision": "release", - "findings": [], - "remediation": [ - "Release invoice with current exemption evidence packet." - ] - }, - { - "invoiceId": "inv-expired-certificate", - "customer": "Bay Ridge Materials Center", - "billingModel": "institutional_invoice", - "requestedTaxTreatment": "exempt", - "amountCents": 720000, - "taxableCents": 0, - "decision": "hold", - "findings": [ - { - "code": "CERTIFICATE_EXPIRED", - "severity": "hold", - "message": "inv-expired-certificate certificate cert-us-2024-144 expired 46 days before billing review." - } - ], - "remediation": [ - "Request renewed exemption certificate before invoice release." - ] - }, - { - "invoiceId": "inv-eu-missing-vat", - "customer": "Alpine Neuroimaging Institute", - "billingModel": "institutional_invoice", - "requestedTaxTreatment": "reverse_charge", - "amountCents": 460000, - "taxableCents": 0, - "decision": "hold", - "findings": [ - { - "code": "MISSING_VAT_EVIDENCE", - "severity": "hold", - "message": "inv-eu-missing-vat is missing VAT or reverse-charge evidence for institutional billing." - } - ], - "remediation": [ - "Collect validated VAT ID or reverse-charge evidence before release." - ] - }, - { - "invoiceId": "inv-mixed-taxable-lines", - "customer": "East Harbor Quant Lab", - "billingModel": "institutional_invoice", - "requestedTaxTreatment": "exempt", - "amountCents": 985000, - "taxableCents": 85000, - "decision": "review", - "findings": [ - { - "code": "CERTIFICATE_EXPIRING_SOON", - "severity": "review", - "message": "inv-mixed-taxable-lines certificate cert-us-2026-212 expires in 18 days." - }, - { - "code": "MIXED_TAXABLE_LINES", - "severity": "review", - "message": "inv-mixed-taxable-lines contains $850.00 in taxable lines under exempt treatment." - } - ], - "remediation": [ - "Confirm certificate remains valid for service period and archive renewal task.", - "Route taxable line items to finance review before final tax treatment." - ] - }, - { - "invoiceId": "inv-po-tax-conflict", - "customer": "Cedar Clinical Trials Office", - "billingModel": "institutional_invoice", - "requestedTaxTreatment": "standard", - "amountCents": 320000, - "taxableCents": 320000, - "decision": "review", - "findings": [ - { - "code": "PO_TAX_TREATMENT_CONFLICT", - "severity": "review", - "message": "inv-po-tax-conflict PO tax treatment is exempt, but invoice requests standard." - } - ], - "remediation": [ - "Resolve PO and invoice tax-treatment mismatch with finance operations." - ] - } - ], - "warnings": [] -} diff --git a/revenue-tax-exemption-hold-guard/reports/tax-exemption-report.md b/revenue-tax-exemption-hold-guard/reports/tax-exemption-report.md deleted file mode 100644 index 67d8c2a8..00000000 --- a/revenue-tax-exemption-hold-guard/reports/tax-exemption-report.md +++ /dev/null @@ -1,24 +0,0 @@ -# Revenue Tax Exemption Hold Guard - -Status: **hold** - -## Summary - -- Invoices checked: 5 -- Release: 1 -- Review: 2 -- Hold: 2 -- Invoice amount reviewed: $38650.00 -- Taxable line amount: $4050.00 - -## Decisions - -- inv-clean-us-lab: release (none) -- inv-expired-certificate: hold (CERTIFICATE_EXPIRED) -- inv-eu-missing-vat: hold (MISSING_VAT_EVIDENCE) -- inv-mixed-taxable-lines: review (CERTIFICATE_EXPIRING_SOON, MIXED_TAXABLE_LINES) -- inv-po-tax-conflict: review (PO_TAX_TREATMENT_CONFLICT) - -## Scope - -This guard supports SCIBASE revenue operations by holding or routing institutional tax-exempt and reverse-charge invoices before billing release when exemption evidence, VAT evidence, PO terms, certificate freshness, or mixed taxable lines need attention. diff --git a/revenue-tax-exemption-hold-guard/sampleInvoices.js b/revenue-tax-exemption-hold-guard/sampleInvoices.js deleted file mode 100644 index 99340f92..00000000 --- a/revenue-tax-exemption-hold-guard/sampleInvoices.js +++ /dev/null @@ -1,119 +0,0 @@ -"use strict"; - -const sampleInvoices = { - checkedAt: "2026-05-31T08:55:00.000Z", - policy: { - expiryWarningDays: 30, - taxableLineReviewThresholdCents: 25000, - }, - invoices: [ - { - id: "inv-clean-us-lab", - customer: "Northlake Genomics Lab", - country: "US", - billingModel: "institutional_invoice", - requestedTaxTreatment: "exempt", - certificate: { - id: "cert-us-2026-001", - type: "state_exemption", - status: "verified", - expiresAt: "2027-04-30", - jurisdiction: "US-MA", - }, - purchaseOrder: { - id: "PO-8842", - taxTreatment: "exempt", - jurisdiction: "US-MA", - }, - lines: [ - { id: "line-sub", category: "subscription", description: "Institutional annual plan", taxable: false, amountCents: 1200000 }, - { id: "line-compute", category: "compute", description: "Reproducibility compute pack", taxable: false, amountCents: 180000 }, - ], - }, - { - id: "inv-expired-certificate", - customer: "Bay Ridge Materials Center", - country: "US", - billingModel: "institutional_invoice", - requestedTaxTreatment: "exempt", - certificate: { - id: "cert-us-2024-144", - type: "state_exemption", - status: "verified", - expiresAt: "2026-04-15", - jurisdiction: "US-CA", - }, - purchaseOrder: { - id: "PO-8120", - taxTreatment: "exempt", - jurisdiction: "US-CA", - }, - lines: [ - { id: "line-sub", category: "subscription", description: "Lab annual plan", taxable: false, amountCents: 720000 }, - ], - }, - { - id: "inv-eu-missing-vat", - customer: "Alpine Neuroimaging Institute", - country: "DE", - billingModel: "institutional_invoice", - requestedTaxTreatment: "reverse_charge", - certificate: { - id: "cert-eu-2026-087", - type: "vat_reverse_charge", - status: "verified", - expiresAt: "2026-12-31", - jurisdiction: "EU", - }, - purchaseOrder: { - id: "PO-2239", - taxTreatment: "reverse_charge", - jurisdiction: "DE", - }, - lines: [ - { id: "line-license", category: "analytics_license", description: "Research trend analytics API", taxable: false, amountCents: 460000 }, - ], - }, - { - id: "inv-mixed-taxable-lines", - customer: "East Harbor Quant Lab", - country: "US", - billingModel: "institutional_invoice", - requestedTaxTreatment: "exempt", - certificate: { - id: "cert-us-2026-212", - type: "state_exemption", - status: "verified", - expiresAt: "2026-06-18", - jurisdiction: "US-NY", - }, - purchaseOrder: { - id: "PO-3371", - taxTreatment: "exempt", - jurisdiction: "US-NY", - }, - lines: [ - { id: "line-sub", category: "subscription", description: "Lab annual plan", taxable: false, amountCents: 900000 }, - { id: "line-training", category: "professional_services", description: "Onsite onboarding workshop", taxable: true, amountCents: 85000 }, - ], - }, - { - id: "inv-po-tax-conflict", - customer: "Cedar Clinical Trials Office", - country: "US", - billingModel: "institutional_invoice", - requestedTaxTreatment: "standard", - certificate: null, - purchaseOrder: { - id: "PO-5581", - taxTreatment: "exempt", - jurisdiction: "US-TX", - }, - lines: [ - { id: "line-compute", category: "compute", description: "Clinical reproducibility compute runs", taxable: true, amountCents: 320000 }, - ], - }, - ], -}; - -module.exports = { sampleInvoices }; diff --git a/revenue-tax-exemption-hold-guard/taxExemptionHoldGuard.js b/revenue-tax-exemption-hold-guard/taxExemptionHoldGuard.js deleted file mode 100644 index b0114393..00000000 --- a/revenue-tax-exemption-hold-guard/taxExemptionHoldGuard.js +++ /dev/null @@ -1,188 +0,0 @@ -"use strict"; - -const EU_COUNTRIES = new Set([ - "AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", - "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", - "SI", "ES", "SE", -]); - -function normalizeText(value) { - return String(value || "").trim().toLowerCase().replace(/\s+/g, " "); -} - -function parseDate(value) { - const date = new Date(`${value}T00:00:00.000Z`); - return Number.isNaN(date.getTime()) ? null : date; -} - -function daysUntil(dateValue, nowValue) { - const date = parseDate(dateValue); - const now = new Date(nowValue); - if (!date || Number.isNaN(now.getTime())) { - return null; - } - return Math.ceil((date.getTime() - now.getTime()) / 86400000); -} - -function lineTotalCents(invoice) { - return (invoice.lines || []).reduce((sum, line) => sum + Number(line.amountCents || 0), 0); -} - -function taxableLineTotalCents(invoice) { - return (invoice.lines || []) - .filter((line) => line.taxable) - .reduce((sum, line) => sum + Number(line.amountCents || 0), 0); -} - -function hasVatEvidence(invoice) { - return Boolean(invoice.vatId || invoice.taxRegistrationId || invoice.reverseChargeEvidenceId); -} - -function evaluateInvoice(invoice, context) { - const policy = context.policy; - const findings = []; - const taxTreatment = normalizeText(invoice.requestedTaxTreatment); - const poTaxTreatment = normalizeText(invoice.purchaseOrder?.taxTreatment); - const certificate = invoice.certificate; - const country = String(invoice.country || "").toUpperCase(); - const taxableCents = taxableLineTotalCents(invoice); - - if ((taxTreatment === "exempt" || taxTreatment === "reverse_charge") && !certificate) { - findings.push({ - code: "MISSING_EXEMPTION_EVIDENCE", - severity: "hold", - message: `${invoice.id} requests ${taxTreatment} treatment without a certificate or evidence record.`, - }); - } - - if (certificate) { - const days = daysUntil(certificate.expiresAt, context.checkedAt); - if (normalizeText(certificate.status) !== "verified") { - findings.push({ - code: "CERTIFICATE_NOT_VERIFIED", - severity: "hold", - message: `${invoice.id} has certificate ${certificate.id} but it is not verified.`, - }); - } - if (days !== null && days < 0) { - findings.push({ - code: "CERTIFICATE_EXPIRED", - severity: "hold", - message: `${invoice.id} certificate ${certificate.id} expired ${Math.abs(days)} days before billing review.`, - }); - } else if (days !== null && days <= policy.expiryWarningDays) { - findings.push({ - code: "CERTIFICATE_EXPIRING_SOON", - severity: "review", - message: `${invoice.id} certificate ${certificate.id} expires in ${days} days.`, - }); - } - } - - if (taxTreatment === "reverse_charge") { - if (!EU_COUNTRIES.has(country)) { - findings.push({ - code: "REVERSE_CHARGE_JURISDICTION_MISMATCH", - severity: "hold", - message: `${invoice.id} requests reverse-charge treatment outside a supported EU jurisdiction.`, - }); - } - if (!hasVatEvidence(invoice)) { - findings.push({ - code: "MISSING_VAT_EVIDENCE", - severity: "hold", - message: `${invoice.id} is missing VAT or reverse-charge evidence for institutional billing.`, - }); - } - } - - if (poTaxTreatment && poTaxTreatment !== taxTreatment) { - findings.push({ - code: "PO_TAX_TREATMENT_CONFLICT", - severity: taxTreatment === "standard" ? "review" : "hold", - message: `${invoice.id} PO tax treatment is ${poTaxTreatment}, but invoice requests ${taxTreatment}.`, - }); - } - - if (taxableCents > 0 && (taxTreatment === "exempt" || taxTreatment === "reverse_charge")) { - findings.push({ - code: "MIXED_TAXABLE_LINES", - severity: taxableCents >= policy.taxableLineReviewThresholdCents ? "review" : "note", - message: `${invoice.id} contains ${formatUsd(taxableCents)} in taxable lines under ${taxTreatment} treatment.`, - }); - } - - const blockers = findings.filter((finding) => finding.severity === "hold"); - const reviews = findings.filter((finding) => finding.severity === "review"); - const decision = blockers.length > 0 ? "hold" : reviews.length > 0 ? "review" : "release"; - - return { - invoiceId: invoice.id, - customer: invoice.customer, - billingModel: invoice.billingModel, - requestedTaxTreatment: invoice.requestedTaxTreatment, - amountCents: lineTotalCents(invoice), - taxableCents, - decision, - findings, - remediation: remediationFor(decision, findings), - }; -} - -function remediationFor(decision, findings) { - if (decision === "release") { - return ["Release invoice with current exemption evidence packet."]; - } - return findings - .filter((finding) => finding.severity === "hold" || finding.severity === "review") - .map((finding) => { - if (finding.code === "CERTIFICATE_EXPIRED") return "Request renewed exemption certificate before invoice release."; - if (finding.code === "MISSING_VAT_EVIDENCE") return "Collect validated VAT ID or reverse-charge evidence before release."; - if (finding.code === "MIXED_TAXABLE_LINES") return "Route taxable line items to finance review before final tax treatment."; - if (finding.code === "PO_TAX_TREATMENT_CONFLICT") return "Resolve PO and invoice tax-treatment mismatch with finance operations."; - if (finding.code === "CERTIFICATE_EXPIRING_SOON") return "Confirm certificate remains valid for service period and archive renewal task."; - return "Attach missing tax evidence and rerun the hold guard."; - }); -} - -function analyzeTaxExemptionHolds(input) { - const policy = { - expiryWarningDays: input.policy?.expiryWarningDays ?? 30, - taxableLineReviewThresholdCents: input.policy?.taxableLineReviewThresholdCents ?? 25000, - }; - const checkedAt = input.checkedAt || new Date().toISOString(); - const invoices = input.invoices || []; - const decisions = invoices.map((invoice) => evaluateInvoice(invoice, { checkedAt, policy })); - const summary = { - invoices: decisions.length, - release: decisions.filter((item) => item.decision === "release").length, - review: decisions.filter((item) => item.decision === "review").length, - hold: decisions.filter((item) => item.decision === "hold").length, - totalAmountCents: decisions.reduce((sum, item) => sum + item.amountCents, 0), - taxableAmountCents: decisions.reduce((sum, item) => sum + item.taxableCents, 0), - }; - - return { - status: summary.hold > 0 ? "hold" : summary.review > 0 ? "review" : "release", - checkedAt, - policy, - summary, - decisions, - warnings: invoices.length === 0 ? [{ code: "NO_INVOICES", message: "No invoices were available for tax exemption review." }] : [], - }; -} - -function formatUsd(cents) { - return `$${(Number(cents || 0) / 100).toFixed(2)}`; -} - -module.exports = { - analyzeTaxExemptionHolds, - daysUntil, - evaluateInvoice, - formatUsd, - hasVatEvidence, - lineTotalCents, - normalizeText, - taxableLineTotalCents, -}; diff --git a/revenue-tax-exemption-hold-guard/test.js b/revenue-tax-exemption-hold-guard/test.js deleted file mode 100644 index be436016..00000000 --- a/revenue-tax-exemption-hold-guard/test.js +++ /dev/null @@ -1,72 +0,0 @@ -"use strict"; - -const assert = require("assert"); -const { - analyzeTaxExemptionHolds, - daysUntil, - formatUsd, - hasVatEvidence, - lineTotalCents, - taxableLineTotalCents, -} = require("./taxExemptionHoldGuard"); -const { sampleInvoices } = require("./sampleInvoices"); - -assert.strictEqual(daysUntil("2026-06-30", "2026-05-31T00:00:00.000Z"), 30); -assert.strictEqual(formatUsd(123456), "$1234.56"); -assert.strictEqual(lineTotalCents(sampleInvoices.invoices[0]), 1380000); -assert.strictEqual(taxableLineTotalCents(sampleInvoices.invoices[3]), 85000); -assert.strictEqual(hasVatEvidence({ vatId: "DE123456789" }), true); -assert.strictEqual(hasVatEvidence({}), false); - -const packet = analyzeTaxExemptionHolds(sampleInvoices); -assert.strictEqual(packet.status, "hold"); -assert.strictEqual(packet.summary.invoices, 5); -assert.strictEqual(packet.summary.release, 1); -assert.strictEqual(packet.summary.review, 2); -assert.strictEqual(packet.summary.hold, 2); - -const expired = packet.decisions.find((item) => item.invoiceId === "inv-expired-certificate"); -assert(expired); -assert.strictEqual(expired.decision, "hold"); -assert(expired.findings.some((finding) => finding.code === "CERTIFICATE_EXPIRED")); - -const missingVat = packet.decisions.find((item) => item.invoiceId === "inv-eu-missing-vat"); -assert(missingVat); -assert.strictEqual(missingVat.decision, "hold"); -assert(missingVat.findings.some((finding) => finding.code === "MISSING_VAT_EVIDENCE")); - -const mixed = packet.decisions.find((item) => item.invoiceId === "inv-mixed-taxable-lines"); -assert(mixed); -assert.strictEqual(mixed.decision, "review"); -assert(mixed.findings.some((finding) => finding.code === "CERTIFICATE_EXPIRING_SOON")); -assert(mixed.findings.some((finding) => finding.code === "MIXED_TAXABLE_LINES")); - -const clean = analyzeTaxExemptionHolds({ - checkedAt: "2026-05-31T08:55:00.000Z", - invoices: [ - { - id: "inv-clean", - customer: "Clean Lab", - country: "US", - billingModel: "institutional_invoice", - requestedTaxTreatment: "exempt", - certificate: { - id: "cert-clean", - type: "state_exemption", - status: "verified", - expiresAt: "2027-01-01", - jurisdiction: "US-WA", - }, - purchaseOrder: { id: "PO-clean", taxTreatment: "exempt", jurisdiction: "US-WA" }, - lines: [{ id: "line-sub", category: "subscription", taxable: false, amountCents: 100000 }], - }, - ], -}); -assert.strictEqual(clean.status, "release"); -assert.strictEqual(clean.summary.release, 1); - -const empty = analyzeTaxExemptionHolds({ invoices: [] }); -assert.strictEqual(empty.status, "release"); -assert.strictEqual(empty.warnings[0].code, "NO_INVOICES"); - -console.log("revenue-tax-exemption-hold-guard tests passed");