# NEG AG — Exposé Automation Pipeline
### Complete System Architecture

---

## 1. HIGH-LEVEL OVERVIEW

```mermaid
flowchart TB
    subgraph INPUT["📁 INPUT — Client Uploads"]
        OD["🔵 OneDrive Folder<br/><i>Client dumps files here</i><br/>Images, PDFs, Documents, CAD files<br/><b>No naming convention. No structure.</b>"]
    end

    subgraph PHASE1["⚙️ PHASE 1 — Automated Ingestion & AI Analysis"]
        direction TB
        W1["🔄 n8n Workflow<br/><i>Watches OneDrive folder</i>"]
        W2["📂 File Discovery<br/>& Inventory"]
        W3["🔀 Format Normalization<br/><i>HEIC→JPG, PDF→Pages, etc.</i>"]
        W4["🤖 AI Vision Classification<br/><i>GPT-4o analyzes every image</i>"]
        W5["📄 Text Extraction<br/><i>Azure OCR + Document Parsing</i>"]
        W6["📍 Location Intelligence<br/><i>Geocoding + Nearby POIs + Maps</i>"]
        W7["🧠 AI Section Assembly<br/><i>Decides which sections to include</i>"]
        W8["💾 Store Everything in DB<br/><i>NocoDB — full audit trail</i>"]

        W1 --> W2 --> W3 --> W4
        W3 --> W5
        W4 --> W7
        W5 --> W7
        W5 --> W6 --> W7
        W7 --> W8
    end

    subgraph PHASE2["👤 PHASE 2 — Human Review & Editing"]
        direction TB
        R1["🖥️ React Form<br/><i>Section-based editor</i>"]
        R2["✅ Complete missing data<br/>🔀 Reorder sections<br/>🖼️ Reassign images<br/>✏️ Edit AI-generated text"]
        R3["👍 APPROVE Button"]
        R1 --> R2 --> R3
    end

    subgraph PHASE3["📄 PHASE 3 — PDF Generation & Delivery"]
        direction TB
        P1["🔗 Webhook Triggered"]
        P2["🏗️ Dynamic HTML Assembly<br/><i>Component-based builder</i>"]
        P3["📄 HTML → PDF<br/><i>Gotenberg (Headless Chrome)</i>"]
        P4["☁️ Upload to OneDrive"]
        P5["📧 Email Notification<br/><i>PDF attached</i>"]
        P1 --> P2 --> P3 --> P4
        P3 --> P5
    end

    INPUT --> PHASE1
    PHASE1 -->|"📧 Notification:<br/>Draft ready for review"| PHASE2
    PHASE2 -->|"Webhook"| PHASE3

    style INPUT fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
    style PHASE1 fill:#fff3e0,stroke:#e65100,stroke-width:2px
    style PHASE2 fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    style PHASE3 fill:#f3e5f5,stroke:#6a1b9a,stroke-width:2px
```

---

## 2. PHASE 1 — DETAILED FILE PROCESSING PIPELINE

