auto-claude: subtask-3-4 - Integrate AI recommendations using Gemini service

Added AI-powered recommendation generation to GBP audit service:

- Import gemini_service module for AI integration
- generate_ai_recommendations(): Main method to generate personalized
  recommendations using Gemini with proper cost tracking
- _build_ai_recommendation_prompt(): Builds context-aware prompt with
  company info, audit results, and field statuses in Polish
- _parse_ai_recommendations(): Parses JSON response from Gemini with
  robust error handling and fallback to static recommendations
- audit_with_ai(): Convenience method for running audit with AI
- audit_company_with_ai(): Module-level convenience function

Features:
- Recommendations are personalized to company industry/category
- Includes action_steps and expected_impact for each recommendation
- Graceful fallback to static recommendations if AI unavailable
- Cost tracking via 'gbp_audit_ai' feature tag
- Updated test runner with --ai flag for testing AI mode

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-01-08 19:20:25 +01:00
parent 8fa23bc77e
commit 47c415a63b

View File

@ -14,6 +14,7 @@ Author: Norda Biznes Development Team
Created: 2026-01-08 Created: 2026-01-08
""" """
import json
import logging import logging
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import datetime from datetime import datetime
@ -23,6 +24,7 @@ from typing import Dict, List, Optional, Any
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from database import Company, GBPAudit, CompanyWebsiteAnalysis, SessionLocal from database import Company, GBPAudit, CompanyWebsiteAnalysis, SessionLocal
import gemini_service
# Configure logging # Configure logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -644,6 +646,266 @@ class GBPAuditService:
return 'low' return 'low'
# === AI-Powered Recommendations ===
def generate_ai_recommendations(
self,
company: Company,
result: AuditResult,
user_id: Optional[int] = None
) -> List[Dict[str, Any]]:
"""
Generate AI-powered recommendations using Gemini.
Args:
company: Company being audited
result: AuditResult from the audit
user_id: Optional user ID for cost tracking
Returns:
List of AI-generated recommendation dicts with keys:
- priority: 'high', 'medium', 'low'
- field: field name this applies to
- recommendation: AI-generated recommendation text
- action_steps: list of specific action steps
- expected_impact: description of expected improvement
"""
service = gemini_service.get_gemini_service()
if not service:
logger.warning("Gemini service not available - using static recommendations")
return result.recommendations
try:
# Build context for AI
prompt = self._build_ai_recommendation_prompt(company, result)
# Call Gemini with cost tracking
response_text = service.generate_text(
prompt=prompt,
feature='gbp_audit_ai',
user_id=user_id,
temperature=0.7,
max_tokens=2000
)
# Parse AI response
ai_recommendations = self._parse_ai_recommendations(response_text, result)
logger.info(
f"AI recommendations generated for company {company.id}: "
f"{len(ai_recommendations)} recommendations"
)
return ai_recommendations
except Exception as e:
logger.error(f"AI recommendation generation failed: {e}")
# Fall back to static recommendations
return result.recommendations
def _build_ai_recommendation_prompt(
self,
company: Company,
result: AuditResult
) -> str:
"""
Build prompt for Gemini to generate personalized recommendations.
Args:
company: Company being audited
result: AuditResult with field statuses
Returns:
Formatted prompt string
"""
# Build field status summary
field_summary = []
for field_name, field_status in result.fields.items():
status_emoji = {
'complete': '',
'partial': '⚠️',
'missing': ''
}.get(field_status.status, '')
field_summary.append(
f"- {field_name}: {status_emoji} {field_status.status} "
f"({field_status.score:.1f}/{field_status.max_score:.1f} pkt)"
)
# Get category info
category_name = company.category.name if company.category else 'Nieznana'
prompt = f"""Jesteś ekspertem od Google Business Profile (Wizytówki Google) i lokalnego SEO.
FIRMA: {company.name}
BRANŻA: {category_name}
MIASTO: {company.address_city or 'Nieznane'}
WYNIK AUDYTU: {result.completeness_score}/100
STATUS PÓL WIZYTÓWKI:
{chr(10).join(field_summary)}
LICZBA ZDJĘĆ: {result.photo_count}
LICZBA OPINII: {result.review_count}
OCENA: {result.average_rating or 'Brak'}
ZADANIE:
Wygeneruj 3-5 spersonalizowanych rekomendacji dla tej firmy, aby poprawić jej wizytówkę Google.
WYMAGANIA:
1. Każda rekomendacja powinna być konkretna i dostosowana do branży firmy
2. Skup się na polach z najniższymi wynikami
3. Podaj praktyczne kroki do wykonania
4. Używaj języka polskiego
ZWRÓĆ ODPOWIEDŹ W FORMACIE JSON (TYLKO JSON, BEZ MARKDOWN):
[
{{
"priority": "high|medium|low",
"field": "nazwa_pola",
"recommendation": "Krótki opis co poprawić",
"action_steps": ["Krok 1", "Krok 2", "Krok 3"],
"expected_impact": "Opis spodziewanej poprawy"
}}
]
Priorytety:
- high: kluczowe pola (name, address, categories, description)
- medium: ważne pola (phone, website, photos, services)
- low: dodatkowe pola (hours, reviews)
Odpowiedź (TYLKO JSON):"""
return prompt
def _parse_ai_recommendations(
self,
response_text: str,
fallback_result: AuditResult
) -> List[Dict[str, Any]]:
"""
Parse AI response into structured recommendations.
Args:
response_text: Raw text from Gemini
fallback_result: AuditResult to use for fallback
Returns:
List of recommendation dicts
"""
try:
# Clean up response - remove markdown code blocks if present
cleaned = response_text.strip()
if cleaned.startswith('```'):
# Remove markdown code block markers
lines = cleaned.split('\n')
# Find JSON content between ``` markers
json_lines = []
in_json = False
for line in lines:
if line.startswith('```') and not in_json:
in_json = True
continue
elif line.startswith('```') and in_json:
break
elif in_json:
json_lines.append(line)
cleaned = '\n'.join(json_lines)
# Parse JSON
recommendations = json.loads(cleaned)
# Validate and enhance recommendations
valid_recommendations = []
valid_priorities = {'high', 'medium', 'low'}
valid_fields = set(FIELD_WEIGHTS.keys())
for rec in recommendations:
if not isinstance(rec, dict):
continue
# Validate priority
priority = rec.get('priority', 'medium')
if priority not in valid_priorities:
priority = 'medium'
# Validate field
field = rec.get('field', 'general')
if field not in valid_fields:
field = 'general'
# Get impact score from field weights
impact = FIELD_WEIGHTS.get(field, 5)
valid_recommendations.append({
'priority': priority,
'field': field,
'recommendation': rec.get('recommendation', ''),
'action_steps': rec.get('action_steps', []),
'expected_impact': rec.get('expected_impact', ''),
'impact': impact,
'source': 'ai'
})
if valid_recommendations:
# Sort by priority and impact
priority_order = {'high': 0, 'medium': 1, 'low': 2}
valid_recommendations.sort(
key=lambda x: (priority_order.get(x['priority'], 3), -x['impact'])
)
return valid_recommendations
except json.JSONDecodeError as e:
logger.warning(f"Failed to parse AI recommendations JSON: {e}")
except Exception as e:
logger.warning(f"Error processing AI recommendations: {e}")
# Return fallback recommendations with source marker
fallback = []
for rec in fallback_result.recommendations:
rec_copy = dict(rec)
rec_copy['source'] = 'static'
rec_copy['action_steps'] = []
rec_copy['expected_impact'] = ''
fallback.append(rec_copy)
return fallback
def audit_with_ai(
self,
company_id: int,
user_id: Optional[int] = None
) -> AuditResult:
"""
Run full GBP audit with AI-powered recommendations.
Args:
company_id: ID of the company to audit
user_id: Optional user ID for cost tracking
Returns:
AuditResult with AI-enhanced recommendations
"""
# Run standard audit
result = self.audit_company(company_id)
# Get company for AI context
company = self.db.query(Company).filter(Company.id == company_id).first()
if not company:
return result
# Generate AI recommendations
ai_recommendations = self.generate_ai_recommendations(
company=company,
result=result,
user_id=user_id
)
# Replace static recommendations with AI-generated ones
result.recommendations = ai_recommendations
return result
# === Convenience Functions === # === Convenience Functions ===
@ -683,6 +945,33 @@ def get_company_audit(db: Session, company_id: int) -> Optional[GBPAudit]:
return service.get_latest_audit(company_id) return service.get_latest_audit(company_id)
def audit_company_with_ai(
db: Session,
company_id: int,
save: bool = True,
user_id: Optional[int] = None
) -> AuditResult:
"""
Audit a company's GBP completeness with AI-powered recommendations.
Args:
db: Database session
company_id: Company ID to audit
save: Whether to save audit to database
user_id: Optional user ID for cost tracking
Returns:
AuditResult with AI-enhanced recommendations
"""
service = GBPAuditService(db)
result = service.audit_with_ai(company_id, user_id=user_id)
if save:
service.save_audit(result, source='ai')
return result
def batch_audit_companies( def batch_audit_companies(
db: Session, db: Session,
company_ids: Optional[List[int]] = None, company_ids: Optional[List[int]] = None,
@ -722,9 +1011,14 @@ def batch_audit_companies(
# === Main for Testing === # === Main for Testing ===
if __name__ == '__main__': if __name__ == '__main__':
import sys
# Test the service # Test the service
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
# Check for --ai flag to test AI recommendations
use_ai = '--ai' in sys.argv
db = SessionLocal() db = SessionLocal()
try: try:
# Get first active company # Get first active company
@ -733,19 +1027,39 @@ if __name__ == '__main__':
print(f"\nAuditing company: {company.name} (ID: {company.id})") print(f"\nAuditing company: {company.name} (ID: {company.id})")
print("-" * 50) print("-" * 50)
result = audit_company(db, company.id, save=False) if use_ai:
print("\n[AI MODE] Generating AI-powered recommendations...")
result = audit_company_with_ai(db, company.id, save=False)
else:
result = audit_company(db, company.id, save=False)
print(f"\nCompleteness Score: {result.completeness_score}/100") print(f"\nCompleteness Score: {result.completeness_score}/100")
print(f"\nField Status:") print(f"\nField Status:")
for name, field in result.fields.items(): for name, field in result.fields.items():
status_icon = {'complete': '[check mark]', 'partial': '~', 'missing': '[X]'}.get(field.status, '?') status_icon = {'complete': '', 'partial': '⚠️', 'missing': ''}.get(field.status, '?')
print(f" {status_icon} {name}: {field.status} ({field.score:.1f}/{field.max_score:.1f})") print(f" {status_icon} {name}: {field.status} ({field.score:.1f}/{field.max_score:.1f})")
print(f"\nRecommendations ({len(result.recommendations)}):") print(f"\nRecommendations ({len(result.recommendations)}):")
for rec in result.recommendations[:5]: for rec in result.recommendations[:5]:
print(f" [{rec['priority'].upper()}] {rec['field']}: {rec['recommendation'][:80]}...") source = rec.get('source', 'static')
source_label = '[AI]' if source == 'ai' else '[STATIC]'
print(f"\n {source_label} [{rec['priority'].upper()}] {rec['field']}:")
print(f" {rec['recommendation']}")
# Print AI-specific fields if present
if rec.get('action_steps'):
print(" Action steps:")
for step in rec['action_steps']:
print(f"{step}")
if rec.get('expected_impact'):
print(f" Expected impact: {rec['expected_impact']}")
else: else:
print("No active companies found") print("No active companies found")
print("\n" + "-" * 50)
print("Usage: python gbp_audit_service.py [--ai]")
print(" --ai Generate AI-powered recommendations using Gemini")
finally: finally:
db.close() db.close()