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

# Review Excerpt Sentiment

> Quick sentiment classification of Yelp review excerpts using Mavera Chat

## Scenario

You need a quick sentiment read on a specific business — is the trend positive or negative? Yelp's API returns up to 3 review excerpts per business (full reviews require scraping, which violates ToS). You pull these excerpts and send them to Mavera Chat for rapid sentiment classification. It's a fast signal — not deep analysis — useful for screening competitors or monitoring your own listings.

**Flow:** Yelp `GET /businesses/{id}/reviews` → Up to 3 excerpts → Mavera `POST /responses` → Quick sentiment classification

## Architecture

```mermaid theme={"dark"}
flowchart LR
    A["Yelp GET /businesses/{id}/reviews"] --> B["Up to 3 review excerpts"]
    B --> C["POST /api/v1/responses"]
    C --> D["Quick sentiment summary"]
```

## Code

<CodeGroup>
  ```python Python theme={"dark"}
  import os, requests, json
  from openai import OpenAI

  YELP = os.environ["YELP_API_KEY"]
  MV = os.environ["MAVERA_API_KEY"]
  YELP_H = {"Authorization": f"Bearer {YELP}"}

  # 1. Get business details + reviews
  BUSINESS_IDS = [
      "houndstooth-coffee-austin",
      "fleet-coffee-austin",
      "merit-coffee-austin",
  ]

  mavera = OpenAI(api_key=MV, base_url="https://app.mavera.io/api/v1")

  results = []
  for biz_id in BUSINESS_IDS:
      # Business details
      biz = requests.get(f"https://api.yelp.com/v3/businesses/{biz_id}",
          headers=YELP_H).json()
      name = biz.get("name", biz_id)
      rating = biz.get("rating", 0)
      review_count = biz.get("review_count", 0)

      # Review excerpts (max 3)
      revs = requests.get(f"https://api.yelp.com/v3/businesses/{biz_id}/reviews",
          headers=YELP_H).json().get("reviews", [])

      excerpt_block = "\n".join(
          f"[{r.get('rating',0)}/5] {r.get('text','')[:300]}"
          for r in revs
      )

      # 2. Quick sentiment via Mavera Chat
      response = mavera.responses.create(model="mavera-1",
          input=[{"role": "user", "content": f"""Quick sentiment analysis for {name} ({rating}/5, {review_count} reviews).

  Review excerpts:
  {excerpt_block}

  Return JSON: {{
    "sentiment": "positive|negative|mixed",
    "confidence": 0-1,
    "key_themes": ["theme1", "theme2"],
    "one_line_summary": "..."
  }}"""}],
          extra_body={"response_format": {"type": "json_object"}})

      parsed = json.loads(response.output[0].content[0].text)
      results.append({"name": name, "rating": rating, "reviews": review_count, **parsed})
      print(f"{name}: {parsed.get('sentiment','')} ({parsed.get('confidence',0):.0%}) — {parsed.get('one_line_summary','')}")

  # 3. Summary
  print(f"\n=== Sentiment Summary ({len(results)} businesses) ===")
  for r in results:
      emoji = {"positive": "↑", "negative": "↓", "mixed": "→"}.get(r.get("sentiment", ""), "?")
      print(f"  {emoji} {r['name']}: {r['rating']}/5, {r.get('sentiment','?')}, themes: {', '.join(r.get('key_themes', []))}")
  ```

  ```javascript JavaScript theme={"dark"}
  const OpenAI = require("openai").default;
  const YELP = process.env.YELP_API_KEY;
  const MV = process.env.MAVERA_API_KEY;
  const YELP_H = { Authorization: `Bearer ${YELP}` };

  const BUSINESS_IDS = ["houndstooth-coffee-austin", "fleet-coffee-austin", "merit-coffee-austin"];
  const mavera = new OpenAI({ apiKey: MV, baseURL: "https://app.mavera.io/api/v1" });

  const results = [];
  for (const bizId of BUSINESS_IDS) {
    const biz = await fetch(`https://api.yelp.com/v3/businesses/${bizId}`, { headers: YELP_H })
      .then((r) => r.json());
    const revs = await fetch(`https://api.yelp.com/v3/businesses/${bizId}/reviews`, { headers: YELP_H })
      .then((r) => r.json()).then((d) => d.reviews || []);

    const excerpts = revs.map((r) => `[${r.rating}/5] ${(r.text || "").slice(0, 300)}`).join("\n");

    const response = await mavera.responses.create({
      model: "mavera-1",
      input: [{ role: "user", content: `Sentiment for ${biz.name} (${biz.rating}/5, ${biz.review_count} reviews):\n\n${excerpts}\n\nReturn JSON: {sentiment, confidence, key_themes, one_line_summary}` }],
      extra_body: { response_format: { type: "json_object" } },
    });

    const parsed = JSON.parse(response.output[0].content[0].text);
    results.push({ name: biz.name, rating: biz.rating, ...parsed });
    console.log(`${biz.name}: ${parsed.sentiment} (${(parsed.confidence * 100).toFixed(0)}%) — ${parsed.one_line_summary}`);
  }

  console.log(`\n=== Summary (${results.length}) ===`);
  results.forEach((r) => {
    const arrow = { positive: "↑", negative: "↓", mixed: "→" }[r.sentiment] || "?";
    console.log(`  ${arrow} ${r.name}: ${r.rating}/5, ${r.sentiment}, themes: ${(r.key_themes || []).join(", ")}`);
  });
  ```
</CodeGroup>

## Example Output

```json theme={"dark"}
{
  "businesses_analyzed": 3,
  "results": [
    {
      "name": "Houndstooth Coffee",
      "rating": 4.5,
      "sentiment": "positive",
      "confidence": 0.92,
      "key_themes": ["espresso quality", "minimalist design", "knowledgeable staff"],
      "one_line_summary": "Consistently praised for exceptional espresso and curated ambiance."
    },
    {
      "name": "Fleet Coffee",
      "rating": 4.0,
      "sentiment": "mixed",
      "confidence": 0.71,
      "key_themes": ["outdoor seating", "food truck partnerships", "parking challenges"],
      "one_line_summary": "Loved for the outdoor vibe, dinged for limited parking and inconsistent hours."
    },
    {
      "name": "Merit Coffee",
      "rating": 4.5,
      "sentiment": "positive",
      "confidence": 0.88,
      "key_themes": ["single-origin beans", "pour-over quality", "friendly baristas"],
      "one_line_summary": "Strong loyal following for pour-over enthusiasts; consistently high marks."
    }
  ]
}
```

## Error Handling

<AccordionGroup>
  <Accordion title="Only 3 review excerpts">Yelp's API returns a maximum of 3 review excerpts per business. This is a ToS limitation — do not scrape for more. Use the excerpts as a directional signal only.</Accordion>
  <Accordion title="Business ID format">Yelp uses URL-slug-style IDs (e.g., `houndstooth-coffee-austin`). Find them via business search or from Yelp page URLs.</Accordion>
  <Accordion title="Sentiment confidence calibration">With only 3 excerpts, confidence should be interpreted loosely. Flag any result with confidence below 0.6 as "insufficient data."</Accordion>
</AccordionGroup>