```mermaid
flowchart TD
    START(("🔵 OneDrive<br/>Folder Change<br/>Detected"))

    subgraph DISCOVERY["Step 0: File Discovery"]
        D1["Scan OneDrive folder<br/><i>recursive</i>"]
        D2["Create FileRegistry record<br/>for EVERY file found"]
        D3["Log: original filename,<br/>format, size, OneDrive ID"]
        D1 --> D2 --> D3
    end

    subgraph NORMALIZE["Step 1: Format Normalization"]
        N1{"What format<br/>is the file?"}
        N_IMG["✅ JPG/PNG/WEBP/GIF/BMP<br/><i>Ready — no conversion</i>"]
        N_HEIC["📱 HEIC/HEIF/TIFF<br/><i>Convert → JPG</i>"]
        N_PDF1["📄 PDF (1 page)<br/><i>Render → PNG</i>"]
        N_PDFM["📄 PDF (multi-page)<br/><i>Split into pages,<br/>render each → PNG</i>"]
        N_DOC["📝 DOCX/XLSX/PPTX<br/><i>Extract text +<br/>render to images</i>"]
        N_CAD["⚠️ DWG/DXF (CAD)<br/><i>Flag: unprocessable<br/>Needs manual upload as PDF</i>"]
        N_ZIP["📦 ZIP/RAR<br/><i>Extract contents,<br/>process each file</i>"]
        N_FAIL["❌ Corrupt/Unknown<br/><i>Flag in DB,<br/>show in React form</i>"]

        N1 -->|"jpg,png,webp"| N_IMG
        N1 -->|"heic,tiff"| N_HEIC
        N1 -->|"pdf 1pg"| N_PDF1
        N1 -->|"pdf multi"| N_PDFM
        N1 -->|"docx,xlsx"| N_DOC
        N1 -->|"dwg,dxf"| N_CAD
        N1 -->|"zip,rar"| N_ZIP
        N1 -->|"corrupt"| N_FAIL
    end

    subgraph PARALLEL["Step 2: AI Analysis (Parallel Tracks)"]
        direction LR
        subgraph VISION["Track A: AI Vision"]
            V1["Send image to<br/>GPT-4o Vision"]
            V2["Classify:<br/>• content_type<br/>• subject<br/>• temporal era<br/>• quality score<br/>• hero candidate?<br/>• detected text<br/>• floor plan level<br/>• map type"]
            V3["Store in<br/>FileClassification<br/>table"]
            V1 --> V2 --> V3
        end

        subgraph OCR["Track B: Text Extraction"]
            T1["Azure Document<br/>Intelligence OCR"]
            T2["Extract:<br/>• Raw text<br/>• Addresses<br/>• Property data<br/>• Numbers & areas<br/>• Dates"]
            T3["Store in<br/>ExtractedText<br/>table"]
            T1 --> T2 --> T3
        end
    end

    subgraph MULTI_PDF["PDF Page Handling"]
        MP1["Parent PDF record<br/>in FileRegistry"]
        MP2["Child record per page<br/>with page_number"]
        MP3["Each page classified<br/>independently"]
        MP1 --> MP2 --> MP3
    end

    START --> DISCOVERY
    DISCOVERY --> NORMALIZE
    N_IMG --> PARALLEL
    N_HEIC --> PARALLEL
    N_PDF1 --> PARALLEL
    N_DOC --> PARALLEL
    N_PDFM --> MULTI_PDF --> PARALLEL
    N_CAD --> DB_FLAG[("💾 DB: status =<br/>unprocessable")]
    N_FAIL --> DB_FLAG

    style START fill:#1565c0,color:#fff
    style DB_FLAG fill:#ffcdd2,stroke:#c62828
```

---

## 3. PHASE 1 — LOCATION INTELLIGENCE & SECTION ASSEMBLY

```mermaid
flowchart TD
    subgraph LOC["Step 3: Location Intelligence"]
        L1["Extract address from<br/>AI Vision + OCR results"]
        L2{"Address<br/>found?"}
        L3["Geocode → lat/lng<br/><i>Google Geocoding API</i>"]
        L4["Search Makrolage POIs<br/><i>5-15km radius</i><br/>Major employers, airports,<br/>highways, exhibition centers"]
        L5["Search Mikrolage POIs<br/><i>0-5km radius</i><br/>Train stations, city center,<br/>parks, local landmarks"]
        L6["Calculate distances<br/><i>Google Distance Matrix</i>"]
        L7["Fetch POI photos<br/><i>Google Places Photos</i>"]
        L8["Generate map images<br/><i>Google Static Maps API<br/>with numbered markers<br/>+ red connection lines</i>"]
        L9["⚠️ Flag: address missing<br/><i>Human must enter in form</i>"]

        L1 --> L2
        L2 -->|"Yes"| L3 --> L4
        L3 --> L5
        L4 --> L6
        L5 --> L6
        L6 --> L7 --> L8
        L2 -->|"No"| L9
    end

    subgraph STATS["Step 4: City & Market Data"]
        S1["AI generates<br/>Standort/City stats page<br/><i>Population, students,<br/>employers, growth</i>"]
        S2{"Property type<br/>needs market<br/>analysis?"}
        S3["AI generates<br/>market analysis<br/><i>e.g. Hotelmarkt Leipzig</i>"]
        S4["Skip market analysis"]
        S1 --> S2
        S2 -->|"Hotel, Office, etc."| S3
        S2 -->|"No"| S4
    end

    subgraph ASSEMBLY["Step 5: AI Section Assembly"]
        A1["AI reviews ALL classified<br/>files + extracted data"]
        A2["Decides which section types<br/>to include based on<br/>available content"]
        A3["Assigns files to sections<br/><i>Hero images, gallery photos,<br/>floor plans, maps</i>"]
        A4["Orders sections<br/><i>Cover → KeyFacts → Maps →<br/>Location → Stats → Images →<br/>Floor Plans → Contact</i>"]
        A5["Flags missing data<br/><i>Per-field confidence scoring</i>"]
        A6["💾 Store all in NocoDB<br/><i>SectionPlan +<br/>SectionFileAssignment +<br/>ExposeCompleteness</i>"]

        A1 --> A2 --> A3 --> A4 --> A5 --> A6
    end

    LOC --> ASSEMBLY
    STATS --> ASSEMBLY

    ASSEMBLY -->|"📧 Email notification"| NOTIFY["📧 Draft ready<br/>for review"]

    style NOTIFY fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
```

