diff --git a/custom-skills/35-seo-signal-validation/code/CLAUDE.md b/custom-skills/35-seo-signal-validation/code/CLAUDE.md index 4501a1b..da26660 100644 --- a/custom-skills/35-seo-signal-validation/code/CLAUDE.md +++ b/custom-skills/35-seo-signal-validation/code/CLAUDE.md @@ -12,6 +12,8 @@ python3 scripts/gsc_signal_delta.py \ Returns day-normalized site totals, top gainers/decliners, and a `verdict_hint` (heuristic only — the final verdict is the skill's job, after L2/L3). +**Surge-tuning note**: `verdict_hint` and `in_top_movers` are calibrated for upward "surge" claims (movers ranked by click gain). For a claimed *drop*, inspect `top_decliners` directly rather than relying on the hint. + ## Getting the exports `mcp__dda__gsc_fetch_performance` (property pinned per workspace, e.g. JHR `sc-domain:josunhotel.com`) → save the query-dimension rows to a file → run the diff --git a/custom-skills/35-seo-signal-validation/code/scripts/gsc_signal_delta.py b/custom-skills/35-seo-signal-validation/code/scripts/gsc_signal_delta.py index 79c718e..fc34263 100644 --- a/custom-skills/35-seo-signal-validation/code/scripts/gsc_signal_delta.py +++ b/custom-skills/35-seo-signal-validation/code/scripts/gsc_signal_delta.py @@ -106,9 +106,10 @@ def compute_delta(recent, prior, recent_days, prior_days, gainer_terms = {g["query"] for g in gainers} rc, pc = r_by.get(claim_term, {}), p_by.get(claim_term, {}) in_movers = claim_term in gainer_terms + found = bool(rc or pc) share = (rc.get("clicks", 0.0) / rt["clicks"] * 100) if rt["clicks"] else 0.0 out["claim_term"] = { - "term": claim_term, "found": bool(rc or pc), + "term": claim_term, "found": found, "recent": {"clicks": rc.get("clicks", 0.0), "impressions": rc.get("impressions", 0.0), "position": rc.get("position")}, @@ -118,7 +119,12 @@ def compute_delta(recent, prior, recent_days, prior_days, "in_top_movers": in_movers, "click_share_pct": round(share, 2), } - if not in_movers and share < 1.0: + if not found: + out["verdict_hint"] = ( + f"'{claim_term}' is absent from both GSC windows (no impressions / " + f"likely anonymized) -> INCONCLUSIVE, not refuted; confirm via live " + f"SERP + entity layer.") + elif not in_movers and share < 1.0: out["verdict_hint"] = ( f"'{claim_term}' contributes {share:.2f}% of recent clicks and is " f"absent from top movers -> claimed impact likely ARTIFACT; real " diff --git a/custom-skills/35-seo-signal-validation/code/scripts/test_gsc_signal_delta.py b/custom-skills/35-seo-signal-validation/code/scripts/test_gsc_signal_delta.py index 79d6c9a..57a3f6a 100644 --- a/custom-skills/35-seo-signal-validation/code/scripts/test_gsc_signal_delta.py +++ b/custom-skills/35-seo-signal-validation/code/scripts/test_gsc_signal_delta.py @@ -40,6 +40,12 @@ def test_day_normalization(): assert out["site_totals"]["prior"]["clicks_per_day"] == 6.93 # 208/30 +def test_absent_claim_term_inconclusive(): + out = compute_delta(RECENT, PRIOR, 28, 30, claim_term="존재하지않는검색어") + assert out["claim_term"]["found"] is False + assert "INCONCLUSIVE" in out["verdict_hint"] + + def test_positive_days_required(): try: compute_delta(RECENT, PRIOR, 0, 30)