Katkat

Validate KYC CSV Records

StripeSoftware Engineer · Technical Screen

Last updated

Implement a KYC CSV record validator that parses customer records, normalizes fields, and validates tax IDs and owner name matches.

Validate KYC CSV Records

When onboarding new businesses, Stripe verifies business identity by matching submitted application details against official registry records. You are tasked with writing a validation parser for a CSV export of onboarding records.

Write a function validate_kyc_records(csv_data: str) -> list[dict] that parses the CSV text and returns validation results for each record.

Input Format

csv_data is a multi-line string containing a CSV header followed by zero or more data rows. The header row is always:

record_id,business_name,tax_id,legal_owner_name,submitted_owner_name

Fields may contain leading or trailing whitespace that must be stripped.

Validation Rules

Evaluate each row against the following rules in order:

  1. Missing Fields: All 5 fields must be present and non-empty after trimming whitespace. If any field is empty, flag error "MISSING_REQUIRED_FIELD".
  2. Tax ID: A valid tax_id must consist of exactly 9 digits after stripping hyphens (-) and spaces. (e.g., "12-3456789" and " 123456789 " are valid; "12-345" or "12345678A" are invalid). If invalid, flag error "INVALID_TAX_ID".
  3. Owner Name Match: Compare legal_owner_name and submitted_owner_name using token overlap:
    • Normalize both names by converting to lowercase, removing all punctuation/symbols (keep only letters, digits, and spaces), and splitting on whitespace into sets of unique words.
    • Let S be the set of unique words in submitted_owner_name and L be the set of unique words in legal_owner_name.
    • A name is considered a match if at least one word overlaps (len(S & L) >= 1) and the overlap covers at least 50% of the words submitted (len(S & L) / len(S) >= 0.5). If not, flag error "OWNER_NAME_MISMATCH".

A record may accumulate multiple distinct errors if it violates multiple rules (except MISSING_REQUIRED_FIELD skips rules 2 and 3 if the corresponding field is empty).

Output Format

Return a list of dictionaries, one per record in the order they appear:

[
    {
        "record_id": str,      # trimmed record_id (or "" if missing)
        "is_valid": bool,       # True if errors is empty, else False
        "errors": list[str]     # list of error strings in the order defined
    },
    ...
]

Examples

csv_input = """record_id,business_name,tax_id,legal_owner_name,submitted_owner_name
rec_1,Acme Corp,12-3456789,Jane Doe,Jane Doe
rec_2,Beta LLC,99-999,Robert Smith,Bob Smith
rec_3,Gamma Inc,11-2233445,Alice M. Walker,Alice Walker
"""

validate_kyc_records(csv_input)
# Returns:
# [
#     {"record_id": "rec_1", "is_valid": True, "errors": []},
#     {"record_id": "rec_2", "is_valid": False, "errors": ["INVALID_TAX_ID", "OWNER_NAME_MISMATCH"]},
#     {"record_id": "rec_3", "is_valid": True, "errors": []}
# ]

Constraints

  • csv_data contains between 1 and 10,000 rows.
  • Field values do not contain commas or escaped quotes.

Hints

Hint 1

Start by parsing the CSV lines with Python's built-in `csv.reader` to cleanly handle whitespace and varying row lengths.

Hint 2

For the Tax ID, remove all hyphens and spaces first, then check whether the resulting string has length 9 and satisfies `.isdigit()`.

Hint 3

For name matching, use regular expressions or `str.isalnum()` to strip non-alphanumeric characters, lowercase everything, and compare set intersections.

Follow-up questions

  • How would you handle quoted fields that contain commas or newlines within a cell?
  • If common nickname dictionaries (e.g., 'Bob' -> 'Robert') need to be supported in owner matching, how would you integrate that into the pipeline?
  • How would you stream and process this data if the input CSV was several gigabytes and could not fit into memory?