- Created tests/test_gbp_audit_field_checks.py with comprehensive tests
- Tests verify _check_hours() correctly uses google_opening_hours field
- Tests verify _check_photos() correctly uses google_photos_count field
- Tests cover edge cases: null values, missing analysis, partial data
- All field check logic validated: complete/partial/missing status
- Field weights verified: hours=8, photos=15, total=100
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixed a bug where google_opening_hours and google_photos_count were being
fetched from the Google Places API but not passed through to the result
dictionary correctly:
- Changed 'opening_hours' key to 'google_opening_hours' to match what
save_audit_result() expects
- Added 'google_photos_count' to the result dictionary
Verified with dry-run: INPI company now shows opening hours schedule
and 10 photos count from Google Business Profile.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add database migration script to add google_opening_hours (JSONB) and
google_photos_count (INTEGER) columns to company_website_analysis table.
These columns are needed for storing GBP data from Google Places API:
- google_opening_hours: stores weekday_text, open_now, and periods data
- google_photos_count: stores count of photos from Google Business profile
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changed _check_photos method to read from analysis.google_photos_count
instead of total_images. This provides actual GBP photo count rather
than estimated website images.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changed _check_hours() in GBP Audit Service to read opening hours from
google_opening_hours field instead of google_business_status. The method
now properly returns the actual hours value when available.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Added google_opening_hours and google_photos_count to INSERT column list
- Added corresponding placeholders to VALUES list
- Added to ON CONFLICT UPDATE SET clause
- Added to parameter dictionary reading from google_reviews result
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add google_photos_count to result dictionary initialization
- Extract photos count from API response using len(place['photos'])
- Update logging to include photos count in output
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added 'photos' field to the fields list in get_place_details() method
to enable fetching business photos from Google Places API.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Added google_opening_hours Column(JSONB) for storing GBP opening hours
- Added google_photos_count Column(Integer) for storing GBP photos count
- Both columns added to GOOGLE BUSINESS section alongside existing google_* columns
BLOCKED: Cannot verify - dependencies not met:
- Local SQLite database empty (no tables initialized)
- Google Places API not enabled for current API key
- Previous subtask (4-2) was blocked, no audit ran
Documented blockers and options to unblock in build-progress.txt.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add --company-slug argument to social_media_audit.py for easier testing
- Add get_company_id_by_slug() method to SocialMediaAuditor class
- Add python-dotenv support to load .env file from project root
- Create verify_google_places.py script for direct API testing
Note: Full verification blocked - current API key (PageSpeed) doesn't have
Places API enabled. Requires enabling Places API in Google Cloud Console
for project NORDABIZNES (gen-lang-client-0540794446).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixed bug in social media exclusion logic that was too aggressive.
The substring check `any(ex in match.lower() for ex in excludes)`
was incorrectly excluding valid usernames containing exclusion
strings (e.g., 'testcompany' was excluded because it contained 'p').
Changed to exact match only to properly handle Instagram post URLs
(`instagram.com/p/...`) without false positives on valid usernames.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added test for search_google_reviews method to handle API errors gracefully.
The test mocks GooglePlacesSearcher to simulate a RequestException during
get_place_details and verifies that the method returns None values instead
of crashing.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added test_search_google_reviews test in TestBraveSearcherGoogleReviews class
that verifies successful Google reviews retrieval via Google Places API:
- Mocks complete place lookup with realistic place_id
- Mocks full place details response including rating, reviews count,
opening hours, and business status
- Verifies all expected fields are correctly returned
- Validates correct API calls are made with expected parameters
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add comprehensive unit tests for social_media_audit.py:
- WebsiteAuditor tests: SSL checks, hosting detection, HTML parsing, social media extraction
- GooglePlacesSearcher tests: find_place, get_place_details, API error handling
- BraveSearcher tests: Google reviews search, fallback mechanisms
- SocialMediaAuditor tests: company auditing, result saving, integration tests
- Pattern tests: hosting providers, social media URL patterns, exclusion rules
Tests follow the same structure and patterns as test_seo_audit.py.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add google_places_searcher attribute to SocialMediaAuditor
- Initialize GooglePlacesSearcher if GOOGLE_PLACES_API_KEY env var is set
- Update audit_company() to use Places API directly when available
- Fallback to Brave Search when API key not configured
- Log which data source is being used for reviews
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implemented actual Google reviews data collection in BraveSearcher class:
- Uses GooglePlacesSearcher to find company and get place details
- Returns google_rating, google_reviews_count, opening_hours, business_status
- Falls back to Brave Search API parsing when Google API key not available
- Added _search_brave_for_reviews() helper for fallback implementation
- Proper error handling and logging throughout
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements GooglePlacesSearcher class with:
- find_place() method: searches for business by name and city
using Google Places findplacefromtext API
- get_place_details() method: retrieves rating, review count,
opening hours, business status, phone, and website
Features:
- Uses GOOGLE_PLACES_API_KEY environment variable
- Comprehensive error handling (timeout, request errors)
- Polish language locale support
- Follows existing BraveSearcher class pattern
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes:
- Added GBP audit link to company profile contact bar
- Link visible to admins on all profiles
- Link visible to regular users only on their own company profile
- Styled consistently with contact bar design (Google blue)
Verified:
- Template syntax balanced (blocks, if, for)
- Route gbp_audit_dashboard exists in app.py
QA Fix Session: 1
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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>
Add user-facing GBP audit dashboard route at /audit/gbp/<company_slug>:
- Requires login (@login_required)
- Admin users can view audit for any company
- Regular users can only view audit for their own company
- Passes can_audit flag to template for run audit button visibility
- Gracefully handles missing audit data (template shows "no audit" state)
- Logs audit dashboard views for monitoring
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Creates templates/gbp_audit.html for GBP completeness audit display:
- Score circle with visual conic gradient showing completion percentage
- Field status cards (10 fields) with icons, badges, and progress bars
- Recommendations list sorted by priority (high/medium/low)
- Breadcrumb navigation and responsive mobile layout
- Loading overlay and info modal for audit operations
- Polish UI text matching existing platform conventions
Template follows patterns from admin_seo_dashboard.html and integrates
with the GBP audit API endpoints created in subtask-3-1.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added GBP (Google Business Profile) audit API endpoints:
- GET /api/gbp/audit/health - Health check for GBP audit service
- GET /api/gbp/audit - Fetch latest audit results by company_id or slug
- GET /api/gbp/audit/<slug> - Fetch audit results by slug path
- POST /api/gbp/audit - Run GBP audit for a company
Features:
- Health endpoint returns service status and version
- GET endpoints return completeness score, field status, and recommendations
- POST endpoint runs audit and saves results (configurable)
- Access control: members can audit own company, admins can audit any
- Rate limited to 20 requests per hour
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create gbp_audits table with all required fields:
- completeness_score (0-100)
- fields_status and recommendations (JSONB)
- Individual field flags (has_name, has_phone, etc.)
- Photo and review metrics
- Google Place integration fields
- Audit metadata (source, version, errors)
- Add indexes for company_id, audit_date, and score
- Add update trigger for updated_at timestamp
- Create views: v_company_gbp_overview, v_gbp_audit_history
- Include GRANT statements for nordabiz_app user
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add GBPAudit SQLAlchemy model for Google Business Profile audit tracking:
- Completeness score (0-100) with score category property
- Field-by-field status tracking (JSONB)
- AI-generated recommendations storage (JSONB)
- Individual field boolean flags (name, address, phone, etc.)
- Photo/review metrics
- Google Place ID integration
- Audit metadata and timestamps
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Widoczny link do Google PageSpeed Insights (Lighthouse) w nagłówku panelu.
Użytkownik/admin wie skąd pochodzą metryki.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Konfiguracja Google PageSpeed Insights API
- Metryki i interpretacja wyników
- Skrypty uruchamiania audytów
- WAŻNE: połączenie z bazą przez localhost
- Tabela seo_metrics
- UI stylizowanych modali
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Zamieniono natywne confirm/alert na ładne modale CSS
- Dodano link "Audyt SEO" w menu administracyjnym
- Modal z animacją slide-in, ikony ostrzeżenia/info
- Obsługa zamykania przez backdrop
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Problem: /admin/seo zwracał błąd 500 z AttributeError: category
Przyczyna: Company.category jest relacją SQLAlchemy (obiektem), nie stringiem.
Rozwiązanie: Użycie Category.name.label('category_name') z właściwym JOIN.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Naprawione błędy wprowadzone podczas synchronizacji:
- Przywrócono sekcję company-logo z obrazkiem w karcie firmy
- Przywrócono style CSS dla logo (100% szerokość, 80px wysokość)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Naprawione błędy wprowadzone podczas synchronizacji:
- Przywrócono sekcję company-logo-header z obrazkiem
- Przywrócono style CSS dla logo (240x240px, shadow, border-radius)
- Przywrócono przyciski Social Media (Facebook, Instagram, LinkedIn, YouTube, X, TikTok)
- Przywrócono style kolorów dla platform social media
- Naprawiono pole website_url → website
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Created comprehensive test suite for SEO admin dashboard functionality:
- tests/test_admin_seo_dashboard.py with 8 test functions
- Dashboard data query verification (SQLAlchemy joins)
- Statistics calculation (good/medium/poor/not_audited counts)
- Score color coding (green/yellow/red thresholds)
- Sorting logic (name, score, date)
- Filtering logic (category, score range, text search)
- Drill-down links validation
- API response structure verification
- Template data requirements verification
All 8 dashboard tests pass. All 64 existing SEO unit tests pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Changed PostgreSQL-specific ANY(:ids) to use IN clause with
dynamic placeholders for SQLite/PostgreSQL compatibility
- Verified SEO audit dry-run extracts all metrics correctly:
- HTTP status, load time, final URL
- Meta title, H1 count, image analysis
- Structured data detection
- robots.txt, sitemap.xml, indexability
- Overall SEO score calculation (95 for pixlab.pl)
Note: Company ID 26 has no website configured, tested with ID 1 instead.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Features:
- Single company HTML reports with full SEO audit data
- Batch HTML summary reports for multiple companies
- JSON exports for integration with other tools
- SEO recommendations based on audit findings
- CLI interface with --company-id, --batch, --all selection
- Output format options: --html, --json
- Score visualization with color-coded badges
- Core Web Vitals section with threshold indicators
- Issues and recommendations sections
- Statistics calculation for batch reports
- Polish language support in reports
Usage examples:
- python seo_report_generator.py --company-id 26 --html
- python seo_report_generator.py --all --html --output ./reports
- python seo_report_generator.py --batch 1-10 --json
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extend templates/company_detail.html to display SEO metrics when
seo_audited_at exists:
- Overall SEO score banner with color-coded gradient (green/yellow/red)
- PageSpeed scores grid (SEO, Performance, Accessibility, Best Practices)
- On-Page SEO card (meta tags, headings, images, links)
- Technical SEO card (canonical, indexing, structured data, OG tags)
- Core Web Vitals section (LCP, FID, CLS) when available
- Structured data types display (Schema.org)
- Score legend with color coding explanation
- Responsive design for mobile/tablet screens
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added /admin/seo route with:
- Admin-only access with @login_required and is_admin check
- Fetches all active companies with SEO analysis data using outerjoin
- Calculates statistics: good_count (90-100), medium_count (50-89),
poor_count (0-49), not_audited_count, avg_score
- Gets unique categories for filter dropdown
- Passes companies, stats, categories, and now to template
- Uses CompanyRow class for Jinja2 attribute access
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Created templates/admin_seo_dashboard.html with:
- Sortable table showing all companies with SEO metrics
- PageSpeed scores color-coded (green/yellow/red for good/medium/poor)
- Summary stats cards showing score distribution
- Filtering by category, score range, and search
- Drill-down links to company profiles
- Single company audit trigger button (admin only)
- Responsive design with mobile-friendly layout
- Legend explaining score color coding
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added POST /api/seo/audit endpoint to trigger SEO audits for companies:
- Admin-only access with current_user.is_admin check
- Rate limited to 10 requests per hour per user
- Accepts company_id or slug in JSON body
- Runs full SEO audit (PageSpeed, on-page, technical)
- Saves results to database and returns audit data
- Comprehensive error handling and logging
- Uses existing _build_seo_audit_response helper for response format
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added two API endpoints for retrieving SEO audit data:
- GET /api/seo/audit?company_id=X or ?slug=Y
- GET /api/seo/audit/<slug>
Features:
- Returns pagespeed scores (SEO, performance, accessibility, best practices)
- Returns on-page metrics (meta tags, headings, images, links, structured data)
- Returns technical SEO checks (SSL, sitemap, robots.txt, mobile-friendly)
- Returns Core Web Vitals (LCP, FID, CLS)
- Automatically generates issues list from audit data
- Handles companies without SEO audit gracefully
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Enhanced save_audit_result method with complete column coverage
- Added missing columns to idempotent upsert query:
- broken_links_count (for future link checking)
- viewport_configured (derived from meta viewport tag)
- is_mobile_friendly (derived from viewport content)
- has_hreflang (for international SEO detection)
- All 45+ SEO columns now properly mapped for database upserts
- ON CONFLICT (company_id) DO UPDATE ensures idempotent operations
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>