Spaces:
Starting
Starting
Vanshik Waghela
feat: complete Phase 2 modules 9-11, fix ledger page SSR rendering, and implement voice note facts reconciliation
10f0b14 | 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"; | |
| () | |
| export class LedgerController { | |
| constructor((DB) private readonly db: Database) {} | |
| /** | |
| * GET /api/ledger | |
| * Returns anonymized, aggregated resolution outcomes. | |
| * Only includes outcomes where the user consented to publish. | |
| */ | |
| ("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<number>`count(*)::int`, | |
| }) | |
| .from(schema.missions) | |
| .groupBy(schema.missions.issueDomain); | |
| const missionCounts: Record<string, number> = {}; | |
| 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(), | |
| })), | |
| }; | |
| } | |
| } | |