# Maps Review Recency and Coverage Audit (`practical_ai_lab_jp/pal-maps-review-recency-audit`) Actor

Audit how fresh and evenly spread your existing review exports are per location. Offline analysis, no scraping, no reviewer identifiers.

- **URL**: https://apify.com/practical_ai_lab_jp/pal-maps-review-recency-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 location 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

## Maps Review Recency and Coverage Audit

Measures how fresh and how evenly spread the reviews in your **existing review export** are, per business location. It answers: which locations have gone quiet, which have thin or bursty review coverage, and where is the sample too small to trust?

This Actor does **not** scrape Google Maps or any other site, makes no network requests, and never reads, stores or returns reviewer names, IDs, profile links or review text. You supply the rows; it returns per-location statistics.

### Who it is for

Teams that already hold review exports (from their own business profiles, licensed data, or prior exports) for many locations and need a quick, repeatable freshness check, for example multi-location operators, agencies reporting to clients, or analysts screening which locations need a review-request campaign.

### Input

Copy-and-paste example:

```json
{
  "reviews": [
    {"placeId": "demo-cafe-1", "locationName": "Demo Cafe", "publishedAt": "2026-09-20", "rating": 5},
    {"placeId": "demo-cafe-1", "locationName": "Demo Cafe", "publishedAt": "2026-08-02", "rating": 4},
    {"placeId": "demo-gym-2", "locationName": "Demo Gym", "publishedAt": "2025-05-11", "rating": 3}
  ],
  "referenceDate": "2026-10-01",
  "freshDays": 90,
  "staleDays": 365,
  "minReviewsForConfidence": 20
}
```

- **Location**: `placeId` (also `place_id`, `locationId`), or `locationName` / `title` plus optional `address`.
- **Date**: `publishedAt`, `publishedAtDate`, `reviewDate`, `date`, `createdAt`, `time`. Accepted formats: ISO dates/timestamps, epoch seconds or milliseconds, `2026/09/01`, `Sep 1, 2026`, and relative text such as `3 months ago` (treated as approximate).
- **Rating** (optional): `rating`, `stars`, `reviewRating`.
- Other fields are ignored.

### Output

One dataset item per location, plus a `SUMMARY` record in the key-value store.

| Field | Meaning |
|---|---|
| `recencyStatus` | `fresh`, `aging`, `stale` (by age of newest review) or `no_usable_dates` |
| `recencyScore` | 0-100 heuristic: 50% newest-review recency, 50% share of the last 12 thirty-day periods with at least one review |
| `sampleConfidence` | `adequate` or `low` (fewer dated reviews than your threshold) |
| `reviewsLast30Days` / `90` / `365` | Counts of dated reviews |
| `monthsWithReviewsLast12`, `coverageRatio12` | Coverage across twelve 30-day periods |
| `longestGapDaysLast365` | Longest review-free stretch in the last 365 days |
| `ratingChange` | Average rating of last 90 days minus days 91-365, only when each side has at least 5 rated reviews |
| `flags` | Plain-language caveats, e.g. `small_sample`, `mostly_approximate_dates`, `concentrated_recent_activity` |

### Pricing

The code emits one `location-audited` event per location that has at least one usable date. No payable plan is configured in this repository. Invalid input, locations without usable dates, and failed runs are not charged.

### Limitations and caveats

- Results describe only the rows you provide. If the export is a partial sample (for example only the most recent N reviews per place), "oldest review", gaps and coverage will be misleading.
- Small samples are unreliable. Locations below your minimum are marked `low` confidence; do not compare their scores with well-sampled locations.
- Without reviewer identifiers, duplicate rows cannot be detected. Duplicated exports inflate counts.
- Relative dates ("3 months ago") are approximated (month = 30 days, year = 365 days) and flagged when they dominate.
- Date-only values are interpreted as UTC calendar days. Rows dated after the reference date are excluded and counted.
- `recencyScore` and `concentrated_recent_activity` are heuristics, not evidence of review manipulation or business quality.
- Rows are limited by `maxReviews` (hard cap 200000) and `maxLocations`; the `SUMMARY` reports anything skipped.
- Rows lacking a location identifier are skipped. Locations identified by name and address are merged case-insensitively, so differently spelled names will be split.

### Local tests

Tests are offline: `python -m unittest discover -s tests`.

# Actor input Schema

## `reviews` (type: `array`):

Existing review export rows you already have the right to analyze. Each row needs a location (placeId, or locationName with optional address) and a date (publishedAt, or similar). Rating is optional. Reviewer names, IDs and review text are ignored and never returned.

## `referenceDate` (type: `string`):

Date that ages are measured from. Leave empty to use today (UTC). Set it to the export date for reproducible results.

