# Branch Hours Policy Exception Audit (`practical_ai_lab_jp/pal-maps-branch-hours-policy-exception-audit`) Actor

Compare an authorized branch hours export with your own corporate hours and holiday policy. Reports schedule mismatches and missing confirmations from supplied data only.

- **URL**: https://apify.com/practical_ai_lab_jp/pal-maps-branch-hours-policy-exception-audit.md
- **Developed by:** [Practical AI Lab](https://apify.com/practical_ai_lab_jp) (community)
- **Categories:** Lead generation, SEO tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 branch auditeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Branch Hours Policy Exception Audit

Compare an **authorized branch hours export you already hold** against **your own corporate hours and holiday policy**. The Actor lists branches whose schedules disagree with policy and branches that lack a recent confirmation.

It works only on the data you paste in. It does not scrape Google Maps or any other site, makes no network requests, and never claims to know a branch's live opening hours.

### What it checks

| Check | Codes |
| --- | --- |
| Weekly hours differ from policy (with optional minute tolerance and regional overrides) | `WEEKLY_HOURS_MISMATCH`, `MISSING_DAY_HOURS`, `UNPARSEABLE_HOURS` |
| Holiday handling differs from policy | `HOLIDAY_OPEN_POLICY_CLOSED`, `HOLIDAY_CLOSED_POLICY_OPEN`, `HOLIDAY_HOURS_MISMATCH`, `MISSING_HOLIDAY_ENTRY` |
| Branch confirmation is absent, stale, invalid or dated in the future | `MISSING_CONFIRMATION`, `STALE_CONFIRMATION`, `INVALID_CONFIRMATION_DATE`, `FUTURE_CONFIRMATION_DATE` |
| Export hygiene | `DUPLICATE_BRANCH_ID`, `UNKNOWN_DAY_KEY`, `INVALID_SPECIAL_DATE` |

Each finding has a severity (`high`, `medium`, `low`), the policy value, the exported value and the largest time difference in minutes where that applies.

### Input

Copy the prefilled input in the Console as a starting point. Main fields:

- `policy` (required): `weeklyHours`, optional `regionalWeeklyHours`, and `holidays` (each with `date`, `name`, `hours`, optional `regions`).
- `branches`: JSON list with `branchId`, `name`, `region`, `hours`, `specialHours`, `confirmedOn`.
- `branchesCsv`: optional CSV with columns `branch_id`, `branch_name`, `region`, `day`, `hours`, `confirmed_on`. The `day` column holds a weekday (`Mon`, `monday`) or a `YYYY-MM-DD` holiday date. CSV and JSON branches are merged.
- `asOfDate`, `toleranceMinutes`, `maxConfirmationAgeDays`, `requireConfirmation`, `maxFindingsPerBranch`.

Accepted hours text: `09:00-18:00`, `9am-5:30pm`, `09:00-12:00,13:00-17:00`, `22:00-02:00` (closes after midnight), `closed`, `24h`.

Region names are matched case-insensitively. A branch with no region only gets the base weekly policy and holidays that are not region-limited.

### Output

The default dataset contains two record types:

- `branch_summary`: one per branch, with status (`clean` or `exceptions`), counts by severity, and the number of weekdays and holidays checked.
- `finding`: one per exception, capped by `maxFindingsPerBranch`. The summary keeps the true total.

A `SUMMARY` record in the key-value store holds run totals and counts per code.

### Pricing event

The code emits one `branch-audited` event per branch after the whole audit has finished and results are stored. Runs that fail on input validation or crash before completion emit no event. No payable plan is configured in this repository; pricing is set separately by the owner.

### Limitations and sample-size caveats

- **Not live hours.** Results describe the consistency of your export against your policy at the reference date. A branch listed as consistent may still be wrong in reality.
- **Garbage in, garbage out.** If the export is outdated or came from an unreliable source, matching policy proves nothing. Confirmation dates are only as good as the dates you supply.
- **Past holidays** are compared only when the export contains an entry for them; missing entries are flagged only for holidays on or after `asOfDate`.
- **Time zones, daylight saving and public-holiday calendars are not modelled.** Every time is compared as written. Holiday lists are exactly what you supply.
- **Overnight ranges** are read as closing after midnight; a Monday closing at 02:00 is not carried into Tuesday's checks.
- **Duplicate CSV rows** for the same branch and day overwrite each other; the last row wins.
- **Small samples.** With a handful of branches, exception rates are anecdotes, not trends. Rates should not be extrapolated to a wider network.
- Limits: 5,000 branches, 2 million CSV characters, 100,000 CSV rows per run.
- Only English weekday names and `am`/`pm` or 24-hour times are understood.

### Local tests

The unit tests run offline with the standard library only:

```
python -m unittest discover -s tests
```

# Actor input Schema

## `branches` (type: `array`):

List of branch objects: branchId, name, region, hours (weekday to hours text), specialHours (YYYY-MM-DD to hours text) and confirmedOn (YYYY-MM-DD). Hours text examples: 09:00-18:00, 9am-5pm, 09:00-12:00,13:00-17:00, closed, 24h.

## `branchesCsv` (type: `string`):

Optional CSV text with header branch_id,branch_name,region,day,hours,confirmed_on. The day column holds a weekday name or a YYYY-MM-DD holiday date. Rows are merged with the JSON list above.

## `policy` (type: `object`):

weeklyHours (weekday to hours text), optional regionalWeeklyHours (region to partial weekday map) and holidays (list of date, name, hours, optional regions).

## `asOfDate` (type: `string`):

YYYY-MM-DD. Used for confirmation age and to decide which holidays are still upcoming. Leave empty to use today (UTC).

## `toleranceMinutes` (type: `integer`):

Opening or closing times within this many minutes of policy are treated as matching.

## `maxConfirmationAgeDays` (type: `integer`):

Branch confirmations older than this are flagged as stale.

## `requireConfirmation` (type: `boolean`):

Flag branches that have no confirmedOn date.

## `maxFindingsPerBranch` (type: `integer`):

Caps reported findings per branch. The branch summary still shows the true count.

## Actor input object example

```json
{
  "branches": [
    {
      "branchId": "BR-001",
      "name": "Central Branch",
      "region": "north",
      "confirmedOn": "2026-10-20",
      "hours": {
        "monday": "09:00-18:00",
        "tuesday": "09:00-18:00",
        "wednesday": "09:00-18:00",
        "thursday": "09:00-18:00",
        "friday": "09:00-18:00",
        "saturday": "10:00-16:00",
        "sunday": "closed"
      },
      "specialHours": {
        "2026-12-25": "closed",
        "2026-12-31": "09:00-15:00"
      }
    },
    {
      "branchId": "BR-002",
      "name": "Harbor Branch",
      "region": "south",
      "confirmedOn": "2026-05-02",
      "hours": {
        "monday": "09:00-18:00",
        "tuesday": "09:00-17:00",
        "wednesday": "09:00-18:00",
        "thursday": "09:00-18:00",
        "friday": "09:00-18:00",
        "saturday": "10:00-16:00",
        "sunday": "10:00-14:00"
      },
      "specialHours": {
        "2026-12-25": "10:00-14:00"
      }
    },
    {
      "branchId": "BR-003",
      "name": "Airport Kiosk",
      "region": "south",
      "hours": {
        "monday": "09:00-18:00",
        "tuesday": "09:00-18:00",
        "wednesday": "09:00-18:00",
        "thursday": "09:00-18:00",
        "friday": "09:00-18:00",
        "saturday": "10:00-16:00"
      }
    }
  ],
  "policy": {
    "weeklyHours": {
      "monday": "09:00-18:00",
      "tuesday": "09:00-18:00",
      "wednesday": "09:00-18:00",
      "thursday": "09:00-18:00",
      "friday": "09:00-18:00",
      "saturday": "10:00-16:00",
      "sunday": "closed"
    },
    "regionalWeeklyHours": {},
    "holidays": [
      {
        "date": "2026-12-25",
        "name": "Year-end closure",
        "hours": "closed"
      },
      {
        "date": "2026-12-31",
        "name": "Short trading day",
        "hours": "09:00-15:00"
      }
    ]
  },
  "asOfDate": "2026-11-05",
  "toleranceMinutes": 0,
  "maxConfirmationAgeDays": 90,
  "requireConfirmation": true,
  "maxFindingsPerBranch": 50
}
```

# Actor output Schema

## `results` (type: `string`):

Download rows from the latest run in JSON/CSV.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "branches": [
        {
            "branchId": "BR-001",
            "name": "Central Branch",
            "region": "north",
            "confirmedOn": "2026-10-20",
            "hours": {
                "monday": "09:00-18:00",
                "tuesday": "09:00-18:00",
                "wednesday": "09:00-18:00",
                "thursday": "09:00-18:00",
                "friday": "09:00-18:00",
                "saturday": "10:00-16:00",
                "sunday": "closed"
            },
            "specialHours": {
                "2026-12-25": "closed",
                "2026-12-31": "09:00-15:00"
            }
        },
        {
            "branchId": "BR-002",
            "name": "Harbor Branch",
            "region": "south",
            "confirmedOn": "2026-05-02",
            "hours": {
                "monday": "09:00-18:00",
                "tuesday": "09:00-17:00",
                "wednesday": "09:00-18:00",
                "thursday": "09:00-18:00",
                "friday": "09:00-18:00",
                "saturday": "10:00-16:00",
                "sunday": "10:00-14:00"
            },
            "specialHours": {
                "2026-12-25": "10:00-14:00"
            }
        },
        {
            "branchId": "BR-003",
            "name": "Airport Kiosk",
            "region": "south",
            "hours": {
                "monday": "09:00-18:00",
                "tuesday": "09:00-18:00",
                "wednesday": "09:00-18:00",
                "thursday": "09:00-18:00",
                "friday": "09:00-18:00",
                "saturday": "10:00-16:00"
            }
        }
    ],
    "policy": {
        "weeklyHours": {
            "monday": "09:00-18:00",
            "tuesday": "09:00-18:00",
            "wednesday": "09:00-18:00",
            "thursday": "09:00-18:00",
            "friday": "09:00-18:00",
            "saturday": "10:00-16:00",
            "sunday": "closed"
        },
        "regionalWeeklyHours": {},
        "holidays": [
            {
                "date": "2026-12-25",
                "name": "Year-end closure",
                "hours": "closed"
            },
            {
                "date": "2026-12-31",
                "name": "Short trading day",
                "hours": "09:00-15:00"
            }
        ]
    },
    "asOfDate": "2026-11-05"
};

