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>
138 lines
4.9 KiB
TypeScript
138 lines
4.9 KiB
TypeScript
/**
|
||
* Die Termintypen, die Ferdi führt.
|
||
*
|
||
* Mit Runde 8 (§4) kommen die Vertragstypen dazu: Ferdi verwaltet nicht mehr
|
||
* nur allgemeine Erinnerungen, sondern erkennt die Zeitpunkte, an denen ein
|
||
* Gewerbemietvertrag eine Entscheidung verlangt.
|
||
*
|
||
* Die Unterscheidung zwischen echter und unechter Option ist keine Spitzfindigkeit,
|
||
* sondern der Kern des Ganzen: die **echte Option** gibt dem Mieter ein einseitiges
|
||
* Gestaltungsrecht — verstreicht die Ausübungsfrist, ist das Recht erloschen, und
|
||
* zwar unwiderruflich. Die **unechte Option** ist bloss eine Absichtserklärung,
|
||
* über eine Fortsetzung zu verhandeln; hier gibt es keine Frist, die verfällt,
|
||
* sondern ein Fenster, in dem verhandelt werden sollte.
|
||
*/
|
||
export const ReminderType = {
|
||
LEASE_EXPIRY: 'LEASE_EXPIRY',
|
||
BREAK_OPTION: 'BREAK_OPTION',
|
||
RENT_REVIEW: 'RENT_REVIEW',
|
||
INSPECTION: 'INSPECTION',
|
||
INSURANCE_RENEWAL: 'INSURANCE_RENEWAL',
|
||
MAINTENANCE: 'MAINTENANCE',
|
||
SCHATTENMARKT_RELEASE: 'SCHATTENMARKT_RELEASE',
|
||
CUSTOM: 'CUSTOM',
|
||
|
||
// ── Contract Intelligence (Runde 8, §4) ────────────────────────────────────
|
||
/** Kündigungsfrist — ab wann eine Kündigung noch fristgerecht möglich ist. */
|
||
NOTICE_PERIOD: 'NOTICE_PERIOD',
|
||
/** Echte Option — verbindlicher Ausübungszeitpunkt, danach erloschen. */
|
||
REAL_OPTION: 'REAL_OPTION',
|
||
/** Unechte Option — Zeitfenster für Verhandlungen über eine Fortsetzung. */
|
||
UNREAL_OPTION: 'UNREAL_OPTION',
|
||
/** Verhandlungsfenster ohne Option, etwa vor einer Staffelmietanpassung. */
|
||
NEGOTIATION_WINDOW: 'NEGOTIATION_WINDOW',
|
||
/** Anstehendes Vertragsgespräch mit dem Mieter. */
|
||
CONTRACT_TALK: 'CONTRACT_TALK',
|
||
} as const
|
||
export type ReminderType = typeof ReminderType[keyof typeof ReminderType]
|
||
|
||
/** Welche Vertragstypen aus der Dokumentanalyse stammen (Runde 8, §4). */
|
||
export const CONTRACT_REMINDER_TYPES: ReminderType[] = [
|
||
ReminderType.NOTICE_PERIOD,
|
||
ReminderType.REAL_OPTION,
|
||
ReminderType.UNREAL_OPTION,
|
||
ReminderType.NEGOTIATION_WINDOW,
|
||
ReminderType.CONTRACT_TALK,
|
||
]
|
||
|
||
export const ReminderPriority = {
|
||
URGENT: 'URGENT', // ≤14 days
|
||
HIGH: 'HIGH', // 15–30 days
|
||
MEDIUM: 'MEDIUM', // 31–60 days
|
||
LOW: 'LOW', // >60 days
|
||
} as const
|
||
export type ReminderPriority = typeof ReminderPriority[keyof typeof ReminderPriority]
|
||
|
||
export const ReminderStatus = {
|
||
ACTIVE: 'ACTIVE',
|
||
SNOOZED: 'SNOOZED',
|
||
COMPLETED: 'COMPLETED',
|
||
DISMISSED: 'DISMISSED',
|
||
} as const
|
||
export type ReminderStatus = typeof ReminderStatus[keyof typeof ReminderStatus]
|
||
|
||
export const ShadowMarketRisk = {
|
||
NONE: 'NONE',
|
||
LOW: 'LOW',
|
||
MEDIUM: 'MEDIUM',
|
||
HIGH: 'HIGH',
|
||
} as const
|
||
export type ShadowMarketRisk = typeof ShadowMarketRisk[keyof typeof ShadowMarketRisk]
|
||
|
||
export interface ReminderActivity {
|
||
at: string // ISO date
|
||
by: string // user display name
|
||
action: 'CREATED' | 'SNOOZED' | 'COMPLETED' | 'DISMISSED' | 'NOTED'
|
||
note?: string
|
||
}
|
||
|
||
export interface Reminder {
|
||
id: string
|
||
type: ReminderType
|
||
priority: ReminderPriority
|
||
status: ReminderStatus
|
||
|
||
propertyId: string
|
||
propertyTitle: string
|
||
propertyCity: string
|
||
propertyDistrict?: string
|
||
tenantName: string
|
||
|
||
dueDate: string // ISO date — when action must be taken
|
||
eventDate: string // ISO date — contract event date (expiry / review / etc.)
|
||
contractEndDate?: string // ISO date — lease end
|
||
breakOptionDate?: string // ISO date
|
||
|
||
areaSqm: number
|
||
currentRentPerSqm: number
|
||
currency: 'CHF'
|
||
|
||
shadowMarketRisk: ShadowMarketRisk
|
||
schattenmarktEnabled: boolean
|
||
|
||
note?: string
|
||
snoozedUntil?: string // ISO date
|
||
|
||
// ── Contract Intelligence (Runde 8, §4) ────────────────────────────────────
|
||
|
||
/**
|
||
* Der Zeitpunkt, an dem tatsächlich entschieden werden muss.
|
||
*
|
||
* Bewusst getrennt von `contractEndDate`: Ferdi orientiert sich nicht am
|
||
* Vertragsende, sondern am früheren Entscheidungs-, Kündigungs-, Options-
|
||
* oder Verhandlungszeitpunkt. Wer erst am Vertragsende erinnert, erinnert an
|
||
* etwas, das nicht mehr zu ändern ist.
|
||
*/
|
||
decisionDate?: string
|
||
/** Kündigungsfrist in Monaten, wie im Vertrag vereinbart. */
|
||
noticePeriodMonths?: number
|
||
/** Bei unechten Optionen: ab wann verhandelt werden sollte. */
|
||
negotiationWindowFrom?: string
|
||
/** Bei unechten Optionen: bis wann eine Einigung stehen sollte. */
|
||
negotiationWindowTo?: string
|
||
/** Bezeichnung des Vertrags oder Nachtrags, aus dem der Termin stammt. */
|
||
contractRef?: string
|
||
/** Fundstelle im Dokument — Ziffer, Artikel oder Seite. */
|
||
contractClause?: string
|
||
/** Warum dieser Zeitpunkt relevant ist, in einem Satz. */
|
||
relevanceReason?: string
|
||
/** Die empfohlene nächste Handlung — kein Fazit, ein Auftrag. */
|
||
recommendedAction?: string
|
||
|
||
activity: ReminderActivity[]
|
||
|
||
organizationId: string
|
||
createdAt: string
|
||
updatedAt: string
|
||
}
|