LogiTrain/ Spécification (SPEC)

InTech LogiTrain — Odoo Logistics Training Suite for ATFP

Technical Build Specification — v0.1 (draft)

Client order ATFP — Bon de commande n° 36001/36002, exercice 2026 (4 lines, 40,252.100 TND HT)
Audience InTech Cairo engineering — build team. Internal document, not for the client.
Status Draft. Functional detail is derived from the BC line descriptions. The four cahiers des charges techniques (CDC n° 1–4) have not been received; where they differ, they override this document. Items likely to move are tagged [CDC].
Owner A. Nasser (scope, client) — tech lead TBD
Date 8 September 2026
Location docs/SPEC.md at the root of the suite repo; CLAUDE.md points here

Working name LogiTrain, technical prefix logitrain_. Rename before the first release if needed, but keep the prefix product-neutral: the suite is meant to be reusable across ATFP centres and other training bodies.


1. Scope

Four pedagogical software packages delivered as Odoo modules and installed on the ATFP centre's server. Each must let trainees (apprenants) work "in conditions close to real life". One functional module per BC line, plus a shared base and a demo-data module.

BC line BC description (abridged) Module Native Odoo reused
1 Delivery-tour management with map integration and route optimisation; regulatory and operational transport constraints logitrain_tour stock (deliveries), base_geolocalize, fleet
2 Load planning with 3D optimisation — vehicles, containers, pallets; volume, weight, stability logitrain_loadplan product, stock (package types)
3 Integrated fleet management — administrative, technical, financial aspects logitrain_fleet fleet, hr_fleet, account_fleet
4 WMS integrating RFID — stock management, RFID traceability, logistics best practice logitrain_wms + rfid-bridge service stock, stock_picking_batch, product_expiry, barcodes
Training framework, shared catalogue, map/3D assets, settings, security logitrain_base base, mail, web
Tunisian demo datasets, scenarios and exercises logitrain_demo_tn

Out of scope unless a CDC says otherwise: RFID hardware supply, GPS/telematics, mobile apps, accounting localisation, any integration with the centre's production ERP data.


2. Gating inputs — do not start UI or deployment work until these are answered

# Input Why it matters Owner
G1 CDC n° 1–4 Acceptance is checked line by line against them AN
G2 ATFP server: OS, CPU/RAM/disk, Docker allowed, SSH access, who administers it Sizing, deployment topology AN
G3 Existing Odoo: version, Community/Enterprise, on-prem vs Odoo.sh/Online, production instance or not Decisions D1/D2 AN
G4 Outbound Internet from the server and from classroom PCs Online vs offline tiles/geocoding AN
G5 RFID hardware: supplied by whom, reader model(s) if known Bridge adapter, EPC scheme AN
G6 Users: number of trainers/trainees, browsers, UI language (FR only or FR+AR) Sizing, i18n AN
G7 Contract: deadline, penalties, warranty, IP clause, formation des formateurs Delivery plan, licence choice AN

Until G1 arrives, build against the BC wording and this spec.


3. Architecture decisions

D1 — Topology: dedicated Odoo instance for the training suite, in Docker, on the ATFP server. Training data must be resettable per cohort, trainees will break things, and the production ERP's version must not dictate ours. Fallback if the centre insists on its existing instance: install into a separate database on that instance; D2 then becomes "match their version" and the JS layer is re-estimated (below 16.0: +30 %).

D2 — Version: Odoo 18.0 Community baseline. 19.0 is acceptable only if every dependency in §4 is verified available for it in Sprint 0. Use 18.0 model names throughout (stock.lot, stock.route, stock.package.type); isolate anything version-specific.

D3 — No Enterprise dependencies. No web_map, stock_barcode, web_gantt, quality, industry_fsm. Map = Leaflet; scanning = our own scan console (+ optional OCA stock_barcodes); timeline = simple OWL component or none. CI greps for enterprise imports.

D4 — Optimisation engines self-hosted, with degraded fallbacks. Routing and distance matrices: OSRM on a Tunisia OSM extract. Vehicle routing: VROOM (vroom-express) on top of OSRM. 3D packing: in-process Python solver, no service. If OSRM/VROOM is unreachable the tour module still works (Haversine matrix, nearest-neighbour + 2-opt) and shows an "engine offline — approximate mode" banner. A lesson must never be blocked by an engine.

D5 — Training data lifecycle: golden template databases. One template DB per module (or one combined template) built from logitrain_demo_tn. The trainer duplicates the template per cohort from the DB manager (reachable only from admin/trainer IPs, see §11). No in-database "reset" mechanism in v1.

D6 — Licence. Code must be licensable on InTech's terms (OPL-1 or LGPL-3; management decides before the first tag; manifest license key set accordingly). Vendored libraries: Leaflet (BSD-2), Three.js (MIT), protomaps-leaflet (BSD-3), @mapbox/polyline (BSD-3). Nothing GPL-only inside the addons.

D7 — Third-party JS is pre-bundled, not fed to Odoo's asset pipeline. Three.js (ESM-only since r160) and protomaps-leaflet are bundled with esbuild into IIFE files under static/lib/, exposing window.LogitrainMap and window.LogitrainViewer; OWL components call those globals. Leaflet's UMD build can go straight into web.assets_backend.


4. Module map and repository

intech-logitrain/
├── addons/
│   ├── logitrain_base/
│   ├── logitrain_fleet/
│   ├── logitrain_wms/
│   ├── logitrain_tour/
│   ├── logitrain_loadplan/
│   ├── logitrain_tour_loadplan/   # auto-install glue: load plan from a tour
│   └── logitrain_demo_tn/
├── frontend/          # esbuild sources for the map and 3D viewer bundles
├── rfid-bridge/       # Python service: RFID reader -> Odoo
├── deploy/            # docker-compose, nginx, osrm/vroom/tiles build scripts, backup
├── docs/              # SPEC.md (this file), install, admin, trainer and trainee guides
└── .github/workflows/ # lint + Odoo tests

Dependency graph: logitrain_base ← {logitrain_fleet, logitrain_wms, logitrain_tour, logitrain_loadplan} ← logitrain_demo_tn. The four functional modules must stay installable independently: a centre may buy only some of them.

Third-party Odoo dependencies to verify for the chosen version in Sprint 0: OCA stock_barcodes (optional), OCA fleet_vehicle_log_fuel (optional — otherwise we add fuel fields ourselves, §6).


5. logitrain_base

Purpose: training framework, shared catalogue, shared JS assets, settings, security.

5.1 Security groups (security/groups.xml, category "LogiTrain")

Record rules: trainee-owned models carry owner_id (res.users). Trainee rule ['|', ('owner_id', '=', user.id), ('owner_id', '=', False)]; trainers unrestricted.

5.2 Models

