Export partially-captured groups as stable
Stop treating partially-captured oldest timestamp groups (including Arc 10-pulls) as a special "incomplete" case. All decoded rows are now exported and marked with stable UIDs; the previous INCOMPLETE_* warnings and related console hint logic were removed. annotate_groups and annotate_arc_groups were simplified to always set uid_status="stable" and export_record=True, and arc_stability_warnings was deleted. CLI, live capture runner, console, tests, docs and mappings were updated to reflect the new behavior and to remove emission/handling of those informational warnings.
This commit is contained in:
parent
20b2086299
commit
5d16dac4d5
10 changed files with 53 additions and 170 deletions
|
|
@ -20,7 +20,6 @@ from nte_history_exporter.constants import POOL_META
|
|||
from nte_history_exporter.mappings import ARC_META, CHARACTERS, ITEMS, REWARDS_BY_ID
|
||||
from nte_history_exporter.decoder.protocol import decode_reward_key, infer_reward_type
|
||||
from nte_history_exporter.decoder.arc import (
|
||||
arc_stability_warnings,
|
||||
build_arc_rows_from_pairs,
|
||||
decode_arc_key,
|
||||
decode_arc_timestamp,
|
||||
|
|
@ -163,19 +162,15 @@ class BoundaryExportTests(unittest.TestCase):
|
|||
}
|
||||
self.assertNotEqual(make_uid(row, 0), "5adcf52282e15445466863b271f3b745")
|
||||
|
||||
def test_pages_1_to_5_exports_dice_complete_oldest_group(self):
|
||||
def test_pages_1_to_5_exports_every_row(self):
|
||||
rows = load_reference_csv("monopoly_history_poc_13_pages_1_to_5_v4.csv")
|
||||
annotated, warnings = annotate_groups(rows)
|
||||
annotated = annotate_groups(rows)
|
||||
|
||||
exported = [row for row in annotated if row["export_record"] is True]
|
||||
skipped = [row for row in annotated if row["export_record"] is False]
|
||||
|
||||
# The oldest group has exactly 10 dice rolls (plus a Points Gift), which
|
||||
# proves the pull set is complete even though the scan stopped on a full page.
|
||||
# Every decoded row is exported; boundary groups are never dropped.
|
||||
self.assertEqual(len(annotated), 25)
|
||||
self.assertEqual(len(exported), 25)
|
||||
self.assertEqual(len(skipped), 0)
|
||||
self.assertEqual(len(warnings), 0)
|
||||
|
||||
@staticmethod
|
||||
def _synthetic_row(page, timestamp_hex, result_type):
|
||||
|
|
@ -189,24 +184,21 @@ class BoundaryExportTests(unittest.TestCase):
|
|||
"quantity": 1,
|
||||
}
|
||||
|
||||
def test_oldest_group_with_partial_dice_count_exports_with_warning(self):
|
||||
def test_oldest_group_with_partial_dice_count_exports_without_warning(self):
|
||||
rows = [self._synthetic_row(1, "aa", "dice") for _ in range(5)]
|
||||
rows += [self._synthetic_row(2, "bb", "dice") for _ in range(5)]
|
||||
rows += [self._synthetic_row(3, "bb", "dice") for _ in range(4)]
|
||||
rows += [self._synthetic_row(3, "bb", "points_gift")]
|
||||
|
||||
annotated, warnings = annotate_groups(rows)
|
||||
annotated = annotate_groups(rows)
|
||||
exported = [row for row in annotated if row["export_record"] is True]
|
||||
|
||||
# Oldest group is an unfinished 10-pull on a full final page. Its captured
|
||||
# prefix is ordinal-stable, so it is exported with an informational warning
|
||||
# rather than dropped.
|
||||
# Oldest group is a partially captured 10-pull on a full final page. Its
|
||||
# captured prefix is ordinal-stable, so it is exported with stable UIDs.
|
||||
self.assertEqual(len(exported), 15)
|
||||
self.assertEqual(len(warnings), 1)
|
||||
self.assertEqual(warnings[0]["code"], "INCOMPLETE_TIMESTAMP_GROUP_EXPORTED")
|
||||
self.assertEqual(warnings[0]["dice_records"], 9)
|
||||
oldest = [row for row in annotated if row["timestamp_raw_hex"] == "bb"]
|
||||
self.assertTrue(all(row["uid"] for row in oldest))
|
||||
self.assertTrue(all(row["uid_status"] == "stable" for row in oldest))
|
||||
self.assertEqual([row["timestamp_group_ordinal"] for row in oldest], list(range(10)))
|
||||
|
||||
def test_incomplete_oldest_prefix_keeps_stable_uids(self):
|
||||
|
|
@ -215,8 +207,8 @@ class BoundaryExportTests(unittest.TestCase):
|
|||
full += [self._synthetic_row(3, "bb", "dice") for _ in range(2)]
|
||||
truncated = [r for r in full if r["page"] in (1, 2)]
|
||||
|
||||
full_rows, _ = annotate_groups([dict(r) for r in full])
|
||||
trunc_rows, _ = annotate_groups([dict(r) for r in truncated])
|
||||
full_rows = annotate_groups([dict(r) for r in full])
|
||||
trunc_rows = annotate_groups([dict(r) for r in truncated])
|
||||
|
||||
full_uids = [r["uid"] for r in full_rows if r["timestamp_raw_hex"] == "bb"][:3]
|
||||
trunc_uids = [r["uid"] for r in trunc_rows if r["timestamp_raw_hex"] == "bb"]
|
||||
|
|
@ -230,11 +222,10 @@ class BoundaryExportTests(unittest.TestCase):
|
|||
rows += [self._synthetic_row(2, "bb", "dice") for _ in range(5)]
|
||||
rows += [self._synthetic_row(3, "bb", "dice") for _ in range(5)]
|
||||
|
||||
annotated, warnings = annotate_groups(rows)
|
||||
annotated = annotate_groups(rows)
|
||||
exported = [row for row in annotated if row["export_record"] is True]
|
||||
|
||||
self.assertEqual(len(exported), 15)
|
||||
self.assertEqual(len(warnings), 0)
|
||||
|
||||
def test_run_selection_anchors_to_page_1_and_keeps_newest(self):
|
||||
# Page 2's response was lost: captured pages 1, 3, 4, 5.
|
||||
|
|
@ -257,7 +248,7 @@ class BoundaryExportTests(unittest.TestCase):
|
|||
|
||||
def test_full_reference_scan_exports_all_rows(self):
|
||||
rows = load_reference_csv("monopoly_history_poc_10_all_44_pages_v4.csv")
|
||||
annotated, warnings = annotate_groups(rows)
|
||||
annotated = annotate_groups(rows)
|
||||
exported = [row for row in annotated if row["export_record"] is True]
|
||||
|
||||
json_path = EXPORTS / "monopoly_history_export_10_all_44_pages_v4.json"
|
||||
|
|
@ -270,12 +261,11 @@ class BoundaryExportTests(unittest.TestCase):
|
|||
|
||||
self.assertEqual(len(annotated), reference["scan"]["decoded_records"])
|
||||
self.assertEqual(len(exported), reference["scan"]["exported_records"])
|
||||
self.assertEqual(warnings, [])
|
||||
|
||||
def test_sanitized_export_omits_raw_packet_fields(self):
|
||||
rows = load_reference_csv("monopoly_history_poc_13_pages_1_to_5_v4.csv")
|
||||
annotated, warnings = annotate_groups(rows)
|
||||
export = build_export_json(annotated, warnings)
|
||||
annotated = annotate_groups(rows)
|
||||
export = build_export_json(annotated, [])
|
||||
|
||||
self.assertEqual(export["format"], "nte-history-export")
|
||||
self.assertIn("exporter", export)
|
||||
|
|
@ -366,7 +356,7 @@ class BoundaryExportTests(unittest.TestCase):
|
|||
self.assertEqual(decoded[0]["reward_type"], "arc")
|
||||
self.assertEqual(decoded[0]["reward_key_hex"], reference_rows[0]["arc_key_hex"])
|
||||
|
||||
def test_arc_partial_timestamp_group_is_exported_with_warning(self):
|
||||
def test_arc_partial_timestamp_group_is_exported_without_warning(self):
|
||||
rows = load_arc_csv("arc_pages_1_to_5_v2.csv")
|
||||
pairs = []
|
||||
for page in range(1, 6):
|
||||
|
|
@ -375,17 +365,14 @@ class BoundaryExportTests(unittest.TestCase):
|
|||
pairs.append((page, page * 2, page, 1.0, page + 100, 1.1, response))
|
||||
decoded = build_arc_rows_from_pairs(pairs)
|
||||
exported = [row for row in decoded if row["export_record"] is True]
|
||||
incomplete = [row for row in decoded if row["uid_status"] == "incomplete_stable"]
|
||||
warnings = arc_stability_warnings(decoded)
|
||||
|
||||
# The oldest group is a 10-pull split by stopping at page 5 (5 of 10 rows).
|
||||
# Its captured prefix is ordinal-stable, so it is exported, not dropped.
|
||||
# Its captured prefix is ordinal-stable, so every row is exported with a
|
||||
# stable UID and no warning.
|
||||
self.assertEqual(len(decoded), 25)
|
||||
self.assertEqual(len(exported), 25)
|
||||
self.assertEqual(len(incomplete), 5)
|
||||
self.assertEqual(len(warnings), 1)
|
||||
self.assertEqual(warnings[0]["code"], "INCOMPLETE_ARC_10_PULL_EXPORTED")
|
||||
self.assertTrue(all(row["uid"] for row in incomplete))
|
||||
self.assertTrue(all(row["uid"] for row in decoded))
|
||||
self.assertTrue(all(row["uid_status"] == "stable" for row in decoded))
|
||||
|
||||
def test_arc_incomplete_prefix_keeps_stable_uids(self):
|
||||
rows = load_arc_csv("arc_pull_10_all_pages_v2.csv")
|
||||
|
|
@ -468,13 +455,14 @@ class BoundaryExportTests(unittest.TestCase):
|
|||
},
|
||||
]
|
||||
|
||||
annotated, warnings = annotate_groups(rows)
|
||||
annotated = annotate_groups(rows)
|
||||
|
||||
# Ordinals cover every row in the group, but the dice-only count drives
|
||||
# timestamp_group_size_seen (2 dice in the 4-record group).
|
||||
self.assertEqual([row["timestamp_group_ordinal"] for row in annotated[:4]], [0, 1, 2, 3])
|
||||
self.assertEqual({row["timestamp_group_size_seen"] for row in annotated[:4]}, {2})
|
||||
self.assertEqual({row["timestamp_group_record_size_seen"] for row in annotated[:4]}, {4})
|
||||
self.assertEqual(warnings[0]["dice_records"], 1)
|
||||
self.assertEqual(warnings[0]["records"], 1)
|
||||
self.assertTrue(all(row["export_record"] for row in annotated))
|
||||
|
||||
def test_live_session_pairs_request_and_response(self):
|
||||
session = LiveHistorySession("192.168.0.10")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue