algocoach 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "algocoach",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "bin": {
@@ -106,7 +106,7 @@ async function scheduleReview(userId: string, problem: any, status: string) {
106
106
  }
107
107
 
108
108
  async function getDueReviews(userId: string): Promise<any[]> {
109
- const now = new Date()
109
+ const now = Date.now()
110
110
  const due = await db.query.problemReview.findMany({
111
111
  where: and(eq(problemReview.userId, userId), sql`${problemReview.nextReviewAt} <= ${now}`),
112
112
  orderBy: [sql`${problemReview.nextReviewAt} ASC`],
@@ -168,11 +168,29 @@ export async function selectDailyProblems(params: {
168
168
  const exclude = [...new Set([...overusedSlugs, ...solvedSlugs, ...extraExclude])]
169
169
 
170
170
  const topicSlugs = parseTopicToSlugs(week.topic)
171
- let searchResults = await searchLeetCodeProblems({
172
- topics: topicSlugs,
173
- excludeSlugs: exclude,
174
- limit: 30,
175
- })
171
+ let searchResults: LeetCodeProblem[] = []
172
+ if (topicSlugs.length > 1) {
173
+ const seen = new Set<string>()
174
+ for (const slug of topicSlugs) {
175
+ const results = await searchLeetCodeProblems({
176
+ topics: [slug],
177
+ excludeSlugs: exclude,
178
+ limit: 30,
179
+ })
180
+ for (const p of results) {
181
+ if (!seen.has(p.titleSlug)) {
182
+ seen.add(p.titleSlug)
183
+ searchResults.push(p)
184
+ }
185
+ }
186
+ }
187
+ } else {
188
+ searchResults = await searchLeetCodeProblems({
189
+ topics: topicSlugs,
190
+ excludeSlugs: exclude,
191
+ limit: 30,
192
+ })
193
+ }
176
194
 
177
195
  if (!searchResults.length) {
178
196
  return {
@@ -204,23 +222,42 @@ export async function selectDailyProblems(params: {
204
222
  const hard = searchResults.filter((p) => p.difficulty === "Hard").sort((a, b) => b.acRate - a.acRate)
205
223
 
206
224
  const selected: LeetCodeProblem[] = []
207
- const usedTags = new Set<string>()
225
+ const usedSlugs = new Set<string>()
208
226
 
209
227
  function pickFromBucket(bucket: LeetCodeProblem[], want: number): LeetCodeProblem[] {
210
228
  const result: LeetCodeProblem[] = []
211
- const diverse = bucket.filter((p) => p.topicTags.some((t) => !usedTags.has(t.slug)))
212
- const rest = bucket.filter((p) => !diverse.includes(p))
213
- for (const pool of [diverse, rest]) {
214
- for (const p of pool) {
215
- if (result.length >= want) break
216
- result.push(p)
217
- p.topicTags.forEach((t) => usedTags.add(t.slug))
218
- }
229
+ const candidates = bucket.filter((p) => !usedSlugs.has(p.titleSlug))
230
+ for (const p of candidates) {
231
+ if (result.length >= want) break
232
+ result.push(p)
233
+ usedSlugs.add(p.titleSlug)
219
234
  }
220
235
  return result
221
236
  }
222
237
 
223
- if (difficultyFilter !== "MIXED") {
238
+ // When multiple constituent topics, round-robin across them for topic balance
239
+ if (topicSlugs.length > 1 && difficultyFilter === "MIXED") {
240
+ const byTopic = topicSlugs.map((slug) => ({
241
+ slug,
242
+ easy: easy.filter((p) => p.topicTags.some((t) => t.slug === slug)),
243
+ medium: medium.filter((p) => p.topicTags.some((t) => t.slug === slug)),
244
+ hard: hard.filter((p) => p.topicTags.some((t) => t.slug === slug)),
245
+ }))
246
+
247
+ for (let round = 0; round < count; round++) {
248
+ const topic = byTopic[round % byTopic.length]
249
+ const tier = round === 0 ? topic.easy : round === 1 ? topic.medium : topic.hard
250
+ const picked = pickFromBucket(tier, 1)
251
+ if (picked.length) {
252
+ selected.push(...picked)
253
+ continue
254
+ }
255
+ const fallback = [...topic.easy, ...topic.medium, ...topic.hard].filter(
256
+ (p) => !usedSlugs.has(p.titleSlug),
257
+ )
258
+ selected.push(...pickFromBucket(fallback, 1))
259
+ }
260
+ } else if (difficultyFilter !== "MIXED") {
224
261
  const pool = difficultyFilter === "EASY" ? easy : difficultyFilter === "MEDIUM" ? medium : hard
225
262
  selected.push(...pickFromBucket(pool, count))
226
263
  } else {