feat(nora): Runde 10 Prompt 3 — Property Matching gegen den eigenen Bestand

Nora arbeitet jetzt mit dem Objektbestand statt mit Quellen.

- Lead-Detail auf ganzer Seite statt in der Schublade; darunter der neue
  Bereich «Property Match». Derselbe Baustein wie bisher, nur mit einem
  Layoutschalter — keine zweite Detailansicht
- «Das hat Livia recherchiert» heisst «Erstelltes Suchprofil» und zeigt
  lead.semanticSearchProfile. Ein Kasten, nicht zwei: Bestandsleads fallen auf
  ihren bisherigen Text zurück
- Matching-Logik in features/matching/leadPropertyMatcher.ts: Das Suchprofil
  wird nach Merkmalen gelesen, die darin tatsächlich vorkommen, und gegen das
  Objekt geprüft. Ein fehlendes Objektmerkmal geht nicht in die Rechnung ein —
  es gilt als nicht bekannt, nicht als erfüllt. Deterministisch, kein Zufall
- Trefferliste ist dieselbe Tabelle wie «Meine Objekte», ergänzt um eine erste
  Spalte «Match». Die übrigen Spalten werden anteilig gestaucht, damit keine
  waagrechte Bildlaufleiste entsteht
- «Belegte Objekte einbeziehen», Vorgabe aus; Umschalten rechnet neu
- MatchRun als eigene Entität mit Persistenz. Genau ein Eintrag je Lauf, auch
  bei der Übergabe aus Livia — eine Sperre verhindert den zweiten Lauf durch
  Neuaufbau der Komponente
- Aus Livia übergebene Leads starten das Matching einmal; ein normal geöffneter
  Lead startet nichts
- Kennzahlen «Anzahl durchgeführte Matchings» und «Anzahl betroffene Objekte»
  kommen aus dem laufenden Stand, nicht aus dem Katalog
- Aufgaben, Systeme (ERP statt Zefix/SHAB/Webquellen) und Einstellungen auf
  das Matching zugeschnitten
- Info-Knopf erklärt den Match-Wert entlang der tatsächlichen Rechnung

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Benjamin SutterandClaude Opus 5 committed 2026-09-13 00:02:06 +02:00
1 parent 314a6a36e3
commit c6011ac093
15 files changed
+1338 -275

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
import type { MatchRun } from '../domain/matchRun'
/** Ablage der durchgeführten Matching-Läufe (Runde 10, §§3.11/3.13). */
export interface IMatchRunProvider {
getAll(): Promise<MatchRun[]>
add(run: MatchRun): Promise<MatchRun>
}
+26
View File
@@ -0,0 +1,26 @@
/**
* Property On — die Matching-Läufe überleben einen Reload (Runde 10, §3.11).
*
* Kein Seed: Ein vorab erfundener Lauf wäre genau der Fantasiewert, den die
* Kennzahl nicht zeigen darf. Die Liste beginnt leer und füllt sich nur durch
* tatsächliche Läufe.
*/
import type { IMatchRunProvider } from './IMatchRunProvider'
import type { MatchRun } from '../domain/matchRun'
import { TEAM_STORAGE_KEYS, loadVersioned, persistVersioned } from './teamPersistence'
let store: MatchRun[] = loadVersioned<MatchRun[]>(TEAM_STORAGE_KEYS.MATCH_RUNS) ?? []
export const MockupMatchRunProvider: IMatchRunProvider = {
async getAll() {
return [...store]
},
async add(run) {
// Neuestes zuerst — das Protokoll liest man von oben.
store = [run, ...store]
persistVersioned(TEAM_STORAGE_KEYS.MATCH_RUNS, store)
return run
},
}
+2
View File
@@ -19,6 +19,8 @@ export const TEAM_STORAGE_KEYS = {
CONNECTIONS: `${PREFIX}connections`,
/** Von einem Recherchelauf erzeugte Leads (Runde 10, §2.12). */
RESEARCH_LEADS: `${PREFIX}research-leads`,
/** Durchgeführte Matching-Läufe (Runde 10, §3.11). */
MATCH_RUNS: `${PREFIX}match-runs`,
/** Manuell abgelegte Newsquellen als Dokumente (Runde 10, §2.6). */
RESEARCH_DOCUMENTS: `${PREFIX}research-documents`,
} as const