Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """ | |
| USLaP Schema Consolidation — 185 tables → ~80 | |
| بِسْمِ اللَّهِ الرَّحْمَٰنِ الرَّحِيمِ | |
| Phase 1: RU mirrors → EN parents (13 eliminated) | |
| Phase 2: Body 46 → 5 tables (41 eliminated) | |
| Phase 3: Bitig 15 → 10 (5 eliminated) | |
| Phase 4: Foundation/Mechanism consolidation | |
| Usage: python3 consolidate_schema_v5.py [--dry-run] | |
| """ | |
| import sqlite3 | |
| import json | |
| import os | |
| import sys | |
| from datetime import datetime | |
| BASE = os.path.dirname(os.path.abspath(__file__)) | |
| DB = os.path.join(BASE, "uslap_database_v3.db") | |
| DRY_RUN = '--dry-run' in sys.argv | |
| def run(): | |
| conn = sqlite3.connect(DB) | |
| conn.execute("PRAGMA foreign_keys = OFF") | |
| conn.execute("PRAGMA journal_mode = WAL") | |
| print("=" * 70) | |
| print(f"USLaP Schema Consolidation {'(DRY RUN)' if DRY_RUN else ''}") | |
| print(f"Started: {datetime.now().isoformat()}") | |
| print("=" * 70) | |
| # Save triggers | |
| print("\n[PREP] Saving triggers...") | |
| all_triggers = conn.execute("SELECT name, sql FROM sqlite_master WHERE type='trigger'").fetchall() | |
| print(f" {len(all_triggers)} triggers saved") | |
| # Drop ALL triggers for migration (NOT in dry run) | |
| if not DRY_RUN: | |
| for tname, _ in all_triggers: | |
| conn.execute(f'DROP TRIGGER IF EXISTS "{tname}"') | |
| conn.commit() | |
| print(" All triggers dropped") | |
| else: | |
| print(" (dry run — triggers preserved)") | |
| # Drop existing compatibility views that conflict | |
| existing_views = set() | |
| for view_name in ['a1_записи', 'a1_entries']: | |
| vtype = conn.execute("SELECT type FROM sqlite_master WHERE name=?", (view_name,)).fetchone() | |
| if vtype and vtype[0] == 'view': | |
| existing_views.add(view_name) | |
| if not DRY_RUN: | |
| conn.execute(f'DROP VIEW IF EXISTS "{view_name}"') | |
| print(f" {'Would drop' if DRY_RUN else 'Dropped'} view: {view_name}") | |
| # ================================================================ | |
| # PHASE 1: RU MIRRORS → EN PARENTS | |
| # ================================================================ | |
| print("\n" + "=" * 70) | |
| print("PHASE 1: RU Mirrors → EN Parents") | |
| print("=" * 70) | |
| # Add lang column to parent tables (skip views) | |
| lang_tables = ['entries', 'names_of_allah', 'a3_quran_refs', 'a4_derivatives', | |
| 'a5_cross_refs', 'a6_country_names'] | |
| for tbl in lang_tables: | |
| ttype = conn.execute("SELECT type FROM sqlite_master WHERE name=?", (tbl,)).fetchone() | |
| if not ttype or ttype[0] == 'view': | |
| print(f" Skipping {tbl} (view or missing)") | |
| continue | |
| cols = [r[1] for r in conn.execute(f"PRAGMA table_info('{tbl}')").fetchall()] | |
| if 'lang' not in cols: | |
| if not DRY_RUN: | |
| conn.execute(f'ALTER TABLE "{tbl}" ADD COLUMN lang TEXT DEFAULT "EN"') | |
| conn.execute(f'UPDATE "{tbl}" SET lang="EN" WHERE lang IS NULL') | |
| print(f" {tbl}: lang column {'would be added' if DRY_RUN else 'added'}") | |
| else: | |
| print(f" {tbl}: lang column exists") | |
| if not DRY_RUN: | |
| conn.commit() | |
| # Merge a1_записи → entries (SKIP if it's already a view) | |
| a1_type = conn.execute("SELECT type FROM sqlite_master WHERE name='a1_записи'").fetchone() | |
| if a1_type and a1_type[0] == 'view': | |
| print(" a1_записи is already a VIEW (data in entries) — skipping merge") | |
| ru_rows = [] | |
| else: | |
| ru_cols = {r[1]: r for r in conn.execute("PRAGMA table_info('a1_записи')").fetchall()} | |
| col_map = { | |
| 'запись_id': 'entry_id', 'балл': 'score', 'рус_термин': 'ru_term', | |
| 'ар_слово': 'ar_word', 'корень_id': 'root_id', 'корневые_буквы': 'root_letters', | |
| 'коранич_значение': 'qur_meaning', 'паттерн': 'pattern', | |
| 'тип_инверсии': 'inversion_type', 'сеть_id': 'network_id', | |
| 'имя_аллаха_id': 'allah_name_id', 'фонетическая_цепь': 'phonetic_chain', | |
| 'исходная_форма': 'source_form', 'основание': 'foundation_refs', | |
| } | |
| ru_rows = conn.execute('SELECT * FROM "a1_записи"').fetchall() | |
| ru_col_names = [r[1] for r in conn.execute("PRAGMA table_info('a1_записи')").fetchall()] | |
| migrated = 0 | |
| for row in ru_rows: | |
| row_dict = dict(zip(ru_col_names, row)) | |
| # Map to EN column names | |
| en_vals = {} | |
| for ru_name, value in row_dict.items(): | |
| if ru_name.startswith('quf_'): | |
| en_vals[ru_name] = value | |
| elif ru_name in col_map: | |
| en_vals[col_map[ru_name]] = value | |
| en_vals['lang'] = 'RU' | |
| en_vals['en_term'] = row_dict.get('рус_термин', '') # RU term goes in en_term for now | |
| # Insert | |
| cols_str = ', '.join(f'"{k}"' for k in en_vals.keys()) | |
| placeholders = ', '.join('?' * len(en_vals)) | |
| if not DRY_RUN: | |
| try: | |
| conn.execute(f'INSERT INTO entries ({cols_str}) VALUES ({placeholders})', | |
| list(en_vals.values())) | |
| migrated += 1 | |
| except Exception as e: | |
| if migrated == 0: | |
| print(f" WARN: {e}") | |
| conn.commit() | |
| print(f" a1_записи → entries: {migrated}/{len(ru_rows)} rows migrated") | |
| # Merge a2_имена_аллаха → names_of_allah | |
| a2_map = {'имя_id': 'allah_id', 'арабское_имя': 'arabic_name', | |
| 'транслитерация': 'transliteration', 'значение': 'meaning', | |
| 'коран_ссылка': 'qur_ref', 'записи_id': 'entry_ids', 'корень_id': 'root_id'} | |
| _migrate_ru_table(conn, 'a2_имена_аллаха', 'names_of_allah', a2_map) | |
| # Merge a3 | |
| a3_map = {'ссылка_id': 'ref_id', 'сура': 'surah', 'аят': 'ayah', | |
| 'арабский_текст': 'arabic_text', 'кв_id': 'qv_id', | |
| 'слой': 'layer_ref', 'записи_id': 'entry_ids', | |
| 'сеть_id': 'network_id', 'релевантность': 'relevance'} | |
| _migrate_ru_table(conn, 'a3_коранические_ссылки', 'a3_quran_refs', a3_map) | |
| # Merge a4 | |
| a4_map = {'произв_id': 'deriv_id', 'запись_id': 'entry_id', | |
| 'рус_термин': 'en_term', 'производное': 'derivative', | |
| 'тип_связи': 'link_type'} | |
| _migrate_ru_table(conn, 'a4_производные', 'a4_derivatives', a4_map) | |
| # Merge a5 | |
| a5_map = {'перекр_id': 'xref_id', 'от_id': 'from_id', 'к_id': 'to_id', | |
| 'тип_связи': 'link_type', 'описание': 'description', 'слой': 'layer_ref'} | |
| _migrate_ru_table(conn, 'a5_перекрёстные_ссылки', 'a5_cross_refs', a5_map) | |
| # Merge a6 | |
| a6_map = {'страна_id': 'country_id', 'название_страны': 'country_name', | |
| 'корень_id': 'root_id', 'слово_al': 'al_word', 'корень_al': 'al_root', | |
| 'коранич_значение': 'qur_meaning', 'фонетическая_цепь': 'phonetic_chain', | |
| 'основание_названия': 'naming_basis', 'запись_ids': 'entry_ids', | |
| 'примечания': 'notes'} | |
| _migrate_ru_table(conn, 'a6_названия_стран', 'a6_country_names', a6_map) | |
| # Foundation RU mirrors → add lang column to EN tables | |
| for ru_tbl, en_tbl in [ | |
| ('f1_один_источник_два_коридора', 'f1_two_originals'), | |
| ('f2_нисходящий_поток', 'f2_script_downstream'), | |
| ('f2_критерии_деградации', 'f2_decay_criteria'), | |
| ('f3_модель_застывших_стадий', 'f3_frozen_stage_model'), | |
| ('f4_градиент_деградации', 'f4_decay_gradient'), | |
| ('f5_хронология_уничтожения', 'f5_destruction_timeline'), | |
| ('f6_рукописные_свидетельства', 'f6_manuscript_evidence'), | |
| ('f7_направление_потока', 'f7_direction_of_flow'), | |
| ]: | |
| _merge_generic_ru(conn, ru_tbl, en_tbl) | |
| # Mechanism RU → new mechanism_data table | |
| conn.execute(""" | |
| CREATE TABLE IF NOT EXISTS mechanism_data ( | |
| mech_id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| layer TEXT NOT NULL, | |
| lang TEXT DEFAULT 'RU', | |
| data TEXT, | |
| quf_q TEXT, quf_u TEXT, quf_f TEXT, quf_pass TEXT, quf_date TEXT, quf_token TEXT | |
| ) | |
| """) | |
| for ru_tbl, layer in [ | |
| ('m1_фонетические_сдвиги', 'm1'), ('m2_паттерны_обнаружения', 'm2'), | |
| ('m3_учёные', 'm3'), ('m4_сети', 'm4'), ('m5_коранич_верификация', 'm5'), | |
| ]: | |
| try: | |
| rows = conn.execute(f'SELECT * FROM "{ru_tbl}"').fetchall() | |
| cols = [r[1] for r in conn.execute(f"PRAGMA table_info('{ru_tbl}')").fetchall()] | |
| for row in rows: | |
| row_dict = {cols[i]: row[i] for i in range(len(cols)) if not cols[i].startswith('quf_')} | |
| if not DRY_RUN: | |
| conn.execute("INSERT INTO mechanism_data (layer, lang, data) VALUES (?, 'RU', ?)", | |
| (layer, json.dumps(row_dict, ensure_ascii=False))) | |
| print(f" {ru_tbl} → mechanism_data: {len(rows)} rows") | |
| except Exception as e: | |
| print(f" WARN {ru_tbl}: {e}") | |
| conn.commit() | |
| # Retire all RU tables | |
| ru_retire = [ | |
| 'a1_записи', 'a2_имена_аллаха', 'a3_коранические_ссылки', | |
| 'a4_производные', 'a5_перекрёстные_ссылки', 'a6_названия_стран', | |
| 'f1_один_источник_два_коридора', 'f2_нисходящий_поток', 'f2_критерии_деградации', | |
| 'f3_модель_застывших_стадий', 'f4_градиент_деградации', 'f5_хронология_уничтожения', | |
| 'f6_рукописные_свидетельства', 'f7_направление_потока', | |
| 'm1_фонетические_сдвиги', 'm2_паттерны_обнаружения', 'm3_учёные', 'm4_сети', 'm5_коранич_верификация', | |
| ] | |
| retired = 0 | |
| for tbl in ru_retire: | |
| try: | |
| if not DRY_RUN: | |
| conn.execute(f'ALTER TABLE "{tbl}" RENAME TO "_retired_consol_{tbl}"') | |
| retired += 1 | |
| except: | |
| pass | |
| conn.commit() | |
| print(f" Retired {retired} RU tables") | |
| # Create compatibility views | |
| if not DRY_RUN: | |
| conn.execute('CREATE VIEW IF NOT EXISTS "a1_записи" AS SELECT * FROM entries WHERE lang="RU"') | |
| print(" Compatibility view created: a1_записи") | |
| # ================================================================ | |
| # PHASE 2: BODY → 5 TABLES | |
| # ================================================================ | |
| print("\n" + "=" * 70) | |
| print("PHASE 2: Body 46 → 5 tables") | |
| print("=" * 70) | |
| # Create body_data | |
| conn.execute(""" | |
| CREATE TABLE IF NOT EXISTS body_data ( | |
| body_id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| subsystem TEXT NOT NULL, | |
| subtable TEXT NOT NULL, | |
| heptad INTEGER, | |
| category TEXT, | |
| arabic TEXT, | |
| transliteration TEXT, | |
| english TEXT, | |
| description TEXT, | |
| root_letters TEXT, | |
| aa_root_id TEXT, | |
| quranic_ref TEXT, | |
| score REAL, | |
| status TEXT, | |
| specific_data TEXT, | |
| quf_q TEXT, quf_u TEXT, quf_f TEXT, quf_pass TEXT, quf_date TEXT, quf_token TEXT | |
| ) | |
| """) | |
| # Create body_cross_refs | |
| conn.execute(""" | |
| CREATE TABLE IF NOT EXISTS body_cross_refs_unified ( | |
| xref_id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| subsystem TEXT NOT NULL, | |
| subtable TEXT NOT NULL, | |
| source_ref TEXT, | |
| target_ref TEXT, | |
| relationship TEXT, | |
| notes TEXT, | |
| quf_q TEXT, quf_u TEXT, quf_f TEXT, quf_pass TEXT, quf_date TEXT, quf_token TEXT | |
| ) | |
| """) | |
| # Create body_prayer_map | |
| conn.execute(""" | |
| CREATE TABLE IF NOT EXISTS body_prayer_map ( | |
| prayer_id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| subsystem TEXT NOT NULL, | |
| subtable TEXT NOT NULL, | |
| prayer TEXT, | |
| state TEXT, | |
| arabic TEXT, | |
| quranic_ref TEXT, | |
| specific_data TEXT, | |
| quf_q TEXT, quf_u TEXT, quf_f TEXT, quf_pass TEXT, quf_date TEXT, quf_token TEXT | |
| ) | |
| """) | |
| conn.commit() | |
| # Migrate body data tables | |
| COMMON_COLS = ['category', 'arabic', 'transliteration', 'english', 'description', | |
| 'root_letters', 'aa_root_id', 'quranic_ref', 'score', 'status'] | |
| body_data_tables = [] | |
| body_xref_tables = [] | |
| body_prayer_tables = [] | |
| for (tbl,) in conn.execute("""SELECT name FROM sqlite_master WHERE type='table' | |
| AND (name LIKE 'body_%' OR name LIKE 'sensory_%' OR name LIKE 'nutrition_%' | |
| OR name LIKE 'healing_%' OR name LIKE 'nafs_%' OR name LIKE 'lifecycle_%' | |
| OR name LIKE 'heart_%' OR name LIKE 'prayer_%' OR name LIKE 'emotional_%' | |
| OR name LIKE 'social_%' OR name LIKE 'death_%' OR name LIKE 'perception_%' | |
| OR name LIKE 'pelvis_%' OR name LIKE 'spirit_%' OR name LIKE 'qalb_%' | |
| OR name LIKE 'transition_%' OR name LIKE 'therapy_%' OR name LIKE 'food_%' | |
| OR name LIKE 'agricultural_%') | |
| AND name NOT LIKE '_retired%' | |
| AND name != 'body_data' AND name != 'body_cross_refs_unified' AND name != 'body_prayer_map' | |
| AND name != 'body_heptad_meta' AND name != 'body_extraction_intel' | |
| """).fetchall(): | |
| if 'cross_ref' in tbl or 'edges' in tbl: | |
| body_xref_tables.append(tbl) | |
| elif 'prayer' in tbl and ('map' in tbl or 'state' in tbl or 'transition' in tbl): | |
| body_prayer_tables.append(tbl) | |
| else: | |
| body_data_tables.append(tbl) | |
| # Determine subsystem from table name | |
| def get_subsystem(tbl): | |
| for prefix in ['body_', 'sensory_', 'nutrition_', 'healing_', 'nafs_', | |
| 'lifecycle_', 'heart_', 'emotional_', 'social_', 'death_', | |
| 'perception_', 'pelvis_', 'spirit_', 'qalb_', 'transition_', | |
| 'therapy_', 'food_', 'agricultural_']: | |
| if tbl.startswith(prefix): | |
| return prefix.rstrip('_') | |
| return tbl.split('_')[0] | |
| # Migrate data tables | |
| total_migrated = 0 | |
| for tbl in body_data_tables: | |
| try: | |
| cols = [r[1] for r in conn.execute(f"PRAGMA table_info('{tbl}')").fetchall()] | |
| rows = conn.execute(f'SELECT * FROM "{tbl}"').fetchall() | |
| subsystem = get_subsystem(tbl) | |
| for row in rows: | |
| row_dict = dict(zip(cols, row)) | |
| # Extract common columns | |
| common_vals = {c: row_dict.get(c) for c in COMMON_COLS} | |
| # Everything else goes to specific_data | |
| specific = {k: v for k, v in row_dict.items() | |
| if k not in COMMON_COLS and not k.startswith('quf_') | |
| and k not in ('rowid_pk',) and v is not None} | |
| if not DRY_RUN: | |
| conn.execute(""" | |
| INSERT INTO body_data (subsystem, subtable, category, arabic, transliteration, | |
| english, description, root_letters, aa_root_id, quranic_ref, score, status, | |
| specific_data) | |
| VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) | |
| """, (subsystem, tbl, common_vals.get('category'), common_vals.get('arabic'), | |
| common_vals.get('transliteration'), common_vals.get('english'), | |
| common_vals.get('description'), common_vals.get('root_letters'), | |
| common_vals.get('aa_root_id'), common_vals.get('quranic_ref'), | |
| common_vals.get('score'), common_vals.get('status'), | |
| json.dumps(specific, ensure_ascii=False) if specific else None)) | |
| total_migrated += 1 | |
| except Exception as e: | |
| print(f" WARN {tbl}: {e}") | |
| conn.commit() | |
| print(f" body_data: {total_migrated} rows from {len(body_data_tables)} tables") | |
| # Migrate cross-ref tables | |
| xref_migrated = 0 | |
| for tbl in body_xref_tables: | |
| try: | |
| cols = [r[1] for r in conn.execute(f"PRAGMA table_info('{tbl}')").fetchall()] | |
| rows = conn.execute(f'SELECT * FROM "{tbl}"').fetchall() | |
| for row in rows: | |
| row_dict = dict(zip(cols, row)) | |
| specific = {k: v for k, v in row_dict.items() | |
| if not k.startswith('quf_') and k not in ('rowid_pk',) and v is not None} | |
| if not DRY_RUN: | |
| conn.execute(""" | |
| INSERT INTO body_cross_refs_unified (subsystem, subtable, source_ref, target_ref, notes) | |
| VALUES (?, ?, ?, ?, ?) | |
| """, (get_subsystem(tbl), tbl, | |
| json.dumps({k: v for k, v in specific.items() if 'source' in k or 'from' in k}, ensure_ascii=False), | |
| json.dumps({k: v for k, v in specific.items() if 'target' in k or 'to' in k}, ensure_ascii=False), | |
| json.dumps(specific, ensure_ascii=False))) | |
| xref_migrated += 1 | |
| except Exception as e: | |
| print(f" WARN xref {tbl}: {e}") | |
| conn.commit() | |
| print(f" body_cross_refs_unified: {xref_migrated} rows from {len(body_xref_tables)} tables") | |
| # Migrate prayer tables | |
| prayer_migrated = 0 | |
| for tbl in body_prayer_tables: | |
| try: | |
| cols = [r[1] for r in conn.execute(f"PRAGMA table_info('{tbl}')").fetchall()] | |
| rows = conn.execute(f'SELECT * FROM "{tbl}"').fetchall() | |
| for row in rows: | |
| row_dict = dict(zip(cols, row)) | |
| specific = {k: v for k, v in row_dict.items() | |
| if not k.startswith('quf_') and k not in ('rowid_pk',) and v is not None} | |
| if not DRY_RUN: | |
| conn.execute(""" | |
| INSERT INTO body_prayer_map (subsystem, subtable, specific_data) | |
| VALUES (?, ?, ?) | |
| """, (get_subsystem(tbl), tbl, json.dumps(specific, ensure_ascii=False))) | |
| prayer_migrated += 1 | |
| except Exception as e: | |
| print(f" WARN prayer {tbl}: {e}") | |
| conn.commit() | |
| print(f" body_prayer_map: {prayer_migrated} rows from {len(body_prayer_tables)} tables") | |
| # Retire body tables | |
| body_retired = 0 | |
| for tbl in body_data_tables + body_xref_tables + body_prayer_tables: | |
| try: | |
| if not DRY_RUN: | |
| conn.execute(f'ALTER TABLE "{tbl}" RENAME TO "_retired_body_{tbl}"') | |
| body_retired += 1 | |
| except: | |
| pass | |
| conn.commit() | |
| print(f" Retired {body_retired} body tables") | |
| # ================================================================ | |
| # PHASE 3: BITIG CLEANUP | |
| # ================================================================ | |
| print("\n" + "=" * 70) | |
| print("PHASE 3: Bitig Cleanup") | |
| print("=" * 70) | |
| # Drop empty tables | |
| for tbl in ['bitig_bridge_xref', 'bitig_dispersal_edges', 'bitig_sibling_propagation']: | |
| try: | |
| cnt = conn.execute(f'SELECT COUNT(*) FROM "{tbl}"').fetchone()[0] | |
| if cnt == 0 and not DRY_RUN: | |
| conn.execute(f'DROP TABLE "{tbl}"') | |
| print(f" Dropped {tbl} (empty)") | |
| except: | |
| pass | |
| # Merge corrections + investigation | |
| try: | |
| conn.execute(""" | |
| CREATE TABLE IF NOT EXISTS bitig_corrections_merged ( | |
| corr_id TEXT, | |
| source_table TEXT, | |
| word TEXT, | |
| issue TEXT, | |
| resolution TEXT, | |
| evidence TEXT, | |
| status TEXT, | |
| next_step TEXT, | |
| quf_q TEXT, quf_u TEXT, quf_f TEXT, quf_pass TEXT, quf_date TEXT, quf_token TEXT | |
| ) | |
| """) | |
| # Copy corrections | |
| for row in conn.execute('SELECT * FROM bitig_corrections').fetchall(): | |
| cols = [r[1] for r in conn.execute("PRAGMA table_info('bitig_corrections')").fetchall()] | |
| d = dict(zip(cols, row)) | |
| if not DRY_RUN: | |
| conn.execute("INSERT INTO bitig_corrections_merged (corr_id, source_table, issue, resolution, evidence, status) VALUES (?, 'corrections', ?, ?, ?, ?)", | |
| (d.get('corr_id'), d.get('what_was_wrong'), d.get('corrected_to'), d.get('evidence'), d.get('status'))) | |
| # Copy investigations | |
| for row in conn.execute('SELECT * FROM bitig_investigation').fetchall(): | |
| cols = [r[1] for r in conn.execute("PRAGMA table_info('bitig_investigation')").fetchall()] | |
| d = dict(zip(cols, row)) | |
| if not DRY_RUN: | |
| conn.execute("INSERT INTO bitig_corrections_merged (corr_id, source_table, word, issue, status, next_step) VALUES (?, 'investigation', ?, ?, ?, ?)", | |
| (d.get('inv_id'), d.get('word'), d.get('issue'), d.get('status'), d.get('next_step'))) | |
| conn.commit() | |
| if not DRY_RUN: | |
| conn.execute('ALTER TABLE bitig_corrections RENAME TO "_retired_bitig_corrections"') | |
| conn.execute('ALTER TABLE bitig_investigation RENAME TO "_retired_bitig_investigation"') | |
| conn.execute('ALTER TABLE bitig_corrections_merged RENAME TO bitig_corrections') | |
| print(f" Merged bitig_corrections + bitig_investigation → bitig_corrections") | |
| except Exception as e: | |
| print(f" WARN merge corrections: {e}") | |
| # Merge intelligence_summary + operator_profiles | |
| try: | |
| conn.execute(""" | |
| CREATE TABLE IF NOT EXISTS bitig_intelligence_merged ( | |
| intel_id TEXT, | |
| source_table TEXT, | |
| category TEXT, | |
| content TEXT, | |
| period TEXT, | |
| dp_codes TEXT, | |
| method TEXT, | |
| specific_data TEXT, | |
| quf_q TEXT, quf_u TEXT, quf_f TEXT, quf_pass TEXT, quf_date TEXT, quf_token TEXT | |
| ) | |
| """) | |
| for row in conn.execute('SELECT * FROM bitig_intelligence_summary').fetchall(): | |
| cols = [r[1] for r in conn.execute("PRAGMA table_info('bitig_intelligence_summary')").fetchall()] | |
| d = dict(zip(cols, row)) | |
| if not DRY_RUN: | |
| conn.execute("INSERT INTO bitig_intelligence_merged (intel_id, source_table, category, dp_codes, specific_data) VALUES (?, 'summary', ?, ?, ?)", | |
| (d.get('intel_id'), d.get('category'), d.get('dp_code'), | |
| json.dumps({k:v for k,v in d.items() if not k.startswith('quf_')}, ensure_ascii=False))) | |
| for row in conn.execute('SELECT * FROM bitig_operator_profiles').fetchall(): | |
| cols = [r[1] for r in conn.execute("PRAGMA table_info('bitig_operator_profiles')").fetchall()] | |
| d = dict(zip(cols, row)) | |
| if not DRY_RUN: | |
| conn.execute("INSERT INTO bitig_intelligence_merged (intel_id, source_table, category, period, method, specific_data) VALUES (?, 'profiles', ?, ?, ?, ?)", | |
| (d.get('profile_id'), d.get('role'), d.get('period'), d.get('method'), | |
| json.dumps({k:v for k,v in d.items() if not k.startswith('quf_')}, ensure_ascii=False))) | |
| conn.commit() | |
| if not DRY_RUN: | |
| conn.execute('ALTER TABLE bitig_intelligence_summary RENAME TO "_retired_bitig_intel_summary"') | |
| conn.execute('ALTER TABLE bitig_operator_profiles RENAME TO "_retired_bitig_op_profiles"') | |
| conn.execute('ALTER TABLE bitig_intelligence_merged RENAME TO bitig_intelligence') | |
| print(f" Merged bitig_intelligence_summary + bitig_operator_profiles → bitig_intelligence") | |
| except Exception as e: | |
| print(f" WARN merge intel: {e}") | |
| conn.commit() | |
| # ================================================================ | |
| # PHASE 4: FOUNDATION/MECHANISM | |
| # ================================================================ | |
| print("\n" + "=" * 70) | |
| print("PHASE 4: Foundation/Mechanism") | |
| print("=" * 70) | |
| # Add lang column to foundation tables | |
| for tbl in ['f1_two_originals', 'f2_script_downstream', 'f2_decay_criteria', | |
| 'f3_frozen_stage_model', 'f4_decay_gradient', 'f5_destruction_timeline', | |
| 'f6_manuscript_evidence', 'f7_direction_of_flow']: | |
| cols = [r[1] for r in conn.execute(f"PRAGMA table_info('{tbl}')").fetchall()] | |
| if 'lang' not in cols: | |
| try: | |
| conn.execute(f'ALTER TABLE "{tbl}" ADD COLUMN lang TEXT DEFAULT "EN"') | |
| conn.execute(f'UPDATE "{tbl}" SET lang="EN" WHERE lang IS NULL') | |
| except: | |
| pass | |
| conn.commit() | |
| print(" Foundation tables: lang column added") | |
| # ================================================================ | |
| # RECREATE TRIGGERS (on surviving tables only) | |
| # ================================================================ | |
| print("\n" + "=" * 70) | |
| print("RESTORE: Recreating triggers") | |
| print("=" * 70) | |
| # Get surviving table names | |
| surviving = set(r[0] for r in conn.execute( | |
| "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE '_retired%'" | |
| ).fetchall()) | |
| trg_ok = 0 | |
| trg_skip = 0 | |
| for tname, tsql in all_triggers: | |
| if not tsql: | |
| continue | |
| # Check if the trigger's table still exists | |
| tbl_match = None | |
| for tbl in surviving: | |
| if f'ON [{tbl}]' in tsql or f'ON "{tbl}"' in tsql: | |
| tbl_match = tbl | |
| break | |
| if tbl_match and not DRY_RUN: | |
| try: | |
| conn.execute(tsql) | |
| trg_ok += 1 | |
| except: | |
| trg_skip += 1 | |
| else: | |
| trg_skip += 1 | |
| conn.commit() | |
| print(f" Recreated: {trg_ok}, Skipped (retired tables): {trg_skip}") | |
| # ================================================================ | |
| # VERIFY | |
| # ================================================================ | |
| print("\n" + "=" * 70) | |
| print("VERIFICATION") | |
| print("=" * 70) | |
| tables_after = conn.execute( | |
| "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '_retired%' AND name NOT LIKE 'entries_fts%'" | |
| ).fetchone()[0] | |
| retired_count = conn.execute( | |
| "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name LIKE '_retired%'" | |
| ).fetchone()[0] | |
| triggers_after = conn.execute("SELECT COUNT(*) FROM sqlite_master WHERE type='trigger'").fetchone()[0] | |
| # Data verification | |
| if not DRY_RUN: | |
| ru_in_entries = conn.execute("SELECT COUNT(*) FROM entries WHERE lang='RU'").fetchone()[0] | |
| body_data_count = conn.execute("SELECT COUNT(*) FROM body_data").fetchone()[0] | |
| body_subsystems = conn.execute("SELECT COUNT(DISTINCT subsystem) FROM body_data").fetchone()[0] | |
| integrity = conn.execute("PRAGMA integrity_check").fetchone()[0] | |
| else: | |
| ru_in_entries = body_data_count = body_subsystems = 0 | |
| integrity = 'DRY RUN' | |
| db_size = os.path.getsize(DB) / 1024 / 1024 | |
| print(f" Active tables: {tables_after}") | |
| print(f" Retired tables: {retired_count}") | |
| print(f" Triggers: {triggers_after}") | |
| print(f" entries WHERE lang='RU': {ru_in_entries}") | |
| print(f" body_data rows: {body_data_count}") | |
| print(f" body_data subsystems: {body_subsystems}") | |
| print(f" Integrity: {integrity}") | |
| print(f" DB size: {db_size:.1f} MB") | |
| print(f"\n{'=' * 70}") | |
| print(f"CONSOLIDATION {'COMPLETE' if not DRY_RUN else 'DRY RUN COMPLETE'}") | |
| print(f"Finished: {datetime.now().isoformat()}") | |
| print(f"{'=' * 70}") | |
| conn.close() | |
| def _migrate_ru_table(conn, ru_tbl, en_tbl, col_map): | |
| """Generic RU → EN migration with column mapping.""" | |
| try: | |
| ru_cols = [r[1] for r in conn.execute(f"PRAGMA table_info('{ru_tbl}')").fetchall()] | |
| en_cols = [r[1] for r in conn.execute(f"PRAGMA table_info('{en_tbl}')").fetchall()] | |
| rows = conn.execute(f'SELECT * FROM "{ru_tbl}"').fetchall() | |
| # Add lang column if missing | |
| if 'lang' not in en_cols: | |
| conn.execute(f'ALTER TABLE "{en_tbl}" ADD COLUMN lang TEXT DEFAULT "EN"') | |
| conn.execute(f'UPDATE "{en_tbl}" SET lang="EN" WHERE lang IS NULL') | |
| migrated = 0 | |
| for row in rows: | |
| row_dict = dict(zip(ru_cols, row)) | |
| en_vals = {'lang': 'RU'} | |
| for ru_name, value in row_dict.items(): | |
| if ru_name.startswith('quf_'): | |
| en_vals[ru_name] = value | |
| elif ru_name in col_map and col_map[ru_name] in en_cols: | |
| en_vals[col_map[ru_name]] = value | |
| if en_vals and not DRY_RUN: | |
| cols_str = ', '.join(f'"{k}"' for k in en_vals.keys()) | |
| placeholders = ', '.join('?' * len(en_vals)) | |
| try: | |
| conn.execute(f'INSERT INTO "{en_tbl}" ({cols_str}) VALUES ({placeholders})', | |
| list(en_vals.values())) | |
| migrated += 1 | |
| except: | |
| pass | |
| conn.commit() | |
| print(f" {ru_tbl} → {en_tbl}: {migrated}/{len(rows)} rows") | |
| except Exception as e: | |
| print(f" WARN {ru_tbl}: {e}") | |
| def _merge_generic_ru(conn, ru_tbl, en_tbl): | |
| """Merge generic RU table into EN parent using JSON data column.""" | |
| try: | |
| ru_cols = [r[1] for r in conn.execute(f"PRAGMA table_info('{ru_tbl}')").fetchall()] | |
| en_cols = [r[1] for r in conn.execute(f"PRAGMA table_info('{en_tbl}')").fetchall()] | |
| rows = conn.execute(f'SELECT * FROM "{ru_tbl}"').fetchall() | |
| if 'lang' not in en_cols: | |
| conn.execute(f'ALTER TABLE "{en_tbl}" ADD COLUMN lang TEXT DEFAULT "EN"') | |
| conn.execute(f'UPDATE "{en_tbl}" SET lang="EN" WHERE lang IS NULL') | |
| for row in rows: | |
| row_dict = {ru_cols[i]: row[i] for i in range(len(ru_cols)) if not ru_cols[i].startswith('quf_')} | |
| # Try to insert mapped values; store unmappable as JSON in notes | |
| if not DRY_RUN: | |
| if 'notes' not in en_cols: | |
| conn.execute(f'ALTER TABLE "{en_tbl}" ADD COLUMN notes TEXT') | |
| en_cols.append('notes') | |
| conn.execute(f'INSERT INTO "{en_tbl}" (lang, notes) VALUES ("RU", ?)', | |
| (json.dumps(row_dict, ensure_ascii=False),)) | |
| conn.commit() | |
| print(f" {ru_tbl} → {en_tbl}: {len(rows)} rows (as JSON)") | |
| except Exception as e: | |
| print(f" WARN {ru_tbl}: {e}") | |
| if __name__ == "__main__": | |
| run() | |