feat(gbp-audit): Use Google data for all audit fields

- Add new columns to CompanyWebsiteAnalysis: google_name, google_address,
  google_phone, google_website, google_types, google_maps_url
- Update _check_name(), _check_address(), _check_phone(), _check_website(),
  _check_categories() to use Google data instead of NordaBiz database
- All audit fields now show real data from Google Business Profile
- Fallback to NordaBiz data only when Google data unavailable

Migration: database/migrations/add_google_gbp_fields.sql

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-01-09 04:43:03 +01:00
parent 7aacdd5951
commit 3b2dcc279b
2 changed files with 104 additions and 63 deletions

View File

@ -463,6 +463,12 @@ class CompanyWebsiteAnalysis(Base):
google_business_status = Column(String(50))
google_opening_hours = Column(JSONB) # Opening hours from GBP
google_photos_count = Column(Integer) # Number of photos on GBP
google_name = Column(String(255)) # Business name from Google
google_address = Column(String(500)) # Formatted address from Google
google_phone = Column(String(50)) # Phone from Google
google_website = Column(String(500)) # Website from Google
google_types = Column(ARRAY(Text)) # Business types/categories
google_maps_url = Column(String(500)) # Google Maps URL
# === AUDIT METADATA ===
audit_source = Column(String(50), default='automated')

View File

