"""Utility script to repair orphaned Case records. This script attempts to link Case records to Client entries based on legacy identifiers. It should be executed inside the running Docker container. Usage: docker compose exec delphi-db python scripts/fix_case_links.py Safety: - Prints a summary before making changes. - Requires confirmation unless run with --yes. """ from __future__ import annotations import argparse from collections import defaultdict from sqlalchemy.orm import joinedload from app.database import SessionLocal from app.models import Case, Client def build_client_maps(session) -> tuple[dict[str, int], dict[str, list[int]]]: rolodex_to_client: dict[str, int] = {} file_to_client: dict[str, list[int]] = defaultdict(list) for client in session.query(Client).options(joinedload(Client.cases)).all(): if client.rolodex_id: rolodex_to_client[client.rolodex_id] = client.id for case in client.cases: file_to_client[case.file_no].append(client.id) return rolodex_to_client, file_to_client def find_orphaned_cases(session): return session.query(Case).filter(Case.client_id == None).all() # noqa: E711 def main(confirm: bool) -> int: session = SessionLocal() try: rolodex_map, file_map = build_client_maps(session) orphans = find_orphaned_cases(session) if not orphans: print("No orphaned cases found. 🎉") return 0 assignments: list[tuple[Case, int]] = [] for case in orphans: candidate_client_id = None if case.file_no in file_map and file_map[case.file_no]: candidate_client_id = file_map[case.file_no][0] elif case.file_no in rolodex_map: candidate_client_id = rolodex_map[case.file_no] if candidate_client_id: assignments.append((case, candidate_client_id)) if not assignments: print("No matching clients found for orphaned cases. Nothing to do.") return 1 print("Orphaned cases detected:") for case, client_id in assignments: print(f" Case {case.file_no} (id={case.id}) → Client id {client_id}") if not confirm: response = input("Apply these fixes? [y/N]: ").strip().lower() if response not in {"y", "yes"}: print("Aborting with no changes.") return 1 updated = 0 for case, client_id in assignments: case.client_id = client_id updated += 1 session.commit() print(f"Updated {updated} cases with matching clients.") return 0 finally: session.close() if __name__ == "__main__": parser = argparse.ArgumentParser(description="Repair orphaned Case links.") parser.add_argument("--yes", action="store_true", help="Apply changes without confirmation") args = parser.parse_args() raise SystemExit(main(confirm=args.yes))