Transaction Fraud Report Part 3 — User Behavior Matching
Problem statement
Four-part sequence: This is Part 3 of one progressive transaction-fraud interview exercise. It retains Parts 1–2 and adds behavior-baseline matching.
- Part 1: Verify Transaction Data Integrity.
- Part 2: High-Risk Rule Validation.
- Part 3: User Behavior Matching (you are here).
- Part 4: Smart Fraud Error Reporting.
Validate each transaction, apply the global risk rules, and compare its behavior with the corresponding user's baseline.
Input
Each string in transactions is one unquoted CSV row with fields transaction_id,user_id,amount,payment_method,country,hour. Trim surrounding whitespace from every field.
Each row of baselines is [user_id,usual_country,start_hour,end_hour,min_amount,max_amount]. Every transaction whose fields are non-empty has exactly one matching baseline. Hour and amount intervals are inclusive and do not wrap.
Review rules
- If any transaction field is empty, mark the row
SUSPICIOUSand stop evaluating that row. - Otherwise, mark it
SUSPICIOUSwhen its amount is outside the inclusive global interval[normalMin, normalMax]or its payment method is in the case-sensitiveblockedMethodsset. - Compare three behavior features: exact country match, hour inside the baseline interval, and amount inside the baseline interval. At least two of the three features must match. Fewer than two matches makes the transaction
SUSPICIOUS. - A transaction is
OKonly when it passes every applicable rule.
Result
Return a two-column string table whose first row is ["transaction_id","status"]. Append one result row per transaction in input order.
Function
reviewTransactionBehavior(transactions: String[], baselines: String[][], normalMin: int, normalMax: int, blockedMethods: String[]) → String[][]Examples
Example 1
transactions = ["tx1,u1,500,card,US,10","tx2,u1,50,cash,CA,20","tx3,u1,550,card,CA,10","tx4,u1,550,card,CA,20","tx5,u1,,card,US,10"]baselines = [["u1","US","9","17","400","600"]]normalMin = 100normalMax = 1000blockedMethods = ["cash"]return = [["transaction_id","status"],["tx1","OK"],["tx2","SUSPICIOUS"],["tx3","OK"],["tx4","SUSPICIOUS"],["tx5","SUSPICIOUS"]]tx1 passes all rules. tx2 violates both global rules and matches no behavior feature. tx3 matches hour and amount, so it reaches the required two matches. tx4 matches only amount. tx5 stops at integrity validation.
Example 2
transactions = ["country,u2,100,Card,US,23","hour,u2,100,Card,CA,0","amount,u2,200,Card,CA,23"]baselines = [["u2","US","0","23","100","100"]]normalMin = 100normalMax = 200blockedMethods = ["card"]return = [["transaction_id","status"],["country","OK"],["hour","OK"],["amount","SUSPICIOUS"]]country matches all three features. hour matches the inclusive hour and amount boundaries. amount matches only the hour boundary, so it is suspicious. Method matching is case-sensitive, so Card does not equal blocked method card.
Constraints
1 <= transactions.length <= 10000; every row contains exactly six comma-separated fields and no quoted commas.1 <= baselines.length <= 10000; baseline user IDs are unique.0 <= normalMin <= normalMax <= 1000000000.- Every non-empty amount and baseline amount bound is an integer from
0through1000000000. - Every non-empty transaction hour and baseline hour bound is an integer from
0through23, and each baseline lower bound is at most its upper bound. - Every transaction with six non-empty fields has a matching user baseline. Labels and identifiers contain at most
100characters.