# Maps Reviews Issue Matrix by Location (`practical_ai_lab_jp/pal-maps-reviews-issue-matrix`) Actor

Compare issue-topic prevalence across store locations from review datasets you already hold. Aggregate output only.

- **URL**: https://apify.com/practical_ai_lab_jp/pal-maps-reviews-issue-matrix.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 matrix analyses

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

## Google Maps Reviews Analysis by Location | PAL

Compare how often issue topics (waiting time, cleanliness, pricing, order mistakes and more) appear in the reviews of each of your store locations, using review data you already hold. The output is aggregate only: counts, prevalence, confidence intervals and a peer comparison per location and topic.

**Published Apify Actor.** Runs on the public Store with review data supplied by the customer. The Actor never scrapes Google Maps or obtains reviews automatically. Review sample results before running large datasets.

### What it does

Use this Google Maps reviews analysis tool with exported reviews you already have rights to process. Compare waiting time, cleanliness, pricing and other operational issues across multiple business locations. No login, Google Maps scraping or reviewer-identity output.

- Reads review rows from the `reviews` input array and/or from datasets in your own Apify account (`datasetIds`).
- Matches each review against a keyword lexicon of ten built-in topics plus optional custom topics.
- Per location and topic it reports mentions, prevalence, a 95% Wilson interval, the pooled prevalence of all other locations, the difference in percentage points, a two-proportion z-test and a Benjamini-Hochberg adjusted q-value across all tested cells.
- Flags a cell as `higher_than_peers` or `lower_than_peers` only when the q-value is below your threshold and the gap is at least your minimum number of points.
- Stores a summary (top findings, a location by topic prevalence matrix, input statistics, limitations) in the key-value store under `OUTPUT`.

### What it does not do

- It never contacts Google Maps or any review site. You supply existing data that you are allowed to use.
- It never stores review text, reviewer names, profile links or any field other than the location name. Those fields are read only for matching and then dropped.

### Privacy controls

- Locations with fewer than `minReviewsPerLocation` analyzed reviews (floor of 5) are excluded.
- Cells with 1 to `minCellCount - 1` mentions are suppressed (null values, status `suppressed_small_cell`).
- Location names appear in the output, so only use names of business locations.

### Sample input

```json
{
    "reviews": [
        {"location": "Harbor Cafe - North", "text": "Long wait at the counter, but the coffee was great.", "stars": 3},
        {"location": "Harbor Cafe - North", "text": "Waited 30 minutes for a simple order. Slow service.", "stars": 2},
        {"location": "Harbor Cafe - North", "text": "Friendly staff and clean tables.", "stars": 5},
        {"location": "Harbor Cafe - North", "text": "Wrong order again and the line was very slow.", "stars": 2},
        {"location": "Harbor Cafe - North", "text": "Nice place, a bit pricey.", "stars": 4},
        {"location": "Harbor Cafe - South", "text": "Quick service and lovely staff.", "stars": 5},
        {"location": "Harbor Cafe - South", "text": "Tables were dirty and the restroom smelled.", "stars": 2},
        {"location": "Harbor Cafe - South", "text": "Great coffee, fair price.", "stars": 5},
        {"location": "Harbor Cafe - South", "text": "Very noisy inside, hard to talk.", "stars": 3},
        {"location": "Harbor Cafe - South", "text": "Dirty floor but fast and polite.", "stars": 3}
    ],
    "topics": ["wait_time", "cleanliness", "price_value", "noise_crowding"],
    "minReviewsPerLocation": 5,
    "minCellCount": 2
}
```

The sample only demonstrates the input format. Five reviews per location are far too few for a meaningful comparison.

### Input fields

| Field | Meaning |
| --- | --- |
| `reviews` | Inline array of review objects. |
| `datasetIds` | Up to 10 dataset IDs from your own account. |
| `locationField`, `textField`, `ratingField` | Override field names. Common names are tried automatically. |
| `topics` | Subset of built-in topics (default all): `wait_time`, `staff_attitude`, `cleanliness`, `price_value`, `product_quality`, `order_accuracy`, `noise_crowding`, `parking_access`, `safety_hygiene`, `billing_issues`. |
| `customTopics` | Object of topic name to keywords. A trailing `*` matches any word ending. |
| `maxRating` | Analyze only reviews at or below this star rating. |
| `minReviewsPerLocation`, `minCellCount` | Privacy and reliability thresholds. |
| `minDifferencePoints`, `fdrAlpha` | Flagging thresholds. |
| `maxReviews`, `maxLocations` | Safety caps (defaults 50000 and 200). |

### Output

One dataset row per eligible location and topic. Key fields: `location`, `topic`, `reviewsInLocation`, `mentionCount`, `prevalencePct`, `ciLowPct`, `ciHighPct`, `peerPrevalencePct`, `differencePoints`, `pValue`, `qValue`, `direction`, `status`, `sampleNote`.

