mirror of
https://github.com/prdlk/leetcode.git
synced 2026-11-03 05:27:36 +00:00
107 lines
3.6 KiB
TypeScript
107 lines
3.6 KiB
TypeScript
/**
|
|||
|
|
* GET /api/stats — the one JSON document behind the docs progress page.
|
||
|
|
* Read-only aggregation over D1; shape is the page's contract, change both
|
||
|
|
* together. A problem counts as "done" once it entered the ladder
|
||
|
|
* (stage != 'new').
|
||
|
|
*/
|
||
|
|
import {
|
||
|
|
CAMPAIGN_DAYS,
|
||
|
|
CAMPAIGN_START,
|
||
|
|
addDays,
|
||
|
|
campaignDay,
|
||
|
|
campaignWeek,
|
||
|
|
streak,
|
||
|
|
} from "./srs.ts";
|
||
|
|
|
||
|
|
const PHASE_NAMES: Record<number, string> = {
|
||
|
|
1: "I — Linear",
|
||
|
|
2: "II — Nodal & Grid",
|
||
|
|
3: "III — Hierarchical",
|
||
|
|
4: "IV — Relational",
|
||
|
|
5: "V — Decision Space",
|
||
|
|
};
|
||
|
|
|
||
|
|
export async function buildStats(db: D1Database, today: string): Promise<object> {
|
||
|
|
const { results: phaseRows } = await db
|
||
|
|
.prepare(
|
||
|
|
`SELECT t.milestone AS milestone, p.set_label AS set_label,
|
||
|
|
COUNT(*) AS total,
|
||
|
|
SUM(CASE WHEN p.stage != 'new' THEN 1 ELSE 0 END) AS done
|
||
|
|
FROM problems p JOIN topics t ON t.issue = p.topic_issue
|
||
|
|
WHERE t.milestone IS NOT NULL
|
||
|
|
GROUP BY t.milestone, p.set_label
|
||
|
|
ORDER BY t.milestone`,
|
||
|
|
)
|
||
|
|
.all<{ milestone: number; set_label: string; total: number; done: number }>();
|
||
|
|
const phases = new Map<
|
||
|
|
number,
|
||
|
|
{ milestone: number; name: string } & Record<string, number | string>
|
||
|
|
>();
|
||
|
|
for (const row of phaseRows) {
|
||
|
|
const phase =
|
||
|
|
phases.get(row.milestone) ??
|
||
|
|
({
|
||
|
|
milestone: row.milestone,
|
||
|
|
name: PHASE_NAMES[row.milestone] ?? `Phase ${row.milestone}`,
|
||
|
|
core_done: 0,
|
||
|
|
core_total: 0,
|
||
|
|
optional_done: 0,
|
||
|
|
optional_total: 0,
|
||
|
|
deferred_done: 0,
|
||
|
|
deferred_total: 0,
|
||
|
|
} as { milestone: number; name: string } & Record<string, number | string>);
|
||
|
|
phase[`${row.set_label}_done`] = row.done;
|
||
|
|
phase[`${row.set_label}_total`] = row.total;
|
||
|
|
phases.set(row.milestone, phase);
|
||
|
|
}
|
||
|
|
|
||
|
|
const { results: ladderRows } = await db
|
||
|
|
.prepare("SELECT stage, COUNT(*) AS n FROM problems GROUP BY stage")
|
||
|
|
.all<{ stage: string; n: number }>();
|
||
|
|
const ladder: Record<string, number> = { new: 0, "+2": 0, "+5": 0, "+10": 0, retired: 0 };
|
||
|
|
for (const row of ladderRows) ladder[row.stage] = row.n;
|
||
|
|
|
||
|
|
const { results: gates } = await db
|
||
|
|
.prepare("SELECT week, issue, pass_rate, closed_on FROM gates ORDER BY week")
|
||
|
|
.all<{ week: number; issue: number | null; pass_rate: number | null; closed_on: string | null }>();
|
||
|
|
|
||
|
|
// Review-queue depth for the next 14 days: everything due by that day.
|
||
|
|
const queue: { date: string; due: number }[] = [];
|
||
|
|
for (let i = 0; i < 14; i++) {
|
||
|
|
const date = addDays(today, i);
|
||
|
|
const row = await db
|
||
|
|
.prepare(
|
||
|
|
`SELECT COUNT(*) AS n FROM problems
|
||
|
|
WHERE stage != 'retired' AND next_review IS NOT NULL AND next_review <= ?1
|
||
|
|
AND (defer_until IS NULL OR defer_until <= ?1)`,
|
||
|
|
)
|
||
|
|
.bind(date)
|
||
|
|
.first<{ n: number }>();
|
||
|
|
queue.push({ date, due: row?.n ?? 0 });
|
||
|
|
}
|
||
|
|
|
||
|
|
const recent: { date: string; attempts: number; passes: number }[] = [];
|
||
|
|
for (let i = 6; i >= 0; i--) {
|
||
|
|
const date = addDays(today, -i);
|
||
|
|
const row = await db
|
||
|
|
.prepare(
|
||
|
|
`SELECT COUNT(*) AS attempts,
|
||
|
|
SUM(CASE WHEN result = 'pass' THEN 1 ELSE 0 END) AS passes
|
||
|
|
FROM attempts WHERE date = ?`,
|
||
|
|
)
|
||
|
|
.bind(date)
|
||
|
|
.first<{ attempts: number; passes: number | null }>();
|
||
|
|
recent.push({ date, attempts: row?.attempts ?? 0, passes: row?.passes ?? 0 });
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
generated: new Date().toISOString(),
|
||
|
|
campaign: { day: campaignDay(today), week: campaignWeek(today), start: CAMPAIGN_START, days: CAMPAIGN_DAYS },
|
||
|
|
phases: [...phases.values()],
|
||
|
|
ladder,
|
||
|
|
gates,
|
||
|
|
streak: await streak(db, today),
|
||
|
|
queue,
|
||
|
|
recent,
|
||
|
|
};
|
||
|
|
}
|