Files
forge/claude_artifacts/FORGE-parity-spec.md
T
Dmytro Tkachenko 28d817ebe9 Init
2026-08-29 11:59:28 +03:00

13 KiB
Raw Blame History

FORGE parity spec — rebuilding "Let it Snow" on FORGE (Husky design)

Deep-check of the initial app (/Users/dmytrotkachenko/WebstormProjects/Let It Snow) cross-referenced with storage-dump.json (its localStorage export) and the Husky design template. Goal: a functionally near-identical app under FORGE's stack (Vite+React+TS client · Express+TS server · Postgres forge) with a new view built from Husky's hand-rolled chart components.

Sources: 6 discovery agents (analytics charts · KPI/SLA/finance · board behavior · data inventory · Husky design · architecture docs). All findings self-verified.


0. Load-bearing architecture facts

  1. Every board is a READ-ONLY mirror rebuilt from storage by background scans. No drag-and-drop write-back to ServiceNow/Jira anywhere (the ADO board has local DnD only; the one write action is "Create Jira"). → FORGE keeps its model: the Chrome extension scans and POSTs to /api/sync; the web app renders DB state and links out to ServiceNow/Jira for changes. No transitions to build.
  2. Single analytics source in the app: analytics_data (954 ticketsMeta records
    • precomputed aggregations). Everything else enriches it, joined on the RITM number.
  3. No charting library in either app — Let it Snow hand-rolls SVG/CSS; Husky does too. FORGE rebuilds charts as Husky components (or a real lib if we choose; spec assumes Husky-style).
  4. Money rule: finalCost = 0 means absence of price, not free work — never counts as money, never gets a size, always a "missing cost" margin leak.
  5. Currency: display currency (default GBP); fixed cross-rates 1 GBP = 1.2 EUR = 20 MXN.

1. Feature/view map (initial app)

Screen Rebuild target in FORGE
Popup (RITM list + Jira tab) N/A (extension already syncs; optional toolbar)
Dashboard: Kanban board + List view Board page (7-state columns, filters, card fields, list toggle)
Analytics panel — 3 tabs: Overall / Active / PMs KPI 3 analytics pages (the bulk of "missing statistics")
PM Insights popup (alert KPIs + drill-downs + Excel) Insights page
At-risk / AI Advisor OUT OF SCOPE (user excluded AI)
Jira board (read-only mirror) Jira board page (later phase)
Size CALC (labor→size estimator) Tool (later phase)
Figma links, Teams, Weather out of scope / optional

Excluded per DEAD_CODE.md: melody.js, removed finance-filter UI, live-FX fetch, USD from FX cache, Assist/Jira bar-graph toggle (replaced by dual calendars).


2. Statistics / charts inventory (29) → Husky component mapping

TAB 1 — Overall (11)

