Align preference show totals
- SHA
74d85c261a4697e033ecc41a08d8acf17c856ec1- Parents
-
31d74c7 - Tree
6aba767
74d85c2
74d85c261a4697e033ecc41a08d8acf17c856ec131d74c7
6aba767| Status | File | + | - |
|---|---|---|---|
| M |
src/dlm/cli/commands.py
|
11 | 5 |
| M |
src/dlm/metrics/queries.py
|
34 | 0 |
| M |
tests/unit/cli/test_show.py
|
4 | 0 |
| M |
tests/unit/metrics/test_queries.py
|
10 | 0 |
src/dlm/cli/commands.pymodified@@ -2794,6 +2794,8 @@ def show_cmd( | ||
| 2794 | 2794 | payload_full["gate"] = gate |
| 2795 | 2795 | if preference_mining is not None: |
| 2796 | 2796 | payload_full["preference_mining"] = preference_mining |
| 2797 | + payload_full["preference_mining_runs"] = preference_mining["run_count"] | |
| 2798 | + payload_full["total_auto_mined_pairs"] = preference_mining["total_mined_pairs"] | |
| 2797 | 2799 | if base_security is not None: |
| 2798 | 2800 | payload_full["base_security"] = base_security |
| 2799 | 2801 | # Write JSON to raw stdout — Rich's Console wraps lines at the |
@@ -3079,15 +3081,19 @@ def _summarize_preference_mining(store_root: Path) -> dict[str, object] | None: | ||
| 3079 | 3081 | """Return the latest preference-mine summary for `dlm show --json`.""" |
| 3080 | 3082 | from dlm.metrics import queries as _queries |
| 3081 | 3083 | |
| 3082 | - last = _queries.latest_preference_mining(store_root) | |
| 3083 | - if last is None: | |
| 3084 | + totals = _queries.preference_mining_totals(store_root) | |
| 3085 | + if totals is None: | |
| 3084 | 3086 | return None |
| 3087 | + last = _queries.latest_preference_mining(store_root) | |
| 3088 | + assert last is not None | |
| 3085 | 3089 | rows = _queries.preference_mining_for_run(store_root, last.run_id) |
| 3086 | 3090 | return { |
| 3091 | + "run_count": totals.run_count, | |
| 3092 | + "event_count": totals.event_count, | |
| 3093 | + "total_mined_pairs": totals.total_mined_pairs, | |
| 3094 | + "total_skipped_prompts": totals.total_skipped_prompts, | |
| 3087 | 3095 | "last_run_id": last.run_id, |
| 3088 | - "event_count": len(rows), | |
| 3089 | - "total_mined_pairs": sum(row.mined_pairs for row in rows), | |
| 3090 | - "total_skipped_prompts": sum(row.skipped_prompts for row in rows), | |
| 3096 | + "last_run_event_count": len(rows), | |
| 3091 | 3097 | "last_event": _queries.preference_mining_to_dict([last])[0], |
| 3092 | 3098 | } |
| 3093 | 3099 | |
src/dlm/metrics/queries.pymodified@@ -84,6 +84,16 @@ class PreferenceMineRow: | ||
| 84 | 84 | at: str |
| 85 | 85 | |
| 86 | 86 | |
| 87 | +@dataclass(frozen=True) | |
| 88 | +class PreferenceMineTotals: | |
| 89 | + """Aggregate counts across the whole `preference_mining` table.""" | |
| 90 | + | |
| 91 | + run_count: int | |
| 92 | + event_count: int | |
| 93 | + total_mined_pairs: int | |
| 94 | + total_skipped_prompts: int | |
| 95 | + | |
| 96 | + | |
| 87 | 97 | def recent_runs( |
| 88 | 98 | store_root: Path, |
| 89 | 99 | *, |
@@ -215,6 +225,30 @@ def latest_preference_mining(store_root: Path) -> PreferenceMineRow | None: | ||
| 215 | 225 | return PreferenceMineRow(*row) |
| 216 | 226 | |
| 217 | 227 | |
| 228 | +def preference_mining_totals(store_root: Path) -> PreferenceMineTotals | None: | |
| 229 | + """Aggregate counts across all preference-mine events. | |
| 230 | + | |
| 231 | + Returns None when the table is absent or empty. | |
| 232 | + """ | |
| 233 | + try: | |
| 234 | + with connect(store_root) as conn: | |
| 235 | + row = conn.execute( | |
| 236 | + "SELECT COUNT(DISTINCT run_id), COUNT(*), " | |
| 237 | + "COALESCE(SUM(mined_pairs), 0), COALESCE(SUM(skipped_prompts), 0) " | |
| 238 | + "FROM preference_mining" | |
| 239 | + ).fetchone() | |
| 240 | + except sqlite3.Error: | |
| 241 | + return None | |
| 242 | + if row is None or int(row[1]) == 0: | |
| 243 | + return None | |
| 244 | + return PreferenceMineTotals( | |
| 245 | + run_count=int(row[0]), | |
| 246 | + event_count=int(row[1]), | |
| 247 | + total_mined_pairs=int(row[2]), | |
| 248 | + total_skipped_prompts=int(row[3]), | |
| 249 | + ) | |
| 250 | + | |
| 251 | + | |
| 218 | 252 | @dataclass(frozen=True) |
| 219 | 253 | class GateEventRow: |
| 220 | 254 | """One row of the gate_events table (per-run per-adapter).""" |
tests/unit/cli/test_show.pymodified@@ -225,11 +225,15 @@ class TestInitializedStore: | ||
| 225 | 225 | payload = json.loads(result.output) |
| 226 | 226 | pref = payload["preference_mining"] |
| 227 | 227 | assert pref["last_run_id"] == 7 |
| 228 | + assert pref["run_count"] == 1 | |
| 228 | 229 | assert pref["event_count"] == 1 |
| 230 | + assert pref["last_run_event_count"] == 1 | |
| 229 | 231 | assert pref["total_mined_pairs"] == 2 |
| 230 | 232 | assert pref["total_skipped_prompts"] == 1 |
| 231 | 233 | assert pref["last_event"]["judge_name"] == "sway" |
| 232 | 234 | assert pref["last_event"]["write_mode"] == "applied" |
| 235 | + assert payload["preference_mining_runs"] == 1 | |
| 236 | + assert payload["total_auto_mined_pairs"] == 2 | |
| 233 | 237 | |
| 234 | 238 | |
| 235 | 239 | class TestTrainingSources: |
tests/unit/metrics/test_queries.pymodified@@ -13,6 +13,7 @@ from dlm.metrics.queries import ( | ||
| 13 | 13 | latest_run_id, |
| 14 | 14 | preference_mining_for_run, |
| 15 | 15 | preference_mining_to_dict, |
| 16 | + preference_mining_totals, | |
| 16 | 17 | recent_runs, |
| 17 | 18 | runs_to_dict, |
| 18 | 19 | steps_for_run, |
@@ -145,6 +146,15 @@ class TestPreferenceMiningQueries: | ||
| 145 | 146 | pass |
| 146 | 147 | assert latest_preference_mining(tmp_path) is None |
| 147 | 148 | |
| 149 | + def test_preference_mining_totals_aggregate_across_events(self, tmp_path: Path) -> None: | |
| 150 | + _seed(tmp_path) | |
| 151 | + totals = preference_mining_totals(tmp_path) | |
| 152 | + assert totals is not None | |
| 153 | + assert totals.run_count == 1 | |
| 154 | + assert totals.event_count == 2 | |
| 155 | + assert totals.total_mined_pairs == 3 | |
| 156 | + assert totals.total_skipped_prompts == 3 | |
| 157 | + | |
| 148 | 158 | |
| 149 | 159 | class TestDictSerialization: |
| 150 | 160 | def test_runs_to_dict_shape(self, tmp_path: Path) -> None: |