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:
parent
7aacdd5951
commit
3b2dcc279b
@ -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')
|
||||
|
||||
@ -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,61 +370,55 @@ 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:
|
||||
# Use Google phone if available
|
||||
phone = analysis.google_phone if analysis and analysis.google_phone else company.phone
|
||||
|
||||
if phone and len(phone.strip()) >= 9:
|
||||
return FieldStatus(
|
||||
field_name='phone',
|
||||
status='complete',
|
||||
value=company.phone,
|
||||
value=phone,
|
||||
score=max_score,
|
||||
max_score=max_score
|
||||
)
|
||||
|
||||
# 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:
|
||||
return FieldStatus(
|
||||
field_name='phone',
|
||||
status='complete',
|
||||
value=phones[0].value,
|
||||
score=max_score,
|
||||
max_score=max_score
|
||||
)
|
||||
|
||||
return FieldStatus(
|
||||
field_name='phone',
|
||||
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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user