Files
hms-licht-ton/docs/task_graph.json

515 lines
30 KiB
JSON

{
"project": "hms-licht-ton",
"version": "1.0.0",
"created": "2026-07-08",
"total_tasks": 8,
"tasks": [
{
"id": "T01",
"title": "Frontend Foundation: Nuxt 3 + Design System + Layout + Meta",
"description": "Nuxt 3 project setup with Tailwind CSS, design tokens (CSS custom properties for A0 Dark Theme + Orange accent), base layout (AppHeader sticky with mobile burger, AppFooter 4-column), HmsLogo SVG component, robots.txt (Disallow all), error page (404/500), and global meta tags (noindex/nofollow/noarchive/nosnippet, OpenGraph, JSON-LD LocalBusiness on home). Includes nuxt.config.ts with routeRules (SSG/SSR strategy), tailwind.config.ts with custom theme, app.vue, layouts/default.vue, server middleware for noindex headers, and all shared components: LoadingSkeleton, EmptyState, ErrorState, LegalContentPage, SpeakerIcon.",
"dependencies": [],
"requirement_ids": [
"F-HMS-10",
"F-HMS-11",
"F-HMS-15",
"F-HMS-18",
"F-HMS-19"
],
"assigned_subagent": "implementation_engineer",
"estimated_effort": "L",
"test_spec": {
"commands": [
"cd frontend && npm ci",
"cd frontend && npm run build",
"cd frontend && npx vitest run --reporter verbose",
"cd frontend && npx playwright test --grep 'Header|Footer|404|Layout'",
"curl -s http://localhost:3000/robots.txt | grep 'Disallow: /'",
"curl -s http://localhost:3000/ | grep 'noindex'",
"curl -s http://localhost:3000/ | grep 'LocalBusiness'",
"curl -s http://localhost:3000/nicht-existent | grep '404'"
],
"expected_results": "Build succeeds. All Vitest tests pass. Playwright E2E for header/footer/404 pass. robots.txt contains 'Disallow: /'. Home page HTML contains noindex meta tag. Home page HTML contains JSON-LD LocalBusiness schema. Non-existent route returns 404 page with home link.",
"test_files": [
"frontend/tests/unit/Layout.test.ts",
"frontend/tests/unit/DesignTokens.test.ts",
"frontend/tests/e2e/header-footer.spec.ts",
"frontend/tests/e2e/error-pages.spec.ts",
"frontend/tests/e2e/meta-robots.spec.ts"
],
"coverage_target": 80
},
"acceptance_criteria": [
"npm run build exits with code 0",
"AppHeader is sticky (position: sticky) and contains nav items: Home, Referenzen, Mietkatalog, Kontakt",
"AppHeader mobile burger menu opens/closes on click, items have min 44px touch targets",
"AppFooter contains: address (Zur Schönhalde 8, 89352 Ellzee), phone, email, links to Impressum/DSGVO/AGB Vermietung",
"AppFooter does NOT contain 'Neu in der Vermietung' or 'AGB Shop'",
"robots.txt contains 'User-agent: *' and 'Disallow: /'",
"All pages have meta robots noindex,nofollow,noarchive,nosnippet",
"Home page HTML contains JSON-LD script with '@type': 'LocalBusiness'",
"404 page shows 'Seite nicht gefunden' with link to /",
"CSS variables --bg, --panel, --color-accent defined in :root",
"HmsLogo renders SVG with orange (#EC6925) border",
"AppHeader contains clickable phone link (tel:+491726264796)",
"AppHeader contains social icons (Facebook, Instagram) with correct links to social profiles"
]
},
{
"id": "T02",
"title": "Static Pages: Home, Referenzen, Kontakt, Legal (Impressum/DSGVO/AGB)",
"description": "All static content pages: Home (hero with bg image + overlay, speaker grid 6 types, über uns section with stats, 8 service cards, mietkatalog CTA), Referenzen (filterable gallery with 9 images, 4 category chips, lightbox with prev/next/close), Kontakt (office/warehouse address cards, opening hours, 2 contact persons, contact form with Name/Email/Phone/Message/Privacy consent, client-side + submit to /api/contact), Impressum (§5 TMG legal info), Datenschutz (DSGVO privacy policy), AGB Vermietung (rental terms), NotFound catch-all page. All pages use LegalContentPage component for legal pages, ServiceCard for services, Lightbox component for gallery.",
"dependencies": [
"T01"
],
"requirement_ids": [
"F-HMS-01",
"F-HMS-02",
"F-HMS-03",
"F-HMS-12",
"F-HMS-13",
"F-HMS-14"
],
"assigned_subagent": "implementation_engineer",
"estimated_effort": "L",
"test_spec": {
"commands": [
"cd frontend && npm run build",
"cd frontend && npx vitest run --reporter verbose",
"cd frontend && npx playwright test --grep 'Home|Referenzen|Kontakt|Impressum|Datenschutz|AGB'",
"curl -s http://localhost:3000/ | grep 'Veranstaltungstechnik'",
"curl -s http://localhost:3000/referenzen | grep 'Referenzen'",
"curl -s http://localhost:3000/kontakt | grep 'Planen Sie'",
"curl -s http://localhost:3000/impressum | grep 'Hammerschmidt'",
"curl -s http://localhost:3000/datenschutz | grep 'Datenschutz'",
"curl -s http://localhost:3000/agb-vermietung | grep 'AGB'"
],
"expected_results": "Build succeeds. All Vitest tests pass. Playwright E2E for all static pages pass. Home page contains hero headline and 8 service cards. Referenzen page loads gallery with 9 images and filter chips. Kontakt page has form with required fields and privacy checkbox. Legal pages contain correct legal text. Contact form validates: empty email blocked, no privacy consent blocked.",
"test_files": [
"frontend/tests/unit/HomePage.test.ts",
"frontend/tests/unit/ReferenzenPage.test.ts",
"frontend/tests/unit/KontaktPage.test.ts",
"frontend/tests/e2e/home.spec.ts",
"frontend/tests/e2e/referenzen.spec.ts",
"frontend/tests/e2e/kontakt.spec.ts",
"frontend/tests/e2e/legal-pages.spec.ts"
],
"coverage_target": 80
},
"acceptance_criteria": [
"Home page renders hero with headline containing 'Veranstaltungstechnik'",
"Home page shows all 8 services: Vermietung, Verkauf, Personal, Transport, Lagerung, Werkstatt, Installation, Booking",
"Home page has CTA button linking to /mietkatalog",
"Referenzen page shows 9 images in grid layout",
"Referenzen page has 4 category filter chips: Alle, Open-Air, Indoor, Rigging (or similar)",
"Clicking image opens lightbox with prev/next/close buttons",
"Kontakt form has fields: Name*, Email*, Phone, Message*, Privacy consent checkbox*",
"Kontakt form blocks submit when email is empty (shows validation error)",
"Kontakt form blocks submit when privacy consent is unchecked",
"Impressum contains: Hammerschmidt u. Mössle GbR, address, phone, email",
"Datenschutz contains DSGVO-relevant sections",
"AGB Vermietung page is accessible at /agb-vermietung",
"All pages are mobile-responsive at 375px width"
]
},
{
"id": "T03",
"title": "Backend Core: FastAPI + DB Models + Equipment API + Redis Cache + Health",
"description": "FastAPI application setup with config (Pydantic Settings for env vars), SQLAlchemy async engine + session, all 5 DB models (equipment_cache, rental_requests, rental_request_items, contacts, admin_users, sync_log), Pydantic schemas, equipment router (GET /api/equipment with search/filter/sort/pagination, GET /api/equipment/{id}, GET /api/equipment/categories), health router (GET /api/health), Redis cache client (equipment list/detail/categories with 1h TTL), CORS middleware, main.py app assembly, Alembic migration setup, requirements.txt, and Dockerfile.",
"dependencies": [],
"requirement_ids": [
"F-HMS-04",
"F-HMS-06"
],
"assigned_subagent": "implementation_engineer",
"estimated_effort": "L",
"test_spec": {
"commands": [
"cd backend && pip install -r requirements.txt",
"cd backend && python -m pytest tests/ -v --cov=app --cov-report=term-missing",
"cd backend && python -c 'from app.main import app; print(app.title)'",
"curl -s http://localhost:8000/api/health | grep 'ok'",
"curl -s 'http://localhost:8000/api/equipment?page=1&page_size=5' | python -m json.tool",
"curl -s 'http://localhost:8000/api/equipment?search=Lautsprecher' | python -m json.tool",
"curl -s 'http://localhost:8000/api/equipment/categories' | python -m json.tool",
"curl -s http://localhost:8000/api/equipment/999999 | grep '404'"
],
"expected_results": "All dependencies install. Pytest passes with >=80% coverage. FastAPI app starts. Health endpoint returns {status: ok, db: connected, redis: connected}. Equipment list returns paginated response with items, total, page, page_size, total_pages. Search filters by name. Categories endpoint returns string array. Non-existent equipment ID returns 404.",
"test_files": [
"backend/tests/test_equipment_router.py",
"backend/tests/test_health.py",
"backend/tests/test_models.py",
"backend/tests/test_cache.py",
"backend/tests/conftest.py"
],
"coverage_target": 80
},
"acceptance_criteria": [
"GET /api/health returns 200 with {status: 'ok', db: 'connected', redis: 'connected'}",
"GET /api/equipment returns 200 with paginated response (items, total, page, page_size, total_pages)",
"GET /api/equipment?search=K2 returns only items matching 'K2' in name",
"GET /api/equipment?category=Lautsprecher returns only items with category 'Lautsprecher'",
"GET /api/equipment?sort=name_asc returns items sorted alphabetically",
"GET /api/equipment/categories returns 200 with array of category strings",
"GET /api/equipment/{id} returns 200 with full equipment detail for valid ID",
"GET /api/equipment/999999 returns 404",
"Redis caches equipment list (second call faster than first)",
"All 6 DB models defined with correct columns and indexes",
"Alembic migration generates all tables successfully"
]
},
{
"id": "T04",
"title": "Rentman Integration: Import Service + Request Service + Admin Auth & Sync",
"description": "Complete Rentman integration: (1) Import service (rentman_import.py) using RentmanAPI Python client, paginated GET /equipment (limit=100, iterate offset until data empty), upsert into equipment_cache, invalidate Redis cache, sync_log entries. (2) Request service (rentman_request.py) with POST /projectrequests + POST /projectrequests/{id}/projectrequestequipment, retry queue with exponential backoff (max 3 retries), admin notification on failure. (3) Admin auth: JWT login (auth_service.py, python-jose, HS256, HttpOnly cookie), admin router (POST /api/admin/login, GET /api/admin/me, POST /api/admin/sync, GET /api/admin/sync-status, GET /api/admin/sync-log). (4) Auth schemas, admin_user model seeding script, rate limiting for login endpoint.",
"dependencies": [
"T03"
],
"requirement_ids": [
"F-HMS-08",
"F-HMS-09",
"F-HMS-17"
],
"assigned_subagent": "implementation_engineer",
"estimated_effort": "L",
"test_spec": {
"commands": [
"cd backend && python -m pytest tests/test_rentman_import.py tests/test_rentman_request.py tests/test_admin_auth.py -v --cov=app.services --cov-report=term-missing",
"curl -s -X POST http://localhost:8000/api/admin/login -H 'Content-Type: application/json' -d '{\"username\":\"admin\",\"password\":\"wrong\"}' | grep '401'",
"curl -s -X POST http://localhost:8000/api/admin/login -H 'Content-Type: application/json' -d '{\"username\":\"admin\",\"password\":\"correct\"}' | grep 'token'",
"curl -s http://localhost:8000/api/admin/sync-status | grep '401'",
"curl -s -X POST http://localhost:8000/api/admin/sync -H 'Cookie: hms_admin_token=invalid' | grep '401'",
"cd backend && python -m pytest tests/test_rentman_import.py::test_paginated_import -v",
"cd backend && python -m pytest tests/test_rentman_request.py::test_projectrequest_success -v",
"cd backend && python -m pytest tests/test_rentman_request.py::test_equipment_retry -v"
],
"expected_results": "All tests pass with >=80% coverage. Login with wrong password returns 401. Login with correct password returns 200 with token. Admin endpoints without JWT return 401. Paginated import mock returns correct number of items. Project request mock creates request and adds equipment. Equipment retry mechanism retries failed items with exponential backoff.",
"test_files": [
"backend/tests/test_rentman_import.py",
"backend/tests/test_rentman_request.py",
"backend/tests/test_admin_auth.py",
"backend/tests/test_admin_router.py"
],
"coverage_target": 80
},
"acceptance_criteria": [
"POST /api/admin/login with valid credentials returns 200 with token in HttpOnly cookie",
"POST /api/admin/login with invalid credentials returns 401",
"GET /api/admin/me with valid JWT returns 200 with username",
"GET /api/admin/me without JWT returns 401",
"POST /api/admin/sync with valid JWT triggers equipment import and returns 200 with sync_id",
"POST /api/admin/sync without JWT returns 401",
"GET /api/admin/sync-status returns last sync date, item count, status badge",
"GET /api/admin/sync-log returns paginated sync log entries",
"Import service iterates all pages (mocked Rentman API with 250 items, 3 pages)",
"Import service upserts equipment into equipment_cache table",
"Request service creates POST /projectrequests with correct field mapping",
"Request service adds equipment via POST /projectrequests/{id}/projectrequestequipment for each item",
"Failed equipment POST triggers retry with exponential backoff",
"6th login attempt within 60s is rate-limited (429 or 403)"
]
},
{
"id": "T05",
"title": "Email Service + Contact & Rental Request Backend Endpoints",
"description": "Complete backend for user-facing forms: (1) Email service (email_service.py) with aiosmtplib, HTML+text templates for contact form email (to info@hms-licht-ton.de, Reply-To sender) and rental confirmation email (to customer, with reference_number + item list). SMTP via env vars. Retry queue on failure. (2) Contact router (POST /api/contact) with Pydantic validation (name*, email*, phone, message*, privacy_consent* must be True), rate limiting (5/min per IP), saves to contacts table, sends email. (3) Rental request router (POST /api/rental-requests) with full validation (event data, contact data, items array), generates reference_number (HMS-YYYY-NNNNN), saves to rental_requests + rental_request_items, calls rentman_request service, sends confirmation email, returns 201 with reference_number. (4) Rate limiting for both endpoints.",
"dependencies": [
"T03",
"T04"
],
"requirement_ids": [
"F-HMS-07",
"F-HMS-20"
],
"assigned_subagent": "implementation_engineer",
"estimated_effort": "M",
"test_spec": {
"commands": [
"cd backend && python -m pytest tests/test_contact_router.py tests/test_rental_router.py tests/test_email_service.py -v --cov=app.routers --cov=app.services.email --cov-report=term-missing",
"curl -s -X POST http://localhost:8000/api/contact -H 'Content-Type: application/json' -d '{\"name\":\"Test\",\"email\":\"test@example.com\",\"message\":\"Hallo\",\"privacy_consent\":true}' | grep '200'",
"curl -s -X POST http://localhost:8000/api/contact -H 'Content-Type: application/json' -d '{\"name\":\"Test\",\"email\":\"invalid\",\"message\":\"Hallo\",\"privacy_consent\":true}' | grep '422'",
"curl -s -X POST http://localhost:8000/api/contact -H 'Content-Type: application/json' -d '{\"name\":\"Test\",\"email\":\"test@example.com\",\"message\":\"Hallo\",\"privacy_consent\":false}' | grep '422'",
"curl -s -X POST http://localhost:8000/api/rental-requests -H 'Content-Type: application/json' -d '{\"event_name\":\"Test Event\",\"date_start\":\"2026-08-15\",\"date_end\":\"2026-08-16\",\"location\":\"München\",\"contact_name\":\"Max Mustermann\",\"contact_email\":\"max@example.com\",\"items\":[{\"equipment_id\":1,\"quantity\":2}]}' | grep '201'",
"curl -s -X POST http://localhost:8000/api/rental-requests -H 'Content-Type: application/json' -d '{\"event_name\":\"Test\",\"date_start\":\"2026-08-16\",\"date_end\":\"2026-08-15\",\"location\":\"München\",\"contact_name\":\"Max\",\"contact_email\":\"max@example.com\",\"items\":[]}' | grep '422'"
],
"expected_results": "All tests pass with >=80% coverage. Contact POST with valid data returns 200. Contact POST with invalid email returns 422. Contact POST without privacy consent returns 422. Rental request POST with valid data returns 201 with reference_number. Rental request with date_end before date_start returns 422. Email service mock sends correct emails. Rate limiting blocks 6th request within 60s.",
"test_files": [
"backend/tests/test_contact_router.py",
"backend/tests/test_rental_router.py",
"backend/tests/test_email_service.py",
"backend/tests/test_rate_limiting.py"
],
"coverage_target": 80
},
"acceptance_criteria": [
"POST /api/contact with valid data returns 200 and saves to contacts table",
"POST /api/contact with invalid email returns 422",
"POST /api/contact with privacy_consent=false returns 422",
"POST /api/contact triggers email to info@hms-licht-ton.de",
"POST /api/rental-requests with valid data returns 201 with reference_number (format HMS-YYYY-NNNNN)",
"POST /api/rental-requests with date_end < date_start returns 422",
"POST /api/rental-requests with empty items array returns 422",
"POST /api/rental-requests saves to rental_requests and rental_request_items tables",
"POST /api/rental-requests triggers Rentman projectrequest creation",
"POST /api/rental-requests triggers confirmation email to customer",
"6th POST /api/contact within 60s from same IP is rate-limited",
"6th POST /api/rental-requests within 60s from same IP is rate-limited",
"Email service gracefully handles SMTP failure (saves to retry queue, no error to user)"
]
},
{
"id": "T06",
"title": "Mietkatalog Frontend: Equipment List, Detail, Search/Filter, SSR",
"description": "Complete Mietkatalog frontend: (1) Mietkatalog list page (/mietkatalog) with SSR (useAsyncData), equipment grid with EquipmentCard components, search input (freitext), category filter chips, sort dropdown (name_asc, name_desc), result count, pagination/infinite scroll, loading skeleton, empty state, error state with retry. (2) Equipment detail page (/mietkatalog/[id]) with SSR, full specs table, image/placeholder, related items, 'Mietanfrage' add-to-cart button. (3) Composables: useEquipment (API fetch wrapper), useApi (base API client). (4) EquipmentCard component with image, badge, name, description, add-to-cart emit. (5) Breadcrumb navigation on detail page.",
"dependencies": [
"T01",
"T03"
],
"requirement_ids": [
"F-HMS-04",
"F-HMS-05",
"F-HMS-06"
],
"assigned_subagent": "implementation_engineer",
"estimated_effort": "L",
"test_spec": {
"commands": [
"cd frontend && npm run build",
"cd frontend && npx vitest run --reporter verbose",
"cd frontend && npx playwright test --grep 'Mietkatalog|Equipment|Filter'",
"curl -s http://localhost:3000/mietkatalog | grep 'Mietkatalog'",
"curl -s http://localhost:3000/mietkatalog | grep 'EquipmentCard'",
"curl -s 'http://localhost:3000/mietkatalog?search=Lautsprecher' | grep 'Lautsprecher'",
"curl -s http://localhost:3000/mietkatalog/1 | grep 'Specs'",
"curl -s http://localhost:3000/mietkatalog/999999 | grep 'nicht gefunden'"
],
"expected_results": "Build succeeds. All Vitest tests pass. Playwright E2E for mietkatalog pass. Mietkatalog page SSR-renders equipment cards in HTML (not just client JS). Search filters results in real-time. Category filter works. Sort changes order. Detail page shows specs and add-to-cart. Non-existent ID shows 404-style error with link back to catalog.",
"test_files": [
"frontend/tests/unit/MietkatalogPage.test.ts",
"frontend/tests/unit/EquipmentDetailPage.test.ts",
"frontend/tests/unit/useEquipment.test.ts",
"frontend/tests/e2e/mietkatalog.spec.ts",
"frontend/tests/e2e/equipment-detail.spec.ts"
],
"coverage_target": 80
},
"acceptance_criteria": [
"GET /mietkatalog returns SSR HTML with equipment cards (server-rendered, not empty div)",
"Equipment list shows name, category, and thumbnail/placeholder for each item",
"Search input filters results by name in real-time (<500ms)",
"Category filter chips filter results by category",
"Sort dropdown changes order (name_asc, name_desc)",
"Result count displays total filtered items",
"Filter reset button clears all filters",
"Empty search result shows 'Keine Artikel gefunden' with reset suggestion",
"GET /mietkatalog/{id} shows equipment detail with name, description, specs, image",
"Detail page has 'Mietanfrage' button that adds item to cart",
"Non-existent equipment ID shows error with link back to /mietkatalog",
"Loading state shows skeleton cards",
"Pagination shows correct page count and navigation"
]
},
{
"id": "T07",
"title": "Warenkorb & Mietanfrage Frontend: Cart Store + Cart Drawer + Mietanfrage Form",
"description": "Complete cart and rental request frontend: (1) Pinia cart store (stores/cart.ts) with localStorage persistence (plugin pinia-persist.client.ts), add/remove items, adjust quantity, cart total count getter, clear cart. (2) CartDrawer component (mobile bottom-sheet, desktop side panel) accessible from header cart icon with counter badge. (3) Warenkorb page (/warenkorb) with item list, quantity steppers, remove buttons, rental dates display, CTA to /mietanfrage, empty state. (4) Mietanfrage page (/mietanfrage) with: cart overview (read-only), event data form (name*, date_start*, date_end*, location*, person_count), contact data form (name*, company, email*, phone, street, postalcode, city), message field, submit button, validation (client-side), states (form, validation-error, submitting, success, error), success page with reference_number, empty cart handling. (5) useCart composable. (6) Header cart counter integration.",
"dependencies": [
"T01",
"T06"
],
"requirement_ids": [
"F-HMS-16",
"F-HMS-07"
],
"assigned_subagent": "implementation_engineer",
"estimated_effort": "L",
"test_spec": {
"commands": [
"cd frontend && npm run build",
"cd frontend && npx vitest run --reporter verbose",
"cd frontend && npx playwright test --grep 'Warenkorb|Mietanfrage|Cart'",
"curl -s http://localhost:3000/warenkorb | grep 'Warenkorb'",
"curl -s http://localhost:3000/mietanfrage | grep 'Mietanfrage'"
],
"expected_results": "Build succeeds. All Vitest tests pass. Playwright E2E for cart and mietanfrage pass. Cart persists in localStorage across page reloads. Adding item updates header counter. Mietanfrage form validates required fields. Submit sends POST /api/rental-requests and shows success with reference_number. Empty cart on mietanfrage shows redirect suggestion.",
"test_files": [
"frontend/tests/unit/CartStore.test.ts",
"frontend/tests/unit/WarenkorbPage.test.ts",
"frontend/tests/unit/MietanfragePage.test.ts",
"frontend/tests/e2e/cart.spec.ts",
"frontend/tests/e2e/mietanfrage.spec.ts"
],
"coverage_target": 80
},
"acceptance_criteria": [
"Adding equipment to cart from detail page updates header counter badge",
"Cart items persist in localStorage across page reloads",
"Warenkorb page shows items with quantity steppers and remove buttons",
"Removing all items shows empty state with link to /mietkatalog",
"Header cart icon shows counter (0 when empty, N when items added)",
"CartDrawer opens on mobile as bottom-sheet, on desktop as side panel",
"Mietanfrage page shows cart overview (read-only items with quantities)",
"Mietanfrage form has: event_name*, date_start*, date_end*, location*, contact_name*, contact_email*, message",
"Mietanfrage form blocks submit with validation errors for missing required fields",
"Mietanfrage form blocks submit when date_end < date_start",
"Mietanfrage submit shows 'submitting' state (spinner, disabled fields)",
"Successful submit shows success page with reference_number and 'Zurück zur Startseite' button",
"Failed submit shows error state with retry button",
"Navigating to /mietanfrage with empty cart shows 'Keine Artikel ausgewählt' with link to /mietkatalog",
"Cart clears after successful mietanfrage submit"
]
},
{
"id": "T08",
"title": "Docker, Deployment & CI/CD Configuration",
"description": "Complete deployment infrastructure: (1) Frontend Dockerfile (Node 20, multi-stage build, Nuxt 3 production). (2) Backend Dockerfile (Python 3.12, pip install, uvicorn/gunicorn). (3) docker-compose.yml with 4 services (frontend, backend, postgres, redis), volumes, networks, health checks, restart policies. (4) Coolify deployment config (docker-compose for Coolify with env var placeholders). (5) Forgejo Actions CI/CD pipeline (.forgejo/workflows/ci.yml): lint, test (frontend + backend), build Docker images, deploy to Coolify via API. (6) .env.example with all required env vars. (7) README.md with setup, development, and deployment instructions.",
"dependencies": [
"T01",
"T02",
"T03",
"T04",
"T05",
"T06",
"T07"
],
"requirement_ids": [],
"assigned_subagent": "implementation_engineer",
"estimated_effort": "M",
"test_spec": {
"commands": [
"docker build -t hms-frontend ./frontend --dry-run",
"docker build -t hms-backend ./backend --dry-run",
"docker compose config --quiet",
"python -c 'import yaml; yaml.safe_load(open(\"docker-compose.yml\"))'",
"cat .env.example | grep 'RENTMAN_API_TOKEN'",
"cat .env.example | grep 'JWT_SECRET'",
"cat .env.example | grep 'DATABASE_URL'",
"cat .env.example | grep 'SMTP_HOST'",
"cat .forgejo/workflows/ci.yml | grep 'test'",
"cat README.md | grep 'Setup'"
],
"expected_results": "Dockerfiles build without errors. docker-compose.yml is valid YAML and passes docker compose config. .env.example contains all required env vars. CI/CD pipeline has test and build steps. README contains setup and deployment instructions.",
"test_files": [
"tests/test_docker_config.py",
"tests/test_env_example.py"
],
"coverage_target": 80
},
"acceptance_criteria": [
"Frontend Dockerfile builds successfully (multi-stage, Node 20)",
"Backend Dockerfile builds successfully (Python 3.12)",
"docker-compose.yml has 4 services: frontend, backend, postgres, redis",
"docker-compose.yml has volumes for postgres_data and redis_data",
"docker-compose.yml has health checks for all services",
"docker-compose.yml has restart: unless-stopped for all services",
".env.example contains: RENTMAN_API_TOKEN, JWT_SECRET, DATABASE_URL, REDIS_URL, SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_FROM",
"CI/CD pipeline runs frontend tests (npm ci, npm test, npm run build)",
"CI/CD pipeline runs backend tests (pip install, pytest)",
"CI/CD pipeline builds Docker images",
"README.md contains: project overview, setup instructions, development commands, deployment instructions"
]
}
],
"dependency_graph": {
"T01": [],
"T02": [
"T01"
],
"T03": [],
"T04": [
"T03"
],
"T05": [
"T03",
"T04"
],
"T06": [
"T01",
"T03"
],
"T07": [
"T01",
"T06"
],
"T08": [
"T01",
"T02",
"T03",
"T04",
"T05",
"T06",
"T07"
]
},
"parallel_tracks": [
"Track A (Frontend): T01 → T02, T06, T07",
"Track B (Backend): T03 → T04, T05",
"Track C (Deployment): T08 (after all)"
],
"feature_coverage": {
"F-HMS-01": [
"T02"
],
"F-HMS-02": [
"T02"
],
"F-HMS-03": [
"T02"
],
"F-HMS-04": [
"T03",
"T06"
],
"F-HMS-05": [
"T06"
],
"F-HMS-06": [
"T03",
"T06"
],
"F-HMS-07": [
"T05",
"T07"
],
"F-HMS-08": [
"T04"
],
"F-HMS-09": [
"T04",
"T05"
],
"F-HMS-10": [
"T01"
],
"F-HMS-11": [
"T01"
],
"F-HMS-12": [
"T02"
],
"F-HMS-13": [
"T02"
],
"F-HMS-14": [
"T02"
],
"F-HMS-15": [
"T01"
],
"F-HMS-16": [
"T07"
],
"F-HMS-17": [
"T04"
],
"F-HMS-18": [
"T01"
],
"F-HMS-19": [
"T01"
],
"F-HMS-20": [
"T05"
]
}
}