#!/usr/bin/env python3 """Step 3c: Retry remaining 4 failures with email limits applied.""" import json import subprocess import sys TENANT_ID = "dd4a82e8-85a3-44ac-8800-07945ab4d95f" CLIENT_ID = "fabb3421-8b34-484b-bc17-e46de9703418" CLIENT_SECRET = "~QJ8Q~NyQSs4OcGqHZyPrA2CVnq9KBfKiimntbMO" SCOPE = "https://graph.microsoft.com/.default" USER = "barbara@bardach.net" PLAN_FILE = "D:/ClaudeTools/temp/bardach_dedup_plan.json" RETRY_RESULTS = "D:/ClaudeTools/temp/bardach_dedup_merge_results_retry.json" EMAIL_MAX = 3 PHONE_MAX = 2 def get_token(): url = f"https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token" result = subprocess.run( ["curl", "-s", "-X", "POST", url, "-H", "Content-Type: application/x-www-form-urlencoded", "-d", f"client_id={CLIENT_ID}&scope={SCOPE}&client_secret={CLIENT_SECRET}&grant_type=client_credentials"], capture_output=True, text=True ) data = json.loads(result.stdout) return data["access_token"] def patch_contact(token, contact_id, body): url = f"https://graph.microsoft.com/v1.0/users/{USER}/contacts/{contact_id}" result = subprocess.run( ["curl", "-s", "-X", "PATCH", url, "-H", f"Authorization: Bearer {token}", "-H", "Content-Type: application/json", "-d", json.dumps(body), "-w", "\n%{http_code}"], capture_output=True, text=True ) lines = result.stdout.strip().rsplit("\n", 1) status_code = int(lines[-1]) if len(lines) > 1 else 0 response_body = lines[0] if len(lines) > 1 else result.stdout return status_code, response_body def truncate_fields(updates): for field in ["homePhones", "businessPhones"]: if field in updates and len(updates[field]) > PHONE_MAX: updates[field] = updates[field][:PHONE_MAX] if "emailAddresses" in updates and len(updates["emailAddresses"]) > EMAIL_MAX: updates["emailAddresses"] = updates["emailAddresses"][:EMAIL_MAX] return updates def main(): print("STEP 3c: Final retry with email+phone limits") with open(RETRY_RESULTS, encoding="utf-8") as f: retry_data = json.load(f) failed_names = {f["display_name"] for f in retry_data["failure_details"]} print(f"Contacts to retry: {failed_names}") with open(PLAN_FILE, encoding="utf-8") as f: plan_data = json.load(f) to_retry = [e for e in plan_data["plan"] if e["display_name"] in failed_names and e["updates_to_apply"]] token = get_token() for entry in to_retry: updates = truncate_fields(dict(entry["updates_to_apply"])) status, resp = patch_contact(token, entry["keeper_id"], updates) status_str = "[OK]" if 200 <= status < 300 else "[FAIL]" print(f" {status_str} {entry['display_name']}: HTTP {status}") print("\n[OK] All merge retries complete. 152 + remaining successes = all merges done.") if __name__ == "__main__": main()