Commit Graph

8 Commits

Author SHA1 Message Date
Semih
09438cf513 fix(vin-decode): repair Business page 500 + drop redundant VIN buttons
- Business page returned 500: getTrialFunnel's `ORDER BY CASE bucket ...`
  referenced the SELECT-list alias inside an expression, which Postgres
  can't resolve (error 42703 "column bucket does not exist"). Wrap the
  GROUP BY in a subquery so `bucket` is a real column the ORDER BY can use.
  Verified against prod: original errors, fixed query returns ordered buckets.
- Remove the "Çözülen / Çözülemeyen VIN'ler" dashboard buttons — the VIN
  list already has a success/error status filter, so they were redundant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 02:22:21 +03:00
Semih
985592aa73 feat(vin-decode): WMI opportunity radar, per-source p50, unknown-VIN spike alarm
Systematize Faz 4 + close three observability gaps:

- WMI opportunity radar (getWmiOpportunities): group query_logs by
  substring(vin,1,3) = WMI, rank low-success WMIs by distinct-user demand.
  brandMatched=0 flags fully-uncovered manufacturer codes. Surfaced on the
  Trends page (#wmi) + a dashboard header shortcut. Replaces the manual Faz 4.
- Per-source p50: add percentile_cont(0.50) to getProviderAttempts and
  getProviderDeepStats; show P50·P95 on the dashboard and provider drill-down.
- unknown_vin_spike anomaly: track "tanınamadı" rate in the 15min/baseline
  windows, fire when it jumps >=2x baseline (or surges from ~0), and attribute
  the dominant failing source (+aborted count) in the Telegram message. Runs
  in the existing 5min anomaly cron; worker/telegram unchanged (generic type).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 01:58:26 +03:00
Semih
f05e0bc808 feat(vin-decode): add solved/unsolved VIN list shortcut buttons
Add two header buttons on the VIN decode dashboard linking to the
existing /vins list pre-filtered by success=true / success=false,
carrying the current time-range window via the `from` param so the
list matches the dashboard view. Shows actual VIN codes (not counts).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:07:55 +03:00
Semih
39c26bd4d4 feat(sase): VIN list management view
A user-list-style table at /projects/sase/vin-decode/vins for inspecting
and managing decoded VINs. Pairs with sase.tr#34 (cache-clear + delete
endpoints).

Repo (lib/sase/vin-list.ts)
- listVinDecodes(filter, sort, page) — raw SQL with dynamic WHERE for
  search (VIN/email), provider IN, success boolean, brand IDs, date
  range. Joins users + brands, computes EXISTS(vehicles) per row so the
  action menu knows whether DB delete is meaningful.
- KNOWN_PROVIDERS export.

Admin SDK
- clearVinCache({ vin, reason, founderId }) — POST cache-clear
- deleteVehicleByVin({ vin, reason, founderId }) — DELETE vehicle
- Result types: VinCacheClearResult, VinDeleteResult.
- SASE_ADMIN_ENDPOINTS list updated.

Panel routes
- POST /api/sase/vins/[vin]/cache-clear — auth + spoke-wired + reason
  ≥ 5 chars. Audit on both paths.
- DELETE /api/sase/vins/[vin] — same shape.

UI (/projects/sase/vin-decode/vins)
- URL-driven filters (VIN/email search, provider multi-select pills,
  success/fail toggle) — sharable links.
- Sortable columns: Tarih (createdAt) and RT (responseTimeMs).
- Per-row cells: timestamp · VIN (mono) · brand slug+name · user email
  (link to user detail) · provider badge (link to provider drill-down)
  · ok/fail badge (fail title = errorMessage) · responseTimeMs ·
  cacheSource from timings jsonb · action menu.
- Per-row action menu (gated on saseAdminWired):
    [Cache] — opens reason modal, POSTs cache-clear
    [DB]    — only when vehicles row exists; opens reason modal,
              destructive variant, DELETE
- Pager with prefix/postfix range + ←/→ links preserving filters.

