Validate transactions with risk rules and reporting
StripeSoftware Engineer · Technical ScreenLast updated
Implement an end-to-end transaction validation pipeline that parses raw CSV records, evaluates behavioral fraud detection rules, and outputs a formatted fixed-width audit report.
Problem Statement
You are tasked with building a transaction validation and fraud detection engine. Your engine processes a list of raw CSV transaction records, evaluates validation and risk rules, and generates a fixed-width audit summary.
Each CSV record contains the following comma-separated fields:
transaction_id,user_id,amount_cents,currency,timestamp_iso
Validation & Risk Rules
Process each transaction against these rules in priority order. If a transaction violates multiple rules, report only the highest-priority reason:
INVALID_FORMAT: The record does not have exactly 5 fields,amount_centsis not a positive integer (> 0), ortimestamp_isocannot be parsed as a valid ISO-8601 UTC timestamp (YYYY-MM-DDTHH:MM:SSZ).UNSUPPORTED_CURRENCY: The currency is not one ofUSD,EUR,GBP.HIGH_VELOCITY: The sameuser_idhas already executed 2 or more approved transactions within the trailing 60-second window (inclusive:t - 60s <= prev_t <= t).AMOUNT_SPIKE: Theamount_centsexceeds 3 times the user's historical approved average transaction amount prior to this transaction. (Note: Only applies if the user has at least 2 previously approved transactions).
If none of the rules trigger, mark the transaction as APPROVED and add it to the user's approved history.
Transactions are guaranteed to be presented in strictly ascending order by timestamp_iso.
Expected Output
Return a list of fixed-width formatted strings, one for each input record, with columns separated by two spaces (" "):
TX_ID: Left-aligned, width 10 (truncate if longer).STATUS: Left-aligned, width 8 (APPROVEDorDECLINED).REASON: Left-aligned, width 20 (OKif approved, otherwise the rule name above).
Function Signature
def evaluate_transactions(records: list[str]) -> list[str]:
pass
Constraints
- Number of records:
1 <= N <= 10^4 transaction_idanduser_id: alphanumeric strings up to 32 charactersamount_cents: integer up to10^9
Example
records = [
"tx_01,user_a,1000,USD,2023-01-01T00:00:00Z",
"tx_02,user_a,1200,USD,2023-01-01T00:00:15Z",
"tx_03,user_a,1100,USD,2023-01-01T00:00:30Z",
"tx_04,user_a,5000,USD,2023-01-01T00:02:00Z",
]
output = evaluate_transactions(records)
# Output:
# [
# "tx_01 APPROVED OK ",
# "tx_02 APPROVED OK ",
# "tx_03 DECLINED HIGH_VELOCITY ",
# "tx_04 DECLINED AMOUNT_SPIKE "
# ]
Explanation: tx_01 and tx_02 are approved (avg = 1100). tx_03 occurs at 00:00:30Z, which is within 60s of tx_01 and tx_02 (2 previous approved), so it triggers HIGH_VELOCITY. tx_04 occurs at 00:02:00Z (velocity window cleared); however, 5000 > 3 * 1100 (3300), triggering AMOUNT_SPIKE.
Hints
Hint 1
Parse the timestamp to Unix epoch seconds or comparable integer timestamps to simplify sliding-window velocity checks.
Hint 2
Maintain state per `user_id` storing approved timestamps in a deque/list and a running sum and count of approved transaction amounts.
Hint 3
Use Python string formatting (e.g., `f"{tx_id[:10]:<10} {status:<8} {reason:<20}"`) to cleanly enforce exact column widths.
Follow-up questions
- How would you handle out-of-order or late-arriving transaction streams without loading all records into memory?
- If the historical average rule needed to be evaluated across a rolling 30-day window instead of all-time, what data structure would you use to maintain `O(1)` updates?
- How would you scale this design if users make transactions concurrently across multiple worker instances?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.