Skip to content

Latest commit

 

History

History
490 lines (393 loc) · 24.9 KB

File metadata and controls

490 lines (393 loc) · 24.9 KB

ImmoShark

Version TypeScript Bun React Tailwind CSS Express SQLite Tests OpenAI License: MIT

Lokale Webanwendung zur Verwaltung von Immobiliendaten. Importiere Objekte per CSV, durchsuche den Bestand mit Volltextsuche und pflege alle Daten per CRUD-Oberfläche — alles läuft lokal, ohne Cloud-Abhängigkeiten.

ImmoShark Dashboard


Inhaltsverzeichnis


Zweck

ImmoShark richtet sich an Immobilienmakler, die eine schlanke, lokale Lösung zum Verwalten ihres Bestands brauchen:

  • CSV-Import mit KI-Mapping — Bestehende Daten aus Tabellenkalkulationen übernehmen (deutsche Formate: ;-Trennzeichen, Dezimalkomma). GPT-5 erkennt unbekannte Spaltenformate automatisch und extrahiert strukturierte Daten aus Freitext-Spalten
  • Volltextsuche — Über alle Textfelder suchen (Adressen, Beschreibungen, Kontaktdaten, Notizen, Status u.v.m.)
  • CRUD-Verwaltung — Immobilien anlegen, bearbeiten, löschen mit Validierung
  • Dashboard — Bestand auf einen Blick: Statistiken, Schnellsuche, letzte Objekte
  • Filterbare Liste — Nach Typ, Status, Ort, Preis, Fläche, Zimmerzahl und Datum filtern
  • Import-Profile — Spalten-Mapping als benanntes Profil speichern und bei zukünftigen Imports laden. Default-Profil wird automatisch angewendet
  • Einstellungen — KI-Mapping an/aus, Import-Profile verwalten, Versionsinformation im Header

Features

Feature Beschreibung
Sortierbare Spalten 8 Tabellenspalten per Klick sortierbar (aufsteigend → absteigend → unsortiert), inkl. "Hinzugefügt am"
Schieberegler-Filter Preis, Fläche und Zimmeranzahl per Slider oder Direkteingabe filtern
Datumsbereich-Filter "Hinzugefügt von/bis" mit nativen Datepickern filtern
Veröffentlichungsdatum Optionales Feld pro Objekt: wann wurde die Immobilie im Portal/in der Zeitung veröffentlicht
Such-Button Filter werden lokal aufgebaut und erst beim Klick auf "Suchen" oder Enter ausgelöst
Notizen Freitextfeld (max. 500 Zeichen) für interne Notizen pro Objekt
Kontakt-Gruppierung Objekte nach Ansprechpartner gruppiert anzeigen
Volltextsuche FTS5-Suche über 13 Textfelder + LIKE-Fallback für numerische Felder
CSV-Import 4-Schritt-Wizard: Upload → Spalten-Mapping (mit Auto-Erkennung + Profilen) → Vorschau → Import. Erkennt dt. Datumsformat (TT.MM.JJJJ)
Import-Profile Spalten-Mapping + KI-Einstellung als benanntes Profil speichern. Profile laden, überschreiben, löschen. Default-Profil wird beim Upload automatisch angewendet
KI-Spalten-Mapping GPT-5 analysiert CSV-Headers und Beispieldaten, um unbekannte Spaltenformate automatisch den 21 Datenbankfeldern zuzuordnen. Per Toggle ein-/ausschaltbar
Freitext-Extraktion (KI) GPT-5 extrahiert strukturierte Immobiliendaten (Adresse, Preis, Kontakt etc.) aus Fließtext-Spalten. Direkte Mappings haben Vorrang vor LLM-extrahierten Werten
Telefonnormalisierung Deutsche Telefonnummern werden beim Import automatisch in das Format +49 VORWAHL NUMMER konvertiert (0681/12345 → +49 681 12345)
Ortsnamen-Auflösung Regionale Kfz-Kürzel (SB, SLS, HOM, NK, MZG, WND, IGB) werden automatisch zu vollen Stadtnamen aufgelöst
Einstellungen Globale Einstellungen (KI-Mapping Default, Import-Profile verwalten) + Versionsanzeige im Header
URL-basierte Filter Alle Filterparameter in der URL — bookmarkbar, teilbar