@ -124,35 +124,35 @@ class GBPAuditService:
total_score = 0.0
recommendations = []
# Name check
fields['name'] = self._check_name(company)
# Name check (uses Google data)
fields['name'] = self._check_name(company, website_analysis)
total_score += fields['name'].score
# Address check
fields['address'] = self._check_address(company)
# Address check (uses Google data)
fields['address'] = self._check_address(company, website_analysis)
total_score += fields['address'].score
# Phone check
fields['phone'] = self._check_phone(company)
# Phone check (uses Google data)
fields['phone'] = self._check_phone(company, website_analysis)
total_score += fields['phone'].score
# Website check
fields['website'] = self._check_website(company)
# Website check (uses Google data)
fields['website'] = self._check_website(company, website_analysis)
total_score += fields['website'].score
# Hours check (from website analysis if available)
# Hours check (uses Google data)
fields['hours'] = self._check_hours(company, website_analysis)
total_score += fields['hours'].score
# Categories check
fields['categories'] = self._check_categories(company)
# Categories check (uses Google data - google_types)
fields['categories'] = self._check_categories(company, website_analysis)
total_score += fields['categories'].score
# Photos check (from website analysis)
# Photos check (uses Google data)
fields['photos'] = self._check_photos(company, website_analysis)
total_score += fields['photos'].score
# Description check
# Description check - Google API doesn't provide owner description
fields['description'] = self._check_description(company)
total_score += fields['description'].score
@ -307,15 +307,18 @@ class GBPAuditService:
# === Field Check Methods ===
def _check_name(self, company: Company) -> FieldStatus:
"""Check business name completeness"""
def _check_name(self, company: Company, analysis: Optional[CompanyWebsiteAnalysis]) -> FieldStatus:
"""Check business name completeness - uses Google data"""
max_score = FIELD_WEIGHTS['name']
if company.name and len(company.name.strip()) >= 3:
# Use Google name if available, fallback to NordaBiz
name = analysis.google_name if analysis and analysis.google_name else company.name
if name and len(name.strip()) >= 3:
return FieldStatus(
field_name='name',
status='complete',
value=company.name,
value=name,
score=max_score,
max_score=max_score
)
@ -328,24 +331,26 @@ class GBPAuditService:
recommendation='Dodaj nazwę firmy do wizytówki Google. Nazwa powinna być oficjalną nazwą firmy.'
)
def _check_address(self, company: Company) -> FieldStatus:
"""Check address completeness"""
def _check_address(self, company: Company, analysis: Optional[CompanyWebsiteAnalysis]) -> FieldStatus:
"""Check address completeness - uses Google data"""
max_score = FIELD_WEIGHTS['address']
# Check all address components
has_street = bool(company.address_street)
has_city = bool(company.address_city)
has_postal = bool(company.address_postal)
# Use Google address if available
google_address = analysis.google_address if analysis else None
if has_street and has_city and has_postal:
if google_address and len(google_address.strip()) >= 10:
return FieldStatus(
field_name='address',
status='complete',
value=company.address_full or f"{company.address_street}, {company.address_postal} {company.address_city}",
value=google_address,
score=max_score,
max_score=max_score
)
# Fallback to NordaBiz data
has_street = bool(company.address_street)
has_city = bool(company.address_city)
if has_city or has_street:
partial_score = max_score * 0.5
return FieldStatus(
@ -354,7 +359,7 @@ class GBPAuditService:
value=company.address_city or company.address_street,
score=partial_score,
max_score=max_score,
recommendation='Uzupełnij pełny adres firmy (ulica, kod pocztowy, miasto) dla lepszej widoczności w mapach.'
recommendation='Uzupełnij pełny adres firmy w Google Business Profile.'
)
return FieldStatus(
@ -365,27 +370,18 @@ class GBPAuditService:
recommendation='Dodaj adres firmy do wizytówki Google. Pełny adres jest kluczowy dla lokalnego SEO.'
)
def _check_phone(self, company: Company) -> FieldStatus:
"""Check phone number presence"""
def _check_phone(self, company: Company, analysis: Optional[CompanyWebsiteAnalysis]) -> FieldStatus:
"""Check phone number presence - uses Google data"""
max_score = FIELD_WEIGHTS['phone']
if company.phone and len(company.phone.strip()) >= 9:
return FieldStatus(
field_name='phone',
status='complete',
value=company.phone,
score=max_score,
max_score=max_score
)
# Use Google phone if available
phone = analysis.google_phone if analysis and analysis.google_phone else company.phone
# Check contacts relationship for additional phones
if hasattr(company, 'contacts') and company.contacts:
phones = [c for c in company.contacts if c.contact_type == 'phone']
if phones:
if phone and len(phone.strip()) >= 9:
return FieldStatus(
field_name='phone',
status='complete',
value=phones[0].value,
value=phone,
score=max_score,
max_score=max_score
)
@ -395,31 +391,34 @@ class GBPAuditService:
status='missing',
score=0,
max_score=max_score,
recommendation='Dodaj numer telefonu do wizytówki. Klienci oczekują możliwości bezpośredniego kontaktu.'
recommendation='Dodaj numer telefonu do wizytówki Google. Klienci oczekują możliwości bezpośredniego kontaktu.'
)
def _check_website(self, company: Company) -> FieldStatus:
"""Check website presence"""
def _check_website(self, company: Company, analysis: Optional[CompanyWebsiteAnalysis]) -> FieldStatus:
"""Check website presence - uses Google data"""
max_score = FIELD_WEIGHTS['website']
if company.website and company.website.strip().startswith(('http://', 'https://')):
# Use Google website if available
website = analysis.google_website if analysis and analysis.google_website else company.website
if website and website.strip().startswith(('http://', 'https://')):
return FieldStatus(
field_name='website',
status='complete',
value=company.website,
value=website,
score=max_score,
max_score=max_score
)
if company.website:
if website:
# Has website but might not be properly formatted
return FieldStatus(
field_name='website',
status='partial',
value=company.website,
value=website,
score=max_score * 0.7,
max_score=max_score,
recommendation='Upewnij się, że adres strony internetowej zawiera protokół (https://).'
recommendation='Upewnij się, że adres strony w Google Business Profile zawiera protokół (https://).'
)
return FieldStatus(
@ -427,7 +426,7 @@ class GBPAuditService:
status='missing',
score=0,
max_score=max_score,
recommendation='Dodaj stronę internetową firmy. Link do strony zwiększa wiarygodność i ruch.'
recommendation='Dodaj stronę internetową do wizytówki Google. Link do strony zwiększa wiarygodność i ruch.'
)
def _check_hours(self, company: Company, analysis: Optional[CompanyWebsiteAnalysis]) -> FieldStatus:
@ -452,26 +451,41 @@ class GBPAuditService:
recommendation='Dodaj godziny otwarcia firmy. Klienci chcą wiedzieć, kiedy mogą Cię odwiedzić.'
)
def _check_categories(self, company: Company) -> FieldStatus:
"""Check business category completeness"""
def _check_categories(self, company: Company, analysis: Optional[CompanyWebsiteAnalysis]) -> FieldStatus:
"""Check business category completeness - uses Google data"""
max_score = FIELD_WEIGHTS['categories']
# Check if company has a category assigned
if company.category_id and company.category:
# Use Google types if available
google_types = analysis.google_types if analysis and analysis.google_types else None
if google_types and len(google_types) > 0:
# Format Google types for display (remove underscores, title case)
formatted_types = [t.replace('_', ' ').title() for t in google_types[:3]]
return FieldStatus(
field_name='categories',
status='complete',
value=company.category.name if company.category else None,
value=', '.join(formatted_types),
score=max_score,
max_score=max_score
)
# Fallback to NordaBiz category
if company.category_id and company.category:
return FieldStatus(
field_name='categories',
status='partial',
value=company.category.name if company.category else None,
score=max_score * 0.5,
max_score=max_score,
recommendation='Dodaj kategorie w Google Business Profile. Kategorie z Google są ważniejsze dla SEO niż dane lokalne.'
)
return FieldStatus(
field_name='categories',
status='missing',
score=0,
max_score=max_score,
recommendation='Wybierz główną kategorię działalności. Kategoria pomaga klientom znaleźć Twoją firmę.'
recommendation='Wybierz główną kategorię działalności w Google Business Profile. Kategoria pomaga klientom znaleźć Twoją firmę.'
)
def _check_photos(self, company: Company, analysis: Optional[CompanyWebsiteAnalysis]) -> FieldStatus:
@ -1215,12 +1229,16 @@ def fetch_google_business_data(
try:
fields = [
'name',
'formatted_address',
'formatted_phone_number',
'website',
'types',
'url',
'rating',
'user_ratings_total',
'opening_hours',
'business_status',
'formatted_phone_number',
'website',
'photos',
]
@ -1245,16 +1263,27 @@ def fetch_google_business_data(
place = details_data.get('result', {})
# Extract data
# Extract all data from Google
google_name = place.get('name')
google_address = place.get('formatted_address')
phone = place.get('formatted_phone_number')
website = place.get('website')
types = place.get('types', [])
maps_url = place.get('url')
rating = place.get('rating')
reviews_count = place.get('user_ratings_total')
photos = place.get('photos', [])
photos_count = len(photos) if photos else 0
opening_hours = place.get('opening_hours', {})
business_status = place.get('business_status')
phone = place.get('formatted_phone_number')
website = place.get('website')
# Store all data in result
result['data']['google_name'] = google_name
result['data']['google_address'] = google_address
result['data']['google_phone'] = phone
result['data']['google_website'] = website
result['data']['google_types'] = types
result['data']['google_maps_url'] = maps_url
result['data']['google_rating'] = rating
result['data']['google_reviews_count'] = reviews_count
result['data']['google_photos_count'] = photos_count
@ -1305,8 +1334,14 @@ def fetch_google_business_data(
)
db.add(analysis)
# Update Google fields
# Update all Google fields
analysis.google_place_id = place_id
analysis.google_name = google_name
analysis.google_address = google_address
analysis.google_phone = phone
analysis.google_website = website
analysis.google_types = types if types else None
analysis.google_maps_url = maps_url
analysis.google_rating = rating
analysis.google_reviews_count = reviews_count
analysis.google_photos_count = photos_count