> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mavera.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-Location Review Analysis

> Pull reviews across all Google Business Profile locations and compare with Mave Agent for cross-location intelligence

## Scenario

You have 15 locations across 3 cities. Customer feedback varies dramatically — the downtown location gets praised for speed, the suburban one gets complaints about parking, the airport location gets dinged for prices. You pull reviews across all locations using batch endpoints, then send the aggregate to Mave for cross-location comparison. The output highlights location-specific strengths, weaknesses, and operational recommendations.

**Flow:** Google `GET /accounts/{id}/locations` → Per-location `GET /locations/{id}/reviews` → Aggregate → Mavera `POST /mave/chat` → Cross-location analysis

## Architecture

```mermaid theme={"dark"}
flowchart LR
    A["Google GET /accounts/{id}/locations"] --> B["Per location: GET /reviews"]
    B --> C["Aggregate by location"]
    C --> D["POST /api/v1/mave/chat"]
    D --> E["Cross-location intelligence report"]
```

## Code

<CodeGroup>
  ```python Python theme={"dark"}
  import os, requests, time
  from collections import defaultdict

  GOOG = os.environ["GOOGLE_ACCESS_TOKEN"]
  ACCT = os.environ["GOOGLE_ACCOUNT_ID"]
  MV = os.environ["MAVERA_API_KEY"]
  GB_BASE = "https://mybusiness.googleapis.com/v4"
  MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
  GB_H = {"Authorization": f"Bearer {GOOG}"}

  # 1. List all locations
  locations = requests.get(f"{GB_BASE}/{ACCT}/locations",
      headers=GB_H,
      params={"pageSize": 100}).json().get("locations", [])

  print(f"Found {len(locations)} locations")

  # 2. Pull reviews for each location
  location_data = []
  for loc in locations:
      loc_id = loc.get("name", "")
      loc_name = loc.get("locationName", loc.get("title", "Unknown"))
      address = loc.get("address", {})
      city = address.get("locality", "Unknown")
      state = address.get("administrativeArea", "")

      reviews = []
      page_token = None
      while len(reviews) < 100:
          params = {"pageSize": 50}
          if page_token:
              params["pageToken"] = page_token
          r = requests.get(f"{GB_BASE}/{loc_id}/reviews",
              headers=GB_H, params=params)
          if r.status_code == 429:
              time.sleep(2)
              continue
          if r.status_code != 200:
              break
          data = r.json()
          reviews.extend(data.get("reviews", []))
          page_token = data.get("nextPageToken")
          if not page_token:
              break
          time.sleep(0.3)

      ratings = [rev.get("starRating", "FIVE") for rev in reviews]
      star_map = {"ONE": 1, "TWO": 2, "THREE": 3, "FOUR": 4, "FIVE": 5}
      numeric_ratings = [star_map.get(r, 3) for r in ratings]
      avg_rating = sum(numeric_ratings) / len(numeric_ratings) if numeric_ratings else 0

      review_texts = []
      for rev in reviews:
          comment = rev.get("comment", "")
          if comment:
              stars = star_map.get(rev.get("starRating", "FIVE"), 5)
              review_texts.append(f"[{stars}★] {comment[:200]}")

      location_data.append({
          "name": loc_name,
          "city": city,
          "state": state,
          "review_count": len(reviews),
          "avg_rating": round(avg_rating, 1),
          "reviews": review_texts,
      })
      time.sleep(0.5)

  # 3. Mave cross-location analysis
  loc_block = "\n\n".join(
      f"## {ld['name']} ({ld['city']}, {ld['state']})\n"
      f"Reviews: {ld['review_count']} | Avg: {ld['avg_rating']}/5\n"
      f"Sample reviews:\n" + "\n".join(ld["reviews"][:5])
      for ld in location_data
  )

  analysis = requests.post("https://app.mavera.io/api/v1/mave/chat",
      headers=MV_H,
      json={"message": f"""Compare customer feedback across these {len(location_data)} business locations.

  {loc_block}

  Produce:
  1. Location ranking (best to worst by customer satisfaction)
  2. Per-location strengths (what each does well)
  3. Per-location weaknesses (what each needs to fix)
  4. Cross-location patterns (issues affecting multiple locations)
  5. Location-specific operational recommendations
  6. Best practices from top-rated locations to apply elsewhere"""}).json()

  print("=== Multi-Location Review Intelligence ===")
  print(analysis.get("content", "")[:2000])
  ```

  ```javascript JavaScript theme={"dark"}
  const GOOG = process.env.GOOGLE_ACCESS_TOKEN;
  const ACCT = process.env.GOOGLE_ACCOUNT_ID;
  const MV = process.env.MAVERA_API_KEY;
  const GB_BASE = "https://mybusiness.googleapis.com/v4";
  const MV_H = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
  const GB_H = { Authorization: `Bearer ${GOOG}` };

  const STAR_MAP = { ONE: 1, TWO: 2, THREE: 3, FOUR: 4, FIVE: 5 };

  // 1. Locations
  const locations = (await fetch(`${GB_BASE}/${ACCT}/locations?pageSize=100`, { headers: GB_H })
    .then((r) => r.json())).locations || [];

  // 2. Reviews per location
  const locationData = [];
  for (const loc of locations) {
    const locId = loc.name || "";
    const locName = loc.locationName || loc.title || "Unknown";
    const city = loc.address?.locality || "Unknown";

    const reviews = [];
    let pageToken = null;
    while (reviews.length < 100) {
      const params = new URLSearchParams({ pageSize: "50" });
      if (pageToken) params.set("pageToken", pageToken);
      const res = await fetch(`${GB_BASE}/${locId}/reviews?${params}`, { headers: GB_H });
      if (res.status === 429) { await new Promise((r) => setTimeout(r, 2000)); continue; }
      if (!res.ok) break;
      const data = await res.json();
      reviews.push(...(data.reviews || []));
      pageToken = data.nextPageToken;
      if (!pageToken) break;
      await new Promise((r) => setTimeout(r, 300));
    }

    const ratings = reviews.map((r) => STAR_MAP[r.starRating] || 3);
    const avg = ratings.length ? +(ratings.reduce((s, v) => s + v, 0) / ratings.length).toFixed(1) : 0;
    const texts = reviews.filter((r) => r.comment)
      .map((r) => `[${STAR_MAP[r.starRating] || 3}★] ${r.comment.slice(0, 200)}`);

    locationData.push({ name: locName, city, reviewCount: reviews.length, avg, reviews: texts });
    await new Promise((r) => setTimeout(r, 500));
  }

  // 3. Mave analysis
  const locBlock = locationData.map((ld) =>
    `## ${ld.name} (${ld.city})\nReviews: ${ld.reviewCount} | Avg: ${ld.avg}/5\n${ld.reviews.slice(0, 5).join("\n")}`
  ).join("\n\n");

  const analysis = await fetch("https://app.mavera.io/api/v1/mave/chat", {
    method: "POST", headers: MV_H,
    body: JSON.stringify({
      message: `Compare feedback across ${locationData.length} locations:\n\n${locBlock}\n\nProduce: 1) Ranking 2) Per-location strengths 3) Weaknesses 4) Cross-location patterns 5) Recommendations 6) Best practices to share`,
    }),
  }).then((r) => r.json());

  console.log("=== Multi-Location Intelligence ===");
  console.log((analysis.content || "").slice(0, 2000));
  ```
</CodeGroup>

## Example Output

```text theme={"dark"}
=== Multi-Location Review Intelligence ===

