Katkat

Evaluate Configurable Rideshare Payout Audit Rules as Data

RipplingSoftware Engineer · Technical Screen

Last updated

Implement a generic rule-evaluation engine for a rideshare payout audit tool, where per-fare and per-driver-total rules, each with its own AND-combined conditions, are supplied entirely as data rather than hard-coded branches, using exact integer-cent arithmetic.

A rideshare payout audit tool flags fares and drivers that trip configurable policies — e.g., "any airport fare over $10," or "a driver whose total surge-zone earnings this batch exceed $30." New policies are added by finance without a code change, so the rules must be evaluated as data, not as a hard-coded if per policy.

Implement evaluate_payout_rules(fares, rules, conditions) -> list[list[str]].

Input contract

Every dollar figure is tracked in whole cents rather than a decimal currency type, which would otherwise accumulate rounding drift across repeated additions. The table representation and operator set below are fixed for this exercise.

  • Each fare is [fareId, driverId, amountCents, fareType, zoneType, zoneName]. Fare IDs are unique; every field is present. Up to 10,000 fares, 100 rules, 1,000 conditions. amountCents is a nonnegative integer string, at most 100,000,000.
  • Each rule is [policyId, scope, capCents], with unique policyId. scope is FARE or DRIVER_SUM. For FARE, capCents is the empty string and unused. For DRIVER_SUM, it's a nonnegative integer string.
  • Each condition is [policyId, field, operator, value]. Conditions sharing a policyId are combined with AND. A rule with zero conditions matches every fare. Every condition references a rule that exists.
  • Valid fields: fare_id, driver_id, amount_cents, fare_type, zone_type, zone_name.
  • For amount_cents: operators EQ, NE, GT, GE, LT, LE, compared as integers. For every other field: operators EQ, NE only, compared as exact, case-sensitive strings. Input is always well-formed.
  • A FARE rule flags every matching fare individually. A DRIVER_SUM rule sums amountCents of matching fares per driver, then flags a driver only if that driver's sum is strictly greater than the threshold. A driver with no matching fares has sum zero and is never flagged, since thresholds are nonnegative.

Output contract

Return one row [scope, subjectId, policyId, actualCents] per violation. subjectId is a fare ID or a driver ID. actualCents is that fare's amount, or the driver's filtered sum, as an integer string. Order the output by rule input order; within a FARE rule, by fare input order; within a DRIVER_SUM rule, by driverId ascending (lexicographic).

Worked example

fares = [
  ["F1","D1","1200","airport","surge","downtown"],
  ["F2","D1","900","standard","normal","suburb"],
  ["F3","D2","5000","airport","normal","downtown"],
  ["F4","D2","300","standard","normal","suburb"],
  ["F5","D1","2200","airport","surge","downtown"],
]
rules = [
  ["R1","FARE",""],            # airport fares over 1000 cents
  ["R2","DRIVER_SUM","3000"],  # per-driver sum of surge-zone fares over 3000 cents
]
conditions = [
  ["R1","fare_type","EQ","airport"],
  ["R1","amount_cents","GT","1000"],
  ["R2","zone_type","EQ","surge"],
]

Expected output:

[["FARE","F1","R1","1200"],
 ["FARE","F3","R1","5000"],
 ["FARE","F5","R1","2200"],
 ["DRIVER_SUM","D1","R2","3400"]]

D1's surge total is 1200 + 2200 = 3400 > 3000, so it's flagged; D2 has no surge fares at all.

Hints

Hint 1

Model a condition as a (field, operator) pair resolved to a comparator function at setup time, so evaluating a fare against a rule is just 'all comparators pass,' with no per-field branching scattered through the main loop.

Hint 2

Keep amount comparisons as integers end to end — never format cents as a float or a currency string before the comparison.

Hint 3

Group conditions by policyId once, up front, rather than re-scanning the whole conditions list for every fare.

Hint 4

For DRIVER_SUM, accumulate a running sum per driver in one pass over the fares that match the rule's conditions, then filter and sort the accumulated sums separately from the matching step.

Follow-up questions

  • How would you extend this to support an OR between some conditions, not just AND across all of a rule's conditions?
  • If this needed to run incrementally as fares stream in throughout the day rather than as one batch, what would need to change about the DRIVER_SUM accounting?
  • How would you add a new scope, say ZONE_SUM, without duplicating the FARE-matching logic?
  • What would you change about the condition representation to let a rule reference a derived field, like 'fare count this week for this driver', not just columns present on the fare itself?