feat(livia): drei Übersichtsseiten je Newsquelle statt einer
Eine Seite reicht nicht. Die Handelskammer erneuert ihre obersten zehn Meldungen im Lauf eines Tages; stehen dort gerade Personalien und Meinungsbeiträge, findet Livia zu Recht nichts, und die Demo wirkt schwach, obwohl die Mechanik stimmt. Genau das war zwischen zwei Läufen zu beobachten. Neu werden je Quelle drei Übersichtsseiten gelesen — bei der Handelskammer über `…/page/N.html`, bei Greater Zurich über `?page=N` —, gedeckelt auf 24 Artikel je Quelle. Die Artikel werden parallel geholt statt nacheinander, die Beurteilung läuft in begrenzt parallelen Fünferstapeln. Wirkung, gemessen am selben Tag: aus 20 gelesenen Einträgen und 1 Lead werden 49 Einträge und 8 Leads, darunter zwei mit hoher Relevanz — eine Arealentwicklung über 38 000 m² und eine Standorteröffnung. Laufzeit 30 Sekunden statt 16, weiterhin klar innerhalb des 60-Sekunden-Limits. Die Begrenzung der Gleichzeitigkeit ist kein Detail: alle Teilstapel auf einmal loszuschicken wäre schneller, läuft aber in die Drosselung des Anbieters — und ein gedrosselter Lauf sieht für den Nutzer aus wie ein kaputter. Ein gescheiterter Teilstapel reisst die übrigen nicht mehr mit. Geprüft: tsc 0 Fehler, eslint 0 Fehler/0 Warnungen, Build erfolgreich, Live-Lauf 49 Einträge aus 3/3 Quellen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
f53f3be1e8
commit
f58d841e7d
2 files changed
+142
-69
No files matched your search
@@ -90,6 +90,15 @@ function extractJson(text: string): unknown {
|
||||
*/
|
||||
const BATCH_SIZE = 5
|
||||
|
||||
/**
|
||||
* Wie viele Teilstapel gleichzeitig beurteilt werden.
|
||||
*
|
||||
* Alles auf einmal loszuschicken wäre schneller, läuft aber in die Drosselung
|
||||
* des Anbieters — und ein gedrosselter Lauf sieht für den Nutzer aus wie ein
|
||||
* kaputter.
|
||||
*/
|
||||
const ANALYSIS_CONCURRENCY = 5
|
||||
|
||||
function chunk<T>(list: T[], size: number): T[][] {
|
||||
const out: T[][] = []
|
||||
for (let i = 0; i < list.length; i += size) out.push(list.slice(i, i + size))
|
||||
@@ -148,11 +157,28 @@ export async function analyzeResearchItems(items: ResearchItem[]): Promise<Analy
|
||||
|
||||
const client = new Anthropic({ apiKey })
|
||||
|
||||
// Parallel: die Laufzeit ist damit die des langsamsten Teilstapels und nicht
|
||||
// deren Summe — das hält den Aufruf innerhalb des Zeitlimits der Funktion.
|
||||
const batches = await Promise.all(
|
||||
chunk(items, BATCH_SIZE).map(b => analyzeBatch(client, b)),
|
||||
// Parallel, aber begrenzt: die Laufzeit ist damit nicht die Summe der
|
||||
// Teilstapel, und gleichzeitig laufen nie so viele Anfragen, dass der
|
||||
// Anbieter drosselt.
|
||||
const teilstapel = chunk(items, BATCH_SIZE)
|
||||
const ergebnisse: Awaited<ReturnType<typeof analyzeBatch>>[] = new Array(teilstapel.length)
|
||||
let next = 0
|
||||
const worker = async (): Promise<void> => {
|
||||
for (;;) {
|
||||
const i = next++
|
||||
if (i >= teilstapel.length) return
|
||||
// Ein gescheiterter Teilstapel darf die übrigen nicht mitreissen.
|
||||
try {
|
||||
ergebnisse[i] = await analyzeBatch(client, teilstapel[i])
|
||||
} catch {
|
||||
ergebnisse[i] = []
|
||||
}
|
||||
}
|
||||
}
|
||||
await Promise.all(
|
||||
Array.from({ length: Math.min(ANALYSIS_CONCURRENCY, teilstapel.length) }, worker),
|
||||
)
|
||||
const batches = ergebnisse
|
||||
|
||||
const leads: ResearchLead[] = []
|
||||
let discarded = 0
|
||||
|
||||
Reference in new issue
Block a user