diff --git a/event-sponsorship-revenue-readiness-guard/README.md b/event-sponsorship-revenue-readiness-guard/README.md new file mode 100644 index 00000000..9b83e7dc --- /dev/null +++ b/event-sponsorship-revenue-readiness-guard/README.md @@ -0,0 +1,50 @@ +# Event Sponsorship Revenue Readiness Guard + +This module is a self-contained revenue-infrastructure slice for event sponsorship packages. It determines whether a sponsorship packet is ready to invoice, needs finance review, or must be held before release. + +It is intentionally aligned with the event-oriented surface of this repository (`get-event-sponsorship`, event CRM, and event operations) rather than adding another generic billing ledger. + +## What It Checks + +- Sponsorship contract signature and sponsor approval +- Purchase order or finance approval readiness +- Required sponsorship deliverables and sponsor signoff +- Attendee lead guarantees and make-good risk +- Sponsor category exclusivity conflicts +- Cancellation/refund exposure windows +- Proof artifacts required for audit-ready invoicing + +## Decisions + +- `RELEASE_INVOICE`: evidence is complete enough to invoice or release revenue. +- `REVIEW_BEFORE_RELEASE`: finance or sponsorship owner should review before release. +- `HOLD_INVOICE`: material blocker exists; invoice/revenue release should not proceed. + +## Run + +```bash +npm run check +npm test +npm run demo +``` + +The demo writes: + +- `reports/event-sponsorship-readiness-report.json` +- `reports/event-sponsorship-readiness-report.md` +- `reports/event-sponsorship-readiness-summary.svg` +- `reports/demo.mp4` + +## Requirement Mapping + +| Issue #20 requirement | Module coverage | +| --- | --- | +| Subscription and institutional revenue controls | Sponsor package readiness and finance approval controls | +| Usage/value-aligned monetization | Lead guarantee and deliverable evidence checks before invoice release | +| Licensing/API analytics revenue discipline | Proof-artifact and approval gates for sponsor-facing revenue claims | +| Predictable recurring revenue | Prevents premature invoicing, refund exposure, and exclusivity conflicts | +| Secure payment integrations | Does not call payment processors; emits hold/release decisions before finance action | + +## Safety Boundary + +This module uses synthetic data only. It does not call Stripe, PayPal, banks, ERPs, CRMs, sponsor portals, payment processors, external APIs, or accounting systems. It reads no credentials and contains no real sponsor/customer data. diff --git a/event-sponsorship-revenue-readiness-guard/demo.js b/event-sponsorship-revenue-readiness-guard/demo.js new file mode 100644 index 00000000..be924bce --- /dev/null +++ b/event-sponsorship-revenue-readiness-guard/demo.js @@ -0,0 +1,71 @@ +const fs = require("fs"); +const path = require("path"); +const { evaluateSponsorshipRevenueReadiness, summarizeEvaluations } = require("./index"); +const { sponsorshipPackets } = require("./sample-data"); + +const reportsDir = path.join(__dirname, "reports"); +fs.mkdirSync(reportsDir, { recursive: true }); + +const evaluations = sponsorshipPackets.map(evaluateSponsorshipRevenueReadiness); +const summary = summarizeEvaluations(evaluations); + +fs.writeFileSync( + path.join(reportsDir, "event-sponsorship-readiness-report.json"), + JSON.stringify({ summary, evaluations }, null, 2) +); + +const markdown = [ + "# Event Sponsorship Revenue Readiness Guard - Demo Report", + "", + "Synthetic reviewer demo for SCIBASE Revenue Infrastructure issue #20.", + "", + "## Summary", + "", + `- Packets evaluated: ${summary.packet_count}`, + `- Ready to invoice: ${summary.packets_ready_to_invoice}`, + `- Needs finance review: ${summary.packets_requiring_review}`, + `- Held before invoice: ${summary.packets_on_hold}`, + "", + "## Decisions", + "", + "| Packet | Sponsor | Tier | Decision | Score | Top reasons |", + "| --- | --- | --- | --- | ---: | --- |", + ...evaluations.map((item) => { + const topReasons = item.reasons.slice(0, 3).map((reason) => reason.code).join(", ") || "none"; + return `| ${item.packet_id} | ${item.sponsor_id} | ${item.package_tier} | ${item.decision} | ${item.readiness_score} | ${topReasons} |`; + }), + "", + "## Boundary", + "", + "- Synthetic data only.", + "- No payment processors called.", + "- No real sponsor/customer data used.", + "- No external APIs used.", + "- No credentials, bank data, Stripe, PayPal, ERP, or accounting systems touched.", + "" +].join("\n"); + +fs.writeFileSync(path.join(reportsDir, "event-sponsorship-readiness-report.md"), markdown); + +const bar = (label, value, color, y) => ` + ${label} + + + ${value}`; + +const svg = ` + + Event Sponsorship Revenue Readiness + Synthetic guard demo for invoice release, finance review, and hold decisions. + ${bar("RELEASE_INVOICE", summary.decision_counts.RELEASE_INVOICE, "#16a34a", 160)} + ${bar("REVIEW_BEFORE_RELEASE", summary.decision_counts.REVIEW_BEFORE_RELEASE, "#f59e0b", 220)} + ${bar("HOLD_INVOICE", summary.decision_counts.HOLD_INVOICE, "#dc2626", 280)} + + Boundary + Synthetic data only · no payment processors · no external APIs · no credentials + Designed to make event sponsorship revenue release auditable before invoice actions. +`; + +fs.writeFileSync(path.join(reportsDir, "event-sponsorship-readiness-summary.svg"), svg.replace(/[ \t]+$/gm, "")); + +console.log(JSON.stringify({ summary, report_dir: reportsDir }, null, 2)); diff --git a/event-sponsorship-revenue-readiness-guard/index.js b/event-sponsorship-revenue-readiness-guard/index.js new file mode 100644 index 00000000..a729d264 --- /dev/null +++ b/event-sponsorship-revenue-readiness-guard/index.js @@ -0,0 +1,265 @@ +const crypto = require("crypto"); + +const DECISIONS = Object.freeze({ + RELEASE: "RELEASE_INVOICE", + REVIEW: "REVIEW_BEFORE_RELEASE", + HOLD: "HOLD_INVOICE" +}); + +const SEVERITY_WEIGHT = Object.freeze({ + info: 0, + review: 1, + hold: 3 +}); + +function stableStringify(value) { + if (Array.isArray(value)) { + return `[${value.map(stableStringify).join(",")}]`; + } + if (value && typeof value === "object") { + return `{${Object.keys(value) + .sort() + .map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`) + .join(",")}}`; + } + return JSON.stringify(value); +} + +function stableHash(value) { + return crypto + .createHash("sha256") + .update(stableStringify(value)) + .digest("hex"); +} + +function reason(code, severity, message, remediation) { + return { code, severity, message, remediation }; +} + +function missingRequiredDeliverables(packet) { + return (packet.deliverables || []).filter((item) => item.required && !item.completed); +} + +function unsignedRequiredDeliverables(packet) { + return (packet.deliverables || []).filter( + (item) => item.required && item.completed && !item.sponsorApproved + ); +} + +function missingEvidence(packet) { + return (packet.evidence || []).filter((item) => !item.present); +} + +function evaluateSponsorshipRevenueReadiness(packet) { + const reasons = []; + const financeActions = []; + const contract = packet.contract || {}; + const leadGuarantee = packet.leadGuarantee || {}; + const exclusivity = packet.exclusivity || {}; + const missingDeliverables = missingRequiredDeliverables(packet); + const unsignedDeliverables = unsignedRequiredDeliverables(packet); + const evidenceGaps = missingEvidence(packet); + const delivered = Number(leadGuarantee.qualifiedDelivered || 0); + const promised = Number(leadGuarantee.promised || 0); + const minimumAcceptable = Number(leadGuarantee.minimumAcceptable || 0); + const leadShortfall = Math.max(0, minimumAcceptable - delivered); + + if (!contract.signed) { + reasons.push( + reason( + "SPONSOR_CONTRACT_UNSIGNED", + "hold", + "Sponsorship contract is not signed.", + "Collect signed sponsorship agreement before invoicing or releasing revenue." + ) + ); + financeActions.push("hold invoice until contract is signed"); + } + + if (!contract.purchaseOrderApproved) { + reasons.push( + reason( + "PURCHASE_ORDER_NOT_APPROVED", + contract.signed ? "review" : "hold", + "Purchase order or finance approval is missing.", + "Attach approved PO or finance approval before invoice release." + ) + ); + financeActions.push("request PO or finance approval evidence"); + } + + if (!contract.sponsorApproval) { + reasons.push( + reason( + "SPONSOR_APPROVAL_MISSING", + "hold", + "Sponsor has not approved the final package state.", + "Obtain sponsor signoff for package scope before recognizing revenue readiness." + ) + ); + financeActions.push("collect sponsor package approval"); + } + + if (!contract.cancellationWindowClosed || Number(contract.refundExposureUsd || 0) > 0) { + reasons.push( + reason( + "REFUND_WINDOW_OR_EXPOSURE_OPEN", + "hold", + "Cancellation or refund exposure is still open.", + "Defer invoice release or record finance review until refund exposure is cleared." + ) + ); + financeActions.push("defer release until cancellation/refund exposure closes"); + } + + if ((exclusivity.conflicts || []).length > 0) { + reasons.push( + reason( + "SPONSOR_EXCLUSIVITY_CONFLICT", + "hold", + "Sponsor category exclusivity conflicts with another sponsor.", + "Resolve category conflict or amend sponsorship package before release." + ) + ); + financeActions.push("route exclusivity conflict to sponsorship owner"); + } + + for (const deliverable of missingDeliverables) { + reasons.push( + reason( + "REQUIRED_DELIVERABLE_INCOMPLETE", + "hold", + `Required deliverable is incomplete: ${deliverable.label}.`, + "Complete the deliverable or reduce the invoiceable package scope." + ) + ); + } + + for (const deliverable of unsignedDeliverables) { + reasons.push( + reason( + "DELIVERABLE_SIGNOFF_MISSING", + "review", + `Required deliverable lacks sponsor signoff: ${deliverable.label}.`, + "Collect sponsor signoff or mark the line item for manual finance review." + ) + ); + } + + if (leadShortfall > 0) { + const severity = delivered < promised * 0.75 ? "hold" : "review"; + reasons.push( + reason( + "ATTENDEE_LEAD_GUARANTEE_SHORTFALL", + severity, + `Qualified leads delivered (${delivered}) are below the acceptable floor (${minimumAcceptable}).`, + "Deliver remaining qualified leads, apply make-good credit, or reduce invoice amount." + ) + ); + financeActions.push("calculate make-good credit or revised invoice amount"); + } + + for (const gap of evidenceGaps) { + reasons.push( + reason( + "PROOF_ARTIFACT_MISSING", + "review", + `Proof artifact is missing: ${gap.type}.`, + "Attach proof artifact before revenue packet is marked audit-ready." + ) + ); + } + + const highestWeight = reasons.reduce( + (weight, item) => Math.max(weight, SEVERITY_WEIGHT[item.severity] || 0), + 0 + ); + const decision = + highestWeight >= SEVERITY_WEIGHT.hold + ? DECISIONS.HOLD + : highestWeight >= SEVERITY_WEIGHT.review + ? DECISIONS.REVIEW + : DECISIONS.RELEASE; + + const completedRequired = (packet.deliverables || []).filter( + (item) => item.required && item.completed && item.sponsorApproved + ).length; + const totalRequired = (packet.deliverables || []).filter((item) => item.required).length; + const deliverableReadiness = totalRequired === 0 ? 100 : Math.round((completedRequired / totalRequired) * 100); + const leadReadiness = + minimumAcceptable === 0 ? 100 : Math.min(100, Math.round((delivered / minimumAcceptable) * 100)); + const evidenceReadiness = + (packet.evidence || []).length === 0 + ? 0 + : Math.round((((packet.evidence || []).length - evidenceGaps.length) / (packet.evidence || []).length) * 100); + + const readinessScore = Math.max( + 0, + Math.min( + 100, + Math.round( + deliverableReadiness * 0.35 + + leadReadiness * 0.25 + + evidenceReadiness * 0.2 + + (contract.signed ? 10 : 0) + + (contract.purchaseOrderApproved ? 10 : 0) + ) + ) + ); + + return { + schema_version: "event_sponsorship_revenue_readiness_guard_v1", + packet_id: packet.id, + event_id: packet.eventId, + sponsor_id: packet.sponsorId, + package_tier: packet.packageTier, + invoice_amount_usd: packet.invoiceAmountUsd, + decision, + readiness_score: readinessScore, + reason_count: reasons.length, + reasons, + finance_actions: [...new Set(financeActions)], + metrics: { + deliverable_readiness_percent: deliverableReadiness, + lead_readiness_percent: leadReadiness, + evidence_readiness_percent: evidenceReadiness, + lead_shortfall: leadShortfall, + exclusivity_conflict_count: (exclusivity.conflicts || []).length, + refund_exposure_usd: Number(contract.refundExposureUsd || 0) + }, + audit_packet: { + synthetic_data_only: true, + external_apis_used: false, + payment_processors_called: false, + private_customer_data_used: false, + packet_sha256: stableHash(packet) + } + }; +} + +function summarizeEvaluations(evaluations) { + const counts = evaluations.reduce( + (acc, item) => { + acc[item.decision] = (acc[item.decision] || 0) + 1; + return acc; + }, + { [DECISIONS.RELEASE]: 0, [DECISIONS.REVIEW]: 0, [DECISIONS.HOLD]: 0 } + ); + + return { + schema_version: "event_sponsorship_revenue_readiness_summary_v1", + packet_count: evaluations.length, + decision_counts: counts, + packets_ready_to_invoice: counts[DECISIONS.RELEASE], + packets_requiring_review: counts[DECISIONS.REVIEW], + packets_on_hold: counts[DECISIONS.HOLD], + audit_note: + "Synthetic event sponsorship packets only; no payment processors, bank data, credentials, or external APIs used." + }; +} + +module.exports = { + DECISIONS, + evaluateSponsorshipRevenueReadiness, + summarizeEvaluations +}; diff --git a/event-sponsorship-revenue-readiness-guard/package.json b/event-sponsorship-revenue-readiness-guard/package.json new file mode 100644 index 00000000..68ec38b4 --- /dev/null +++ b/event-sponsorship-revenue-readiness-guard/package.json @@ -0,0 +1,13 @@ +{ + "name": "event-sponsorship-revenue-readiness-guard", + "version": "1.0.0", + "description": "Deterministic revenue readiness guard for event sponsorship invoices and release decisions.", + "private": true, + "type": "commonjs", + "scripts": { + "check": "node --check index.js && node --check sample-data.js && node --check test.js && node --check demo.js", + "test": "node test.js", + "demo": "node demo.js" + }, + "license": "MIT" +} diff --git a/event-sponsorship-revenue-readiness-guard/reports/demo.mp4 b/event-sponsorship-revenue-readiness-guard/reports/demo.mp4 new file mode 100644 index 00000000..dc4b5636 Binary files /dev/null and b/event-sponsorship-revenue-readiness-guard/reports/demo.mp4 differ diff --git a/event-sponsorship-revenue-readiness-guard/reports/event-sponsorship-readiness-report.json b/event-sponsorship-revenue-readiness-guard/reports/event-sponsorship-readiness-report.json new file mode 100644 index 00000000..1f4ddba6 --- /dev/null +++ b/event-sponsorship-revenue-readiness-guard/reports/event-sponsorship-readiness-report.json @@ -0,0 +1,263 @@ +{ + "summary": { + "schema_version": "event_sponsorship_revenue_readiness_summary_v1", + "packet_count": 4, + "decision_counts": { + "RELEASE_INVOICE": 1, + "REVIEW_BEFORE_RELEASE": 1, + "HOLD_INVOICE": 2 + }, + "packets_ready_to_invoice": 1, + "packets_requiring_review": 1, + "packets_on_hold": 2, + "audit_note": "Synthetic event sponsorship packets only; no payment processors, bank data, credentials, or external APIs used." + }, + "evaluations": [ + { + "schema_version": "event_sponsorship_revenue_readiness_guard_v1", + "packet_id": "packet-ready-platinum", + "event_id": "deep-events-berlin-2026", + "sponsor_id": "sponsor-orion-labs", + "package_tier": "platinum", + "invoice_amount_usd": 25000, + "decision": "RELEASE_INVOICE", + "readiness_score": 100, + "reason_count": 0, + "reasons": [], + "finance_actions": [], + "metrics": { + "deliverable_readiness_percent": 100, + "lead_readiness_percent": 100, + "evidence_readiness_percent": 100, + "lead_shortfall": 0, + "exclusivity_conflict_count": 0, + "refund_exposure_usd": 0 + }, + "audit_packet": { + "synthetic_data_only": true, + "external_apis_used": false, + "payment_processors_called": false, + "private_customer_data_used": false, + "packet_sha256": "09ddd62a317d1c6e6b7e91579183eb22dd02e7df5f0e9ac9e7c518f529d68e2e" + } + }, + { + "schema_version": "event_sponsorship_revenue_readiness_guard_v1", + "packet_id": "packet-review-logo-signoff", + "event_id": "deep-events-paris-2026", + "sponsor_id": "sponsor-nova-data", + "package_tier": "gold", + "invoice_amount_usd": 14000, + "decision": "REVIEW_BEFORE_RELEASE", + "readiness_score": 82, + "reason_count": 2, + "reasons": [ + { + "code": "DELIVERABLE_SIGNOFF_MISSING", + "severity": "review", + "message": "Required deliverable lacks sponsor signoff: Homepage logo placement.", + "remediation": "Collect sponsor signoff or mark the line item for manual finance review." + }, + { + "code": "PROOF_ARTIFACT_MISSING", + "severity": "review", + "message": "Proof artifact is missing: deliverable_signoff.", + "remediation": "Attach proof artifact before revenue packet is marked audit-ready." + } + ], + "finance_actions": [], + "metrics": { + "deliverable_readiness_percent": 67, + "lead_readiness_percent": 100, + "evidence_readiness_percent": 67, + "lead_shortfall": 0, + "exclusivity_conflict_count": 0, + "refund_exposure_usd": 0 + }, + "audit_packet": { + "synthetic_data_only": true, + "external_apis_used": false, + "payment_processors_called": false, + "private_customer_data_used": false, + "packet_sha256": "c52dadd3606e19ac006fe5ae187549aab0fd0887bef26510c79569a4d1bb183a" + } + }, + { + "schema_version": "event_sponsorship_revenue_readiness_guard_v1", + "packet_id": "packet-hold-exclusivity", + "event_id": "deep-events-lisbon-2026", + "sponsor_id": "sponsor-vector-capital", + "package_tier": "platinum", + "invoice_amount_usd": 30000, + "decision": "HOLD_INVOICE", + "readiness_score": 38, + "reason_count": 8, + "reasons": [ + { + "code": "PURCHASE_ORDER_NOT_APPROVED", + "severity": "review", + "message": "Purchase order or finance approval is missing.", + "remediation": "Attach approved PO or finance approval before invoice release." + }, + { + "code": "REFUND_WINDOW_OR_EXPOSURE_OPEN", + "severity": "hold", + "message": "Cancellation or refund exposure is still open.", + "remediation": "Defer invoice release or record finance review until refund exposure is cleared." + }, + { + "code": "SPONSOR_EXCLUSIVITY_CONFLICT", + "severity": "hold", + "message": "Sponsor category exclusivity conflicts with another sponsor.", + "remediation": "Resolve category conflict or amend sponsorship package before release." + }, + { + "code": "REQUIRED_DELIVERABLE_INCOMPLETE", + "severity": "hold", + "message": "Required deliverable is incomplete: Exclusive category slot.", + "remediation": "Complete the deliverable or reduce the invoiceable package scope." + }, + { + "code": "REQUIRED_DELIVERABLE_INCOMPLETE", + "severity": "hold", + "message": "Required deliverable is incomplete: Qualified attendee lead export.", + "remediation": "Complete the deliverable or reduce the invoiceable package scope." + }, + { + "code": "ATTENDEE_LEAD_GUARANTEE_SHORTFALL", + "severity": "hold", + "message": "Qualified leads delivered (74) are below the acceptable floor (180).", + "remediation": "Deliver remaining qualified leads, apply make-good credit, or reduce invoice amount." + }, + { + "code": "PROOF_ARTIFACT_MISSING", + "severity": "review", + "message": "Proof artifact is missing: deliverable_signoff.", + "remediation": "Attach proof artifact before revenue packet is marked audit-ready." + }, + { + "code": "PROOF_ARTIFACT_MISSING", + "severity": "review", + "message": "Proof artifact is missing: lead_export_hash.", + "remediation": "Attach proof artifact before revenue packet is marked audit-ready." + } + ], + "finance_actions": [ + "request PO or finance approval evidence", + "defer release until cancellation/refund exposure closes", + "route exclusivity conflict to sponsorship owner", + "calculate make-good credit or revised invoice amount" + ], + "metrics": { + "deliverable_readiness_percent": 33, + "lead_readiness_percent": 41, + "evidence_readiness_percent": 33, + "lead_shortfall": 106, + "exclusivity_conflict_count": 1, + "refund_exposure_usd": 18000 + }, + "audit_packet": { + "synthetic_data_only": true, + "external_apis_used": false, + "payment_processors_called": false, + "private_customer_data_used": false, + "packet_sha256": "cd37765dc01d96fb70775152c728ffc617fb0b498021c429796d2b033410fbd6" + } + }, + { + "schema_version": "event_sponsorship_revenue_readiness_guard_v1", + "packet_id": "packet-hold-no-contract", + "event_id": "deep-events-remote-2026", + "sponsor_id": "sponsor-signal-harbor", + "package_tier": "silver", + "invoice_amount_usd": 6500, + "decision": "HOLD_INVOICE", + "readiness_score": 0, + "reason_count": 10, + "reasons": [ + { + "code": "SPONSOR_CONTRACT_UNSIGNED", + "severity": "hold", + "message": "Sponsorship contract is not signed.", + "remediation": "Collect signed sponsorship agreement before invoicing or releasing revenue." + }, + { + "code": "PURCHASE_ORDER_NOT_APPROVED", + "severity": "hold", + "message": "Purchase order or finance approval is missing.", + "remediation": "Attach approved PO or finance approval before invoice release." + }, + { + "code": "SPONSOR_APPROVAL_MISSING", + "severity": "hold", + "message": "Sponsor has not approved the final package state.", + "remediation": "Obtain sponsor signoff for package scope before recognizing revenue readiness." + }, + { + "code": "REFUND_WINDOW_OR_EXPOSURE_OPEN", + "severity": "hold", + "message": "Cancellation or refund exposure is still open.", + "remediation": "Defer invoice release or record finance review until refund exposure is cleared." + }, + { + "code": "REQUIRED_DELIVERABLE_INCOMPLETE", + "severity": "hold", + "message": "Required deliverable is incomplete: Sponsor page logo.", + "remediation": "Complete the deliverable or reduce the invoiceable package scope." + }, + { + "code": "REQUIRED_DELIVERABLE_INCOMPLETE", + "severity": "hold", + "message": "Required deliverable is incomplete: Qualified attendee lead export.", + "remediation": "Complete the deliverable or reduce the invoiceable package scope." + }, + { + "code": "ATTENDEE_LEAD_GUARANTEE_SHORTFALL", + "severity": "hold", + "message": "Qualified leads delivered (0) are below the acceptable floor (35).", + "remediation": "Deliver remaining qualified leads, apply make-good credit, or reduce invoice amount." + }, + { + "code": "PROOF_ARTIFACT_MISSING", + "severity": "review", + "message": "Proof artifact is missing: contract.", + "remediation": "Attach proof artifact before revenue packet is marked audit-ready." + }, + { + "code": "PROOF_ARTIFACT_MISSING", + "severity": "review", + "message": "Proof artifact is missing: deliverable_signoff.", + "remediation": "Attach proof artifact before revenue packet is marked audit-ready." + }, + { + "code": "PROOF_ARTIFACT_MISSING", + "severity": "review", + "message": "Proof artifact is missing: lead_export_hash.", + "remediation": "Attach proof artifact before revenue packet is marked audit-ready." + } + ], + "finance_actions": [ + "hold invoice until contract is signed", + "request PO or finance approval evidence", + "collect sponsor package approval", + "defer release until cancellation/refund exposure closes", + "calculate make-good credit or revised invoice amount" + ], + "metrics": { + "deliverable_readiness_percent": 0, + "lead_readiness_percent": 0, + "evidence_readiness_percent": 0, + "lead_shortfall": 35, + "exclusivity_conflict_count": 0, + "refund_exposure_usd": 6500 + }, + "audit_packet": { + "synthetic_data_only": true, + "external_apis_used": false, + "payment_processors_called": false, + "private_customer_data_used": false, + "packet_sha256": "e31e26d3b2303d27340d85ad7967cb6baa861e54e61f345975741371b987bb02" + } + } + ] +} \ No newline at end of file diff --git a/event-sponsorship-revenue-readiness-guard/reports/event-sponsorship-readiness-report.md b/event-sponsorship-revenue-readiness-guard/reports/event-sponsorship-readiness-report.md new file mode 100644 index 00000000..d436d8eb --- /dev/null +++ b/event-sponsorship-revenue-readiness-guard/reports/event-sponsorship-readiness-report.md @@ -0,0 +1,27 @@ +# Event Sponsorship Revenue Readiness Guard - Demo Report + +Synthetic reviewer demo for SCIBASE Revenue Infrastructure issue #20. + +## Summary + +- Packets evaluated: 4 +- Ready to invoice: 1 +- Needs finance review: 1 +- Held before invoice: 2 + +## Decisions + +| Packet | Sponsor | Tier | Decision | Score | Top reasons | +| --- | --- | --- | --- | ---: | --- | +| packet-ready-platinum | sponsor-orion-labs | platinum | RELEASE_INVOICE | 100 | none | +| packet-review-logo-signoff | sponsor-nova-data | gold | REVIEW_BEFORE_RELEASE | 82 | DELIVERABLE_SIGNOFF_MISSING, PROOF_ARTIFACT_MISSING | +| packet-hold-exclusivity | sponsor-vector-capital | platinum | HOLD_INVOICE | 38 | PURCHASE_ORDER_NOT_APPROVED, REFUND_WINDOW_OR_EXPOSURE_OPEN, SPONSOR_EXCLUSIVITY_CONFLICT | +| packet-hold-no-contract | sponsor-signal-harbor | silver | HOLD_INVOICE | 0 | SPONSOR_CONTRACT_UNSIGNED, PURCHASE_ORDER_NOT_APPROVED, SPONSOR_APPROVAL_MISSING | + +## Boundary + +- Synthetic data only. +- No payment processors called. +- No real sponsor/customer data used. +- No external APIs used. +- No credentials, bank data, Stripe, PayPal, ERP, or accounting systems touched. diff --git a/event-sponsorship-revenue-readiness-guard/reports/event-sponsorship-readiness-summary.svg b/event-sponsorship-revenue-readiness-guard/reports/event-sponsorship-readiness-summary.svg new file mode 100644 index 00000000..59061f0f --- /dev/null +++ b/event-sponsorship-revenue-readiness-guard/reports/event-sponsorship-readiness-summary.svg @@ -0,0 +1,24 @@ + + + Event Sponsorship Revenue Readiness + Synthetic guard demo for invoice release, finance review, and hold decisions. + + RELEASE_INVOICE + + + 1 + + REVIEW_BEFORE_RELEASE + + + 1 + + HOLD_INVOICE + + + 2 + + Boundary + Synthetic data only · no payment processors · no external APIs · no credentials + Designed to make event sponsorship revenue release auditable before invoice actions. + \ No newline at end of file diff --git a/event-sponsorship-revenue-readiness-guard/sample-data.js b/event-sponsorship-revenue-readiness-guard/sample-data.js new file mode 100644 index 00000000..5d3a86c1 --- /dev/null +++ b/event-sponsorship-revenue-readiness-guard/sample-data.js @@ -0,0 +1,135 @@ +const sponsorshipPackets = [ + { + id: "packet-ready-platinum", + eventId: "deep-events-berlin-2026", + sponsorId: "sponsor-orion-labs", + packageTier: "platinum", + invoiceAmountUsd: 25000, + contract: { + signed: true, + sponsorApproval: true, + purchaseOrderApproved: true, + cancellationWindowClosed: true, + refundExposureUsd: 0 + }, + deliverables: [ + { id: "logo-homepage", label: "Homepage logo placement", required: true, completed: true, sponsorApproved: true }, + { id: "stage-mention", label: "Main-stage sponsor mention", required: true, completed: true, sponsorApproved: true }, + { id: "lead-export", label: "Qualified attendee lead export", required: true, completed: true, sponsorApproved: true } + ], + leadGuarantee: { + promised: 150, + qualifiedDelivered: 168, + minimumAcceptable: 140 + }, + exclusivity: { + category: "AI infrastructure", + conflicts: [] + }, + evidence: [ + { type: "contract", present: true, reference: "contract:orion-2026" }, + { type: "deliverable_signoff", present: true, reference: "signoff:orion:platinum" }, + { type: "lead_export_hash", present: true, reference: "sha256:lead-export-orion" } + ] + }, + { + id: "packet-review-logo-signoff", + eventId: "deep-events-paris-2026", + sponsorId: "sponsor-nova-data", + packageTier: "gold", + invoiceAmountUsd: 14000, + contract: { + signed: true, + sponsorApproval: true, + purchaseOrderApproved: true, + cancellationWindowClosed: true, + refundExposureUsd: 0 + }, + deliverables: [ + { id: "logo-homepage", label: "Homepage logo placement", required: true, completed: true, sponsorApproved: false }, + { id: "newsletter-slot", label: "Newsletter sponsor slot", required: true, completed: true, sponsorApproved: true }, + { id: "lead-export", label: "Qualified attendee lead export", required: true, completed: true, sponsorApproved: true } + ], + leadGuarantee: { + promised: 100, + qualifiedDelivered: 96, + minimumAcceptable: 90 + }, + exclusivity: { + category: "Research tooling", + conflicts: [] + }, + evidence: [ + { type: "contract", present: true, reference: "contract:nova-2026" }, + { type: "deliverable_signoff", present: false, reference: "" }, + { type: "lead_export_hash", present: true, reference: "sha256:lead-export-nova" } + ] + }, + { + id: "packet-hold-exclusivity", + eventId: "deep-events-lisbon-2026", + sponsorId: "sponsor-vector-capital", + packageTier: "platinum", + invoiceAmountUsd: 30000, + contract: { + signed: true, + sponsorApproval: true, + purchaseOrderApproved: false, + cancellationWindowClosed: false, + refundExposureUsd: 18000 + }, + deliverables: [ + { id: "logo-homepage", label: "Homepage logo placement", required: true, completed: true, sponsorApproved: true }, + { id: "exclusive-category", label: "Exclusive category slot", required: true, completed: false, sponsorApproved: false }, + { id: "lead-export", label: "Qualified attendee lead export", required: true, completed: false, sponsorApproved: false } + ], + leadGuarantee: { + promised: 200, + qualifiedDelivered: 74, + minimumAcceptable: 180 + }, + exclusivity: { + category: "AI infrastructure", + conflicts: ["sponsor-orion-labs"] + }, + evidence: [ + { type: "contract", present: true, reference: "contract:vector-2026" }, + { type: "deliverable_signoff", present: false, reference: "" }, + { type: "lead_export_hash", present: false, reference: "" } + ] + }, + { + id: "packet-hold-no-contract", + eventId: "deep-events-remote-2026", + sponsorId: "sponsor-signal-harbor", + packageTier: "silver", + invoiceAmountUsd: 6500, + contract: { + signed: false, + sponsorApproval: false, + purchaseOrderApproved: false, + cancellationWindowClosed: false, + refundExposureUsd: 6500 + }, + deliverables: [ + { id: "logo-page", label: "Sponsor page logo", required: true, completed: false, sponsorApproved: false }, + { id: "lead-export", label: "Qualified attendee lead export", required: true, completed: false, sponsorApproved: false } + ], + leadGuarantee: { + promised: 40, + qualifiedDelivered: 0, + minimumAcceptable: 35 + }, + exclusivity: { + category: "Event operations", + conflicts: [] + }, + evidence: [ + { type: "contract", present: false, reference: "" }, + { type: "deliverable_signoff", present: false, reference: "" }, + { type: "lead_export_hash", present: false, reference: "" } + ] + } +]; + +module.exports = { sponsorshipPackets }; diff --git a/event-sponsorship-revenue-readiness-guard/test.js b/event-sponsorship-revenue-readiness-guard/test.js new file mode 100644 index 00000000..27bd6ff4 --- /dev/null +++ b/event-sponsorship-revenue-readiness-guard/test.js @@ -0,0 +1,55 @@ +const assert = require("assert"); +const { DECISIONS, evaluateSponsorshipRevenueReadiness, summarizeEvaluations } = require("./index"); +const { sponsorshipPackets } = require("./sample-data"); + +function byId(id) { + return sponsorshipPackets.find((packet) => packet.id === id); +} + +const ready = evaluateSponsorshipRevenueReadiness(byId("packet-ready-platinum")); +assert.strictEqual(ready.decision, DECISIONS.RELEASE); +assert.strictEqual(ready.reason_count, 0); +assert.strictEqual(ready.metrics.lead_shortfall, 0); +assert.strictEqual(ready.audit_packet.payment_processors_called, false); + +const review = evaluateSponsorshipRevenueReadiness(byId("packet-review-logo-signoff")); +assert.strictEqual(review.decision, DECISIONS.REVIEW); +assert(review.reasons.some((item) => item.code === "DELIVERABLE_SIGNOFF_MISSING")); +assert(review.reasons.some((item) => item.code === "PROOF_ARTIFACT_MISSING")); +assert(review.readiness_score >= 70); + +const hold = evaluateSponsorshipRevenueReadiness(byId("packet-hold-exclusivity")); +assert.strictEqual(hold.decision, DECISIONS.HOLD); +assert(hold.reasons.some((item) => item.code === "SPONSOR_EXCLUSIVITY_CONFLICT")); +assert(hold.reasons.some((item) => item.code === "REFUND_WINDOW_OR_EXPOSURE_OPEN")); +assert(hold.reasons.some((item) => item.code === "ATTENDEE_LEAD_GUARANTEE_SHORTFALL")); +assert(hold.metrics.exclusivity_conflict_count === 1); + +const unsigned = evaluateSponsorshipRevenueReadiness(byId("packet-hold-no-contract")); +assert.strictEqual(unsigned.decision, DECISIONS.HOLD); +assert(unsigned.reasons.some((item) => item.code === "SPONSOR_CONTRACT_UNSIGNED")); +assert(unsigned.reasons.some((item) => item.severity === "hold")); + +const evaluations = sponsorshipPackets.map(evaluateSponsorshipRevenueReadiness); +const summary = summarizeEvaluations(evaluations); +assert.strictEqual(summary.packet_count, 4); +assert.strictEqual(summary.decision_counts[DECISIONS.RELEASE], 1); +assert.strictEqual(summary.decision_counts[DECISIONS.REVIEW], 1); +assert.strictEqual(summary.decision_counts[DECISIONS.HOLD], 2); + +for (const evaluation of evaluations) { + assert.strictEqual(evaluation.audit_packet.synthetic_data_only, true); + assert.strictEqual(evaluation.audit_packet.external_apis_used, false); + assert.match(evaluation.audit_packet.packet_sha256, /^[a-f0-9]{64}$/); + if (evaluation.decision !== DECISIONS.RELEASE) { + assert(evaluation.reasons.length > 0, `${evaluation.packet_id} must explain non-release decisions`); + } +} + +const tampered = JSON.parse(JSON.stringify(byId("packet-ready-platinum"))); +tampered.leadGuarantee.qualifiedDelivered = 149; +const originalHash = ready.audit_packet.packet_sha256; +const tamperedHash = evaluateSponsorshipRevenueReadiness(tampered).audit_packet.packet_sha256; +assert.notStrictEqual(tamperedHash, originalHash, "packet hash should change when nested evidence changes"); + +console.log("event-sponsorship-revenue-readiness-guard tests passed");