Source code for search_names.pipeline.step2_augment
"""Add prefix and nickname variants to normalized name records."""
import csv
from pathlib import Path
DEFAULT_OUTPUT = "augmented_clean_names.csv"
DEFAULT_NAME_LOOKUP = "FirstName"
DEFAULT_PREFIX_LOOKUP = "seat"
OUTPUT_COLUMNS = ("prefixes", "nick_names")
def load_prefixes(filename: str | Path | None, lookup_column: str) -> dict[str, str]:
"""Load prefix values keyed by the configured lookup column."""
if filename is None:
return {}
with Path(filename).open(encoding="utf-8", newline="") as input_stream:
reader = csv.DictReader(input_stream)
missing = {lookup_column, "prefixes"}.difference(reader.fieldnames or [])
if missing:
raise ValueError(
f"prefix file is missing columns: {', '.join(sorted(missing))}"
)
return {row[lookup_column]: row["prefixes"] for row in reader}
def load_nick_names(filename: str | Path | None) -> dict[str, str]:
"""Load ``name[,name]-nickname[,nickname]`` mappings."""
if filename is None:
return {}
nicknames: dict[str, str] = {}
with Path(filename).open(encoding="utf-8") as input_stream:
for line_number, raw_line in enumerate(input_stream, start=1):
line = raw_line.strip().casefold()
if not line or line.startswith("#"):
continue
if "-" not in line:
raise ValueError(f"invalid nickname mapping on line {line_number}")
names_text, nicknames_text = line.split("-", maxsplit=1)
names = [value.strip() for value in names_text.split(",") if value.strip()]
values = [
value.strip() for value in nicknames_text.split(",") if value.strip()
]
if not names or not values:
raise ValueError(f"invalid nickname mapping on line {line_number}")
joined_values = ";".join(values)
for name in names:
if name in nicknames:
raise ValueError(f"duplicate nickname mapping for {name!r}")
nicknames[name] = joined_values
return nicknames
[docs]
def augment_names(
input_file: str | Path,
prefix_column: str = DEFAULT_PREFIX_LOOKUP,
name_column: str = DEFAULT_NAME_LOOKUP,
output_file: str | Path = DEFAULT_OUTPUT,
prefix_file: str | Path | None = None,
nickname_file: str | Path | None = None,
) -> int:
"""Write a copy of the input with deterministic prefix/nickname columns."""
prefixes = load_prefixes(prefix_file, prefix_column)
nicknames = load_nick_names(nickname_file)
with Path(input_file).open(encoding="utf-8", newline="") as input_stream:
reader = csv.DictReader(input_stream)
missing = {prefix_column, name_column}.difference(reader.fieldnames or [])
if missing:
raise ValueError(
f"input file is missing columns: {', '.join(sorted(missing))}"
)
source_columns = [
column
for column in (reader.fieldnames or [])
if column not in OUTPUT_COLUMNS
]
rows = []
for source_row in reader:
row = {column: source_row[column] for column in source_columns}
row["prefixes"] = prefixes.get(source_row[prefix_column], "")
row["nick_names"] = nicknames.get(source_row[name_column].casefold(), "")
rows.append(row)
with Path(output_file).open("w", encoding="utf-8", newline="") as output_stream:
writer = csv.DictWriter(
output_stream, fieldnames=[*source_columns, *OUTPUT_COLUMNS]
)
writer.writeheader()
writer.writerows(rows)
return len(rows)