---

## 4. SECTION TYPE REGISTRY — What AI Can Choose From

```mermaid
flowchart LR
    subgraph ALWAYS["✅ Always Included"]
        SEC_COVER["📄 Cover<br/><i>Hero image + title</i>"]
        SEC_KF["📋 Key-Facts<br/><i>Property data table</i>"]
        SEC_CONTACT["👤 Contact<br/><i>NEG AG disclaimer<br/>+ Jürgen Wilms</i>"]
    end

    subgraph LOCATION["📍 Location (if address known)"]
        SEC_MAPS["🗺️ Lagekarte +<br/>Flurkarte"]
        SEC_MAKRO["🌍 Makrolage<br/><i>5-15km POIs</i>"]
        SEC_MIKRO["📍 Mikrolage<br/><i>0-5km POIs</i>"]
        SEC_CITY["🏙️ Standort Stats<br/><i>City data page</i>"]
    end

    subgraph CONDITIONAL["🔀 Conditional (based on available files)"]
        SEC_PO["🏗️ Projektübersicht<br/><i>If renderings exist</i>"]
        SEC_MARKET["📊 Market Analysis<br/><i>If property type<br/>warrants it</i>"]
        SEC_BILDER["🖼️ Image Gallery<br/><i>If photos exist</i>"]
        SEC_COMPARE["🔄 Image Comparison<br/><i>If historical +<br/>current + future<br/>images exist</i>"]
        SEC_AKTUELLE["📸 Aktuelle Bilder<br/><i>If drone/current<br/>photos exist</i>"]
        SEC_GRUND_B["📐 Grundrisse Bestand<br/><i>If existing floor<br/>plans uploaded</i>"]
        SEC_GRUND_P["📐 Planungsentwurf<br/><i>If proposed floor<br/>plans uploaded</i>"]
        SEC_TABLE["📊 Data Tables<br/><i>If structured data<br/>like room counts</i>"]
        SEC_TEXT["📝 Text Content<br/><i>If descriptive<br/>text needed</i>"]
    end

    style ALWAYS fill:#c8e6c9,stroke:#2e7d32
    style LOCATION fill:#bbdefb,stroke:#1565c0
    style CONDITIONAL fill:#fff9c4,stroke:#f57f17
```

---

## 5. PHASE 2 — REACT FORM (Human Review)

