feat(livia): Runde 10 Prompt 2 — eine Leadliste, dynamische Quellen, zeitliche Relevanz
Livia arbeitet nicht mehr neben dem Leadbestand, sondern in ihn hinein. - Systemzugänge tragen Titel, Beschreibung und Adresse; die Liste zeigt je Eintrag nur noch Aktiv/Inaktiv und Lesen/Schreiben — kein Berechtigungsbalken, kein Auge-Icon, keine Lese-/Schreibgruppen - Die Quellenregistry ist dynamisch: Der Lauf liest genau die aktiven Lesezugänge mit Adresse aus Livias Personalblatt. Für die drei gepflegten Quellen greift weiterhin ihr eigener Leser, für alles andere ein allgemeiner — eine Übersichtsseite, bis zu zehn Unterseiten, kein Spider. Freie Adressen werden auf http/https geprüft und gegen interne Netze gesperrt - Manuell abgelegte PDF- und Word-Dokumente fliessen in denselben Lauf. Text wird beim Ablegen nativ extrahiert (ZIP + Flate über DecompressionStream, ohne neue Abhängigkeit), Datei und Text liegen in IndexedDB - Neuer harter Filter «Zeitliche Relevanz»: das Modell beurteilt die Ereigniszeit der Veränderung, nicht das Publikationsdatum. «unknown» fällt bewusst nicht durch — ein erfundenes Datum wäre die schlechtere Antwort - Quellentypen und Beobachtungsraum sind Chips mit Freitext; jeder Wert lässt sich einzeln entfernen, auch die vorgegebenen - Info-Knopf bei «Zeitliche Relevanz» und «Mindestrelevanz» erklärt, wie der Wert entsteht — ohne erfundene Prozentgewichte - Gefundene Leads gehen in den zentralen Signalbestand statt in eine zweite Ergebnisliste; die Herkunft steht am Signal (origin: RESEARCH_RUN) - Der Lauf erzeugt einen echten Protokolleintrag mit den Zahlen des Laufs - Lead-Detail: zeitliche Einordnung mit Fundstelle, semantisches Suchprofil bei Chancen mit «Matching starten», und bei Risiko ausdrücklich «Kein internes Objekt eindeutig zugeordnet» statt einer beliebigen Mock-Immobilie - Aufgabenliste nach §11 bereinigt, «Unsichere Signale in Review Queue legen» von Nora übernommen Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1c4b3f7dd8
commit
30daa77937
33 files changed
+2275
-301
No files matched your search
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Property On — manuell abgelegte Newsquellen (Runde 10, §2.6).
|
||||
*
|
||||
* Der Dienst nimmt die Datei entgegen, gewinnt einmal ihren Text und legt
|
||||
* beides ab. Die Textextraktion gehört hierher und nicht in die Komponente:
|
||||
* Sie ist fachliche Arbeit, und sie soll für jeden künftigen Weg ins System —
|
||||
* Upload, Mailanhang, Ablageordner — dieselbe sein.
|
||||
*/
|
||||
|
||||
import { IndexedDbResearchDocumentProvider } from '../provider/IndexedDbResearchDocumentProvider'
|
||||
import type { ResearchDocument } from '../domain/researchDocument'
|
||||
import { RESEARCH_DOCUMENT_MAX_BYTES } from '../domain/researchDocument'
|
||||
import { extractDocumentText, kindOf } from '../lib/documentText'
|
||||
import type { ListResponse, ItemResponse } from './types'
|
||||
import { throwServiceError } from './errors'
|
||||
|
||||
const provider = IndexedDbResearchDocumentProvider
|
||||
|
||||
export const researchDocumentService = {
|
||||
async getAll(): Promise<ListResponse<ResearchDocument>> {
|
||||
try {
|
||||
const data = await provider.getAll()
|
||||
return { data, meta: { total: data.length, page: 1, pageSize: data.length, hasMore: false } }
|
||||
} catch (err) {
|
||||
throwServiceError(err)
|
||||
}
|
||||
},
|
||||
|
||||
async upload(file: File): Promise<ItemResponse<ResearchDocument>> {
|
||||
const kind = kindOf(file.name)
|
||||
if (!kind) {
|
||||
throw new Error('Nur PDF- und Word-Dateien (.pdf, .docx) können abgelegt werden.')
|
||||
}
|
||||
if (file.size > RESEARCH_DOCUMENT_MAX_BYTES) {
|
||||
throw new Error(
|
||||
`Die Datei ist zu gross (${Math.round(file.size / 1024 / 1024)} MB). Höchstens ${RESEARCH_DOCUMENT_MAX_BYTES / 1024 / 1024} MB.`,
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
const { text, note } = await extractDocumentText(file, kind)
|
||||
const doc: ResearchDocument = {
|
||||
id: `doc-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
|
||||
name: file.name,
|
||||
kind,
|
||||
size: file.size,
|
||||
uploadedAt: new Date().toISOString(),
|
||||
text,
|
||||
...(note ? { extractionNote: note } : {}),
|
||||
}
|
||||
await provider.add(doc, file)
|
||||
return { data: doc }
|
||||
} catch (err) {
|
||||
throwServiceError(err)
|
||||
}
|
||||
},
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
try {
|
||||
await provider.remove(id)
|
||||
} catch (err) {
|
||||
throwServiceError(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
Reference in new issue
Block a user