conflictData / api /bootstrap.py
Hardik Singh
Frontier Overhaul
bc1ac55
import logging
from api.database import db
import asyncio
log = logging.getLogger("api.bootstrap")
TABLE_ACTIVE_CONFLICTS = """
CREATE TABLE IF NOT EXISTS active_conflicts (
conflict_id SERIAL PRIMARY KEY,
name VARCHAR(200),
countries TEXT[],
region VARCHAR(100),
start_date DATE,
status VARCHAR(20) DEFAULT 'ACTIVE',
intensity VARCHAR(20) DEFAULT 'CRISIS',
total_events INTEGER DEFAULT 0,
last_event_at TIMESTAMP DEFAULT NOW()
);
"""
TABLE_CONFLICT_EVENTS = """
CREATE TABLE IF NOT EXISTS conflict_events (
id BIGSERIAL PRIMARY KEY,
event_id VARCHAR(100) UNIQUE NOT NULL,
source VARCHAR(20) NOT NULL,
source_reliability VARCHAR(10) DEFAULT 'MEDIUM',
event_time TIMESTAMP NOT NULL,
event_date DATE NOT NULL,
country VARCHAR(100) NOT NULL,
country_iso3 CHAR(3) NOT NULL,
region VARCHAR(100),
admin1 VARCHAR(100),
admin2 VARCHAR(100),
city VARCHAR(100),
lat DECIMAL(9,6),
lon DECIMAL(10,6),
geom GEOGRAPHY(POINT,4326),
geo_precision SMALLINT DEFAULT 3,
event_type VARCHAR(50),
event_subtype VARCHAR(80),
interaction_code VARCHAR(60),
actor1 VARCHAR(200),
actor1_type VARCHAR(50),
actor2 VARCHAR(200),
actor2_type VARCHAR(50),
fatalities INTEGER DEFAULT 0,
fatalities_civilians INTEGER DEFAULT 0,
fatalities_confidence VARCHAR(10) DEFAULT 'LOW',
severity VARCHAR(20),
severity_score DECIMAL(4,2),
title VARCHAR(500),
notes TEXT,
tags TEXT[],
source_url TEXT,
conflict_name VARCHAR(200),
conflict_id INTEGER REFERENCES active_conflicts(conflict_id),
category VARCHAR(20) DEFAULT 'GENERAL',
ingested_at TIMESTAMP DEFAULT NOW()
);
"""
TABLE_INTEL_ARTICLES = """
CREATE TABLE IF NOT EXISTS intel_articles (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
author VARCHAR(100),
tags TEXT[],
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
"""
SEARCH_VECTOR_ADD = """
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='intel_articles' AND column_name='search_vector') THEN
ALTER TABLE intel_articles ADD COLUMN search_vector TSVECTOR
GENERATED ALWAYS AS (to_tsvector('english', title || ' ' || content)) STORED;
END IF;
END $$;
"""
async def bootstrap_db():
log.info("Starting database bootstrap process...")
try:
async with db.pool.acquire() as conn:
# 1. Extensions
await conn.execute("CREATE EXTENSION IF NOT EXISTS postgis;")
# 2. Tables
await conn.execute(TABLE_ACTIVE_CONFLICTS)
await conn.execute(TABLE_CONFLICT_EVENTS)
await conn.execute(TABLE_INTEL_ARTICLES)
# 3. Features
await conn.execute(SEARCH_VECTOR_ADD)
# 4. Indexes (Safe Creation)
await conn.execute("CREATE INDEX IF NOT EXISTS idx_event_time ON conflict_events(event_time DESC);")
await conn.execute("CREATE INDEX IF NOT EXISTS idx_country_time ON conflict_events(country_iso3, event_time DESC);")
await conn.execute("CREATE INDEX IF NOT EXISTS idx_geom ON conflict_events USING GIST(geom);")
await conn.execute("CREATE INDEX IF NOT EXISTS idx_articles_search ON intel_articles USING GIN(search_vector);")
# 5. Geocoding Metadata V2 Migration
log.info("Checking for Geospatial Metadata V2 columns...")
await conn.execute("""
ALTER TABLE conflict_events
ADD COLUMN IF NOT EXISTS geo_confidence NUMERIC(4,3),
ADD COLUMN IF NOT EXISTS geo_method VARCHAR(40),
ADD COLUMN IF NOT EXISTS geocode_provider VARCHAR(40),
ADD COLUMN IF NOT EXISTS location_raw TEXT,
ADD COLUMN IF NOT EXISTS location_admin1 VARCHAR(100),
ADD COLUMN IF NOT EXISTS geo_validation_flags TEXT[],
ADD COLUMN IF NOT EXISTS ai_analysis TEXT,
ADD COLUMN IF NOT EXISTS verification_count INTEGER DEFAULT 1,
ADD COLUMN IF NOT EXISTS source_urls TEXT[] DEFAULT '{}',
ADD COLUMN IF NOT EXISTS strategic_relevance VARCHAR(15) DEFAULT 'LOW';
CREATE INDEX IF NOT EXISTS idx_geo_confidence ON conflict_events(geo_confidence);
CREATE INDEX IF NOT EXISTS idx_ai_analysis ON conflict_events USING GIN(to_tsvector('english', COALESCE(ai_analysis, '')));
CREATE INDEX IF NOT EXISTS idx_verification ON conflict_events(verification_count DESC);
""")
log.info("[Bootstrap] Database schema validation and initialization complete.")
except Exception as e:
log.error(f"[Bootstrap] Critical Failure: {e}")
# We don't raise here to allow the API to heartbeat even if DB is partially broken