mirror of
https://github.com/prdlk/leetcode.git
synced 2026-11-02 13:12:44 +00:00
feat(api): add difficulty‑segmented solved data to yearly stats
This commit is contained in:
1 parent
6e9c407698
commit
dde06daea3
1 file changed
+44
-30
+44
-30
@@ -10,8 +10,8 @@
|
||||
* free to colour it however it likes.
|
||||
*
|
||||
* `recent`, `attempting` and `year` feed the docs page's LeetCode-style cards
|
||||
* (recently solved, the solved ring, the year heatmap). They are additive: the
|
||||
* terminal dashboard ignores them.
|
||||
* (recently solved, the solved ring, the heatmap and the submissions chart).
|
||||
* They are additive: the terminal dashboard ignores them.
|
||||
*/
|
||||
import {
|
||||
CAMPAIGN_DAYS,
|
||||
@@ -59,7 +59,7 @@ interface TopicStat {
|
||||
temperature: Temperature;
|
||||
}
|
||||
|
||||
/** Days in the year heatmap: the 365 ending today, like LeetCode's "past one year". */
|
||||
/** Days in `year`: the 365 ending today, which always covers the current calendar year to date. */
|
||||
const YEAR_DAYS = 365;
|
||||
|
||||
/** How many problems `recent` lists. */
|
||||
@@ -252,38 +252,52 @@ export async function buildStats(db: D1Database, today: string): Promise<object>
|
||||
)
|
||||
.first<{ n: number }>();
|
||||
|
||||
// Attempts per day over the past year, every day present, plus the three
|
||||
// figures LeetCode prints above its heatmap. `heat` stays campaign-scoped
|
||||
// (the terminal dashboard lays it out by campaign week); this is the
|
||||
// calendar-scoped view of the same table.
|
||||
// The past year, one entry per day (zeros included), split by difficulty two
|
||||
// ways: every logged attempt, and every first solve — the same first-pass
|
||||
// date `recent` uses. `heat` stays campaign-scoped (the terminal dashboard
|
||||
// lays it out by campaign week); this is the calendar-scoped view the docs
|
||||
// heatmap and submissions chart window and bucket for themselves.
|
||||
const yearStart = addDays(today, -(YEAR_DAYS - 1));
|
||||
const { results: yearRows } = await db
|
||||
const { results: attemptRows } = await db
|
||||
.prepare(
|
||||
`SELECT date,
|
||||
COUNT(*) AS attempts,
|
||||
SUM(CASE WHEN result = 'pass' THEN 1 ELSE 0 END) AS passes
|
||||
FROM attempts WHERE date BETWEEN ?1 AND ?2 GROUP BY date`,
|
||||
`SELECT a.date AS date, p.difficulty AS difficulty,
|
||||
COUNT(*) AS n,
|
||||
SUM(CASE WHEN a.result = 'pass' THEN 1 ELSE 0 END) AS passes
|
||||
FROM attempts a JOIN problems p ON p.lc_number = a.lc_number
|
||||
WHERE a.date BETWEEN ?1 AND ?2
|
||||
GROUP BY a.date, p.difficulty`,
|
||||
)
|
||||
.bind(yearStart, today)
|
||||
.all<{ date: string; attempts: number; passes: number | null }>();
|
||||
const yearByDate = new Map(yearRows.map((r) => [r.date, r]));
|
||||
const yearDays = Array.from({ length: YEAR_DAYS }, (_, i) => {
|
||||
const date = addDays(yearStart, i);
|
||||
const row = yearByDate.get(date);
|
||||
return { date, attempts: row?.attempts ?? 0, passes: row?.passes ?? 0 };
|
||||
});
|
||||
let run = 0;
|
||||
let maxStreak = 0;
|
||||
for (const d of yearDays) {
|
||||
run = d.attempts > 0 ? run + 1 : 0;
|
||||
maxStreak = Math.max(maxStreak, run);
|
||||
.all<{ date: string; difficulty: string; n: number; passes: number | null }>();
|
||||
const { results: solvedRows } = await db
|
||||
.prepare(
|
||||
`SELECT date, difficulty, COUNT(*) AS n
|
||||
FROM (SELECT MIN(a.date) AS date, p.difficulty AS difficulty
|
||||
FROM attempts a JOIN problems p ON p.lc_number = a.lc_number
|
||||
WHERE a.result = 'pass'
|
||||
GROUP BY p.lc_number)
|
||||
WHERE date BETWEEN ?1 AND ?2
|
||||
GROUP BY date, difficulty`,
|
||||
)
|
||||
.bind(yearStart, today)
|
||||
.all<{ date: string; difficulty: string; n: number }>();
|
||||
const year = Array.from({ length: YEAR_DAYS }, (_, i) => ({
|
||||
date: addDays(yearStart, i),
|
||||
passes: 0,
|
||||
attempts: Object.fromEntries(DIFFICULTIES.map((d) => [d, 0])),
|
||||
solved: Object.fromEntries(DIFFICULTIES.map((d) => [d, 0])),
|
||||
}));
|
||||
const yearIndex = new Map(year.map((d, i) => [d.date, i]));
|
||||
for (const row of attemptRows) {
|
||||
const day = year[yearIndex.get(row.date) ?? -1];
|
||||
if (!day || !(row.difficulty in day.attempts)) continue;
|
||||
day.attempts[row.difficulty] = row.n;
|
||||
day.passes += row.passes ?? 0;
|
||||
}
|
||||
for (const row of solvedRows) {
|
||||
const day = year[yearIndex.get(row.date) ?? -1];
|
||||
if (day && row.difficulty in day.solved) day.solved[row.difficulty] = row.n;
|
||||
}
|
||||
const year = {
|
||||
days: yearDays,
|
||||
attempts: yearDays.reduce((n, d) => n + d.attempts, 0),
|
||||
active_days: yearDays.filter((d) => d.attempts > 0).length,
|
||||
max_streak: maxStreak,
|
||||
};
|
||||
|
||||
return {
|
||||
generated: new Date().toISOString(),
|
||||
|
||||
Reference in new issue
Block a user