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.
|
* free to colour it however it likes.
|
||||||
*
|
*
|
||||||
* `recent`, `attempting` and `year` feed the docs page's LeetCode-style cards
|
* `recent`, `attempting` and `year` feed the docs page's LeetCode-style cards
|
||||||
* (recently solved, the solved ring, the year heatmap). They are additive: the
|
* (recently solved, the solved ring, the heatmap and the submissions chart).
|
||||||
* terminal dashboard ignores them.
|
* They are additive: the terminal dashboard ignores them.
|
||||||
*/
|
*/
|
||||||
import {
|
import {
|
||||||
CAMPAIGN_DAYS,
|
CAMPAIGN_DAYS,
|
||||||
@@ -59,7 +59,7 @@ interface TopicStat {
|
|||||||
temperature: Temperature;
|
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;
|
const YEAR_DAYS = 365;
|
||||||
|
|
||||||
/** How many problems `recent` lists. */
|
/** How many problems `recent` lists. */
|
||||||
@@ -252,38 +252,52 @@ export async function buildStats(db: D1Database, today: string): Promise<object>
|
|||||||
)
|
)
|
||||||
.first<{ n: number }>();
|
.first<{ n: number }>();
|
||||||
|
|
||||||
// Attempts per day over the past year, every day present, plus the three
|
// The past year, one entry per day (zeros included), split by difficulty two
|
||||||
// figures LeetCode prints above its heatmap. `heat` stays campaign-scoped
|
// ways: every logged attempt, and every first solve — the same first-pass
|
||||||
// (the terminal dashboard lays it out by campaign week); this is the
|
// date `recent` uses. `heat` stays campaign-scoped (the terminal dashboard
|
||||||
// calendar-scoped view of the same table.
|
// 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 yearStart = addDays(today, -(YEAR_DAYS - 1));
|
||||||
const { results: yearRows } = await db
|
const { results: attemptRows } = await db
|
||||||
.prepare(
|
.prepare(
|
||||||
`SELECT date,
|
`SELECT a.date AS date, p.difficulty AS difficulty,
|
||||||
COUNT(*) AS attempts,
|
COUNT(*) AS n,
|
||||||
SUM(CASE WHEN result = 'pass' THEN 1 ELSE 0 END) AS passes
|
SUM(CASE WHEN a.result = 'pass' THEN 1 ELSE 0 END) AS passes
|
||||||
FROM attempts WHERE date BETWEEN ?1 AND ?2 GROUP BY date`,
|
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)
|
.bind(yearStart, today)
|
||||||
.all<{ date: string; attempts: number; passes: number | null }>();
|
.all<{ date: string; difficulty: string; n: number; passes: number | null }>();
|
||||||
const yearByDate = new Map(yearRows.map((r) => [r.date, r]));
|
const { results: solvedRows } = await db
|
||||||
const yearDays = Array.from({ length: YEAR_DAYS }, (_, i) => {
|
.prepare(
|
||||||
const date = addDays(yearStart, i);
|
`SELECT date, difficulty, COUNT(*) AS n
|
||||||
const row = yearByDate.get(date);
|
FROM (SELECT MIN(a.date) AS date, p.difficulty AS difficulty
|
||||||
return { date, attempts: row?.attempts ?? 0, passes: row?.passes ?? 0 };
|
FROM attempts a JOIN problems p ON p.lc_number = a.lc_number
|
||||||
});
|
WHERE a.result = 'pass'
|
||||||
let run = 0;
|
GROUP BY p.lc_number)
|
||||||
let maxStreak = 0;
|
WHERE date BETWEEN ?1 AND ?2
|
||||||
for (const d of yearDays) {
|
GROUP BY date, difficulty`,
|
||||||
run = d.attempts > 0 ? run + 1 : 0;
|
)
|
||||||
maxStreak = Math.max(maxStreak, run);
|
.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 {
|
return {
|
||||||
generated: new Date().toISOString(),
|
generated: new Date().toISOString(),
|
||||||
|
|||||||
Reference in new issue
Block a user