23 KiB
audit — Gap analysis: "Let it Snow" → FORGE (what has NOT been ported)
Date: 2026-08-27 · Author: audit (read-only) · Scope: full parity sweep
Checklist source: FORGE-parity-spec.md (29 charts + PM-Insights + boards + data contract)
Compared against: live FORGE code (server/*.ts, client/src/**, extension/*) and the initial app source
(/Users/dmytrotkachenko/WebstormProjects/Let It Snow/Let it Snow/*.js, verified by grep — files are huge).
Legend: ✅ done · 🟡 partial · ❌ missing. Value: MH must-have · NTH nice-to-have · LOW.
0. Executive summary
FORGE has delivered a solid read-only spine: full data ingest (966 tickets + finance + jira + config), a server analytics engine, the Overall tab (~9 of 11 charts), the Active tab (5 of 6), the 6 SLA heatmaps (charts 24–29), PM Insights (alert groups + waiting-PO + per-PM roll-up), a 6-column board with list view + filtered Excel export, and new RBAC/Users/Tokens admin (beyond the original).
What is not ported is roughly half of the analytics surface and nearly all of the operator tooling:
- PMs-KPI tab is only the 6 heatmaps — charts 18–23 (workload, engagement + dual calendar heatmaps, missing-cost, awaiting-PO money, brand/market sub-cards) are absent, and the heatmaps have no period/year granularity and no editable norms.
- Board is missing the 7th synthetic column, 13 of 16 card fields, all 14 sorts, all 6 group-by permutations, and ~9 of 11 filters (region, staleness, jira, requester, custom-label…).
- Whole features missing: the standalone Jira board, the Size CALC estimator, the finance view, and the entire options/config editor surface (brand colours, norms, thresholds, card-field toggles, colleagues/region, currency, custom labels).
- Sync is shallow: active-RITM only, 9 fields, no SCTASK, no Jira, no closed, no finance, no statusDurations collectors.
- Overall/Active charts lack the YoY pies (#5/#6), CGO overlay, #/% toggle, Jira status durations (#17), and time-in-status min/median/max (#15).
Per the spec, AI Advisor / at-risk, weather are intentionally out of scope (confirmed below).
1. Overall analytics tab — client/src/pages/Overview/index.tsx + server/analytics.ts:getOverview
| # | Item | Status | Note / where it lives |
|---|---|---|---|
| 1 | Opened per month | ✅ | GroupedBars — multi-year grouped, bars/line toggle, YoY badge. |
| 2 | Closed per month | ✅ | GroupedBars, same. |
| 3 | Revenue per month (cost/tickets, ±YoY) | ✅ | GroupedBars + Cost/Tickets toggle in Overview; GBP via fixed FX. |
| 4 | Lifetime-at-close histogram + median/avg/closed tiles | ✅ | BarList 6 buckets + median tile. avgDays/closed computed server-side. |
| 5 | Opened YoY pie (fair/full) | ❌ | No opened-YoY donut. Would live in Overview as a DonutChart; server has openedByMonth but no fair-window (same-day-of-year) series. MH |
| 6 | Closed YoY pie (fair/full) | ❌ | Same — no closed-YoY donut / fair-vs-full comparison. MH |
| 7 | By business unit (per-year, brand drill) | 🟡 | Flat BarList (byBusinessUnit), no per-year split, no brand drill-down. server/analytics.ts:169. NTH |
| 8 | By requester (bars + YoY, market drill) | 🟡 | ExpandBarList drills requester→brand (spec wants →market), no YoY. analytics.ts:170. NTH |
| 9 | Ticket-share donut (#/%, top 18 + Others) | 🟡 | DonutChart on byRequesterShare (full 966 dist — the REDO fix). No #/% toggle, no top-18+Others rollup. NTH |
| 10 | By brand (per-year segments, market drill, YoY) | 🟡 | ExpandBarList brand→market counts only. No per-year segments, no YoY, no size/status segmentation. NTH |
| 11 | By market | 🟡 | Flat BarList, top-20. No brand drill / segments. LOW |
| — | Toggle: bars/line | ✅ | GroupedBars per-chart. |
| — | Toggle: ±YoY | 🟡 | Shown as a computed badge, not a user toggle; only on the month GroupedBars. LOW |
| — | Toggle: cost/tickets | 🟡 | Only on the Revenue chart; original applies it across money charts. LOW |
| — | Toggle: #/% | ❌ | No percentage mode anywhere. NTH |
| — | Growth badges | 🟡 | Only the month-total YoY badge; no per-series/per-requester growth. LOW |
| — | CGO overlay | ❌ | Original overlays a "CGO" series per group (dashboard.js CgoBars/CGO_REQUESTER, toggle). Not in FORGE — needs a CGO flag on tickets + overlay in GroupedBars. NTH |
Bottom line: the month/revenue/lifetime core is faithful; the two YoY pies are missing outright, and the by-dimension charts are flat counts without the per-year segmentation, drill, and YoY the original had.
2. Active tab — client/src/pages/Active/index.tsx (client-computed from active tickets)
| # | Item | Status | Note / where it lives |
|---|---|---|---|
| 12 | Open-not-closed by month | ✅ | TrendChart area, openedByMonth. |
| 13 | By status (kanban cats) | ✅ | BarList over BOARD_COLUMNS, state colours. |
| 14 | Age histogram (0-50…200+ d) | ✅ | BarList, 5 buckets. |
| 15 | Time-in-status min/median/max | 🟡 | Only median days-in-state per column (Active/index.tsx:62). No min/max grouped mini-bars. NTH |
| 16 | By brand, status-segmented (market drill) | 🟡 | Flat BarList of active-by-brand. No status segmentation, no market drill. NTH |
| 17 | Jira status durations (workflow order) | ❌ | Requires statusDurations from jira_status_map — not ingested (only status/statusChangedAt/key land in jira JSONB; see scripts/dump-to-archives.mjs:36). No chart. MH for Jira ops. |
3. PMs-KPI tab — client/src/pages/SlaKpi/index.tsx + server/analytics.ts:getSlaHeatmaps
The whole tab is currently only the 6 SLA heatmaps. The original "PMs KPI" tab was 12 items (18–29).
3a. The 6 SLA heatmaps (24–29) — present but reduced
| # | Item | Status | Note |
|---|---|---|---|
| 24 | On-Time Delivery heatmap | ✅ | otd metric, PM×size, on-time% vs otd norms. |
| 25 | Avg days to close heatmap | ✅ | avgclose vs avgdays. |
| 26 | Time to assign PM heatmap | ✅ | assign = max(0, fulfillment→firstAssigned) vs asla. |
| 27 | Time to send preview heatmap | ✅ | preview = toDoAt→inUatAt vs psla. |
| 28 | Avg TTFR heatmap | ✅ | ttfr = ttfrMinutes/1440 vs ttfr norms (hrs). |
| 29 | Avg PM response heatmap | ✅ | cresp = clientRespMinutes/1440 vs cresp norms. |
| — | Current + previous year side by side | ❌ | getSlaHeatmaps computes one all-time grid; original renders cur-yr + prev-yr heatmaps. MH |
| — | Period granularity (year / Q1-Q4 / month) | ❌ | No period control at all. MH |
| — | Editable norms panels | ❌ | Norms are read-only from app_config/config.json; original has inline norm editors (buildNormsPanel etc.) that persist. Needs a write endpoint + UI. MH |
| — | Hidden PMs from pm_kpi_settings |
❌ | getSlaHeatmaps:223 has a stubbed hidden = new Set() — never populated. NTH |
3b. Charts 18–23 — entirely missing
| # | Item | Status | Note / where it'd live |
|---|---|---|---|
| 18 | Workload by month (revenue prev/cur yr, per-PM) | ❌ | New GroupedBars+per-PM series; new server agg (closed cost by PM×month). MH |
| 19 | PM Engagement (backlog/Assist✓/Jira✓/Stale) + dual daily-activity calendar heatmaps | ❌ | Needs CalendarHeatmap component (GitHub-style) + activity/jira-movement ingest per day. Original: EngagementKpi() with PM-row accordion. MH |
| 20 | Missing final cost (ranked PM bars + drill) | ❌ | AssigneeBars + list; server: closed rows where finalCost null, grouped by PM. Partial data exists. MH (margin-leak) |
| 21 | Awaiting PO (ranked, Cost / Avg-d / Max-d, sortable) | 🟡 | Insights has a count + L1/L2/L3 + revenue, but no per-PM Cost/Avg-days/Max-days sortable table. MH |
| 22 | No-cost by brand / market | ❌ | Two BucketBars. NTH |
| 23 | Awaiting-PO by brand / market | ❌ | Two BucketBars. NTH |
Bottom line: PMs-KPI is the largest single gap — 6 of 12 charts absent, and the 6 present ones lack year/period slicing and the editable norms that make them operational.
4. Board — client/src/pages/Board/index.tsx, utils/board.ts, components/Board/BoardCard
| Item | Status | Note / where it lives |
|---|---|---|
| 7-state columns | 🟡 | 6 columns built (unassigned, open, hold, wip, replied, awaiting; board.ts:18). 7th synthetic Closed / Awaiting-PO column is missing (original CLOSED / AWAITING PO – SYNTHETIC overlay). MH |
| Customer-replied pseudo-status | ✅ | boardColumn/isAwaitingAgency — WIP + last touch ∉ colleague roster. Faithful. |
| Column SLA subtitles + per-column count | ✅ | col.slaVerb(thr) + count badge. |
| Group-by (6 permutations of WIP·Replied·Awaiting) | ❌ | Column order is fixed; original has 6 orderings (wip-agency-await … await-agency-wip). NTH |
16 toggleable card fields (card_fields) |
❌ | BoardCard is a fixed layout showing ~8 fields (number, jira status, link, shortDesc, brand·market, assignee, in-state days, lifetime, last activity). None toggleable. Missing: description, raisedBy, dueDate, stateChanged, comments, createJira, jiraBar, teamsLink. Original fields: cf-stateBadge/shortDesc/description/assignee/assignmentGroup/brandMarket/raisedBy/lifetime/dueDate/stateChanged/lastActivity/comments/createJira/showJiraStatus/showJiraBar/teamsChannelLink. NTH |
| Custom labels registry | ❌ | Original custom_label system (per-ticket labels + filter). Absent. NTH |
| Staleness / PO / jira-stuck / missing-cost badges | ❌ | BoardCard has no badges beyond the jira-status chip. NTH |
| Sort (14 options) | ❌ | No sort control. Original: default, number, status-age-asc/desc, lifetime-asc/desc, jira-assignee, jira-status, brand, cost-asc/desc, due-asc/desc, analyzed. NTH |
| Filters: search / brand / market / assignee | ✅ | Present in the board header. |
| Filters: region EU/LATAM sentinels | ❌ | latamAssignees is ingested (config.json) but the assignee dropdown has no EU/LATAM region grouping. NTH |
| Filters: requester, custom-label, jira-assignee, jira-status | ❌ | None. NTH |
| Filters: staleness (stale/updated/inactive/PO/jira-stuck/missing-cost), hide-empty, hide-cost | ❌ | Original group-by chips + status-label filters. Absent. NTH |
| List view | ✅ | TicketTable toggle (fixed columns, not the same field set). |
| Card click behaviour | 🟡 | Opens an in-app TicketDetailModal (+ a SNOW link icon). Original opens ServiceNow directly. Acceptable divergence. |
| Filtered Excel export | ✅ | New — utils/excel.utils.ts, 17 cols, honours active filters. |
| Azure DevOps focus board | ❌ | Out of scope per spec (§0). LOW |
5. PM Insights — client/src/pages/Insights/index.tsx + server/insights.ts
| Item | Status | Note / where it lives |
|---|---|---|
| Alert groups (Unassigned, Open/Assigned, On-Hold, WIP-stalled, WIP-no-Jira, Customer-replied, Awaiting) | ✅ | insights.ts 9 groups; support rules jiraBreached/clientOwed/isAwaitingAgency ported. |
| Informational groups (Lifetime ≥90d, Inactive requester) | ✅ | Present, excluded from Total Alerts. |
| ∑ Total Alerts + Revenue-at-Risk | ✅ | Distinct-count reconciled (verifier-log). |
| Waiting-PO count + revenue + L1/L2/L3 | 🟡 | Counts + revenue present; no per-level (L1/L2/L3) drill-down lists. insights.ts:140. NTH |
| PM-KPI table (# Tickets · $ Revenue · ⚠ Rev-at-Risk) | 🟡 | Table present; not sortable, and hidden-PMs from pm_kpi_settings not honoured. NTH |
| Excel export of the insights subtree (PM→Size→Ticket) | ❌ | Original xl-export.js writes PM/Size/Ticket-grouped sheets. FORGE has board export only. MH |
| Region / PO / people filters | ❌ | Original insights has EU/LATAM region + PO + people filters. Absent. NTH |
| Per-PM size stats | ❌ | No size breakdown inside the PM roll-up. NTH |
| "Missing tickets" overlay | ❌ | Original reconciliation overlay (tickets present in one source, absent in another). Absent. LOW |
6. Jira board page — MISSING ENTIRELY ❌ (MH for Jira ops, else NTH)
Original jira-board.html + jira-board.js (2378 LOC): columns from jira_board_state, cards, RITM↔Jira
link (jira-ritm-link.js), quick-filters, settings panel (board URL, scan controls, PO-cache clear), toasts,
scan countdown. FORGE has no route, no page, no jira_board_state ingest (dump-to-archives.mjs only
reads jira_status_map). Would live as client/src/pages/JiraBoard/ + a jira_board archive/table + a
/api/jira/board endpoint.
7. Sync / extension — extension/background.js (+ popup.js, options.js)
FORGE's collector is active-RITM only, shallow. Original background.js (5219 LOC) is far deeper.
| Aspect | Status | Note |
|---|---|---|
| SNOW active RITM scan | ✅ | pageCollector pages sc_req_item, assignment_groupLIKEMarketing Web Presence. |
| Field depth | 🟡 | Only 9 fields (number, short_desc, state, assigned_to, group, opened_at, due_date, updated_by, updated_on). No brand/market/BU/requester/cost/activity timeline/state-change history. Rich fields survive only from the seed, kept alive by COALESCE upsert. MH |
| SCTASK support | ❌ | RITM-only. Original scans SCTASK too (background.js sctask/SCTASK). NTH |
| Closed-ticket collector | ❌ | No closed scan; closed data is seed-only, goes stale. MH |
| Jira collector (+ statusDurations) | ❌ | No Jira scanning; jira_status_map/statusDurations/jira_board_state never refreshed. MH |
| Finance xlsx scan | ❌ | Original scans the SharePoint finance workbook (finance_xlsx); FORGE finance is a one-shot seed from the dump. MH |
| Popup = RITM list + Jira tab | 🟡 | FORGE popup is sync-only (server URL + token + "Sync now"). Original popup is a RITM browser + Jira tab. LOW (by design — web app replaces it) |
8. Finance — server/db.ts:applyFinance + scripts/dump-to-archives.mjs:106
| Item | Status | Note |
|---|---|---|
| Finance ingest (cost / currency / PO / invoiced) | ✅ | finance.json (860 rows) → fills finalCost/poNumber/invoiced, recomputes size. |
| Milestone / PM / state columns | 🟡 | Parsed into finance.json (milestone, pm, state) but never surfaced — DB has no columns for them and no UI reads them. NTH |
| Finance view (invoicing / milestone / filters) | ❌ | No finance page. PO/invoiced only appear as an Excel column + waiting-PO signal. NTH |
9. Size CALC tool — MISSING ENTIRELY ❌ (NTH)
Original size-calc.js (692 LOC): a labor→size estimator (inputs by role × hours × rate, config
lis_size_calc_cfg, MATCH/composite algorithms). FORGE has a server sizeOf(cost) helper
(db.ts:156) that assigns sizes to tickets, but no interactive estimator tool/page. Would live as
client/src/pages/SizeCalc/ + a lis_size_calc_cfg config key.
10. Config / options editors — MISSING ENTIRELY ❌ (mixed)
FORGE loads config read-only from config.json/app_config (seeded once). The original options.html/
options.js (629 LOC) is a rich settings surface with no FORGE equivalent (the FORGE Admin page is
Users+Tokens only). Missing editors, each GET /api/config reads but nothing writes:
| Setting (original id) | Status | Value |
|---|---|---|
Brand colours editor (brand_colors, add/reset) |
❌ | NTH — 45 brands seeded, not editable in-app. |
| SLA norms editors (otd/avgdays/asla/psla/ttfr/cresp) | ❌ | MH — required by §3a editable-norms gap. |
Insights thresholds editor (insights_thresholds) |
❌ | NTH |
Size cost-thresholds editor (otd_cost_thresh) |
❌ | NTH |
Card-field toggles (cf-*, 16) + mobile fields (mf-*) |
❌ | NTH — pairs with the board card-fields gap. |
Colleague names (colleagueNames) / LATAM assignees (latamAssignees) |
❌ | NTH — seeded, not editable. |
Display currency (displayCurrency) |
❌ | LOW — fixed GBP. |
Custom labels (custom-labels, clear-all) |
❌ | NTH |
SNOW field colours (color-*, 6) |
❌ | LOW — SNOW-page injection only, N/A to web app. |
Card layout breakpoints (card_layout) |
❌ | LOW |
| Scan intervals / enables (analyticsIntervalHours, snRescanInterval, dashboardEnabled, jiraNotificationsEnabled, soundEnabled, analyzeButtonsEnabled) | ❌ | LOW — extension/SNOW-page behaviour. |
Teams bot (teamsBotEnabled, chat names) |
❌ | LOW — out of scope. |
11. Other
| Item | Status | Note |
|---|---|---|
AI Advisor / at-risk (advisor-*.js, advisor-server/) |
❌ (intended) | Excluded per spec §1 ("user excluded AI"). Confirmed out of scope — do not port. |
Teams integration (teamsChannelLink, teams bot) |
❌ | Out of scope-ish. LOW |
Figma links (figma-links.js, 244 LOC) |
❌ | Optional per spec. LOW |
| Weather | — (no gap) | Excluded per spec and dead in the original — manifest keeps open-meteo/nominatim host perms but no weather JS remains. Nothing to port. |
Docs page (docs.html/docs.js) |
❌ | In-app help. LOW |
| Archive import / export | 🟡 | scripts/dump-to-archives.mjs + npm run reseed is a manual CLI path; no in-app import/export UI. LOW |
| SCTASK support | ❌ | See §7 — RITM-only. NTH |
| Extension popup RITM browser | 🟡 | See §7. LOW |
| New in FORGE (beyond original): RBAC (4 roles), Users/Tokens Admin page, session auth, Postgres, server analytics engine | ✅ | Net-additive; no parity gap. |
Legacy/unrouted FORGE pages: pages/Dashboard, pages/Stats |
— | Dashboard is unrouted (superseded by Board); Stats is a thin /stats count page. Housekeeping, not a gap. |
12. Recommended next phases (prioritized)
Phase A — PMs-KPI completion (highest value, biggest gap).
- Add year/period granularity + current-vs-previous-year to the 6 SLA heatmaps (
getSlaHeatmaps+ period control). (MH) - Editable norms — a
POST /api/config/normswrite path + inline norm panels (also unblocks the options editor). (MH) - Charts 18 (workload/month per-PM), 20 (missing final cost), 21 (awaiting-PO Cost/Avg/Max sortable). (MH)
- Chart 19 engagement +
CalendarHeatmap(needs per-day activity/jira-movement ingest). (MH, larger) - Charts 22/23 brand-market sub-cards. (NTH)
Phase B — Overall/Active fidelity.
6. YoY pies #5/#6 (fair-window + full) as DonutCharts. (MH)
7. #17 Jira status durations (requires statusDurations ingest — see Phase D). (MH)
8. Per-year segmentation + drill + YoY on by-BU/by-brand/by-market/by-requester; #15 min/median/max; #16 status-segmented brand; #/% toggle; CGO overlay. (NTH)
Phase C — Board operability. 9. Sort (14) + group-by (6) controls. (NTH) 10. Toggleable card fields (16) + badges (stale/PO/jira-stuck/missing-cost). (NTH) 11. Filters: region EU/LATAM, requester, jira-status/assignee, staleness, custom labels. (NTH) 12. 7th synthetic Closed/Awaiting-PO column. (MH)
Phase D — Deeper sync (unblocks a lot above). 13. Extend the collector: richer RITM fields + closed scan + Jira (statusDurations, jira_board_state) + finance scan + SCTASK. (MH — currently rich data is seed-only and goes stale.)
Phase E — Insights depth. 14. Excel export of the insights subtree (PM→Size→Ticket); waiting-PO L1/L2/L3 drill; sortable PM table + hidden-PM support; region/PO/people filters. (MH for export, else NTH.)
Phase F — Standalone features. 15. Jira board page (needs Phase D jira ingest). (MH for Jira ops.) 16. Options/config editor page (brand colours, thresholds, card fields, colleagues/region, currency, custom labels). (NTH.) 17. Size CALC estimator; finance view (milestone/invoicing — data already parsed). (NTH.)
Do not port: AI Advisor/at-risk, weather (excluded); Teams/Figma/ADO board/docs page (low/optional).
13. Addendum — refinements from the source inventory (corroborated)
A parallel deep read of the initial app's source confirmed the above and adds:
- Board columns: the original renders 5 live columns (Open/Assigned, On Hold, WIP, Customer-replied, Awaiting) + the synthetic Closed/Awaiting-PO column; Unassigned is folded into Open/Assigned. FORGE builds 6 (splits Unassigned out) and still lacks the synthetic 7th — the §4 "7th column MH" gap stands.
- Per-chart Excel export: in the original every analytics chart carries its own Excel button
(
_xlData/_xlBtn→xl-export.js). FORGE has no per-chart export — only the board export. Add as an NTH gap across Overall/Active/PMs-KPI. Insights groups also each export (see §5, MH). - Missing-tickets overlay lives on the 3 SLA/OTD charts (OTD/AvgDays/Assign/Preview), grouping the timestamp-less tickets by current Jira status (reachedUat / beforeUat / noJira) — reclassify the §5 "missing-tickets overlay" as part of the PMs-KPI SLA charts (still ❌).
- Collector scope: the original SN scan handles RITM + SCTASK + INC (not just RITM), computes
per-ticket
statusDurationsfrom status-change history, and runs a Jira scan as a tail after every SN scan. FORGE's collector does none of this (§7) — add INC alongside the SCTASK gap. - Threshold value drift: FORGE
config.jsonhaswaitingPo3: 18andcustomerReplied: 1, vs the original defaultswaitingPo3: 21/customerReplied: 3. Minor, but reconcile when the norms/thresholds editor lands (§10). LOW - Engagement KPI reads the live board (not
ticketsMeta), with twin Assist|Jira calendar heatmaps and a ~14-column sortable drill-down — confirms the §3b #19 scope (MH, larger build). - Size CALC uses 4 currencies (GBP/EUR/MXN + MXN·Brazil uplift ×1.111), roles DEV/CM/QA + auto-PM,
PM-budget tiering (
pmThreshold/pmHi/pmLo) — confirms §9. - AI Advisor is a full masked-digest + Node-bridge-to-Claude-CLI subsystem (
advisor-*.js+advisor-server/) with the At-risk breach engine — correctly excluded, do not port. - Archive import/export exists as in-app ⬇ Archive / ⬇ Active buttons in the original (FORGE has only
the
dump-to-archives.mjsCLI) — the §11 🟡 stands.
None of these change the phase priorities; they sharpen scope for Phases A, D, and E.
Next
principal — sequence Phase A (PMs-KPI) as the next build; confirm which board/insights sub-items are in v1 scope.