audit_period_compliance
Run a multi-day wage-and-hour compliance audit evaluating meal and rest breaks. Returns per-day breakdown and period totals with premium hours owed, days at risk, and flag-count heatmap for payroll review or manager dashboard.
Instructions
Run a wage-and-hour compliance audit across multiple days. For each day, resolves attendance and evaluates meal/rest compliance under the chosen jurisdiction rule pack. Returns per-day breakdown plus period totals: hours of premium owed (meal + rest), days at risk, days with rebuttable-presumption exposure, and a flag-count heatmap. Use this for monthly payroll review, pre-audit triage, or a manager dashboard.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| employeeId | No | Optional employee identifier. Echoed in the report for downstream routing. | |
| jurisdiction | Yes | Jurisdiction rule pack. Currently CA only; more arrive in subsequent minor versions. | |
| days | Yes | One ResolveDayInput per duty date in the audit window. Order is preserved. | |
| waivers | No |
Implementation Reference
- The core handler function 'registerAuditPeriodCompliance' which registers the 'audit_period_compliance' MCP tool. It accepts employeeId, jurisdiction, days (array of ResolveDayInput), and optional waivers. For each day it calls resolveDay() and evaluateBreakCompliance(), then returns a comprehensive audit report including premium hours, days at risk, presumption risk counts, flag heatmaps, and per-day breakdowns.
export function registerAuditPeriodCompliance(server: McpServer): void { server.tool( 'audit_period_compliance', "Run a wage-and-hour compliance audit across multiple days. For each day, resolves attendance and evaluates meal/rest compliance under the chosen jurisdiction rule pack. Returns per-day breakdown plus period totals: hours of premium owed (meal + rest), days at risk, days with rebuttable-presumption exposure, and a flag-count heatmap. Use this for monthly payroll review, pre-audit triage, or a manager dashboard.", { employeeId: z .string() .optional() .describe('Optional employee identifier. Echoed in the report for downstream routing.'), jurisdiction: z .enum(['CA']) .describe('Jurisdiction rule pack. Currently CA only; more arrive in subsequent minor versions.'), days: z .array(ResolveDayInputSchema) .describe('One ResolveDayInput per duty date in the audit window. Order is preserved.'), waivers: z .array( z.object({ applies: z.enum(['first-meal', 'second-meal']), date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(), signed: z.boolean(), fileRef: z.string().optional(), }), ) .optional(), }, async ({ employeeId, jurisdiction, days, waivers = [] }) => { const rules = BREAK_RULE_SETS[jurisdiction]; type PerDay = { date: string; result: DayResult; compliance: BreakComplianceResult; }; const perDay: PerDay[] = days.map((input) => { const result = resolveDay(input); const compliance = evaluateBreakCompliance({ result, rules, waivers }); return { date: input.date, result, compliance }; }); const totalPremiumHours = perDay.reduce( (acc, d) => ({ meal: acc.meal + d.compliance.premiumsOwed.meal, rest: acc.rest + d.compliance.premiumsOwed.rest, }), { meal: 0, rest: 0 }, ); const daysByStatus = perDay.reduce<Record<string, number>>((acc, d) => { acc[d.result.status] = (acc[d.result.status] ?? 0) + 1; return acc; }, {}); const flagCounts = perDay.reduce<Record<string, number>>((acc, d) => { for (const f of d.result.flags) acc[f] = (acc[f] ?? 0) + 1; return acc; }, {}); const highRiskDays = perDay .filter((d) => d.compliance.presumptionRisk === 'high') .map((d) => d.date); const daysAtRisk = perDay.filter((d) => d.compliance.presumptionRisk !== 'low').length; const presumptionRiskCounts = perDay.reduce<Record<string, number>>((acc, d) => { acc[d.compliance.presumptionRisk] = (acc[d.compliance.presumptionRisk] ?? 0) + 1; return acc; }, {}); const waiverIssuesAll = perDay.flatMap((d) => d.compliance.waiverIssues.map((issue) => ({ date: d.date, issue })), ); const totalWorkedMinutes = perDay.reduce((acc, d) => acc + d.result.workedMinutes, 0); const totalOtMinutes = perDay.reduce((acc, d) => acc + d.result.otMinutes, 0); return { content: [ jsonText({ employeeId: employeeId ?? null, jurisdiction, daysAnalysed: days.length, workedHours: +(totalWorkedMinutes / 60).toFixed(2), overtimeHours: +(totalOtMinutes / 60).toFixed(2), totalPremiumHours, daysAtRisk, highRiskDays, presumptionRiskCounts, daysByStatus, flagCounts, waiverIssues: waiverIssuesAll, perDay, }), ], }; }, ); } - src/schemas.ts:59-70 (schema)The 'ResolveDayInputSchema' Zod schema used in the 'days' parameter of the tool. Defines the structure for each day input (date, punches, shift, policy, leave, holiday, weekend).
export const ResolveDayInputSchema = z.object({ date: z .string() .regex(/^\d{4}-\d{2}-\d{2}$/) .describe('Duty date in worksite local wall-clock, YYYY-MM-DD.'), punches: z.array(PunchSchema), shift: ShiftConfigSchema, policy: AttendancePolicySchema.optional(), leave: LeaveDaySchema.nullable().optional(), holiday: z.boolean().optional(), weekend: z.boolean().optional(), }); - Inline Zod schema for the optional 'waivers' parameter (array of objects with applies, date, signed, fileRef).
waivers: z .array( z.object({ applies: z.enum(['first-meal', 'second-meal']), date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(), signed: z.boolean(), fileRef: z.string().optional(), }), ) .optional(), - src/server.ts:16-16 (registration)Import of the tool registration function from the audit-period-compliance module.
import { registerAuditPeriodCompliance } from './tools/audit-period-compliance.js'; - src/server.ts:42-42 (registration)Call to registerAuditPeriodCompliance(server) to wire the tool into the MCP server.
registerAuditPeriodCompliance(server);