Files
Messe-Lotse/plan/architecture-vertical-slices-1.0.md
2026-09-29 09:40:32 +00:00

51 KiB
Raw Permalink Blame History

goal, version, date_created, last_updated, owner, status, tags
goal version date_created last_updated owner status tags
Vertical Slice Implementation Plan for 24HRS Messe-Lotse (AI-Optimized) 1.0 2026-06-10 2026-06-10 24HRS Team Planned architecture, vertical-slices, ai-optimized, migration

Introduction

Status: Planned

This plan supersedes architecture-rewrite-1.0.md by re-scoping the 12 sequential phases into 6 AI-sized vertical slices. Each slice delivers a complete, independently shippable user capability across all layers (Pimcore data model → API → React UI → auth). No slice requires a later slice to function. Context: zero developers, AI-only execution. Scale: ~300 jobs/year, peaks at 113 jobs per fair.

1. Requirements & Constraints

Inherited from architecture-rewrite-1.0.md

  • REQ-001: Multi-user with OAuth2 authentication (Pimcore password grant)
  • REQ-002: Four roles — Admin, Editor, Viewer, Customer — with scoped visibility
  • REQ-003: All file uploads stored in Pimcore DAM (not base64 in localStorage)
  • REQ-004: PDF export via Gotenberg (already running)
  • REQ-005: Frontend served from Pimcore domain (no CORS)
  • REQ-006: Feature parity with index.html mockup (8 tabs + dashboard + calendar)
  • REQ-007: Symfony Workflow-driven phase progression (8-state state machine)
  • REQ-008: Normalized 4-entity data model (fairLocation → fair → job ← customer)
  • REQ-009: Date inheritance (job overrides nullable, fall back to fair dates)
  • REQ-010: Fair Detail view — primary grouping of jobs
  • REQ-011: Resource pool management (11 entities, deferred to Slice 5)
  • REQ-012: AI document processing skills (deferred to Slice 4)
  • REQ-013: Tiered document storage (customer/fair/job levels)
  • CON-001: Must run in existing Docker Compose environment
  • CON-002: No legacy data migration needed (greenfield)
  • CON-003: Pimcore ≥ 11.x, Gotenberg, OAuth2 already in place
  • SEC-001: Booking platform passwords stored encrypted (Pimcore encrypted field type)
  • SEC-002: All API routes protected by OAuth2, role-checked per endpoint
  • GUD-001: React 18 + TypeScript + Vite + TanStack Query + Zustand + shadcn/ui

Slice-Specific Constraints

  • CON-SLICE-001: Each slice must produce working, testable, demonstrable software
  • CON-SLICE-002: No slice may depend on a later slice for core functionality
  • CON-SLICE-003: Slices 1-3 deliver minimal viable product; Slices 4-6 add intelligence/automation
  • CON-SLICE-004: All AI-generated code must be verifiable via Playwright E2E tests per slice
  • CON-SLICE-005: Timestamped slice completion tracking per attached date column

Decisions from Architecture Review (2026-06-10)

  • DEC-001: SketchUp 3D bridge is async (message queue worker), not synchronous HTTP call from Pimcore controller. Job-level mutual exclusion prevents duplicate generations. Health monitoring required for SketchUp + MCP availability.
  • DEC-002: Workflow must support admin "force transition" with justification log. Rigid guards alone are insufficient for real-world trade fair prep entropy. Card created: INVESTIGATE workflow guard flexibility.
  • DEC-003: Tiered document model must degrade gracefully when no brandbooks or multi-fair docs exist. If a customer has no brandDoc, the UI shows nothing collapsed — no empty inheritance indicators.
  • DEC-004: LLM operational cost and accuracy risk require investigation before Slice 4 begins. Card created: INVESTIGATE monthly LLM cost at full scale (10,000+ calls/year) and INVESTIGATE SetupProgressAnalyzer auto-check confidence floor.

2. Implementation Steps


Slice 1 — Create and manage a trade fair

  • GOAL-001: Full-stack capability to authenticate, create venues/customers/fairs, and view them on a dashboard. Proves the entire technology stack works end-to-end: Pimcore data objects, OAuth2, REST API, React SPA, role-based access.
