courtmitra / apps /api /src /ledger /ledger.controller.ts
Vanshik Waghela
feat: complete Phase 2 modules 9-11, fix ledger page SSR rendering, and implement voice note facts reconciliation
10f0b14
Raw
History Blame
4.36 kB
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<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(),
})),
};
}
}