Skip to content

feat: 0.9 fuzzy_threshold config tunable - #13

Merged
zeikar merged 6 commits into
mainfrom
feat/fuzzy-threshold-config-0.9
May 14, 2026
Merged

feat: 0.9 fuzzy_threshold config tunable#13
zeikar merged 6 commits into
mainfrom
feat/fuzzy-threshold-config-0.9

Conversation

@zeikar

@zeikar zeikar commented May 14, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add fuzzy_threshold config key in ~/.like-surgeon/config.json (default 85, integer in [0, 100]) tuning the RapidFuzz cross-source match cutoff used by Stage 3 of compare-likes.
  • Single-source default: DEFAULT_FUZZY_THRESHOLD lives once in compare.py (next to CompareInput) and is imported by config.py — no drift risk between dataclass default and config fallback.
  • Validated at config-load: rejects bool (caught before the int check; True/False are int subclass in Python), non-integers, and out-of-range values via InvalidFuzzyThresholdError. Caught in _safe_config_load so a bad value exits cleanly with code 2 (no traceback) — same UX as InvalidRegionError.
  • Plumbing: cli._compare_and_persist passes cfg.fuzzy_threshold into CompareInput via dict-spread (**({"fuzzy_threshold": cfg.fuzzy_threshold} if cfg is not None else {})) — kwarg literally omitted when cfg is None, so the CompareInput dataclass default (also DEFAULT_FUZZY_THRESHOLD) applies. No duplicate constants.
  • Docs: README version table 0.9 row + config example, ARCHITECTURE roadmap 0.9 entry, CLAUDE.md sync-state-model bullet covering the false-positive-drift caveat.

Side fix (regression from v0.8.0)

CLAUDE.md "Releases" checklist updated: step 1 now requires bumping both pyproject.toml AND src/likesurgeon/__init__.py in lockstep. v0.8.0 was tagged with __init__.py stale at 0.7.1, so likesurgeon --version printed likesurgeon 0.7.1 after the tag. v0.9.0 release will fix this (jumps __init__.py 0.7.1 → 0.9.0).

Live e2e validation (operator run)

  • ~/.like-surgeon/config.json set to {"region": "KR", "fuzzy_threshold": 80}.
  • compare-likes against latest snapshots (YT #34 vs YTM #32) → Diagnosis #21:
    • vs Diagnosis #20 (threshold 85): matched 947→948, possibly_missing 3→2, pointer_drift 0→1, ytmusic_only 147→146.
    • The new fuzzy match is exactly the 0.8 plan's validation target: f9DzbpmWbMoRANoZl9UXJM ("irucaice - Rainy Step feat. Hatsune Miku" ↔ "Rainy Step (feat. Hatsune Miku)") at score 82/100, confidence 0.817.
  • sync --dry-run with default --drift-min-confidence 0.95: the new pointer-drift correctly skipped (0.817 < 0.95). Two-stage safety net (compare-time fuzzy_threshold + sync-time drift-min-confidence) confirmed working.
  • sync --yes --drift-min-confidence 0.80: applied the drift fix (yt_relike_like ok + yt_relike_unlike ok) — Diagnosis item flipped to 'applied'. Sibling ytm_like items hit an unrelated ytmusicapi verify-parse failure (not 0.9 related; YT-side rate calls succeeded).

Tests

  • 17 new tests in tests/test_config.py: validator boundaries, bool-before-int guard, loader fall-throughs (missing file / malformed JSON / non-dict / null / valid / out-of-range / non-int / JSON true-as-bool), Config.load integration with default vs override, drift-invariant assert that CompareInput().fuzzy_threshold == DEFAULT_FUZZY_THRESHOLD == 85.
  • 3 new tests in tests/test_cli.py: cfg.fuzzy_threshold flows into CompareInput, default applies when cfg=None, invalid config exits cleanly with "150" in result.output and no Traceback.
  • Full suite: 323 pass, ruff format + check clean.

Test plan

  • uv run pytest tests/ (303 → 323 with new tests)
  • uv run ruff format --check .
  • uv run ruff check
  • Manual: Config.load() with {"fuzzy_threshold": 80}cfg.fuzzy_threshold == 80
  • Manual: Config.load() with {"fuzzy_threshold": 150} → exits 2, "150" in output, no traceback
  • Manual: compare-likes at threshold 80 surfaces the f9DzbpmWbMo fuzzy match
  • Manual: sync --drift-min-confidence 0.80 applies the drift fix end-to-end

Copilot AI review requested due to automatic review settings May 14, 2026 10:38
@zeikar
zeikar merged commit 244514b into main May 14, 2026
3 checks passed

Copilot AI 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.

Pull request overview

Adds a fuzzy_threshold configuration key (default 85, range [0, 100]) so users can tune the RapidFuzz cross-source match cutoff used by compare-likes Stage 3 from ~/.like-surgeon/config.json. The default constant lives in compare.py and is imported by config.py to avoid drift between the dataclass default and the loader fallback. Invalid values produce a clean exit-2 via the existing _safe_config_load path.

Changes:

  • New _load_fuzzy_threshold / _validate_fuzzy_threshold / InvalidFuzzyThresholdError in config.py, plus a fuzzy_threshold: int field on Config.
  • cli._compare_and_persist forwards cfg.fuzzy_threshold to CompareInput (kwarg omitted when cfg is None); _safe_config_load catches the new error.
  • Docs (README, ARCHITECTURE.md, CLAUDE.md) updated; CLAUDE.md release checklist now requires bumping __init__.py in lockstep with pyproject.toml. Tests added in tests/test_config.py (17) and tests/test_cli.py (3).

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/likesurgeon/compare.py Extracts DEFAULT_FUZZY_THRESHOLD = 85 module constant; CompareInput.fuzzy_threshold now defaults to it.
src/likesurgeon/config.py Adds validator, loader, error class, and fuzzy_threshold field on Config; imports default from compare.py.
src/likesurgeon/cli.py _safe_config_load also catches InvalidFuzzyThresholdError; _compare_and_persist plumbs cfg.fuzzy_threshold into CompareInput.
tests/test_config.py Validator/loader/integration tests + drift-invariant assertion that CompareInput().fuzzy_threshold == DEFAULT_FUZZY_THRESHOLD == 85.
tests/test_cli.py Tests cfg→CompareInput wiring, default-when-cfg-None, and clean exit-2 for invalid config.
README.md Adds 0.9 row to version table and config example for fuzzy_threshold.
docs/ARCHITECTURE.md Adds 0.9 roadmap bullet referencing the motivating fuzzy-match case.
CLAUDE.md Adds 0.9 sync-state-model note; updates release procedure to bump __init__.py together with pyproject.toml.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants