Merchant Fraud Flags from Events
MediumHash Map · Stripe
Decide which merchants to flag for fraud after processing a stream of charge and dispute events.
Events, in order:
["CHARGE", charge_id, merchant_id, mcc, code]: a charge formerchant_id.mccis the merchant's category; a merchant always uses the samemcc. The charge is fraudulent whencodeis infraud_codes. Charge ids are unique.["DISPUTE", charge_id]: the charge was disputed, so remove it from its merchant's counts entirely (from the total and, if it was fraudulent, from the fraudulent count). Disputes for unknown charge ids, or for charges already disputed, are ignored. A dispute always comes after its charge.
After all events, evaluate each merchant that still has at least one undisputed charge, using t = thresholds[mcc]:
- If
t < 1, it is a ratio threshold: flag whenfraudulent / total >= t. - If
t >= 1, it is a count threshold: flag whenfraudulent >= t.
Return the flagged merchant ids sorted lexicographically.
Example 1
- Input:
- events = [["CHARGE", "c1", "pizza_co", "food", "stolen"], ["CHARGE", "c2", "pizza_co", "food", "ok"], ["CHARGE", "c3", "pizza_co", "food", "ok"], ["CHARGE", "c4", "fly_inc", "travel", "stolen"], ["CHARGE", "c5", "fly_inc", "travel", "stolen"]], thresholds = {"food": 0.3, "travel": 3}, fraud_codes = ["stolen"]
- Output:
- ["pizza_co"]
- Explanation
- pizza_co has 1 fraudulent charge out of 3, a ratio of about 0.33, which meets its ratio threshold of 0.3. fly_inc has 2 fraudulent charges, short of its count threshold of 3.
Example 2
- Input:
- events = [["CHARGE", "c1", "m1", "retail", "ok"], ["CHARGE", "c2", "m1", "retail", "fraud"], ["CHARGE", "c3", "m1", "retail", "ok"], ["DISPUTE", "c3"], ["DISPUTE", "c3"], ["DISPUTE", "zz"]], thresholds = {"retail": 0.5}, fraud_codes = ["fraud"]
- Output:
- ["m1"]
- Explanation
- Before the dispute m1 is at 1 of 3. Disputing the legitimate
c3leaves 1 fraudulent out of 2, a ratio of 0.5, which meets the threshold. The repeat dispute and the unknown id are ignored.
Example 3
- Input:
- events = [["CHARGE", "c1", "m1", "gaming", "fraud"], ["DISPUTE", "c1"], ["CHARGE", "c2", "m2", "gaming", "fraud"]], thresholds = {"gaming": 1}, fraud_codes = ["fraud"]
- Output:
- ["m2"]
- Explanation
- m1's only charge is disputed, so it has no charges left and is skipped. m2 has 1 fraudulent charge, which meets the count threshold of 1.
Constraints
0 <= events.length <= 10^5- Every
mccthat appears is a key ofthresholds 0 <= thresholds[mcc] <= 10^5- Charge ids are unique among CHARGE events