diff --git a/enterprise-cost-center-chargeback-guard/README.md b/enterprise-cost-center-chargeback-guard/README.md new file mode 100644 index 00000000..bd4d4ada --- /dev/null +++ b/enterprise-cost-center-chargeback-guard/README.md @@ -0,0 +1,25 @@ +# Enterprise cost center chargeback guard + +This module checks whether institutional compute or service usage can be posted back to a department cost center. + +It focuses on one enterprise tooling slice: chargeback readiness. It does not process payments, move money, call finance systems, or store credentials. + +## What it checks + +- active cost center +- PI approval +- grant allowability +- expense category rules +- remaining budget and overage approval +- usage evidence +- data residency +- purchase order and tax code gaps + +## Run it + +```bash +node enterprise-cost-center-chargeback-guard/test.js +node enterprise-cost-center-chargeback-guard/demo.js +``` + +The demo writes a JSON result and Markdown report to `artifacts/`. diff --git a/enterprise-cost-center-chargeback-guard/artifacts/chargeback-report.md b/enterprise-cost-center-chargeback-guard/artifacts/chargeback-report.md new file mode 100644 index 00000000..57c86d73 --- /dev/null +++ b/enterprise-cost-center-chargeback-guard/artifacts/chargeback-report.md @@ -0,0 +1,28 @@ +# Enterprise cost center chargeback report + +## CB-1001: Neuroscience lab +Amount: 4200 +Decision: APPROVE_CHARGEBACK +Next step: post the chargeback to the institution ledger + +## CB-1002: Materials group +Amount: 9600 +Decision: ROUTE_FOR_APPROVAL +Next step: send to finance for a manual approval pass +Warnings: +- charge exceeds remaining budget but has overage approval +- no purchase order attached + +## CB-1003: Clinical data unit +Amount: 3100 +Decision: BLOCK_CHARGEBACK +Next step: do not post until blockers are fixed +Blockers: +- inactive cost center +- missing PI approval +- expense category not allowed +- missing usage evidence +- data residency mismatch +Warnings: +- no purchase order attached +- missing tax code diff --git a/enterprise-cost-center-chargeback-guard/artifacts/chargeback-results.json b/enterprise-cost-center-chargeback-guard/artifacts/chargeback-results.json new file mode 100644 index 00000000..62450284 --- /dev/null +++ b/enterprise-cost-center-chargeback-guard/artifacts/chargeback-results.json @@ -0,0 +1,41 @@ +[ + { + "recordId": "CB-1001", + "department": "Neuroscience lab", + "amount": 4200, + "decision": "APPROVE_CHARGEBACK", + "blockers": [], + "warnings": [], + "nextStep": "post the chargeback to the institution ledger" + }, + { + "recordId": "CB-1002", + "department": "Materials group", + "amount": 9600, + "decision": "ROUTE_FOR_APPROVAL", + "blockers": [], + "warnings": [ + "charge exceeds remaining budget but has overage approval", + "no purchase order attached" + ], + "nextStep": "send to finance for a manual approval pass" + }, + { + "recordId": "CB-1003", + "department": "Clinical data unit", + "amount": 3100, + "decision": "BLOCK_CHARGEBACK", + "blockers": [ + "inactive cost center", + "missing PI approval", + "expense category not allowed", + "missing usage evidence", + "data residency mismatch" + ], + "warnings": [ + "no purchase order attached", + "missing tax code" + ], + "nextStep": "do not post until blockers are fixed" + } +] \ No newline at end of file diff --git a/enterprise-cost-center-chargeback-guard/artifacts/demo.gif b/enterprise-cost-center-chargeback-guard/artifacts/demo.gif new file mode 100644 index 00000000..bf2efbb8 Binary files /dev/null and b/enterprise-cost-center-chargeback-guard/artifacts/demo.gif differ diff --git a/enterprise-cost-center-chargeback-guard/artifacts/demo.mp4 b/enterprise-cost-center-chargeback-guard/artifacts/demo.mp4 new file mode 100644 index 00000000..62baa3ff Binary files /dev/null and b/enterprise-cost-center-chargeback-guard/artifacts/demo.mp4 differ diff --git a/enterprise-cost-center-chargeback-guard/demo.js b/enterprise-cost-center-chargeback-guard/demo.js new file mode 100644 index 00000000..0cc0315e --- /dev/null +++ b/enterprise-cost-center-chargeback-guard/demo.js @@ -0,0 +1,13 @@ +const fs = require("fs"); +const path = require("path"); +const records = require("./sample-data.json"); +const { evaluateChargebacks, renderMarkdownReport } = require("./index"); + +const artifactsDir = path.join(__dirname, "artifacts"); +fs.mkdirSync(artifactsDir, { recursive: true }); + +const results = evaluateChargebacks(records); +fs.writeFileSync(path.join(artifactsDir, "chargeback-results.json"), JSON.stringify(results, null, 2)); +fs.writeFileSync(path.join(artifactsDir, "chargeback-report.md"), renderMarkdownReport(results)); + +console.log(renderMarkdownReport(results)); diff --git a/enterprise-cost-center-chargeback-guard/index.js b/enterprise-cost-center-chargeback-guard/index.js new file mode 100644 index 00000000..f69eabb2 --- /dev/null +++ b/enterprise-cost-center-chargeback-guard/index.js @@ -0,0 +1,74 @@ +function evaluateChargeback(record) { + const blockers = []; + const warnings = []; + + if (!record.costCenterActive) blockers.push("inactive cost center"); + if (!record.piApproval) blockers.push("missing PI approval"); + if (!record.grantAllowsCharge) blockers.push("grant does not allow this charge"); + if (!record.categoryAllowed) blockers.push("expense category not allowed"); + if (!record.projectActive) blockers.push("project is not active"); + if (!record.usageEvidence) blockers.push("missing usage evidence"); + if (!record.dataResidencyOk) blockers.push("data residency mismatch"); + + if (record.amount > record.remainingBudget && !record.overageApproval) { + blockers.push("charge exceeds remaining budget without overage approval"); + } else if (record.amount > record.remainingBudget) { + warnings.push("charge exceeds remaining budget but has overage approval"); + } + + if (!record.purchaseOrder) warnings.push("no purchase order attached"); + if (!record.taxCode) warnings.push("missing tax code"); + + let decision = "APPROVE_CHARGEBACK"; + if (blockers.length) { + decision = "BLOCK_CHARGEBACK"; + } else if (warnings.length) { + decision = "ROUTE_FOR_APPROVAL"; + } + + return { + recordId: record.id, + department: record.department, + amount: record.amount, + decision, + blockers, + warnings, + nextStep: nextStepFor(decision), + }; +} + +function nextStepFor(decision) { + if (decision === "APPROVE_CHARGEBACK") return "post the chargeback to the institution ledger"; + if (decision === "ROUTE_FOR_APPROVAL") return "send to finance for a manual approval pass"; + return "do not post until blockers are fixed"; +} + +function evaluateChargebacks(records) { + return records.map(evaluateChargeback); +} + +function renderMarkdownReport(results) { + const lines = ["# Enterprise cost center chargeback report", ""]; + for (const result of results) { + lines.push(`## ${result.recordId}: ${result.department}`); + lines.push(`Amount: ${result.amount}`); + lines.push(`Decision: ${result.decision}`); + lines.push(`Next step: ${result.nextStep}`); + if (result.blockers.length) { + lines.push("Blockers:"); + for (const blocker of result.blockers) lines.push(`- ${blocker}`); + } + if (result.warnings.length) { + lines.push("Warnings:"); + for (const warning of result.warnings) lines.push(`- ${warning}`); + } + lines.push(""); + } + return lines.join("\n"); +} + +module.exports = { + evaluateChargeback, + evaluateChargebacks, + renderMarkdownReport, +}; diff --git a/enterprise-cost-center-chargeback-guard/sample-data.json b/enterprise-cost-center-chargeback-guard/sample-data.json new file mode 100644 index 00000000..3607038a --- /dev/null +++ b/enterprise-cost-center-chargeback-guard/sample-data.json @@ -0,0 +1,50 @@ +[ + { + "id": "CB-1001", + "department": "Neuroscience lab", + "amount": 4200, + "remainingBudget": 12000, + "costCenterActive": true, + "piApproval": true, + "grantAllowsCharge": true, + "categoryAllowed": true, + "projectActive": true, + "usageEvidence": true, + "dataResidencyOk": true, + "overageApproval": false, + "purchaseOrder": "PO-4451", + "taxCode": "RND-SVC" + }, + { + "id": "CB-1002", + "department": "Materials group", + "amount": 9600, + "remainingBudget": 8000, + "costCenterActive": true, + "piApproval": true, + "grantAllowsCharge": true, + "categoryAllowed": true, + "projectActive": true, + "usageEvidence": true, + "dataResidencyOk": true, + "overageApproval": true, + "purchaseOrder": "", + "taxCode": "RND-COMPUTE" + }, + { + "id": "CB-1003", + "department": "Clinical data unit", + "amount": 3100, + "remainingBudget": 7000, + "costCenterActive": false, + "piApproval": false, + "grantAllowsCharge": true, + "categoryAllowed": false, + "projectActive": true, + "usageEvidence": false, + "dataResidencyOk": false, + "overageApproval": false, + "purchaseOrder": "", + "taxCode": "" + } +] diff --git a/enterprise-cost-center-chargeback-guard/test.js b/enterprise-cost-center-chargeback-guard/test.js new file mode 100644 index 00000000..0a616416 --- /dev/null +++ b/enterprise-cost-center-chargeback-guard/test.js @@ -0,0 +1,19 @@ +const assert = require("assert"); +const records = require("./sample-data.json"); +const { evaluateChargebacks } = require("./index"); + +const results = evaluateChargebacks(records); +const byId = Object.fromEntries(results.map((result) => [result.recordId, result])); + +assert.strictEqual(byId["CB-1001"].decision, "APPROVE_CHARGEBACK"); +assert.strictEqual(byId["CB-1001"].blockers.length, 0); + +assert.strictEqual(byId["CB-1002"].decision, "ROUTE_FOR_APPROVAL"); +assert.ok(byId["CB-1002"].warnings.some((warning) => warning.includes("overage approval"))); +assert.ok(byId["CB-1002"].warnings.some((warning) => warning.includes("purchase order"))); + +assert.strictEqual(byId["CB-1003"].decision, "BLOCK_CHARGEBACK"); +assert.ok(byId["CB-1003"].blockers.some((blocker) => blocker.includes("inactive cost center"))); +assert.ok(byId["CB-1003"].blockers.some((blocker) => blocker.includes("data residency"))); + +console.log("enterprise cost center chargeback guard tests passed");