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: GoogleGET /accounts/{id}/locations → Per-location GET /locations/{id}/reviews → Aggregate → Mavera POST /mave/chat → Cross-location analysis
Architecture
Code
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])
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));
Example Output
=== 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
Star rating format
Star rating format
Google uses string ratings (
ONE, TWO, etc.), not numbers. The code maps these to 1-5 integers.Pagination with pageToken
Pagination with pageToken
Google uses opaque
nextPageToken cursors. Never cache or reuse tokens across sessions.OAuth token refresh
OAuth token refresh
Access tokens expire in 1 hour. Use a refresh token flow or service account for automated jobs.