This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

Add publication year to cards and enrichment

- Year now extracted from URL (venue-specific patterns) and LLM enrichment
- Card meta shows: year · authors · poster handle
- Removed raw paperUrl from card meta (cleaner)
- Enrich endpoint also updates records missing year
- All 26 records now have year populated

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta Code <noreply@letta.com>

author
Nandi
co-author
Letta Code
date (May 17, 2026, 10:05 PM -0700) commit 00a7f7b9 parent 5d0e0727
+26 -6
+1 -1
src/landing-page.ts
··· 88 88 : ''; 89 89 return '<a class="paper-card" href="/paper/' + s.rkey + '">' 90 90 + '<div class="title">' + venue + escHtml(s.title) + year + '</div>' 91 - + '<div class="meta">' + (authors ? escHtml(authors) + ' · ' : '') + escHtml(s.posterHandle) + ' · ' + s.paperUrl + '</div>' 91 + + '<div class="meta">' + (s.year ? s.year + ' · ' : '') + (authors ? escHtml(authors) + ' · ' : '') + escHtml(s.posterHandle) + '</div>' 92 92 + '<div class="summary">' + escHtml(s.summary).substring(0, 200) + '</div>' 93 93 + (domains ? '<div class="domains">' + domains + '</div>' : '') 94 94 + '</a>';
+19 -4
src/paper-summarizer.ts
··· 46 46 else if (post.paperUrl.includes("aclanthology.org")) result.venue = "ACL"; 47 47 else if (post.paperUrl.includes("thecvf.com")) result.venue = "CVPR/ICCV/ECCV"; 48 48 49 - const yearMatch = post.paperUrl.match(/(\d{4})/); 50 - if (yearMatch) { 51 - const y = parseInt(yearMatch[1]); 52 - if (y >= 1990 && y <= 2030) result.year = y; 49 + // Extract year from URL — venue-specific patterns to avoid matching IDs 50 + const yearPatterns: Array<[RegExp, number]> = [ 51 + [/arxiv\.org\/abs\/(\d{2})(\d{2})\./, 2], // arXiv: YYMM -> 20YY 52 + [/biorxiv\.org\/content\/.*?(\d{4})\./, 1], // bioRxiv: YYYY.MM 53 + [/medrxiv\.org\/content\/.*?(\d{4})\./, 1], // medRxiv: YYYY.MM 54 + [/nature\.com\/.*?(\d{4})/, 1], // Nature: embedded in article ID 55 + [/nber\.org\/papers\/w(\d{2})/, 1], // NBER: wYYnnn 56 + [/20(\d{2})/, 0], // Fallback: 20XX anywhere 57 + ]; 58 + for (const [pattern, groupIdx] of yearPatterns) { 59 + const m = post.paperUrl.match(pattern); 60 + if (m) { 61 + const raw = m[groupIdx + 1] || m[1]; 62 + const y = raw.length === 2 ? 2000 + parseInt(raw) : parseInt(raw); 63 + if (y >= 1990 && y <= 2030) { result.year = y; break; } 64 + } 53 65 } 54 66 55 67 if (post.embedDescription) { ··· 186 198 - "summary": 2-3 sentence summary in your own words. Focus on what the paper does and why it matters. Not a restatement of the abstract. 187 199 - "domains": array of 1-3 research domain strings (e.g. ["machine learning", "optimization"]) 188 200 - "takeaway": one sentence — the single most important thing a reader should remember 201 + - "year": the publication year as an integer (e.g. 2026). Derive from the URL, abstract, or title. 189 202 190 203 Paper: {title} 191 204 URL: {paperUrl} ··· 198 211 summary: string; 199 212 domains: string[]; 200 213 takeaway: string; 214 + year?: number; 201 215 } 202 216 203 217 export async function enrichWithLetta( ··· 260 274 summary: parsed.summary || content, 261 275 domains: parsed.domains || paper.domains, 262 276 takeaway: parsed.takeaway || "", 277 + year: parsed.year || undefined, 263 278 }; 264 279 }
+6 -1
src/worker.ts
··· 228 228 summary.summary = enrichment.summary; 229 229 summary.domains = enrichment.domains; 230 230 if (enrichment.takeaway) (summary as any).takeaway = enrichment.takeaway; 231 + if (enrichment.year) summary.year = enrichment.year; 231 232 } catch (err: any) { 232 233 console.error(`Enrichment failed for ${post.paperUrl}: ${err?.message ?? err}, using heuristic`); 233 234 } ··· 303 304 304 305 await ensureContrailReady(db); 305 306 306 - // Get records that need enrichment (empty/placeholder summaries, no takeaway, or Untitled) 307 + // Get records that need enrichment (empty/placeholder summaries, no takeaway, Untitled, or missing year) 307 308 const rows = await db 308 309 .prepare( 309 310 `SELECT rkey, record FROM records_summary ··· 312 313 OR json_extract(record, '$.title') = 'Untitled' 313 314 OR json_extract(record, '$.takeaway') IS NULL 314 315 OR json_extract(record, '$.takeaway') = '' 316 + OR json_extract(record, '$.year') IS NULL 317 + OR json_extract(record, '$.year') = 0 315 318 ORDER BY time_us DESC LIMIT 20` 316 319 ) 317 320 .all<{ rkey: string; record: string }>(); ··· 352 355 record.domains = result.domains; 353 356 if (result.takeaway) record.takeaway = result.takeaway; 354 357 if (result.title) record.title = result.title; 358 + if (result.year) record.year = result.year; 355 359 356 360 await db 357 361 .prepare("UPDATE records_summary SET record = ? WHERE rkey = ?") ··· 508 512 summary.summary = enrichment.summary; 509 513 summary.domains = enrichment.domains; 510 514 if (enrichment.takeaway) (summary as any).takeaway = enrichment.takeaway; 515 + if (enrichment.year) summary.year = enrichment.year; 511 516 } catch { 512 517 // Fall back to heuristic summary 513 518 }