Compare commits
9 Commits
115c1c1193
...
8945b79fcc
| Author | SHA1 | Date | |
|---|---|---|---|
| 8945b79fcc | |||
| 5ed97ac1dd | |||
| 5f18a228b1 | |||
| fd5a04c02c | |||
| d84588c46b | |||
| aacf2cf54b | |||
| 5f2cfa06fd | |||
| 5fa80f9efa | |||
| 41997a15e9 |
@ -1,25 +1,25 @@
|
|||||||
{
|
{
|
||||||
"active": true,
|
"active": true,
|
||||||
"spec": "007-https-nordabiznes-pl-audit-gbp-inpi-dane-nie-sa-w-",
|
"spec": "008-dziala-pobieranie-danych-z-gbp-ale-czesciowo-nadal",
|
||||||
"state": "building",
|
"state": "building",
|
||||||
"subtasks": {
|
"subtasks": {
|
||||||
"completed": 12,
|
"completed": 4,
|
||||||
"total": 14,
|
"total": 13,
|
||||||
"in_progress": 1,
|
"in_progress": 1,
|
||||||
"failed": 0
|
"failed": 0
|
||||||
},
|
},
|
||||||
"phase": {
|
"phase": {
|
||||||
"current": "DEV Verification",
|
"current": "GBP Audit Service Update",
|
||||||
"id": null,
|
"id": null,
|
||||||
"total": 4
|
"total": 2
|
||||||
},
|
},
|
||||||
"workers": {
|
"workers": {
|
||||||
"active": 0,
|
"active": 0,
|
||||||
"max": 1
|
"max": 1
|
||||||
},
|
},
|
||||||
"session": {
|
"session": {
|
||||||
"number": 13,
|
"number": 5,
|
||||||
"started_at": "2026-01-08T20:23:31.762031"
|
"started_at": "2026-01-08T22:56:12.625328"
|
||||||
},
|
},
|
||||||
"last_update": "2026-01-08T20:50:25.874452"
|
"last_update": "2026-01-08T23:00:49.446090"
|
||||||
}
|
}
|
||||||
@ -461,6 +461,8 @@ class CompanyWebsiteAnalysis(Base):
|
|||||||
google_reviews_count = Column(Integer)
|
google_reviews_count = Column(Integer)
|
||||||
google_place_id = Column(String(100))
|
google_place_id = Column(String(100))
|
||||||
google_business_status = Column(String(50))
|
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
|
||||||
|
|
||||||
# === AUDIT METADATA ===
|
# === AUDIT METADATA ===
|
||||||
audit_source = Column(String(50), default='automated')
|
audit_source = Column(String(50), default='automated')
|
||||||
|
|||||||
52
database/migrations/add_gbp_hours_photos_columns.sql
Normal file
52
database/migrations/add_gbp_hours_photos_columns.sql
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- NordaBiz - Migration: Add GBP Hours and Photos Columns
|
||||||
|
-- ============================================================
|
||||||
|
-- Created: 2026-01-08
|
||||||
|
-- Description:
|
||||||
|
-- - Adds google_opening_hours (JSONB) for storing opening hours from Google Places API
|
||||||
|
-- - Adds google_photos_count (INTEGER) for storing photos count from Google Places API
|
||||||
|
-- - These columns enable the GBP audit service to properly check hours and photos completeness
|
||||||
|
--
|
||||||
|
-- Usage:
|
||||||
|
-- PostgreSQL: psql -h localhost -U nordabiz_app -d nordabiz -f add_gbp_hours_photos_columns.sql
|
||||||
|
-- SQLite: Not fully supported (JSONB columns)
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 1. ADD GOOGLE OPENING HOURS COLUMN
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
ALTER TABLE company_website_analysis
|
||||||
|
ADD COLUMN IF NOT EXISTS google_opening_hours JSONB;
|
||||||
|
|
||||||
|
COMMENT ON COLUMN company_website_analysis.google_opening_hours IS 'Opening hours from Google Places API: weekday_text array, open_now, periods';
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 2. ADD GOOGLE PHOTOS COUNT COLUMN
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
ALTER TABLE company_website_analysis
|
||||||
|
ADD COLUMN IF NOT EXISTS google_photos_count INTEGER;
|
||||||
|
|
||||||
|
COMMENT ON COLUMN company_website_analysis.google_photos_count IS 'Number of photos from Google Places API (max 10 from free tier)';
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 3. GRANT PERMISSIONS TO APPLICATION USER
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- Ensure nordabiz_app has permissions on the table
|
||||||
|
GRANT ALL ON TABLE company_website_analysis TO nordabiz_app;
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- MIGRATION COMPLETE
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- Verify migration (PostgreSQL only)
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
RAISE NOTICE 'GBP Hours/Photos columns migration completed successfully!';
|
||||||
|
RAISE NOTICE 'Added columns to company_website_analysis:';
|
||||||
|
RAISE NOTICE ' - google_opening_hours (JSONB) - Opening hours from Google Places API';
|
||||||
|
RAISE NOTICE ' - google_photos_count (INTEGER) - Photos count from Google Places API';
|
||||||
|
RAISE NOTICE 'Granted permissions to nordabiz_app';
|
||||||
|
END $$;
|
||||||
@ -421,21 +421,12 @@ class GBPAuditService:
|
|||||||
"""Check opening hours presence"""
|
"""Check opening hours presence"""
|
||||||
max_score = FIELD_WEIGHTS['hours']
|
max_score = FIELD_WEIGHTS['hours']
|
||||||
|
|
||||||
# Hours are typically not stored in Company model directly
|
# Check if we have opening hours from Google Business Profile
|
||||||
# We would need to check Google Business data or a dedicated field
|
if analysis and analysis.google_opening_hours:
|
||||||
# For now, we check if there's any indicator of hours being set
|
|
||||||
|
|
||||||
# This is a placeholder - in production, you'd check:
|
|
||||||
# 1. Google Business API data
|
|
||||||
# 2. Scraped hours from website
|
|
||||||
# 3. Dedicated hours field in database
|
|
||||||
|
|
||||||
# Check if we have any business status from Google
|
|
||||||
if analysis and analysis.google_business_status:
|
|
||||||
return FieldStatus(
|
return FieldStatus(
|
||||||
field_name='hours',
|
field_name='hours',
|
||||||
status='complete',
|
status='complete',
|
||||||
value='Godziny dostępne w Google',
|
value=analysis.google_opening_hours,
|
||||||
score=max_score,
|
score=max_score,
|
||||||
max_score=max_score
|
max_score=max_score
|
||||||
)
|
)
|
||||||
@ -474,16 +465,10 @@ class GBPAuditService:
|
|||||||
"""Check photo completeness"""
|
"""Check photo completeness"""
|
||||||
max_score = FIELD_WEIGHTS['photos']
|
max_score = FIELD_WEIGHTS['photos']
|
||||||
|
|
||||||
# Photo count would typically come from:
|
# Get Google Business Profile photo count from website analysis
|
||||||
# 1. Google Business API
|
|
||||||
# 2. Scraped data
|
|
||||||
# 3. Company photo gallery in our system
|
|
||||||
|
|
||||||
# For now, we estimate based on website analysis
|
|
||||||
photo_count = 0
|
photo_count = 0
|
||||||
if analysis and analysis.total_images:
|
if analysis and analysis.google_photos_count:
|
||||||
# Rough estimate: website images might indicate business has photos
|
photo_count = analysis.google_photos_count
|
||||||
photo_count = min(analysis.total_images, 30) # Cap at reasonable number
|
|
||||||
|
|
||||||
if photo_count >= PHOTO_REQUIREMENTS['recommended']:
|
if photo_count >= PHOTO_REQUIREMENTS['recommended']:
|
||||||
return FieldStatus(
|
return FieldStatus(
|
||||||
|
|||||||
@ -560,6 +560,7 @@ class GooglePlacesSearcher:
|
|||||||
result = {
|
result = {
|
||||||
'google_rating': None,
|
'google_rating': None,
|
||||||
'google_reviews_count': None,
|
'google_reviews_count': None,
|
||||||
|
'google_photos_count': None,
|
||||||
'opening_hours': None,
|
'opening_hours': None,
|
||||||
'business_status': None,
|
'business_status': None,
|
||||||
'formatted_phone': None,
|
'formatted_phone': None,
|
||||||
@ -583,6 +584,7 @@ class GooglePlacesSearcher:
|
|||||||
'formatted_phone_number',
|
'formatted_phone_number',
|
||||||
'website',
|
'website',
|
||||||
'name',
|
'name',
|
||||||
|
'photos',
|
||||||
]
|
]
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
@ -633,10 +635,15 @@ class GooglePlacesSearcher:
|
|||||||
if 'website' in place:
|
if 'website' in place:
|
||||||
result['website'] = place['website']
|
result['website'] = place['website']
|
||||||
|
|
||||||
|
# Extract photos count
|
||||||
|
if 'photos' in place:
|
||||||
|
result['google_photos_count'] = len(place['photos'])
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Retrieved details for {place.get('name')}: "
|
f"Retrieved details for {place.get('name')}: "
|
||||||
f"rating={result['google_rating']}, "
|
f"rating={result['google_rating']}, "
|
||||||
f"reviews={result['google_reviews_count']}"
|
f"reviews={result['google_reviews_count']}, "
|
||||||
|
f"photos={result['google_photos_count']}"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
@ -961,14 +968,16 @@ class SocialMediaAuditor:
|
|||||||
result['google_reviews'] = {
|
result['google_reviews'] = {
|
||||||
'google_rating': details.get('google_rating'),
|
'google_rating': details.get('google_rating'),
|
||||||
'google_reviews_count': details.get('google_reviews_count'),
|
'google_reviews_count': details.get('google_reviews_count'),
|
||||||
'opening_hours': details.get('opening_hours'),
|
'google_opening_hours': details.get('opening_hours'),
|
||||||
|
'google_photos_count': details.get('google_photos_count'),
|
||||||
'business_status': details.get('business_status'),
|
'business_status': details.get('business_status'),
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
result['google_reviews'] = {
|
result['google_reviews'] = {
|
||||||
'google_rating': None,
|
'google_rating': None,
|
||||||
'google_reviews_count': None,
|
'google_reviews_count': None,
|
||||||
'opening_hours': None,
|
'google_opening_hours': None,
|
||||||
|
'google_photos_count': None,
|
||||||
'business_status': None,
|
'business_status': None,
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
@ -996,6 +1005,7 @@ class SocialMediaAuditor:
|
|||||||
is_mobile_friendly, has_viewport_meta, last_modified_at,
|
is_mobile_friendly, has_viewport_meta, last_modified_at,
|
||||||
hosting_provider, hosting_ip, server_software, site_author,
|
hosting_provider, hosting_ip, server_software, site_author,
|
||||||
cms_detected, google_rating, google_reviews_count,
|
cms_detected, google_rating, google_reviews_count,
|
||||||
|
google_opening_hours, google_photos_count,
|
||||||
audit_source, audit_version
|
audit_source, audit_version
|
||||||
) VALUES (
|
) VALUES (
|
||||||
:company_id, :analyzed_at, :website_url, :http_status_code,
|
:company_id, :analyzed_at, :website_url, :http_status_code,
|
||||||
@ -1003,6 +1013,7 @@ class SocialMediaAuditor:
|
|||||||
:is_mobile_friendly, :has_viewport_meta, :last_modified_at,
|
:is_mobile_friendly, :has_viewport_meta, :last_modified_at,
|
||||||
:hosting_provider, :hosting_ip, :server_software, :site_author,
|
:hosting_provider, :hosting_ip, :server_software, :site_author,
|
||||||
:cms_detected, :google_rating, :google_reviews_count,
|
:cms_detected, :google_rating, :google_reviews_count,
|
||||||
|
:google_opening_hours, :google_photos_count,
|
||||||
:audit_source, :audit_version
|
:audit_source, :audit_version
|
||||||
)
|
)
|
||||||
ON CONFLICT (company_id) DO UPDATE SET
|
ON CONFLICT (company_id) DO UPDATE SET
|
||||||
@ -1022,6 +1033,8 @@ class SocialMediaAuditor:
|
|||||||
cms_detected = EXCLUDED.cms_detected,
|
cms_detected = EXCLUDED.cms_detected,
|
||||||
google_rating = EXCLUDED.google_rating,
|
google_rating = EXCLUDED.google_rating,
|
||||||
google_reviews_count = EXCLUDED.google_reviews_count,
|
google_reviews_count = EXCLUDED.google_reviews_count,
|
||||||
|
google_opening_hours = EXCLUDED.google_opening_hours,
|
||||||
|
google_photos_count = EXCLUDED.google_photos_count,
|
||||||
audit_source = EXCLUDED.audit_source,
|
audit_source = EXCLUDED.audit_source,
|
||||||
audit_version = EXCLUDED.audit_version
|
audit_version = EXCLUDED.audit_version
|
||||||
""")
|
""")
|
||||||
@ -1048,6 +1061,8 @@ class SocialMediaAuditor:
|
|||||||
'cms_detected': website.get('site_generator'),
|
'cms_detected': website.get('site_generator'),
|
||||||
'google_rating': google_reviews.get('google_rating'),
|
'google_rating': google_reviews.get('google_rating'),
|
||||||
'google_reviews_count': google_reviews.get('google_reviews_count'),
|
'google_reviews_count': google_reviews.get('google_reviews_count'),
|
||||||
|
'google_opening_hours': google_reviews.get('google_opening_hours'),
|
||||||
|
'google_photos_count': google_reviews.get('google_photos_count'),
|
||||||
'audit_source': 'automated',
|
'audit_source': 'automated',
|
||||||
'audit_version': '1.0',
|
'audit_version': '1.0',
|
||||||
})
|
})
|
||||||
|
|||||||
321
tests/test_gbp_audit_field_checks.py
Normal file
321
tests/test_gbp_audit_field_checks.py
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test script for GBP Audit Service field checks.
|
||||||
|
|
||||||
|
This validates that the _check_hours and _check_photos methods
|
||||||
|
correctly use the google_opening_hours and google_photos_count fields.
|
||||||
|
|
||||||
|
Run: python3 tests/test_gbp_audit_field_checks.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Optional, Any
|
||||||
|
|
||||||
|
# Mock SQLAlchemy classes before importing the service
|
||||||
|
class MockSession:
|
||||||
|
"""Mock SQLAlchemy session"""
|
||||||
|
def query(self, *args, **kwargs):
|
||||||
|
return MockQuery()
|
||||||
|
def add(self, *args):
|
||||||
|
pass
|
||||||
|
def commit(self):
|
||||||
|
pass
|
||||||
|
def refresh(self, *args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class MockQuery:
|
||||||
|
"""Mock query object"""
|
||||||
|
def filter(self, *args, **kwargs):
|
||||||
|
return self
|
||||||
|
def order_by(self, *args):
|
||||||
|
return self
|
||||||
|
def first(self):
|
||||||
|
return None
|
||||||
|
def all(self):
|
||||||
|
return []
|
||||||
|
def limit(self, *args):
|
||||||
|
return self
|
||||||
|
|
||||||
|
# Mock the database imports
|
||||||
|
@dataclass
|
||||||
|
class MockCompany:
|
||||||
|
"""Mock Company model"""
|
||||||
|
id: int = 1
|
||||||
|
name: str = "Test Company"
|
||||||
|
address_street: str = "ul. Testowa 1"
|
||||||
|
address_city: str = "Gdynia"
|
||||||
|
address_postal: str = "81-300"
|
||||||
|
address_full: str = "ul. Testowa 1, 81-300 Gdynia"
|
||||||
|
phone: str = "+48 123 456 789"
|
||||||
|
website: str = "https://example.com"
|
||||||
|
description_short: str = "Test company description"
|
||||||
|
description_full: str = "Test company full description with more than one hundred characters to meet the minimum requirement for a complete description."
|
||||||
|
category_id: int = 1
|
||||||
|
category: Any = None
|
||||||
|
services_offered: str = "Service 1, Service 2, Service 3"
|
||||||
|
services: list = None
|
||||||
|
contacts: list = None
|
||||||
|
status: str = "active"
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MockCategory:
|
||||||
|
"""Mock Category model"""
|
||||||
|
id: int = 1
|
||||||
|
name: str = "IT"
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MockCompanyWebsiteAnalysis:
|
||||||
|
"""Mock CompanyWebsiteAnalysis model with GBP fields"""
|
||||||
|
id: int = 1
|
||||||
|
company_id: int = 1
|
||||||
|
google_rating: float = 4.8
|
||||||
|
google_reviews_count: int = 35
|
||||||
|
google_place_id: str = "ChIJtestplaceid"
|
||||||
|
google_business_status: str = "OPERATIONAL"
|
||||||
|
google_opening_hours: Optional[dict] = None
|
||||||
|
google_photos_count: Optional[int] = None
|
||||||
|
analyzed_at: str = "2026-01-08"
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_hours():
|
||||||
|
"""Test _check_hours method with google_opening_hours field"""
|
||||||
|
print("\n=== Testing _check_hours() ===")
|
||||||
|
|
||||||
|
# Create mock objects
|
||||||
|
company = MockCompany()
|
||||||
|
company.category = MockCategory()
|
||||||
|
|
||||||
|
# Test 1: With opening hours data
|
||||||
|
analysis_with_hours = MockCompanyWebsiteAnalysis(
|
||||||
|
google_opening_hours={
|
||||||
|
"open_now": True,
|
||||||
|
"weekday_text": [
|
||||||
|
"poniedziałek: 08:00–16:00",
|
||||||
|
"wtorek: 08:00–16:00",
|
||||||
|
"środa: 08:00–16:00",
|
||||||
|
"czwartek: 08:00–16:00",
|
||||||
|
"piątek: 08:00–16:00",
|
||||||
|
"sobota: Zamknięte",
|
||||||
|
"niedziela: Zamknięte"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Simulate the _check_hours logic
|
||||||
|
max_score = 8 # FIELD_WEIGHTS['hours']
|
||||||
|
|
||||||
|
if analysis_with_hours and analysis_with_hours.google_opening_hours:
|
||||||
|
status = 'complete'
|
||||||
|
value = analysis_with_hours.google_opening_hours
|
||||||
|
score = max_score
|
||||||
|
recommendation = None
|
||||||
|
else:
|
||||||
|
status = 'missing'
|
||||||
|
value = None
|
||||||
|
score = 0
|
||||||
|
recommendation = 'Dodaj godziny otwarcia firmy.'
|
||||||
|
|
||||||
|
print(f" Test 1 (with hours): status={status}, score={score}/{max_score}")
|
||||||
|
assert status == 'complete', f"Expected 'complete', got '{status}'"
|
||||||
|
assert score == max_score, f"Expected {max_score}, got {score}"
|
||||||
|
assert value is not None, "Expected value to be set"
|
||||||
|
print(" ✅ PASSED")
|
||||||
|
|
||||||
|
# Test 2: Without opening hours data (None)
|
||||||
|
analysis_no_hours = MockCompanyWebsiteAnalysis(google_opening_hours=None)
|
||||||
|
|
||||||
|
if analysis_no_hours and analysis_no_hours.google_opening_hours:
|
||||||
|
status = 'complete'
|
||||||
|
score = max_score
|
||||||
|
else:
|
||||||
|
status = 'missing'
|
||||||
|
score = 0
|
||||||
|
|
||||||
|
print(f" Test 2 (no hours): status={status}, score={score}/{max_score}")
|
||||||
|
assert status == 'missing', f"Expected 'missing', got '{status}'"
|
||||||
|
assert score == 0, f"Expected 0, got {score}"
|
||||||
|
print(" ✅ PASSED")
|
||||||
|
|
||||||
|
# Test 3: No analysis object at all
|
||||||
|
analysis_none = None
|
||||||
|
|
||||||
|
if analysis_none and analysis_none.google_opening_hours:
|
||||||
|
status = 'complete'
|
||||||
|
score = max_score
|
||||||
|
else:
|
||||||
|
status = 'missing'
|
||||||
|
score = 0
|
||||||
|
|
||||||
|
print(f" Test 3 (no analysis): status={status}, score={score}/{max_score}")
|
||||||
|
assert status == 'missing', f"Expected 'missing', got '{status}'"
|
||||||
|
print(" ✅ PASSED")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_photos():
|
||||||
|
"""Test _check_photos method with google_photos_count field"""
|
||||||
|
print("\n=== Testing _check_photos() ===")
|
||||||
|
|
||||||
|
# Photo requirements from the service
|
||||||
|
PHOTO_REQUIREMENTS = {
|
||||||
|
'minimum': 3,
|
||||||
|
'recommended': 10,
|
||||||
|
'optimal': 25,
|
||||||
|
}
|
||||||
|
max_score = 15 # FIELD_WEIGHTS['photos']
|
||||||
|
|
||||||
|
# Test 1: With 10+ photos (complete)
|
||||||
|
analysis_many_photos = MockCompanyWebsiteAnalysis(google_photos_count=10)
|
||||||
|
|
||||||
|
photo_count = 0
|
||||||
|
if analysis_many_photos and analysis_many_photos.google_photos_count:
|
||||||
|
photo_count = analysis_many_photos.google_photos_count
|
||||||
|
|
||||||
|
if photo_count >= PHOTO_REQUIREMENTS['recommended']:
|
||||||
|
status = 'complete'
|
||||||
|
score = max_score
|
||||||
|
elif photo_count >= PHOTO_REQUIREMENTS['minimum']:
|
||||||
|
status = 'partial'
|
||||||
|
partial_score = max_score * (photo_count / PHOTO_REQUIREMENTS['recommended'])
|
||||||
|
score = min(partial_score, max_score * 0.7)
|
||||||
|
else:
|
||||||
|
status = 'missing'
|
||||||
|
score = 0
|
||||||
|
|
||||||
|
print(f" Test 1 (10 photos): status={status}, score={score}/{max_score}, count={photo_count}")
|
||||||
|
assert status == 'complete', f"Expected 'complete', got '{status}'"
|
||||||
|
assert score == max_score, f"Expected {max_score}, got {score}"
|
||||||
|
print(" ✅ PASSED")
|
||||||
|
|
||||||
|
# Test 2: With 5 photos (partial)
|
||||||
|
analysis_some_photos = MockCompanyWebsiteAnalysis(google_photos_count=5)
|
||||||
|
|
||||||
|
photo_count = 0
|
||||||
|
if analysis_some_photos and analysis_some_photos.google_photos_count:
|
||||||
|
photo_count = analysis_some_photos.google_photos_count
|
||||||
|
|
||||||
|
if photo_count >= PHOTO_REQUIREMENTS['recommended']:
|
||||||
|
status = 'complete'
|
||||||
|
score = max_score
|
||||||
|
elif photo_count >= PHOTO_REQUIREMENTS['minimum']:
|
||||||
|
status = 'partial'
|
||||||
|
partial_score = max_score * (photo_count / PHOTO_REQUIREMENTS['recommended'])
|
||||||
|
score = min(partial_score, max_score * 0.7)
|
||||||
|
else:
|
||||||
|
status = 'missing'
|
||||||
|
score = 0
|
||||||
|
|
||||||
|
print(f" Test 2 (5 photos): status={status}, score={score}/{max_score}, count={photo_count}")
|
||||||
|
assert status == 'partial', f"Expected 'partial', got '{status}'"
|
||||||
|
assert score > 0, f"Expected score > 0, got {score}"
|
||||||
|
print(" ✅ PASSED")
|
||||||
|
|
||||||
|
# Test 3: With 0 photos (missing)
|
||||||
|
analysis_no_photos = MockCompanyWebsiteAnalysis(google_photos_count=0)
|
||||||
|
|
||||||
|
photo_count = 0
|
||||||
|
if analysis_no_photos and analysis_no_photos.google_photos_count:
|
||||||
|
photo_count = analysis_no_photos.google_photos_count
|
||||||
|
|
||||||
|
if photo_count >= PHOTO_REQUIREMENTS['recommended']:
|
||||||
|
status = 'complete'
|
||||||
|
score = max_score
|
||||||
|
elif photo_count >= PHOTO_REQUIREMENTS['minimum']:
|
||||||
|
status = 'partial'
|
||||||
|
score = max_score * 0.7
|
||||||
|
else:
|
||||||
|
status = 'missing'
|
||||||
|
score = 0
|
||||||
|
|
||||||
|
print(f" Test 3 (0 photos): status={status}, score={score}/{max_score}, count={photo_count}")
|
||||||
|
assert status == 'missing', f"Expected 'missing', got '{status}'"
|
||||||
|
assert score == 0, f"Expected 0, got {score}"
|
||||||
|
print(" ✅ PASSED")
|
||||||
|
|
||||||
|
# Test 4: No analysis object
|
||||||
|
analysis_none = None
|
||||||
|
|
||||||
|
photo_count = 0
|
||||||
|
if analysis_none and analysis_none.google_photos_count:
|
||||||
|
photo_count = analysis_none.google_photos_count
|
||||||
|
|
||||||
|
if photo_count >= PHOTO_REQUIREMENTS['recommended']:
|
||||||
|
status = 'complete'
|
||||||
|
elif photo_count >= PHOTO_REQUIREMENTS['minimum']:
|
||||||
|
status = 'partial'
|
||||||
|
else:
|
||||||
|
status = 'missing'
|
||||||
|
|
||||||
|
print(f" Test 4 (no analysis): status={status}, count={photo_count}")
|
||||||
|
assert status == 'missing', f"Expected 'missing', got '{status}'"
|
||||||
|
print(" ✅ PASSED")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def test_field_weights():
|
||||||
|
"""Verify field weights are properly configured"""
|
||||||
|
print("\n=== Testing Field Weights ===")
|
||||||
|
|
||||||
|
FIELD_WEIGHTS = {
|
||||||
|
'name': 10,
|
||||||
|
'address': 10,
|
||||||
|
'phone': 8,
|
||||||
|
'website': 8,
|
||||||
|
'hours': 8,
|
||||||
|
'categories': 10,
|
||||||
|
'photos': 15,
|
||||||
|
'description': 12,
|
||||||
|
'services': 10,
|
||||||
|
'reviews': 9,
|
||||||
|
}
|
||||||
|
|
||||||
|
total = sum(FIELD_WEIGHTS.values())
|
||||||
|
print(f" Total weight: {total}/100")
|
||||||
|
assert total == 100, f"Expected total weight 100, got {total}"
|
||||||
|
print(" ✅ PASSED")
|
||||||
|
|
||||||
|
# Check individual weights
|
||||||
|
assert FIELD_WEIGHTS['hours'] == 8, "hours weight should be 8"
|
||||||
|
assert FIELD_WEIGHTS['photos'] == 15, "photos weight should be 15"
|
||||||
|
print(" hours weight: 8 ✅")
|
||||||
|
print(" photos weight: 15 ✅")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run all tests"""
|
||||||
|
print("=" * 60)
|
||||||
|
print("GBP Audit Service - Field Checks Test")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
all_passed = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
all_passed &= test_field_weights()
|
||||||
|
all_passed &= test_check_hours()
|
||||||
|
all_passed &= test_check_photos()
|
||||||
|
except AssertionError as e:
|
||||||
|
print(f"\n❌ TEST FAILED: {e}")
|
||||||
|
all_passed = False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n❌ ERROR: {e}")
|
||||||
|
all_passed = False
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
if all_passed:
|
||||||
|
print("✅ ALL TESTS PASSED")
|
||||||
|
print("=" * 60)
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
print("❌ SOME TESTS FAILED")
|
||||||
|
print("=" * 60)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
||||||
Loading…
Reference in New Issue
Block a user