Sign in to run and submit your work
Reading is open to everyone. Running code and saving drafts need an account so your work is yours and comes back on your next visit.
or
CODE WORKSPACE
When a row fails validation the pipeline logs the record so somebody can work out why. The records contain email addresses and card numbers, and the logs are kept for ninety days in a system half the company can read. The current redaction only looks at the top level, and the card numbers are two levels down.
Write redact_fields(record, sensitive_fields, keep_last). Return a redacted copy.
Function to write
redact_fields(record: dict, sensitive_fields: list[str], keep_last: int) -> dictA copy of the record with every sensitive value masked, at any depth, structure preserved.
How to approach it
Walk the whole structure; decide at each key whether to mask it or to keep descending.
Sample cases
+ 2 held back until you submit
a nested customer record
A sensitive field one level down, two more inside a list, a differently cased key and a null.
Input
Argument 1
{
'order_id': 'o1',
'customer': {
'Email': 'ada@example.com',
'name': 'Ada',
'cards': [
{
'pan': '4111111111111111',
'brand': 'visa'
},
{
'pan': '5500000000000004',
'brand': 'mc'
}
]
},
'notes': None,
'ssn': None
}Argument 2
[
'email',
'pan',
'ssn'
]Argument 3
4Returns
{
'order_id': 'o1',
'customer': {
'Email': '***********.com',
'name': 'Ada',
'cards': [
{
'pan': '************1111',
'brand': 'visa'
},
{
'pan': '************0004',
'brand': 'mc'
}
]
},
'notes': None,
'ssn': None
}an empty record
Nothing to redact returns an empty dict rather than raising on the walk.
Input
Argument 1
{}Argument 2
[
'email'
]Argument 3
4Returns
{}nothing declared sensitive
With no sensitive fields the record comes back intact, structure and all.
Input
Argument 1
{
'order_id': 'o1',
'customer': {
'Email': 'ada@example.com',
'name': 'Ada',
'cards': [
{
'pan': '4111111111111111',
'brand': 'visa'
},
{
'pan': '5500000000000004',
'brand': 'mc'
}
]
},
'notes': None,
'ssn': None
}Argument 2
[] (empty list)
Argument 3
4Returns
{
'order_id': 'o1',
'customer': {
'Email': 'ada@example.com',
'name': 'Ada',
'cards': [
{
'pan': '4111111111111111',
'brand': 'visa'
},
{
'pan': '5500000000000004',
'brand': 'mc'
}
]
},
'notes': None,
'ssn': None
}Constraints
sensitive_fields, compared after stripping and lowercasing both. Email and email are the same field.keep_last characters, which are kept so a human can still tell two values apart.keep_last, or keep_last is zero or less, keep nothing. Revealing a short value entirely defeats the point.REDACTED, except None, which stays None — a field that was never set reveals nothing.Worked example
The card numbers live at customer.cards[0].pan and customer.cards[1].pan — inside a list, inside a dict, inside the record. A redaction that walks only the top-level keys finds neither, and the log line looks redacted, which is worse than one that obviously is not.
The customer's email is stored under Email with a capital letter, because it came from a system that title-cases its columns. Matching keys exactly means that one field passes straight through. Both faults produce output that looks correct at a glance, which is why the rule has to be about the shape of the record rather than about the fields you remembered.
What this tests
Recursion over a mixed structure with a rule that stops at the sensitive node, and the habit of normalising a key before matching it. Getting this wrong is not a bug report — it is a retention problem you cannot undo.
redact_fields(record: dict, sensitive_fields: list[str], keep_last: int) -> dictSubmit for review to find out what your query gets right, what it gets wrong, and how it compares with the best working query for this exercise.
This scenario runs a full workspace — editor, canvas and results side by side. It needs a laptop or desktop to be usable. Open this page on a bigger screen to start building.