### Limitations and sample-size caveats

- Keyword matching is not sentiment analysis. "No wait at all" still counts as a wait mention. Sarcasm and negation are not handled.
- The built-in lexicon is English only. Use `customTopics` for other languages.
- Prevalence is the share of analyzed reviews that mention a topic, not the share of customers affected. Reviewers are a self-selected group.
- Statistical tests need adequate samples. As a rough guide, aim for at least 100 reviews per location; below that rows are marked `small_sample` and intervals are wide.
- The peer baseline is the pooled set of other eligible locations, so one very large location can dominate it.
- Tests across many cells can still yield false positives even with false discovery control. Treat flags as leads to investigate.
- Duplicate reviews in your input are not removed and will inflate counts.

### Billing

The code references one event, `matrix-analysis`, triggered once after output has been stored successfully. Failed or invalid runs fail before any charge. The Actor uses Apify pay-per-event pricing; the latest verified rate on 2026-10-30 is $0.02 per completed analysis, plus a small Actor start event ($0.00005). The live Pricing tab is authoritative for current charges.

### Development

Tests are offline and use only the standard library: `python -m unittest discover -s tests -t .` from the project root.

# Actor input Schema

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

Array of objects, each with a location name, review text and optionally a star rating. Reviewer names and any other fields are ignored and never stored.

## `datasetIds` (type: `array`):

IDs of datasets in your own Apify account that already contain review rows. Up to 10 datasets.

## `locationField` (type: `string`):

Field holding the store or branch name. Common names such as location, placeName, title and name are tried automatically.

## `textField` (type: `string`):

Field holding the review text. Common names such as text, reviewText and snippet are tried automatically.

## `ratingField` (type: `string`):

Field holding a 0-5 star rating. Only needed when you set a maximum rating filter.

## `topics` (type: `array`):

Subset of built-in topics: wait_time, staff_attitude, cleanliness, price_value, product_quality, order_accuracy, noise_crowding, parking_access, safety_hygiene, billing_issues. Leave empty to use all.

## `customTopics` (type: `object`):

Object mapping a topic name (lowercase letters, digits, underscore) to a list of keywords or phrases. A trailing \* matches any word ending, e.g. "refund\*".

## `maxRating` (type: `number`):

When set, only reviews with a rating at or below this value are analyzed. Reviews without a rating are skipped.

## `minReviewsPerLocation` (type: `integer`):

For real comparison use at least 30 reviews per location (100+ recommended); sample preview uses 5 per location and will be flagged small_sample.

## `minCellCount` (type: `integer`):

Cells with between 1 and this value minus 1 mentions are suppressed to avoid exposing single reviews.

## `minDifferencePoints` (type: `number`):

A difference is flagged only if it is statistically clear and at least this many percentage points.

## `fdrAlpha` (type: `number`):

Benjamini-Hochberg threshold applied across all tested location-topic cells.

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

Safety cap on rows read across all sources.

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

Rows of further new locations beyond this cap are skipped and counted.

## Actor input object example

```json
{
  "reviews": [
    {
      "location": "Harbor Cafe - North",
      "text": "Long wait at the counter, but the coffee was great.",
      "stars": 3
    },
    {
      "location": "Harbor Cafe - North",
      "text": "Waited 30 minutes for a simple order. Slow service.",
      "stars": 2
    },
    {
      "location": "Harbor Cafe - North",
      "text": "Friendly staff and clean tables.",
      "stars": 5
    },
    {
      "location": "Harbor Cafe - North",
      "text": "Wrong order again and the line was very slow.",
      "stars": 2
    },
    {
      "location": "Harbor Cafe - North",
      "text": "Nice place, a bit pricey.",
      "stars": 4
    },
    {
      "location": "Harbor Cafe - South",
      "text": "Quick service and lovely staff.",
      "stars": 5
    },
    {
      "location": "Harbor Cafe - South",
      "text": "Tables were dirty and the restroom smelled.",
      "stars": 2
    },
    {
      "location": "Harbor Cafe - South",
      "text": "Great coffee, fair price.",
      "stars": 5
    },
    {
      "location": "Harbor Cafe - South",
      "text": "Very noisy inside, hard to talk.",
      "stars": 3
    },
    {
      "location": "Harbor Cafe - South",
      "text": "Dirty floor but fast and polite.",
      "stars": 3
    }
  ],
  "customTopics": {
    "loyalty_program": [
      "loyalty card",
      "points",
      "stamp card"
    ]
  },
  "minReviewsPerLocation": 5,
  "minCellCount": 5,
  "minDifferencePoints": 5,
  "fdrAlpha": 0.05,
  "maxReviews": 50000,
  "maxLocations": 200
}
```

