# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## System Overview ScalesApp is a three-tier weighing scale management system with real-time data synchronization: - **Django Backend** (REST API + Server-Sent Events) on port 8000 - **React Frontend** (SPA) on port 3000 - **Serial Bridge** (Flask SSE server) on port 5000 for COM port reading ## Development Commands ### Backend (Django) ```bash cd backend python -m venv venv venv\Scripts\activate # Windows: venv\Scripts\activate | Unix: source venv/bin/activate pip install -r requirements.txt # Database operations python manage.py makemigrations python manage.py migrate python manage.py createsuperuser # Run server python manage.py runserver # Development (WSGI) uvicorn scalesapp.asgi:application --reload # Development (ASGI - for SSE) # Testing (when implemented) python manage.py test python manage.py test api.tests.test_models # Single test file ``` ### Frontend (React) ```bash cd frontend npm install npm start # Development server npm run build # Production build npm test # Run tests ``` ### Serial Bridge ```bash cd serial_bridge python -m venv venv venv\Scripts\activate pip install -r requirements.txt python app.py # Run Flask SSE server # Build standalone executable pyinstaller serial_bridge.spec ``` ### Testing COM Port (requires com0com) ```bash cd test_comport_writer python test_writer.py # Sends test data to virtual COM port ``` ## Architecture ### Django Backend Structure **Three Django Apps:** - **`api/`** - Core API, user management, COM port readings, report templates - **`vehicles/`** - Vehicle weighing management with dynamic nomenclature data - **`nomenclatures/`** - Runtime-configurable schema system (lookup tables and custom fields) **Custom User Model:** `api.User` (extends AbstractUser) - Fields: `role` (employee/viewer), `is_admin` (boolean) - All authentication requires JWT tokens **Database:** PostgreSQL (production), SQLite (development fallback) **Key Models:** - `User` - Custom user with roles - `ComPortReading` - Serial port data log - `Report` - Report templates (JSONField stores element definitions) - `Vehicle` - Core weighing entity (vehicle_number, tare, gross, with user/timestamp tracking) - `VehicleExtra` - OneToOne with Vehicle, JSONField for dynamic nomenclature data - `Nomenclature` - Configurable data types (applies_to: vehicle/container, kind: lookup/field) - `NomenclatureField` - Field definitions (text, number, bool, choice types) - `NomenclatureEntry` - Data entries for lookup tables **ViewSet Pattern:** All views use DRF's ModelViewSet with custom actions via `@action` decorator - Examples: `@action(detail=True, methods=['post']) def set_tare()` ### Real-Time Communication (SSE) **Implementation:** `backend/scalesapp/sse.py` - ASGI-based async SSE endpoint: `/sse-connect/` - JWT authentication via query parameter or Authorization header - Global `updates_queue` with 100-event buffer - Event ID tracking for reconnection/replay of missed events - `sse_broadcast_update(operation, model, data)` called from views after mutations - Auto-reconnect with Last-Event-ID header support **Usage in Views:** ```python from scalesapp.sse import sse_broadcast_update def create(self, request): # ... create logic ... sse_broadcast_update('insert', 'vehicle', serializer.data) ``` ### Nomenclature System (Dynamic Schema) **Purpose:** Runtime-configurable extra fields without database migrations **Flow:** 1. Admin creates `Nomenclature` (e.g., "CARGO_TYPE", kind=lookup, applies_to=vehicle) 2. Defines `NomenclatureField`s (e.g., name: text, code: text) 3. Creates `NomenclatureEntry` records (e.g., "Coal", "Iron Ore") 4. Frontend loads nomenclatures on mount via REST API 5. User selects from dropdown → stores entry ID in `vehicle.extra.data.CARGO_TYPE` (JSONField) 6. Validation in `VehicleExtraSerializer` enforces type constraints ### React Frontend Structure **Context Providers (State Management):** - **`AuthContext`** - JWT token management, auto-refresh every 14 minutes, axios interceptor for 401 handling - **`NomenclatureContext`** - Centralized data cache with SSE subscription to Django `/sse-connect/` - Real-time updates (insert/update operations) - Maintains sorted arrays for all nomenclatures - Auto-reconnect on connection loss - Parallel loading of all nomenclature endpoints on mount - **`DataContext`** - Report editor data fetching (for preview mode) - **`BandContext`** - Report editor band iteration state **Key Components:** - **`Main.jsx`** - Primary weighing interface - Left panel: Vehicle list with search - Right panel: Selected vehicle details with dynamic nomenclature fields - Real-time weight display from serial bridge - Tare/Gross weight setting with user/timestamp tracking - SSE-based automatic UI updates - **`ReportEditor/`** - Custom report builder (see Report Editor section) **API Communication:** `services/api.js` - Axios instance with JWT interceptor - Token refresh queue (pauses failed requests during refresh) - Auto-logout on refresh failure **Routing:** - `/` - Main weighing interface (protected) - `/report-editor` - Report designer (protected) - Shows Login component if not authenticated ### Serial Bridge Architecture **Components:** - `app.py` - Flask SSE server on port 5000 - `serial_reader.py` - SerialPortReader class with threading - `tray_icon.py` - Windows system tray integration **Data Flow:** 1. Thread reads from COM port continuously (pyserial) 2. Data queued in global `data_queue` 3. SSE endpoint `/events` streams to frontend 4. Frontend's `useSerialData` hook receives updates 5. **Note:** Serial bridge is independent of Django (direct SSE to frontend) **Configuration:** `.env` file with COM_PORT, BAUD_RATE, TIMEOUT, READ_INTERVAL ### Real-Time Data Flow Example ``` COM Port → Serial Bridge (Flask SSE :5000) → Frontend useSerialData hook ↓ Main.jsx displays weight ↓ User clicks "Set Tare" ↓ POST /api/vehicles/{id}/set-tare/ ↓ Django sse_broadcast_update() ↓ SSE → Frontend NomenclatureContext ↓ Vehicle list auto-updates ``` ## Report Editor System **Architecture:** Custom drag-drop report designer with grid-based layout **Grid System:** - Page dimensions: 80 columns × 66 rows (character grid) - All elements positioned in grid coordinates - Pixel conversion: `x * charWidth`, `y * charHeight` **Element Types (OOP Class Hierarchy):** ``` Element (base class) ├── TextElement (static text) ├── DBTextElement (data-bound field with objectKey/fieldPath) ├── FrameElement (borders with box-drawing characters) ├── HorizontalLineElement ├── VerticalLineElement ├── SymbolElement (single character) └── BandElement (repeating section with children[]) ``` **Band Types:** - `header` - Page header - `footer` - Page footer - `detail` - Main repeating data section - `subdetail` - Nested repeating (child of detail) - `summary` - Summary/totals section **Key Components:** - `ReportEditor.jsx` - Main orchestrator with save/load to Django `/api/reports/` - `EditorCanvas.jsx` - Canvas rendering, drag/drop, multi-select - `Toolbar.jsx` - Tool selection (text, frame, lines, bands, etc.) - `ObjectInspector.jsx` - Property editor for selected elements - `ConfigPanel.jsx` - API endpoint configuration - `CharacterPalette.jsx` - Box-drawing character picker - `models/Element.jsx` - Element class definitions **Data Binding:** - `DBTextElement` has `objectKey` and `fieldPath` - Example: objectKey="invoice", fieldPath="customer.name" - Preview mode fetches data from configured `apiEndpoint` - BandContext provides current item during band iteration **Persistence:** - Entire report serialized to JSON - Saved to Django `Report` model (JSONField stores elements array) **Features:** - Multi-select with drag/drop - Resize handles - Keyboard shortcuts (Delete, Ctrl+S, etc.) - Grid snapping - Character palette for box-drawing - Preview mode with live data ## Authentication & Security **JWT Token Flow:** - Access token: 15 minutes lifetime - Refresh token: 7 days lifetime - Stored in localStorage (XSS risk - consider httpOnly cookies for production) - Frontend auto-refreshes tokens every 14 minutes - All API requests require Bearer token (except `/api/token/` and `/api/token/refresh/`) - SSE connection authenticated via query param: `/sse-connect/?token=` or Authorization header **CORS Configuration:** - Allowed origins: http://localhost:3000, http://127.0.0.1:3000, http://localhost:5174 - Credentials allowed (for cookies if needed) **User Roles:** - `employee` - Full access to weighing operations - `viewer` - Read-only access - `is_admin` - Access to Django admin panel ## Key Technical Decisions **Why SSE instead of WebSockets?** - Simpler server implementation (one-way communication sufficient) - Auto-reconnect built into EventSource API - Event ID-based replay for missed events - HTTP-based (easier to proxy/firewall) **Why ASGI?** - Required for async SSE endpoint - Uses uvicorn server - `sync_to_async` wrapper for ORM queries in SSE handler **Why Separate Serial Bridge?** - Independence from Django (can restart Django without losing COM connection) - System tray integration for user control - Portable executable deployment - Dedicated threading for serial I/O **Why Nomenclature System?** - Avoid schema migrations for custom fields - Different deployments have different requirements - User-configurable without code changes - Validation still enforced via serializers ## Important Files for Understanding Architecture 1. `backend/scalesapp/settings.py` - Django configuration, JWT, CORS, database 2. `backend/scalesapp/sse.py` - Real-time SSE implementation 3. `backend/api/models.py` - Core models (User, ComPortReading, Report) 4. `backend/vehicles/models.py` - Vehicle and VehicleExtra with JSONField 5. `backend/nomenclatures/models.py` - Dynamic schema system 6. `frontend/src/App.jsx` - Routing and AuthProvider setup 7. `frontend/src/contexts/NomenclatureContext.jsx` - SSE client and data cache 8. `frontend/src/contexts/AuthContext.jsx` - JWT token management 9. `frontend/src/components/Main.jsx` - Primary weighing interface 10. `frontend/src/components/ReportEditor/ReportEditor.jsx` - Report builder 11. `serial_bridge/app.py` - Flask SSE server for COM port ## Configuration Files **Backend:** `backend/.env` ``` SECRET_KEY=your-secret-key DEBUG=True ALLOWED_HOSTS=localhost,127.0.0.1 DB_NAME=scalesapp DB_USER=postgres DB_PASSWORD= DB_HOST=localhost DB_PORT=5432 ``` **Frontend:** `frontend/.env` ``` REACT_APP_API_URL=http://localhost:8000 ``` **Serial Bridge:** `serial_bridge/.env` ``` COM_PORT=COM1 BAUD_RATE=9600 BACKEND_URL=http://localhost:8000 AUTO_CONNECT=True DEBUG=False ``` ## Running the Full Application 1. Start Django: `cd backend && python manage.py runserver` 2. Start Serial Bridge: `cd serial_bridge && python app.py` 3. Start React: `cd frontend && npm start` For SSE to work properly in Django, use uvicorn instead: `uvicorn scalesapp.asgi:application --reload` ## Common Patterns **Adding a new model:** 1. Define model in appropriate app (api/vehicles/nomenclatures) 2. Create serializer in `serializers.py` 3. Create ViewSet in `views.py` with `sse_broadcast_update()` calls 4. Register route in `urls.py` 5. Run `python manage.py makemigrations && python manage.py migrate` 6. Update frontend Context to subscribe to SSE updates **Adding a new nomenclature field:** 1. Create Nomenclature via Django admin or API 2. Define NomenclatureFields 3. Frontend auto-loads on next mount 4. No code changes needed (dynamic rendering in Main.jsx) **Adding a report element type:** 1. Create new Element subclass in `models/Element.jsx` 2. Add component file (e.g., `MyElement.jsx`) 3. Register in `EditorCanvas.jsx` render switch 4. Add tool button in `Toolbar.jsx` 5. Add properties in `ObjectInspector.jsx`