setup_notebook_html()setup_polars_display()validation_mode = notebook_validation_mode()progress = TqdmProgress(disable=validation_mode)# LightGBM learning-to-rank can otherwise use all host CPU threads during full# benchmark runs. Increase only when the host has spare cores and memory.NOTEBOOK_LTR_NUM_THREADS =1if validation_mode else8
Decision question: after deterministic hard rules remove obvious payroll violations, which model should rank the remaining SNF payroll records for limited human review?
Across the current scenario benchmark, probability-based models – especially the cost-sensitive classifier – are the most robust winners for queue quality and utility under the implemented DGPs. Expected-value scoring remains the most conceptually aligned model for payroll loss prevention, but its current implementation does not consistently beat the classifier family across scenario-seed results. The next modeling iteration should focus on improving expected-value calibration and exposure estimation.
1. Decision Context: Residual Review After Hard Rules
Hard rules are the first-stage control. They catch impossible or obvious payroll defects before ML begins. The model ranks only the residual review queue: employee-pay-cycle records that survive the gate, grouped within facility x payroll cycle review queues.
The objective is payroll loss prevention, not staffing compliance. PBJ, HPRD, and regulatory staffing-risk metrics are excluded from targets and evaluation; facility, role, pay-period, timekeeping, payroll-history, and peer context remain allowed as payroll signals.
The benchmark uses synthetic SNF payroll data so latent residual truth, dollar impact, severe misses, and label bias are observable for evaluation. Scenarios vary issue density, severe-tail rate, dollar exposure, issue mix, and historical label bias. Review budget and model objective are evaluated as operating choices, not scenario definitions.
flowchart LR
classDef source fill:#F8FAFC,stroke:#64748B,stroke-width:1px,color:#0F172A;
classDef gate fill:#FEF2F2,stroke:#DC2626,stroke-width:1px,color:#7F1D1D;
classDef residual fill:#F0FDF4,stroke:#16A34A,stroke-width:1px,color:#14532D;
classDef bias fill:#FFFBEB,stroke:#D97706,stroke-width:1px,color:#78350F,stroke-dasharray: 5 3;
subgraph world["Synthetic payroll world"]
facilities["Facility context<br/>region, size tier, payroll maturity"]:::source
employees["Employee population<br/>role, tenure, home facility"]:::source
payroll["Payroll and timekeeping generation<br/>hours, overtime, rate changes, edits"]:::source
facilities --> payroll
employees --> payroll
end
subgraph gate_stage["Hard-rule gate"]
critical["Critical rule violations"]:::gate
excluded["Excluded before ML"]:::gate
end
subgraph residual_stage["Residual ranking setup"]
residual["Residual payroll issues<br/>ambiguous risks that survive the gate"]:::residual
truth["Latent truth for evaluation"]:::residual
cycles["Employee-pay-cycle modeling table<br/>active ranking grain"]:::residual
end
observed["Observed corrections<br/>(biased reviewed subset)"]:::bias
payroll --> critical
payroll --> residual
critical --> excluded
residual -->|survives gate| cycles
residual --> truth
residual -->|historically reviewed subset| observed
observed -->|auxiliary historical signal| cycles
Code
# Validation mode is a CI execution check, not an analytical run. Keep enough# data to exercise model training, grouped ranking, temporal splits, and plot# code while avoiding the full scenario x seed x model workload.## `pay_periods=6` is intentional: below 6 periods, rolling-origin temporal# diagnostics are empty by design, so 6 is the smallest useful setting for# validating temporal-path assumptions without paying full notebook cost.# A single review budget is enough to cover grouped-budget code paths in CI.sim_config = PayrollConfig( facility_count=3if validation_mode else25, employee_count=60if validation_mode else1500, pay_periods=6if validation_mode else36, ltr_num_threads=NOTEBOOK_LTR_NUM_THREADS, employee_cycle_review_budget_percents=( (0.05,) if validation_mode else (0.01, 0.03, 0.05, 0.10) ),)review_budget_percents = sim_config.employee_cycle_review_budget_percents ortuple(float(budget) for budget in sim_config.review_budgets)# The scenario benchmark uses the default holdout splitter, which needs eight# periods for its 4-period validation and 4-period test windows. Keep the main# validation data at six periods, but raise only the reduced benchmark workload.scenario_benchmark_config = replace( sim_config, pay_periods=8if validation_mode else sim_config.pay_periods,)
Code
data = generate_employee_pay_cycles(sim_config, progress=progress)
scenario_benchmark_seeds = ( (sim_config.seed,)if validation_modeelsetuple(sim_config.seed + offset for offset inrange(20)))# In validation mode, exercise the default scenario plus one scenario with drift# controls. The full implemented scenario catalog is analysis-oriented and is# the dominant CI runtime cost because each scenario retrains every model family.scenario_benchmark_scenarios =Noneif validation_mode: implemented_scenarios = implemented_dgp_scenario_catalog() scenario_benchmark_scenarios = { name: implemented_scenarios[name]for name in ("baseline-operations", "temporal-payroll-drift") }
IOPub message rate exceeded.
The Jupyter server will temporarily stop sending output
to the client in order to avoid crashing it.
To change this limit, set the config variable
`--ServerApp.iopub_msg_rate_limit`.
Current values:
ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)
ServerApp.rate_limit_window=3.0 (secs)
3. Hard-Rule Gate: Defining the Residual Review Queue
Hard rules are an upstream gate, not a competing model. They remove critical deterministic violations before ML ranking. Soft warnings remain eligible as contextual model features because they are ambiguous after gating.
The model task is therefore:
Rank residual review queue records within each facility x payroll cycle by expected review value.
The residual review queue is not a simple fraud/no-fraud problem. The same record can matter because it is likely wrong, because it has high dollar impact, or because it is a severe miss that survived hard rules.
The benchmark therefore compares three practical model families:
Probability models: rank records by residual issue likelihood.
Value models: rank records by issue likelihood combined with dollar exposure.
Learning-to-rank models: rank records by graded residual review priority within facility x payroll cycle.
Historical observed corrections are retained for bias analysis only. They are not treated as ground truth.
The scenario landscape shows why the benchmark aggregates over scenario and seed units instead of picking a winner from one synthetic world. The residual issue mix below explains why the same model need not win every objective.
residual_family_pareto_plot_data = residual_family_mix.with_columns( pl.col(PayrollCol.ANOMALY_CATEGORY).cast(pl.String).alias("anomaly_family"), pl.col("share_of_residual_issues").round(4),).sort("share_of_residual_issues")( ggplot( residual_family_pareto_plot_data, aes( x="anomaly_family", y="share_of_residual_issues", fill="severe_share", ), )+ geom_bar(stat="identity")+ coord_flip()+ theme_minimal()+ scale_fill_gradient(low="#dbeafe", high="#991b1b")+ labs( x="Residual anomaly family", y="Share of residual issues", fill="Severe share", )+ ggtitle("Residual Issue Mix Is Concentrated but Not One-Dimensional"))
Most residual issues are material but non-severe; the severe tail is smaller but operationally important. The ranking problem is therefore broader than severe-case detection.
paid_vs_scheduled_mismatch is the largest family by count, while overtime_double_shift is the most severe and dollar-heavy family. That split is the main reason probability, value, and severity objectives can point to different rankers.
Contextual features separate ambiguous-but-benign residual records from ambiguous-and-costly ones. The comparison uses the same residual scoring universe, train/test split, facility x payroll cycle grouping, review budgets, and leakage rules for every primary model family.
5. Main Results: Which Ranker Wins By Objective
The main study evaluates residual review queues across DGP scenarios and seeds, then aggregates by model, review budget, and operating objective.
Seeds estimate random-draw stability within a scenario. Scenario comparisons test structural robustness across different payroll-generating conditions.
Code
# Full employee-cycle evaluation includes rolling-origin and production-readiness# diagnostics that are useful for analysis but redundant for CI notebook runtime# checks. Validation mode only needs the downstream model-comparison contract.if validation_mode: model_comparison = employee_cycle_model_comparison(scored, sim_config)else: evaluation = evaluate_employee_cycle_scores(scored, sim_config, progress=progress) model_comparison = evaluation.model_comparison
The benchmark shows a split leaderboard rather than one universal winner. That is expected: issue probability, dollar recovery, utility, and severity ordering reward different queue behavior. Under the implemented DGPs, the empirical production default should come from the classifier family: the cost-sensitive classifier is strongest at tight budgets and for queue quality, while the standard classifier often catches up at broader review budgets.
Expected value remains the conceptual target because the residual task is financial: high-priority records are not merely likely to be wrong, they are costly when ignored. The current implementation should be treated as a model improvement track rather than the empirical default until exposure estimation and calibration consistently improve scenario-seed results. Learning to rank remains a severity-oriented challenger for operating modes that prioritize top-of-queue ordering over dollar-weighted net value.
6. Why Results Differ By Objective
Three ablation findings explain the split results:
Timekeeping and soft-warning context drive most of the feature lift after hard rules remove obvious defects.
Residual-only training remains preferable because the deployed model scores the residual review queue, not all payroll records.
Label choice changes the winner: single-run label ablations show why expected value is conceptually attractive for dollar/utility targets, but the cross-scenario benchmark favors the classifier family as the more robust empirical default.
Detailed ablation rows stay in the appendix; the main implication is simple: expected value remains useful because value-aware ranking is aligned with the residual payroll-loss objective, but the current implementation is not robust enough across scenario-seed results to displace the classifier family.
7. Recommended Deployment Pattern
Deploy the ranker as a second-stage residual review queue, not as a replacement for hard rules. Keep the decision surface small: default model, challenger, reviewer context, and monitoring slices.
Use as the empirical default, especially for tight review budgets and queue-quality robustness.
Broad-budget fallback
Classifier
Consider when review budgets are less constrained and median winner-map results favor pure probability ranking.
Model-improvement target
Expected value
Keep as the conceptually aligned payroll-loss objective, but improve calibration and exposure estimation before promoting it.
Severity challenger
Learning to rank
Track when severe top-of-queue ordering becomes the primary goal.
Required monitoring
facility x pay period x issue family
Monitor drift, severe misses, and issue-family blind spots after deployment.
For residual SNF payroll loss prevention after hard-rule screening, use the cost-sensitive classifier as the current empirical default and keep the standard classifier as the broad-budget fallback. Keep expected-value scoring on the model-improvement path until its exposure component consistently beats the classifier family across scenario-seed results.
Deployment pattern:
Keep critical hard rules upstream as deterministic controls.
Score only the residual review queue with ML.
Use the cost-sensitive classifier as the default residual queue ranker.
Track the standard classifier, expected value, and learning-to-rank as challengers.
Display reviewer-facing reason codes, issue probability, and estimated dollar exposure.
Monitor performance by facility, pay period, and issue family.
Periodically audit random residual records to reduce label bias.
8. Limitations
This benchmark uses synthetic payroll data, so model conclusions are evidence about modeling strategy rather than production performance claims.
Key limitations:
issue rates and dollar impacts are simulation assumptions
severe residual issues are concentrated in a small number of anomaly families
observed corrections are simulated rather than real reviewer actions
feature distributions may not fully match a real SNF operator
real deployment requires adjudicated review samples and monitoring by facility, role, and pay period
9. Technical Appendix
A. residual dataset diagnostics
These baseline diagnostics support the compact stress-design view in section 4. They are useful for auditing the synthetic residual queue, but they are kept out of the main narrative so the model-comparison story stays concise.
This chart excludes normal records and compares each population’s share of true issue records by anomaly family. The companion table keeps raw counts, but the visual uses shares so the large normal residual review queue does not hide the issue-family pattern.
Large schedule mismatch is handled as an upstream gate rather than residual ambiguity
D. metric definitions
Code
appendix_metric_definitions = pl.DataFrame( [ {"metric": str(MetricCol.RESIDUAL_NDCG_AT_K),"scope": "residual only","aggregation": "mean across facility x pay_period groups","numerator_or_gain": "DCG of ranked relevance_grade values within each group budget","denominator_or_reference": "ideal DCG for the same group budget","zero_positive_behavior": "group contributes 0 when ideal DCG is 0", }, {"metric": str(MetricCol.RULE_MISSED_SEVERE_RECALL_AT_K),"scope": "residual only","aggregation": "global over reviewed residual rows","numerator_or_gain": "reviewed rule_missed_severe_issue count","denominator_or_reference": "all rule_missed_severe_issue count in residual evaluation frame","zero_positive_behavior": "returns 0 when total severe count is 0", }, {"metric": str(MetricCol.DOLLARS_CAPTURED_AT_K),"scope": "residual positives only","aggregation": "global sum over reviewed residual rows","numerator_or_gain": "sum of y_dollar on reviewed residual issue rows","denominator_or_reference": "reported directly; capture rate uses total residual y_dollar","zero_positive_behavior": "returns 0 when no residual dollars exist", }, {"metric": str(MetricCol.REVIEWER_YIELD_AT_K),"scope": "residual only","aggregation": "global reviewed share","numerator_or_gain": "reviewed residual rows with y_issue == 1","denominator_or_reference": "all reviewed residual rows","zero_positive_behavior": "returns 0 when no rows are reviewed", }, {"metric": str(MetricCol.INCREMENTAL_UTILITY_AT_K),"scope": "residual only","aggregation": "global sum over reviewed residual rows","numerator_or_gain": "sum of net_utility on reviewed rows","denominator_or_reference": "reported directly rather than normalized","zero_positive_behavior": "returns 0 when no rows are reviewed", }, {"metric": str(MetricCol.PRECISION_AT_K),"scope": "residual only","aggregation": "mean across facility x pay_period groups","numerator_or_gain": "group true positives","denominator_or_reference": "group reviewed rows","zero_positive_behavior": "group denominator clipped to at least 1", }, {"metric": str(MetricCol.RECALL_AT_K),"scope": "residual only","aggregation": "mean across facility x pay_period groups","numerator_or_gain": "group true positives","denominator_or_reference": "group residual positives","zero_positive_behavior": "group denominator clipped to at least 1", }, {"metric": str(MetricCol.PR_AUC),"scope": "residual only","aggregation": "single residual-frame summary","numerator_or_gain": "average_precision_score over y_issue and final score","denominator_or_reference": "not a ratio table metric","zero_positive_behavior": "falls back to 0 on degenerate label cases", }, ],)appendix_metric_definitions
Loading ITables v2.7.3 from the internet...
(need help?)
E. ranking group construction
Code
appendix_group_construction = pl.DataFrame( [ {"component": "ranking item","active_definition": str(PayrollCol.EMPLOYEE_PAY_CYCLE_ID), }, {"component": "ranking group","active_definition": f"{PayrollCol.FACILITY_ID} x {PayrollCol.PAY_PERIOD_INDEX}", }, {"component": "evaluation scope","active_definition": f"{PayrollCol.RESIDUAL_RECORD} == 1 only", }, {"component": "default budget framing","active_definition": ", ".join( format_review_budget_pct(budget) for budget in review_budget_percents ), }, {"component": "percent budget conversion","active_definition": "ceil(group_size * budget) with minimum 1 reviewed row per non-empty group", }, {"component": "score ordering","active_definition": f"descending {ScoreCol.FINAL_ANOMALY_SCORE} within each group", }, ],)appendix_group_construction
Loading ITables v2.7.3 from the internet...
(need help?)
F. handling zero-positive residual groups
case
implemented_behavior
result
group recall with zero residual positives
group_anomalies denominator is clipped to at least 1
group recall becomes 0 instead of undefined
group NDCG with zero ideal gain
if ideal DCG is 0, group NDCG is set to 0
all-negative groups remain in the grouped average
global severe recall with zero severe residual issues
denominator uses max(total_severe, 1.0)
reported severe recall is 0 instead of undefined
PR-AUC on degenerate residual labels
ValueError is caught and PR-AUC is set to 0
notebook remains executable under degenerate slices
tiny percent budgets on non-empty groups
review budget count is clipped to a minimum of 1
every non-empty facility-cycle group contributes at least one reviewed row
G. model settings and documented tuning space
formulation summary
Code
pl.DataFrame( {"model": ["classifier","cost_sensitive_classifier","regressor","expected_value","learning_to_rank", ],"training_target": [f"{PayrollCol.Y_ISSUE} on residual records",f"{PayrollCol.Y_ISSUE} with severity-aware weights on residual records",f"{PayrollCol.Y_DOLLAR} on residual records","y_issue + estimated exposure on residual records",f"{PayrollCol.RELEVANCE_GRADE}, grouped by facility x pay period", ],"score_column": [str(ScoreCol.CLASSIFICATION_SCORE),str(ScoreCol.COST_SENSITIVE_CLASSIFICATION_SCORE),str(ScoreCol.REGRESSION_SCORE),str(ScoreCol.EXPECTED_VALUE_SCORE),str(ScoreCol.RANKING_SCORE), ],"business_question": ["Which residual records are most likely to still contain a payroll issue?","Which residual issue records deserve extra weight when severity and dollars matter?","Which residual records imply the largest unresolved dollar impact?","Which residual records combine issue likelihood with financial exposure?","Which residual records deserve the strongest top-of-queue priority?", ], },)
Loading ITables v2.7.3 from the internet...
(need help?)
fair comparison rules
Code
pl.DataFrame( {"rule": ["scoring universe","queue grouping","review budgets","temporal framing","training universe","leakage control","cost-sensitive coverage", ],"applied_setting": ["residual records only for notebook comparison outputs","facility x payroll cycle",", ".join(format_review_budget_pct(k) for k in review_budget_percents),"same employee-cycle temporal split logic for all formulations","primary supervised training rows are residual records only","evaluation labels remain excluded from feature columns","cost-sensitive classifier is included alongside the standard classifier", ], },)
Loading ITables v2.7.3 from the internet...
(need help?)
( ggplot( appendix_score_bucket_calibration, aes(x="bucket_rank", y="avg_gross_gap"), )+ geom_line()+ geom_point()+ theme_minimal()+ labs(x="Score bucket", y="Average gross gap")+ ggtitle("Gross Gap by Final-Score Bucket"))
I. stress-test configurations
These tables support the compact stress-design and benchmark visuals in the main narrative. They are kept here so the main report can stay decision-first while the scenario design remains auditable.
cross-scenario residual sanity summary
Code
scenario_summary_compact
Loading ITables v2.7.3 from the internet...
(need help?)