Skip to content

feat: reuse agentic URL classification for referral-traffic topic/category enrichment#2641

Open
cwjwisse wants to merge 4 commits into
mainfrom
feat/referral-traffic-topic-category-enrichment
Open

feat: reuse agentic URL classification for referral-traffic topic/category enrichment#2641
cwjwisse wants to merge 4 commits into
mainfrom
feat/referral-traffic-topic-category-enrichment

Conversation

@cwjwisse

@cwjwisse cwjwisse commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

What

Reuse the agentic (CDN-logs) URL classification rules to enrich referral-traffic reports with topic + category columns — both the weekly (llmo-referral-traffic) and daily (llmo-referral-traffic-daily) audits. No page-intent replacement; no new per-URL LLM calls.

How

  • Shared JS classifier src/common/agentic-url-classification.js — JS twin of the existing SQL CASE WHEN REGEXP_LIKE(...) logic. createClassifier(rules, { log }) compiles all patterns once and returns { classify(urlPath) }, or null when no rules are present.
  • Rules-present gatehasClassificationRules() false (null/error/empty) ⇒ enrichment skipped entirely (rows are not tagged 'Other').
  • Cost model — LLM authors regex rules once per site (Tier A, cached in Postgres); every run reuses them with zero LLM (Tier B). Same lever that replaced the deleted per-URL page-intent path.
  • Spreadsheet safety — shared src/common/spreadsheet-safe.js neutralizes formula-injection cells for both the weekly Excel and daily CSV paths.
  • Columns are explicit & stable — weekly WEEKLY_COLUMNS, daily 14-col CSV_COLUMNS; topic/category always present, empty '' when not classifying.

Hardening (from prior review rounds)

  • ReDoS guardtoRegExp rejects non-string / >1000-char / catastrophic-backtracking patterns (5 heuristics); classify caps input to 2048 chars. Guards are a screen, not a proof (see ADR caveats).
  • Dialect drift — V8 Irregexp vs Trino RE2J: POSIX classes / possessive quantifiers / mid-pattern inline flags silently skipped; covered by SQL-twin parity tests.

Docs

  • ADR: docs/decisions/001-reuse-agentic-url-classification-for-referral-traffic.md
  • Spec: docs/specs/referral-traffic-topic-category-enrichment.md

Caveats

  • P1: referral sites without agentic rules get no topic/category (by design).
  • P2: agentic regex is authored against the full CDN-log url; referral classifies on path only — host/scheme-dependent patterns won't match.

Testing

  • New: test/common/agentic-url-classification.test.js, test/common/spreadsheet-safe.test.js.
  • Full suite green: 100% lines/branches/statements; 103 targeted tests passing.

🤖 Generated with Claude Code

Review notes

  • Incidental API migration: src/llmo-referral-traffic/handler.js switches site.getSiteId()site.getId() (the current data-access accessor). Unrelated to the topic/category feature but included here; called out for reviewability per PR feedback.
  • Review nits addressed (commit afc04d06, all non-blocking): timing logs around the agentic rule fetch in both referral handlers; doc note on the deliberate CSV - neutralization; doc note that the RE2J/Trino rule dialect excludes backreferences/lookaround (explicit ReDoS-safety rationale).

@cwjwisse cwjwisse requested a review from MysticatBot June 11, 2026 05:24
@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@MysticatBot MysticatBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @cwjwisse,

Verdict: Comment - well-structured feature with thorough documentation and tests; a few minor observations below.
Changes: Reuses agentic CDN-logs regex classification rules to enrich both weekly and daily referral-traffic reports with topic/category columns, via a new shared JS classifier module and a consolidated spreadsheet-safety module (15 files).

