fix(hypotheses): prune concluded entries from active.json after each run

Concluded hypotheses already live in concluded/ — keeping them in active.json
causes the registry to grow unboundedly. Runner now removes them at the end
of each cycle. Also cleaned up the existing social_dd concluded entry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Youssef Aitousarrah 2026-04-14 13:17:11 -07:00
parent 97b2755e97
commit 7ffbadca09
2 changed files with 24 additions and 24 deletions

View File

@ -22,24 +22,6 @@
],
"baseline_scanner": "insider_buying",
"conclusion": null
},
{
"id": "social_dd-ranker-suppression",
"scanner": "social_dd",
"title": "Does ranker suppression cause us to miss social_dd 30d winners?",
"description": "social_dd shows 60% 30d win rate (+2.32% avg) but only 41.7% 7d (-1.92%). Hypothesis: the ranker and recommendation system evaluate at 7d horizon, unfairly penalizing a slow-win scanner. Most picks (22/25) already score >=65, so score suppression is not the primary issue \u2014 horizon mismatch is.",
"branch": null,
"pr_number": null,
"status": "concluded",
"priority": 0,
"expected_impact": "medium",
"hypothesis_type": "statistical",
"created_at": "2026-04-13",
"min_days": 0,
"days_elapsed": 0,
"picks_log": [],
"baseline_scanner": "social_dd",
"conclusion": "statistical"
}
]
}

View File

@ -427,7 +427,9 @@ def conclude_statistical_hypothesis(hyp: dict) -> None:
try:
with open(DB_PATH) as f:
db = json.load(f)
picks = [p for p in db if p.get("scanner") == scanner or p.get("strategy_match") == scanner]
picks = [
p for p in db if p.get("scanner") == scanner or p.get("strategy_match") == scanner
]
except Exception as e:
print(f" Could not read performance database: {e}", flush=True)
@ -439,7 +441,11 @@ def conclude_statistical_hypothesis(hyp: dict) -> None:
avg_score = round(sum(scores) / len(scores), 1) if scores else None
returns_7d = [p["return_7d"] for p in picks if p.get("return_7d") is not None]
win_rate = round(100 * sum(1 for r in returns_7d if r > 0) / len(returns_7d), 1) if returns_7d else None
win_rate = (
round(100 * sum(1 for r in returns_7d if r > 0) / len(returns_7d), 1)
if returns_7d
else None
)
avg_return = round(sum(returns_7d) / len(returns_7d), 2) if returns_7d else None
stats_block = (
@ -489,8 +495,10 @@ def promote_pending(registry: dict) -> None:
# Only implementation/forward_test hypotheses count toward max_active.
# Statistical hypotheses are concluded immediately and never occupy runner slots.
running_count = sum(
1 for h in registry["hypotheses"]
if h["status"] == "running" and h.get("hypothesis_type", "implementation") == "implementation"
1
for h in registry["hypotheses"]
if h["status"] == "running"
and h.get("hypothesis_type", "implementation") == "implementation"
)
max_active = registry.get("max_active", 5)
if running_count >= max_active:
@ -518,8 +526,10 @@ def main():
# Fast-path: conclude all pending statistical hypotheses immediately.
# They answer questions from existing data — no cap, no worktree, no waiting.
statistical_pending = [
h for h in registry.get("hypotheses", [])
if h["status"] == "pending" and h.get("hypothesis_type") == "statistical"
h
for h in registry.get("hypotheses", [])
if h["status"] == "pending"
and h.get("hypothesis_type") == "statistical"
and (not filter_id or h["id"] == filter_id)
]
for hyp in statistical_pending:
@ -546,6 +556,14 @@ def main():
print(f" Error processing {hyp['id']}: {e}", flush=True)
promote_pending(registry)
# Prune concluded hypotheses from active.json — they live in concluded/ already.
before = len(registry["hypotheses"])
registry["hypotheses"] = [h for h in registry["hypotheses"] if h["status"] != "concluded"]
pruned = before - len(registry["hypotheses"])
if pruned:
print(f"\n Pruned {pruned} concluded hypothesis/hypotheses from active.json.", flush=True)
save_registry(registry)
print("\nRegistry updated.", flush=True)