## `freshDays` (type: `integer`):

A location is 'fresh' if its newest review is at most this many days old.

## `staleDays` (type: `integer`):

A location is 'stale' if its newest review is older than this. Must exceed the fresh threshold.

## `minReviewsForConfidence` (type: `integer`):

Locations with fewer dated reviews are marked low confidence.

## `maxReviews` (type: `integer`):

Safety cap on rows processed (hard maximum 200000).

## `maxLocations` (type: `integer`):

Safety cap on distinct locations audited.

## Actor input object example

```json
{
  "reviews": [
    {
      "placeId": "demo-cafe-1",
      "locationName": "Demo Cafe",
      "publishedAt": "2026-09-20",
      "rating": 5
    },
    {
      "placeId": "demo-cafe-1",
      "locationName": "Demo Cafe",
      "publishedAt": "2026-08-02",
      "rating": 4
    },
    {
      "placeId": "demo-cafe-1",
      "locationName": "Demo Cafe",
      "publishedAt": "2026-03-15",
      "rating": 4
    },
    {
      "placeId": "demo-gym-2",
      "locationName": "Demo Gym",
      "publishedAt": "2025-05-11",
      "rating": 3
    },
    {
      "placeId": "demo-gym-2",
      "locationName": "Demo Gym",
      "publishedAt": "2025-01-30",
      "rating": 4
    }
  ],
  "referenceDate": "2026-10-01",
  "freshDays": 90,
  "staleDays": 365,
  "minReviewsForConfidence": 20,
  "maxReviews": 50000,
  "maxLocations": 1000
}
```

# 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 = {
    "reviews": [
        {
            "placeId": "demo-cafe-1",
            "locationName": "Demo Cafe",
            "publishedAt": "2026-09-20",
            "rating": 5
        },
        {
            "placeId": "demo-cafe-1",
            "locationName": "Demo Cafe",
            "publishedAt": "2026-08-02",
            "rating": 4
        },
        {
            "placeId": "demo-cafe-1",
            "locationName": "Demo Cafe",
            "publishedAt": "2026-03-15",
            "rating": 4
        },
        {
            "placeId": "demo-gym-2",
            "locationName": "Demo Gym",
            "publishedAt": "2025-05-11",
            "rating": 3
        },
        {
            "placeId": "demo-gym-2",
            "locationName": "Demo Gym",
            "publishedAt": "2025-01-30",
            "rating": 4
        }
    ],
    "referenceDate": "2026-10-01"
};

// Run the Actor and wait for it to finish
const run = await client.actor("practical_ai_lab_jp/pal-maps-review-recency-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 = {
    "reviews": [
        {
            "placeId": "demo-cafe-1",
            "locationName": "Demo Cafe",
            "publishedAt": "2026-09-20",
            "rating": 5,
        },
        {
            "placeId": "demo-cafe-1",
            "locationName": "Demo Cafe",
            "publishedAt": "2026-08-02",
            "rating": 4,
        },
        {
            "placeId": "demo-cafe-1",
            "locationName": "Demo Cafe",
            "publishedAt": "2026-03-15",
            "rating": 4,
        },
        {
            "placeId": "demo-gym-2",
            "locationName": "Demo Gym",
            "publishedAt": "2025-05-11",
            "rating": 3,
        },
        {
            "placeId": "demo-gym-2",
            "locationName": "Demo Gym",
            "publishedAt": "2025-01-30",
            "rating": 4,
        },
    ],
    "referenceDate": "2026-10-01",
}

# Run the Actor and wait for it to finish
run = client.actor("practical_ai_lab_jp/pal-maps-review-recency-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 '{
  "reviews": [
    {
      "placeId": "demo-cafe-1",
      "locationName": "Demo Cafe",
      "publishedAt": "2026-09-20",
      "rating": 5
    },
    {
      "placeId": "demo-cafe-1",
      "locationName": "Demo Cafe",
      "publishedAt": "2026-08-02",
      "rating": 4
    },
    {
      "placeId": "demo-cafe-1",
      "locationName": "Demo Cafe",
      "publishedAt": "2026-03-15",
      "rating": 4
    },
    {
      "placeId": "demo-gym-2",
      "locationName": "Demo Gym",
      "publishedAt": "2025-05-11",
      "rating": 3
    },
    {
      "placeId": "demo-gym-2",
      "locationName": "Demo Gym",
      "publishedAt": "2025-01-30",
      "rating": 4
    }
  ],
  "referenceDate": "2026-10-01"
}' |
apify call practical_ai_lab_jp/pal-maps-review-recency-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-review-recency-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/ZcGHf5KmInroaxsqb/builds/BknHiiQZjW01atPKU/openapi.json
