Merge pull request 'fix(insights): suppress no-insight search-affordance misuse in tagger' (#6) from fix/suppress-search-affordance-noise into main
This commit was merged in pull request #6.
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"start": "tsx src/index.ts",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "tsx src/lib/dedup.smoke.ts"
|
||||
"test": "tsx src/lib/dedup.smoke.ts && tsx src/lib/tagger.smoke.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@panel/web": "workspace:*",
|
||||
|
||||
125
apps/worker/src/lib/tagger.smoke.ts
Normal file
125
apps/worker/src/lib/tagger.smoke.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* Smoke test for the tagger search-affordance-misuse suppression.
|
||||
* Run: pnpm --filter worker exec tsx src/lib/tagger.smoke.ts
|
||||
*
|
||||
* Guarantees the new guard is SURGICAL: it silences generic ux_friction /
|
||||
* frustrated_session ONLY for "user fiddling with the VIN search box, nothing
|
||||
* broken" sessions, while every concrete failure stays actionable.
|
||||
*/
|
||||
import type { SessionMeta } from "@prisma/client";
|
||||
import type { CanonicalEvent } from "./event-taxonomy";
|
||||
import { tagSession } from "./tagger";
|
||||
|
||||
let pass = 0;
|
||||
let fail = 0;
|
||||
function assert(label: string, cond: boolean): void {
|
||||
if (cond) pass++;
|
||||
else {
|
||||
fail++;
|
||||
console.error(`✗ ${label}`);
|
||||
}
|
||||
}
|
||||
|
||||
const ev = (name: string, properties: Record<string, unknown> = {}): CanonicalEvent =>
|
||||
({ name, properties, rawName: name } as unknown as CanonicalEvent);
|
||||
|
||||
function mkSession(over: Partial<SessionMeta>): SessionMeta {
|
||||
return {
|
||||
errorCount: 0,
|
||||
rageClickCount: 0,
|
||||
deadClickCount: 0,
|
||||
network5xxCount: 0,
|
||||
startUrl: "https://sase.tr/dashboard/search",
|
||||
durationMs: 200_000,
|
||||
isAuthenticated: true,
|
||||
clickCount: 5,
|
||||
subscriptionTier: null,
|
||||
startedAt: new Date(1_700_000_000_000),
|
||||
...over,
|
||||
} as unknown as SessionMeta;
|
||||
}
|
||||
const ctx = (events: CanonicalEvent[], userProperties: Record<string, unknown> = {}) =>
|
||||
({ customEvents: events, userProperties, groupProperties: null });
|
||||
|
||||
const tags = (s: SessionMeta, events: CanonicalEvent[], up: Record<string, unknown> = {}) =>
|
||||
tagSession(s, ctx(events, up)).tags;
|
||||
|
||||
// 1) The reported case: VIN search-box misuse, 17 rage clicks, NOTHING broken → suppressed.
|
||||
{
|
||||
const t = tags(
|
||||
mkSession({ rageClickCount: 17, errorCount: 0, network5xxCount: 0 }),
|
||||
[ev("search_input_focused"), ev("search_history_item_selected"), ev("parts_panel_viewed")],
|
||||
);
|
||||
assert("misuse: no ux_friction", !t.includes("ux_friction"));
|
||||
assert("misuse: no frustrated_session", !t.includes("frustrated_session"));
|
||||
assert("misuse: tag-less → will be discarded", t.length === 0);
|
||||
}
|
||||
|
||||
// 2) Real JS errors + rage → still bug_suspected (misuse guard off when errorCount>0).
|
||||
{
|
||||
const t = tags(
|
||||
mkSession({ rageClickCount: 5, errorCount: 2 }),
|
||||
[ev("search_input_focused")],
|
||||
);
|
||||
assert("js-error: bug_suspected kept", t.includes("bug_suspected"));
|
||||
}
|
||||
|
||||
// 3) Server 5xx storm → server_error_impact kept (misuse guard off when 5xx>0).
|
||||
{
|
||||
const t = tags(
|
||||
mkSession({ rageClickCount: 4, network5xxCount: 3 }),
|
||||
[ev("search_input_focused")],
|
||||
);
|
||||
assert("5xx: server_error_impact kept", t.includes("server_error_impact"));
|
||||
}
|
||||
|
||||
// 4) Payment friction on a search session → conversion signal kept (misuse guard off).
|
||||
{
|
||||
const t = tags(
|
||||
mkSession({ rageClickCount: 4 }),
|
||||
[ev("search_input_focused"), ev("payment_initiated")],
|
||||
);
|
||||
assert("payment: payment_friction kept", t.includes("payment_friction"));
|
||||
assert("payment: frustrated_session NOT suppressed", t.includes("frustrated_session"));
|
||||
}
|
||||
|
||||
// 5) Category-tree rage WITHOUT the search box → still surfaces (scope = search box only).
|
||||
{
|
||||
const t = tags(
|
||||
mkSession({ rageClickCount: 10, startUrl: "https://sase.tr/dashboard/vehicles/x/categories/y" }),
|
||||
[ev("parts_panel_viewed")],
|
||||
);
|
||||
assert("category: ux_friction kept (no search_input_focused)", t.includes("ux_friction"));
|
||||
assert("category: frustrated_session kept", t.includes("frustrated_session"));
|
||||
}
|
||||
|
||||
// 6) Real upstream VIN decode failures on a search session → provider signal kept.
|
||||
{
|
||||
const t = tags(
|
||||
mkSession({ rageClickCount: 3 }),
|
||||
[
|
||||
ev("search_input_focused"),
|
||||
ev("vin_decode_failed", { provider_attempted: "PL24" }),
|
||||
ev("vin_decode_failed", { provider_attempted: "PL24" }),
|
||||
],
|
||||
);
|
||||
assert("vin-fail: vin_decode_fail_pattern kept", t.includes("vin_decode_fail_pattern"));
|
||||
}
|
||||
|
||||
// 7) Search box used but ≥3 validation failures → that has its own P3 signal, not suppressed.
|
||||
{
|
||||
const t = tags(
|
||||
mkSession({ rageClickCount: 6 }),
|
||||
[
|
||||
ev("search_input_focused"),
|
||||
ev("search_input_validation_failed"),
|
||||
ev("search_input_validation_failed"),
|
||||
ev("search_input_validation_failed"),
|
||||
],
|
||||
);
|
||||
assert("validation-friction: search_validation_friction kept", t.includes("search_validation_friction"));
|
||||
assert("validation-friction: frustrated_session NOT suppressed", t.includes("frustrated_session"));
|
||||
}
|
||||
|
||||
console.log(`\ntagger smoke: ${pass} passed, ${fail} failed`);
|
||||
if (fail > 0) process.exit(1);
|
||||
@@ -47,6 +47,27 @@ export function tagSession(s: SessionMeta, ctx: EnrichmentCtx | null): TagResult
|
||||
const userProps = ctx?.userProperties ?? {};
|
||||
const groupProps = ctx?.groupProperties ?? null;
|
||||
|
||||
// ─── Expected search-box misuse → not insight-worthy ───
|
||||
// The /dashboard/search box is VIN-only, but users routinely use it to look
|
||||
// for a part by *name* (e.g. "Cam düğme") or fiddle with search/history and
|
||||
// rage-click out of affordance confusion — while *nothing is actually broken*
|
||||
// (no JS errors, no 5xx, no decode/validation/provider failure, no payment).
|
||||
// These pure-affordance rage sessions are noise, not product defects, so we
|
||||
// do NOT let the generic ux_friction / frustrated_session tags fire for them;
|
||||
// with no other actionable tag the session ends up tag-less → discarded
|
||||
// (no compress / analyze / insight). Real failures still carry a concrete
|
||||
// event below and stay actionable. Scoped narrowly to search-box sessions on
|
||||
// purpose, to avoid hiding genuine parts/category bugs.
|
||||
const searchAffordanceMisuse =
|
||||
s.errorCount === 0 &&
|
||||
s.network5xxCount === 0 &&
|
||||
has(events, "search_input_focused") &&
|
||||
!has(events, "vin_decode_failed") &&
|
||||
count(events, "search_input_validation_failed") < 3 &&
|
||||
!has(events, "payment_initiated") &&
|
||||
!has(events, "payment_failed") &&
|
||||
!has(events, "checkout_started");
|
||||
|
||||
// ─── Bug detection (rrweb-based, generic fallback) ───
|
||||
if (s.errorCount > 0 && (s.rageClickCount > 0 || s.network5xxCount > 0)) {
|
||||
tags.push("bug_suspected");
|
||||
@@ -57,7 +78,7 @@ export function tagSession(s: SessionMeta, ctx: EnrichmentCtx | null): TagResult
|
||||
severity = bump(severity, "P1");
|
||||
}
|
||||
// ─── UX friction ───
|
||||
if (s.errorCount === 0 && (s.rageClickCount > 0 || s.deadClickCount > 0)) {
|
||||
if (s.errorCount === 0 && (s.rageClickCount > 0 || s.deadClickCount > 0) && !searchAffordanceMisuse) {
|
||||
tags.push("ux_friction");
|
||||
severity = bump(severity, "P2");
|
||||
}
|
||||
@@ -65,7 +86,7 @@ export function tagSession(s: SessionMeta, ctx: EnrichmentCtx | null): TagResult
|
||||
// ─── Frustrated session ───
|
||||
// Sustained rage clicking (3+ clusters) is a stronger signal than a single
|
||||
// cluster — promote it past ux_friction so it surfaces above generic noise.
|
||||
if (s.rageClickCount >= 3) {
|
||||
if (s.rageClickCount >= 3 && !searchAffordanceMisuse) {
|
||||
tags.push("frustrated_session");
|
||||
severity = bump(severity, "P2");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user