mirror of
https://github.com/prdlk/leetcode.git
synced 2026-11-03 13:27:36 +00:00
feat(api): add structured schedule plan with topics, problems, gate, sweep and reserved problem handling
This commit is contained in:
1 parent
00f44a1c47
commit
86107d5ef7
7 files changed
+344
-113
No files matched your search
+85
-17
@@ -83,7 +83,7 @@ export interface DigestRow {
|
||||
failUrl: string;
|
||||
}
|
||||
|
||||
/** A learning-day core problem — the only rows allowed a link. */
|
||||
/** A plan-named problem — the only rows allowed a link. */
|
||||
export interface TopicRow {
|
||||
lc: number;
|
||||
difficulty: string;
|
||||
@@ -91,6 +91,13 @@ export interface TopicRow {
|
||||
solved: boolean;
|
||||
}
|
||||
|
||||
/** One learning-day topic: the core list, then the optional "if time" list. */
|
||||
export interface TopicSection {
|
||||
name: string;
|
||||
core: TopicRow[];
|
||||
ifTime: TopicRow[];
|
||||
}
|
||||
|
||||
export interface DigestData {
|
||||
/** "Tuesday, August 25" */
|
||||
day: string;
|
||||
@@ -98,11 +105,16 @@ export interface DigestData {
|
||||
progress: string;
|
||||
streak: number;
|
||||
rest: boolean;
|
||||
topic?: { name: string; core: TopicRow[] };
|
||||
/** Topics taught today, in plan order — empty on every other day. */
|
||||
topics: TopicSection[];
|
||||
/** A set day's problems, assigned by number rather than by topic. */
|
||||
assigned?: TopicRow[];
|
||||
reviews: DigestRow[];
|
||||
/** Reviews past the daily cap; levelled onto the coming days. */
|
||||
carried: number;
|
||||
drills: DigestRow[];
|
||||
/** A sweep day's miss log: present (even empty) only on a sweep day. */
|
||||
sweep?: DigestRow[];
|
||||
gate?: { week: number; url: string };
|
||||
yesterday: { total: number; failed: number[] };
|
||||
gateRate?: number;
|
||||
@@ -278,10 +290,15 @@ function Block({
|
||||
// ── email ────────────────────────────────────────────────────────
|
||||
|
||||
function DigestEmail({ data }: { data: DigestData }) {
|
||||
const load = data.reviews.length + data.drills.length;
|
||||
const preview = data.rest
|
||||
? "Rest day — nothing to do but rest."
|
||||
: `${load} to work through today${data.topic ? `, starting with ${data.topic.name}` : ""}.`;
|
||||
const load = data.reviews.length + data.drills.length + (data.sweep?.length ?? 0);
|
||||
const lead = data.topics[0]
|
||||
? `, starting with ${data.topics[0].name}`
|
||||
: data.assigned
|
||||
? ", starting with today's set"
|
||||
: data.sweep
|
||||
? " — sweep day, nothing new"
|
||||
: "";
|
||||
const preview = data.rest ? "Rest day — nothing to do but rest." : `${load} to work through today${lead}.`;
|
||||
|
||||
const passed = data.yesterday.total - data.yesterday.failed.length;
|
||||
const missed = data.yesterday.failed.map((lc) => `LC ${lc}`).join(", ");
|
||||
@@ -310,15 +327,47 @@ function DigestEmail({ data }: { data: DigestData }) {
|
||||
|
||||
{data.rest ? null : (
|
||||
<>
|
||||
{data.topic ? (
|
||||
<Section style={{ marginTop: "28px" }}>
|
||||
<Text style={label}>Something new today</Text>
|
||||
{data.topics.map((topic, i) => (
|
||||
<Section key={topic.name} style={{ marginTop: i === 0 ? "28px" : "20px" }}>
|
||||
{i === 0 ? (
|
||||
<Text style={label}>
|
||||
{data.topics.length === 1 ? "Something new today" : "Two new things today"}
|
||||
</Text>
|
||||
) : null}
|
||||
<Text style={{ color: INK, fontSize: "16px", fontWeight: 600, margin: "0 0 8px" }}>
|
||||
{data.topic.name}
|
||||
{topic.name}
|
||||
</Text>
|
||||
<Text style={hint}>Work through these from the top — a tick means it is already solved.</Text>
|
||||
<TopicTable rows={data.topic.core} />
|
||||
<TopicTable rows={topic.core} />
|
||||
{topic.ifTime.length > 0 ? (
|
||||
<>
|
||||
<Text style={{ ...hint, margin: "12px 0 8px" }}>
|
||||
If time — only once the core is done inside the clock.
|
||||
</Text>
|
||||
<TopicTable rows={topic.ifTime} />
|
||||
</>
|
||||
) : null}
|
||||
</Section>
|
||||
))}
|
||||
|
||||
{data.assigned ? (
|
||||
<Section style={{ marginTop: "28px" }}>
|
||||
<Text style={label}>Today's set</Text>
|
||||
<Text style={hint}>
|
||||
Picked by number, not by topic — all of them, in order, inside the clock.
|
||||
</Text>
|
||||
<TopicTable rows={data.assigned} />
|
||||
</Section>
|
||||
) : null}
|
||||
|
||||
{data.sweep ? (
|
||||
<Block
|
||||
title="Sweep — redo every red"
|
||||
note="Every problem still standing on a miss. Nothing new today; clear these first."
|
||||
rows={data.sweep}
|
||||
staged
|
||||
empty="The miss log is clear — nothing is standing on a fail."
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Block
|
||||
@@ -409,16 +458,27 @@ function plainDigest(data: DigestData): string {
|
||||
: ` · ${data.streak} day${data.streak === 1 ? "" : "s"} in a row, keep it going.`),
|
||||
];
|
||||
|
||||
const linked = (p: TopicRow) =>
|
||||
` LC ${p.lc} — ${DIFFICULTY[p.difficulty]?.label ?? p.difficulty}${p.solved ? " (done)" : ""} — ${p.url}`;
|
||||
|
||||
if (!data.rest) {
|
||||
if (data.topic) {
|
||||
for (const topic of data.topics) {
|
||||
out.push(
|
||||
"",
|
||||
`SOMETHING NEW TODAY — ${data.topic.name}`,
|
||||
`SOMETHING NEW TODAY — ${topic.name}`,
|
||||
"Work through these from the top; the ones marked done you have already solved.",
|
||||
...data.topic.core.map(
|
||||
(p) =>
|
||||
` LC ${p.lc} — ${DIFFICULTY[p.difficulty]?.label ?? p.difficulty}${p.solved ? " (done)" : ""} — ${p.url}`,
|
||||
),
|
||||
...topic.core.map(linked),
|
||||
);
|
||||
if (topic.ifTime.length > 0) {
|
||||
out.push(" If time — only once the core is done inside the clock:", ...topic.ifTime.map(linked));
|
||||
}
|
||||
}
|
||||
if (data.assigned) {
|
||||
out.push(
|
||||
"",
|
||||
"TODAY'S SET",
|
||||
"Picked by number, not by topic — all of them, in order, inside the clock.",
|
||||
...data.assigned.map(linked),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -436,6 +496,14 @@ function plainDigest(data: DigestData): string {
|
||||
"No cold starts today.",
|
||||
],
|
||||
];
|
||||
if (data.sweep) {
|
||||
lists.unshift([
|
||||
"SWEEP — REDO EVERY RED",
|
||||
"Every problem still standing on a miss. Nothing new today; clear these first.",
|
||||
data.sweep,
|
||||
"The miss log is clear — nothing is standing on a fail.",
|
||||
]);
|
||||
}
|
||||
for (const [title, note, rows, empty] of lists) {
|
||||
out.push("", title);
|
||||
if (rows.length === 0) {
|
||||
|
||||
Reference in new issue
Block a user