learning_ai_invt_trdg/backend/src/services/capitalLedgerRepository.ts

152 lines
5.0 KiB
TypeScript

import { getContainer } from '@bytelyst/cosmos';
import { config } from '../config/index.js';
import logger from '../utils/logger.js';
import { getLegacySupabaseClient } from './legacySupabaseClient.js';
import type { CapitalLedgerRecord } from './CapitalLedger.js';
const CONTAINER_NAME = 'capital_ledgers';
interface CapitalLedgerDocument {
id: string;
productId: string;
profile_id: string;
allocated_capital: number;
reserved_for_orders: number;
reserved_for_positions: number;
realized_pnl: number;
updated_at: string;
}
const toNumeric = (value: unknown): number => {
const numeric = Number(value);
return Number.isFinite(numeric) ? numeric : 0;
};
function isCosmosConfigured(): boolean {
return Boolean(config.COSMOS_ENDPOINT && config.COSMOS_KEY);
}
function getLegacyClient() {
return getLegacySupabaseClient();
}
function toLedgerRecord(doc: Partial<CapitalLedgerDocument> | null | undefined): CapitalLedgerRecord | null {
const profileId = String(doc?.profile_id || '').trim();
if (!profileId) return null;
return {
profile_id: profileId,
allocated_capital: toNumeric(doc?.allocated_capital),
reserved_for_orders: toNumeric(doc?.reserved_for_orders),
reserved_for_positions: toNumeric(doc?.reserved_for_positions),
realized_pnl: toNumeric(doc?.realized_pnl),
updated_at: String(doc?.updated_at || new Date().toISOString())
};
}
function toLedgerDocument(record: CapitalLedgerRecord): CapitalLedgerDocument {
return {
id: record.profile_id,
productId: config.PRODUCT_ID,
...record
};
}
async function readFromCosmos(profileId: string): Promise<CapitalLedgerRecord | null> {
const container = getContainer(CONTAINER_NAME);
try {
const { resource } = await container.item(profileId, config.PRODUCT_ID).read<CapitalLedgerDocument>();
return toLedgerRecord(resource);
} catch (error) {
const code = (error as { code?: number })?.code;
if (code === 404) return null;
throw error;
}
}
async function writeToCosmos(record: CapitalLedgerRecord): Promise<CapitalLedgerRecord | null> {
const container = getContainer(CONTAINER_NAME);
const { resource } = await container.items.upsert(toLedgerDocument(record));
return toLedgerRecord(resource as unknown as CapitalLedgerDocument);
}
async function readFromLegacy(profileId: string): Promise<CapitalLedgerRecord | null> {
const client = getLegacyClient();
if (!client) return null;
const { data, error } = await client
.from('capital_ledgers')
.select('*')
.eq('profile_id', profileId)
.maybeSingle();
if (error) {
logger.error(`[CapitalLedgerRepo] Legacy read failed for ${profileId}: ${error.message}`);
return null;
}
return toLedgerRecord(data as CapitalLedgerDocument);
}
export async function getCapitalLedger(profileId: string): Promise<CapitalLedgerRecord | null> {
if (!profileId) return null;
if (!isCosmosConfigured()) {
return readFromLegacy(profileId);
}
if (isCosmosConfigured()) {
try {
const cosmosRecord = await readFromCosmos(profileId);
if (cosmosRecord) return cosmosRecord;
const legacyRecord = await readFromLegacy(profileId);
if (!legacyRecord) return null;
await writeToCosmos(legacyRecord);
logger.info(`[CapitalLedgerRepo] Seeded capital ledger ${profileId} from legacy store into Cosmos.`);
return legacyRecord;
} catch (error) {
logger.warn(`[CapitalLedgerRepo] Cosmos read/seed failed for ${profileId}: ${error instanceof Error ? error.message : 'unknown error'}`);
return null;
}
}
return null;
}
export async function upsertCapitalLedger(
record: CapitalLedgerRecord
): Promise<CapitalLedgerRecord | null> {
if (!record.profile_id) return null;
if (isCosmosConfigured()) {
try {
const saved = await writeToCosmos(record);
if (saved) return saved;
} catch (error) {
logger.warn(`[CapitalLedgerRepo] Cosmos upsert failed, falling back to legacy store: ${error instanceof Error ? error.message : 'unknown error'}`);
}
}
const client = getLegacyClient();
if (!client) return null;
const { data, error } = await client
.from('capital_ledgers')
.upsert({
profile_id: record.profile_id,
allocated_capital: record.allocated_capital,
reserved_for_orders: record.reserved_for_orders,
reserved_for_positions: record.reserved_for_positions,
realized_pnl: record.realized_pnl,
updated_at: record.updated_at
}, { onConflict: 'profile_id' })
.select('*')
.maybeSingle();
if (error) {
logger.error(`[CapitalLedgerRepo] Legacy upsert failed for ${record.profile_id}: ${error.message}`);
return null;
}
return toLedgerRecord(data as CapitalLedgerDocument);
}