Atrax exploitation puller — discovery & build handoff (#6197 / #6710)
Daily puller for Atrax GET /api/exploitation, populating p24_gps_daily_stats (core) +
p24_gps_daily_stats_native (Phase-7 vendor fraud/consumption signal for #6138 / #6194).
Status: DISCOVERY RESOLVED + puller runnable (#6710, 2026-09-01). The “genuine unknown” in §2 below was a false premise: Atrax’s
X-Request-Hash/X-Timestampsigning was already reverse-engineered and verified end-to-end without a browser by #5921 (2026-08-12,docs/playbooks/atrax-request-signing.md), and the liveatrax-daily-stats-collectorn8n workflow has used that raw signed path in production since #6204. A live signed capture (#6710) confirmed the full request contract and response shape, so the puller now issues the raw signed HTTP request directly — no Playwright, no login-form automation, no UI navigation.loginToAtrax()/captureExploitationJson()(which threwDiscoveryRequiredError) are replaced bygetAtraxToken()
signedHeaders()+fetchExploitation()inscripts/atrax-playwright-puller.js. Verified live against real vehicles on bms-4 (a moving unit returned populated distance/consumption and a real nativefrauds/total_fraud_time_ssignal). Remaining deferred work: apply the #6720 native-table migration to the live DB, schedule the nightly cron, and (optionally) teach the live n8nmap_statsnode to also emit native rows — see §4 Ownership.(The file keeps its historical
atrax-playwright-puller.jsname — referenced by the #6720 migration + operator docs — but no longer uses a browser.)
1. What is already DONE (shipped in #6197)
| Artefact | Path | Notes |
|---|---|---|
Pure transform (map_stats port) | scripts/lib/atrax-exploitation-transform.js | Raw /api/exploitation → p24_gps_daily_stats row + richer native object. **Corrected in 6710 to faithfully mirror the live map_stats node: summary-based sources (the #6197 port read non-existent top-level keys), no DB-generated columns emitted, correct native field names. Pure, no I/O. |
| Unit tests | scripts/tests/atrax-exploitation-transform.test.mjs | node --test (zero deps). 16 cases against the real response shape; run: node --test scripts/tests/atrax-exploitation-transform.test.mjs. |
| Puller wiring | scripts/atrax-playwright-puller.js | env load, validateEnv (names only), active-vehicle selection, per-vehicle error isolation, 1 s vendor-politeness delay, transform call, upsert, zero-success sanity check, Discord + GH-issue error notification. #6710: the two browser stubs replaced by the raw signed getAtraxToken/fetchExploitation path (see §2). |
2. Discovery — RESOLVED (no browser needed) (#6710, 2026-09-01)
The original §2 asked for browser login-form + UI-navigation capture, on the belief that only a live browser could produce Atrax’s signing headers. That belief was already wrong when #6197 was written: #5921 (2026-08-12) reverse-engineered the signature from Atrax’s own JS bundle and proved it end-to-end with our service-account OAuth token — no browser — and the live n8n collector has run it in production since #6204. #6710 re-verified with a live signed capture on bms-4. So there is no Playwright discovery to do; the puller uses the same raw signed path the n8n workflow uses.
Confirmed request contract (live, #6710)
- Token:
POST /oauth/token, form bodygrant_type=password+username/password/scope/client_id/client_secret(the last two are the public literalsjatrax/passid), headerAuthorization: <ATRAX_AUTH_STRING>(already carries itsBasicprefix — do NOT re-prefix it). - Exploitation:
GET /api/exploitation?vehicleId=<atrax_car_id>&start=<epoch_s>&stop=<epoch_s>&useGps=false, headersAuthorization: Bearer <token>,tronik-webappinstanceid: <ATRAX_WEBAPP_INSTANCE_ID>,X-Timestamp(ms),X-Request-Hash = SHA256(X-Timestamp + token)(fresh per request). - Window: the 02:00-UTC op-day
[D 02:00 UTC, D+1 02:00 UTC)(computeDayWindow, #6204 Bug 2) — NOT a CET-midnight window. - An invalid/placeholder
atrax_car_id(e.g. theTEST-001row inp24_gps_current_state) returns400 "Błędny identyfikator pojazdu"— handled by per-vehicle isolation.
Confirmed response shape (live, #6710)
- Top level keys are ONLY
summary,lines,details,daily,name,color. There are no top-level metric keys — every metric is undersummary.*.daily[]holds per-day entries with the same field names assummary. - The native fraud/consumption fields all live under
summary.*:losses,lossFuel,frauds,totalFraudTime,standardConsumption,pouredFuel, and the refuel COUNT asrefueling(singular, populated) —refuelings(plural) is null (extractNativecoalesces plural→singular). /api/exploitationcarries NO fuel counter (fuelCounter*does not exist anywhere in the body). The 100%-nullfuel_counter_*/fuel_used_literscolumns are therefore a data-availability limit, not a bug — correcting the odometer/fuel-counter-branch misdiagnosis in #6710’s design comment. Available fuel data isfirstFuelLevel/lastFuelLevel(level %) andfuelConsumption/fuelConsumptionTotal(litres); the DB’sfuel_level_avg/min/maxcolumns have no matching source and stay null. Distance comes fromsummary.firstOdometer/lastOdometer(km → m).
Known request contract (from the atrax-daily-stats-collector n8n workflow — do NOT re-derive)
- Endpoint:
GET https://tronik.atrax4.com/api/exploitation?vehicleId=<atrax_car_id>&start=<epoch_s>&stop=<epoch_s>&useGps=false - Auth (browser session): the app supplies
Authorization: Bearer <token>plus the browser-computedx-request-hash/x-timestampsigning headers — the whole reason for the browser approach (the raw HTTP node cannot compute them → 401, #5921). - OAuth token (if ever needed directly):
POST /oauth/token,grant_type=password, withusername/password/scope/client_id/client_secretfrom theATRAX_*env keys. - Day window:
computeDayWindow(statDate, tzOffsetHours)in the transform module reproduces the legacy CET window (local[00:00, 23:59:59]as epoch seconds). Confirm CET vs CEST at run time.
3. Transform & write contract
transformExploitation(raw, { atraxCarId, carId, statDate })→{ core, native }.coreupserts intop24_gps_daily_stats—POST .../p24_gps_daily_stats?on_conflict=atrax_car_id,stat_date,Prefer: resolution=merge-duplicates(same pattern the n8nupsert_statsnode used; the table exists and is empty today — this puller is what finally populates it).nativeupserts intop24_gps_daily_stats_native— a new staging table for the richer fields (standardConsumption,losses/lossFuel,frauds/totalFraudTime,refuelings/pouredFuel) that feed #6194’s Phase 7 native fraud signal.- **Created by 6720 as a new table (chosen over extending
p24_gps_daily_stats— keeps the fraud/native signal isolated for #6194 to reconcile against, and avoids anALTERon a table with no in-repo migration):supabase/migrations/20260901103919_p24_gps_daily_stats_native_6710.sql, columnsatrax_car_id,stat_date(unique / on_conflict) + the seven native columns, RLS matching the siblingp24_gps_*tables. Key nesting confirmed against the #6710 live capture (all undersummary.*; refuel count isrefuelingsingular). ⚠️ The migration is merged but not yet applied to the live DB (PGRST205) — the puller’s native upsert 404s until CI applies it.
- **Created by 6720 as a new table (chosen over extending
- Active-vehicle filter:
getActiveVehicles()readsp24_gps_current_state(active=true,atrax_car_id not null) — the live interim source. When #6194’sp24_gps_trips/p24_gps_fuel_snapshotsland (404 today), switch to a per-day distance/ignition-delta query on those. Never blind-loop the full ~263-vehicle roster (§1a doctrine, #6194) — costly through a browser.
4. Ownership & SLA
This crosses the dev-coder / sys-admin boundary exactly as #6194 documented:
| Phase | Owner | Deliverable | Status |
|---|---|---|---|
| Foundation (transform, tests, wiring, this doc) | dev-coder (#6197) | merged PR | ✅ done |
Native table p24_gps_daily_stats_native (§3) | dev-coder (#6720) | migration merged to main | ✅ merged (⚠️ NOT yet applied to live DB — PGRST205; CI apply pending) |
| Live discovery (§2) + raw-signed build | infra-task on bms-4 (#6710) | replace browser stubs with getAtraxToken/fetchExploitation, correct the transform, document | ✅ done |
| Apply the #6720 native migration to live DB | infra-task / CI | table must exist before the puller’s native upsert stops 404’ing | ⏳ pending |
| Cron scheduling + first production run | infra-task on bms-4 | nightly cron (measured ~1 s/vehicle over the active fleet + politeness delay); register in dev_r_services at deploy time | ⏳ pending |
(optional) live n8n map_stats → also emit native rows | infra-task on bms-4 | teach the production nightly node to write p24_gps_daily_stats_native too, OR let this puller be the sole native populator | ⏳ open decision |
Why the last three are still deferred (not done in #6710): applying a migration is CI’s job on
merge (do not hand-run apply_migration — banned, #5653); the cron cadence + dev_r_services
registration is a deploy-time decision; and editing the live n8n workflow has no repo→n8n deploy
path (the JSON in n8n-workflows/ is an export/snapshot, not a source) — it is a hand-edit in the
n8n UI/API against the currently-working nightly job, which deserves its own supervised change rather
than a blind edit inside a light task.
Deferred compliance: dev_r_services registration is done by sys-admin at deployment time (the
service is not runnable until discovery lands — registering a non-functional service now would be
misleading). Not an AI/ML system → no dev_r_ai_systems entry needed (per #6197).
Error handling (already wired): Discord embed via P24_DISCORD_INFRA_SCRIPTS_ERRORS_WEBHOOK_URL
- a
bug-labelled GH issue on fatal error or zero successful pulls. Login failures surface distinctly from per-vehicle navigation failures (the latter are isolated and never abort the run).
5. Cross-references
- #5921 — direct-API signature still technically unsolved; this puller makes it moot in practice but does not close it. This work carries no closing keyword against that root-cause issue — the decision to downgrade/archive #5921 is left to whoever confirms this puller is live and reliable in production.
- **#6138 / 6194 — this is their “Phase 7” richer Atrax-native source.
scripts/rotate/n8n-update-credential-playwright.js— the established headless-Chromium idiom on bms-4.scripts/atrax-archival.py— the vendor-politeness delay + env-load conventions this puller mirrors.