From 71435e99944f0601e40c03090eb9e48c8c9b252a Mon Sep 17 00:00:00 2001 From: ryder777777 Date: Tue, 20 Oct 2026 22:06:19 +0000 Subject: [PATCH] feat(services): add project-trade-redeemed-and-feesswept service and unit tests (#17) --- ...ct-trade-redeemed-and-feesswept.service.ts | 40 +++++++++++++++++++ ...oject-trade-redeemed-and-feesswept.test.ts | 10 +++++ 2 files changed, 50 insertions(+) create mode 100644 src/services/project-trade-redeemed-and-feesswept.service.ts create mode 100644 tests/project-trade-redeemed-and-feesswept.test.ts diff --git a/src/services/project-trade-redeemed-and-feesswept.service.ts b/src/services/project-trade-redeemed-and-feesswept.service.ts new file mode 100644 index 0000000..b097a62 --- /dev/null +++ b/src/services/project-trade-redeemed-and-feesswept.service.ts @@ -0,0 +1,40 @@ +export interface ProjectTradeRedeemedAndFeessweptReportInput { + marketKey: string; + primaryUnits: number; + secondaryUnits: number; + maxThreshold?: number; +} + +export interface ProjectTradeRedeemedAndFeessweptReport { + marketKey: string; + primaryUnits: number; + secondaryUnits: number; + utilizationBps: number; + verdict: 'healthy' | 'degraded' | 'halted'; +} + +export function evaluateProjectTradeRedeemedAndFeesswept(input: ProjectTradeRedeemedAndFeessweptReportInput): ProjectTradeRedeemedAndFeessweptReport { + const marketKey = input.marketKey.trim(); + if (!marketKey) { + throw new Error('evaluateProjectTradeRedeemedAndFeesswept: marketKey is required'); + } + const primaryUnits = Math.max(0, Math.trunc(input.primaryUnits)); + const secondaryUnits = Math.max(0, Math.trunc(input.secondaryUnits)); + const limit = Math.max(1, Math.trunc(input.maxThreshold ?? 100)); + + const utilizationBps = Math.min(10000, Math.round(((primaryUnits + secondaryUnits) * 10000) / limit)); + let verdict: 'healthy' | 'degraded' | 'halted' = 'healthy'; + if (utilizationBps >= 8500) { + verdict = 'halted'; + } else if (utilizationBps >= 5000) { + verdict = 'degraded'; + } + + return { + marketKey, + primaryUnits, + secondaryUnits, + utilizationBps, + verdict, + }; +} diff --git a/tests/project-trade-redeemed-and-feesswept.test.ts b/tests/project-trade-redeemed-and-feesswept.test.ts new file mode 100644 index 0000000..7c268cc --- /dev/null +++ b/tests/project-trade-redeemed-and-feesswept.test.ts @@ -0,0 +1,10 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { evaluateProjectTradeRedeemedAndFeesswept } from '../src/services/project-trade-redeemed-and-feesswept.service'; + +test('evaluateProjectTradeRedeemedAndFeesswept: evaluates healthy, degraded, and halted verdicts', () => { + assert.equal(evaluateProjectTradeRedeemedAndFeesswept({ marketKey: 'mkt-1', primaryUnits: 15, secondaryUnits: 10, maxThreshold: 100 }).verdict, 'healthy'); + assert.equal(evaluateProjectTradeRedeemedAndFeesswept({ marketKey: 'mkt-2', primaryUnits: 35, secondaryUnits: 25 }).verdict, 'degraded'); + assert.equal(evaluateProjectTradeRedeemedAndFeesswept({ marketKey: 'mkt-3', primaryUnits: 70, secondaryUnits: 20, maxThreshold: 100 }).verdict, 'halted'); + assert.throws(() => evaluateProjectTradeRedeemedAndFeesswept({ marketKey: ' ', primaryUnits: 1, secondaryUnits: 1 }), /marketKey is required/); +});