Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions institutional-account-transfer-revenue-guard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Institutional Account Transfer Revenue Guard

This module adds a narrow revenue-control slice for SCIBASE issue
[#20](https://github.com/SCIBASE-AI/SCIBASE.AI/issues/20). It validates lab or
institutional account transfers before subscription ownership, AI compute
credits, and analytics-license entitlements move between organizations.

It is intentionally not another generic billing ledger, usage meter, payment
webhook, failover router, dispute workflow, SLA-credit module, tax/FX guard,
storage overage guard, trial/promotion guard, procurement workflow, invoice
acceptance flow, sanctions gate, consortium-pricing checker, or compute
idempotency meter. The focus is transfer-specific revenue leakage and
entitlement continuity.

## What It Checks

- Outstanding invoices and overdue balances before transfer release.
- Purchase-order transfer authority and budget-owner approval.
- Cross-domain SSO transfers with verified domain claim and admin signoff.
- Consortium discount eligibility and discount cap reset.
- Analytics-license assignment and requested-scope continuity.
- Compute-credit ownership, transferability, and near-expiry review.
- Effective-date proration for source credits and target charges.

## Local Usage

```bash
cd institutional-account-transfer-revenue-guard
npm run check
npm test
npm run demo
```

`npm run demo` writes reviewer artifacts under `reports/`:

- `transfer-revenue-packet.json`
- `transfer-revenue-report.md`
- `summary.svg`
- `demo.mp4`

All data is synthetic. The module does not call payment providers, does not
require API keys, and does not collect private financial or identity data.

## Result Model

Each transfer returns:

- `approve` when no blockers, warnings, or discount adjustments are present.
- `conditional` when finance can proceed after review or discount reset.
- `hold` when invoices, authority, license, SSO, or compute-credit ownership
blocks are present.

Every decision includes a deterministic audit digest so reviewers can compare
packet changes during future edits.
29 changes: 29 additions & 0 deletions institutional-account-transfer-revenue-guard/acceptance-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Acceptance Notes

## Reviewer Checklist

- The module is self-contained under `institutional-account-transfer-revenue-guard/`.
- The implementation is dependency-free and runs with Node.js only.
- Sample data is synthetic and does not include private billing data.
- Tests cover approval, conditional discount reset, overdue invoice hold,
license-transfer hold, SSO-domain hold, and compute-credit ownership hold.
- Demo artifacts can be regenerated locally.

## Commands Run

```bash
npm run check
npm test
npm run demo
ffprobe -v error -show_entries format=duration,size -show_entries stream=codec_name,width,height -of default=noprint_wrappers=1 reports/demo.mp4
git diff --check
```

## Limitations

- This is a deterministic local guard, not a live billing-provider integration.
- It does not perform KYB/KYC, collect payment details, or call Stripe, PayPal,
SSO, DPA, or procurement systems.
- The policy thresholds are sample defaults and are meant to be replaced by
SCIBASE production policy values if this slice is integrated into a larger
application.
83 changes: 83 additions & 0 deletions institutional-account-transfer-revenue-guard/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
const fs = require("node:fs")
const path = require("node:path")
const { spawnSync } = require("node:child_process")
const { evaluateTransferPortfolio } = require("./index")
const { policy, transferRequests } = require("./sample-data")

const reportsDir = path.join(__dirname, "reports")
fs.mkdirSync(reportsDir, { recursive: true })

const packet = evaluateTransferPortfolio({ transferRequests, policy })
const { summary } = packet

fs.writeFileSync(
path.join(reportsDir, "transfer-revenue-packet.json"),
`${JSON.stringify(packet, null, 2)}\n`,
)

const markdown = [
"# Institutional Account Transfer Revenue Guard Report",
"",
`Generated transfers: ${summary.totalTransfers}`,
`Approved: ${summary.approved}`,
`Conditional: ${summary.conditional}`,
`Held: ${summary.held}`,
`Net transfer invoice cents: ${summary.netTransferInvoiceCents}`,
`Held compute credits: ${summary.heldComputeCredits}`,
`Audit digest: \`${packet.audit.digest}\``,
"",
"## Transfer Decisions",
...packet.decisions.flatMap((decision) => [
"",
`### ${decision.id}: ${decision.title}`,
`- Status: ${decision.status}`,
`- Source: ${decision.sourceOrg}`,
`- Target: ${decision.targetOrg}`,
`- Risk score: ${decision.riskScore}`,
`- Blockers: ${decision.blockers.map((finding) => finding.code).join(", ") || "none"}`,
`- Warnings: ${decision.warnings.map((finding) => finding.code).join(", ") || "none"}`,
`- Adjustments: ${decision.adjustments.map((finding) => finding.code).join(", ") || "none"}`,
`- Finance actions: ${decision.financeActions.map((financeAction) => financeAction.code).join(", ") || "none"}`,
]),
"",
]

fs.writeFileSync(path.join(reportsDir, "transfer-revenue-report.md"), markdown.join("\n"))

const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540">
<rect width="960" height="540" fill="#111827"/>
<text x="48" y="78" fill="#f9fafb" font-family="Arial" font-size="34" font-weight="700">Institutional Account Transfer Revenue Guard</text>
<text x="48" y="124" fill="#cbd5e1" font-family="Arial" font-size="18">Synthetic finance review packet for SCIBASE issue #20</text>
<rect x="48" y="170" width="250" height="150" rx="14" fill="#0f766e"/>
<text x="78" y="230" fill="#ecfeff" font-family="Arial" font-size="56" font-weight="700">${summary.approved}</text>
<text x="78" y="270" fill="#ccfbf1" font-family="Arial" font-size="22">approved</text>
<rect x="355" y="170" width="250" height="150" rx="14" fill="#b45309"/>
<text x="385" y="230" fill="#fff7ed" font-family="Arial" font-size="56" font-weight="700">${summary.conditional}</text>
<text x="385" y="270" fill="#ffedd5" font-family="Arial" font-size="22">conditional</text>
<rect x="662" y="170" width="250" height="150" rx="14" fill="#991b1b"/>
<text x="692" y="230" fill="#fef2f2" font-family="Arial" font-size="56" font-weight="700">${summary.held}</text>
<text x="692" y="270" fill="#fee2e2" font-family="Arial" font-size="22">held</text>
<text x="48" y="390" fill="#e5e7eb" font-family="Arial" font-size="20">Checks: invoices, PO authority, SSO domain, consortium discounts, analytics license, compute-credit ownership.</text>
<text x="48" y="430" fill="#9ca3af" font-family="Arial" font-size="16">Digest ${packet.audit.digest.slice(0, 24)}...</text>
</svg>
`
fs.writeFileSync(path.join(reportsDir, "summary.svg"), svg)

const ffmpeg = spawnSync("ffmpeg", [
"-y",
"-f",
"lavfi",
"-i",
"color=c=0x111827:s=960x540:d=6:r=15",
"-vf",
"drawbox=x=48:y=170:w=250:h=150:color=0x0f766e@1:t=fill,drawbox=x=355:y=170:w=250:h=150:color=0xb45309@1:t=fill,drawbox=x=662:y=170:w=250:h=150:color=0x991b1b@1:t=fill,drawbox=x=48:y=370:w=864:h=18:color=0x38bdf8@1:t=fill",
"-pix_fmt",
"yuv420p",
path.join(reportsDir, "demo.mp4"),
], { stdio: "ignore" })

if (ffmpeg.status !== 0) {
console.warn("ffmpeg video generation failed; summary.svg and JSON/Markdown reports were still generated.")
}

console.log(`Wrote transfer revenue artifacts to ${reportsDir}`)
Loading