nordabiz/database/migrations/add_gbp_audit.sql
Maciej Pienczyn ecf811f168 auto-claude: subtask-2-3 - Create database migration script for GBP audit tab
- 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>
2026-01-08 19:08:05 +01:00

217 lines
7.5 KiB
PL/PgSQL

-- ============================================================
-- NordaBiz - Migration: Google Business Profile (GBP) Audit Tables
-- ============================================================
-- Created: 2026-01-08
-- Description:
-- - Creates gbp_audits table for storing GBP completeness audit results
-- - Tracks field-by-field status with JSONB for flexibility
-- - Stores AI-generated recommendations
-- - Includes indexes and helpful views
--
-- Usage:
-- PostgreSQL: psql -h localhost -U nordabiz_app -d nordabiz -f add_gbp_audit.sql
-- SQLite: Not fully supported (JSONB columns)
-- ============================================================
-- ============================================================
-- 1. MAIN GBP_AUDITS TABLE
-- ============================================================
CREATE TABLE IF NOT EXISTS gbp_audits (
id SERIAL PRIMARY KEY,
-- Company reference
company_id INTEGER NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
-- Audit timestamp
audit_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Completeness scoring (0-100)
completeness_score INTEGER,
-- Field-by-field status tracking (JSONB)
-- Example: {"name": {"status": "complete", "value": "Company Name"}, "phone": {"status": "missing"}, ...}
fields_status JSONB,
-- AI-generated recommendations (JSONB)
-- Example: [{"priority": "high", "field": "description", "recommendation": "Add a detailed business description..."}, ...]
recommendations JSONB,
-- Individual field completion flags
has_name BOOLEAN DEFAULT FALSE,
has_address BOOLEAN DEFAULT FALSE,
has_phone BOOLEAN DEFAULT FALSE,
has_website BOOLEAN DEFAULT FALSE,
has_hours BOOLEAN DEFAULT FALSE,
has_categories BOOLEAN DEFAULT FALSE,
has_photos BOOLEAN DEFAULT FALSE,
has_description BOOLEAN DEFAULT FALSE,
has_services BOOLEAN DEFAULT FALSE,
has_reviews BOOLEAN DEFAULT FALSE,
-- Photo metrics
photo_count INTEGER DEFAULT 0,
logo_present BOOLEAN DEFAULT FALSE,
cover_photo_present BOOLEAN DEFAULT FALSE,
-- Review metrics
review_count INTEGER DEFAULT 0,
average_rating NUMERIC(2, 1),
-- Google Place integration
google_place_id VARCHAR(100),
google_maps_url VARCHAR(500),
-- Audit metadata
audit_source VARCHAR(50) DEFAULT 'manual', -- manual, automated, api
audit_version VARCHAR(20) DEFAULT '1.0',
audit_errors TEXT,
-- Timestamps
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE gbp_audits IS 'Google Business Profile completeness audit results';
COMMENT ON COLUMN gbp_audits.completeness_score IS 'Overall GBP completeness score 0-100';
COMMENT ON COLUMN gbp_audits.fields_status IS 'Field-by-field status with values as JSON';
COMMENT ON COLUMN gbp_audits.recommendations IS 'AI-generated improvement recommendations as JSON array';
COMMENT ON COLUMN gbp_audits.audit_source IS 'How audit was triggered: manual, automated, api';
COMMENT ON COLUMN gbp_audits.google_place_id IS 'Google Places API place_id for verification';
COMMENT ON COLUMN gbp_audits.google_maps_url IS 'Direct link to Google Maps listing';
-- ============================================================
-- 2. INDEXES FOR PERFORMANCE
-- ============================================================
CREATE INDEX IF NOT EXISTS idx_gbp_audits_company ON gbp_audits(company_id);
CREATE INDEX IF NOT EXISTS idx_gbp_audits_date ON gbp_audits(audit_date);
CREATE INDEX IF NOT EXISTS idx_gbp_audits_score ON gbp_audits(completeness_score);
CREATE INDEX IF NOT EXISTS idx_gbp_audits_company_date ON gbp_audits(company_id, audit_date DESC);
-- ============================================================
-- 3. UPDATE TRIGGER FOR updated_at
-- ============================================================
CREATE OR REPLACE FUNCTION gbp_audits_update_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trigger_gbp_audits_update ON gbp_audits;
CREATE TRIGGER trigger_gbp_audits_update
BEFORE UPDATE ON gbp_audits
FOR EACH ROW
EXECUTE FUNCTION gbp_audits_update_timestamp();
-- ============================================================
-- 4. GBP AUDIT OVERVIEW VIEW
-- ============================================================
CREATE OR REPLACE VIEW v_company_gbp_overview AS
SELECT
c.id,
c.name,
c.slug,
c.website,
cat.name as category_name,
ga.completeness_score,
ga.has_name,
ga.has_address,
ga.has_phone,
ga.has_website,
ga.has_hours,
ga.has_categories,
ga.has_photos,
ga.has_description,
ga.has_services,
ga.has_reviews,
ga.photo_count,
ga.review_count,
ga.average_rating,
ga.google_place_id,
ga.audit_date,
ga.audit_source,
-- Score category
CASE
WHEN ga.completeness_score >= 90 THEN 'excellent'
WHEN ga.completeness_score >= 70 THEN 'good'
WHEN ga.completeness_score >= 50 THEN 'needs_work'
WHEN ga.completeness_score IS NOT NULL THEN 'poor'
ELSE 'not_audited'
END as score_category
FROM companies c
LEFT JOIN categories cat ON c.category_id = cat.id
LEFT JOIN LATERAL (
SELECT * FROM gbp_audits
WHERE company_id = c.id
ORDER BY audit_date DESC
LIMIT 1
) ga ON TRUE
ORDER BY ga.completeness_score DESC NULLS LAST;
COMMENT ON VIEW v_company_gbp_overview IS 'Latest GBP audit results per company for dashboard';
-- ============================================================
-- 5. GBP AUDIT HISTORY VIEW
-- ============================================================
CREATE OR REPLACE VIEW v_gbp_audit_history AS
SELECT
ga.id as audit_id,
c.id as company_id,
c.name as company_name,
c.slug as company_slug,
ga.completeness_score,
ga.audit_date,
ga.audit_source,
ga.audit_version,
-- Previous score for comparison
LAG(ga.completeness_score) OVER (
PARTITION BY ga.company_id
ORDER BY ga.audit_date
) as previous_score,
-- Score change
ga.completeness_score - LAG(ga.completeness_score) OVER (
PARTITION BY ga.company_id
ORDER BY ga.audit_date
) as score_change
FROM gbp_audits ga
JOIN companies c ON ga.company_id = c.id
ORDER BY ga.audit_date DESC;
COMMENT ON VIEW v_gbp_audit_history IS 'GBP audit history with score trend tracking';
-- ============================================================
-- 6. GRANTS FOR APPLICATION USER
-- ============================================================
-- Grant permissions on table
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE gbp_audits TO nordabiz_app;
-- Grant permissions on sequence
GRANT USAGE, SELECT ON SEQUENCE gbp_audits_id_seq TO nordabiz_app;
-- Grant permissions on views
GRANT SELECT ON v_company_gbp_overview TO nordabiz_app;
GRANT SELECT ON v_gbp_audit_history TO nordabiz_app;
-- ============================================================
-- MIGRATION COMPLETE
-- ============================================================
-- Verify migration (PostgreSQL only)
DO $$
BEGIN
RAISE NOTICE 'GBP Audit migration completed successfully!';
RAISE NOTICE 'Created:';
RAISE NOTICE ' - Table: gbp_audits';
RAISE NOTICE ' - Indexes: company_id, audit_date, completeness_score, company_date';
RAISE NOTICE ' - Trigger: updated_at auto-update';
RAISE NOTICE ' - Views: v_company_gbp_overview, v_gbp_audit_history';
RAISE NOTICE ' - Grants: nordabiz_app permissions';
END $$;