Dashboard header gets a "VIN list →" pill next to Business/Trends.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 07:59:22 +03:00
Semih
b5ba8919b8 feat(sase): VIN observability Faz 4 — trends + provider drill-down
Closes Phase 8: long-term capacity view + per-provider deep dive.
Alert-threshold config UI deferred to teknikborc — the cron defaults
are well-calibrated enough to ship without a config table.

Trends page — /projects/sase/vin-decode/trends
1. Son 90 gün — daily decode volume sparkline, success/total ratio
   shading.
2. Haftalık büyüme — WoW % change for last 12 weeks, color-coded.
3. Provider yük dağılımı — stacked area for the last 30 days, one bar
   per day decomposed by winning provider; legend below.
4. Düşük destekli markalar — brands with ≥20 30d volume but ≤75%
   success. New-provider/decoder investment signal.
5. Peak heatmap — 7×24 grid (day-of-week × hour) for the last 30d,
   intensity-shaded; B2B working-hours pattern + infra scaling guide.
6. Cache hit trend — daily cache hit rate sparkline + 30d average.
7. Verimlilik composite — daily score combining success (50%), cache
   (20%), latency (20%, 1.0 at <500ms decaying to 0 at >10s), and
   1-fallback (10%). 14-day table view.

Provider drill-down — /providers/[name]?range=7d|30d
- Four KPIs: wins total, success rate, avg/P95 latency, chain-attempt
  count (separately tracked for chain key vs winning-source).
- Daily wins sparkline scoped to the range.
- Top 10 brands the provider successfully decoded for.
- Fallback chains the provider participated in.
- 20 most recent failures with sanitized VIN + user-link drill.

Dashboard wiring
- Winning-provider and provider-attempts cards on the main VIN
  dashboard now link each provider row to /providers/[name]. The
  timings-key aliases (pcat → parts-catalogs, vin_api → vin-api) are
  remapped so the link reaches the right page.
- New "Trends →" pill in the header next to "Business →".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 13:13:19 +03:00
Semih
e583f6b6be feat(sase): VIN business-impact dashboard — Faz 3 (no-quota model)
Sase.tr has no monthly VIN quota — access is gated by plan.brandCount
+ user_brands assignments. The PRD's Faz 3 quota-centric metrics don't
apply; this commit ships the 8 business signals that DO apply to that
model. All queries are RO joins of query_logs to subscription/plan/brand.

1. Tier × decode volume (30d) — unique users, decode count, decodes/user,
   and success rate per plan. Plan derived from each user's most-recent
   active/trial subscription.
2. Brand-tier alignment — for plans with brandCount > 0, the fraction
   of decodes that landed on a brand the user actually has access to via
   user_brands. Low alignment = wrong tier or upsell signal.
3. Dormant payers — active subscription + no decode in the last 14 days.
   Sorted by lastDecodeAt ASC (most dormant first), 30 rows.
4. Empty-handed payers — active subscription + zero lifetime decodes.
   Onboarding-broken signal, sorted oldest start_date first.
5. Trial → paid funnel — bucket trial users by decodes-during-trial
   (0, 1–2, 3–5, 6–10, 11–25, 26+) and show the conversion rate (any
   subsequent active/cancelled subscription) per bucket. Answers
   "how many decodes is the aha moment".
6. Retention cohort — weekly signup cohorts (mature ≥30d), split by
   "had a successful decode in first 7 days" vs not; columns show 30d
   active rate per bucket and the retention lift (pp difference).
7. Power users — top 20 by 30d decode count: plan, decode count,
   brand diversity, top brand + occurrences, success rate.
8. Tier mismatch — brand-spesifik plan kullanıcıları whose 30d decodes
   include ≥3 hits on brands they don't have access to. Upsell candidates
   (or refund/downgrade conversation).

All tables link emails to /projects/sase/users/[id] (existing detail page),
so the dashboard is a "find users to talk to" tool, not just numbers.

VIN dashboard header now has a "Business →" pill next to the time range
picker.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 12:49:14 +03:00
Semih
717e4ae369 feat(sase): VIN observability Faz 2 — error drill-down + deploy regression + BIP
Three additions to the VIN Decode Observability dashboard, all read-only.