# 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": [
        {
            "location": "Harbor Cafe - North",
            "text": "Long wait at the counter, but the coffee was great.",
            "stars": 3
        },
        {
            "location": "Harbor Cafe - North",
            "text": "Waited 30 minutes for a simple order. Slow service.",
            "stars": 2
        },
        {
            "location": "Harbor Cafe - North",
            "text": "Friendly staff and clean tables.",
            "stars": 5
        },
        {
            "location": "Harbor Cafe - North",
            "text": "Wrong order again and the line was very slow.",
            "stars": 2
        },
        {
            "location": "Harbor Cafe - North",
            "text": "Nice place, a bit pricey.",
            "stars": 4
        },
        {
            "location": "Harbor Cafe - South",
            "text": "Quick service and lovely staff.",
            "stars": 5
        },
        {
            "location": "Harbor Cafe - South",
            "text": "Tables were dirty and the restroom smelled.",
            "stars": 2
        },
        {
            "location": "Harbor Cafe - South",
            "text": "Great coffee, fair price.",
            "stars": 5
        },
        {
            "location": "Harbor Cafe - South",
            "text": "Very noisy inside, hard to talk.",
            "stars": 3
        },
        {
            "location": "Harbor Cafe - South",
            "text": "Dirty floor but fast and polite.",
            "stars": 3
        }
    ],
    "customTopics": {
        "loyalty_program": [
            "loyalty card",
            "points",
            "stamp card"
        ]
    },
    "minReviewsPerLocation": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("practical_ai_lab_jp/pal-maps-reviews-issue-matrix").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": [
        {
            "location": "Harbor Cafe - North",
            "text": "Long wait at the counter, but the coffee was great.",
            "stars": 3,
        },
        {
            "location": "Harbor Cafe - North",
            "text": "Waited 30 minutes for a simple order. Slow service.",
            "stars": 2,
        },
        {
            "location": "Harbor Cafe - North",
            "text": "Friendly staff and clean tables.",
            "stars": 5,
        },
        {
            "location": "Harbor Cafe - North",
            "text": "Wrong order again and the line was very slow.",
            "stars": 2,
        },
        {
            "location": "Harbor Cafe - North",
            "text": "Nice place, a bit pricey.",
            "stars": 4,
        },
        {
            "location": "Harbor Cafe - South",
            "text": "Quick service and lovely staff.",
            "stars": 5,
        },
        {
            "location": "Harbor Cafe - South",
            "text": "Tables were dirty and the restroom smelled.",
            "stars": 2,
        },
        {
            "location": "Harbor Cafe - South",
            "text": "Great coffee, fair price.",
            "stars": 5,
        },
        {
            "location": "Harbor Cafe - South",
            "text": "Very noisy inside, hard to talk.",
            "stars": 3,
        },
        {
            "location": "Harbor Cafe - South",
            "text": "Dirty floor but fast and polite.",
            "stars": 3,
        },
    ],
    "customTopics": { "loyalty_program": [
            "loyalty card",
            "points",
            "stamp card",
        ] },
    "minReviewsPerLocation": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("practical_ai_lab_jp/pal-maps-reviews-issue-matrix").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": [
    {
      "location": "Harbor Cafe - North",
      "text": "Long wait at the counter, but the coffee was great.",
      "stars": 3
    },
    {
      "location": "Harbor Cafe - North",
      "text": "Waited 30 minutes for a simple order. Slow service.",
      "stars": 2
    },
    {
      "location": "Harbor Cafe - North",
      "text": "Friendly staff and clean tables.",
      "stars": 5
    },
    {
      "location": "Harbor Cafe - North",
      "text": "Wrong order again and the line was very slow.",
      "stars": 2
    },
    {
      "location": "Harbor Cafe - North",
      "text": "Nice place, a bit pricey.",
      "stars": 4
    },
    {
      "location": "Harbor Cafe - South",
      "text": "Quick service and lovely staff.",
      "stars": 5
    },
    {
      "location": "Harbor Cafe - South",
      "text": "Tables were dirty and the restroom smelled.",
      "stars": 2
    },
    {
      "location": "Harbor Cafe - South",
      "text": "Great coffee, fair price.",
      "stars": 5
    },
    {
      "location": "Harbor Cafe - South",
      "text": "Very noisy inside, hard to talk.",
      "stars": 3
    },
    {
      "location": "Harbor Cafe - South",
      "text": "Dirty floor but fast and polite.",
      "stars": 3
    }
  ],
  "customTopics": {
    "loyalty_program": [
      "loyalty card",
      "points",
      "stamp card"
    ]
  },
  "minReviewsPerLocation": 5
}' |
apify call practical_ai_lab_jp/pal-maps-reviews-issue-matrix --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-reviews-issue-matrix"
        }
    }
}
```

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/pad2rYrRBojFyPUSy/builds/fbZaw6NYGRBLQTCOn/openapi.json