# Chart Data Husky component
1 Opened per month (grouped bars ±YoY, Brand/Market/CGO sub-group, bar/line) openedByMonth/ByDay, ticketsMeta new GroupedBars (from MonthlyByUser) + TrendArea
2 Closed per month closedByMonth same
3 Revenue per month (closed, cost/tickets, ±YoY) ticketsMeta.finalCost+FX GroupedBars/TrendArea
4 Lifetime-at-close histogram (6 buckets) + median/avg/closed tiles ticketsMeta open→close BucketBars + KpiTile×3
5 Opened YoY pie (fair/full) openedByDay StateDonut/DonutChart
6 Closed YoY pie closedByDay donut
7 By business unit (per-year, brand drill-down) ticketsMeta.businessUnit BucketBars + expandable
8 By requester (bars + YoY, market drill-down) byRequester RequestorBars + drill
9 Ticket-share donut (#/%, top 18 + Others) byRequester StateDonut/InsightChart pie
10 By brand (per-year segments, market drill, YoY) ticketsMeta.brand/market BucketBars segmented
11 By market same BucketBars

TAB 2 — Active (6) — from live board tickets (status='active')

# Chart Data Husky component
12 Open-not-closed by month active tickets TrendArea/bars
13 By status (6 kanban cats) active BucketBars (state colors)
14 Age histogram (0-50…200+ days) active openedAt BucketBars
15 Time-in-status min/median/max active grouped mini-bars
16 By brand, status-segmented (market drill) active stacked BucketBars
17 Jira status durations (workflow order) jira_status_map.statusDurations BucketBars ordered

TAB 3 — PMs KPI (12)

# Chart Data Husky component
18 Workload by month (revenue, prev/cur yr, per-PM) ticketsMeta closed cost GroupedBars + MonthlyByUser
19 PM Engagement (backlog/Assist✓/Jira✓/Stale) + dual daily-activity heatmaps live meta.activity + jira movements AssigneeBars + new CalendarHeatmap
20 Missing final cost (ranked PM bars + drill) ticketsMeta.finalCost null AssigneeBars + list
21 Awaiting PO (ranked, Cost/Avg-d/Max-d, sortable) finance PO + sn_waiting_po_meta + Jira AssigneeBars + table
22 No-cost by brand/market derived BucketBars×2
23 Awaiting-PO by brand/market derived BucketBars×2
24 On-Time Delivery heatmap (PM×size, cur+prev yr) + editable norms close-days vs otd_day_norms new KpiHeatmap
25 Avg days to close heatmap vs avgdays_day_norms KpiHeatmap
26 Time to assign PM heatmap firstAssignedDate vs asla_day_norms KpiHeatmap
27 Time to send preview heatmap inUatAt vs psla_day_norms KpiHeatmap
28 Avg TTFR heatmap ttfrMinutes vs lisr_ttfr_norms (hrs) KpiHeatmap
29 Avg PM response heatmap clientRespMinutes vs lisr_cresp_norms KpiHeatmap

New components needed (not in Husky): KpiHeatmap (PM×size grid vs norms, green/red), CalendarHeatmap (GitHub-style daily activity), GroupedBars (multi-year month bars — extend MonthlyByUser), a period/granularity control (year/quarter/month + ±YoY).

KPI definitions (SLA grid #2429)

  • TTFR = ttfrMinutes/1440 (days), anchor openedDate, norm hours.
  • PM response (cresp) = clientRespMinutes/1440, anchor openedDate. (Our reply speed, not client wait.)
  • Avg close = daysBetween(opened, closed), anchor closedDate.
  • OTD = same duration scored vs otd_day_norms; on-time% = onTime·100/scored.
  • Assign SLA = max(0, daysBetween(fulfillment, firstAssigned)), anchor fulfillmentDate.
  • Preview SLA = daysBetween(toDoAt, inUatAt), anchor inUatAt.
  • Grid cell = {avgDays, count, onTime, onTimePct}, per PM×size; "No cost" column norm = 1d.
  • Periods: current vs previous Year, and Q1Q4 each.

3. PM Insights alert KPIs (the Insights page)

Thresholds insights_thresholds (days): unassigned:1, assigned:2, hold:5, wip:7, customerReplied:3, awaiting:7, lifetime:90, jiraStuck:7, jiraUAT:7, noChase:5, waitingPo1:7, waitingPo2:14, waitingPo3:21.

Alert lists: Unassigned · Open/Assigned-long · On-Hold (age OR jiraBreached OR clientOwed) · WIP-stalled (Jira age) · WIP-no-Jira · Customer-replied · Awaiting-info · Lifetime-monsters (≥90d) · Inactive-requester · ∑ Total Alerts. Waiting-PO with L1/L2/L3 escalation + waitingPoRevenue. PM-KPI table: # Tickets · $ Revenue · ⚠ Rev-at-Risk (sortable, hidden PMs from pm_kpi_settings). Excel export preserves PM→Size→Ticket grouping.

Support rules: _jiraBreached, _clientOwed (client replied last ≥1d), _isAwaitingAgency (WIP + last touch by client → "Customer replied").


4. Board page (7-state kanban + list)

Columns (fixed order): Unassigned → Open/Assigned → On Hold → [WIP · Customer-replied · Awaiting] → Closed/Awaiting-PO*. * middle-3 reorderable via group-by (6 perms).

  • Customer-replied is a pseudo-status: WIP ticket where lastActivityBy ∉ colleague roster.
  • Column SLA subtitles ("assign within Nd", "max Nd in progress", …); per-column count.
  • Filters: search, brand, market, assignee(+region EU/LATAM sentinels), requester, custom-label, jira-assignee, jira-status, staleness (stale/updated/inactive/PO/jira-stuck/missing-cost), hide-empty, hide-cost.
  • Sort (14): default/number/status-age/lifetime/jira/brand/cost/due/analyzed.
  • 16 toggleable card fields (card_fields): stateBadge, shortDesc, description, assignee, group, brand-market pill, raisedBy, lifetime, dueDate, stateChanged, lastActivity, comments, createJira, jiraStatus, jiraBar, teamsLink.
  • Card click → opens ServiceNow in new tab (no in-app transition). List view = same data as table.
  • Waiting-PO detection: synthetic closed card OR Jira "Waiting PO" OR empty PO cell in finance Excel.

5. Data contract — what to ingest (fixes "not all statistics moved")

FORGE currently seeds only 3 dump keys (sn_tickets, analytics_meta_cache, jira_status_map → 966 tickets). The analytics engine needs the full dump:

Dump key Rows Feeds
analytics_data.ticketsMeta 954 THE analytics dataset (SLA/cost/jira/dates) — charts 1-11,18-29
analytics_data.{openedByMonth,closedByMonth,openedByDay,closedByDay} time-series (or recompute server-side)
analytics_data.byRequester(Numbers) 280 requester/brand/market drill-downs
analytics_meta_cache 965 closed-ticket enrichment
sn_tickets 100 active board + activity timeline
sn_waiting_po_meta 884 waiting-PO backlog
jira_status_map 127 RITM↔Jira, statusDurations
jira_board_state/snapshot 1045 Jira board page
finance_xlsx_data 1128 PO/invoicing/milestones
insights_thresholds, pm_kpi_settings insights + PM roster
fx_rates_cache, lis_size_calc_cfg FX + size model
brand_colors, snow_colors, sn_states_order palettes + column order
windowLocalStorage *_norms (psla/otd/ttfr/cresp/avgdays/asla) SLA target lines

Ticket master fields (from ticketsMeta): number, shortDesc, state, year, brand, market, businessUnit, assignedTo, openedBy, openedDate, closedDate, firstAssignedDate, firstReplyAt, fulfillmentDate, toDoAt, inUatAt, ttfrMinutes, clientRespMinutes, finalCost, currencyCode, jiraKey.

Coverage: ~24 months (2024-08 → 2026-08); 45 brands, 41 markets, 8 BUs, 10 PMs, 280 requesters; GBP/EUR/MXN. Data-quality: businessUnit casing dupes, currency blanks/junk ("NO"/"PART"), "(Inactive)" suffixes, finalCost is a string, two date formats — normalize on ingest.


6. Design mapping (Husky as template)

  • Stack/tokens: Husky's SCSS-module system, :root tokens, card+mono-label+srOnly skeleton, useInView+useCountUp reveal hooks. Component = folder (index.tsx + .module.scss).
  • Reuse verbatim: KpiTile, StateDonut, BucketBars, AssigneeBars, RequestorBars, TrendArea, MonthlyByUser, InsightChart, plus Stats-page archetypes (ActivityList, IssuesBySeverityCard, DonutCard).
  • Build new (Husky-styled): KpiHeatmap, CalendarHeatmap, GroupedBars, period control.
  • Palettes: size {XS:#14b8a6,S:#1a73e8,M:#f59e0b,L:#10b981,XL:#8b5cf6,XXL:#ef4444}; year ['#f97316','#1a73e8','#16a34a','#dc2626','#7c3aed']; state colors per §4; brand from brand_colors.
  • FORGE already uses the indigo palette (#6366f1) matching Let it Snow's UI.primary — keep it, or adopt Husky's Fluent blue. DECISION NEEDED.

7. Phased build plan

  • Phase 0 — Full data ingest (foundation). Extend dump-to-archives + schema/seed to load analytics_data.ticketsMeta (954) + finance + waiting-PO + jira board + thresholds + norms + size cfg + palettes. Normalize dimensions. This alone restores the data behind every stat.
  • Phase 1 — Server analytics engine. Compute aggregations from the DB (opened/closed by month/day, byRequester/brand/market/BU, revenue/month, lifetime buckets, the 6 SLA metrics PM×size, missing-cost, waiting-PO, engagement). Expose /api/analytics/*. Tests.
  • Phase 2 — Overall tab (charts 1-11). New view on Husky components.
  • Phase 3 — Active tab (12-17) + Board page redesign (7-state, filters, card fields, list).
  • Phase 4 — PMs KPI tab (18-29): KpiHeatmap + editable norms + engagement + CalendarHeatmap.
  • Phase 5 — PM Insights page (alerts, drill-downs, Excel export).
  • Phase 6 — Jira board page · Size CALC · finance detail (as desired).

Each phase: engineer→reviewer→verifier; dba for schema; designer for new components.


8. Decisions to resolve before/within the build

  1. Size algorithm — three coexist (nearest-nominal / largest-fits / Excel-MATCH). Pick one (recommend composite for cards, as the app defaults).
  2. psla_day_norms default mismatch (dashboard {XS:1…XXL:15} vs collector {XS:3…XXL:30}). Pick one.
  3. FX — keep fixed GBP-base rates (recommended; live-fetch was retired).
  4. Palette — keep FORGE indigo, or switch to Husky Fluent blue.
  5. Scope of charts for v1 — all 29, or the high-value subset first (recommend Overall + SLA heatmaps).
  6. Brand naming — popup says "FORGE Tasks", product "Let it Snow" → FORGE.
  7. Boards — SNOW board is core; Jira/ADO boards are later/optional.