Faz 2a — Error fingerprint drill-down
- New route /projects/sase/vin-decode/errors/[key] where [key] is one
  of the eight bucket keys (BUDGET_EXCEEDED, UNKNOWN_VIN, TIMEOUT, …).
- getErrorBucketDetail(key, range) returns: total + unique-user count,
  hourly time series, top 10 affected brands (joined to brands.name/
  slug), top providers (winning + chain attempts from timings keys),
  top 20 affected users with link to their detail page, six distinct
  sample error messages, last 30 raw failures (sanitized VIN, source,
  RT, message).
- Error breakdown cards on the main dashboard now link to this page
  with the current time range preserved.
- ERROR_BUCKET_KEYS + ErrorBucketKey exported so the [key] route
  validates against the same list.

Faz 2b — Deploy regression analysis
- listSaseDeploys(limit) fetches the Sase.tr Coolify app's recent
  finished deployments via the Coolify API (COOLIFY_API_TOKEN).
- analyzeDeployRegressions(deploys) slices a 30-minute window
  before deploy.startedAt and after deploy.finishedAt, computes the
  success-rate delta, and flags rows where the drop ≥ 10pp (and
  both windows have ≥ 5 samples).
- Dashboard card lists last ~10 deploys with before/after rates and
  Δ pp, color-coded; "regression" badge on flagged rows.

Faz 2c — BIP integration
- getVinRelatedInsights() reads from the panel-pg `insights` table:
  type='provider_quality' (always VIN-related by prompt-tag routing)
  + bug_triage insights whose body mentions VIN/decode/provider
  keywords. Deduped, severity-then-recency sorted.
- New dashboard card surfaces insight titles with severity + type
  badges, occurrence/user counts, confidence, and a link to the
  Gitea issue if one was opened.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 12:31:27 +03:00
Semih
66a5828ee6 feat(sase): VIN Decode Observability dashboard — Phase 8 Faz 1
Süper Panel VIN Decode Observability Module (SP-VIN-001), Faz 1 MVP.
Reads directly from Sase.tr's query_logs via DATABASE_URL_SASE_RO —
no PostHog hop, no ingestion worker, no aggregation tables. Sase.tr's
existing query_logs.timings jsonb already captures the full chain
context (per-provider durations, cache source, circuit-breaker state).

Repo (apps/web/src/lib/sase/vin-decode.ts)
- getOperationalHealth(range) — total/success/failure counts, success
  & error rates, P50/P95/P99 + avg response time, cache hit rate,
  timeout rate, unknown-VIN rate. Single raw query with FILTER aggs.
- getWinningProviders(range) — group by source (decoded by), share +
  avg response time per provider.
- getProviderAttempts(range) — group by timings jsonb keys
  (pcat/emex/pl24/vin_api). A row may contribute to multiple providers
  if the chain ran through several.
- getFallbackChains(range) — multi-provider rows grouped into chain
  strings (e.g. 'pcat→emex→pl24') with frequency.
- getErrorBreakdown(range) — bucket errors via regex
  (BUDGET_EXCEEDED, UNKNOWN_VIN, TIMEOUT, …) on errorMessage.
- getDailyTrend(days) — date_trunc('day') volume + success + failure
  + avg latency. Filled with zero-rows so the sparkline is continuous.
- getBrandBreakdown(range, limit) — top brands joined to brands.name/slug
  with per-brand success rate.

Dashboard (/projects/sase/vin-decode)
- 4-button time range picker (1h/24h/7d/30d) via URL search-params.
- 6 KPI cards (Success, Error, P95 RT, Cache hit, Timeout, Unknown VIN)
  color-coded by threshold.
- Two parallel provider views: "winning provider" (decode resolver)
  vs "provider attempt" (timings keys — wider, includes fallthroughs).
- Fallback chain frequency table.
- Error bucket cards with example messages.
- Top brands by query volume with success rate cells (green/red).
- 30-day daily-volume sparkline with success/total ratio shading.

Sase landing gets a "VIN Decode →" link next to the Users link.

No new tables, no migrations, no workers. RO-only by design.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 12:06:41 +03:00