You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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 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:

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 NomenclatureFields (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=<jwt> or Authorization header

CORS Configuration:

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