```mermaid
flowchart TD
    OPEN["User opens React app<br/><i>Notification link</i>"]

    subgraph FORM["🖥️ Section-Based Exposé Editor"]
        direction TB

        STATUS["📊 Status Bar<br/><b>12/15 fields complete · 23 files processed · 2 need attention</b>"]

        subgraph ALERTS["⚠️ Attention Needed"]
            AL1["⚠️ DSC00847.dwg<br/><i>CAD file — needs<br/>manual PDF upload</i>"]
            AL2["⚠️ WhatsApp Image.jpeg<br/><i>Unclassified (34%)<br/>Please assign manually</i>"]
        end

        subgraph DATA["📋 Property Data Editor"]
            DF1["🟢 Anschrift: <i>extracted</i>"]
            DF2["🟡 Grundstücksgröße: <i>AI inferred (72%)</i>"]
            DF3["🔴 Kaufpreis: <i>MISSING</i>"]
            DF4["🔵 Baujahr: 1913 <i>human verified</i>"]
        end

        subgraph SECTIONS["📐 Sections (drag to reorder)"]
            S1["[1] ✅ Cover"]
            S2["[2] ⚠️ Key-Facts <i>3 fields missing</i>"]
            S3["[3] ✅ Lagekarte + Flurkarte"]
            S4["[4] ✅ Makrolage"]
            S5["[5] ✅ Mikrolage"]
            S6["[6] ✅ Standort Leipzig"]
            S7["[7] ✅ Bilder <i>4 photos</i>"]
            S8["[8] ✅ Grundrisse Bestand"]
            S9["[9] ✅ Contact"]
            ADD["[+ Add Section ▼]"]
        end

        subgraph UNASSIGNED["🖼️ Unassigned Files"]
            U1["thumbnail + AI label<br/><i>drag to assign</i>"]
        end

        APPROVE["👍 APPROVE & GENERATE PDF"]

        STATUS --> ALERTS --> DATA --> SECTIONS --> UNASSIGNED --> APPROVE
    end

    OPEN --> FORM

    subgraph LEGEND["Legend"]
        direction LR
        LEG1["🟢 Extracted<br/>high confidence"]
        LEG2["🟡 AI inferred<br/>low confidence"]
        LEG3["🔴 Missing<br/>human must fill"]
        LEG4["🔵 Human verified<br/>or corrected"]
    end

    style FORM fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    style APPROVE fill:#2e7d32,color:#fff,stroke-width:2px
```

---

## 6. PHASE 3 — PDF GENERATION (Component Assembly)

```mermaid
flowchart TD
    WEBHOOK(("🔗 Webhook<br/>Triggered by<br/>APPROVE"))

    FETCH["Fetch from NocoDB:<br/>• Exposé master data<br/>• All sections (ordered)<br/>• All file assignments<br/>• Location POIs<br/>• Generated maps"]

    subgraph BUILD["🏗️ HTML Assembly Engine"]
        direction TB

        SHELL_START["HTML Shell Start<br/><i>DOCTYPE, head, CSS</i><br/><br/><b>BAKED IN:</b><br/>• NEG AG logo (SVG)<br/>• Color palette<br/>• Typography<br/>• All component CSS<br/>• Print styles"]

        LOOP{"For each section<br/>in order..."}

        subgraph COMPONENTS["Component Renderer (switch)"]
            C_COVER["renderCover()<br/><i>Baked: header bar, logo,<br/>footer bar, 'EXPOSÉ'<br/>Dynamic: hero image,<br/>title, address</i>"]
            C_KF["renderKeyFacts()<br/><i>Baked: table styling<br/>Dynamic: fact rows</i>"]
            C_MAPS["renderMapsDual()<br/><i>Baked: header bars<br/>Dynamic: map images</i>"]
            C_MAKRO["renderLocationMacro()<br/><i>Baked: layout, footer<br/>Dynamic: map, POIs,<br/>photos, distances</i>"]
            C_MIKRO["renderLocationMicro()<br/><i>Same as Makro<br/>different color scheme</i>"]
            C_CITY["renderCityStats()<br/><i>Baked: layout<br/>Dynamic: stats, text,<br/>city photo</i>"]
            C_MARKET["renderMarketAnalysis()<br/><i>Baked: layout<br/>Dynamic: stats, text</i>"]
            C_GALLERY["renderImageGallery()<br/><i>Baked: header<br/>Dynamic: images,<br/>auto-layout based<br/>on image count</i>"]
            C_COMPARE["renderImageComparison()<br/><i>Baked: layout<br/>Dynamic: images +<br/>era labels</i>"]
            C_FLOORS["renderFloorPlans()<br/><i>Baked: headers<br/>Dynamic: plan images,<br/>level labels,<br/>auto-pagination</i>"]
            C_TABLE["renderDataTable()<br/><i>Baked: styling<br/>Dynamic: table data</i>"]
            C_CONTACT["renderContact()<br/><i>100% BAKED IN<br/>Zero dynamic data</i>"]
        end

        SHELL_END["HTML Shell End<br/><i>Close body, html</i>"]

        SHELL_START --> LOOP
        LOOP --> COMPONENTS
        COMPONENTS --> LOOP
        LOOP -->|"All sections<br/>rendered"| SHELL_END
    end

    subgraph OUTPUT["📤 Output & Delivery"]
        PDF["📄 Gotenberg<br/><i>HTML → PDF<br/>Headless Chrome</i>"]
        UPLOAD["☁️ Upload PDF<br/>to OneDrive"]
        EMAIL["📧 Email to team<br/><i>PDF attached</i>"]
        DB_UPDATE["💾 Update NocoDB<br/><i>status = Completed<br/>pdf_url = OneDrive link</i>"]
    end

    WEBHOOK --> FETCH --> BUILD
    BUILD --> PDF --> UPLOAD --> DB_UPDATE
    PDF --> EMAIL

    style WEBHOOK fill:#6a1b9a,color:#fff
    style C_CONTACT fill:#c8e6c9,stroke:#2e7d32
    style PDF fill:#f3e5f5,stroke:#6a1b9a
```

