Implement an optimized rules engine `solution(rules, expenses, blocklisted_vendors)`. Rules may have scope `expense` or `trip`. Expressions use the same boolean structure as Part 2, but leaf predicates are one of: `{'type': 'field', 'field': str, 'cmp': op, 'value': str, 'cost': int}`, `{'type': 'blocklist_contains', 'field': 'vendor_name', 'cost': int}`, `{'type': 'trip_total', 'cmp': op, 'value': str, 'cost': int}`, or `{'type': 'trip_meals_total', 'cmp': op, 'value': str, 'cost': int}`. To make optimizations observable, also return stats. Your evaluator must: (1) short-circuit boolean expressions, (2) evaluate lower-cost children before higher-cost children inside `AND` and `OR`, (3) cache repeated leaf results for the same expense or trip, and (4) pre-aggregate trip totals and trip meal totals once up front.
Examples
Input: ([{'rule_id': 'E1', 'scope': 'expense', 'expr': {'op': 'AND', 'args': [{'type': 'blocklist_contains', 'field': 'vendor_name', 'cost': 10}, {'type': 'field', 'field': 'amount_usd', 'cmp': '>', 'value': '100', 'cost': 1}]}}, {'rule_id': 'E2', 'scope': 'expense', 'expr': {'op': 'OR', 'args': [{'type': 'blocklist_contains', 'field': 'vendor_name', 'cost': 10}, {'type': 'field', 'field': 'expense_type', 'cmp': '==', 'value': 'airfare', 'cost': 1}]}}, {'rule_id': 'T1', 'scope': 'trip', 'expr': {'type': 'trip_total', 'cmp': '>', 'value': '2000', 'cost': 1}}, {'rule_id': 'T2', 'scope': 'trip', 'expr': {'type': 'trip_meals_total', 'cmp': '>', 'value': '200', 'cost': 1}}], [{'expense_id': 'a', 'trip_id': 't1', 'amount_usd': '50', 'expense_type': 'supplies', 'vendor_name': 'BadVendor'}, {'expense_id': 'b', 'trip_id': 't1', 'amount_usd': '150', 'expense_type': 'supplies', 'vendor_name': 'BadVendor'}, {'expense_id': 'c', 'trip_id': 't1', 'amount_usd': '2100', 'expense_type': 'lodging', 'vendor_name': 'GoodHotel'}], {'BadVendor'})
Expected Output: {'expense_violations': {'a': ['E2'], 'b': ['E1', 'E2']}, 'trip_violations': {'t1': ['T1']}, 'stats': {'expensive_lookups': 3, 'trips_preaggregated': 1}}
Explanation: Ordering, short-circuiting, and caching reduce blocklist checks: the repeated vendor predicate is not recomputed for the same expense.
Input: ([{'rule_id': 'R', 'scope': 'expense', 'expr': {'op': 'AND', 'args': [{'type': 'blocklist_contains', 'field': 'vendor_name', 'cost': 10}, {'type': 'field', 'field': 'expense_type', 'cmp': '==', 'value': 'meals', 'cost': 1}]}}], [{'expense_id': '1', 'trip_id': 'x', 'amount_usd': '20', 'expense_type': 'taxi', 'vendor_name': 'BadVendor'}], {'BadVendor'})
Expected Output: {'expense_violations': {}, 'trip_violations': {}, 'stats': {'expensive_lookups': 0, 'trips_preaggregated': 1}}
Explanation: The cheap predicate is false, so the expensive blocklist lookup is skipped entirely.
Input: ([], [], set())
Expected Output: {'expense_violations': {}, 'trip_violations': {}, 'stats': {'expensive_lookups': 0, 'trips_preaggregated': 0}}
Explanation: Edge case: no rules and no expenses.
Input: ([{'rule_id': 'E', 'scope': 'expense', 'expr': {'op': 'OR', 'args': [{'type': 'blocklist_contains', 'field': 'vendor_name', 'cost': 10}, {'type': 'field', 'field': 'expense_type', 'cmp': '==', 'value': 'airfare', 'cost': 1}]}}, {'rule_id': 'T', 'scope': 'trip', 'expr': {'type': 'trip_meals_total', 'cmp': '>', 'value': '200', 'cost': 1}}], [{'expense_id': '1', 'trip_id': 't2', 'amount_usd': '30', 'expense_type': 'airfare', 'vendor_name': 'GoodAir'}, {'expense_id': '2', 'trip_id': 't2', 'amount_usd': '190', 'expense_type': 'meals', 'vendor_name': 'Cafe'}, {'expense_id': '3', 'trip_id': 't2', 'amount_usd': '20', 'expense_type': 'meals', 'vendor_name': 'Cafe2'}], {'BadVendor'})
Expected Output: {'expense_violations': {'1': ['E']}, 'trip_violations': {'t2': ['T']}, 'stats': {'expensive_lookups': 2, 'trips_preaggregated': 1}}
Explanation: The airfare expense makes the cheap side of the `OR` true, so its expensive lookup is avoided. The trip exceeds the meal-total limit.