// Run the Actor and wait for it to finish
const run = await client.actor("practical_ai_lab_jp/pal-maps-branch-hours-policy-exception-audit").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "branches": [
        {
            "branchId": "BR-001",
            "name": "Central Branch",
            "region": "north",
            "confirmedOn": "2026-10-20",
            "hours": {
                "monday": "09:00-18:00",
                "tuesday": "09:00-18:00",
                "wednesday": "09:00-18:00",
                "thursday": "09:00-18:00",
                "friday": "09:00-18:00",
                "saturday": "10:00-16:00",
                "sunday": "closed",
            },
            "specialHours": {
                "2026-12-25": "closed",
                "2026-12-31": "09:00-15:00",
            },
        },
        {
            "branchId": "BR-002",
            "name": "Harbor Branch",
            "region": "south",
            "confirmedOn": "2026-05-02",
            "hours": {
                "monday": "09:00-18:00",
                "tuesday": "09:00-17:00",
                "wednesday": "09:00-18:00",
                "thursday": "09:00-18:00",
                "friday": "09:00-18:00",
                "saturday": "10:00-16:00",
                "sunday": "10:00-14:00",
            },
            "specialHours": { "2026-12-25": "10:00-14:00" },
        },
        {
            "branchId": "BR-003",
            "name": "Airport Kiosk",
            "region": "south",
            "hours": {
                "monday": "09:00-18:00",
                "tuesday": "09:00-18:00",
                "wednesday": "09:00-18:00",
                "thursday": "09:00-18:00",
                "friday": "09:00-18:00",
                "saturday": "10:00-16:00",
            },
        },
    ],
    "policy": {
        "weeklyHours": {
            "monday": "09:00-18:00",
            "tuesday": "09:00-18:00",
            "wednesday": "09:00-18:00",
            "thursday": "09:00-18:00",
            "friday": "09:00-18:00",
            "saturday": "10:00-16:00",
            "sunday": "closed",
        },
        "regionalWeeklyHours": {},
        "holidays": [
            {
                "date": "2026-12-25",
                "name": "Year-end closure",
                "hours": "closed",
            },
            {
                "date": "2026-12-31",
                "name": "Short trading day",
                "hours": "09:00-15:00",
            },
        ],
    },
    "asOfDate": "2026-11-05",
}

