import { Controller, Get } from "@nestjs/common"; import { Inject } from "@nestjs/common"; import { eq, desc, sql } from "drizzle-orm"; import { schema, type Database } from "@courtmitra/db"; import { DB } from "../db/db.module.js"; @Controller() export class LedgerController { constructor(@Inject(DB) private readonly db: Database) {} /** * GET /api/ledger * Returns anonymized, aggregated resolution outcomes. * Only includes outcomes where the user consented to publish. */ @Get("ledger") async getLedger() { // 1. Query from ledger_stats_view materialized view const statsResult = await this.db.execute( sql`SELECT domain, total_resolved, avg_recovery, avg_satisfaction, total_refunds FROM ledger_stats_view` ); const viewRows = (statsResult.rows as any[]) || []; // Calculate overall aggregates const totalResolved = viewRows.reduce((acc, r) => acc + Number(r.total_resolved || 0), 0); let recoverySum = 0; let recoveryCount = 0; let satisfactionSum = 0; let satisfactionCount = 0; for (const r of viewRows) { const resolved = Number(r.total_resolved || 0); if (r.avg_recovery !== null && r.avg_recovery !== undefined) { recoverySum += Number(r.avg_recovery) * resolved; recoveryCount += resolved; } if (r.avg_satisfaction !== null && r.avg_satisfaction !== undefined) { satisfactionSum += Number(r.avg_satisfaction) * resolved; satisfactionCount += resolved; } } const avgRecovery = recoveryCount > 0 ? recoverySum / recoveryCount : null; const avgSatisfaction = satisfactionCount > 0 ? satisfactionSum / satisfactionCount : null; const byDomain = viewRows.map((r) => ({ domain: String(r.domain), count: Number(r.total_resolved || 0), })); // 2. Query from repeat_offender_view materialized view const offendersResult = await this.db.execute( sql`SELECT company_name, total_complaints, total_closed, total_active FROM repeat_offender_view ORDER BY total_complaints DESC` ); const repeatOffenders = (offendersResult.rows as any[] || []).map((r) => ({ companyName: String(r.company_name), totalComplaints: Number(r.total_complaints || 0), totalClosed: Number(r.total_closed || 0), totalActive: Number(r.total_active || 0), })); // 3. Compute route success rates (needs total missions count per domain) const totalMissionsByDomain = await this.db .select({ domain: schema.missions.issueDomain, count: sql`count(*)::int`, }) .from(schema.missions) .groupBy(schema.missions.issueDomain); const missionCounts: Record = {}; for (const row of totalMissionsByDomain) { missionCounts[row.domain] = row.count; } const routeSuccessRates = Object.keys(missionCounts).map((domain) => { const viewRow = viewRows.find((v) => String(v.domain) === domain); const domainResolved = viewRow ? Number(viewRow.total_resolved || 0) : 0; const totalMissions = missionCounts[domain] || 0; const successRate = totalMissions > 0 ? domainResolved / totalMissions : 0; return { domain, totalMissions, totalResolved: domainResolved, successRate, }; }); // 4. Query recent outcomes (this requires individual records, so query from table directly) const recentRows = await this.db .select({ outcomeType: schema.resolutionOutcomes.outcomeType, anonymizedSummary: schema.resolutionOutcomes.anonymizedPublicSummary, domain: schema.missions.issueDomain, createdAt: schema.resolutionOutcomes.createdAt, }) .from(schema.resolutionOutcomes) .innerJoin(schema.missions, eq(schema.resolutionOutcomes.missionId, schema.missions.id)) .where(eq(schema.resolutionOutcomes.publishConsent, true)) .orderBy(desc(schema.resolutionOutcomes.createdAt)) .limit(20); return { totalResolved, byDomain, avgRecovery, avgSatisfaction, repeatOffenders, routeSuccessRates, recentOutcomes: recentRows.map((r) => ({ outcomeType: r.outcomeType, anonymizedSummary: r.anonymizedSummary, domain: r.domain, createdAt: r.createdAt.toISOString(), })), }; } }