quantumbit Copilot commited on
Commit
051660a
·
1 Parent(s): ba9555f

correlation for review and insights

Browse files

Co-authored-by: Copilot <copilot@github.com>

Files changed (2) hide show
  1. .gitignore +3 -1
  2. main.py +214 -0
.gitignore CHANGED
@@ -1,3 +1,5 @@
1
  .env
2
  env
3
- __pycache__
 
 
 
1
  .env
2
  env
3
+ __pycache__
4
+ .vscode
5
+ test*
main.py CHANGED
@@ -1,5 +1,8 @@
1
  from fastapi import FastAPI, Depends, HTTPException, BackgroundTasks
 
 
2
  import requests
 
3
  from sqlalchemy.orm import Session
4
  from database import Base, engine,SessionLocal
5
  from models import User, Log, CtrReport
@@ -12,10 +15,113 @@ from utils import extract_points
12
 
13
 
14
  app = FastAPI()
 
 
15
 
16
  Base.metadata.create_all(bind=engine)
17
 
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  @app.post("/auth/signup")
20
  def signup(data: SignupRequest, db: Session = Depends(get_db)):
21
  existing = db.query(User).filter(User.email == data.email).first()
@@ -283,6 +389,114 @@ def get_few_reviews(
283
  }
284
 
285
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
 
287
  @app.get("/few-insights")
288
  def get_all_insights(
 
1
  from fastapi import FastAPI, Depends, HTTPException, BackgroundTasks
2
+ from pathlib import Path
3
+ import os
4
  import requests
5
+ from dotenv import load_dotenv
6
  from sqlalchemy.orm import Session
7
  from database import Base, engine,SessionLocal
8
  from models import User, Log, CtrReport
 
15
 
16
 
17
  app = FastAPI()
18
+ env_path = Path(__file__).resolve().parent / ".env"
19
+ load_dotenv(env_path)
20
 
21
  Base.metadata.create_all(bind=engine)
22
 
23
 
24
+ def get_representatives(
25
+ texts: list[str],
26
+ eps: float,
27
+ min_samples: int,
28
+ error_label: str
29
+ ) -> list[str]:
30
+ if not texts:
31
+ return []
32
+
33
+ try:
34
+ response = requests.post(
35
+ f"{API_BASE_URL}/get_representatives",
36
+ json={
37
+ "texts": texts,
38
+ "eps": eps,
39
+ "min_samples": min_samples
40
+ },
41
+ timeout=60
42
+ )
43
+ except requests.RequestException as exc:
44
+ raise HTTPException(
45
+ 502,
46
+ f"{error_label} service failed: {exc}"
47
+ )
48
+
49
+ if response.status_code != 200:
50
+ raise HTTPException(
51
+ 502,
52
+ f"{error_label} service error"
53
+ )
54
+
55
+ payload = response.json()
56
+
57
+ return payload.get("representatives", [])
58
+
59
+
60
+ def requesty_chat(prompt: str) -> str:
61
+ api_key = os.getenv("REQUESTY_API_KEY")
62
+ if not api_key:
63
+ raise HTTPException(500, "Requesty API key not configured")
64
+
65
+ base_url = os.getenv(
66
+ "REQUESTY_API_URL",
67
+ "https://router.requesty.ai/v1"
68
+ ).rstrip("/")
69
+
70
+ headers = {
71
+ "Authorization": f"Bearer {api_key}",
72
+ }
73
+
74
+ referer = os.getenv("REQUESTY_HTTP_REFERER")
75
+ title = os.getenv("REQUESTY_X_TITLE")
76
+ if referer:
77
+ headers["HTTP-Referer"] = referer
78
+ if title:
79
+ headers["X-Title"] = title
80
+
81
+ try:
82
+ response = requests.post(
83
+ f"{base_url}/chat/completions",
84
+ headers=headers,
85
+ json={
86
+ "model": "openai/gpt-4o",
87
+ "temperature": 0.2,
88
+ "max_tokens": 256,
89
+ "messages": [
90
+ {
91
+ "role": "system",
92
+ "content": (
93
+ "You find correlations between insights and reviews. "
94
+ "Return 3-6 short numbered points, each under 20 words."
95
+ )
96
+ },
97
+ {"role": "user", "content": prompt}
98
+ ]
99
+ },
100
+ timeout=60
101
+ )
102
+ except requests.RequestException as exc:
103
+ raise HTTPException(502, f"Requesty service failed: {exc}")
104
+
105
+ if response.status_code != 200:
106
+ detail = response.text.strip()
107
+ if detail:
108
+ raise HTTPException(
109
+ 502,
110
+ f"Requesty service error: {detail}"
111
+ )
112
+ raise HTTPException(502, "Requesty service error")
113
+
114
+ payload = response.json()
115
+ choices = payload.get("choices", [])
116
+ if not choices:
117
+ raise HTTPException(502, "Requesty service returned no choices")
118
+
119
+ message = choices[0].get("message", {})
120
+ content = message.get("content", "")
121
+
122
+ return content.strip()
123
+
124
+
125
  @app.post("/auth/signup")
126
  def signup(data: SignupRequest, db: Session = Depends(get_db)):
127
  existing = db.query(User).filter(User.email == data.email).first()
 
389
  }
