import { asc, count, desc, eq, sql } from "drizzle-orm"; import { db } from "@/lib/db"; import { users } from "@/modules/_core/schema"; import { bangEvents } from "../schema"; export type BangStatsDto = { total: number; recent: RecentBangDto[]; }; export type RecentBangDto = { id: string; occurredOn: string; recordedByName: string | null; }; export type BangAggregatesDto = { thisMonth: number; thisYear: number; averageDaysBetween: number | null; monthlyCounts: { month: string; count: number }[]; }; export async function getBangStatsForScope( householdId: string, limit: number, ): Promise { const [totalRow] = await db .select({ total: count() }) .from(bangEvents) .where(eq(bangEvents.householdId, householdId)); const recent = await db .select({ id: bangEvents.id, occurredOn: bangEvents.occurredOn, recordedByName: users.name, }) .from(bangEvents) .leftJoin(users, eq(bangEvents.recordedBy, users.id)) .where(eq(bangEvents.householdId, householdId)) .orderBy(desc(bangEvents.occurredOn), desc(bangEvents.createdAt)) .limit(limit); return { total: totalRow?.total ?? 0, recent: recent.map((r) => ({ id: r.id, occurredOn: r.occurredOn, recordedByName: r.recordedByName, })), }; } export async function getBangStats(householdId: string, limit: number): Promise { return getBangStatsForScope(householdId, limit); } export async function getBangAggregates(householdId: string): Promise { const now = new Date(); const monthPrefix = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`; const yearPrefix = `${now.getFullYear()}-`; const [thisMonthRow] = await db .select({ count: sql`count(*)::int` }) .from(bangEvents) .where( sql`${bangEvents.householdId} = ${householdId} and ${bangEvents.occurredOn} like ${`${monthPrefix}%`}`, ); const [thisYearRow] = await db .select({ count: sql`count(*)::int` }) .from(bangEvents) .where( sql`${bangEvents.householdId} = ${householdId} and ${bangEvents.occurredOn} like ${`${yearPrefix}%`}`, ); const monthlyRows = await db .select({ month: sql`substring(${bangEvents.occurredOn}, 1, 7)`, count: sql`count(*)::int`, }) .from(bangEvents) .where(eq(bangEvents.householdId, householdId)) .groupBy(sql`substring(${bangEvents.occurredOn}, 1, 7)`) .orderBy(asc(sql`substring(${bangEvents.occurredOn}, 1, 7)`)); const dateRows = await db .select({ occurredOn: bangEvents.occurredOn }) .from(bangEvents) .where(eq(bangEvents.householdId, householdId)) .orderBy(asc(bangEvents.occurredOn)); let averageDaysBetween: number | null = null; if (dateRows.length >= 2) { const dates = dateRows.map((row) => parseBangDate(row.occurredOn).getTime()); let totalGapDays = 0; for (let i = 1; i < dates.length; i++) { totalGapDays += (dates[i]! - dates[i - 1]!) / (1000 * 60 * 60 * 24); } averageDaysBetween = Math.round((totalGapDays / (dates.length - 1)) * 10) / 10; } return { thisMonth: thisMonthRow?.count ?? 0, thisYear: thisYearRow?.count ?? 0, averageDaysBetween, monthlyCounts: monthlyRows.map((row) => ({ month: row.month, count: row.count })), }; } function parseBangDate(isoDate: string): Date { const [year, month, day] = isoDate.split("-").map(Number); return new Date(year!, month! - 1, day!); }