|
@@ -7,9 +7,13 @@ from hermes_mcp.store import (
|
|
|
DB_PATH,
|
|
DB_PATH,
|
|
|
delete_concern,
|
|
delete_concern,
|
|
|
init_db,
|
|
init_db,
|
|
|
|
|
+ prune_older_than_hours,
|
|
|
|
|
+ upsert_decision_profile,
|
|
|
upsert_concern,
|
|
upsert_concern,
|
|
|
upsert_cycle,
|
|
upsert_cycle,
|
|
|
upsert_decision,
|
|
upsert_decision,
|
|
|
|
|
+ upsert_strategy_assignment,
|
|
|
|
|
+ upsert_strategy_group,
|
|
|
upsert_narrative,
|
|
upsert_narrative,
|
|
|
upsert_observation,
|
|
upsert_observation,
|
|
|
upsert_regime_sample,
|
|
upsert_regime_sample,
|
|
@@ -24,6 +28,13 @@ def _count(table: str, value: str, column: str = "concern_id") -> int:
|
|
|
return int(row["n"] if row else 0)
|
|
return int(row["n"] if row else 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def _total_count(table: str) -> int:
|
|
|
|
|
+ with sqlite3.connect(DB_PATH) as conn:
|
|
|
|
|
+ conn.row_factory = sqlite3.Row
|
|
|
|
|
+ row = conn.execute(f"select count(*) as n from {table}").fetchone()
|
|
|
|
|
+ return int(row["n"] if row else 0)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def test_delete_concern_purges_related_rows():
|
|
def test_delete_concern_purges_related_rows():
|
|
|
init_db()
|
|
init_db()
|
|
|
concern_id = f"test:{uuid4().hex}"
|
|
concern_id = f"test:{uuid4().hex}"
|
|
@@ -99,3 +110,111 @@ def test_delete_concern_purges_related_rows():
|
|
|
for table in ("observations", "states", "narratives", "decisions", "coverage_gaps", "regime_samples"):
|
|
for table in ("observations", "states", "narratives", "decisions", "coverage_gaps", "regime_samples"):
|
|
|
assert _count(table, concern_id) == 0
|
|
assert _count(table, concern_id) == 0
|
|
|
assert _count("actions", decision_id, "decision_id") == 0
|
|
assert _count("actions", decision_id, "decision_id") == 0
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_prune_older_than_hours_keeps_concerns_and_config_tables():
|
|
|
|
|
+ init_db()
|
|
|
|
|
+ concern_id = f"test:{uuid4().hex}"
|
|
|
|
|
+ cycle_id = f"cycle:{uuid4().hex}"
|
|
|
|
|
+ decision_id = f"decision:{uuid4().hex}"
|
|
|
|
|
+ profile_id = f"profile:{uuid4().hex}"
|
|
|
|
|
+ group_id = f"group:{uuid4().hex}"
|
|
|
|
|
+ assignment_id = f"assignment:{uuid4().hex}"
|
|
|
|
|
+ old_at = "2026-04-24T00:00:00+00:00"
|
|
|
|
|
+
|
|
|
|
|
+ upsert_concern(
|
|
|
|
|
+ id=concern_id,
|
|
|
|
|
+ account_id="acct-1",
|
|
|
|
|
+ market_symbol="xrpusd",
|
|
|
|
|
+ base_currency="XRP",
|
|
|
|
|
+ quote_currency="USD",
|
|
|
|
|
+ strategy_id="trend-1",
|
|
|
|
|
+ source="test",
|
|
|
|
|
+ status="active",
|
|
|
|
|
+ notes="preserve me",
|
|
|
|
|
+ )
|
|
|
|
|
+ upsert_decision_profile(id=profile_id, name="Config profile", config={"keep": True})
|
|
|
|
|
+ upsert_strategy_group(id=group_id, concern_id=concern_id, name="Config group", strategy_family="mixed", decision_profile_id=profile_id)
|
|
|
|
|
+ upsert_strategy_assignment(id=assignment_id, strategy_group_id=group_id, strategy_id="trend-1", strategy_type="trend_follower", role="primary")
|
|
|
|
|
+ upsert_cycle(id=cycle_id, started_at=old_at, finished_at=None, status="ok", trigger="test")
|
|
|
|
|
+ upsert_observation(id=f"obs:{uuid4().hex}", cycle_id=cycle_id, concern_id=concern_id, source="test", kind="snapshot", payload_json="{}", observed_at=old_at)
|
|
|
|
|
+ upsert_state(
|
|
|
|
|
+ id=f"state:{uuid4().hex}",
|
|
|
|
|
+ cycle_id=cycle_id,
|
|
|
|
|
+ concern_id=concern_id,
|
|
|
|
|
+ market_regime="bull",
|
|
|
|
|
+ volatility_state="normal",
|
|
|
|
|
+ liquidity_state="good",
|
|
|
|
|
+ sentiment_pressure="neutral",
|
|
|
|
|
+ event_risk="low",
|
|
|
|
|
+ execution_quality="good",
|
|
|
|
|
+ confidence=0.9,
|
|
|
|
|
+ payload_json="{}",
|
|
|
|
|
+ created_at=old_at,
|
|
|
|
|
+ )
|
|
|
|
|
+ upsert_narrative(
|
|
|
|
|
+ id=f"narr:{uuid4().hex}",
|
|
|
|
|
+ cycle_id=cycle_id,
|
|
|
|
|
+ concern_id=concern_id,
|
|
|
|
|
+ summary="old",
|
|
|
|
|
+ key_drivers_json="[]",
|
|
|
|
|
+ risk_flags_json="[]",
|
|
|
|
|
+ uncertainties_json="[]",
|
|
|
|
|
+ confidence=0.8,
|
|
|
|
|
+ created_at=old_at,
|
|
|
|
|
+ )
|
|
|
|
|
+ upsert_decision(
|
|
|
|
|
+ id=decision_id,
|
|
|
|
|
+ cycle_id=cycle_id,
|
|
|
|
|
+ concern_id=concern_id,
|
|
|
|
|
+ action="replace_with_grid",
|
|
|
|
|
+ target_strategy="grid-1",
|
|
|
|
|
+ target_policy_json="{}",
|
|
|
|
|
+ reason_summary="old",
|
|
|
|
|
+ confidence=0.7,
|
|
|
|
|
+ requires_action=True,
|
|
|
|
|
+ created_at=old_at,
|
|
|
|
|
+ )
|
|
|
|
|
+ upsert_regime_sample(id=f"regime:{uuid4().hex}", cycle_id=cycle_id, concern_id=concern_id, timeframe="1h", regime_json="{}", captured_at=old_at)
|
|
|
|
|
+ with sqlite3.connect(DB_PATH) as conn:
|
|
|
|
|
+ conn.execute(
|
|
|
|
|
+ "insert into actions(id, decision_id, target, command, request_json, response_json, status, executed_at) values(?, ?, ?, ?, ?, ?, ?, ?)",
|
|
|
|
|
+ (f"action:{uuid4().hex}", decision_id, "trader", "switch", "{}", None, "done", old_at),
|
|
|
|
|
+ )
|
|
|
|
|
+ conn.commit()
|
|
|
|
|
+
|
|
|
|
|
+ before_counts = {
|
|
|
|
|
+ "cycles": _total_count("cycles"),
|
|
|
|
|
+ "decisions": _total_count("decisions"),
|
|
|
|
|
+ "actions": _total_count("actions"),
|
|
|
|
|
+ "observations": _total_count("observations"),
|
|
|
|
|
+ "states": _total_count("states"),
|
|
|
|
|
+ "narratives": _total_count("narratives"),
|
|
|
|
|
+ "coverage_gaps": _total_count("coverage_gaps"),
|
|
|
|
|
+ "regime_samples": _total_count("regime_samples"),
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ deleted = prune_older_than_hours(24)
|
|
|
|
|
+
|
|
|
|
|
+ assert deleted["cycles"] == before_counts["cycles"] - _total_count("cycles")
|
|
|
|
|
+ assert deleted["decisions"] == before_counts["decisions"] - _total_count("decisions")
|
|
|
|
|
+ assert deleted["actions"] == before_counts["actions"] - _total_count("actions")
|
|
|
|
|
+ assert deleted["observations"] == before_counts["observations"] - _total_count("observations")
|
|
|
|
|
+ assert deleted["states"] == before_counts["states"] - _total_count("states")
|
|
|
|
|
+ assert deleted["narratives"] == before_counts["narratives"] - _total_count("narratives")
|
|
|
|
|
+ assert deleted["coverage_gaps"] == before_counts["coverage_gaps"] - _total_count("coverage_gaps")
|
|
|
|
|
+ assert deleted["regime_samples"] == before_counts["regime_samples"] - _total_count("regime_samples")
|
|
|
|
|
+ assert "concerns" not in deleted
|
|
|
|
|
+ assert "decision_profiles" not in deleted
|
|
|
|
|
+ assert "strategy_groups" not in deleted
|
|
|
|
|
+ assert "strategy_assignments" not in deleted
|
|
|
|
|
+
|
|
|
|
|
+ assert _count("concerns", concern_id, "id") == 1
|
|
|
|
|
+ assert _count("decision_profiles", profile_id, "id") == 1
|
|
|
|
|
+ assert _count("strategy_groups", group_id, "id") == 1
|
|
|
|
|
+ assert _count("strategy_assignments", assignment_id, "id") == 1
|
|
|
|
|
+ assert _count("decisions", concern_id) == 0
|
|
|
|
|
+ assert _count("observations", concern_id) == 0
|
|
|
|
|
+ assert _count("states", concern_id) == 0
|
|
|
|
|
+ assert _count("narratives", concern_id) == 0
|
|
|
|
|
+ assert _count("regime_samples", concern_id) == 0
|