> ## 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.

# Local Persona Creation

> Create geo-specific Mavera personas from Google Business Profile review signals per location

## Scenario

Customers at your downtown café are different from customers at your suburban location. Downtown draws office workers during lunch; suburbs get families on weekends. You extract review text with location context, identify distinct customer profiles per location, then create geo-specific Mavera personas. These local personas power hyper-targeted marketing for each location.

**Flow:** Google reviews + location data → Identify per-location customer profiles → Mavera `POST /personas` → Geo-specific personas

## Code

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

  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}"}
  STAR_MAP = {"ONE": 1, "TWO": 2, "THREE": 3, "FOUR": 4, "FIVE": 5}

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

  persona_ids = []
  for loc in locations[:8]:
      loc_id = loc.get("name", "")
      loc_name = loc.get("locationName", "Unknown")
      city = loc.get("address", {}).get("locality", "Unknown")
      state = loc.get("address", {}).get("administrativeArea", "")
      category = loc.get("primaryCategory", {}).get("displayName", "Business")

      r = requests.get(f"{GB_BASE}/{loc_id}/reviews",
          headers=GB_H, params={"pageSize": 50})
      if r.status_code != 200:
          continue
      reviews = r.json().get("reviews", [])

      if len(reviews) < 5:
          continue

      # 2. Extract customer signals from review text
      all_text = " ".join(rev.get("comment", "") for rev in reviews).lower()
      signals = {
          "family": len(re.findall(r'\b(family|kids|children|stroller)\b', all_text)),
          "business": len(re.findall(r'\b(meeting|office|work|lunch break|colleague)\b', all_text)),
          "tourist": len(re.findall(r'\b(tourist|visiting|trip|vacation|hotel)\b', all_text)),
          "local_regular": len(re.findall(r'\b(regular|every week|always come|our spot)\b', all_text)),
          "date_night": len(re.findall(r'\b(date|anniversary|romantic|evening)\b', all_text)),
      }
      dominant_segment = max(signals, key=signals.get) if any(signals.values()) else "general"

      avg_rating = sum(STAR_MAP.get(r.get("starRating", "FIVE"), 5) for r in reviews) / len(reviews)

      # 3. Create geo-specific persona
      sample_quotes = [rev.get("comment", "")[:150] for rev in reviews if rev.get("comment")][:3]

      p = requests.post("https://app.mavera.io/api/v1/personas", headers=MV_H, json={
          "name": f"GBP: {loc_name} — {dominant_segment.replace('_', ' ').title()}",
          "description": (
              f"Customer of {loc_name} in {city}, {state}. Category: {category}. "
              f"N={len(reviews)} reviews, avg {avg_rating:.1f}/5. "
              f"Dominant segment: {dominant_segment}. "
              f"Signal scores: {', '.join(f'{k}: {v}' for k, v in sorted(signals.items(), key=lambda x: -x[1])[:3])}. "
              f"Sample: \"{sample_quotes[0][:100]}...\""
          ),
          "demographic": {
              "location": f"{city}, {state}",
              "customer_type": dominant_segment,
          },
          "psychographic": {
              "visit_context": dominant_segment,
              "satisfaction": "high" if avg_rating >= 4 else "mixed" if avg_rating >= 3 else "low",
          },
      }).json()
      persona_ids.append({"id": p["id"], "location": loc_name, "city": city, "segment": dominant_segment})
      print(f"Persona: {p['id']} — {loc_name} ({city}) → {dominant_segment}")
      time.sleep(0.5)

  print(f"\nCreated {len(persona_ids)} local personas")
  ```

  ```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 };

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

  const personaIds = [];
  for (const loc of locations.slice(0, 8)) {
    const locId = loc.name || "";
    const locName = loc.locationName || "Unknown";
    const city = loc.address?.locality || "Unknown";
    const state = loc.address?.administrativeArea || "";

    const res = await fetch(`${GB_BASE}/${locId}/reviews?pageSize=50`, { headers: GB_H });
    if (!res.ok) continue;
    const reviews = (await res.json()).reviews || [];
    if (reviews.length < 5) continue;

    const allText = reviews.map((r) => r.comment || "").join(" ").toLowerCase();
    const signals = {
      family: (allText.match(/\b(family|kids|children)\b/g) || []).length,
      business: (allText.match(/\b(meeting|office|work|lunch break)\b/g) || []).length,
      tourist: (allText.match(/\b(tourist|visiting|trip|vacation)\b/g) || []).length,
      local_regular: (allText.match(/\b(regular|every week|always come)\b/g) || []).length,
    };
    const dominant = Object.entries(signals).sort(([, a], [, b]) => b - a)[0]?.[0] || "general";
    const avg = reviews.reduce((s, r) => s + (STAR_MAP[r.starRating] || 3), 0) / reviews.length;

    const p = await fetch("https://app.mavera.io/api/v1/personas", {
      method: "POST", headers: MV_H,
      body: JSON.stringify({
        name: `GBP: ${locName} — ${dominant.replace(/_/g, " ")}`,
        description: `Customer of ${locName} (${city}). N=${reviews.length}, avg ${avg.toFixed(1)}/5. Segment: ${dominant}.`,
        demographic: { location: `${city}, ${state}`, customer_type: dominant },
        psychographic: { visit_context: dominant },
      }),
    }).then((r) => r.json());
    personaIds.push({ id: p.id, location: locName, city, segment: dominant });
    console.log(`Persona: ${p.id} — ${locName} (${city}) → ${dominant}`);
    await new Promise((r) => setTimeout(r, 500));
  }

  console.log(`\nCreated ${personaIds.length} local personas`);
  ```
</CodeGroup>

## Example Output

```json theme={"dark"}
{
  "personas_created": 5,
  "local_profiles": [
    { "location": "Downtown Café", "city": "Austin", "segment": "business", "avg_rating": 4.5,
      "insight": "Office workers on lunch. Care about speed, Wi-Fi, quiet spaces." },
    { "location": "Suburban Store", "city": "Round Rock", "segment": "family", "avg_rating": 4.1,
      "insight": "Families on weekends. Care about kid-friendliness, parking, portion sizes." },
    { "location": "Airport Kiosk", "city": "Austin", "segment": "tourist", "avg_rating": 3.7,
      "insight": "Travelers in a hurry. Care about grab-and-go, clear pricing, speed." },
    { "location": "University Ave", "city": "Austin", "segment": "local_regular", "avg_rating": 4.6,
      "insight": "Regulars who come weekly. Care about consistency, loyalty, and knowing the staff." }
  ]
}
```

## Error Handling

<AccordionGroup>
  <Accordion title="Review access per location">Each location requires separate review API calls. Batch across locations with delays to stay within 300 req/min.</Accordion>
  <Accordion title="Sparse review text">Some Google reviews are ratings-only with no text. Filter `comment: null` reviews before persona creation.</Accordion>
  <Accordion title="Regex limitations">Simple keyword matching for segments is approximate. For better accuracy, send review text to Mave for classification.</Accordion>
</AccordionGroup>
