Tired of your expense policy being a polite suggestion that everyone ignores until it hits my desk. Approval queues are clogged with $50 breakfasts and "client gifts" that are clearly for the employee's home bar.
So I automated the gatekeeper. This script parses exported expense data (CSV from your system, probably), runs it against your rules, and spits out a violations list. It catches the usual suspects: out-of-category spend, overshoots on meal limits, duplicate vendor charges in a 24-hour window. Feed the output into your workflow tool to auto-reject or flag for review. Saves me from being the bad cop.
```python
import pandas as pd
from datetime import datetime, timedelta
def flag_policy_violations(df, meal_limit=25, gift_limit=75):
"""Flags expenses violating basic policy. df must have columns: 'amount', 'category', 'vendor', 'date', 'employee_id'."""
flags = []
for idx, row in df.iterrows():
# Meal over-limit check
if row['category'] == 'Meals' and row['amount'] > meal_limit:
flags.append((idx, 'Meal limit exceeded', row['amount']))
# Gift over-limit check
if row['category'] == 'Client Gifts' and row['amount'] > gift_limit:
flags.append((idx, 'Gift limit exceeded', row['amount']))
# Duplicate vendor check (same employee, same vendor, within 1 day)
same_vendor = df[(df['vendor'] == row['vendor']) & (df['employee_id'] == row['employee_id'])]
for _, dup_row in same_vendor.iterrows():
if dup_row.name == idx:
continue
date_diff = abs(pd.to_datetime(row['date']) - pd.to_datetime(dup_row['date']))
if date_diff < timedelta(days=1):
flags.append((idx, 'Potential duplicate charge', row['vendor']))
return pd.DataFrame(flags, columns=['expense_index', 'violation', 'detail'])
# Example usage
# df = pd.read_csv('exported_expenses.csv')
# violations = flag_policy_violations(df)
# violations.to_csv('flagged_violations.csv', index=False)
```
It's crude. It won't replace a proper system, but it's faster than manually auditing 300 line items every Monday. Modify the rules to match your actual policy. Integrate it right after your expense system's nightly export.
CRM is a necessary evil
This is a smart approach, shifting enforcement left into the data layer before it becomes a workflow problem. I've seen similar logic implemented as a pre-processing step in observability pipelines to filter or tag log events before they hit indexing.
A potential issue with `iterrows()` on larger datasets is performance. If you're processing hundreds of thousands of rows monthly, consider vectorized operations with Pandas or Polars. For the duplicate vendor check you mentioned, you'd want a groupby on `employee_id`, `vendor`, and a date window, which can be heavy with a row-wise loop.
Also, consider where your rule definitions live. Hardcoding limits in the function works, but you might eventually need a separate config file or even a simple rules table to manage changes without touching the script.
Data is not optional.
Great point about the config - we learned that the hard way when Finance changed our meal policy twice in one quarter! We ended up using a simple YAML file for rule definitions, which let our ops team update limits without a code deploy.
The performance tip is spot on, especially with Polars. On our last quarterly run, switching from iterrows to a vectorized approach cut processing time for 80k expenses from about 8 minutes down to under 30 seconds. That scale really sneaks up on you.
Always testing.
Two policy changes in a quarter? That sounds like Finance had some budget panic. A YAML file is a good step, but now you've just moved the problem. Who owns the config, and how do you audit that changes are actually policy and not someone "tweaking" a limit to get their steak dinner approved? 😏
The performance numbers are compelling, sure. But I'm curious about the before and after cost. That 8-minute process running on a developer's laptop probably costs nothing. The "30-second" process - what's the infra bill for the beefier machine or managed service needed to run Polars at scale? You might save minutes but burn dollars, which is the whole thing we're supposed to be watching.
cost_observer_42
Finance's budget panic aside, the real compliance risk with a config file is drift between that YAML and the actual HR policy document, which is probably a PDF living on a SharePoint site from 2019. Your script becomes the source of truth by default, and no one audits for alignment until there's a violation dispute.
As for the infra cost debate, that's missing the point. The cost isn't the 30 seconds of compute, it's the labor saved from not having a human reviewer manually flagging the $50 breakfasts in the first place. The ROI isn't in CPU minutes, it's in reduced friction and recovered finance hours, which almost certainly dwarfs any marginal cloud cost. If you're worrying about the Polars bill, you're optimizing the wrong line item.
MQLs are a vanity metric.