390
 
391
 
392
+ @app.get("/correlated-review-insights")
393
+ def correlated_review_insights(
394
+ restaurant_id: int,
395
+ eps: float = 0.41,
396
+ min_samples: int = 2,
397
+ db: Session = Depends(get_db)
398
+ ):
399
+
400
+ restaurant = (
401
+ db.query(Restaurant)
402
+ .filter(Restaurant.id == restaurant_id)
403
+ .first()
404
+ )
405
+
406
+ if not restaurant:
407
+ raise HTTPException(404, "Restaurant not found")
408
+
409
+ reviews = (
410
+ db.query(Review)
411
+ .filter(Review.restaurant_id == restaurant_id)
412
+ .all()
413
+ )
414
+
415
+ reports = (
416
+ db.query(CtrReport)
417
+ .order_by(CtrReport.id.desc())
418
+ .all()
419
+ )
420
+
421
+ if not reviews or not reports:
422
+ return {
423
+ "restaurant_id": restaurant.id,
424
+ "restaurant_name": restaurant.name,
425
+ "correlation_points": []
426
+ }
427
+
428
+ review_texts = [r.review for r in reviews]
429
+
430
+ all_points = []
431
+ for report in reports:
432
+ if not report.insights:
433
+ continue
434
+ all_points.extend(extract_points(report.insights))
435
+
436
+ unique_points = list(dict.fromkeys(all_points))
437
+
438
+ if not unique_points:
439
+ return {
440
+ "restaurant_id": restaurant.id,
441
+ "restaurant_name": restaurant.name,
442
+ "correlation_points": []
443
+ }
444
+
445
+ representative_reviews = get_representatives(
446
+ review_texts,
447
+ eps=eps,
448
+ min_samples=min_samples,
449
+ error_label="Representative review"
450
+ )
451
+
452
+ representative_insights = get_representatives(
453
+ unique_points,
454
+ eps=eps,
455
+ min_samples=min_samples,
456
+ error_label="Representative insight"
457
+ )
458
+
459
+ trimmed_reviews = representative_reviews[:25]
460
+ trimmed_insights = representative_insights[:25]
461
+
462
+ prompt = (
463
+ "Insights:\n"
464
+ + "\n".join(f"- {point}" for point in trimmed_insights)
465
+ + "\n\nReviews:\n"
466
+ + "\n".join(f"- {text}" for text in trimmed_reviews)
467
+ + "\n\nCorrelate them in short numbered points."
468
+ )
469
+
470
+ content = requesty_chat(prompt)
471
+
472
+ correlation_points = extract_points(content)
473
+
474
+ if not correlation_points:
475
+ correlation_points = [
476
+ line.strip("- ").strip()
477
+ for line in content.splitlines()
478
+ if line.strip()
479
+ ]
480
+
481
+ if not correlation_points and content:
482
+ correlation_points = [content]
483
+
484
+ cleaned_points = []
485
+ for point in correlation_points[:6]:
486
+ cleaned = point.strip()
487
+ if len(cleaned) > 200:
488
+ cleaned = cleaned[:197].rstrip() + "..."
489
+ cleaned_points.append(cleaned)
490
+
491
+ return {
492
+ "restaurant_id": restaurant.id,
493
+ "restaurant_name": restaurant.name,
494
+ "reviews_considered": len(trimmed_reviews),
495
+ "insights_considered": len(trimmed_insights),
496
+ "correlation_points": cleaned_points
497
+ }
498
+
499
+
500
 
501
  @app.get("/few-insights")
502
  def get_all_insights(