## Location Ranking
1. Downtown (4.6/5, 234 reviews) — Top performer
2. Midtown (4.3/5, 178 reviews) — Strong with minor issues
3. Airport (3.8/5, 312 reviews) — High volume, lower satisfaction
4. Suburbs (3.5/5, 89 reviews) — Needs attention

## Per-Location Strengths
- **Downtown**: Speed of service (mentioned 45x), friendly staff (38x)
- **Midtown**: Product quality (32x), ambiance (21x)
- **Airport**: Convenience/hours (28x), location (25x)
- **Suburbs**: Parking (22x), family-friendly (15x)

## Per-Location Weaknesses
- **Airport**: Price complaints (67x — "airport markup"), wait times (34x)
- **Suburbs**: Slow service (18x), limited menu (12x)
- **Downtown**: Parking (14x), crowding (11x)

## Cross-Location Pattern
Wait time complaints appear at 3/4 locations — systemic staffing issue.
Recommendation: Implement queue management across all locations.

## Best Practice Transfer
Downtown's staffing model (shift overlap during peak) should be replicated.
Airport needs differentiated pricing communication ("value menu" framing).
```

## Error Handling

<AccordionGroup>
  <Accordion title="Star rating format">Google uses string ratings (`ONE`, `TWO`, etc.), not numbers. The code maps these to 1-5 integers.</Accordion>
  <Accordion title="Pagination with pageToken">Google uses opaque `nextPageToken` cursors. Never cache or reuse tokens across sessions.</Accordion>
  <Accordion title="OAuth token refresh">Access tokens expire in 1 hour. Use a refresh token flow or service account for automated jobs.</Accordion>
</AccordionGroup>
