refactor: 收口平台钱包余额 delta

This commit is contained in:
2026-06-04 03:57:51 +08:00
parent 4b4af11dbc
commit bced46ad92
7 changed files with 227 additions and 54 deletions

View File

@@ -539,6 +539,11 @@ import {
buildWoodenFishSessionFromWorkDetail,
} from './platformMiniGameSessionMappingModel';
import { resolvePlatformPlayedWorkOpenIntent } from './platformPlayedWorkOpenModel';
import {
adjustProfileDashboardWalletBalance,
reconcileProfileWalletLocalDeltaWithServerDashboard,
resolveProfileWalletBalance,
} from './platformProfileWalletDeltaModel';
import {
type PlatformPublicCodeSearchStep,
resolvePlatformPublicCodeSearchPlan,
@@ -1049,60 +1054,6 @@ function openPuzzleRuntimeStage(
writePuzzleRuntimeUrlState(state);
}
function resolveProfileWalletBalance(
dashboard: { walletBalance?: number | null } | null | undefined,
) {
const walletBalance = dashboard?.walletBalance;
return typeof walletBalance === 'number' && Number.isFinite(walletBalance)
? Math.max(0, Math.floor(walletBalance))
: 0;
}
function adjustProfileDashboardWalletBalance(
dashboard: ProfileDashboardSummary | null,
delta: number,
): ProfileDashboardSummary | null {
if (!dashboard || !Number.isFinite(delta) || delta === 0) {
return dashboard;
}
return {
...dashboard,
walletBalance: Math.max(
0,
resolveProfileWalletBalance(dashboard) + Math.trunc(delta),
),
updatedAt: new Date().toISOString(),
};
}
function reconcileProfileWalletLocalDeltaWithServerDashboard(
previousDashboard: ProfileDashboardSummary | null,
latestDashboard: ProfileDashboardSummary | null,
localDelta: number,
) {
if (
!previousDashboard ||
!latestDashboard ||
!Number.isFinite(localDelta) ||
localDelta === 0
) {
return Number.isFinite(localDelta) ? Math.trunc(localDelta) : 0;
}
const previousBalance = resolveProfileWalletBalance(previousDashboard);
const latestBalance = resolveProfileWalletBalance(latestDashboard);
const normalizedDelta = Math.trunc(localDelta);
if (normalizedDelta < 0) {
const reflectedDebit = Math.max(0, previousBalance - latestBalance);
return Math.min(0, normalizedDelta + reflectedDebit);
}
const reflectedCredit = Math.max(0, latestBalance - previousBalance);
return Math.max(0, normalizedDelta - reflectedCredit);
}
function isPuzzleFormOnlyDraft(session: PuzzleAgentSessionSnapshot | null) {
return Boolean(
session?.stage === 'collecting_anchors' && session.draft?.formDraft,

View File

@@ -0,0 +1,117 @@
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import type { ProfileDashboardSummary } from '../../../packages/shared/src/contracts/runtime';
import {
adjustProfileDashboardWalletBalance,
reconcileProfileWalletLocalDeltaWithServerDashboard,
resolveProfileWalletBalance,
} from './platformProfileWalletDeltaModel';
const NOW = Date.parse('2026-06-04T04:30:00.000Z');
function buildDashboard(
overrides: Partial<ProfileDashboardSummary> = {},
): ProfileDashboardSummary {
return {
walletBalance: 100,
totalPlayTimeMs: 0,
playedWorldCount: 0,
updatedAt: '2026-06-01T00:00:00.000Z',
...overrides,
};
}
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(NOW);
});
afterEach(() => {
vi.useRealTimers();
});
describe('platformProfileWalletDeltaModel', () => {
test('normalizes wallet balance to a non-negative integer', () => {
expect(resolveProfileWalletBalance(buildDashboard({ walletBalance: 12.8 }))).toBe(
12,
);
expect(
resolveProfileWalletBalance(buildDashboard({ walletBalance: -4 })),
).toBe(0);
expect(resolveProfileWalletBalance({ walletBalance: Number.NaN })).toBe(0);
expect(resolveProfileWalletBalance(null)).toBe(0);
});
test('applies local delta and refreshes dashboard timestamp', () => {
expect(
adjustProfileDashboardWalletBalance(buildDashboard(), -3.8),
).toMatchObject({
walletBalance: 97,
updatedAt: '2026-06-04T04:30:00.000Z',
});
expect(
adjustProfileDashboardWalletBalance(buildDashboard({ walletBalance: 2 }), -10),
).toMatchObject({
walletBalance: 0,
});
expect(adjustProfileDashboardWalletBalance(null, 5)).toBeNull();
const dashboard = buildDashboard();
expect(adjustProfileDashboardWalletBalance(dashboard, Number.POSITIVE_INFINITY)).toBe(
dashboard,
);
});
test('reconciles debit delta already reflected by latest server dashboard', () => {
const previous = buildDashboard({ walletBalance: 100 });
expect(
reconcileProfileWalletLocalDeltaWithServerDashboard(
previous,
buildDashboard({ walletBalance: 98 }),
-5,
),
).toBe(-3);
expect(
reconcileProfileWalletLocalDeltaWithServerDashboard(
previous,
buildDashboard({ walletBalance: 92 }),
-5,
),
).toBe(0);
});
test('reconciles credit delta already reflected by latest server dashboard', () => {
const previous = buildDashboard({ walletBalance: 100 });
expect(
reconcileProfileWalletLocalDeltaWithServerDashboard(
previous,
buildDashboard({ walletBalance: 103 }),
8,
),
).toBe(5);
expect(
reconcileProfileWalletLocalDeltaWithServerDashboard(
previous,
buildDashboard({ walletBalance: 120 }),
8,
),
).toBe(0);
});
test('does not reconcile when server balance moves against local delta', () => {
const previous = buildDashboard({ walletBalance: 100 });
expect(
reconcileProfileWalletLocalDeltaWithServerDashboard(
previous,
buildDashboard({ walletBalance: 104 }),
-5,
),
).toBe(-5);
expect(
reconcileProfileWalletLocalDeltaWithServerDashboard(
previous,
buildDashboard({ walletBalance: 96 }),
8,
),
).toBe(8);
});
});

View File

@@ -0,0 +1,61 @@
import type { ProfileDashboardSummary } from '../../../packages/shared/src/contracts/runtime';
type ProfileWalletBalanceSource =
| Pick<ProfileDashboardSummary, 'walletBalance'>
| { walletBalance?: number | null }
| null
| undefined;
export function resolveProfileWalletBalance(
dashboard: ProfileWalletBalanceSource,
) {
const walletBalance = dashboard?.walletBalance;
return typeof walletBalance === 'number' && Number.isFinite(walletBalance)
? Math.max(0, Math.floor(walletBalance))
: 0;
}
export function adjustProfileDashboardWalletBalance(
dashboard: ProfileDashboardSummary | null,
delta: number,
): ProfileDashboardSummary | null {
if (!dashboard || !Number.isFinite(delta) || delta === 0) {
return dashboard;
}
return {
...dashboard,
walletBalance: Math.max(
0,
resolveProfileWalletBalance(dashboard) + Math.trunc(delta),
),
updatedAt: new Date().toISOString(),
};
}
export function reconcileProfileWalletLocalDeltaWithServerDashboard(
previousDashboard: ProfileDashboardSummary | null,
latestDashboard: ProfileDashboardSummary | null,
localDelta: number,
) {
if (
!previousDashboard ||
!latestDashboard ||
!Number.isFinite(localDelta) ||
localDelta === 0
) {
return Number.isFinite(localDelta) ? Math.trunc(localDelta) : 0;
}
const previousBalance = resolveProfileWalletBalance(previousDashboard);
const latestBalance = resolveProfileWalletBalance(latestDashboard);
const normalizedDelta = Math.trunc(localDelta);
if (normalizedDelta < 0) {
const reflectedDebit = Math.max(0, previousBalance - latestBalance);
return Math.min(0, normalizedDelta + reflectedDebit);
}
const reflectedCredit = Math.max(0, latestBalance - previousBalance);
return Math.max(0, normalizedDelta - reflectedCredit);
}