| """ |
| Schema migration: 2026 UNESCO upgrade. Idempotent; safe on SQLite + Postgres. |
| |
| Adds to items: |
| - alignment_scores (JSON TEXT) + alignment_version (framework alignment) |
| - coauthor_countries / african_country_count / is_intra_african (collaboration) |
| - openalex_id / counts_by_year / cited_by_count / african_citation_share (citations) |
| - ark / ark_assigned_at (ARK persistent identifiers) |
| |
| New tables (item_affiliations, alignment_aggregates) are created by |
| scripts/init_db.py via Base.metadata.create_all β run init_db.py first. |
| """ |
|
|
| import os |
| import sys |
|
|
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
|
|
| from sqlalchemy import inspect, text |
|
|
| from uraas.database import engine |
|
|
| |
| ITEMS_COLUMNS = [ |
| ("alignment_scores", "TEXT"), |
| ("alignment_version", "INTEGER DEFAULT 0"), |
| ("coauthor_countries", "TEXT"), |
| ("african_country_count", "INTEGER DEFAULT 0"), |
| ("is_intra_african", "BOOLEAN DEFAULT FALSE"), |
| ("openalex_id", "VARCHAR(64)"), |
| ("counts_by_year", "TEXT"), |
| ("cited_by_count", "INTEGER DEFAULT 0"), |
| ("african_citation_share", "FLOAT"), |
| ("ark", "VARCHAR(128)"), |
| ("ark_assigned_at", "TIMESTAMP"), |
| ] |
|
|
| INDEXES = [ |
| "CREATE INDEX IF NOT EXISTS ix_items_is_intra_african ON items (is_intra_african)", |
| "CREATE UNIQUE INDEX IF NOT EXISTS ux_items_ark ON items (ark)", |
| ] |
|
|
|
|
| def column_exists(insp, table: str, column: str) -> bool: |
| return column in {c["name"] for c in insp.get_columns(table)} |
|
|
|
|
| def main() -> int: |
| print("Migration: 2026 upgrade (alignment / collaboration / citations / ARK)") |
|
|
| dialect = engine.dialect.name |
| print(f"Dialect: {dialect}") |
|
|
| insp = inspect(engine) |
| statements = [] |
| for column, ddl_type in ITEMS_COLUMNS: |
| if column_exists(insp, "items", column): |
| print(f" {column} already present, skipping") |
| continue |
| if dialect == "sqlite" and "BOOLEAN" in ddl_type: |
| |
| ddl_type = ddl_type.replace("BOOLEAN", "INTEGER").replace("FALSE", "0") |
| statements.append(f"ALTER TABLE items ADD COLUMN {column} {ddl_type}") |
|
|
| if statements: |
| with engine.begin() as conn: |
| for stmt in statements: |
| print(f" -> {stmt}") |
| conn.execute(text(stmt)) |
| else: |
| print(" All columns already present.") |
|
|
| for stmt in INDEXES: |
| try: |
| with engine.begin() as conn: |
| conn.execute(text(stmt)) |
| print(f" -> {stmt.split(' ON ')[0].replace('CREATE ', '').strip()} ensured") |
| except Exception as e: |
| print(f" (index creation skipped: {e})") |
|
|
| print("Done.") |
| return 0 |
|
|
|
|
| if __name__ == "__main__": |
| sys.exit(main()) |
|
|