Spaces:
Running
Running
| 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 | |