Task Description Completed Date
TASK-001 Scaffold MessebauBundle in bundles/MessebauBundle/ with Symfony bundle class, services.yaml, routes.yaml
TASK-002 Create Data Object FairLocation (venue) — text: name, address, googleMapsUrl; contact: contactName, contactPhone, contactEmail; textarea: notes
TASK-003 Create Data Object Customer (company) — text: company, address; contact: contactName, contactPhone, contactEmail; asset: brandDoc (document, nullable), consultationDocs (gallery, nullable), contractDocs (gallery, nullable); textarea: notes
TASK-004 Create Data Object Fair (trade fair) — text: name, hallsUsed; relation: fairLocation (1→1 to FairLocation); datetime: setupStart, setupEnd, eventStart, eventEnd, teardownStart, teardownEnd; asset: constructionBriefing (gallery), floorPlan (gallery), regulations (gallery), protocols (gallery); textarea: notes
TASK-005 Create FairLocationController — GET /api/messebau/fairlocations (list), GET/POST/PUT/DELETE /api/messebau/fairlocations/{id}
TASK-006 Create CustomerController — GET /api/messebau/customers (list, searchable by company name), GET/POST/PUT/DELETE /api/messebau/customers/{id}
TASK-007 Create FairController — GET /api/messebau/fairs (list, filterable by fairLocation, date range), GET/POST/PUT/DELETE /api/messebau/fairs/{id}
TASK-008 Create AssetController (generalized) — POST /api/messebau/{entity}/{id}/upload/{fieldName} handles customer and fair entity uploads; DELETE /api/messebau/{entity}/{id}/asset/{fieldName}
TASK-009 Configure OAuth2 firewall — protect /api/messebau/* with pimcore.api firewall; define 4 roles: ROLE_MESSEBAU_CUSTOMER, ROLE_MESSEBAU_VIEWER, ROLE_MESSEBAU_EDITOR, ROLE_MESSEBAU_ADMIN; Customer role auto-filters by user.messebauCustomer relation; add messebauCustomer field to Pimcore User class
TASK-010 Scaffold Vite + React + TypeScript in frontend/ with Tailwind CSS v3, shadcn/ui, react-router-dom, TanStack Query, Zustand, Axios
TASK-011 Configure vite.config.ts — build output to ../public/build/messebau/, base path /build/messebau/, proxy /api to Pimcore in dev mode
TASK-012 Create src/api/client.ts — Axios instance, request interceptor for Bearer token, response interceptor for 401 → refresh token → retry; implement refresh token queue
TASK-013 Create src/stores/authStore.ts — Zustand: accessToken, refreshToken, user (id, username, roles), login/logout/refresh, persist to sessionStorage
TASK-014 Create src/stores/uiStore.ts — Zustand: currentView, sidebarCollapsed, toastQueue
TASK-015 Create src/hooks/usePermissions.ts — reads role from authStore, returns boolean flags: canEdit, canDelete, canCreateJob, canManageResources, isCustomer, etc.
TASK-016 Create src/components/auth/LoginPage.tsx — username + password form, calls authStore.login(), error display, redirect on success
TASK-017 Create src/components/auth/ProtectedRoute.tsx — checks auth token, redirects to /login if missing
TASK-018 Create src/components/layout/AppShell.tsx — flex: Sidebar (260px) + main (flex-1, 32px padding)
TASK-019 Create src/components/layout/Sidebar.tsx — "24HRS" logo + "Messe-Lotse" subtitle (yellow on black); nav items: Dashboard, Kalender (all roles); logout at bottom; role-gated nav items reserved for later slices
TASK-020 Create src/components/dashboard/FairCard.tsx — card per fair: name, location name, date range, job count placeholder (0); click navigates to /fair/{id}
TASK-021 Create src/components/dashboard/FairCardGrid.tsx — responsive grid of FairCards; search bar filters by name or location
TASK-022 Create src/components/dashboard/EmptyState.tsx — internal roles: "Keine Messen vorhanden" + create button; customer: "Keine Projekte vorhanden" (no create button)
TASK-023 Create src/components/fair/CreateFairFlow.tsx — wizard: Step 1: select or create FairLocation (combobox), Step 2: enter fair details (name, dates, halls), Step 3: confirm
TASK-024 Create src/components/fair/FairDetailView.tsx — fair header (name, location, dates), stats placeholder (0 jobs), "Neuen Job hinzufügen" button (gated by usePermissions)
TASK-025 Create src/App.tsx — routes: /login → LoginPage, / → ProtectedRoute → AppShell → Dashboard, /fair/:id → FairDetailView, /fair/new → CreateFairFlow, 404
TASK-026 Create Pimcore controller MessebauController::appAction() — serves public/build/messebau/index.html via Twig; catch-all route for SPA routing
TASK-027 Write Playwright test slice-1-auth-create-fair.spec.ts — login as editor, create fairLocation, create fair, verify fair appears on dashboard, logout; login as customer, verify dashboard loads (empty), deny access to /fair/new
TASK-028 Write Vitest tests: authStore.test.ts (login, logout, token refresh), usePermissions.test.ts (role → flag mapping)

Slice 2 — Add jobs to a fair

  • GOAL-002: Create and view booth projects (jobs) linked to customers and fairs. File upload works via Pimcore DAM. Basic master data editing with role scoping. Proves entity relations, asset management, and role-gated UI.
Task Description Completed Date
TASK-101 Create Data Object Job (booth project) — core entity: standType (select: Systemstand/Reihstand/Eckstand/Kopfstand/Blockstand/Inselstand), standSize, standHeight, hall, area, project; relations: customer (n→1 to Customer), fair (n→1 to Fair); date overrides: setupStartOverride, setupEndOverride, eventStartOverride, eventEndOverride, teardownStartOverride, teardownEndOverride (all nullable — if null, inherit from fair); workflowState (input, managed by Symfony Workflow); textarea: notes
TASK-102 Create Data Object ChecklistItem — text, checked (boolean), order (integer); 1:n relation to Job
TASK-103 Create Data Object Material — type (select: flooring/standsystem/hardware/furniture), name, quantity (integer), notes; 1:n relation to Job
TASK-104 Create field collections for Job: BookingPlatformInfo (portalLink, email, password[encrypted], deadline), PowerInfo (kw, distributor, deadline), WlanInfo (type, deadline), CleaningInfo (deadline), RiggingInfo (supplierFirstname, supplierLastname, supplierEmail, supplierPhone, messeFirstname, messeLastname, messeEmail, messePhone, deadline), DruckproduktionInfo (qm, blockout, forexplatten, deadline), CateringInfo (baristaEnabled, baristaPackage, days, drinksEnabled, drinksDetails, foodEnabled, foodDetails), CostEstimate (standConstruction, activation, furniture, logistics — all integer EUR)
TASK-105 Add asset fields to Job: standImage (image), standGallery (image gallery), briefingCustomerDoc (document, nullable — if null, inherits from customer), briefingPlanDoc (document, nullable — if null, inherits from fair), briefingAdditionalDoc (document), powerConfirmation (document), wlanConfirmation (document), cleaningConfirmation (document), riggingQuote (document), riggingDrawing (document), riggingMesseConfirmation (document), standApprovalDoc (document), acceptancePhotos (image gallery), acceptanceSignature (image), weeklyProtocols (document gallery)
TASK-106 Create JobController — GET /api/messebau/jobs (paginated, filterable by fairId/customerId/workflowState/search), GET /api/messebau/jobs/{id} (full serialization with relations + asset URLs + resolved effectiveDates), POST /api/messebau/jobs (create — requires customer and fair relation IDs, initializes default checklist items), PUT /api/messebau/jobs/{id}, PATCH /api/messebau/jobs/{id}, DELETE /api/messebau/jobs/{id}. Customer role auto-filtered to own company's jobs; 403 on DELETE
TASK-107 Extend AssetController to handle job entity uploads via existing generalized endpoint
TASK-108 Extend FairController — GET /api/messebau/fairs/{id}/jobs lists all jobs for this fair (the "todo" view); fair serialization includes job count
TASK-109 Create src/types/job.ts — TypeScript interfaces for all entities and field collections from TASK-101 through TASK-105
TASK-110 Create src/api/fairLocations.ts + src/api/customers.ts + src/api/fairs.ts + src/api/jobs.ts — typed API functions for all CRUD operations
TASK-111 Create src/hooks/useFairs.ts + src/hooks/useJobs.ts + src/hooks/useJob.ts + src/hooks/useFair.ts — TanStack Query hooks
TASK-112 Create src/components/job/CreateJobFlow.tsx — wizard: Step 1: select or create Customer (combobox with search), Step 2: select Fair (combobox grouped by fairLocation), Step 3: enter booth details (project, standType, standSize, hall). Navigate to job detail on creation
TASK-113 Create src/components/job/JobCard.tsx — stand image, customer company name, fair name, hall, workflow progress placeholder; click navigates to /jobs/{id}
TASK-114 Create src/components/job/JobCardGrid.tsx — responsive grid, maps jobs to JobCards
TASK-115 Update FairDetailView — show JobCardGrid filtered to fair; "Neuen Job hinzufügen" button triggers CreateJobFlow (gated by usePermissions: true for editor/admin, false for customer/viewer); customer sees their own jobs only
TASK-116 Create src/components/job-detail/DetailHeader.tsx — black bar: breadcrumb (fair.name → customer.company → job.project), back button; action buttons gated by usePermissions: save (editor/admin), duplicate (editor/admin), export PDF (all), delete (admin); customer: save + export only
TASK-117 Create src/components/job-detail/TabContainer.tsx — dynamic tab list based on role: internal sees all 8 tabs; customer sees 4 tabs (Tab 0, 1, 4, 5+6); active tab in search params (?tab=0); inactive tabs hidden, not just disabled
TASK-118 Create src/components/job-detail/tabs/MasterDataTab.tsx — two-column form using react-hook-form + zod: entity selectors (Customer + Fair read-only, with Google Maps link for fairLocation), Stammdaten (Projekt, Standart, Standgröße, Standhöhe, Halle/Stand, Standfläche — all editable by editor/admin, read-only for viewer, select fields editable by customer), Zeitplanung (fair dates shown read-only as reference; editor/admin sees override fields with inherited defaults; customer sees resolved effective dates read-only), Status (read-only badge), Messestand-Bild (file input, image preview, download/remove), Notizen (full-width textarea)
TASK-119 Create src/components/job-detail/tabs/DokumenteTab.tsx — checklist with 8 default items from index.html prototype; checkbox + strikethrough; editor/admin can add/remove items; customer can only check/uncheck existing items (usePermissions.canAddChecklistItems)
TASK-120 Write Playwright test slice-2-jobs-crud.spec.ts — login as editor, open fair detail, create job for customer A at that fair, verify job appears in fair detail grid, open job detail, edit master data fields, save, reload, verify persistence; login as customer A, verify only own jobs visible, verify edit fields limited to select fields, verify delete button absent
TASK-121 Write Vitest tests: useJobs.test.ts (fetch, filter, pagination), MasterDataTab.test.tsx (form validation, inherited dates display)

Slice 3 — Complete a job's documents and workflow

  • GOAL-003: Full job lifecycle with all 8 tabs operational, Symfony Workflow with manual transitions, file uploads for all tab fields, and PDF export via Gotenberg. At this point the system is a complete replacement for the index.html prototype.

Workflow note per DEC-002: Guards are implemented as described in TASK-202, but an admin-only POST /api/messebau/jobs/{id}/workflow/{transition}?force=true&reason=... endpoint is provided. Force transitions log the admin user + reason to transition history. This is a safety valve for the gap between ideal workflow and real-world fair prep.

Task Description Completed Date
TASK-201 Create Symfony Workflow config in bundles/MessebauBundle/Resources/config/workflows/job.yml — 8 places (angebot, planung, organisation, produktion, aufbau, messe, abbau, abgeschlossen); initial marking: angebot; transitions with guards: angebot→planung requires customer+fair relations, planung→organisation requires briefingCustomerDoc uploaded, organisation→produktion requires power/wlan/cleaning deadlines set if their checkboxes enabled, produktion→aufbau requires all checklist items checked, abbau→abgeschlossen requires acceptanceSignature+acceptanceDate set; date-based transitions (aufbau→messe, messe→abbau) auto-fire via cron checking effective dates
TASK-202 Create WorkflowController — GET /api/messebau/jobs/{id}/workflow returns current place, available transitions (with guard status: allowed/blocked/reason), transition history; POST /api/messebau/jobs/{id}/workflow/{transition} applies manual transition; POST ...?force=true&reason=... (admin only) bypasses guards, logs justification to transition history
TASK-203 Create WorkflowTransitionListener — on workflow.enter: log timestamp to transition history (JSON array); on workflow.enter.aufbau: auto-generate PDF export for setup team; on workflow.enter.messe: send customer email with booth details; on workflow.enter.abgeschlossen: mark job as read-only
TASK-204 Create src/components/job-detail/tabs/BriefingTab.tsx — 3 collapsible sections with scope badges: "☰ Kunde" (customer.brandDoc, customer.consultationDocs, customer.contractDocs), "🌐 Messe" (fair.constructionBriefing, fair.floorPlan, fair.regulations, fair.protocols), "📋 Job" (job.briefingCustomerDoc override with "inherited from customer" indicator if null, job.briefingPlanDoc override with "inherited from fair" indicator if null, job.briefingAdditionalDoc). Each section has upload area. Follows DEC-003: empty levels show nothing collapsed — no empty inheritance indicators if parent has no docs
TASK-205 Create src/components/job-detail/tabs/OrganisationTab.tsx — collapsible sections for Buchungsplattform, Strombuchung, WLAN, Reinigungsservice, Rigging/Truss, Standfreigabe; each section has enable checkbox + fields + file upload with preview; resource summary (read-only placeholder, filled in Slice 5)
TASK-206 Create src/components/job-detail/tabs/ProduktionTab.tsx — material lists (4 sections: Bodenbeläge, Standsystem, Hardware, Möbel) with add/remove; Druckproduktion section (qm, blockout, forexplatten, deadline); Catering section (barista, drinks, food); Leadmagneten section (9 checkboxes)
TASK-207 Create src/components/job-detail/tabs/StandfotosTab.tsx — upload zone (dashed border, drag-and-drop, image/*, multiple, max 10), gallery grid with download + delete, click opens Lightbox
TASK-208 Create src/components/shared/Lightbox.tsx — portal-based (ReactDOM.createPortal), dark overlay, centered image, close button, prev/next arrows, keyboard navigation (arrow keys, escape), click-outside-to-close
TASK-209 Create src/components/job-detail/tabs/StandabnahmeTab.tsx — acceptance photo gallery (separate from Standfotos), signature pad (react-signature-canvas, 600x200, clear + save), remarks textarea, Abnahmedatum (datetime-local)
TASK-210 Create src/components/job-detail/tabs/WeeklyTab.tsx — multi-file upload for PDFs, list of uploaded protocols with filename/size/download/delete, empty state with PDF icon
TASK-211 Create src/components/shared/WorkflowProgress.tsx — horizontal stepper: 8 phases with current/active/complete states, phase icon + label; "Nächster Schritt: [Phase]" button triggers workflow transition; blocked transitions show tooltip with guard reason; force transition button (admin only) with reason text input modal; transition history expandable timeline
TASK-212 Create JobExportService — accepts job ID, loads full data with joined customer/fair/fairLocation, resolves effective dates, renders Twig template (templates/messebau/pdf/export.html.twig), POSTs HTML to Gotenberg, returns PDF binary
TASK-213 Create job PDF export endpoint — GET /api/messebau/jobs/{id}/export-pdf invokes JobExportService, returns PDF with Content-Disposition: attachment
TASK-214 Create templates/messebau/pdf/export.html.twig — print-optimized HTML: 24HRS header, customer + fair + location info grid, stand details, material tables, checklist with checkmarks, acceptance info + signature; CSS @page, yellow accents, Inter font, page-break-inside: avoid
TASK-215 Create CalendarController — GET /api/messebau/calendar?year=&month=&fairId= — aggregate effective dates (override or inherited from fair) plus all deadline fields across jobs; return flat event list with type+color+jobId+fairId
TASK-216 Create src/hooks/useCalendar.ts — TanStack Query hook fetching calendar endpoint; store current month/year/fairId in local state
TASK-217 Create src/components/calendar/CalendarToolbar.tsx — prev/next month buttons, month-year heading, "Heute" button, fair selector dropdown, color legend
TASK-218 Create src/components/calendar/CalendarGrid.tsx — 7-column grid, day headers (Mo-So), correct day cells, leading/trailing grayed out
TASK-219 Create src/components/calendar/CalendarDay.tsx — day number (bold + today border), event pills with color-coded types: setup=#3498db, event=#FFE000, teardown=#e74c3c, deadline=#FF6B35; truncated text with customer+fair name, click navigates to /jobs/{jobId}; span events render on each day in range
TASK-220 Write Playwright test slice-3-full-workflow.spec.ts — create fair+job, upload briefing doc, transition angebot→planung→organisation→produktion, verify guards block (try to skip to aufbau), fill required fields, complete checklist, transition to aufbau, add acceptance photo+signature, transition to abgeschlossen, verify read-only; force-transition from abgeschlossen to abbau (admin override), verify reason logged in history; export PDF, verify download
TASK-221 Write Playwright test slice-3-calendar.spec.ts — create 2 jobs at same fair with different dates, open calendar scoped to that fair, verify events render on correct days, click event pill navigates to job detail

Slice 4 — AI document intelligence

  • GOAL-004: Pluggable AI skills triggered on asset upload to Pimcore DAM. Common chain (Classifier → MismatchDetector → VersionDetector → DocumentScopeDetector) runs on every upload. Field-specific skills extract data and surface non-destructive suggestions. User explicitly applies or ignores each suggestion.

Precondition: INVESTIGATE cards from DEC-004 must be resolved before Slice 4 work begins:

  1. Monthly LLM cost estimate at 10,000+ calls/year
  2. SetupProgressAnalyzer auto-check confidence threshold and false-positive safety measures
Task Description Completed Date
TASK-301 INVESTIGATE: Estimate monthly LLM cost — audit expected call volume per skill type × model pricing per token (gpt-4o-mini for most skills, gpt-4o for LeadMagnetIdeator, gpt-4o vision for SetupProgressAnalyzer + BrandConsistencyChecker); produce budget range; if >500€/month at full scale, recommend cost optimization strategy
TASK-302 INVESTIGATE: Define confidence floor for auto-actions — what confidence threshold prevents SetupProgressAnalyzer from auto-checking checklist items that weren't actually done; propose: auto-check only if confidence ≥ 0.90 AND the corresponding checklist item text semantically matches the detected change by cosine similarity ≥ 0.80; all lower-confidence matches surface as suggestions only
TASK-303 Create SuggestionService in bundles/MessebauBundle/Service/ — stores AI suggestions as JSON field aiSuggestions on Job DO (array of {id, skill, field, suggestedValue, confidence, sourceAssetId, status(pending/applied/ignored), createdAt}); methods: add(), apply() writes to actual field, ignore(); API endpoints: GET /api/messebau/jobs/{id}/suggestions, POST .../suggestions/{suggestionId}/apply, POST .../suggestions/{suggestionId}/ignore
TASK-304 Create DocumentClassifier (SKILL-001) — Symfony service triggered on asset.postAdd; analyzes file extension, MIME type, filename patterns, first 4KB of content; classifies into: briefing, brandbook, floorplan, confirmation, confirmation-quote, stand-photo, acceptance-photo, weekly-protocol, consultation-summary, other. Pure regex + metadata — no LLM
TASK-305 Create DocumentMismatchDetector (SKILL-008) — triggered by common chain; compares classified type against upload field context; if mismatch (e.g. floorplan uploaded to briefing area), returns {suggestedField, reason}; blocks skill chain execution until user resolves (Move / Keep / Cancel)
TASK-306 Create DocumentVersionDetector (SKILL-008a) — triggered if field has ≥1 prior asset; filename Levenshtein distance + content comparison; if newer version detected: diff extracted dimensions/budgets/dates, surface warning with severity (info/warning/critical); user chooses Replace or Keep both
TASK-307 Create DocumentScopeDetector (SKILL-001a) — triggered on briefing-type uploads; detects customer/fair/job scope via heuristics (multi-fair mentions → customer, "alle Stände" + booth counts → fair, single booth + company → job); if scope mismatches upload field, suggests relocation with target entity preview; if user moves: asset relocated, skills re-run on correct entity
TASK-308 Create AiSkillTriggerListener — Symfony event listener on asset.postAdd; runs Classifier + MismatchDetector inline (sync, <5ms); dispatches one AnalyzeDocumentMessage per applicable skill to Symfony Messenger queue ai_skills (Redis or Doctrine transport)
TASK-309 Configure Symfony Messenger — ai_skills transport, multiple messenger:consume workers for parallel skill execution
TASK-310 Create ContactExtractor (SKILL-002) — triggered on briefing/confirmation docs; regex for email/phone → LLM fallback for name/role; output: {name, email, phone, role}[] → targets customer.contactName/Phone/Email
TASK-311 Create DeadlineDetector (SKILL-003 + SKILL-004a) — triggered on any document; regex for date patterns (DD.MM.YYYY, German relative dates, date ranges like "01.–02.07.2026"); field mapping heuristics (Aufbau→setupStart, Strom→power.deadline, etc.); conflict detection if document date differs from stored deadline
TASK-312 Create MultiFairDetector (SKILL-004b) — triggered on briefing/consultation-summary; LLM extraction of fair name, size, standType, location, budget; deduplication against existing fairs; suggestion: "Create N new fair records for customer X?"
TASK-313 Create BudgetExtractor (SKILL-004c) — triggered on any doc with currency amounts; regex for monetary patterns (€/EUR/k€) with context mapping (Standbau→costEstimate.standConstruction, Aktivierung→activation, etc.); fallback to regex-only if no LLM available
TASK-314 Create PainPointSummarizer (SKILL-004d) — triggered on consultation-summary/briefing; LLM extraction of painPoints[], expectations[], structuredNotes; suggestion: append/replace job.notes
TASK-315 Create ChecklistGenerator (SKILL-004) — triggered on briefing; LLM extraction of actionable tasks in German; deduplication against existing checklist items via cosine similarity of text embeddings
TASK-316 Create StandDataExtractor (SKILL-005) — triggered on floorplan; PDF/DWG text layer parsing for dimensions, standType, hall, standNumber; image-based fallback: LLM vision; comparison >10% discrepancy flags warning
TASK-317 Create ConfirmationParser (SKILL-006) — triggered on confirmation; LLM extraction of serviceType, details, confirmationNumber, bookingDate, status; auto-confirm if confirmation number found in text; discrepancy flag if booked values differ from stored
TASK-318 Create BrandConsistencyChecker (SKILL-007) — triggered on 2nd+ stand photo AND brandbook exists; extract brand colors from brandbook, analyze photo color histogram, Delta-E comparison; LLM vision: "Is logo visible? Position? Deviations from guidelines?"; result: green checkmark or amber warning overlay in photo gallery
TASK-319 Create AcceptanceReportGenerator (SKILL-010) — manual trigger only (button on Tab 6); LLM vision: analyze acceptance photos + remarks → structured report in German → Gotenberg PDF; includes photo gallery with annotations, issue summary, sign-off block
TASK-320 Create SetupProgressAnalyzer (SKILL-011) — triggered on 2nd+ stand photo; LLM vision: compare two most recent photos chronologically; detect structural changes (walls erected, flooring laid, graphics mounted, furniture placed, equipment installed, cleaning completed); auto-check mapping per DEC-302 confidence floor; all lower-confidence detections → suggestion only
TASK-321 Create WeeklyProtocolSummarizer (SKILL-012) — triggered on weekly-protocol PDF upload; LLM extract: date, status update, open items, decisions, next steps; renders as structured summary card on Tab 7 above file list
TASK-322 Create MaterialListExtractor (SKILL-009) — triggered on briefing/floorplan; LLM extraction of materials categorized as flooring/standsystem/hardware/furniture with name + suggested quantity
TASK-323 Create LeadMagnetIdeator (SKILL-009a) — triggered on briefing/consultation-summary with activation requirements; LLM (gpt-4o, T=0.7): "Generate 3 creative lead magnet ideas tied to company product, fitting budget, for event type"; context consumed: company description, product info, previous activations, budget constraints, event atmosphere
TASK-324 Create src/components/shared/SuggestionBadge.tsx — yellow notification banner near affected form field; shows suggested value, confidence %, source document name; [Apply] / [Ignore] buttons; per-tab [Apply All]; tab headers show pending suggestion count badge; low-confidence suggestions (< configurable threshold) hidden by default with toggle
TASK-325 Integrate SuggestionBadge into all affected tabs (Tab 0 MasterData, Tab 1 Briefing, Tab 3 Produktion, Tab 4 Checklist, Tab 5 Standfotos, Tab 7 Weekly)
TASK-326 Write Playwright test slice-4-ai-pipeline.spec.ts — upload Brevo consultation doc to briefingCustomerDoc; verify DocumentClassifier identifies it; verify MismatchDetector passes (correct field); verify ContactExtractor surfaces Lisa Reinhardt suggestion; apply it; verify job.contactName updated; upload floor plan to briefingCustomerDoc; verify MismatchDetector suggests move to briefingPlanDoc; move it; verify asset relocated
TASK-327 Write Playwright test slice-4-setup-progress.spec.ts — upload first stand photo; verify no analysis; upload second (different angle, more construction); verify SetupProgressAnalyzer compares images; verify low-confidence matches (<0.90) become suggestions, not auto-checks; verify high-confidence match (>0.90) auto-checks corresponding checklist item

Slice 5 — Fair-level resource planning

  • GOAL-005: Resource pool management with demand planning, worker/vehicle/equipment/accommodation assignment, travel management, and fill-gap visualization at fair level. Replaces per-job ad-hoc team/vehicle/hotel lists from the prototype.
Task Description Completed Date
TASK-401 Create Data Object WorkerRole — name (e.g. Barista, Rigger, LorryDriver, FairWorker, Electrician, Cleaner)
TASK-402 Create Data Object Worker — firstName, lastName, email, phone; role (n→1 to WorkerRole); state (internal/hired/toHire); notes
TASK-403 Create Data Object PoolVehicle — name, type, licensePlate; state (owned/hired/toHire); notes
TASK-404 Create Data Object Equipment — name, description; state (owned/rented/toRent); notes
TASK-405 Create Data Object WorkerDemand — fair (n→1 to Fair, required), job (n→1 to Job, nullable), workerRole (n→1 to WorkerRole, required); quantity (integer); notes
TASK-406 Create Data Object WorkerAssignment — fair (n→1 to Fair, required), job (n→1 to Job, nullable), worker (n→1 to Worker, required); startDate, endDate (datetime); notes
TASK-407 Create Data Object PoolVehicleAssignment — fair, job (nullable), poolVehicle; startDate, endDate; notes
TASK-408 Create Data Object EquipmentAssignment — fair, job (nullable), equipment; startDate, endDate; notes
TASK-409 Create Data Object Accommodation — name, address, bookingReference; type (hotel/apartment/other); notes
TASK-410 Create Data Object AccommodationAssignment — fair, accommodation, worker (nullable); checkIn, checkOut (date); notes. Multiple workers can share same accommodation
TASK-411 Create Data Object Travel — worker (n→1 to Worker, required), fair (n→1 to Fair, required); type (oneWay/roundTrip); origin, destination, transportMethod, bookingReference; departureDateTime, arrivalDateTime; returnDepartureDateTime, returnArrivalDateTime (nullable); notes
TASK-412 Create controllers: WorkerRoleController, WorkerController, PoolVehicleController, EquipmentController, AccommodationController — full CRUD for each pool entity
TASK-413 Create FairResourceController — GET /api/messebau/fairs/{id}/resources returns all demands+assignments grouped by type with gap computed (needed − assigned); POST/PUT/DELETE /api/messebau/fairs/{id}/demands manages demand slots; POST/PUT/DELETE /api/messebau/fairs/{id}/assignments manages worker/vehicle/equipment assignments
TASK-414 Create JobResourceController — GET /api/messebau/jobs/{id}/resources returns demands+assignments scoped to this job
TASK-415 Create FairAccommodationController — GET /api/messebau/fairs/{id}/accommodations returns accommodations + assignments + occupancy; POST/PUT/DELETE /api/messebau/fairs/{id}/accommodations/{accId}/assign
TASK-416 Create WorkerTravelController — GET /api/messebau/workers/{id}/travel (filterable by fairId); POST/PUT/DELETE /api/messebau/workers/{id}/travel
TASK-417 Create src/types/resources.ts — TypeScript interfaces for all 11 resource pool entities
TASK-418 Create src/api/ files for all resource API endpoints (workerroles, workers, poolvehicles, equipment, accommodations, fairResources, jobResources, travel)
TASK-419 Create src/hooks/useFairResources.ts + src/hooks/useJobResources.ts — TanStack Query hooks
TASK-420 Create src/components/fair/FairResourcesTab.tsx — four grids: Workers by Role (role, needed qty, assigned count, gap, assigned workers with job labels, Add Demand / Assign Worker buttons), Trucks (vehicle name, state badge, assigned to job/dates), Equipment (item name, state badge, assigned to job/dates), Accommodation (name, type badge, occupancy, assigned workers with check-in/out, Assign Worker button); modals for add-demand and add-assignment
TASK-421 Create src/components/resources/WorkerPoolView.tsx — list all workers filterable by role+state, CRUD, "Add Worker" form; each worker row shows assigned fairs + travel count
TASK-422 Create src/components/resources/PoolVehicleView.tsx + src/components/resources/EquipmentView.tsx + src/components/resources/AccommodationPoolView.tsx — same CRUD pattern
TASK-423 Create src/components/resources/WorkerDetailView.tsx — worker info + role + state; travel records list (outbound/return info, transport method, booking reference, Add Travel button, one-way toggle); accommodation assignments at each fair; fair assignments
TASK-424 Update Sidebar — add "Ressourcen" nav item (collapsible sub-items: Worker Rollen, Worker Pool, Fahrzeug Pool, Equipment Pool, Unterkünfte); hidden for customer role
TASK-425 Update OrganisationTab (from Slice 3) — replace placeholder resource summary with live data from GET /api/messebau/jobs/{id}/resources; each item shows name + role/type + state badge + dates; "[Manage all resources for this fair →]" links to FairResourcesTab
TASK-426 Write Playwright test slice-5-fair-resources.spec.ts — open fair detail → Resources tab → add demand "3 Baristas" → verify gap=3 → create worker Anna (Barista, internal) → assign Anna → verify assigned=1, gap=2 → assign Anna to specific job → verify job Resources tab shows Anna; remove Anna → verify gap returns to 3
TASK-427 Write Playwright test slice-5-accommodation-travel.spec.ts — create accommodation "Hotel Central" → assign worker Anna (check-in Jun 14, check-out Jun 18) → verify occupancy=1 → add travel for Anna: one-way Berlin→Hannover, Bahn, Jun 14 08:00→10:30 → verify worker detail shows travel + accommodation

Slice 6 — 3D booth generation

  • GOAL-006: Automated 3D booth model generation via SketchUp + Aluvision + MCP bridge. Driven by job data from the 24HRS application. Delivers .skp model, PNG preview, and BOM back to the job.

Architecture note per DEC-001: Pimcore dispatches an async message to generate-3d queue. An MCP server worker pulls the message, invokes the SketchUp Ruby bridge via HTTP, stores results in Pimcore DAM, and updates job fields. Job-level lock prevents duplicate generations. Health endpoint on MCP server enables monitoring.

Precondition: SketchUp Pro with Aluvision 3D Library installed on a dedicated machine. WEBrick bridge extension loaded. MCP server accessible to both Pimcore (for dispatching) and SketchUp bridge (localhost:9494).

Task Description Completed Date
TASK-501 AUDIT: Examine Aluvision 3D Library in SketchUp — catalog component naming conventions, folder structure, parameter patterns; document findings in graphify-out/aluvision-audit.json
TASK-502 Build SketchUp Ruby bridge — aluvision_bridge.rb extension entry point (WEBrick server on port 9494, auto-starts on SketchUp launch via UI.start_timer); implement /health, /component/list, /booth/generate, /booth/export, /booth/bom endpoints
TASK-503 Build AluvisionRegistry — Ruby module mapping logical component names → SketchUp library paths; includes all profile types, connectors, accessories, furniture, flooring as documented in 3d-booth-generation.md § Aluvision Registry Pattern
TASK-504 Build BoothBuilder — Ruby module: LAYOUT_WALLS mapping (6 stand types → wall topology), WALL_POSITIONS (position calculation per side), WALL_SIZES (dimension calculation); clears model, places walls, connectors, flooring, furniture, lead magnets; commits operation, zooms to fit
TASK-505 Build WallBuilder, ConnectorBuilder, FlooringBuilder, FurniturePlacer, StorageBuilder — individual component placement Ruby modules loaded by BoothBuilder
TASK-506 Build BomExtractor — enumerates placed components in active model, maps to Aluvision part numbers, computes totals (component count, profile length, connector count, flooring area); outputs JSON
TASK-507 Build ModelExporter — exports .skp (native save), renders PNG (SketchUp viewport capture), optional .glb/.dae export
TASK-508 Scaffold aluvision-mcp-server/ — Node.js/TypeScript project with @modelcontextprotocol/sdk; implement server.ts entry point with StdioServerTransport
TASK-509 Implement SketchUpClient — HTTP client (fetch wrapper) targeting http://localhost:9494; GET for /health, /component/list, /booth/bom; POST for /booth/generate, /booth/export
TASK-510 Implement LayoutEngine — STAND_TYPE_TO_LAYOUT mapping (6 German standType strings → layout type); parseStandSize ("6x4" → {width:6000, depth:4000}); resolve() returns BoothSpec with layout, width, depth, height, finish
TASK-511 Implement all 12 MCP tool definitions (aluvision_info, aluvision_build_booth, aluvision_create_wall, aluvision_create_corner, aluvision_create_storage, aluvision_create_door, aluvision_add_backlit, aluvision_arrange_layout, aluvision_export_bom, aluvision_export_model, aluvision_export_drawing, aluvision_get_component_info) with parameter schemas in aluvision_tools.ts
TASK-512 Implement tool call handlers — Map each tool name to SketchUpClient method; aluvision_build_booth: resolve layout via LayoutEngine, POST to /booth/generate; aluvision_export_bom: GET /booth/bom; etc.
TASK-513 Create Booth3dService in Pimcore — buildParams(job) collects standType, size, height, flooring, furniture, lead magnets, storage rooms from job; mapStandType (6 German→English layout mapping); getFlooring/getFurniture/getLeadMagnets read from job Materials and field collections
TASK-514 Create GenerateBooth3dMessage — Symfony Messenger message class with jobId, params (serialized BoothSpec); dispatched by Pimcore controller
TASK-515 Create GenerateBooth3dHandler — Symfony Messenger handler: receives message, checks job-level lock (Redis key booth3d:lock:{jobId} with TTL), calls MCP server (spawns node process with stdio), receives result, stores .skp + PNG in Pimcore DAM, updates job.3dModelPath + job.3dPreviewPath + job.3dBom, releases lock
TASK-516 Create Booth3dController — POST /api/messebau/jobs/{id}/generate-3d dispatches GenerateBooth3dMessage; GET /api/messebau/jobs/{id}/3d-status returns generation status (queued/generating/complete/failed) + result paths if complete; GET /api/messebau/jobs/{id}/3d-preview returns PNG; GET /api/messebau/jobs/{id}/3d-model download link for .skp
TASK-517 Add 3D fields to Job DO — 3dModelPath, 3dPreviewPath, 3dBom (JSON); 3dGenerationStatus (queued/generating/complete/failed); 3dLastGenerated (datetime)
TASK-518 Create src/components/job-detail/Generate3dButton.tsx — "3D Booth generieren" button on Tab 0 / DetailHeader; disabled if generation in progress; polling 3d-status every 5s while queued/generating; shows PNG preview in modal on complete; "Regenerieren" button for parameter tweaks; download .skp link; BOM summary alongside preview
TASK-519 Configure MCP server in Docker Compose — aluvision-mcp service linking to SketchUp bridge URL; environment variables for bridge URL and Aluvision library path
TASK-520 Implement MCP server health endpoint — GET /health returns MCP status, SketchUp bridge connectivity, last generation timestamp, queue depth
TASK-521 Configure Prometheus metrics or simple health check script — curl http://localhost:9494/health + curl http://mcp-server/health monitored via existing Docker health checks; alert if SketchUp bridge unreachable > 5 minutes
TASK-522 Write integration test slice-6-booth-generation.spec.ts — create job with Eckstand, 6x4, height 2.5; click "3D Booth generieren"; verify status transitions: queued → generating → complete; verify PNG preview displays in modal; verify BOM shows component count + profile lengths; download .skp file; regenerate with different standType (Inselstand), verify new model created

Cross-Cutting — Polish & Deployment (after core slices)

Task Description Completed Date
TASK-901 Install sonner for toast notifications; create <Toaster> in App.tsx
TASK-902 Add loading skeletons (shadcn/ui) for stats cards, job card grid, detail header, form fields, gallery items
TASK-903 Add error boundaries wrapping each major route/view; error message + retry button (queryClient.invalidateQueries)
TASK-904 Responsive design — sidebar collapses to hamburger on <768px; form grids stack single column; calendar days reduce height; gallery grid reduces columns
TASK-905 Install vite-plugin-pwa — service worker for static asset caching; offline indicator; PWA manifest (24HRS Messe-Lotse, yellow+black theme)
TASK-906 Create frontend/Dockerfile — multi-stage: Node.js build → nginx or Pimcore-served assets
TASK-907 Create .github/workflows/ci.yml — checkout, setup Node 20, npm ci, lint, typecheck, Vitest, Playwright (Pimcore container as service), build
TASK-908 Full manual QA — create job, fill all 8 tabs, upload files, save, reload, verify persistence; test calendar; test PDF export; test duplicate/delete; test mobile viewport; test all 4 roles (viewer, editor, admin, customer)

3. Alternatives

  • ALT-001: The original architecture-rewrite-1.0.md plan with 12 sequential phases. Rejected because sequential phasing (backend-complete-before-frontend) is unsuitable for AI-only execution where each task must produce a verifiable working increment.
  • ALT-002: Monorepo with separate backend/frontend deployment. Rejected in favor of Pimcore-served React SPA (REQ-005) for simplicity — no CORS, session cookie reuse, single domain.
  • ALT-003: Direct SketchUp bridge calls from Pimcore (synchronous). Rejected per DEC-001 — requires async message queue for reliability, concurrent job locking, and health monitoring.

4. Dependencies

  • DEP-001: Pimcore ≥ 11.x with OAuth2, Gotenberg already running (CON-003)
  • DEP-002: Symfony Messenger with Redis or Doctrine transport (bundled with Pimcore)
  • DEP-003: Docker Compose environment (CON-001) — Slice 6 adds MCP server service
  • DEP-004: SketchUp Pro + Aluvision 3D Library (Slice 6 only)
  • DEP-005: OpenAI API key for gpt-4o-mini / gpt-4o / gpt-4o vision (Slice 4 only)
  • DEP-006: @modelcontextprotocol/sdk npm package (Slice 6 only)
  • DEP-007: react-signature-canvas npm package (Slice 3 only)

5. Files

Plan Documents (this directory)

  • FILE-001: plan/architecture-vertical-slices-1.0.md — this file
  • FILE-002: plan/architecture-rewrite-1.0.md — original plan (superseded, retained for reference)
  • FILE-003: plan/ai-document-skills.md — AI skill specifications (referenced by Slice 4)
  • FILE-004: plan/3d-booth-generation.md — 3D booth generation specification (referenced by Slice 6)

Backend (Pimcore)

  • FILE-005: bundles/MessebauBundle/ — Symfony bundle directory (created in Slice 1)
  • FILE-006: bundles/MessebauBundle/Resources/config/workflows/job.yml — workflow configuration (created in Slice 3)
  • FILE-007: bundles/MessebauBundle/Service/SuggestionService.php — AI suggestion storage (created in Slice 4)
  • FILE-008: bundles/MessebauBundle/EventListener/AiSkillTriggerListener.php — AI skill trigger (created in Slice 4)
  • FILE-009: bundles/MessebauBundle/Service/Booth3dService.php — 3D parameter builder (created in Slice 6)
  • FILE-010: templates/messebau/pdf/export.html.twig — PDF export template (created in Slice 3)

Frontend (React)

  • FILE-011: frontend/ — Vite + React project directory (created in Slice 1)
  • FILE-012: frontend/src/App.tsx — application routes
  • FILE-013: frontend/src/api/client.ts — Axios client with auth interceptors
  • FILE-014: frontend/src/stores/authStore.ts — authentication state
  • FILE-015: frontend/src/stores/uiStore.ts — UI state
  • FILE-016: frontend/src/hooks/usePermissions.ts — role-based permission flags
  • FILE-017: frontend/src/components/ — all React components per slice

3D Pipeline (Slice 6 only)

  • FILE-018: sketchup-aluvision-bridge/ — SketchUp Ruby extension
  • FILE-019: aluvision-mcp-server/ — Node.js MCP server

6. Testing

E2E (Playwright)

  • TEST-001: slice-1-auth-create-fair.spec.ts — auth flow, entity creation, role gating
  • TEST-002: slice-2-jobs-crud.spec.ts — job CRUD, relations, file upload, role scoping
  • TEST-003: slice-3-full-workflow.spec.ts — 8-state workflow, all tabs, PDF export, calendar
  • TEST-004: slice-3-calendar.spec.ts — calendar event rendering, navigation
  • TEST-005: slice-4-ai-pipeline.spec.ts — document upload → classification → extraction → suggestion → apply/ignore
  • TEST-006: slice-4-setup-progress.spec.ts — photo comparison, confidence threshold, auto-check safety
  • TEST-007: slice-5-fair-resources.spec.ts — demand/assignment/gap flow, job-scoped resources
  • TEST-008: slice-5-accommodation-travel.spec.ts — accommodation + travel assignment
  • TEST-009: slice-6-booth-generation.spec.ts — async 3D generation, preview, BOM, regeneration

Unit (Vitest)

  • TEST-010: authStore.test.ts — login, logout, token refresh
  • TEST-011: usePermissions.test.ts — role → flag mapping
  • TEST-012: MasterDataTab.test.tsx — form validation, date inheritance display
  • TEST-013: useJobs.test.ts — fetch, filter, pagination

Backend (PHPUnit)

  • TEST-014: FairLocationControllerTest, CustomerControllerTest, FairControllerTest, JobControllerTest
  • TEST-015: WorkflowTest, WorkflowTransitionListenerTest (force transition guard bypass)
  • TEST-016: SuggestionServiceTest (apply, ignore, deduplication)

7. Risks & Assumptions

  • RISK-001: AI-only execution without human developers — all code must be independently verifiable by Playwright tests. Each slice ships with E2E tests that prove functionality. Slice quality gates on test pass.
  • RISK-002: LLM operational cost may exceed budget (DEC-004). Mitigation: resolve INVESTIGATE card before Slice 4 begins. If cost is prohibitive, defer non-critical vision skills (BrandConsistencyChecker, LeadMagnetIdeator) to a later release.
  • RISK-003: SketchUp must be running on a dedicated machine for 3D generation (Slice 6). Mitigation: health monitoring + alerting (TASK-521); generation is a nice-to-have, not blocking earlier slices.
  • RISK-004: Aluvision 3D Library component naming may not match expected patterns. Mitigation: TASK-501 audits the library before any implementation begins; AluvisionRegistry acts as adapter layer.
  • ASSUMPTION-001: Pimcore, Gotenberg, and OAuth2 are already operational in the Docker Compose environment (CON-003).
  • ASSUMPTION-002: The index.html prototype accurately represents the required feature set for Slices 1-3. Any deviations discovered during implementation are documented as changelog entries against the prototype.
  • ASSUMPTION-003: All 12 AI skills defined in plan/ai-document-skills.md are in scope for Slice 4. If INVESTIGATE card reveals cost concerns, Tier 3 skills may be deferred.