---

## 7. DATABASE SCHEMA

```mermaid
erDiagram
    Exposes ||--o{ FileRegistry : "has files"
    Exposes ||--o{ SectionPlan : "has sections"
    Exposes ||--o{ ExposeCompleteness : "has field status"
    Exposes ||--o| LocationData : "has location"
    Exposes ||--o{ ProcessingLog : "has logs"
    Exposes ||--o{ ExposeHistory : "has audit trail"

    FileRegistry ||--o| FileClassification : "AI classification"
    FileRegistry ||--o| ExtractedText : "OCR results"
    FileRegistry ||--o{ FileRegistry : "PDF pages (parent→children)"

    SectionPlan ||--o{ SectionFileAssignment : "files in section"
    SectionFileAssignment }o--|| FileRegistry : "references file"

    LocationData ||--o{ LocationPOI : "nearby POIs"
    LocationData ||--o{ GeneratedMap : "map images"

    Exposes {
        int id PK
        string title
        string property_id
        string address
        string status "pending|ingesting|classifying|assembling|awaiting_review|approved|generating_pdf|completed"
        int ai_confidence_score
        boolean auto_generated
        string onedrive_source_path
        string pdf_url
        timestamp created_at
        timestamp updated_at
    }

    FileRegistry {
        int id PK
        int expose_id FK
        int parent_file_id FK "nullable — for PDF pages"
        int page_number "nullable"
        string original_filename
        string original_format
        int file_size_bytes
        string onedrive_file_id
        string normalized_format
        string normalized_file_url
        boolean is_processable
        string processing_status "pending|converting|ready_for_ai|classified|failed"
        string failure_reason "nullable"
    }

    FileClassification {
        int id PK
        int file_registry_id FK
        string content_type "photograph|floor_plan|site_map|rendering|etc"
        string subject "building_exterior|room_kitchen|etc"
        string temporal "historical|current|future_planned"
        int quality_score "1-10"
        boolean is_hero_candidate
        json quality_issues "array"
        json detected_text "array"
        string detected_address "nullable"
        string floor_plan_level "KG|EG|1OG|DG|nullable"
        float confidence "0.0-1.0"
        string ai_notes
    }

    ExtractedText {
        int id PK
        int file_registry_id FK
        string extraction_method "azure_ocr|pdf_text|docx_parse"
        text raw_text
        json extracted_fields "address, sqm, price, etc"
        float extraction_confidence
    }

    SectionPlan {
        int id PK
        int expose_id FK
        string section_type "cover|key_facts|location_macro|etc"
        string section_heading
        int section_order
        json content_json "all data the component needs"
        json missing_fields "what human must fill"
        string status "ai_draft|human_edited|approved"
        float ai_confidence
    }

    SectionFileAssignment {
        int id PK
        int section_plan_id FK
        int file_registry_id FK
        string role "hero|sub|full_width|left|right"
        int display_order
        string caption
        boolean human_overridden
    }

    ExposeCompleteness {
        int id PK
        int expose_id FK
        string field_name "kaufpreis|baujahr|etc"
        string data_source "extracted|ai_inferred|missing"
        string value "nullable"
        float confidence
        boolean human_verified
        string human_value "nullable"
    }

    LocationData {
        int id PK
        int expose_id FK
        float latitude
        float longitude
        string geocode_source
    }

    LocationPOI {
        int id PK
        int expose_id FK
        string poi_name
        string poi_type "employer|transport|education|leisure"
        float distance_km
        string range "makrolage|mikrolage"
        string photo_url
        int display_order
    }

    GeneratedMap {
        int id PK
        int expose_id FK
        string map_type "makrolage|mikrolage"
        string map_image_url
    }

    ProcessingLog {
        int id PK
        int expose_id FK
        string step_name
        string status "started|completed|failed|retrying"
        string error_message "nullable"
        int duration_ms
        int retry_count
        timestamp created_at
    }

    ExposeHistory {
        int id PK
        int expose_id FK
        string changed_by "system|ai|human"
        string change_type
        string field_name
        string old_value
        string new_value
        timestamp changed_at
    }
```

---

## 8. ERROR HANDLING & RESILIENCE

```mermaid
flowchart TD
    subgraph ERRORS["🛡️ Error Handling at Every Step"]
        direction TB

        E1["❌ File download fails"]
        E1R["→ Retry 3x with backoff<br/>→ After 3 fails: flag in DB<br/>→ Show in React form<br/>→ Pipeline continues with other files"]

        E2["❌ Format conversion fails<br/><i>(corrupt HEIC, broken PDF)</i>"]
        E2R["→ Try alternative converter<br/>→ If all fail: mark unprocessable<br/>→ Pipeline continues"]

        E3["❌ GPT-4o Vision timeout"]
        E3R["→ Retry 3x<br/>→ If fails: mark as<br/>'needs_manual_classification'<br/>→ Human classifies in form"]

        E4["❌ GPT-4o returns bad JSON"]
        E4R["→ Retry with stricter prompt<br/>→ If fails 2x: set defaults<br/>+ low confidence score"]

        E5["❌ Google Geocoding fails"]
        E5R["→ Fallback to OpenStreetMap<br/>→ If both fail: flag address<br/>as unresolvable<br/>→ Human enters manually"]

        E6["❌ PDF generation fails"]
        E6R["→ Retry<br/>→ If fails: provide HTML<br/>download as alternative"]

        E1 --> E1R
        E2 --> E2R
        E3 --> E3R
        E4 --> E4R
        E5 --> E5R
        E6 --> E6R
    end

    GOLDEN["🏆 GOLDEN RULE:<br/><b>Nothing stops the pipeline.<br/>Bad files get flagged.<br/>Good files continue.<br/>Human sees everything in the form —<br/>both successes and failures.</b>"]

    ERRORS --> GOLDEN

    style GOLDEN fill:#fff9c4,stroke:#f57f17,stroke-width:3px
    style ERRORS fill:#ffebee,stroke:#c62828
```

---

## 9. PROCESSING STATUS FLOW

```mermaid
stateDiagram-v2
    [*] --> pending: New folder detected

    state "Exposé Status" as expose_status {
        pending --> ingesting: Files found
        ingesting --> classifying: All files inventoried
        classifying --> assembling: AI classification complete
        assembling --> awaiting_review: Sections assembled
        awaiting_review --> approved: Human clicks APPROVE
        awaiting_review --> awaiting_review: Human edits & saves
        approved --> generating_pdf: Webhook fired
        generating_pdf --> completed: PDF created & delivered
        generating_pdf --> approved: PDF generation failed (retry)
    }

    state "File Status" as file_status {
        f_pending: pending
        f_converting: converting
        f_ready: ready_for_ai
        f_processing: ai_processing
        f_classified: classified
        f_assigned: assigned_to_section
        f_failed: failed
        f_manual: needs_manual_classification

        f_pending --> f_converting: Needs format conversion
        f_pending --> f_ready: Already image format
        f_converting --> f_ready: Conversion success
        f_converting --> f_failed: Conversion failed
        f_ready --> f_processing: Sent to GPT-4o
        f_processing --> f_classified: AI response received
        f_processing --> f_failed: AI timeout/error
        f_failed --> f_manual: After 3 retries
        f_classified --> f_assigned: Section assembly
        f_manual --> f_assigned: Human classifies in form
    }
```

---

## 10. TECHNOLOGY STACK

