370def _build_report(records, kept_records, removed_records):
371 """Collect duplicate and conflict information for logging/tests."""
372 removed_duplicates = [
373 {
374 "source": record["source_name"],
375 "duplicate_of_source": record.get("duplicate_of_source_name"),
376 "setting": record["original_setting"],
377 "kept_setting": record.get("kept_original_setting"),
378 "normalized_value": record["normalized_value"],
379 "kept_normalized_value": record.get("kept_normalized_value"),
380 }
381 for record in removed_records
382 ]
383
384 removed_identical = [
385 {
386 "source": record["source"],
387 "duplicate_of_source": record.get("duplicate_of_source"),
388 "setting": record["setting"],
389 "kept_setting": record.get("kept_setting"),
390 }
391 for record in removed_duplicates
392 if record["normalized_value"] == record.get("kept_normalized_value")
393 ]
394 duplicates_in_source = {}
395 duplicates_across_sources = {}
396
397 for record in removed_identical:
398 source_name = record.get("source", "<unknown>")
399 duplicate_of_source = record.get("duplicate_of_source", "<unknown>")
400 if duplicate_of_source == source_name:
401 duplicates_in_source[source_name] = (
402 duplicates_in_source.get(source_name, 0) + 1
403 )
404 continue
405
406 source_pair = (source_name, duplicate_of_source)
407 duplicates_across_sources[source_pair] = (
408 duplicates_across_sources.get(source_pair, 0) + 1
409 )
410
411 conflict_details = _build_conflict_details(records, kept_records)
412
413 return {
414 "removed_duplicates": removed_duplicates,
415 "removed_identical": removed_identical,
416 "conflicting_reassignments": [
417 record
418 for record in removed_duplicates
419 if record["normalized_value"] != record.get("kept_normalized_value")
420 ],
421 "removed_overridden": [
422 record
423 for record in removed_duplicates
424 if record["normalized_value"] != record.get("kept_normalized_value")
425 ],
426 "conflict_details": conflict_details,
427 "conflicts": _find_conflicts(records),
428 "duplicates_in_source": duplicates_in_source,
429 "duplicates_across_sources": [
430 {
431 "source": source_name,
432 "duplicate_of_source": duplicate_of_source,
433 "count": duplicate_count,
434 }
435 for (source_name, duplicate_of_source), duplicate_count
436 in sorted(duplicates_across_sources.items())
437 ],
438 }
439
440