whisper-money/experiments/worklog.md

6.7 KiB

Autoresearch Worklog: AI rule-suggestion coverage

Session goal: maximize uncategorized tx the ai:suggest-rules pipeline can categorize for victoor89@gmail.com.

Data summary (clean slate)

  • 1329 uncategorized tx (all server-readable), 64 categories, 0 rules.
  • 650 distinct normalized groups; 499 singletons; 151 groups with count≥2 (covering 830 tx); 90 groups count≥3 (708 tx).
  • Description-dominated: ~1267 tx key off description, ~62 off creditor/debtor.
  • Ceiling map (sum of top-N group counts): top40=515, top60=602, top100=728, top150=828, top200=879, top300=979, all650=1300.

Metric

  • Primary oracle_tx (deterministic, frozen oracle in experiments/bench.php).
  • Ground truth real_tx (live Gemini, noisy, milestone-only).

Run 1: baseline — oracle_tx=830 (KEEP)

  • What changed: nothing; scaffold + frozen oracle benchmark established.
  • Result: oracle_tx=830, reachable_tx=515, groups_sent=40, validated_count=29, coverage_pct=62.45. Ground truth real_tx median ≈416 (437/410/400).
  • Insight: the live model realizes only ~half the oracle ceiling and ~80% of reachable; it covers ~25 of 40 sent groups. Both the ceiling (aggregation) and the realization gap (prompt/batching) are open levers.
  • Next: raise max_groups_sent toward 150 (cover all count≥2 groups).

Run 2: max_groups_sent 40 -> 150 — oracle_tx=1229 (KEEP)

  • Timestamp: 2026-06-13
  • What changed: config/ai_suggestions.php default max_groups_sent 40 -> 150.
  • Result: oracle_tx 830->1229 (+48%), reachable 515->828, groups_sent 150, coverage 92.48%. Tests 24/24 green. Real_tx (3 runs): 711 / 214 / 774, median 711 (baseline 416).
  • Insight: ceiling lift works AND the median real run nearly doubles. BUT the live model is now UNSTABLE on the big single payload — one run returned only 9 suggestions (real_tx 214 < baseline). gemini-flash under-enumerates a large structured-output request. The realization axis is now the bottleneck.
  • Next: BATCH the Gemini call (chunk groups into reliable-size requests, merge raw suggestions). Should stabilize + realize the high ceiling. Judge by real_tx (oracle unaffected — same groups/guard).

Run 3: batch Gemini calls (group_batch_size=40) — real_tx 416->903 (KEEP)

  • Timestamp: 2026-06-13
  • What changed: LaravelAiRuleSuggestionGenerator now array_chunks groups into group_batch_size(40) per-request batches and merges suggestions. New config key ai_suggestions.group_batch_size.
  • Result: oracle_tx unchanged (1229 — bench bypasses generator). real_tx (3 runs): 970/903/885, median 903; raw suggestions ~125 stable (was 9..50); validated ~88. Tests 24/24 green, pint clean.
  • Insight: the 150-group instability was purely a single-payload enumeration failure. Smaller batches make the multilingual model reliably enumerate every group. Real coverage 416->903 (+117%), now 68% of all tx and 74% of the oracle ceiling (was 50%). Decision metric here is real_tx (primary oracle unmoved).
  • Next: language-agnostic clustering (per-user token document-frequency noise removal) to merge merchant variants — must NOT hardcode Spanish (pan-EU users).

Run 4: language-agnostic frequency-based grouping — reachable 828->1027 (KEEP)

  • Timestamp: 2026-06-13
  • What changed: RuleSuggestionAggregator keys descriptions on distinctive (low document-frequency) tokens; drops words in >noise_token_fraction(2%) of tx (language-agnostic, no wordlist); fallback keeps all if all common. New config ai_suggestions.noise_token_fraction.
  • Result: reachable_tx 828->1027 (singletons 499->291), validated 72->115, oracle_tx ~flat (1229->1222, saturated). real_tx (3 runs) 950/900/925 median 925 — FLAT vs batching's 903 (mean 919 vs 925). Tests 24/24, pint clean.
  • Insight: clustering raises the realizable ceiling (+199 reachable) but the live model already covered most via good tokens, so real_tx barely moved THIS step. The headroom matters for the next levers. oracle_tx is now saturated near the 1329 total and is a poor primary.
  • Decision: KEEP (language-agnostic per user steer, cleaner groups, +200 reachable headroom, no real regression).

Segment switch (run 5): primary metric -> reachable_tx

  • oracle_tx saturated (~1222/1329); switched the deterministic loop primary to reachable_tx (sensitive to clustering). real_tx stays the milestone ground truth; prompt/batch experiments are judged by real_tx regardless.
  • Segment-1 baseline: reachable_tx=1027, real_tx≈925.

Run 6: send all groups (min_group_count=1, max_groups_sent=500) — reachable 1329 (KEEP)

  • What changed: config min_group_count 2->1, max_groups_sent 150->500.
  • Result: reachable_tx 1027->1329 (100%), groups_sent 441. real_tx=1124 (84.6%, one clean sample; 2 of 3 sample runs failed operationally — 12 batches/run is slow + fragile, one failed batch lost the whole run). Tests green.
  • Insight: singletons (one-off merchants) ARE realizable — adding them lifted real coverage 925->1124. But high batch counts need resilience.

Run 7: resilient batched generation — real_tx 1122 reliable (KEEP)

  • What changed: LaravelAiRuleSuggestionGenerator retries each batch once, tolerates partial-batch failures (keeps successful batches), only rethrows if every batch fails. Added 3 generator tests.
  • Result: reachable_tx 1329 (unchanged). real_tx=1122 reproducible (was 1124). Full AI suite 27/27, pint clean.
  • Insight: the run-6 fragility was operational, not a model limit. With resilience the min1/cap500 config reliably lands ~1122 (84%).

STOPPED by user after run 7.

Final Result

  • real_tx 416 -> 1122 of 1329 (31% -> 84%) for victoor89@gmail.com.
  • All changes language-agnostic (pan-EU safe): frequency clustering, batching, caps, resilience. No hardcoded wordlists in product code.

Key Insights

  • max_groups_sent=40 is the first hard cap: reachable=515 of a possible 830 at count≥2. Lifting it is the cheapest ceiling win.
  • Tokens legitimately span multiple groups (same merchant, different noise), so oracle_tx (830) > reachable_tx (515) — token extraction is high-value.
  • Real model is conservative (skips groups). Prompt/batching may matter as much as thresholds, but must be judged by real_tx.

Next Ideas (not yet tried)

  • Decide the production default: aggressive min1/cap500 (max coverage, ~12 Gemini calls/run, one-off rules) vs balanced min2/cap~200 (reachable≈1049, ~5 calls, recurring merchants only). Quantify real_tx for the balanced config.
  • Prompt experiment (real_tx-judged): close the 1122->1329 gap by insisting on exhaustive mapping + multilingual merchant-token extraction.
  • group_batch_size tuning (speed vs enumeration quality).
  • Run batches concurrently to cut wall-clock of the many-batch config.
  • noise_token_fraction sweep (0.015 looked marginally best deterministically).