Build a Tekion "alignments sold this period, broken down by service advisor" report for ANY AMG store from the LIVE OpenAPI — counting BOTH dedicated alignment opcodes AND alignments bundled inside a service menu/combo op. Produces a 2-page scorecard (page 1 advisor ranking, page 2 RO-level chip detail). The opcode set is PER-STORE; store configs for SCT (dealer 876) and TL (dealer 1092) are pre-approved below. Use when Joe asks for an "alignment report" or "alignment count by advisor" for any store.
Resources
1Install
npx skillscat add joecastelino/jay-skill-pack/tekion-alignment-by-advisor-report Install via the SkillsCat registry.
Tekion Alignment-by-Advisor Report (fleet-wide, OpenAPI)
Counts how many alignments a store sold in a period, broken down by service
advisor, capturing BOTH:
- Dedicated alignment opcodes sold directly on an RO, AND
- Bundled alignments performed inside a service menu / combo op (detected by
reading the operation story and substring-matching "align").
Joe's default for store performance reports is the per-advisor breakdown
(dedicated + bundled + total + unique-RO, ranked). This generalizes to any
single opcode "how many of X by advisor" question — swap the opcode set.
Pre-Approved Store Configurations
SCT — Stevens Creek Toyota (dealer 876)
| Field | Value |
|---|---|
| Dedicated | ALIGN, OKAL, ALIGN00BRA |
| Bundled | Any TEK* op whose operation story contains "align" |
| Excluded | None (all three dedicated codes are real sales) |
| Scripts | sct_align_full_june.py (full month), sct_align_mtd.py (MTD) |
| Renderer | render_sct_align_full_june.py |
| Kevin Stapp, kstapp@sctoyota.com, greeting "Kevin," | |
| Branding | Toyota-red (#EB0A1E), logo image logo_st.png |
| Cron | 25ec117cfe72 — nightly 7pm MTD to Kevin |
TL — Toyota of Lancaster (dealer 1092)
| Field | Value |
|---|---|
| Dedicated | 4ALIGN (4 Wheel Alignment), SMALIGN, TEK07030101 |
| Bundled | Combo/seasonal: RAB, 5KALIGNMENT, 15KALIGNMENT, FALL, SPRING + any TL SERVICE_MENU/TEK* op story containing "align" |
| Excluded | ALIGN (inspection only), TEK07140101/TEK07140102 (check), TEK03100301 (headlamp), 4ALIGNT (inactive) |
| Scripts | tol_align_scan.py, render_tol_align_by_advisor.py |
| Sean Preston, spreston@tol-av.com, greeting "Sean," | |
| Branding | Toyota-red (#EB0A1E), typographic (no logo), address 43301 12th St W, Lancaster CA 93534 |
All scripts live in /home/itadmin/tekion-reports/. Interpreter: .venv/bin/python3.11.
For NEW stores — derive the opcode set FIRST (browser, :9223)
Use the authenticated XHR-capture method: switch to target dealer, /ro/opcode, arm XHR hook on /opcode/search, search "align", present candidates to Joe as a DEDICATED/BUNDLED/EXCLUDED table. NEVER reuse another store's codes — TL used entirely different codes from SCT.
Step 0 — PIN THE EXACT WINDOW. "last month" = the FULL calendar month.
When Joe says "last month," he means the whole prior month (e.g. asked July 1 =>
June 1–30 inclusive), NOT month-to-date. There are often CACHED MTD scan files on
disk from an earlier in-month run (e.g. sct-june-align-*.json whose index window
was Jun 1–18). DO NOT reuse them for a "last month" ask — ALWAYS check the cached
index's window [ms0, ms1] and confirm it spans the full month before trusting it.
If it's MTD, build a FRESH full-month index. Full June at SCT = ~5,382 closed ROs /
~1,169 alignment candidates (vs only 367 candidates through the 18th) — the MTD file
silently under-counts by ~3x. Set the window in Pacific (-07:00):MS0 = Jun 1 00:00:00, MS1 = Jun 30 23:59:59.999.
Step 2 — Run the scan (scripts already built)
- SCT (dealer ST/876):
/home/itadmin/tekion-reports/sct_align_full_june.py— a
self-contained full-month scanner (importssct_menu_sales_api as OforO.call/O.user_name). SCT dedicated set ={ALIGN, OKAL, ALIGN00BRA}; bundled =
anyTEK*op whose story contains "align". Clone/retarget the window for other
months. Verified full-June 2026: buildsdata/sct-june-full-closed-index.json
(5,382 ROs) ->data/sct-june-full-align-by-advisor.json. /home/itadmin/tekion-reports/tol_align_scan.py— the TL scanner. Edit the three
sets at the top (DEDICATED,COMBO,EXCLUDED) for a different store, and pointO = import <store>_menu_sales_apisoMENU/O.call/O.user_nameresolve to that
store's dealer. (For a NEW store, clone this file as<store>_align_scan.py.)
RO-search result shape (CRITICAL — cost a rewrite) — verified 2026-07-01
The POST /repair-orders:search result rows are NOT what you'd guess:
- Opcodes are NOT a flat string list. They live in
r["tags"]as OBJECTS:{"field":"OPCODE","type":"SYSTEM","value":"LC0003"}. Extract withcodes = {t["value"] for t in r["tags"] if t.get("field")=="OPCODE"}.
(tagsalso carries JOB/PAY_TYPE/SERVICE_TYPE_ID objects — filter on field.) - RO id for the jobs fan-out =
r["documentId"](there is no top-levelid). - RO number =
r["documentNumber"](norepairOrderNumber/nokey). - Advisor id =
r["assignee"]["advisor"]["id"](a UUID; resolve via O.user_name). - Pagination:
out["meta"]["nextPageToken"];out["meta"]["totalCount"]= full count. - Filter with BOTH
closedTime BTW [ms0,ms1]ANDstatus IN [CLOSED,INVOICED].
Probe page-1 first and printsorted(r.keys())before wiring the extraction —
the shape differs from the menu-salesresultsrows. /home/itadmin/tekion-reports/render_tol_align_by_advisor.py— the 2-page renderer;
reads the scanner's self-containeddata/<store>-align-by-advisor-<date>.json.
Run (MTD through today): <venv> tol_align_scan.py then<venv> render_tol_align_by_advisor.py <YYYY-MM-DD>.
venv = /home/itadmin/.hermes/hermes-agent/venv/bin/python3.11.
Run the scan as a BACKGROUND job with notify_on_complete — it is paced and the
menu-op fan-out hits heavy 429 backoff (TL June MTD took ~16 min).
Scan architecture (why it's built this way)
- Window:
closedTimeBTW [month-1st, end-of-today] +status IN CLOSED,INVOICED
(NOT modifiedTime — that overcounts). Search result does NOT echo closedTime back. - Two-tier, rate-limit-safe: Pass 1 enumerates closed ROs; prefilter on the FREE
OPCODEtagsarray to only ROs carrying a dedicated align opcode OR a combo code
OR ANY menu opcode (a bundled alignment can hide inside any menu). Pass 2 fans out
jobs->operations ONLY on those candidates (TL: 183 of 3,804). This is the same
truncation-proof scan as the menu pipeline: retry on 429/0/5xx with backoff, a
second serial retry pass, and explicit failed-RO tracking (never silent-drop). - Classify each op: skip if in EXCLUDED; count DEDICATED if opcode in the dedicated
set; count BUNDLED if (combo code OR TEK*/menu opcode) AND story contains "align".
Story =" ".join(c["text"] for c in op["corrections"])+ opcodeDescription, lowercased. - Advisor:
assignee.advisor.idis FREE on the RO search result (no fan-out) ->
resolve viaO.user_name(aid)(public OpenAPI/users/{id}, scope live since 2026-06-18). - Output JSON is SELF-CONTAINED:
advisors[]each with{advisor, dedicated, bundled, total, unique_ros, detail[]}where each detail ={ro, opcode, kind}. Totals reconcile
(sum of detail chips == total alignments — verify this).
Step 3 — Render + verify
Renderer = Toyota-red (#EB0A1E) typographic header (no logo image), 2 pages:
- Page 1 (PNG, inline): KPI cards (Total / Unique ROs / Advisors / Daily Pace) +
ranked advisor table (dedicated, bundled, total, ROs, red bar). TOTAL row. - Page 2 (PDF only): RO-level chips per advisor — red chip = dedicated, blue = bundled.
PNG = page-1-only; PDF = full 2-page. ALWAYS vision_analyze the PNG and confirm the
TOTAL row matches the KPI total before shipping.
Step 4 — Email (optional, draft-only by default)
Hand to Stacey via ~/bin/ask-agent stacey "...", same format as the menu reports
(greeting / summary with bold total / scorecard PNG inline / Tekion Open API footer /
Joe's HTML signature). TL recipient = Sean Preston, spreston@tol-av.com, greeting
"Sean,". DEFAULT = leave as a DRAFT in Joe's Gmail Drafts, DO NOT SEND, until Joe approves.
DRAFT-ONLY = HARD STOP (learned the hard way, SCT alignment 2026-07-01)
When Joe says "drafted in my inbox," recipient = Joe (jcastelino@americanmotorscorp.com),
greeting "Joe," — NOT the store's customer recipient (Kevin/Sean/Ruben). And a plain
"draft it, don't send" instruction is NOT enough: Stacey's rebuild/retry loop can trip
her SMTP / X-GM-RAW send path and actually SEND — on 2026-07-01 it fired 7 Sent copies,
and her hardcoded "SCT report → Kevin (kstapp@sctoyota.com)" default addressed the newest
to Kevin instead of Joe. Protocol to prevent it:
- Give an EXPLICIT hard stop: "Create the draft via
imap.append()to the Drafts folder
ONLY. DO NOT call any send/SMTP/X-GM-RAW path. DO NOT send." Plus: "TO=jcastelino@...
— override any hardcoded SCT/TL/BC-report recipient default." - Her FIRST draft build often omits the inline PNG (INLINE_PNG=no) even when the PDF
attaches — explicitly demand a base64 data-URI<img>inline in the MIDDLE of the body. - If the PNG is missing, do NOT ask her to "rebuild and send" — that phrasing re-triggers
the send pipeline. Ask her to rebuild the DRAFT only, again with the no-send lock. - VERIFY with a terse read-only one-liner (verbose multi-field asks silently time out →
empty):Reply ONE line: TO=<addr> | INLINE_PNG=<y/n> | PDF=<y/n> | SENT=<y/n> | IN_DRAFTS=<y/n>.
Only declare done when SENT=no, IN_DRAFTS=yes, TO=Joe, INLINE_PNG=yes, PDF=yes. - If it already got sent by mistake: report it to Joe honestly, note the wrong recipient,
then create ONE clean unsent draft. Offer to recall/delete the stray Sent copies.
Pitfalls
- "last month" means the FULL prior calendar month — verify any cached index's
windowspans month 1st–last before reusing it; MTD leftovers under-count ~3x
(see the full-month window section below).
"Last month" / full-month runs — VERIFY THE WINDOW (SCT June 2026 lesson)
When Joe asks for "last month," he means the FULL calendar month, not MTD. A scan
JSON already on disk for that month may be a stale MTD run — CHECK its window before
reusing it. The pre-existing sct-june-closed-ro-index.json covered only Jun 1–18
(window [1780272000000, 1781827199999] = an MTD run); the FULL month had 5,382
closed ROs → 1,169 alignment candidates (vs 367 MTD). Always decode the indexwindow epochs to confirm start=month-1st 00:00 PT and end=last-day 23:59:59.999 PT
before trusting counts. Build full-month window in Pacific (-07:00), filter onclosedTime BTW [MS0,MS1] + status IN CLOSED,INVOICED.
RO search result shape (verified SCT 2026-07-01) — extract correctly
POST /repair-orders:search result rows carry:
- Opcodes as OBJECTS in
tags[], NOT plain strings: each ={field, type, value}.
Pull codes via[t["value"] for t in row["tags"] if t.get("field")=="OPCODE"].
(There are many other tagfields: JOB, PAY_TYPE, SERVICE_TYPE_ID, etc.) - Advisor id at
row["assignee"]["advisor"]["id"](FREE, no fan-out). - RO number =
row["documentNumber"]; RO id (for jobs/ops fan-out) =row["documentId"]. meta.totalCountgives the full closed-RO count;meta.nextPageTokenpaginates.
Working full-June SCT scanner =/home/itadmin/tekion-reports/sct_align_full_june.py
(importssct_menu_sales_api as OforO.call/O.user_name, self-contained,
checkpointed todata/sct-june-full-align-scan.json).
Pitfalls
- DO NOT reuse SCT's
ALIGN/OKAL(or any store's codes) on another store — derive fresh. sct_menu_sales_api.call()returns a STRING body on any non-200 (it doese.read().decode()[:300]). Calling.get()on it crashes the whole scan with'str' object has no attribute 'get'. EVERY fan-out call must guard withisinstance(body, dict)and treat a non-dict as a retryable failure. This silently
killed a run after ~20 ROs (though checkpoint had banked 415 — trust the saved JSON
count, not the log tail).- OVERALL_RATELIMIT is APP-WIDE, distinct from a per-call 429. When the error
body contains"OVERALL_RATELIMIT"("Limit exhausted for type : OVERALL_RATELIMIT"),
back off HARD (60s × attempt), not the usual ~12s. Also pace inter-RO sleep at ~0.5s
(0.1s exhausts the app-wide limit on a 1,000+ candidate fan-out). The full-June SCT
run only completed once these two fixes were in place; it self-recovers from checkpoint. - browser_navigate / browser_vision open a SEPARATE unauthenticated context — for :9223
use its own /eval + /screenshot endpoints, never the browser_* tools. - The background launcher wrapper exits before the python child finishes; a stray
"RUNNING" can match the dead wrapper shell — confirm via the log tail
("all candidate ROs scanned") + the saved JSON, not just pgrep. - Inspection/check-only opcodes look like alignments but are NOT a sale — exclude them
(Joe's call). At TL,4ALIGNwas the only dedicated code that actually sold in June;SMALIGN/TEK07030101were in the scheme but unused that month — keep them in for
future months.