Files
property-match/src/domain/futureSignal.ts
T
Benjamin SutterandClaude Opus 5 ba8dc39483 feat(agenten): Runde 8 — Livia recherchiert, Nora matcht, Ferdi liest Verträge
Livia wird von der Exposé-Master zur Research- & Market-Intelligence-Agentin.
Sie schaut nach draussen: verknüpfte Newsquellen, Geschäftsberichte,
Handelsregister und amtliche Publikationen werden zu qualitativen Leads
verdichtet — Unternehmen, Veränderung, Ort, Zeitpunkt, Begründung, Quellen.
Die Exposé-Funktion bleibt vollständig erhalten, ist aber nachgeordnet und
liegt im zweiten Reiter ihres Arbeitsplatzes.

Das Matching bleibt ausdrücklich bei Nora. Beide Arbeitsplätze teilen sich
dieselbe Liste (`UnifiedLeadList`) und dieselbe Detailansicht (`LeadDetail`);
bei Livia läuft sie mit `showMatching={false}`. Damit ist die Grenze im Code
gezogen und nicht bloss in der Beschriftung. Es gibt keine zweite Lead-Ablage:
ein Lead, zwei Arbeitsplätze.

Neu ist die Einordnung Chance/Risiko als Spalte in Noras Liste und als Block
in der Detailansicht, je mit Begründung und konkreter nächster Handlung. Bei
einem Risiko wird das betroffene Mietverhältnis benannt und auf internes
Re-Letting hingewiesen. `leadSignalTypeOf()` leitet die Einordnung für
Altbestände aus dem Signaltyp ab, damit keine Migration nötig ist.

Thomas bekommt denselben Chat-Einstieg wie die fünf anderen — dieselbe
Komponente, dieselbe Sticky-Mechanik, keine Sonderlösung. Dafür scrollt seine
Seite als Ganzes; die drei Verwaltungsbereiche behalten ihren inneren Scroll.

Ferdi wird vom Reminder-Agenten zur Contract Intelligence: Kündigungsfristen,
echte und unechte Optionen, Verhandlungsfenster und Vertragsgespräche. Der
Unterschied zwischen echter und unechter Option ist der Kern — die echte
Option hat eine Frist, die verfällt, die unechte nur ein Fenster, in dem
verhandelt werden sollte. Alle neuen Termine orientieren sich am
Entscheidungszeitpunkt, nicht am Vertragsende: `decisionDate` und `dueDate`
liegen bewusst davor.

Auslastungsanzeige nachgezogen: Livia zählt ihre Recherche statt offener
Exposés, Nora den Abgleich statt des Findens.

Geprüft: tsc 0 Fehler, eslint 0 Fehler/0 Warnungen, 423/423 Tests grün,
Produktionsbuild erfolgreich.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 00:35:31 +02:00

108 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { SignalType, RiskLevel, ReviewStatus } from './enums'
export type ContactType = 'EMAIL' | 'PHONE' | 'WEBSITE' | 'LINKEDIN' | 'CONTACT_PERSON'
export type ContactConfidence = 'HIGH' | 'MEDIUM' | 'LOW'
export interface ExtractedContact {
type: ContactType
value: string
confidence: ContactConfidence
label?: string
}
export interface SignalSource {
type: 'PRESS' | 'CONSTRUCTION_PERMIT' | 'JOB_POSTING' | 'COMPANY_REPORT' | 'MARKET_DATA' | 'MANUAL' | 'LEASE_CONTRACT'
url?: string
publishedAt?: string
credibility: 'LOW' | 'MEDIUM' | 'HIGH'
}
export interface SignalEvidence {
summary: string
sourceUrls?: string[]
extractedAt?: string
}
export interface FutureSignal {
id: string
signalType: SignalType
companyName?: string
propertyId?: string
locationHint: string
areaSqmEstimate?: number
probability: number
confidenceScore: number
timeHorizonMonths: number
source: SignalSource
sensitivityLevel: 'PUBLIC' | 'INTERNAL' | 'CONFIDENTIAL'
disclaimer: string
riskLevel: RiskLevel
marketIndicator?: string
relevanceScore?: number
isVerified: boolean
verifiedBy?: string
verifiedAt?: string
expiresAt?: string
organizationId?: string
createdAt: string
updatedAt: string
// F004 additions
title?: string // short human-readable headline
evidence?: SignalEvidence // structured evidence block
matchabilityScore?: number // 0–100: how well this signal can be matched to needs
reviewStatus?: ReviewStatus
aiSummary?: string // AI-generated explanation of what this signal means
// Strategic intelligence fields
marketIndicators?: string[] // ["Mitarbeiterzahl rückläufig -15%", ...]
strategicInterpretation?: string // "Die Signale deuten auf ..."
confirmedFacts?: string[] // ["Region bestätigt", ...]
unconfirmedFacts?: string[] // ["Exakte Fläche unbekannt", ...]
// Signal direction
// SUPPLY = space will become available → shown in demand feed
// DEMAND = company is looking for space → shown as Markt-Lead in Verwaltung
// undefined = treated as SUPPLY (backwards compat for generated signals)
signalDirection?: 'SUPPLY' | 'DEMAND'
// Unit-level reference: if set, this signal is about a specific rentable unit
unitId?: string
// KI-extracted contact information from web crawler sources
extractedContacts?: ExtractedContact[]
// ── Recherche-Lead von Livia (Runde 8, §1–§2) ──────────────────────────────
/**
* Chance oder Risiko. Optional, damit bestehende Signale gültig bleiben —
* fehlt der Wert, leitet ihn `leadSignalTypeOf()` aus `signalType` ab.
*/
leadSignalType?: LeadSignalType
/** Warum ist das eine Chance beziehungsweise ein Risiko? */
signalRationale?: string
/** Die konkrete nächste Handlung für den Bewirtschafter — kein Fazit, ein Auftrag. */
recommendedAction?: string
/** Bei Risiko: das Mietverhältnis, das betroffen sein könnte. */
affectedTenant?: string
/** Externe Quellen, aus denen Livia den Lead verdichtet hat. */
researchSources?: SignalSource[]
}
// ── Chance / Risiko (Runde 8, §2) ────────────────────────────────────────────
/**
* Bewertungsrichtung eines Leads.
*
* Bewusst getrennt von `signalType` und `signalDirection`: der Signaltyp sagt,
* *was* passiert (Expansion, Restrukturierung), die Richtung, ob Fläche gesucht
* oder frei wird. Chance und Risiko beantworten die Frage davor — soll der
* Bewirtschafter etwas gewinnen oder etwas verhindern? Danach richtet sich die
* empfohlene Handlung, und nur deshalb steht die Spalte in der Liste.
*/
export const LeadSignalType = {
CHANCE: 'CHANCE',
RISIKO: 'RISIKO',
} as const
export type LeadSignalType = typeof LeadSignalType[keyof typeof LeadSignalType]