Non-blocking (4): minor issues and suggestions
  • nit: escapeCsvValue in src/common/spreadsheet-safe.js will prefix a single quote on any value whose string form starts with - (the formula trigger list includes -). No current CSV field in these reports carries negative numbers, so this is theoretical - but worth a comment or guard (!isNaN(value) skip) if a future column could hold negative deltas. - src/common/spreadsheet-safe.js:79
  • nit: site.getSiteId() changed to site.getId() is an unrelated API migration mixed into the feature PR without mention in the description. Consider noting it in the PR body for reviewability. - src/llmo-referral-traffic/handler.js:133
  • suggestion: Add a brief note in toRegExp that the RE2J source dialect (Trino) inherently excludes constructs (backreferences, lookaheads) that could bypass the ReDoS heuristics - makes the implicit security argument explicit for future maintainers. - src/common/agentic-url-classification.js:165
  • suggestion: Consider adding timing logs around fetchAgenticUrlClassificationRules in both handlers - the Postgres fetch latency directly impacts Lambda execution time and is the first thing needed during latency investigations. - src/llmo-referral-traffic-daily/handler.js:228

Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 1m 16s | Cost: $5.77 | Commit: 1644b39625848d215d84e73197462797415ad5e8
If this code review was useful, please react with 👍. Otherwise, react with 👎.

@github-actions

Copy link
Copy Markdown
Contributor

This PR will trigger a minor release when merged.

cwjwisse and others added 2 commits June 11, 2026 09:56
…egory enrichment

Add topic and category enrichment to both weekly and daily LLMO
referral-traffic audits by reusing the existing agentic (CDN-logs)
two-tier regex-rule classifier. No per-URL LLM calls; rules are
authored once per site and cached in Postgres.

- New shared JS classifier src/common/agentic-url-classification.js
  (JS twin of the Athena/Trino SQL CASE/COALESCE): createClassifier
  compile-once factory plus classifyTopic/classifyPageType parity
  surface. Rules-present gate skips enrichment entirely when no rules.
- ReDoS defense: compile-time REDOS_HEURISTICS screen plus 2048-char
  input cap; bad/unsafe patterns dropped (rule -> 'Other'), one
  aggregated log.warn. No new dependency.
- Formula-injection defense: shared src/common/spreadsheet-safe.js
  (sanitizeSpreadsheetValue, escapeCsvValue, serializeCsv) used by
  both CSV (daily) and Excel (weekly) paths.
- Daily handler: stable 14-column CSV always; topic/category empty
  when classifier absent. Weekly handler: explicit WEEKLY_COLUMNS,
  topic/category always present.
- Export query-builder SQL helpers for JS-twin parity tests.
- ADR docs/decisions/001 and spec
  docs/specs/referral-traffic-topic-category-enrichment.md.

100% line/branch/statement coverage maintained.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The agentic-daily-export and referral-daily-export specs created esmock
module mocks on every test but never purged them. Unpurged esmock
registrations leak into the global module registry and cause
nondeterministic cross-file pollution when the full suite runs in a
single process — surfacing in CI as "log.warn is not a function" and
reading ".args" of null in these specs.

Track each esmocked module and esmock.purge() it in afterEach, matching
the existing convention in handler.test.js, cdn-analysis, preflight, and
llmo-config-utils specs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cwjwisse cwjwisse force-pushed the feat/referral-traffic-topic-category-enrichment branch from 7d152fe to 703ad5a Compare June 11, 2026 08:02
const cappedPath = capUrlPath(urlPath);
return {
topic: applyTopic(cappedPath, topicCompiled),
category: applyPageType(cappedPath, pageCompiled),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

category: applyPageType(cappedPath, pageCompiled),
this is actually pagetype and topic is actually category here

the naming are kept due to old convention

cwjwisse and others added 2 commits June 11, 2026 10:16
- Time the agentic classification-rule fetch in both the weekly and daily
  referral handlers; the Postgres fetch latency feeds Lambda runtime and is
  the first signal to check during latency investigations.
- Document why a leading '-' is deliberately neutralized in CSV cells (no
  negative-numeric column today; quoting kept as the safe default).
- Note in toRegExp that the RE2J/Trino source dialect excludes backreferences
  and lookaround, making the implicit ReDoS-safety argument explicit.

Non-blocking feedback from MysticatBot on PR #2641.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t classify()

akshaymagapu flagged that the classify() return maps `category` ← page-type
and `topic` ← the agentic category rules — the output keys are the inverse of
the source-rule names. The module header documents this, but the call site had
no inline note. Add one explaining the legacy convention is kept for backward
compatibility with downstream report columns.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants