#!/usr/bin/env python3 """ Reset operational data and seed fresh demo records. PRESERVED (not deleted): police_users, incident_types, locations, stations, station_coverage_cells, local_leaders, local_leader_coverage_locations, system_config, special_assignment_units CLEARED: reports (+ evidence, ML predictions, assignments, case links), cases, hotspots, devices, notifications, audit_logs, user_sessions, MFA/password-reset codes, deployment_decisions Then seeds at least 300 new reports (default), devices, cases, and recomputes hotspots. Usage (from backend directory or container): python scripts/reset_operational_data.py --yes python scripts/reset_operational_data.py --yes --count 500 python scripts/reset_operational_data.py --dry-run python scripts/reset_operational_data.py --clear-only --yes """ from __future__ import annotations import argparse import logging import os import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from app.core.hotspot_auto import create_hotspots_from_reports from app.core.operational_data_reset import ( TABLES_PRESERVED, TABLES_TO_CLEAR, clear_operational_data, count_operational_rows, ) from app.database import SessionLocal from simulate_reports import seed_operational_data logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s") logger = logging.getLogger("reset_operational_data") def _print_plan(counts: dict[str, int]) -> None: total = sum(counts.values()) logger.info("Tables to CLEAR (%d rows total):", total) for table in TABLES_TO_CLEAR: n = counts.get(table, 0) if n: logger.info(" - %s: %d", table, n) logger.info("Tables PRESERVED: %s", ", ".join(TABLES_PRESERVED)) def recompute_hotspots(db) -> int: """Build hotspot clusters from all non-rejected seeded reports.""" return create_hotspots_from_reports( db, analyze_all_reports=True, time_window_hours=8760, ) def main() -> int: parser = argparse.ArgumentParser(description="Clear operational data and reseed TrustBond demo records.") parser.add_argument( "--count", type=int, default=300, help="Number of reports to seed (minimum 300). Default: 300", ) parser.add_argument( "--devices", type=int, default=None, help="Number of devices to create (default: max(50, count/5))", ) parser.add_argument( "--days", type=int, default=45, help="Spread report dates over this many days (default: 45)", ) parser.add_argument( "--dry-run", action="store_true", help="Show row counts that would be deleted; do not change data", ) parser.add_argument( "--clear-only", action="store_true", help="Only clear operational tables; do not seed or recompute hotspots", ) parser.add_argument( "--seed-only", action="store_true", help="Skip clear step; only seed and recompute hotspots", ) parser.add_argument( "--skip-hotspots", action="store_true", help="Do not run hotspot DBSCAN recompute after seeding", ) parser.add_argument( "--yes", "-y", action="store_true", help="Confirm destructive reset without interactive prompt", ) args = parser.parse_args() if args.count < 300: parser.error("--count must be at least 300") db = SessionLocal() try: counts = count_operational_rows(db) _print_plan(counts) if args.dry_run: logger.info("Dry run — no changes made.") return 0 if not args.seed_only: if not args.yes: answer = input( "\nThis will DELETE all operational data listed above. Type RESET to continue: " ).strip() if answer != "RESET": logger.info("Aborted.") return 1 cleared = clear_operational_data(db) logger.info("Cleared rows: %s", cleared) if args.clear_only: logger.info("Clear-only complete.") return 0 stats = seed_operational_data( db, num_reports=args.count, num_devices=args.devices, days_back=args.days, ) logger.info("Seeded: %s", stats) hotspots_created = 0 if not args.skip_hotspots: hotspots_created = recompute_hotspots(db) logger.info("Hotspots recomputed: %d clusters", hotspots_created) logger.info( "Done. Preserved reference data; seeded %d reports, %d devices, %d cases, %d hotspots.", stats["reports"], stats["devices"], stats["cases"], hotspots_created, ) return 0 except Exception: db.rollback() logger.exception("Reset failed") return 1 finally: db.close() if __name__ == "__main__": raise SystemExit(main())