gigafibre-fsm/docs/architecture/module-interactions.md
louispaulb 30a867a326 fix(tech): restore Gemini-native scanner + port equipment UX into ops
The ops tech module at /ops/#/j/* had drifted from the field app in two ways:

1. Scanner — a prior "restoration" re-added html5-qrcode, but the
   design has always been native <input capture="environment"> → Gemini
   2.5 Flash via targo-hub /vision/barcodes (up to 3 codes) and
   /vision/equipment (structured labels, up to 5). Revert useScanner.js
   + ScanPage.vue + TechScanPage.vue to commit e50ea88 and drop
   html5-qrcode from both package.json + lockfiles. No JS barcode
   library, no camera stream, no polyfills.

2. Equipment UX — TechJobDetailPage.vue was a 186-line stub missing the
   Ajouter bottom-sheet (Scanner / Rechercher / Créer), the debounced
   SN-then-MAC search, the 5-field create dialog, Type + Priority
   selects on the info card, and the location-detail contact expansion.
   Port the full UX from apps/field/src/pages/JobDetailPage.vue (526
   lines) into the ops module (458 lines after consolidation).

Rebuilt and deployed both apps. Remote smoke test confirms 0 bundles
reference html5-qrcode and the new TechJobDetailPage.1075b3b8.js chunk
(16.7 KB vs ~5 KB stub) ships the equipment bottom-sheet strings.

Docs:

- docs/features/tech-mobile.md — new. Documents all three delivery
  surfaces (legacy SSR /t/{jwt}, transitional apps/field/, unified
  /ops/#/j/*), Gemini-native scanner pipeline, equipment UX, magic-link
  JWT, cutover plan. Replaces an earlier stub that incorrectly
  referenced html5-qrcode.
- docs/features/dispatch.md — new. Dispatch board, scheduling, tags,
  travel-time optimization, magic-link SMS, SSE updates.
- docs/features/customer-portal.md — new. Plan A passwordless magic-link
  at portal.gigafibre.ca, Stripe self-service, file inventory.
- docs/architecture/module-interactions.md — new. One-page call graph
  with sequence diagrams for the hot paths.
- docs/README.md — expanded module index (§2) now lists every deployed
  surface with URL + primary doc + primary code locations (was missing
  dispatch, tickets, équipe, rapports, telephony, network, agent-flows,
  OCR, every customer-portal page). New cross-module edge map in §4.
- docs/features/README.md + docs/architecture/README.md — cross-link
  all new docs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 15:56:38 -04:00

22 KiB

Module Interactions

The cross-cutting "who talks to whom" reference for the Gigafibre FSM codebase. Every feature doc links here for dependency questions instead of duplicating a matrix of its own.

Sibling docs: overview.md (containers, networks, Traefik) · data-model.md (ERPNext doctypes).


1. Purpose

The codebase is split into a handful of frontends (apps/), a Node.js API gateway (services/targo-hub/), a couple of side-car services, and a long tail of external integrations (ERPNext, Stripe, Twilio, GenieACS, etc.). Any given feature crosses three or four of these layers, which means the interesting question is rarely "what does module X do" but "when module X does its job, who does it call, and who reacts to the side-effect?".

This document is the single authoritative answer to that question. Feature docs in ../features/ describe a capability end-to-end and link back here for the cross-module picture. If you are adding a new module or moving an existing call, update this file first.


2. Module catalog

2.1 Apps (frontends)

Module Host / Route Owns Feature doc
apps/ops erp.gigafibre.ca/ops/#/* Staff PWA — Dispatch, Clients, Tickets, Équipe, Rapports, OCR, Téléphonie, Agent Flows, Réseau, Settings. Also serves the tech mobile tree /j/* (same bundle, different layout). Routes registered in apps/ops/src/router/index.js. billing-payments.md · cpe-management.md · vision-ocr.md · flow-editor.md
apps/client portal.gigafibre.ca/#/* Customer portal PWA — Dashboard, Invoices, Tickets, Messages, Account, Catalog/Cart, payment return landings. Hash router, routes in apps/client/src/router/index.js. Plan A: passwordless magic-link JWT, no Authentik. billing-payments.md §4
apps/field /field/ (legacy) Original Quasar tech app. Being retired in favour of apps/ops /j/* + SMS magic-link. See §8.
apps/website www.gigafibre.ca React + Vite marketing site. Lead-capture form posts to targo-hub /api/checkout / /api/order. Built from apps/website/.
apps/portal (Traefik only) Not a runtime. Holds traefik-client-portal.yml which permanently redirects the legacy host client.gigafibre.caportal.gigafibre.ca, so stale SMS and bookmarks keep working.

2.2 Services (backends we own)

Module Host / Port Owns Notes
services/targo-hub msg.gigafibre.ca:3300 API gateway / monolith. Routes registered in services/targo-hub/server.js. Every external integration (Twilio, Stripe, Mailjet, Gemini, GenieACS, Oktopus, Traccar) is reached through the hub — frontends never hit those directly. Lib catalog below.
services/modem-bridge :3301 (internal) Playwright + headless Chromium driving TP-Link XX230v web UI to read encrypted TR-181 parameters the vendor exposes only via client-side JS. Token auth, internal network only. services/modem-bridge/server.js
services/docuseal sign.gigafibre.ca DocuSeal container for commercial-contract e-signature. Residential contracts use a JWT-based acceptance flow (see lib/acceptance.js + lib/contracts.js) — DocuSeal is the commercial track.
services/legacy-db 10.100.80.100:3307 Read-only MariaDB bridge into the old PHP gestionclient database. Used by migration scripts and a handful of lookup queries in lib/auth.js. services/legacy-db/docker-compose.yml

targo-hub library map (services/targo-hub/lib/)

One file per capability; the router in server.js dispatches by path prefix.

File Mount point Responsibility
acceptance.js /accept* Residential contract acceptance tokens — JWT sign + signed-blob storage on Quotation.
address-search.js (lib) Calls the Supabase rddrjzptzhypltuzmere RQA address database for autocomplete.
agent.js /agent/* LLM agent runtime + tool-use dispatch (reads agent-tools.json).
ai.js /ai/* Internal AI decision-making powered by Gemini Flash (classification, summaries, suggestions).
auth.js /auth/* Staff auth bridge — reads Authentik headers, optionally checks legacy gestionclient MySQL for migration.
checkout.js /api/catalog, /api/checkout, /api/accept-for-client, /api/order, /api/address, /api/otp Onboarding wizard: catalog → cart → OTP → order. Creates ERPNext Lead/Quotation/Customer.
config.js (lib) Environment variable accessor with typed defaults.
contracts.js /contract* Signed-contract lifecycle — residential JWT and commercial DocuSeal.
conversation.js /conversations* Threaded inbox: SMS, email, web chat → ERPNext Communication / HD Ticket.
device-extractors.js (lib) Pure functions to extract WAN/LAN/WiFi/optical facts from GenieACS device JSON.
device-hosts.js (lib) Device.Hosts.Host.{n}.* TR-181 host list parser.
devices.js /devices, /acs/* GenieACS NBI proxy — device lookup, presets, tasks, diagnostic summary.
dispatch.js /dispatch* Dispatch Job assignment scoring, tech-to-job matching.
email-templates.js (lib) HTML templates for OTP, magic-link, invoice email.
email.js (lib) SMTP transport (Mailjet in-v3.mailjet.com per config.js).
flow-api.js /flow/start, /flow/advance, /flow/complete, /flow/event, /flow/runs/* HTTP endpoints for the flow runtime.
flow-runtime.js (lib) Execution engine for Flow Templates — dispatchEvent() is how modules fire triggers.
flow-templates.js /flow/templates* CRUD for Flow Template doctype.
helpers.js (lib) HTTP client, ERPNext REST wrapper (erpFetch, erpRequest), JSON response helper.
ical.js /dispatch/ical-token/:tech, /dispatch/calendar/:tech.ics Signed-token iCal feed — lets techs subscribe their jobs in Apple/Google Calendar.
magic-link.js /magic-link* JWT sign/verify + one-time magic-link issuance.
modem-bridge.js /modem* Thin client for the modem-bridge sidecar.
network-intel.js /network/* InfluxDB + Grafana log analysis → Gemini summarization.
oktopus-mqtt.js (worker) MQTT subscriber to the Oktopus broker — compensates for their broken events-controller hook.
oktopus.js /oktopus/* Oktopus CE REST API — TR-369/USP device preauth and provisioning.
olt-snmp.js /olt* SNMP poller for ONU status / optical power on the OLTs.
otp.js (lib) One-time-password issuance + email/SMS delivery (used by checkout + portal).
outage-monitor.js /webhook/kuma Uptime-Kuma webhook ingest; cross-references OLT poller to synthesize mass-outage alerts.
payments.js /payments*, /webhook/stripe Stripe Checkout session creation, customer portal links, webhook verification, Payment Entry reconciliation, PPA cron. Fires on_payment_received flow trigger.
pbx.js /webhook/3cx/call-event 3CX call-event webhook → ERPNext Communication.
portal-auth.js /portal/* Passwordless magic-link endpoint POST /portal/request-link — rate-limited, anti-enumeration.
project-templates.js (lib) Hard-coded job-template catalogue (fibre_install, etc.) for dispatch.
provision.js /provision/* Device provisioning orchestrator — GenieACS preset push + Oktopus preauth.
referral.js /api/referral/* Referral credit validation + application, backing the wizard.
reports.js /reports* GL / revenue / tax / A/R reports, Frappe PostgreSQL queries.
sse.js /sse, /broadcast Server-Sent Events fan-out. Topics: customer:*, conversations, dispatch, etc.
tech-absence-sms.js (worker) Auto-notify customers when their tech is absent.
tech-mobile.js /t/* Lightweight mobile tech page (predecessor of /j/*).
telephony.js /telephony/* Routr / identity SIP pool management.
traccar.js /traccar* Traccar REST API proxy (GPS fleet tracker).
twilio.js /send/sms, /webhook/twilio/sms-*, /voice/* Twilio SMS send + inbound webhook + Voice tokens + TwiML.
vision.js /vision/barcodes, /vision/equipment, /vision/invoice Gemini 2.5 Flash vision OCR endpoints.
voice-agent.js /voice/inbound, /voice/gather, /voice/connect-agent, /voice/ws Twilio IVR + agent runtime with Media Streams WebSocket.

2.3 External integrations

Provider Used from Channel Purpose
ERPNext v16 / Frappe hub (helpers.erpFetch), ops (nginx proxy), client (via hub) REST /api/resource, /api/method Source of truth for Customer, Subscription, Invoice, Ticket, Dispatch Job, etc.
Authentik SSO Traefik ForwardAuth middleware Header injection Guards staff surfaces (/ops/, n8n., hub.).
Twilio lib/twilio.js, lib/voice-agent.js SMS / Voice (REST + webhook + Media Streams WS) SMS magic-links, OTP, IVR, Voice tokens.
Stripe lib/payments.js REST + webhook (Stripe-Signature) Checkout sessions, Billing Portal, card-on-file, Klarna BNPL.
Mailjet lib/email.js (SMTP in-v3.mailjet.com) SMTP Transactional email (OTP, invoices, magic-links).
GenieACS lib/devices.js, helpers.nbiRequest NBI REST / TR-069 Modem provisioning + diagnostics (legacy CPE fleet).
Oktopus CE lib/oktopus.js, lib/oktopus-mqtt.js REST + MQTT (TR-369/USP) New CPE fleet.
Traccar lib/traccar.js REST + WebSocket GPS breadcrumbs for field techs.
Gemini 2.5 Flash lib/vision.js, lib/ai.js REST (generativelanguage.googleapis.com) Vision OCR + internal AI.
n8n (via Authentik header proxy) REST Workflow automation — long-running batch jobs.
DocuSeal sign.gigafibre.ca REST + webhook Commercial contract e-signature.

3. Interaction matrix

Row = caller, column = callee. Cell = primary channel (REST, SSE, WS, MQTT, SMS, SMTP, TR-069, TR-369, Webhook, JWT, ForwardAuth, iCal). Empty = no direct interaction (use the hub as a relay).

caller ↓ / callee → ops client field hub ERPNext GenieACS Oktopus Traccar Twilio Stripe Gemini Authentik DocuSeal
ops REST + SSE REST (nginx proxy w/ token) — (via hub) — (via hub) WS (via hub proxy) — (via hub) — (via hub) — (via hub /vision) ForwardAuth
client REST + SSE — (via hub) — (via hub) — (via hub checkout-link) — (via hub) JWT (magic-link, not Authentik)
field REST REST (legacy, token)
website REST (/api/checkout, /api/order) — (via hub)
hub SSE SSE REST (erpFetch, Authorization: token ...) REST (NBI nbiRequest) REST + MQTT REST + WS SMS REST + Webhook REST + Webhook (Stripe-Signature) REST REST + Webhook
modem-bridge — (hub calls it, :3301)
ERPNext — (fire-and-forget HTTP to hub /flow/* from server scripts)
Traefik Forwards Forwards Forwards Forwards Forwards ForwardAuth
Twilio Webhook (/webhook/twilio/*)
Stripe Webhook (/webhook/stripe)
DocuSeal Webhook (/contract/*)

4. Canonical flows

Six flows cover 90 % of the interactions. Each is grounded in the hub lib file that drives it.

4.1 New customer onboarding

apps/website (React lead form)
    │ POST /api/order  (lib/checkout.js)
    ▼
targo-hub  ──► address-search.js  ──► Supabase RQA
           ──► otp.js              ──► Twilio SMS + Mailjet SMTP
           ──► erpFetch            ──► ERPNext (Lead → Quotation → Customer)
           ──► payments.js         ──► Stripe Checkout session
    │
    │ user pays
    ▼
Stripe ──webhook──► hub /webhook/stripe  (lib/payments.js, Stripe-Signature verify)
                                         ├─► ERPNext Payment Entry
                                         ├─► flow-runtime.dispatchEvent('on_payment_received')
                                         │      ├─► contracts.js  (DocuSeal or JWT accept)
                                         │      └─► dispatch.js    (create Dispatch Job)
                                         └─► sse.broadcast('customer:<name>', ...)

Feature doc: billing-payments.md.

4.2 Tech dispatches a job

apps/ops /dispatch  (DispatchPage.vue)
    │ drag-drop assignment, POST /dispatch/assign (lib/dispatch.js)
    ▼
hub  ──► ERPNext Dispatch Job (scoring weights in dispatch.js)
     ──► twilio.sendSms       →  customer cell:  "/j/<JWT>"
    │
    ▼
Tech opens /j/<JWT> on phone   (apps/ops/src/modules/tech/*)
    │ JWT verified by magic-link.js
    │ GPS check-in → traccar.js → Traccar REST → WS breadcrumbs back to ops
    │ Scan device barcode → useScanner → /vision/barcodes → Gemini
    │ Mark complete → flow-runtime.dispatchEvent('on_job_complete')
    ▼
ops SSE topic `dispatch` refreshes the Kanban

Feature docs: cpe-management.md (device scan) · vision-ocr.md (OCR pipeline) · flow-editor.md (post-job trigger).

4.3 Customer pays an invoice from the portal

apps/client /#/invoices/INV-0000123   (InvoiceDetailPage.vue)
    │ click "Payer maintenant"
    │ POST /billing/checkout-link (lib/payments.js)
    ▼
hub  ──► Stripe Checkout session (mode=payment, client_reference_id=INV-…)
    │ browser redirects to Stripe, user pays
    ▼
Stripe ──webhook──► hub /webhook/stripe
                    ├─► ERPNext Payment Entry
                    ├─► flow-runtime.dispatchEvent('on_payment_received')
                    └─► sse.broadcast('customer:<name>', 'invoice.paid', …)
    ▼
Both apps/ops (cust detail) and apps/client (dashboard) update live over SSE.

4.4 Customer requests support

apps/client /#/messages  (MessagesPage.vue)
    │ POST /conversations  (lib/conversation.js)
    ▼
hub  ──► ERPNext HD Ticket  (erpFetch)
     ──► sse.broadcast('conversations', …)
    ▼
apps/ops /tickets  (TicketsPage.vue)  — live refresh
    │ staff replies → conversation.handle → ERPNext Communication
    ▼
hub ──► twilio.sendSms (if customer channel = SMS) or email.js (if email)

4.5 Modem diagnostic from ops

apps/ops /network  (NetworkPage.vue)
    │ GET /devices/lookup?serial=…  (lib/devices.js)
    ▼
hub  ──► GenieACS NBI  (helpers.nbiRequest)
    │ if TP-Link XX230v → ask modem-bridge :3301 for encrypted params
    ▼
modem-bridge  ──► Playwright headless Chromium  ──► modem web UI (172.17.x.x:443)
              ──► decrypted TR-181 JSON back to hub
    ▼
hub  ──► device-extractors.summarizeDevice  → consolidated JSON
    ▼
ops UI renders WAN / LAN / WiFi / Optical; SSE topic `customer:<name>`
updates any other ops tab watching the same client.

Feature doc: cpe-management.md.

4.6 Passwordless login (customer)

apps/client /#/login  (LoginPage.vue)
    │ POST /portal/request-link  (lib/portal-auth.js)
    │   rate-limited, anti-enumeration (constant-time response)
    ▼
hub  ──► otp.js / magic-link.js  (JWT HS256, 24h exp)
     ──► twilio.sendSms    ("Votre lien: https://portal.gigafibre.ca/#/?token=…")
     ──► email.js (SMTP Mailjet)
    ▼
customer clicks link → apps/client router guard hydrates store from token
                      → store.customerId set → dashboard loads

5. Authentication matrix

Surface Mechanism Source of trust Notes
Staff apps (ops, n8n, hub UI) Authentik ForwardAuth via Traefik authentik-client@file middleware id.gigafibre.ca session cookie Headers X-Authentik-Email, X-Authentik-Groups injected into upstream.
Customer portal (apps/client) 24 h HS256 JWT (magic-link) lib/magic-link.js secret No Authentik; no password form reachable (Traefik redirects legacy /login).
Tech mobile (/j/* inside apps/ops) 24 h HS256 JWT sent by SMS Same secret Same JWT primitive as portal.
hub → ERPNext Authorization: token <key>:<secret> (Frappe API token) ERPNext Administrator token — never regenerate ("Generate Keys" in ERPNext UI breaks the hub) Used by helpers.erpFetch.
ops → ERPNext (frontend) nginx same-origin reverse proxy injects the same token server-side Token stays server-side; browser never sees it See apps/ops/infra/.
Webhooks Per-provider signature verification Stripe-Signature, X-Twilio-Signature, DocuSeal shared secret Rejected synchronously before any side-effect.
modem-bridge Shared bearer token env var on both hub + bridge containers Internal network only, not exposed via Traefik.
Traccar Bearer token preferred (fall-back Basic) env var on hub See lib/traccar.js header note.

6. Data ownership

Source of truth per entity. "Hub cache" means the hub keeps a read-through copy; the authoritative write goes to the listed owner.

Entity Source of truth Hub role Frontend read path
Customer ERPNext Customer thin pass-through (erpFetch) ops: direct via nginx proxy · client: via hub
Address (Service Location) ERPNext Service Location pass-through; address-search.js also calls Supabase RQA for autocomplete
Subscription ERPNext Subscription pass-through
Invoice ERPNext Sales Invoice pass-through + /vision/invoice OCR
Payment ERPNext Payment Entry written from Stripe webhook in lib/payments.js
Ticket ERPNext HD Ticket written from lib/conversation.js
Dispatch Job ERPNext Dispatch Job (custom doctype) written from lib/dispatch.js ops SSE topic dispatch
Technician ERPNext Technician (custom) pass-through
Service Equipment ERPNext Service Equipment (custom) pass-through
Tag ERPNext Tag pass-through
Flow Template ERPNext Flow Template (custom) CRUD in lib/flow-templates.js ops /agent-flows
Flow Run ERPNext Flow Run (custom) written from lib/flow-runtime.js ops /agent-flows
Exceptions
Live modem state (WAN IP, SNR, host list) GenieACS / Oktopus hub never persists — always fetched live
GPS breadcrumbs Traccar hub proxies WS + REST ops /dispatch map
JWT sessions hub process memory + browser localStorage no server-side session store

7. Real-time channels

Channel Transport Producer Consumer Topic / payload
Ops / portal live updates SSE lib/sse.js apps/ops, apps/client customer:<name>, conversations, dispatch, ad-hoc
TR-369 device events MQTT Oktopus broker lib/oktopus-mqtt.js worker device-online, heartbeat
GPS breadcrumbs WebSocket Traccar lib/traccar.js proxy → ops dispatch deviceId, latitude, longitude, speed
Twilio Media Streams WebSocket upgrade on hub /voice/ws Twilio lib/voice-agent.js raw audio frames for IVR agent
SSE keep-alive server-pushed : ping lib/sse.js 25 s interval browsers heartbeat

8. Retired / in-retirement

Do not build new features on these. Linked target is where the replacement lives.

Retiring Replacement Status Notes
auth.targo.ca id.gigafibre.ca (Authentik) active migration ForwardAuth middleware already on the new host.
apps/field apps/ops /j/* tree in retirement Same bundle, different layout — removes the separate deploy surface.
dispatch-app (legacy standalone) apps/ops /dispatch retired Feature parity reached; decommission pending DNS cleanup.
client.gigafibre.ca portal.gigafibre.ca retired Traefik permanent-redirect via apps/portal/traefik-client-portal.yml.
Frappe /login form for customers /portal/request-link magic-link retired Legacy MD5 hashes not migrated — customers forced through the new flow.
Ollama local llama3.2-vision lib/vision.js → Gemini 2.5 Flash retired Ops VM has no GPU.

Back to docs/README.md.