Architektur

ImmoShark ist ein Bun-Monorepo mit drei Paketen:

immoshark/
├── shared/          @immoshark/shared — Types, Enums, Zod-Validierung
├── server/          @immoshark/server — Express 5 REST-API
├── client/          @immoshark/client — React 19 SPA
└── data/            Beispiel-CSV + Generator-Script

Datenfluss

Browser (React SPA)
    │
    │  fetch /api/*
    ▼
Vite Dev Proxy (:5173 → :3002)
    │
    ▼
Express API (:3002)
    │  Zod-Validierung
    │  Service Layer (raw SQL)
    ├──────────────────────▶ OpenAI API (GPT-5)
    │                        CSV-Spalten-Mapping +
    │                        Freitext-Extraktion
    ▼
SQLite (WAL-Modus, FTS5)
    └── data/immoshark.db

Tech-Stack

Schicht Technologie Warum
Runtime Bun Native SQLite-Bindings, TypeScript ohne Compile-Step, schneller Paketmanager
API Express 5 Bewährt, großes Ökosystem, einfaches Routing
Datenbank SQLite (WAL + FTS5) Zero-Config, eingebettet, Volltextsuche ohne externen Service
Validierung Zod Shared zwischen Client und Server, Runtime-Validierung mit Typ-Inferenz
Frontend React 19 + Vite Schnelles HMR, modernes JSX-Transform
Styling Tailwind CSS 4 Utility-First, kein separates Config-File nötig (v4 Vite Plugin)
KI-Mapping OpenAI GPT-5 Spalten-Mapping + Freitext-Extraktion beim CSV-Import (optional, abschaltbar)
Tests bun test Built-in Test-Runner, zero-config, keine Extra-Dependencies

Designentscheidungen

  • Kein ORM — Raw SQL via bun:sqlite ist schneller und expliziter. Das Mapping zwischen camelCase (TypeScript) und snake_case (SQL) findet im Service-Layer statt.
  • FTS5 mit unicode61 Tokenizer — Deutsche Umlaute (ä, ö, ü, ß) werden korrekt tokenisiert. Sync-Triggers halten den FTS-Index automatisch aktuell. Numerische Felder (Preis, Baujahr etc.) werden zusätzlich per LIKE-Fallback durchsucht.
  • URL-basierte Filter — Alle Filterparameter liegen in der URL. Das macht Filter bookmarkbar und braucht keinen globalen State-Manager.
  • SQL-Injection-Schutz bei Sortierung — Sortierbare Spalten werden gegen eine Whitelist validiert, da ORDER BY-Spalten nicht über parametrisierte Queries geschützt werden können.
  • Bilder als separate Tabelle — Statt JSON-Array in der Immobilien-Tabelle. Ermöglicht Sortierung und zukünftige Erweiterungen.
  • Additive Migration — Bestehende Datenbanken werden automatisch erweitert (ALTER TABLE, FTS-Index-Rebuild), ohne Datenverlust.
  • Deutsches CSV-Format — Automatische Erkennung von ; vs. , Delimiter, Dezimalkomma-Konvertierung (1.234,561234.56), deutsches Datumsformat (TT.MM.JJJJYYYY-MM-DD), Telefonnormalisierung (+49-Format) und Kfz-Kürzel-Auflösung (SB → Saarbrücken).
  • Test-Seam per setDb() — Minimaler Injection-Point, damit Tests eine In-Memory-DB nutzen können, ohne Service-Code zu ändern.
  • LLM-Mapping mit Dependency Injection — Der LLMCaller-Typ entkoppelt den Mapping-Service vom OpenAI-SDK. Tests injizieren einen Mock-Caller, Produktion nutzt GPT-5. Fallback auf Dictionary-Mapping bei KI-Fehler — kein harter Fehler für den User.
  • Freitext-Extraktion mit Batch-Verarbeitung — Fließtext-Spalten werden in Batches von 5 Zeilen an GPT-5 gesendet. Direkte Spalten-Mappings überschreiben LLM-extrahierte Werte (Merge-Logik), sodass der User volle Kontrolle behält.
  • Daten-Normalisierung — Telefonnummern werden automatisch in ein einheitliches +49-Format konvertiert, regionale Kfz-Kürzel zu vollen Stadtnamen aufgelöst. Beides läuft als Post-Processing nach dem Import-Merge.

Voraussetzungen

Tool Version Zweck Installation
Git >= 2.x Versionskontrolle git-scm.com oder brew install git
Bun >= 1.1 Runtime, Paketmanager, SQLite bun.sh
OpenAI API Key KI-Spalten-Mapping (optional) platform.openai.com

Bun ersetzt Node.js, npm und einen separaten TypeScript-Compiler. Es bringt native SQLite-Bindings mit — dadurch entfällt eine separate SQLite-Installation.

KI-Mapping: Für das GPT-5-basierte Spalten-Mapping muss ein OPENAI_API_KEY in der .env-Datei hinterlegt sein. Ohne Key funktioniert das Dictionary-basierte Auto-Mapping weiterhin — das KI-Feature ist optional.

Hinweis: Vite und TypeScript werden als Projekt-Abhängigkeiten installiert (nicht global nötig). Docker wird für die Entwicklung nicht benötigt.

Plattform-spezifisch

macOS:

# Xcode Command Line Tools (für Git)
xcode-select --install

# Bun
curl -fsSL https://bun.sh/install | bash

Linux (Ubuntu/Debian):

sudo apt update && sudo apt install -y git curl unzip
curl -fsSL https://bun.sh/install | bash

Windows (WSL2):

# In WSL2-Terminal:
curl -fsSL https://bun.sh/install | bash

Installation & Start

# 1. Repository klonen
git clone https://github.com/pekiti/immoshark.git
cd immoshark

# 2. Abhängigkeiten installieren
bun install

# 3. Testdaten laden (500 Immobilien)
bun run seed

# Optional: 500 Testdaten generieren (erzeugt data/beispiel-immobilien.csv)
bun data/generate-csv.ts

# 4. Entwicklungsserver starten
bun run dev

Das startet zwei Prozesse:

  • API-Server auf http://localhost:3002 (Express + SQLite)
  • Frontend auf http://localhost:5173 (Vite mit API-Proxy)

Öffne http://localhost:5173 im Browser.

Einzelne Dienste starten

bun run dev:server    # Nur API (Port 3002, mit --watch)
bun run dev:client    # Nur Frontend (Port 5173)

Production-Build

bun run build         # Baut das Frontend nach client/dist/

Tests

ImmoShark hat eine umfassende Test-Suite mit 137 automatisierten Tests. Alle Tests laufen mit bun test (Built-in, zero-config, keine extra Dependencies).

bun test              # Alle Tests (~150ms)
bun test:unit         # Nur Unit-Tests (Validation, Utils, CSV, Services)
bun test:integration  # Nur Integration-Tests (HTTP API, CSV-Flow)
bun test:smoke        # Nur Smoke-Tests (Migration, Health)

Test-Übersicht

Suite Datei Tests Prüft
Smoke server/src/__tests__/smoke/smoke.test.ts 4 Migration, Idempotenz, FTS-Triggers, Health
Unit shared/src/__tests__/validation.test.ts 21 Zod-Schemas: Create, Update, Filter, CSV-Mapping
Unit client/src/__tests__/utils.test.ts 15 formatPreis, formatFlaeche, typLabel, statusLabel, statusColor
Unit client/src/__tests__/settings.test.ts 9 Profil-CRUD: getProfiles, saveProfile, deleteProfile, getDefaultProfile, Default-Invariante
Unit server/src/__tests__/unit/csv-parsing.test.ts 22 CSV-Parsing, dt. Zahlen/Datum, Validierungsfehler, Telefonnormalisierung, Ortsauflösung
Unit server/src/__tests__/unit/mapping.test.ts 9 LLM-Mapping + Freitext-Extraktion: valides Mapping, Batching, Fehlerbehandlung
Unit server/src/__tests__/unit/immobilien-service.test.ts 21 Alle 7 Service-Funktionen (CRUD, Filter, Stats)
Integration server/src/__tests__/integration/api.test.ts 16 HTTP CRUD, Filter, FTS-Suche, Stats-Endpoint
Integration server/src/__tests__/integration/csv.test.ts 8 Upload → Import Flow, Suggest-Mapping, Freitext-Mapping, Fehlerfälle
137

Teststrategie

  • DB-Isolation: Jede Test-Suite bekommt eine frische In-Memory-SQLite-DB via setDb()
  • HTTP-Tests: Express wird auf einem zufälligen Port gestartet (app.listen(0)) + fetch()
  • Keine Mocks: Tests laufen gegen echte DB und echte Middleware — kein Mocking nötig
  • Shared Helpers: server/src/__tests__/helpers.ts enthält createTestDb(), makeImmobilie(), seedTestData(), createTestServer()

Detaillierte Informationen: Tester-Dokumentation


Daten-Schema

immobilien — Haupttabelle

Alle Immobilienobjekte mit Adresse, Kennzahlen, Energieausweis und Kontaktdaten.

Spalte Typ Pflicht Beschreibung
id INTEGER PK Auto-Increment Primärschlüssel
strasse TEXT ja Straßenname
hausnummer TEXT ja Hausnummer (inkl. Zusatz wie "12a")
plz TEXT ja 5-stellige Postleitzahl
ort TEXT ja Stadt / Gemeinde
preis REAL Kaufpreis in Euro. NULL = "Preis auf Anfrage"
wohnflaeche REAL Wohnfläche in m²
grundstuecksflaeche REAL Grundstücksfläche in m²
zimmeranzahl REAL Anzahl Zimmer (REAL für Werte wie 2.5)
typ TEXT ja Objekttyp: wohnung, haus, grundstueck, gewerbe
baujahr INTEGER Baujahr (1800–aktuell+5)
beschreibung TEXT Freitext-Beschreibung des Objekts
provision TEXT Provisionsinformation (z.B. "3,57% inkl. MwSt.")
energieausweis_klasse TEXT Energieeffizienzklasse: A+, A, BH
energieausweis_verbrauch REAL Energieverbrauch in kWh/m²a
kontakt_name TEXT Ansprechpartner
kontakt_telefon TEXT Telefonnummer
kontakt_email TEXT E-Mail-Adresse
expose_nummer TEXT Eindeutige Exposé-Nummer (UNIQUE)
notizen TEXT Interne Notizen (max. 500 Zeichen)
veroeffentlicht TEXT Veröffentlichungsdatum (ISO YYYY-MM-DD). Wann das Objekt im Portal/in der Zeitung veröffentlicht wurde
status TEXT ja Objektstatus: verfuegbar, reserviert, verkauft
erstellt_am TEXT auto ISO-Timestamp, gesetzt bei INSERT
aktualisiert_am TEXT auto ISO-Timestamp, aktualisiert bei UPDATE

Indizes: ort, plz, typ, status, preis, kontakt_name

immobilien_bilder — Bildreferenzen

Spalte Typ Pflicht Beschreibung
id INTEGER PK Auto-Increment
immobilie_id INTEGER FK Referenz auf immobilien.id (ON DELETE CASCADE)
url TEXT ja Bild-URL oder Dateipfad
beschreibung TEXT Alt-Text / Bildbeschreibung
reihenfolge INTEGER ja Sortierungsreihenfolge (Default: 0)

immobilien_fts — Volltextsuche (FTS5)

Virtueller FTS5-Index über 13 Text-Spalten der immobilien-Tabelle. Wird automatisch durch Triggers synchronisiert (INSERT, UPDATE, DELETE).

Indizierte Felder: strasse, hausnummer, plz, ort, beschreibung, kontakt_name, kontakt_telefon, kontakt_email, expose_nummer, notizen, provision, typ, status

Tokenizer: unicode61 — unterstützt deutsche Umlaute und diakritische Zeichen.

ER-Diagramm

┌──────────────────────┐        ┌──────────────────────┐
│     immobilien       │        │  immobilien_bilder   │
├──────────────────────┤        ├──────────────────────┤
│ id             PK    │──1:N──▶│ id             PK    │
│ strasse              │        │ immobilie_id   FK    │
│ hausnummer           │        │ url                  │
│ plz                  │        │ beschreibung         │
│ ort                  │        │ reihenfolge          │
│ preis                │        └──────────────────────┘
│ wohnflaeche          │
│ grundstuecksflaeche  │        ┌──────────────────────┐
│ zimmeranzahl         │        │   immobilien_fts     │
│ typ                  │        │ (FTS5 Virtual Table) │
│ baujahr              │        ├──────────────────────┤
│ beschreibung         │──sync─▶│ strasse              │
│ provision            │  via   │ hausnummer           │
│ energieausweis_*     │triggers│ plz, ort             │
│ kontakt_*            │        │ beschreibung         │
│ expose_nummer  UQ    │        │ kontakt_name         │
│ notizen              │        │ kontakt_telefon      │
│ veroeffentlicht      │        │ kontakt_email        │
│ status               │        │ expose_nummer        │
│ erstellt_am          │        │ notizen, provision   │
│ aktualisiert_am      │        │ typ, status          │
└──────────────────────┘        │                      │
                                └──────────────────────┘

API-Endpunkte

Methode Pfad Beschreibung
GET /api/health Health-Check
GET /api/immobilien Liste (paginiert, filterbar, sortierbar, FTS-Suche)
GET /api/immobilien/:id Detailansicht inkl. Bilder
POST /api/immobilien Neues Objekt anlegen
PUT /api/immobilien/:id Objekt aktualisieren
DELETE /api/immobilien/:id Objekt löschen
GET /api/stats Dashboard-Statistiken
POST /api/csv/upload CSV hochladen (gibt Headers + Vorschau zurück)
POST /api/csv/suggest-mapping KI-Spalten-Mapping via GPT-5 (optional)
POST /api/csv/import CSV importieren mit Spalten-Mapping

Filter-Parameter für GET /api/immobilien

Parameter Typ Beispiel Beschreibung
suche string ?suche=München FTS5-Volltextsuche über alle Textfelder (Prefix-Matching)
typ enum ?typ=wohnung Objekttyp filtern
status enum ?status=verfuegbar Status filtern
ort string ?ort=Berlin Ort (Teilstring-Suche)
preis_min number ?preis_min=200000 Mindestpreis
preis_max number ?preis_max=500000 Höchstpreis
flaeche_min number ?flaeche_min=60 Mindest-Wohnfläche (m²)
flaeche_max number ?flaeche_max=120 Höchst-Wohnfläche (m²)
zimmer_min number ?zimmer_min=2 Mindest-Zimmeranzahl
zimmer_max number ?zimmer_max=4 Höchst-Zimmeranzahl
erstellt_von string ?erstellt_von=2026-01-01 Hinzugefügt ab Datum (inklusiv, YYYY-MM-DD)
erstellt_bis string ?erstellt_bis=2026-03-31 Hinzugefügt bis Datum (inklusiv, YYYY-MM-DD)
sort_by enum ?sort_by=preis Sortierung nach Spalte (strasse, typ, ort, preis, wohnflaeche, zimmeranzahl, status, baujahr, grundstuecksflaeche, kontakt_name, erstellt_am, aktualisiert_am)
sort_order enum ?sort_order=desc Sortierrichtung: asc (Default) oder desc
gruppe enum ?gruppe=kontakt Ergebnisse nach Kontaktperson gruppieren
seite number ?seite=2 Seitennummer (Default: 1)
limit number ?limit=10 Ergebnisse pro Seite (Default: 20, Max: 100)

Response-Format

Erfolg (Einzel):

{ "data": { "id": 1, "strasse": "Musterstraße", "..." : "..." } }

Erfolg (Liste mit Pagination):

{ "data": [...], "meta": { "seite": 1, "limit": 20, "gesamt": 42 } }

Fehler:

{ "error": { "message": "Beschreibung", "code": "VALIDATION_ERROR" } }

Projektstruktur

immoshark/
├── package.json                  Workspace-Root, Scripts
├── tsconfig.base.json            Gemeinsame TypeScript-Konfiguration
├── shared/src/
│   ├── types.ts                  Interfaces, Enums, DTOs
│   ├── validation.ts             Zod-Schemas (Client + Server)
│   └── index.ts                  Re-Export
├── server/src/
│   ├── index.ts                  Server-Einstiegspunkt
│   ├── app.ts                    Express-Setup, Middleware, Routing
│   ├── db/
│   │   ├── database.ts           SQLite-Singleton (WAL, Foreign Keys, setDb)
│   │   ├── migrate.ts            Schema-Migration (Tabellen, FTS5, Triggers)
│   │   └── seed.ts               500 Beispiel-Immobilien
│   ├── routes/
│   │   ├── immobilien.ts         CRUD + Stats Endpoints
│   │   └── csv.ts                CSV Upload + Import + Suggest-Mapping
│   ├── services/
│   │   ├── immobilien.service.ts Datenbank-Queries, Filter, Sortierung, FTS
│   │   ├── csv.service.ts        CSV-Parsing, Freitext-Extraktion, Normalisierung,
│   │   │                         dt. Zahlen-/Datumsformat, Telefon, Ortskürzel
│   │   └── mapping.service.ts    LLM-Mapping via GPT-5 (DI-fähig)
│   ├── middleware/
│   │   ├── error.ts              Globaler Error-Handler
│   │   └── validate.ts           Zod-Validierungs-Middleware
│   └── __tests__/
│       ├── helpers.ts            Test-Infrastruktur (DB, Server, Seed, Fixtures)
│       ├── smoke/                Migrations- und Health-Tests
│       ├── unit/                 Service- und CSV-Parsing-Tests
│       └── integration/          HTTP-API- und CSV-Flow-Tests
├── client/
│   ├── index.html                SPA-Einstieg
│   ├── vite.config.ts            Vite + Tailwind v4 + API-Proxy
│   └── src/
│       ├── main.tsx              React-Root mit Router + Toast-Provider
│       ├── App.tsx               Route-Definitionen
│       ├── api/client.ts         Typisierter Fetch-Wrapper
│       ├── hooks/                useImmobilien, useImmobilie
│       ├── pages/                Dashboard, Liste, Detail, Form, CSV,
│       │                         Settings, 404
│       ├── components/
│       │   ├── layout/           Sidebar, Header, Layout
│       │   ├── immobilien/       Table, FilterBar, StatusBadge
│       │   └── ui/               Button, Input, Select, Modal, Pagination,
│       │                         Toast, RangeSlider
│       ├── lib/
│       ├── utils.ts              Formatierung (Preis, Fläche, Labels)
│       └── settings.ts           localStorage-Helper + Profil-CRUD
│       └── __tests__/            Client-Tests (Utils, Settings/Profile-CRUD)
├── data/
│   ├── beispiel-immobilien.csv   500 Beispiel-Immobilien (dt. CSV-Format)
│   └── generate-csv.ts           Generator-Script für realistische Testdaten
└── docs/
    └── stakeholder/              Rollenspezifische Dokumentation

Dokumentation

Stakeholder-Dokumentation

Rollenspezifische Dokumente — jedes zugeschnitten auf die Informationsbedürfnisse der jeweiligen Zielgruppe:

Dokument Zielgruppe Inhalt
Benutzeranleitung Endanwender Bedienung mit Screenshots: Dashboard, Liste, Detail, Formular, CSV-Import
Projektmanager Projektmanagement Geschäftswert, Status, Risiken, Meilensteine
Backend-Entwickler Backend-Entwicklung Architektur, DB-Patterns, Service-Layer, Middleware, Konventionen
Frontend-Entwickler Frontend-Entwicklung Komponenten, Hooks, API-Client, Vite-Config, Styling
Tester / QA Testing Test-Suiten, Helfer, manuelle Testszenarien, Edge Cases
Ops / DevOps Betrieb / Deployment Systemanforderungen, Prozess-Management, Backup, Monitoring, Troubleshooting

Lizenz

MIT