12 KiB
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)
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)
cd frontend
npm install
npm start # Development server
npm run build # Production build
npm test # Run tests
Serial Bridge
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)
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 templatesvehicles/- Vehicle weighing management with dynamic nomenclature datanomenclatures/- 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 rolesComPortReading- Serial port data logReport- 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 dataNomenclature- 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_queuewith 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:
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:
- Admin creates
Nomenclature(e.g., "CARGO_TYPE", kind=lookup, applies_to=vehicle) - Defines
NomenclatureFields (e.g., name: text, code: text) - Creates
NomenclatureEntryrecords (e.g., "Coal", "Iron Ore") - Frontend loads nomenclatures on mount via REST API
- User selects from dropdown → stores entry ID in
vehicle.extra.data.CARGO_TYPE(JSONField) - Validation in
VehicleExtraSerializerenforces type constraints
React Frontend Structure
Context Providers (State Management):
AuthContext- JWT token management, auto-refresh every 14 minutes, axios interceptor for 401 handlingNomenclatureContext- 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 5000serial_reader.py- SerialPortReader class with threadingtray_icon.py- Windows system tray integration
Data Flow:
- Thread reads from COM port continuously (pyserial)
- Data queued in global
data_queue - SSE endpoint
/eventsstreams to frontend - Frontend's
useSerialDatahook receives updates - 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 headerfooter- Page footerdetail- Main repeating data sectionsubdetail- 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-selectToolbar.jsx- Tool selection (text, frame, lines, bands, etc.)ObjectInspector.jsx- Property editor for selected elementsConfigPanel.jsx- API endpoint configurationCharacterPalette.jsx- Box-drawing character pickermodels/Element.jsx- Element class definitions
Data Binding:
DBTextElementhasobjectKeyandfieldPath- 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
Reportmodel (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=<jwt>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 operationsviewer- Read-only accessis_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_asyncwrapper 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
backend/scalesapp/settings.py- Django configuration, JWT, CORS, databasebackend/scalesapp/sse.py- Real-time SSE implementationbackend/api/models.py- Core models (User, ComPortReading, Report)backend/vehicles/models.py- Vehicle and VehicleExtra with JSONFieldbackend/nomenclatures/models.py- Dynamic schema systemfrontend/src/App.jsx- Routing and AuthProvider setupfrontend/src/contexts/NomenclatureContext.jsx- SSE client and data cachefrontend/src/contexts/AuthContext.jsx- JWT token managementfrontend/src/components/Main.jsx- Primary weighing interfacefrontend/src/components/ReportEditor/ReportEditor.jsx- Report builderserial_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
- Start Django:
cd backend && python manage.py runserver - Start Serial Bridge:
cd serial_bridge && python app.py - 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:
- Define model in appropriate app (api/vehicles/nomenclatures)
- Create serializer in
serializers.py - Create ViewSet in
views.pywithsse_broadcast_update()calls - Register route in
urls.py - Run
python manage.py makemigrations && python manage.py migrate - Update frontend Context to subscribe to SSE updates
Adding a new nomenclature field:
- Create Nomenclature via Django admin or API
- Define NomenclatureFields
- Frontend auto-loads on next mount
- No code changes needed (dynamic rendering in Main.jsx)
Adding a report element type:
- Create new Element subclass in
models/Element.jsx - Add component file (e.g.,
MyElement.jsx) - Register in
EditorCanvas.jsxrender switch - Add tool button in
Toolbar.jsx - Add properties in
ObjectInspector.jsx