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>
51 lines
1.7 KiB
TypeScript
51 lines
1.7 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
|
import { researchDocumentService } from '../services/researchDocumentService'
|
|
import { STALE_SIGNALS } from '../lib/constants'
|
|
import { useToastStore } from '../stores/toastStore'
|
|
|
|
const KEY = ['researchDocuments']
|
|
|
|
/** Die manuell abgelegten Newsquellen (Runde 10, §2.6). */
|
|
export function useResearchDocuments() {
|
|
return useQuery({
|
|
queryKey: KEY,
|
|
queryFn: () => researchDocumentService.getAll(),
|
|
staleTime: STALE_SIGNALS,
|
|
select: (res) => res.data,
|
|
})
|
|
}
|
|
|
|
export function useUploadResearchDocument() {
|
|
const queryClient = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (file: File) => researchDocumentService.upload(file),
|
|
onSuccess: (res) => {
|
|
void queryClient.invalidateQueries({ queryKey: KEY })
|
|
// Ein Dokument ohne brauchbaren Text wird abgelegt, trägt aber nichts
|
|
// bei — das muss beim Ablegen gesagt werden und nicht erst, wenn der
|
|
// Lauf keine Leads daraus findet.
|
|
if (res.data.extractionNote) {
|
|
useToastStore.getState().showToast(res.data.extractionNote, 'warning')
|
|
} else {
|
|
useToastStore.getState().showToast(`«${res.data.name}» abgelegt.`, 'success')
|
|
}
|
|
},
|
|
onError: (err: Error) => {
|
|
useToastStore.getState().showToast(err.message, 'error')
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useRemoveResearchDocument() {
|
|
const queryClient = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (id: string) => researchDocumentService.remove(id),
|
|
onSuccess: () => {
|
|
void queryClient.invalidateQueries({ queryKey: KEY })
|
|
},
|
|
onError: () => {
|
|
useToastStore.getState().showToast('Das Dokument konnte nicht entfernt werden.', 'error')
|
|
},
|
|
})
|
|
}
|