```mermaid
flowchart LR
    subgraph ORCHESTRATION["⚙️ Orchestration"]
        N8N["n8n<br/><i>Workflow automation</i>"]
    end

    subgraph AI["🤖 AI Services"]
        GPT["OpenAI GPT-4o<br/><i>Vision classification<br/>+ Content generation</i>"]
        AZURE["Azure Document<br/>Intelligence<br/><i>OCR</i>"]
    end

    subgraph LOCATION_APIS["📍 Location"]
        GOOGLE_GEO["Google Geocoding"]
        GOOGLE_PLACES["Google Places API"]
        GOOGLE_MAPS["Google Static Maps"]
        GOOGLE_DIST["Google Distance Matrix"]
    end

    subgraph DATA["💾 Data"]
        NOCODB["NocoDB<br/><i>Database</i>"]
    end

    subgraph FRONTEND["🖥️ Frontend"]
        REACT["React App<br/><i>Section editor form</i>"]
    end

    subgraph PDF["📄 PDF Generation"]
        GOTENBERG["Gotenberg<br/><i>Headless Chrome<br/>HTML → PDF</i>"]
    end

    subgraph STORAGE["☁️ Storage & Comms"]
        ONEDRIVE["Microsoft OneDrive<br/><i>File storage</i>"]
        OUTLOOK["Microsoft Outlook<br/><i>Email notifications</i>"]
    end

    N8N --> GPT
    N8N --> AZURE
    N8N --> GOOGLE_GEO
    N8N --> GOOGLE_PLACES
    N8N --> GOOGLE_MAPS
    N8N --> GOOGLE_DIST
    N8N --> NOCODB
    N8N --> GOTENBERG
    N8N --> ONEDRIVE
    N8N --> OUTLOOK
    REACT --> NOCODB
    REACT -->|"Webhook"| N8N

    style N8N fill:#ff6d00,color:#fff
    style GPT fill:#10a37f,color:#fff
    style REACT fill:#61dafb,color:#000
    style NOCODB fill:#4b5563,color:#fff
```

---

## 11. COST PER EXPOSÉ

```mermaid
pie title Estimated Cost Per Exposé (~€1-3 total)
    "OpenAI GPT-4o (Vision + Text)" : 60
    "Azure OCR" : 5
    "Google APIs (Maps + Places)" : 20
    "Gotenberg (self-hosted)" : 0
    "n8n (self-hosted)" : 0
    "NocoDB (self-hosted)" : 0
    "OneDrive (existing license)" : 0
    "Infrastructure overhead" : 15
```

---

## 12. WHAT'S BAKED IN vs DYNAMIC

```mermaid
flowchart LR
    subgraph BAKED["🔒 Baked Into HTML (Never Changes)"]
        B1["NEG AG Logo (SVG)"]
        B2["Diagonal Stripe Pattern"]
        B3["Color Palette CSS"]
        B4["Typography"]
        B5["Section Header Styles"]
        B6["Key-Facts Table Styling"]
        B7["Cover Header + Footer Bar"]
        B8["Location Footer Bars"]
        B9["Contact Page (100% static)"]
        B10["Jürgen Wilms Photo (Base64)"]
        B11["'MIETEN I BAUEN I KAUFEN'"]
        B12["Print/PDF CSS"]
        B13["All Component Layout CSS"]
    end

    subgraph DYNAMIC["🔄 Dynamic (From Database)"]
        D1["Hero Image"]
        D2["Title / Subtitle / Address"]
        D3["Key-Facts Row Data"]
        D4["Which Sections Exist"]
        D5["Section Ordering"]
        D6["Property Photos"]
        D7["Floor Plan Images"]
        D8["Generated Map Images"]
        D9["POI Names + Distances"]
        D10["POI Photos"]
        D11["City Stats Numbers"]
        D12["Market Analysis Text"]
        D13["Table Data"]
    end

    BAKED -->|"~70%"| FINAL["📄 Final HTML"]
    DYNAMIC -->|"~30%"| FINAL

    style BAKED fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
    style DYNAMIC fill:#bbdefb,stroke:#1565c0,stroke-width:2px
    style FINAL fill:#f3e5f5,stroke:#6a1b9a,stroke-width:2px
```

---

*Document generated for NEG AG · Exposé Automation Pipeline · February 2026*
