Files
delphi-database-v2/scripts/fix_case_links.py

95 lines
3.0 KiB
Python

"""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))