Model Key fields Notes
logitrain.cohort name, trainer_id, trainee_ids (m2m res.users), date_start, date_end, module_ids Groups users; drives exercise assignment
logitrain.exercise name, module (selection: tour / loadplan / fleet / wms), cohort_id, instructions (html), reference_model, reference_id (dataset anchor), expected_kpis (json), due_date Trainer-authored
logitrain.exercise.submission exercise_id, trainee_id, res_model, res_id (the trainee's record), kpis (json snapshot), score, feedback, state (draft / submitted / graded) Each functional module implements _logitrain_kpis() on its main model
logitrain.container.type name, kind (van / truck / trailer / container / pallet), inner_length, inner_width, inner_height (mm), max_payload (kg), max_volume (computed), door_width, door_height, axle_front_pos, axle_rear_pos (mm, optional), stackable (pallets) Shared by tour and load plan; fleet.vehicle gets an optional m2o to it
logitrain.vehicle.profile container_type_id, speed_factor, cost_per_km, cost_per_hour, fixed_cost, skill_ids (tags), max_stops Used by the tour optimiser; fleet.vehicle gets an optional m2o to it

5.3 Settings (res.config.settings, all stored as config_parameter)

logitrain.osrm_url, logitrain.vroom_url, logitrain.tiles_url (XYZ template or .pmtiles URL), logitrain.nominatim_url (blank = geocoding disabled), logitrain.rfid_token_required (bool), logitrain.offline_mode (bool), logitrain.default_tour_rules_id.

5.4 Shared JS (static/src/ plus bundles built in frontend/)

5.5 Python helpers

logitrain.tools: unit conversions (mm, m³, kg), polyline6 decoding, Haversine, chunked matrix requests. Abstract mixin logitrain.kpi.mixin: kpis_json field, _logitrain_kpis(), action_snapshot().


6. logitrain_fleet — BC line 3

Depends: logitrain_base, fleet, hr_fleet, account_fleet (requires account = Invoicing, Community).

Native Fleet already covers vehicles, models and brands, drivers with assignment history, contracts (insurance, leasing) with expiry alerts, services with types and states, odometer, costs and reporting. This module adds the Tunisian and pedagogical layer.

Area What to build
Regulatory documents logitrain.fleet.document: vehicle_id, doc_type (carte_grise / assurance / vignette / visite_technique / other), number, issue_date, expiry_date, cost, attachment, state (computed: valid / expiring / expired). Daily cron creates mail.activity for the fleet manager 30 and 7 days before expiry; kanban colour by state.
Preventive maintenance logitrain.fleet.maintenance.plan: model_id or vehicle_id, service_type_id, interval_km, interval_months, lead_km, lead_days. Cron compares odometer and dates, creates planned fleet.vehicle.log.services (state new) plus an activity.
Fuel and consumption If OCA fleet_vehicle_log_fuel exists for the target version, depend on it. Otherwise extend fleet.vehicle.log.services with is_fuel, liter, price_per_liter, odometer_at_fill, full_tank. Computed L/100 km per full-to-full fill and per vehicle per month; anomaly flag when consumption exceeds the model's reference value × configurable threshold.
Missions (carnet de bord) logitrain.fleet.mission: vehicle_id, driver_id, date, origin, destination, purpose, odometer_start, odometer_end, distance (computed), fuel_cost, state (draft / ongoing / done). Optional m2o to logitrain.tour.
Incidents logitrain.fleet.incident: vehicle_id, driver_id, date, incident_type (accident / fine / breakdown / theft), description, cost, claim_state, attachment.
Drivers Extend the driver partner / hr.employee: licence categories (A, B, C, D, E…), licence_expiry, medical_check_date. Same expiry cron.
Finance SQL report logitrain.fleet.tco.report per vehicle and month: contract cost (recurring cost from fleet.vehicle.log.contract), services, fuel, depreciation (purchase value ÷ expected life in months), total, km, cost per km. Graph and pivot views. QWeb PDFs: "Fiche véhicule", monthly fleet report.
Dashboard Vehicles by state, expiries in the next 30 days, cost-per-km ranking, consumption trend.

Exercises shipped with the demo module: fleet creation and contracts; renewal planning (vignette, visite technique, assurance); maintenance plan and generated services; fuel-anomaly hunt; TCO and replace-or-keep decision; incident handling; monthly report.


7. logitrain_wms — BC line 4

Depends: logitrain_base, stock, stock_picking_batch, product_expiry, barcodes. Optional: OCA stock_barcodes.

Native Inventory already covers multi-warehouse, hierarchical locations, 1/2/3-step routes, putaway rules, removal strategies (FIFO / LIFO / FEFO), lots and serials with expiry, packages and package types, batch and wave picking, quant-based adjustments and cycle counts, reordering rules, traceability report, valuation. Most of line 4 is configuration + data + scanning + RFID.

7.1 Warehouse configuration (loaded by the demo module; structure fixed here)

Warehouse "Entrepôt pédagogique": receipts in 2 steps (receipt → putaway); deliveries in 3 steps (pick → pack → ship); zones RECEPTION / STOCK (racks A–C, aisle-rack-level bins) / QUARANTAINE / EXPEDITION / RETOURS; putaway rules by product category; FEFO on perishables; cycle-count frequency per location; reordering rules; package types (EUR pallet, half pallet, cartons).

7.2 Scan console (own, minimal — OWL client action logitrain_wms.scan_console)

7.3 RFID layer

Object Purpose
logitrain.rfid.reader name, kind (wedge / fixed_llrp / vendor_sdk), location_id, api_token, active, last_seen
logitrain.rfid.tag epc (96-bit hex, unique), scheme (sgtin96 / custom96), product_id, lot_id, package_id, state (encoded / assigned / retired)
logitrain.rfid.read epc, reader_id, antenna, rssi, timestamp, session_id — raw event log, pruned by cron after N days
logitrain.rfid.session reader_id, target (picking / batch / location count), state (open / closed), start, end. Reconciliation: expected tags vs seen tags → sets move-line quantities or count quantities; lists missing and unexpected tags
Controller POST /logitrain/rfid/reads JSON {reader, session?, reads: [{epc, antenna, rssi, ts}]}; token-authenticated; idempotent on (epc, ts)
Encoding wizard Generates EPCs for selected lots/serials/packages — SGTIN-96 from GTIN + serial, or custom 96-bit with company prefix; exports CSV and ZPL (^RF write commands) for RFID label printers, or prints human-readable labels with the EPC for manual encoding
Simulator Trainer-only page: a "virtual reader" emitting EPCs of chosen demo items into a session. Lets a whole class practise with one physical reader, and lets us test without hardware

7.4 rfid-bridge/ service

Python 3.11. Adapters: llrp (via sllurp; covers Impinj, Zebra and most LLRP-compliant fixed readers), serial_wedge (USB-serial readers), mock. YAML config (reader IP, antennas, Odoo URL, token, session policy). Batches reads every 500 ms, de-duplicates per EPC within a rolling window, retries with back-off. Ships as a systemd unit and as a Docker image.

7.5 Hardware assumptions [G5]

UHF EPC Gen2 (ISO 18000-63), ETSI 865–868 MHz — confirm the licence-free UHF band applicable in Tunisia before any purchase. Recommended minimum: one USB desktop reader (wedge mode) for the scan console, one fixed reader with two antennas for a dock-door demo, 500 label tags. A one-page hardware sheet goes to AN for procurement or for the centre.

7.6 Reports and KPIs

Stock by location/zone, occupancy %, picking accuracy, order cycle time, lot traceability ("fiche de traçabilité" PDF), inventory accuracy: RFID count vs system.

Exercises: receipt with ASN and putaway; replenishment; FEFO wave picking; packing to pallets; shipment and hand-off to a tour (logitrain_tour); cycle count by RFID sweep vs manual count; lot recall (traceability); returns.


8. logitrain_tour — BC line 1

Depends: logitrain_base, base_geolocalize, stock, fleet.

8.1 Models

Model Key fields
logitrain.tour.rules name; max_driving_per_day (min), max_continuous_driving (min), break_duration (min), max_working_amplitude (min), heavy_vehicle_speed_factor, night_window_start/end (optional), default cost parameters. Shipped defaults are EU-561-like placeholders [CDC — ATFP trainers to supply Tunisian values]
logitrain.tour.plan name, date, depot (partner or warehouse address with coordinates), rules_id, vehicle_ids (fleet.vehicle → profile), stop_ids (unassigned pool), tour_ids, state, plan-level KPIs
logitrain.tour plan_id (optional), name, date, vehicle_id, driver_id, depot coordinates, start_time, planned_end_time, stop_ids (ordered o2m), route_geometry (polyline6), distance_km, driving_min, working_min, wait_min, break_min, load_kg and load_m3 vs capacity, cost, violation_ids, state (draft / planned / validated / done), owner_id, kpis_json
logitrain.tour.stop tour_id, plan_id, sequence, partner_id, picking_id (optional outgoing stock.picking), lat, lng (default from partner), demand_kg, demand_m3, tw_start, tw_end, service_min, priority, skill_ids; computed: eta, etd, leg_distance_km, leg_min, tw_violation
logitrain.tour.violation tour_id, kind (capacity_kg / capacity_m3 / time_window / driving_time / amplitude / skill), stop_id, value, limit, severity
logitrain.tour.snapshot tour_id, label (manual / optimised / trainee), kpis_json, stop order, created_at — for comparisons

8.2 Services (models/services/)

8.3 Flows

  1. Create stops: manually with the map picker, from partners, or from outgoing pickings of the WMS (demand computed from move lines' product weight and volume).
  2. Manual planning: assign stops to a tour, drag to reorder → live recompute of route, ETAs, KPIs and violations on the map.
  3. Optimise: sequence only (single tour) or dispatch + sequence (plan, multi-vehicle) through VROOM; fallback heuristic when offline. The result is written as a new snapshot/tour and never silently overwrites the trainee's manual version.
  4. Compare: wizard showing two snapshots side by side — distance, time, cost, violations. This comparison is the core pedagogical output.
  5. Validate → "Feuille de route" PDF (stops, ETAs, legs, driver instructions); optional creation of a logitrain.fleet.mission.

8.4 UI

Tour form with LogitrainMap (numbered markers, polyline, click-to-add), stops list with handle widget, KPI cards, violations panel. Plan dashboard: unassigned pool, tours per vehicle, totals. List/kanban by date.

8.5 Accuracy notes

OSRM's car profile is used for every vehicle class; heavy-vehicle behaviour is approximated by heavy_vehicle_speed_factor on durations. Truck-specific routing (height/weight restrictions) is out of v1 [CDC].

Exercises: plan a 15-stop day manually, optimise, explain the gap; time-window scenario; overloaded-vehicle scenario; two-vehicle dispatch; driving-time and break scenario; cost comparison between vehicle choices.


9. logitrain_loadplan — BC line 2

Depends: logitrain_base, product, stock. Glue module logitrain_tour_loadplan (auto-install when both are present) creates a load plan from a tour, one group per stop, ordered so the first delivery is nearest the door.

9.1 Models

Model Key fields
logitrain.loadplan name, container_type_id, source (manual / tour), tour_id (via glue), item_ids; parameters: min_support_ratio (default 0.8), respect_delivery_order, allow_rotation, solver (ep_bfd / layer), seed; results: placement_ids, unplaced_ids, fill_volume_pct, payload_pct, cog_x, cog_y, cog_z, cog_offset, axle_front_kg, axle_rear_kg (when axle positions are set), stability_index, state (draft / computed / validated), owner_id, kpis_json, viewer_json
logitrain.loadplan.item loadplan_id, product_id or free-text name, qty, length, width, height (mm), weight (kg), stackable, max_stack_load (kg), fragile, allowed_rotations (none / horizontal / all), group (stop sequence or customer), priority
logitrain.loadplan.placement loadplan_id, item_id, index, x, y, z (mm; origin = floor, door side, left), l, w, h as placed, rotation, group, support_ratio, layer, manual (bool), invalid_reason

logitrain.container.type lives in the base module (§5.2).

9.2 Solver (models/solver/, pure Python, no external service; numpy optional)

  1. Expand items by quantity; within each group, heavy and large first; groups ordered by delivery sequence so the first delivery is nearest the door.
  2. Extreme-point best-fit-decreasing (Crainic–Perboli–Tadei EP-BFD). Candidate positions come from extreme points; for each allowed orientation check: inside bounds; no AABB overlap; support ratio ≥ min_support_ratio (floor, or top faces at the same z); stack load ≤ max_stack_load of every supporting box, propagated downwards; cumulative payload ≤ container max; delivery-order band constraint when enabled. Choose by (z, y, x) lexicographic, then a wasted-space score.
  3. Post-compute: fill %, payload %, centre of gravity and offset from the geometric centre, axle split (linear by CoG longitudinal position between the configured axles), stability index (mean support ratio; share of fully supported items; count of items under 100 % support), unplaced items with reasons.
  4. Determinism: same input and seed → same output. Target: ≤ 500 boxes in under 5 s on 2 vCPU.
  5. Manual mode: the trainee edits x/y/z/rotation of placements; validate_placements() re-checks overlap, bounds and support and sets invalid_reason; KPIs recompute. No drag-and-drop in 3D in v1 [CDC].
  6. Tests: invariants (no overlap, in bounds, support ≥ threshold, payload) on random instances; known-answer tests on homogeneous pallets (e.g. 1200 × 800 pallet with 400 × 300 × 200 boxes → expected count and layers).

9.3 Viewer (LogitrainViewer bundle, Three.js)

Container wireframe with door marker; boxes coloured by group; orbit, zoom and pan; hover tooltip (item, dimensions, weight, group); loading-sequence slider stepping through placements; layer toggle; PNG export. Embedded in the load plan form through an OWL field widget reading the server-computed viewer_json (container + placements).

9.4 Reports

"Plan de chargement" QWeb PDF with top and side 2D projections (SVG generated server-side from placements), per-group manifest, KPIs and warnings (CoG offset, unsupported items, payload).

Exercises: same items in a van, a truck and a 20' container; fragile and stackability rules; stability vs fill-rate trade-off; delivery-order loading from a tour; manual vs solver comparison.


10. logitrain_demo_tn

One module whose data/ files load on install (not Odoo demo data, so it also installs on a non-demo database). It is what the golden templates are built from.


11. Deployment stack (deploy/)

docker-compose.yml services:

Service Image / role
odoo Odoo 18.0 Community, custom image with the addons and pip dependencies, 2 workers
db PostgreSQL 16
osrm osrm-backend; Tunisia data built by deploy/osrm/build.sh from Geofabrik tunisia-latest.osm.pbf, MLD pipeline
vroom vroomvrp/vroom-docker (vroom-express), routing backend = osrm
tiles Nginx serving a Tunisia .pmtiles extract (alternative: tileserver-gl with an MBTiles file)
nginx TLS termination, reverse proxy, websocket, gzip; /web/database/* restricted to admin and trainer IPs
backup Nightly pg_dump + filestore archive, 14-day retention
rfid-bridge Optional, when the fixed reader is reachable from the server

Sizing for one centre, ≤ 30 concurrent users: 4 vCPU, 8 GB RAM (Odoo ≈ 2 GB, PostgreSQL ≈ 2 GB, OSRM Tunisia ≈ 1 GB, VROOM and tiles small), 60 GB disk.

Offline mode (G4 = no Internet): everything above runs without Internet; Nominatim disabled; all tiles local.

Install procedure (docs/install.md): prerequisites check, .env, OSRM data build, docker compose up, template databases from logitrain_demo_tn, admin passwords, smoke-test checklist, backup restore test. Target: half a day on a prepared server.


12. Cross-cutting requirements


13. Delivery plan

Two developers (A: backend/Odoo; B: JS/OWL and bundles) with a tech lead reviewing; Claude Code for scaffolding, tests and OCA-style boilerplate. Weekly sprints; target reception end of November 2026 so that payment stays on 2026 credits.

Sprint Weeks (2026) Dev A Dev B
S0 8–11 Sep Gating inputs, repo, CI, Docker stack (Odoo, OSRM, VROOM, tiles), version and dependency check esbuild pipeline, LogitrainMap, latlng_picker
S1–S2 14–25 Sep logitrain_base, logitrain_fleet Scan console UI, viewer skeleton (Three.js)
S3–S4 28 Sep – 9 Oct logitrain_wms models, RFID layer, rfid-bridge Tour UI (map, reorder, KPIs, violations)
S5–S6 12–23 Oct Tour services (OSRM, VROOM, fallback, evaluator), reports Load-plan viewer, manual placement UI
S7–S8 26 Oct – 6 Nov Load-plan solver, glue module, reports Dashboards, exercise UI, French i18n
S9 9–13 Nov logitrain_demo_tn, golden templates, hardening Docs, demo scripts, compliance matrix
S10 16–20 Nov Install on the ATFP server, dry-run acceptance, fixes Support

Effort (rough, before the CDCs): base 5–8 · fleet 6–9 · wms + bridge 10–15 · tour 15–22 · loadplan 15–25 · deploy, docs, acceptance 8–12 → about 60–90 person-days. Lines 1 and 2 carry the risk: scope them to this document and route CDC extras through a change request.


14. Risks

Risk Mitigation
CDCs demand features beyond this spec (truck routing, drag-and-drop 3D, telematics, mobile) Compliance matrix at G1; change request; phase 2
Centre refuses a dedicated instance and runs an old Odoo D1 fallback; re-estimate the JS layer; escalate commercially
No Internet on the server or in classrooms Offline mode is the default design (D4, §11)
RFID hardware absent or late Simulator and wedge-reader path; hardware sheet to AN now
Solver judged on optimality rather than pedagogy Document the heuristics as heuristics in the trainer guide; the compare mode is the product, not a commercial optimiser
Server too small or no Docker Sizing sheet at G2; fallback: separate host or VM
Timeline vs November reception Two parallel tracks; fleet + WMS ready by mid-October as a partial demo

15. Open items