feat: initial architecture - requirements, architecture.md, task_graph, AGENTS.md, UI prototype v8d
This commit is contained in:
+412
@@ -0,0 +1,412 @@
|
||||
# AGENTS.md – ERP Nutzfahrzeuge Implementation Guide
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Datum:** 2026-07-12
|
||||
|
||||
---
|
||||
|
||||
## 1. Project Overview
|
||||
|
||||
ERP-System für Nutzfahrzeug-/Baumaschinen-Handel (~10 Nutzer).
|
||||
- **Backend:** Python 3.12 / FastAPI / SQLAlchemy 2.0 async / PostgreSQL 16 / Redis 7
|
||||
- **Frontend:** Next.js 14 (App Router) / React 18 / TypeScript / Tailwind CSS / next-intl
|
||||
- **Hosting:** Coolify (Docker Compose) auf coolify-01 (46.225.91.159)
|
||||
- **KI:** OpenRouter (Qwen2.5-VL OCR, Flux.1-Pro Bild, Claude/GPT-4 Copilot)
|
||||
- **External:** mobile.de Seller API (Push-Only)
|
||||
|
||||
### Architecture Reference
|
||||
- `docs/architecture.md` – Complete architecture, DB schema, API design, ADRs
|
||||
- `docs/task_graph.json` – Task breakdown with test specs
|
||||
- `docs/requirements.md` – Full requirements (8 modules, 23 features)
|
||||
- `docs/component_inventory.md` – UI components and states
|
||||
|
||||
---
|
||||
|
||||
## 2. Build & Test Commands
|
||||
|
||||
### Backend
|
||||
```bash
|
||||
# Install dependencies
|
||||
cd backend
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Run dev server
|
||||
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
|
||||
|
||||
# Run all tests
|
||||
python -m pytest tests/ -v --cov=app --cov-report=term-missing --cov-report=html
|
||||
|
||||
# Run specific test file
|
||||
python -m pytest tests/test_vehicles.py -v
|
||||
|
||||
# Run with coverage for specific module
|
||||
python -m pytest tests/test_auth.py -v --cov=app/services/auth_service --cov-report=term-missing
|
||||
|
||||
# Lint
|
||||
ruff check app/
|
||||
ruff format app/ --check
|
||||
|
||||
# Database migrations
|
||||
alembic revision --autogenerate -m "description"
|
||||
alembic upgrade head
|
||||
alembic downgrade -1
|
||||
|
||||
# Type check (optional but recommended)
|
||||
mypy app/ --ignore-missing-imports
|
||||
```
|
||||
|
||||
### Frontend
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
|
||||
# Run dev server
|
||||
npm run dev
|
||||
|
||||
# Run all tests
|
||||
npx vitest run --coverage
|
||||
|
||||
# Run specific test
|
||||
npx vitest run src/components/vehicles --coverage
|
||||
|
||||
# Lint
|
||||
npx eslint src/ --max-warnings 0
|
||||
npx prettier --check src/
|
||||
|
||||
# Build
|
||||
npm run build
|
||||
|
||||
# Type check
|
||||
npx tsc --noEmit
|
||||
```
|
||||
|
||||
### Docker
|
||||
```bash
|
||||
# Build and start all services
|
||||
docker-compose up -d --build
|
||||
|
||||
# View logs
|
||||
docker-compose logs -f backend
|
||||
docker-compose logs -f frontend
|
||||
|
||||
# Run database migration in container
|
||||
docker-compose exec backend alembic upgrade head
|
||||
|
||||
# Run tests in container
|
||||
docker-compose exec backend python -m pytest tests/ -v
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Test Rules (MANDATORY)
|
||||
|
||||
### TDD Principle
|
||||
- **Write tests first or alongside implementation** – never after
|
||||
- Every PR must include tests
|
||||
- Coverage target: **≥80%** per module
|
||||
|
||||
### Do NOT Modify Existing Tests
|
||||
- Tests are written by the task spec and must not be modified to make them pass
|
||||
- If a test fails, fix the **implementation**, not the test
|
||||
- Exception: test data fixtures can be extended, but existing assertions must not be weakened
|
||||
|
||||
### Backend Test Conventions
|
||||
- **Framework:** pytest + pytest-asyncio + httpx.AsyncClient
|
||||
- **Test DB:** Separate PostgreSQL database `erp_test` (NOT SQLite)
|
||||
- **Fixtures:** `conftest.py` provides:
|
||||
- `async_db` – async SQLAlchemy session (rolled back after each test)
|
||||
- `client` – httpx AsyncClient with app
|
||||
- `auth_headers` – JWT headers for admin/verkaeufer/buchhaltung roles
|
||||
- `seed_data` – minimal seed data (users, vehicle, contact)
|
||||
- **Mocking:** OpenRouter API MUST be mocked in all tests (no real API calls)
|
||||
- **Naming:** `test_<feature>_<scenario>.py` or `test_<module>.py`
|
||||
|
||||
### Frontend Test Conventions
|
||||
- **Framework:** Vitest + React Testing Library
|
||||
- **E2E:** Playwright for critical paths (login, vehicle create, sale wizard)
|
||||
- **Mocking:** API calls mocked via `vi.mock()` or MSW (Mock Service Worker)
|
||||
- **Naming:** `ComponentName.test.tsx` in `__tests__/` folder
|
||||
|
||||
### Test Spec Compliance
|
||||
Every task in `task_graph.json` has a `test_spec` with:
|
||||
- `commands` – Exact commands to run
|
||||
- `expected_results` – What success looks like
|
||||
- `test_files` – Expected test file paths
|
||||
- `coverage_target` – Minimum coverage percentage
|
||||
|
||||
**ALL commands in test_spec.commands MUST pass before a task is considered done.**
|
||||
|
||||
---
|
||||
|
||||
## 4. Code Conventions
|
||||
|
||||
### Python (Backend)
|
||||
```python
|
||||
# Naming
|
||||
snake_case for variables, functions, methods, modules
|
||||
PascalCase for classes (Models, Schemas, Services)
|
||||
UPPER_SNAKE_CASE for constants
|
||||
|
||||
# Imports (ruff isort)
|
||||
# Standard library
|
||||
import os
|
||||
from datetime import datetime
|
||||
# Third party
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
# Local
|
||||
from app.deps import get_db, get_current_user
|
||||
from app.models.vehicle import Vehicle
|
||||
from app.schemas.vehicle import VehicleCreate, VehicleResponse
|
||||
|
||||
# Type hints (mandatory)
|
||||
async def get_vehicle(vehicle_id: UUID, db: AsyncSession) -> Vehicle:
|
||||
...
|
||||
|
||||
# Async everywhere (SQLAlchemy 2.0 async)
|
||||
result = await db.execute(select(Vehicle).where(Vehicle.id == vehicle_id))
|
||||
vehicle = result.scalar_one_or_none()
|
||||
|
||||
# Error handling
|
||||
if vehicle is None:
|
||||
raise HTTPException(status_code=404, detail={"error": {"code": "NOT_FOUND", "message": "Vehicle not found"}})
|
||||
```
|
||||
|
||||
### TypeScript (Frontend)
|
||||
```typescript
|
||||
// Naming
|
||||
camelCase for variables, functions, hooks
|
||||
PascalCase for components, types, interfaces
|
||||
UPPER_SNAKE_CASE for constants
|
||||
|
||||
// Imports order
|
||||
// 1. React/Next
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
// 2. Third party
|
||||
import { useTranslations } from 'next-intl';
|
||||
// 3. Local
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { apiClient } from '@/lib/api-client';
|
||||
|
||||
// Components: function declaration with explicit return type
|
||||
export function FahrzeugListe(): JSX.Element {
|
||||
...
|
||||
}
|
||||
|
||||
// Types: interface for props
|
||||
type FahrzeugListeProps = {
|
||||
vehicles: Vehicle[];
|
||||
isLoading: boolean;
|
||||
};
|
||||
```
|
||||
|
||||
### File Organization
|
||||
- **One model per file** in `models/`
|
||||
- **One router per module** in `routers/`
|
||||
- **One service per domain** in `services/`
|
||||
- **One schema set per module** in `schemas/`
|
||||
- **Components grouped by feature** in `components/<feature>/`
|
||||
|
||||
---
|
||||
|
||||
## 5. Forbidden Patterns
|
||||
|
||||
### Backend
|
||||
- ❌ **No raw SQL queries** – always use SQLAlchemy ORM
|
||||
- ❌ **No synchronous database calls** – always async (`await db.execute(...)`)
|
||||
- ❌ **No secrets in code** – use environment variables / pydantic-settings
|
||||
- ❌ **No `print()` in production code** – use Python `logging`
|
||||
- ❌ **No bare `except:`** – always catch specific exceptions
|
||||
- ❌ **No `# type: ignore`** without comment explaining why
|
||||
- ❌ **No circular imports** – use dependency injection
|
||||
- ❌ **No business logic in routers** – routers only validate + call service
|
||||
- ❌ **No direct model access from routers** – always go through service layer
|
||||
- ❌ **No unencrypted PII** – Ausweisdaten MUST be AES-256 encrypted
|
||||
- ❌ **No real OpenRouter API calls in tests** – always mock
|
||||
- ❌ **No `commit()` in services without explicit reason** – let caller control transactions
|
||||
|
||||
### Frontend
|
||||
- ❌ **No `any` type** – use proper TypeScript types
|
||||
- ❌ **No `console.log` in production** – use a logger utility
|
||||
- ❌ **No inline styles** – use Tailwind classes or CSS modules
|
||||
- ❌ **No direct `fetch()` without auth wrapper** – use `apiClient`
|
||||
- ❌ **No hardcoded API URLs** – use `NEXT_PUBLIC_API_URL` env var
|
||||
- ❌ **No hardcoded translation strings** – use `t('key')` from next-intl
|
||||
- ❌ **No prop drilling > 2 levels** – use context or state management
|
||||
- ❌ **No `useEffect` for data fetching without loading/error states**
|
||||
- ❌ **No forms without validation** – all inputs must have validation
|
||||
- ❌ **No missing `aria-label`** on icon-only buttons
|
||||
|
||||
### General
|
||||
- ❌ **No commits to main without passing tests**
|
||||
- ❌ **No large PRs** – max 800 lines per task
|
||||
- ❌ **No TODO comments without ticket reference**
|
||||
- ❌ **No commented-out code in PRs**
|
||||
- ❌ **No files > 500 lines** – split into modules
|
||||
|
||||
---
|
||||
|
||||
## 6. Token Rule for File Operations
|
||||
|
||||
When working with files (reading, creating, updating):
|
||||
|
||||
### Backend (Forgejo API)
|
||||
```json
|
||||
// READ file (only for MODIFY, not for reference)
|
||||
{"tool_name":"forgejo","tool_args":{"action":"files_get","owner":"Leopoldadmin","repo":"erp-nutzfahrzeuge","path":"backend/app/models/vehicle.py"}}
|
||||
|
||||
// CREATE new file
|
||||
{"tool_name":"forgejo","tool_args":{"action":"files_create","owner":"Leopoldadmin","repo":"erp-nutzfahrzeuge","path":"backend/app/models/vehicle.py","content":"<content>","message":"Add vehicle model","branch":"main"}}
|
||||
|
||||
// UPDATE existing file (requires SHA from files_get)
|
||||
{"tool_name":"forgejo","tool_args":{"action":"files_update","owner":"Leopoldadmin","repo":"erp-nutzfahrzeuge","path":"backend/app/models/vehicle.py","content":"<updated content>","sha":"<SHA>","message":"Update vehicle model","branch":"main"}}
|
||||
|
||||
// CHECK file existence
|
||||
{"tool_name":"forgejo","tool_args":{"action":"files_list","owner":"Leopoldadmin","repo":"erp-nutzfahrzeuge","path":"backend/app/models/"}}
|
||||
```
|
||||
|
||||
### Rules
|
||||
- Use `files_get` ONLY when you need to modify an existing file (need SHA)
|
||||
- Use `files_create` for new files
|
||||
- Use `files_list` to check existence before creating
|
||||
- **NEVER** copy file contents inline in task descriptions – reference by path
|
||||
- **NEVER** read entire files just for reference – use `curl + sed` for snippets
|
||||
- For large reference content (architecture.md, requirements.md): reference by path, don't inline
|
||||
|
||||
---
|
||||
|
||||
## 7. Task Execution Workflow
|
||||
|
||||
### For implementation_engineer
|
||||
|
||||
1. **Read the task** from `task_graph.json` (task ID: T0X)
|
||||
2. **Read architecture.md** sections relevant to the task (DB schema, API design, ADRs)
|
||||
3. **Read requirements.md** for the specific feature IDs listed in `requirement_ids`
|
||||
4. **Implement backend first:** Model → Schema → Service → Router → Tests
|
||||
5. **Implement frontend:** Components → Pages → Tests
|
||||
6. **Run ALL test_spec.commands** and ensure they pass
|
||||
7. **Run lint** (ruff for backend, eslint for frontend)
|
||||
8. **Run build** (npm run build for frontend)
|
||||
9. **Report results** with test output evidence (not just "done")
|
||||
|
||||
### Evidence Requirements (MANDATORY)
|
||||
- Test command output showing pass/fail counts
|
||||
- Coverage report showing ≥80%
|
||||
- Lint output showing 0 errors
|
||||
- Build output showing success
|
||||
- "File written" is NOT evidence. "Commit made" is NOT evidence.
|
||||
- Test output with pass counts IS evidence.
|
||||
|
||||
### Task Completion Checklist
|
||||
- [ ] All acceptance_criteria from task_graph.json verified
|
||||
- [ ] All test_spec.commands pass
|
||||
- [ ] Coverage ≥ coverage_target for covered modules
|
||||
- [ ] Ruff lint clean (backend)
|
||||
- [ ] ESLint clean (frontend)
|
||||
- [ ] Build succeeds (frontend)
|
||||
- [ ] No forbidden patterns introduced
|
||||
- [ ] Test evidence provided in report
|
||||
|
||||
---
|
||||
|
||||
## 8. Environment Variables (Names Only)
|
||||
|
||||
### Backend
|
||||
```
|
||||
DATABASE_URL=postgresql+asyncpg://erp_user:${DB_PASSWORD}@postgres:5432/erp_db
|
||||
REDIS_URL=redis://redis:6379/0
|
||||
JWT_SECRET=${JWT_SECRET}
|
||||
JWT_ALGORITHM=HS256
|
||||
JWT_ACCESS_TTL_MINUTES=15
|
||||
JWT_REFRESH_TTL_DAYS=7
|
||||
ENCRYPTION_KEY=${ENCRYPTION_KEY}
|
||||
OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
|
||||
MOBILE_DE_API_KEY=${MOBILE_DE_API_KEY}
|
||||
MOBILE_DE_SELLER_ID=${MOBILE_DE_SELLER_ID}
|
||||
UPLOAD_DIR=/data/uploads
|
||||
MAX_FILE_SIZE_MB=50
|
||||
CORS_ORIGINS=https://erp.domain.tld
|
||||
```
|
||||
|
||||
### Frontend
|
||||
```
|
||||
NEXT_PUBLIC_API_URL=http://backend:8000/api/v1
|
||||
NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Git Workflow
|
||||
|
||||
### Branches
|
||||
- `main` – Production branch, deployed via Coolify
|
||||
- `feature/T0X-<short-name>` – Feature branch per task
|
||||
- `fix/T0X-<short-name>` – Fix branch
|
||||
|
||||
### Commit Messages
|
||||
```
|
||||
feat(T02): add vehicle CRUD with type-specific fields
|
||||
fix(T02): correct FIN validation for 17-char check
|
||||
test(T02): add mobile.de batch push tests
|
||||
refactor(T02): extract field mapping to separate function
|
||||
docs(T02): update vehicle API documentation
|
||||
```
|
||||
|
||||
### PR Process
|
||||
1. Create feature branch from main
|
||||
2. Implement + test + lint + build
|
||||
3. Commit with conventional commit format
|
||||
4. Push branch
|
||||
5. Create PR with task ID reference
|
||||
6. CI runs: pytest, ruff, vitest, eslint, build
|
||||
7. Merge after CI passes
|
||||
|
||||
---
|
||||
|
||||
## 10. Database Migration Rules
|
||||
|
||||
- **Always use Alembic** for schema changes
|
||||
- **Never** modify database directly (psql, pgadmin)
|
||||
- **One migration per schema change** – don't bundle multiple changes
|
||||
- **Test migration up AND down** before committing
|
||||
- **Seed data:** Use a separate seed script (`scripts/seed.py`), not migrations
|
||||
- **Cleanup after migration tasks:**
|
||||
```bash
|
||||
alembic upgrade head # Apply
|
||||
# Verify all tables created
|
||||
python -c "from app.database import engine; from sqlalchemy import inspect; insp = inspect(engine); print(insp.get_table_names())"
|
||||
# Run seed data
|
||||
python scripts/seed.py
|
||||
# Verify foreign keys
|
||||
docker-compose exec postgres psql -U erp_user -d erp_db -c "\d vehicles"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. OpenRouter Integration Rules
|
||||
|
||||
- **Always use** `openrouter_client.py` shared client – never call OpenRouter API directly
|
||||
- **Always set timeout** to 30 seconds
|
||||
- **Always log** interaction to `ai_interactions` table (type, model, tokens, metadata)
|
||||
- **Never send** Ausweisdaten or personal customer data to OpenRouter
|
||||
- **Mock OpenRouter** in all automated tests
|
||||
- **Handle errors gracefully:** API error → 502, timeout → 504, rate limit → 429
|
||||
- **Model selection** via config, not hardcoded in service
|
||||
|
||||
---
|
||||
|
||||
## 12. Security Checklist
|
||||
|
||||
- [ ] JWT authentication on all endpoints (except /auth/login, /auth/refresh, /health)
|
||||
- [ ] RBAC enforced (require_role decorator on every router)
|
||||
- [ ] Input validation via Pydantic schemas on all endpoints
|
||||
- [ ] SQL injection protection via SQLAlchemy ORM (no raw SQL)
|
||||
- [ ] File upload: MIME-type allowlist + size limit
|
||||
- [ ] Ausweisdaten: AES-256-GCM encryption
|
||||
- [ ] CORS: only configured origins
|
||||
- [ ] Audit log: all create/update/delete actions logged
|
||||
- [ ] No secrets in code or git
|
||||
- [ ] HTTPS via Traefik/Let's Encrypt
|
||||
- [ ] DSGVO: no PII to OpenRouter, soft-delete with retention concept
|
||||
Reference in New Issue
Block a user