# Run the Actor and wait for it to finish
run = client.actor("practical_ai_lab_jp/pal-maps-branch-hours-policy-exception-audit").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "branches": [
    {
      "branchId": "BR-001",
      "name": "Central Branch",
      "region": "north",
      "confirmedOn": "2026-10-20",
      "hours": {
        "monday": "09:00-18:00",
        "tuesday": "09:00-18:00",
        "wednesday": "09:00-18:00",
        "thursday": "09:00-18:00",
        "friday": "09:00-18:00",
        "saturday": "10:00-16:00",
        "sunday": "closed"
      },
      "specialHours": {
        "2026-12-25": "closed",
        "2026-12-31": "09:00-15:00"
      }
    },
    {
      "branchId": "BR-002",
      "name": "Harbor Branch",
      "region": "south",
      "confirmedOn": "2026-05-02",
      "hours": {
        "monday": "09:00-18:00",
        "tuesday": "09:00-17:00",
        "wednesday": "09:00-18:00",
        "thursday": "09:00-18:00",
        "friday": "09:00-18:00",
        "saturday": "10:00-16:00",
        "sunday": "10:00-14:00"
      },
      "specialHours": {
        "2026-12-25": "10:00-14:00"
      }
    },
    {
      "branchId": "BR-003",
      "name": "Airport Kiosk",
      "region": "south",
      "hours": {
        "monday": "09:00-18:00",
        "tuesday": "09:00-18:00",
        "wednesday": "09:00-18:00",
        "thursday": "09:00-18:00",
        "friday": "09:00-18:00",
        "saturday": "10:00-16:00"
      }
    }
  ],
  "policy": {
    "weeklyHours": {
      "monday": "09:00-18:00",
      "tuesday": "09:00-18:00",
      "wednesday": "09:00-18:00",
      "thursday": "09:00-18:00",
      "friday": "09:00-18:00",
      "saturday": "10:00-16:00",
      "sunday": "closed"
    },
    "regionalWeeklyHours": {},
    "holidays": [
      {
        "date": "2026-12-25",
        "name": "Year-end closure",
        "hours": "closed"
      },
      {
        "date": "2026-12-31",
        "name": "Short trading day",
        "hours": "09:00-15:00"
      }
    ]
  },
  "asOfDate": "2026-11-05"
}' |
apify call practical_ai_lab_jp/pal-maps-branch-hours-policy-exception-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,practical_ai_lab_jp/pal-maps-branch-hours-policy-exception-audit"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/d06PgqzoSzEOYSaT6/builds/oiS2SNLJux3AhvHbT/openapi.json
