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")
group_logitrain_trainee— create/edit own transactional records, read master data, no delete on master data.group_logitrain_trainer— trainee rights + manage cohorts, exercises and master data; run solvers on any record; grading.group_logitrain_admin— settings, engines, templates.
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/)
LogitrainMapOWL component — props:markers[],polylines[],editable,onPick(lat, lng),onReorder(ids). Leaflet with PMTiles or XYZ tiles; numbered markers; fit-to-bounds; offline-safe.latlng_pickerfield widget — forres.partner(partner_latitude/partner_longitudefrombase_geolocalize) and for tour stops: click on the map to set coordinates; optional "Geocode" button that callsbase_geolocalizeonly when a Nominatim URL is configured.- KPI card component, JSON viewer, common CSS.
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)
- Context selection: a picking (receipt / pick / pack / ship), a batch, or a cycle count on a location.
- One always-focused input receives keyboard-wedge scans (barcode or EPC). Resolution order: EPC (tag table) → package name → lot/serial → product barcode → location barcode.
- Expected vs scanned lines, colour states, running totals; actions: put in package, validate, print labels.
- Works with USB barcode scanners and USB/desktop UHF readers in keyboard-wedge mode — no bridge needed.
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/)
osrm.py— route (overview=full,geometries=polyline6), table/matrix in chunks, health check.vroom.py— request builder: vehicles (capacity[kg, dm3], time_window, breaks from rules, max_travel_time, skills), jobs (location, service, delivery, time_windows, priority); parser for routes and unassigned jobs.fallback.py— Haversine matrix × road factor 1.3, nearest neighbour + 2-opt. Used when D4 engines are unreachable oroffline_modeis set.evaluator.py— ETAs from legs + service times + waiting at time windows; break insertion per rules; violation list; cost = fixed + km × cost_per_km + working hours × cost_per_hour.
8.3 Flows
- 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).
- Manual planning: assign stops to a tour, drag to reorder → live recompute of route, ETAs, KPIs and violations on the map.
- 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.
- Compare: wizard showing two snapshots side by side — distance, time, cost, violations. This comparison is the core pedagogical output.
- 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)
- Expand items by quantity; within each group, heavy and large first; groups ordered by delivery sequence so the first delivery is nearest the door.
- 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_loadof 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. - 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.
- Determinism: same input and seed → same output. Target: ≤ 500 boxes in under 5 s on 2 vCPU.
- Manual mode: the trainee edits x/y/z/rotation of placements;
validate_placements()re-checks overlap, bounds and support and setsinvalid_reason; KPIs recompute. No drag-and-drop in 3D in v1 [CDC]. - 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.
- Fictional company (no real names); depot in a Tunisian city; ~120 customer sites with real-world coordinates across several governorates, curated from OSM, no personal data; opening-hour time windows.
- ~200 products with dimensions and weights; categories: beverages, FMCG, building materials, perishables with expiry; barcodes; package types (EUR pallet, half pallet, cartons).
- Warehouse layout of §7.1; opening stock with lots and expiries; six months of receipts and deliveries.
- Fleet: 3.5 t van, 7.5 t, 12 t and 19 t trucks, tractor + semi-trailer, 20' and 40' containers, EUR pallet — all as
logitrain.container.typewith vehicle profiles; twelve months of fuel, services and contracts; drivers with licences; a few incidents. - Default tour rules; six pre-built tour and load-plan scenarios; 40+ exercises across the four modules with expected KPIs.
- Users: admin, two trainers, 25 trainees (generic logins), cohorts.
- Post-install hook: warehouse and route configuration, sequences, branding placeholders.
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
- i18n — source strings in English; complete, reviewed French
.pofiles (trainees work in French); Arabic optional [G6]; all reports in French. - Security — groups and record rules of §5.1; one token per RFID reader; DB manager not publicly reachable; master password set; trainees cannot delete master data or touch other users' records; no outbound calls except the configured engines.
- Performance — tour form under 2 s with 50 stops; solver targets of §9.2; validated with 25 concurrent trainees on the §11 sizing.
- Coding standards — OCA guidelines;
pre-commitwith black, isort, flake8, pylint-odoo; complete manifests (license,version 18.0.1.0.x,summary,author: InTech Solutions); README per module in OCA readme-fragment format; CI grep rejecting any Enterprise import; no hard-coded URLs. - Tests — Python unit tests per module (solver invariants, evaluator, EPC codec, reconciliation); OSRM/VROOM responses recorded as fixtures; CI integration job running
odoo -i logitrain_* --test-enable --stop-after-initin the Docker stack; manual UI scripts per exercise. - Documentation — English drafts by Cairo, French client versions finalised in Tunis: install guide, admin guide (templates, cohorts, backups, engines), trainer guide (exercises with answer keys), trainee quick guides (one per module), RFID hardware sheet.
- Acceptance package — for each CDC line, a demo script walking the commission through every requirement, plus a compliance matrix (CDC requirement → screen/feature → status). Started the day G1 arrives.
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
- CDC n° 1–4 (G1) — blocking for the compliance matrix.
- Answers to G2–G7.
- Suite name, prefix and licence (D6) — before the first tag.
- Tunisian values for tour rules (driving/rest, speed factors) — from ATFP trainers.
- Arabic UI: required or not.