Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """ | |
| USLaP Schema Consolidation β Final Clean Script | |
| Reduces 186 tables β ~130 by merging RU duplicates into EN tables | |
| and dropping retired tables. | |
| Strategy: | |
| Phase 1: Backup triggers + views SQL, then drop all views | |
| Phase 2: Names of Allah β add RU columns to EN table, UPDATE from RU | |
| Phase 3: A3/A4/A5/A6 β add lang column to EN, INSERT RU rows with new PKs | |
| Phase 4: Foundation F1-F7 β add lang column, INSERT RU rows | |
| Phase 5: Mechanism M1-M5 β add lang column, INSERT RU rows | |
| Phase 6: Drop retired tables | |
| Phase 7: Recreate views with correct definitions | |
| Phase 8: Restore contamination triggers | |
| """ | |
| import sqlite3, os, sys, json | |
| from datetime import datetime | |
| DB = os.path.join(os.path.dirname(__file__), "uslap_database_v3.db") | |
| # Column mappings: RU column β EN column (for Foundation/Mechanism merges) | |
| F1_MAP = [ | |
| ("ΠΎΡΠΈΠ³_id", "orig_id"), ("Π½Π°Π·Π²Π°Π½ΠΈΠ΅", "name"), ("Π°ΡΠ°Π±ΡΠΊΠΎΠ΅_ΠΈΠΌΡ", "arabic_name"), | |
| ("Π°Π²ΡΠΎΡΠΈΡΠ΅Ρ", "authority"), ("Π±ΡΠΊΠ²Ρ", "letters"), ("ΡΠΈΠΏ_ΠΏΠΈΡΡΠΌΠ°", "script_type"), | |
| ("ΡΠ°ΠΌΠΎΠ½Π°Π·Π²Π°Π½ΠΈΠ΅", "self_naming"), ("ΠΊΠΎΡΠ°Π½_ΡΡΡΠ»ΠΊΠ°", "qur_ref"), ("ΡΠΈΠΏ_Π΄ΠΎΠΊΠ°Π·Π°ΡΠ΅Π»ΡΡΡΠ²Π°", "proof_type"), | |
| ] | |
| F2_DS_MAP = [ | |
| ("ΠΏΠΈΡΡΠΌΠΎ_id", "script_id"), ("Π½Π°Π·Π²Π°Π½ΠΈΠ΅", "name"), ("ΠΈΡΡΠΎΡΠ½ΠΈΠΊ", "source"), | |
| ("Π΄Π°ΡΠ°", "date"), ("ΡΡΠ°ΡΡΡ", "status"), ("ΠΌΠ΅Ρ Π°Π½ΠΈΠ·ΠΌ", "mechanism"), | |
| ("Π΄ΠΎΠΊΠ°Π·Π°ΡΠ΅Π»ΡΡΡΠ²Π°", "evidence"), ("ΡΡΠΎΠ²Π΅Π½Ρ_Π΄Π΅Π³ΡΠ°Π΄Π°ΡΠΈΠΈ", "decay_level"), ("ΡΡΡΠ»ΠΊΠ°", "lattice_ref"), | |
| ] | |
| F2_DC_MAP = [ | |
| ("ΡΡΠΎΠ²Π΅Π½Ρ", "level"), ("ΠΊΡΠΈΡΠ΅ΡΠΈΠΈ", "criteria"), ("ΠΏΡΠΈΠΌΠ΅Ρ_ds", "example_ds"), | |
| ("ΠΈΠ·ΠΌΠ΅ΡΠΈΠΌΡΠΉ_ΡΠ΅ΡΡ", "measurable_test"), | |
| ] | |
| F3_MAP = [ | |
| ("ΡΡΠ°Π΄ΠΈΡ", "stage"), ("ΡΠ²ΠΎΠ»ΡΡΠΈΡ_Π°ΠΌΠ±", "asb_evolution"), ("Π²ΠΎΡΡΠΎΡΠ½Π°Ρ_Π³ΡΠ°Π½ΠΈΡΠ°", "eastern_terminus"), | |
| ("Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅", "meaning"), ("ΡΡΠ°ΡΡΡ", "status"), | |
| ] | |
| F4_MAP = [ | |
| ("ΡΠ°Π½Π³", "rank"), ("ΡΠ·ΡΠΊ", "language"), ("ΠΏΡΠΈΠΌΠ΅Ρ", "example"), | |
| ("ΠΌΠΎΡΡΠ΅ΠΌΡ", "morphemes"), ("ΡΡΡΡΠΊΠΈΠΉ_ΡΠΊΠ²ΠΈΠ²Π°Π»Π΅Π½Ρ", "english_equiv"), | |
| ("ΡΠ°ΡΡΡΠΎΡΠ½ΠΈΠ΅_ΠΎΡ_ΠΈΡΡΠΎΡΠ½ΠΈΠΊΠ°", "distance_from_source"), ("ΡΠΈΠΏ", "type"), | |
| ] | |
| F5_MAP = [ | |
| ("Π΄Π°ΡΠ°", "date"), ("ΡΠ½ΠΈΡΡΠΎΠΆΠΈΡΠ΅Π»Ρ", "destroyer"), ("ΡΡΠΎ_ΠΏΡΠΎΠΈΠ·ΠΎΡΠ»ΠΎ", "what_happened"), | |
| ("ΡΡΠΎ_ΡΡΡΠ°ΡΠ΅Π½ΠΎ", "what_lost"), ("ΡΠ΅ΠΌ_Π·Π°ΠΌΠ΅Π½Π΅Π½ΠΎ", "replaced_with"), ("ΠΏΡΠΈΠΌΠ΅ΡΠ°Π½ΠΈΠ΅_uslap", "uslap_note"), | |
| ] | |
| F6_MAP = [ | |
| ("ΡΠ²ΠΈΠ΄_id", "evidence_id"), ("ΡΠΈΠΏ", "type"), ("ΠΌΠ΅ΡΡΠΎΠΏΠΎΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅", "location"), | |
| ("ΡΠ°ΡΡΡ_ΠΌΠΈΠ½_ΠΊΠΌ", "distance_min_km"), ("ΡΠ°ΡΡΡ_ΠΌΠ°ΠΊΡ_ΠΊΠΌ", "distance_max_km"), | |
| ("Π΄Π°ΡΠ°", "date"), ("ΠΎΠΏΠΈΡΠ°Π½ΠΈΠ΅", "description"), ("ΠΊΠΎΠ½ΡΠ°ΠΌΠΈΠ½Π°ΡΠΈΡ", "contamination"), | |
| ("ΡΡΡΠ»ΠΊΠ°", "lattice_ref"), | |
| ] | |
| F7_MAP = [ | |
| ("Π΄ΠΎΠΌΠ΅Π½", "domain"), ("ΡΠ΅Π°Π»ΡΠ½ΡΠΉ_ΠΏΠΎΡΠΎΠΊ", "actual_flow"), | |
| ("ΠΏΠ΅ΡΠ΅Π²ΡΡΠ½ΡΡΠΎΠ΅_ΡΡΠ²Π΅ΡΠΆΠ΄Π΅Π½ΠΈΠ΅", "reversed_claim"), ("ΠΎΠΏΡΠΎΠ²Π΅ΡΠΆΠ΅Π½ΠΈΠ΅", "refutation"), | |
| ("ΡΡΡΠ»ΠΊΠ°", "lattice_ref"), | |
| ] | |
| M5_MAP = [ | |
| ("ΠΊΠ²_id", "qv_id"), ("Π½Π°Π·Π²Π°Π½ΠΈΠ΅", "name"), ("ΠΌΠ΅Ρ Π°Π½ΠΈΠ·ΠΌ", "mechanism"), | |
| ("ΠΎΠΏΠΈΡΠ°Π½ΠΈΠ΅", "description"), ("ΠΌΠ°ΡΠΊΠ΅ΡΡ", "markers"), ("ΠΊΠΎΡΠ°Π½_ΡΡΡΠ»ΠΊΠΈ", "qur_refs"), | |
| ("ΠΊΠΎΠ½ΡΡΠ°ΡΡΠ½ΡΠ΅_ΡΡΡΠ»ΠΊΠΈ", "contrast_refs"), ("ΠΎΡΠ½ΠΎΠ²Π°Π½ΠΈΠ΅", "foundation_ref"), | |
| ] | |
| # M1 RU has different schema than shift_lookup β it's a full descriptions table | |
| # M2, M3, M4 have no EN base tables (phonetic_shifts, detection_patterns, scholars, networks don't exist) | |
| # These RU tables ARE the only copy of the data β keep them as-is but rename to EN names | |
| FOUNDATION_MERGES = [ | |
| ("f1_ΠΎΠ΄ΠΈΠ½_ΠΈΡΡΠΎΡΠ½ΠΈΠΊ_Π΄Π²Π°_ΠΊΠΎΡΠΈΠ΄ΠΎΡΠ°", "f1_two_originals", F1_MAP), | |
| ("f2_Π½ΠΈΡΡ ΠΎΠ΄ΡΡΠΈΠΉ_ΠΏΠΎΡΠΎΠΊ", "f2_script_downstream", F2_DS_MAP), | |
| ("f2_ΠΊΡΠΈΡΠ΅ΡΠΈΠΈ_Π΄Π΅Π³ΡΠ°Π΄Π°ΡΠΈΠΈ", "f2_decay_criteria", F2_DC_MAP), | |
| ("f3_ΠΌΠΎΠ΄Π΅Π»Ρ_Π·Π°ΡΡΡΠ²ΡΠΈΡ _ΡΡΠ°Π΄ΠΈΠΉ", "f3_frozen_stage_model", F3_MAP), | |
| ("f4_Π³ΡΠ°Π΄ΠΈΠ΅Π½Ρ_Π΄Π΅Π³ΡΠ°Π΄Π°ΡΠΈΠΈ", "f4_decay_gradient", F4_MAP), | |
| ("f5_Ρ ΡΠΎΠ½ΠΎΠ»ΠΎΠ³ΠΈΡ_ΡΠ½ΠΈΡΡΠΎΠΆΠ΅Π½ΠΈΡ", "f5_destruction_timeline", F5_MAP), | |
| ("f6_ΡΡΠΊΠΎΠΏΠΈΡΠ½ΡΠ΅_ΡΠ²ΠΈΠ΄Π΅ΡΠ΅Π»ΡΡΡΠ²Π°", "f6_manuscript_evidence", F6_MAP), | |
| ("f7_Π½Π°ΠΏΡΠ°Π²Π»Π΅Π½ΠΈΠ΅_ΠΏΠΎΡΠΎΠΊΠ°", "f7_direction_of_flow", F7_MAP), | |
| ] | |
| M5_MERGE = ("m5_ΠΊΠΎΡΠ°Π½ΠΈΡ_Π²Π΅ΡΠΈΡΠΈΠΊΠ°ΡΠΈΡ", "m5_qur_verification", M5_MAP) | |
| # RU tables to rename (no EN counterpart exists) | |
| RU_RENAME = { | |
| "m1_ΡΠΎΠ½Π΅ΡΠΈΡΠ΅ΡΠΊΠΈΠ΅_ΡΠ΄Π²ΠΈΠ³ΠΈ": "m1_shift_descriptions", | |
| "m2_ΠΏΠ°ΡΡΠ΅ΡΠ½Ρ_ΠΎΠ±Π½Π°ΡΡΠΆΠ΅Π½ΠΈΡ": "m2_detection_patterns", | |
| "m3_ΡΡΡΠ½ΡΠ΅": "m3_scholars", | |
| "m4_ΡΠ΅ΡΠΈ": "m4_networks", | |
| } | |
| # Retired tables to drop | |
| RETIRED = [ | |
| "_retired_xlsx_att_terms", "_retired_xlsx_child_schema", "_retired_xlsx_dp_register", | |
| "_retired_xlsx_phonetic_reversal", "_retired_xlsx_protocol_corrections", | |
| "_retired_xlsx_scholar_warnings", "_retired_xlsx_session_index", "_retired_xlsx_umd_operations", | |
| ] | |
| def run(dry=False): | |
| conn = sqlite3.connect(DB) | |
| conn.execute("PRAGMA foreign_keys = OFF;") | |
| cur = conn.cursor() | |
| dropped_tables = [] | |
| # βββ Phase 1: Save and drop ALL views + ALL triggers βββ | |
| print("\n=== Phase 1: Drop all views and triggers ===") | |
| views = cur.execute("SELECT name, sql FROM sqlite_master WHERE type='view' ORDER BY name").fetchall() | |
| triggers = cur.execute("SELECT name, sql, tbl_name FROM sqlite_master WHERE type='trigger' ORDER BY name").fetchall() | |
| print(f" Saving {len(views)} views, {len(triggers)} triggers...") | |
| if not dry: | |
| for vname, vsql in views: | |
| cur.execute(f'DROP VIEW IF EXISTS [{vname}]') | |
| print(f" Dropped {len(views)} views") | |
| for tname, tsql, ttbl in triggers: | |
| cur.execute(f'DROP TRIGGER IF EXISTS [{tname}]') | |
| print(f" Dropped {len(triggers)} triggers") | |
| else: | |
| print(f" [DRY] Would drop {len(views)} views and {len(triggers)} triggers") | |
| # βββ Phase 2: Names of Allah β add RU columns βββ | |
| print("\n=== Phase 2: Names of Allah β merge RU into EN ===") | |
| # RU a2 has: ΠΈΠΌΡ_id, Π°ΡΠ°Π±ΡΠΊΠΎΠ΅_ΠΈΠΌΡ, ΡΡΠ°Π½ΡΠ»ΠΈΡΠ΅ΡΠ°ΡΠΈΡ, Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅, ΠΊΠΎΡΠ°Π½_ΡΡΡΠ»ΠΊΠ°, Π·Π°ΠΏΠΈΡΠΈ_id, ΠΊΠΎΡΠ΅Π½Ρ_id + QUF | |
| # EN has: allah_id, arabic_name, transliteration, meaning, qur_ref, entry_ids, root_id + QUF | |
| # Add ru_meaning, ru_transliteration to EN table | |
| ru_names = cur.execute("SELECT ΠΈΠΌΡ_id, ΡΡΠ°Π½ΡΠ»ΠΈΡΠ΅ΡΠ°ΡΠΈΡ, Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ FROM a2_ΠΈΠΌΠ΅Π½Π°_Π°Π»Π»Π°Ρ Π°").fetchall() | |
| print(f" RU Names: {len(ru_names)} rows") | |
| if not dry: | |
| try: | |
| cur.execute("ALTER TABLE names_of_allah ADD COLUMN ru_transliteration TEXT") | |
| print(" Added ru_transliteration column") | |
| except sqlite3.OperationalError: | |
| print(" ru_transliteration column already exists") | |
| try: | |
| cur.execute("ALTER TABLE names_of_allah ADD COLUMN ru_meaning TEXT") | |
| print(" Added ru_meaning column") | |
| except sqlite3.OperationalError: | |
| print(" ru_meaning column already exists") | |
| updated = 0 | |
| for ΠΈΠΌΡ_id, translit, meaning in ru_names: | |
| cur.execute("""UPDATE names_of_allah | |
| SET ru_transliteration = ?, ru_meaning = ? | |
| WHERE allah_id = ?""", (translit, meaning, ΠΈΠΌΡ_id)) | |
| if cur.rowcount > 0: | |
| updated += 1 | |
| print(f" Updated {updated}/{len(ru_names)} names") | |
| cur.execute("DROP TABLE IF EXISTS a2_ΠΈΠΌΠ΅Π½Π°_Π°Π»Π»Π°Ρ Π°") | |
| dropped_tables.append("a2_ΠΈΠΌΠ΅Π½Π°_Π°Π»Π»Π°Ρ Π°") | |
| print(" Dropped a2_ΠΈΠΌΠ΅Π½Π°_Π°Π»Π»Π°Ρ Π°") | |
| else: | |
| print(f" [DRY] Would add ru_transliteration, ru_meaning columns and update {len(ru_names)} rows") | |
| # βββ Phase 3: A3/A4/A5/A6 β add lang column, INSERT RU rows βββ | |
| print("\n=== Phase 3: A3-A6 β merge RU into EN ===") | |
| # A3: quran_refs (EN=226 rows) β a3_ΠΊΠΎΡΠ°Π½ΠΈΡΠ΅ΡΠΊΠΈΠ΅_ΡΡΡΠ»ΠΊΠΈ (RU=32 rows) | |
| # RU cols: ΡΡΡΠ»ΠΊΠ°_id, ΡΡΡΠ°, Π°ΡΡ, Π°ΡΠ°Π±ΡΠΊΠΈΠΉ_ΡΠ΅ΠΊΡΡ, ΡΠ΅Π»Π΅Π²Π°Π½ΡΠ½ΠΎΡΡΡ, Π·Π°ΠΏΠΈΡΠΈ_id, ΡΠ΅ΡΡ_id, ΡΠ»ΠΎΠΉ, ΠΊΠ²_id, quf* | |
| # EN cols: ref_id, surah, ayah, arabic_text, transliteration, translation, relevance, entry_ids, network_id, layer_ref, qv_id, created_at, quf* | |
| a3_ru = cur.execute("""SELECT ΡΡΡΠ»ΠΊΠ°_id, ΡΡΡΠ°, Π°ΡΡ, Π°ΡΠ°Π±ΡΠΊΠΈΠΉ_ΡΠ΅ΠΊΡΡ, ΡΠ΅Π»Π΅Π²Π°Π½ΡΠ½ΠΎΡΡΡ, | |
| Π·Π°ΠΏΠΈΡΠΈ_id, ΡΠ΅ΡΡ_id, ΡΠ»ΠΎΠΉ, ΠΊΠ²_id, | |
| quf_q, quf_u, quf_f, quf_pass, quf_date | |
| FROM a3_ΠΊΠΎΡΠ°Π½ΠΈΡΠ΅ΡΠΊΠΈΠ΅_ΡΡΡΠ»ΠΊΠΈ""").fetchall() | |
| print(f" A3 quran_refs: EN={cur.execute('SELECT COUNT(*) FROM quran_refs').fetchone()[0]}, RU={len(a3_ru)}") | |
| if not dry: | |
| try: | |
| cur.execute("ALTER TABLE quran_refs ADD COLUMN lang TEXT DEFAULT 'EN'") | |
| print(" Added lang column to quran_refs") | |
| except sqlite3.OperationalError: | |
| print(" lang column already exists on quran_refs") | |
| max_pk = cur.execute("SELECT MAX(CAST(REPLACE(ref_id,'QR','') AS INTEGER)) FROM quran_refs").fetchone()[0] or 0 | |
| inserted = 0 | |
| for row in a3_ru: | |
| max_pk += 1 | |
| new_pk = f"QR{max_pk:04d}" | |
| # Map: ΡΡΡΠ°βsurah, Π°ΡΡβayah, Π°ΡΠ°Π±ΡΠΊΠΈΠΉ_ΡΠ΅ΠΊΡΡβarabic_text, ΡΠ΅Π»Π΅Π²Π°Π½ΡΠ½ΠΎΡΡΡβrelevance, | |
| # Π·Π°ΠΏΠΈΡΠΈ_idβentry_ids, ΡΠ΅ΡΡ_idβnetwork_id, ΡΠ»ΠΎΠΉβlayer_ref, ΠΊΠ²_idβqv_id | |
| vals = (new_pk, row[1], row[2], row[3], None, None, row[4], | |
| row[5], row[6], row[7], row[8], | |
| row[9], row[10], row[11], row[12], row[13], "RU") | |
| try: | |
| cur.execute("""INSERT INTO quran_refs | |
| (ref_id, surah, ayah, arabic_text, transliteration, translation, relevance, | |
| entry_ids, network_id, layer_ref, qv_id, | |
| quf_q, quf_u, quf_f, quf_pass, quf_date, lang) | |
| VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""", vals) | |
| inserted += 1 | |
| except Exception as e: | |
| print(f" Skip A3 row {row[0]}: {e}") | |
| print(f" Inserted {inserted} RU rows into quran_refs") | |
| cur.execute("DROP TABLE IF EXISTS a3_ΠΊΠΎΡΠ°Π½ΠΈΡΠ΅ΡΠΊΠΈΠ΅_ΡΡΡΠ»ΠΊΠΈ") | |
| dropped_tables.append("a3_ΠΊΠΎΡΠ°Π½ΠΈΡΠ΅ΡΠΊΠΈΠ΅_ΡΡΡΠ»ΠΊΠΈ") | |
| else: | |
| print(f" [DRY] Would merge {len(a3_ru)} RU rows into quran_refs") | |
| # A4: a4_derivatives (EN=6293) β a4_ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄Π½ΡΠ΅ (RU=171) | |
| a4_ru = cur.execute("SELECT * FROM a4_ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄Π½ΡΠ΅").fetchall() | |
| print(f" A4 derivatives: EN={cur.execute('SELECT COUNT(*) FROM a4_derivatives').fetchone()[0]}, RU={len(a4_ru)}") | |
| if not dry: | |
| try: | |
| cur.execute("ALTER TABLE a4_derivatives ADD COLUMN lang TEXT DEFAULT 'EN'") | |
| print(" Added lang column to a4_derivatives") | |
| except sqlite3.OperationalError: | |
| print(" lang column already exists on a4_derivatives") | |
| max_d = cur.execute("SELECT MAX(CAST(REPLACE(deriv_id,'D','') AS INTEGER)) FROM a4_derivatives").fetchone()[0] or 0 | |
| inserted = 0 | |
| for row in a4_ru: | |
| max_d += 1 | |
| new_pk = f"D{max_d:05d}" | |
| # RU schema: ΠΏΡΠΎΠΈΠ·Π²_id, Π·Π°ΠΏΠΈΡΡ_id, ΡΡΡ_ΡΠ΅ΡΠΌΠΈΠ½, ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄Π½ΠΎΠ΅, ΡΠΈΠΏ_ΡΠ²ΡΠ·ΠΈ, quf_q..quf_date | |
| # EN schema: deriv_id, entry_id, en_term, derivative, link_type, col_5, col_6, quf_token, quf_q..quf_date | |
| ru_data = list(row[1:]) # skip RU PK | |
| # Map: Π·Π°ΠΏΠΈΡΡ_idβentry_id, ΡΡΡ_ΡΠ΅ΡΠΌΠΈΠ½βen_term, ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄Π½ΠΎΠ΅βderivative, ΡΠΈΠΏ_ΡΠ²ΡΠ·ΠΈβlink_type | |
| en_vals = [ | |
| new_pk, # deriv_id | |
| ru_data[0], # entry_id (Π·Π°ΠΏΠΈΡΡ_id) | |
| ru_data[1], # en_term (ΡΡΡ_ΡΠ΅ΡΠΌΠΈΠ½) | |
| ru_data[2], # derivative (ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄Π½ΠΎΠ΅) | |
| ru_data[3], # link_type (ΡΠΈΠΏ_ΡΠ²ΡΠ·ΠΈ) | |
| None, # col_5 | |
| None, # col_6 | |
| None, # quf_token | |
| ru_data[4] if len(ru_data) > 4 else None, # quf_q | |
| ru_data[5] if len(ru_data) > 5 else None, # quf_u | |
| ru_data[6] if len(ru_data) > 6 else None, # quf_f | |
| ru_data[7] if len(ru_data) > 7 else None, # quf_pass | |
| ru_data[8] if len(ru_data) > 8 else None, # quf_date | |
| "RU", # lang | |
| ] | |
| try: | |
| cur.execute("""INSERT INTO a4_derivatives | |
| (deriv_id, entry_id, en_term, derivative, link_type, col_5, col_6, | |
| quf_token, quf_q, quf_u, quf_f, quf_pass, quf_date, lang) | |
| VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)""", en_vals) | |
| inserted += 1 | |
| except Exception as e: | |
| print(f" Skip A4 row {row[0]}: {e}") | |
| print(f" Inserted {inserted} RU rows into a4_derivatives") | |
| cur.execute("DROP TABLE IF EXISTS a4_ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄Π½ΡΠ΅") | |
| dropped_tables.append("a4_ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄Π½ΡΠ΅") | |
| else: | |
| print(f" [DRY] Would merge {len(a4_ru)} RU rows into a4_derivatives") | |
| # A5: a5_cross_refs (EN=892) β a5_ΠΏΠ΅ΡΠ΅ΠΊΡΡΡΡΠ½ΡΠ΅_ΡΡΡΠ»ΠΊΠΈ (RU=184) | |
| a5_ru = cur.execute("SELECT * FROM a5_ΠΏΠ΅ΡΠ΅ΠΊΡΡΡΡΠ½ΡΠ΅_ΡΡΡΠ»ΠΊΠΈ").fetchall() | |
| print(f" A5 cross_refs: EN={cur.execute('SELECT COUNT(*) FROM a5_cross_refs').fetchone()[0]}, RU={len(a5_ru)}") | |
| if not dry: | |
| try: | |
| cur.execute("ALTER TABLE a5_cross_refs ADD COLUMN lang TEXT DEFAULT 'EN'") | |
| print(" Added lang column to a5_cross_refs") | |
| except sqlite3.OperationalError: | |
| print(" lang column already exists on a5_cross_refs") | |
| max_x = cur.execute("SELECT MAX(CAST(REPLACE(xref_id,'X','') AS INTEGER)) FROM a5_cross_refs").fetchone()[0] or 0 | |
| inserted = 0 | |
| for row in a5_ru: | |
| max_x += 1 | |
| new_pk = f"X{max_x:05d}" | |
| ru_data = list(row[1:]) # skip RU PK | |
| # RU: ΠΏΠ΅ΡΠ΅ΠΊΡ_id, ΠΎΡ_id, ΠΊ_id, ΡΠΈΠΏ_ΡΠ²ΡΠ·ΠΈ, ΠΎΠΏΠΈΡΠ°Π½ΠΈΠ΅, ΡΠ»ΠΎΠΉ, quf_q..quf_date | |
| # EN: xref_id, from_id, to_id, link_type, description, layer_ref, col_6, | |
| # from_entry_id, from_bitig_id, from_child_id, from_term, | |
| # to_entry_id, to_bitig_id, to_child_id, to_term, quf_q..quf_date | |
| en_vals = [ | |
| new_pk, # xref_id | |
| ru_data[0], # from_id | |
| ru_data[1], # to_id | |
| ru_data[2], # link_type | |
| ru_data[3], # description | |
| ru_data[4], # layer_ref | |
| None, # col_6 | |
| None, None, None, None, # from_entry_id..from_term | |
| None, None, None, None, # to_entry_id..to_term | |
| ru_data[5] if len(ru_data) > 5 else None, # quf_q | |
| ru_data[6] if len(ru_data) > 6 else None, # quf_u | |
| ru_data[7] if len(ru_data) > 7 else None, # quf_f | |
| ru_data[8] if len(ru_data) > 8 else None, # quf_pass | |
| ru_data[9] if len(ru_data) > 9 else None, # quf_date | |
| "RU", # lang | |
| ] | |
| try: | |
| cur.execute("""INSERT INTO a5_cross_refs | |
| (xref_id, from_id, to_id, link_type, description, layer_ref, col_6, | |
| from_entry_id, from_bitig_id, from_child_id, from_term, | |
| to_entry_id, to_bitig_id, to_child_id, to_term, | |
| quf_q, quf_u, quf_f, quf_pass, quf_date, lang) | |
| VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""", en_vals) | |
| inserted += 1 | |
| except Exception as e: | |
| print(f" Skip A5 row {row[0]}: {e}") | |
| print(f" Inserted {inserted} RU rows into a5_cross_refs") | |
| cur.execute("DROP TABLE IF EXISTS a5_ΠΏΠ΅ΡΠ΅ΠΊΡΡΡΡΠ½ΡΠ΅_ΡΡΡΠ»ΠΊΠΈ") | |
| dropped_tables.append("a5_ΠΏΠ΅ΡΠ΅ΠΊΡΡΡΡΠ½ΡΠ΅_ΡΡΡΠ»ΠΊΠΈ") | |
| else: | |
| print(f" [DRY] Would merge {len(a5_ru)} RU rows into a5_cross_refs") | |
| # A6: country_names (EN=46) β a6_Π½Π°Π·Π²Π°Π½ΠΈΡ_ΡΡΡΠ°Π½ (RU=3) | |
| # RU: ΡΡΡΠ°Π½Π°_id, Π½Π°Π·Π²Π°Π½ΠΈΠ΅_ΡΡΡΠ°Π½Ρ, ΠΊΠΎΡΠ΅Π½Ρ_al, ΠΊΠΎΡΠ΅Π½Ρ_id, ΡΠ»ΠΎΠ²ΠΎ_al, ΠΊΠΎΡΠ°Π½ΠΈΡ_Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅, | |
| # ΡΠΎΠ½Π΅ΡΠΈΡΠ΅ΡΠΊΠ°Ρ_ΡΠ΅ΠΏΡ, ΠΎΡΠ½ΠΎΠ²Π°Π½ΠΈΠ΅_Π½Π°Π·Π²Π°Π½ΠΈΡ, Π·Π°ΠΏΠΈΡΡ_ids, ΠΏΡΠΈΠΌΠ΅ΡΠ°Π½ΠΈΡ, quf* | |
| # EN: country_id, country_name, al_root, root_id, al_word, qur_meaning, | |
| # phonetic_chain, naming_basis, entry_ids, notes, created_at, quf* | |
| a6_ru = cur.execute("""SELECT ΡΡΡΠ°Π½Π°_id, Π½Π°Π·Π²Π°Π½ΠΈΠ΅_ΡΡΡΠ°Π½Ρ, ΠΊΠΎΡΠ΅Π½Ρ_al, ΠΊΠΎΡΠ΅Π½Ρ_id, ΡΠ»ΠΎΠ²ΠΎ_al, | |
| ΠΊΠΎΡΠ°Π½ΠΈΡ_Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅, ΡΠΎΠ½Π΅ΡΠΈΡΠ΅ΡΠΊΠ°Ρ_ΡΠ΅ΠΏΡ, ΠΎΡΠ½ΠΎΠ²Π°Π½ΠΈΠ΅_Π½Π°Π·Π²Π°Π½ΠΈΡ, | |
| Π·Π°ΠΏΠΈΡΡ_ids, ΠΏΡΠΈΠΌΠ΅ΡΠ°Π½ΠΈΡ, | |
| quf_q, quf_u, quf_f, quf_pass, quf_date | |
| FROM a6_Π½Π°Π·Π²Π°Π½ΠΈΡ_ΡΡΡΠ°Π½""").fetchall() | |
| print(f" A6 country_names: EN={cur.execute('SELECT COUNT(*) FROM country_names').fetchone()[0]}, RU={len(a6_ru)}") | |
| if not dry: | |
| try: | |
| cur.execute("ALTER TABLE country_names ADD COLUMN lang TEXT DEFAULT 'EN'") | |
| print(" Added lang column to country_names") | |
| except sqlite3.OperationalError: | |
| print(" lang column already exists on country_names") | |
| max_c = cur.execute("SELECT MAX(CAST(REPLACE(country_id,'CN','') AS INTEGER)) FROM country_names").fetchone()[0] or 0 | |
| inserted = 0 | |
| for row in a6_ru: | |
| max_c += 1 | |
| new_pk = f"CN{max_c:03d}" | |
| vals = (new_pk, row[1], row[2], row[3], row[4], row[5], | |
| row[6], row[7], row[8], row[9], | |
| row[10], row[11], row[12], row[13], row[14], "RU") | |
| try: | |
| cur.execute("""INSERT INTO country_names | |
| (country_id, country_name, al_root, root_id, al_word, qur_meaning, | |
| phonetic_chain, naming_basis, entry_ids, notes, | |
| quf_q, quf_u, quf_f, quf_pass, quf_date, lang) | |
| VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""", vals) | |
| inserted += 1 | |
| except Exception as e: | |
| print(f" Skip A6 row {row[0]}: {e}") | |
| print(f" Inserted {inserted} RU rows into country_names") | |
| cur.execute("DROP TABLE IF EXISTS a6_Π½Π°Π·Π²Π°Π½ΠΈΡ_ΡΡΡΠ°Π½") | |
| dropped_tables.append("a6_Π½Π°Π·Π²Π°Π½ΠΈΡ_ΡΡΡΠ°Π½") | |
| else: | |
| print(f" [DRY] Would merge {len(a6_ru)} RU rows into country_names") | |
| # βββ Phase 4: Foundation F1-F7 β add lang column, INSERT RU rows βββ | |
| print("\n=== Phase 4: Foundation F1-F7 β merge RU into EN ===") | |
| for ru_table, en_table, col_map in FOUNDATION_MERGES: | |
| ru_count = cur.execute(f"SELECT COUNT(*) FROM [{ru_table}]").fetchone()[0] | |
| en_count = cur.execute(f"SELECT COUNT(*) FROM [{en_table}]").fetchone()[0] | |
| print(f" {en_table}: EN={en_count}, RU={ru_count}") | |
| if not dry: | |
| try: | |
| cur.execute(f"ALTER TABLE [{en_table}] ADD COLUMN lang TEXT DEFAULT 'EN'") | |
| except sqlite3.OperationalError: | |
| pass | |
| # Get RU data | |
| ru_cols = [m[0] for m in col_map] | |
| en_cols = [m[1] for m in col_map] | |
| ru_col_str = ", ".join([f"[{c}]" for c in ru_cols]) | |
| rows = cur.execute(f"SELECT {ru_col_str}, quf_q, quf_u, quf_f, quf_pass, quf_date FROM [{ru_table}]").fetchall() | |
| en_col_str = ", ".join(en_cols) + ", quf_q, quf_u, quf_f, quf_pass, quf_date, lang" | |
| placeholders = ", ".join(["?"] * (len(en_cols) + 6)) | |
| inserted = 0 | |
| for row in rows: | |
| vals = list(row) + ["RU"] | |
| try: | |
| cur.execute(f"INSERT INTO [{en_table}] ({en_col_str}) VALUES ({placeholders})", vals) | |
| inserted += 1 | |
| except Exception as e: | |
| print(f" Skip {ru_table} row: {e}") | |
| print(f" Merged {inserted} RU rows β {en_table}") | |
| cur.execute(f"DROP TABLE IF EXISTS [{ru_table}]") | |
| dropped_tables.append(ru_table) | |
| else: | |
| print(f" [DRY] Would merge {ru_count} RU rows into {en_table}") | |
| # βββ Phase 5: Mechanism M1-M5 βββ | |
| print("\n=== Phase 5: Mechanism tables ===") | |
| # M5: merge like Foundation | |
| ru_table, en_table, col_map = M5_MERGE | |
| ru_count = cur.execute(f"SELECT COUNT(*) FROM [{ru_table}]").fetchone()[0] | |
| en_count = cur.execute(f"SELECT COUNT(*) FROM [{en_table}]").fetchone()[0] | |
| print(f" {en_table}: EN={en_count}, RU={ru_count}") | |
| if not dry: | |
| try: | |
| cur.execute(f"ALTER TABLE [{en_table}] ADD COLUMN lang TEXT DEFAULT 'EN'") | |
| except sqlite3.OperationalError: | |
| pass | |
| ru_cols = [m[0] for m in col_map] | |
| en_cols = [m[1] for m in col_map] | |
| ru_col_str = ", ".join([f"[{c}]" for c in ru_cols]) | |
| rows = cur.execute(f"SELECT {ru_col_str}, quf_q, quf_u, quf_f, quf_pass, quf_date FROM [{ru_table}]").fetchall() | |
| en_col_str = ", ".join(en_cols) + ", quf_q, quf_u, quf_f, quf_pass, quf_date, lang" | |
| placeholders = ", ".join(["?"] * (len(en_cols) + 6)) | |
| inserted = 0 | |
| for row in rows: | |
| vals = list(row) + ["RU"] | |
| try: | |
| cur.execute(f"INSERT INTO [{en_table}] ({en_col_str}) VALUES ({placeholders})", vals) | |
| inserted += 1 | |
| except Exception as e: | |
| print(f" Skip {ru_table} row: {e}") | |
| print(f" Merged {inserted} RU rows β {en_table}") | |
| cur.execute(f"DROP TABLE IF EXISTS [{ru_table}]") | |
| dropped_tables.append(ru_table) | |
| else: | |
| print(f" [DRY] Would merge {ru_count} RU rows into {en_table}") | |
| # M1-M4: rename RU tables to EN names (no EN counterpart exists) | |
| for ru_name, en_name in RU_RENAME.items(): | |
| count = cur.execute(f"SELECT COUNT(*) FROM [{ru_name}]").fetchone()[0] | |
| print(f" Rename [{ru_name}] β [{en_name}] ({count} rows)") | |
| if not dry: | |
| # Check if target already exists | |
| exists = cur.execute(f"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?", (en_name,)).fetchone()[0] | |
| if exists: | |
| print(f" Target {en_name} already exists β adding lang column and merging") | |
| try: | |
| cur.execute(f"ALTER TABLE [{en_name}] ADD COLUMN lang TEXT DEFAULT 'EN'") | |
| except sqlite3.OperationalError: | |
| pass | |
| # Can't easily merge different schemas β just keep both for now | |
| print(f" Skipping merge (different schemas) β keeping [{ru_name}] as-is") | |
| else: | |
| cur.execute(f"ALTER TABLE [{ru_name}] RENAME TO [{en_name}]") | |
| print(f" Renamed successfully") | |
| # βββ Phase 6: Drop retired tables βββ | |
| print("\n=== Phase 6: Drop retired tables ===") | |
| for tbl in RETIRED: | |
| if not dry: | |
| cur.execute(f"DROP TABLE IF EXISTS [{tbl}]") | |
| dropped_tables.append(tbl) | |
| print(f" Dropped {tbl}") | |
| else: | |
| print(f" [DRY] Would drop {tbl}") | |
| # Also drop _qwr_new if empty | |
| qwr_count = cur.execute("SELECT COUNT(*) FROM _qwr_new").fetchone()[0] | |
| if qwr_count == 0: | |
| if not dry: | |
| cur.execute("DROP TABLE IF EXISTS _qwr_new") | |
| dropped_tables.append("_qwr_new") | |
| print(f" Dropped _qwr_new (empty)") | |
| else: | |
| print(f" [DRY] Would drop _qwr_new (empty)") | |
| # βββ Phase 7: Recreate views βββ | |
| print("\n=== Phase 7: Recreate views ===") | |
| view_defs = { | |
| "a1_entries": """CREATE VIEW a1_entries AS | |
| SELECT entry_id, score, en_term, ru_term, fa_term, ar_word, | |
| root_id, root_letters, qur_refs, pattern, inversion_type, | |
| network_id, allah_name_id, phonetic_chain, source_form, | |
| ds_corridor, decay_level, dp_codes, ops_applied, | |
| foundation_refs, notes, qur_meaning, | |
| quf_q, quf_u, quf_f, quf_pass | |
| FROM entries""", | |
| "a1_Π·Π°ΠΏΠΈΡΠΈ": """CREATE VIEW [a1_Π·Π°ΠΏΠΈΡΠΈ] AS | |
| SELECT entry_id AS Π·Π°ΠΏΠΈΡΡ_id, score AS Π±Π°Π»Π», ru_term AS ΡΡΡ_ΡΠ΅ΡΠΌΠΈΠ½, | |
| ar_word AS Π°Ρ_ΡΠ»ΠΎΠ²ΠΎ, root_id AS ΠΊΠΎΡΠ΅Π½Ρ_id, root_letters AS ΠΊΠΎΡΠ½Π΅Π²ΡΠ΅_Π±ΡΠΊΠ²Ρ, | |
| qur_meaning AS ΠΊΠΎΡΠ°Π½ΠΈΡ_Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅, pattern AS ΠΏΠ°ΡΡΠ΅ΡΠ½, | |
| allah_name_id AS ΠΈΠΌΡ_Π°Π»Π»Π°Ρ Π°_id, network_id AS ΡΠ΅ΡΡ_id, | |
| phonetic_chain AS ΡΠΎΠ½Π΅ΡΠΈΡΠ΅ΡΠΊΠ°Ρ_ΡΠ΅ΠΏΡ, inversion_type AS ΡΠΈΠΏ_ΠΈΠ½Π²Π΅ΡΡΠΈΠΈ, | |
| source_form AS ΠΈΡΡ ΠΎΠ΄Π½Π°Ρ_ΡΠΎΡΠΌΠ°, foundation_refs AS ΠΎΡΠ½ΠΎΠ²Π°Π½ΠΈΠ΅, | |
| quf_q, quf_u, quf_f, quf_pass, quf_date, quf_token | |
| FROM entries WHERE ru_term IS NOT NULL""", | |
| "a2_names_of_allah": "CREATE VIEW a2_names_of_allah AS SELECT * FROM names_of_allah", | |
| "a3_quran_refs": "CREATE VIEW a3_quran_refs AS SELECT * FROM quran_refs", | |
| "a6_country_names": "CREATE VIEW a6_country_names AS SELECT * FROM country_names", | |
| "child_schema": """CREATE VIEW child_schema AS | |
| SELECT child_id AS entry_id, shell_name, shell_language, orig_class, orig_root, | |
| orig_lemma, orig_meaning, operation_role, shell_meaning, inversion_direction, | |
| phonetic_chain, qur_anchors, dp_codes, nt_code, pattern, parent_op, | |
| gate_status, notes | |
| FROM child_entries""", | |
| # Fix m1_phonetic_shifts β point to shift_lookup (the actual table) | |
| "m1_phonetic_shifts": "CREATE VIEW m1_phonetic_shifts AS SELECT * FROM shift_lookup", | |
| # m2-m4: point to the newly renamed tables | |
| "m2_detection_patterns": None, # Will check if table exists | |
| "m3_scholars": None, | |
| "m4_networks": None, | |
| "persian_a1_mad_khil": """CREATE VIEW persian_a1_mad_khil AS | |
| SELECT entry_id, fa_term, ar_word, root_letters, qur_meaning, | |
| pattern, allah_name_id, network_id, phonetic_chain, | |
| inversion_type, source_form, foundation_refs, score, | |
| root_id, quf_q, quf_u, quf_f, quf_pass, quf_date, quf_token | |
| FROM entries WHERE fa_term IS NOT NULL""", | |
| } | |
| if not dry: | |
| for vname, vsql in view_defs.items(): | |
| if vsql is None: | |
| # Check if the table exists to create a pass-through view | |
| # m2_detection_patterns β check if renamed table exists | |
| if vname == "m2_detection_patterns": | |
| exists = cur.execute("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='m2_detection_patterns'").fetchone()[0] | |
| if exists: | |
| print(f" {vname} is now a TABLE β no view needed") | |
| continue | |
| elif vname == "m3_scholars": | |
| exists = cur.execute("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='m3_scholars'").fetchone()[0] | |
| if exists: | |
| print(f" {vname} is now a TABLE β no view needed") | |
| continue | |
| elif vname == "m4_networks": | |
| exists = cur.execute("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='m4_networks'").fetchone()[0] | |
| if exists: | |
| print(f" {vname} is now a TABLE β no view needed") | |
| continue | |
| print(f" Skipping {vname} β no valid target") | |
| continue | |
| try: | |
| cur.execute(vsql) | |
| print(f" Created view: {vname}") | |
| except Exception as e: | |
| print(f" ERROR creating view {vname}: {e}") | |
| else: | |
| for vname in view_defs: | |
| print(f" [DRY] Would create view: {vname}") | |
| # βββ Phase 8: Restore triggers βββ | |
| print("\n=== Phase 8: Restore triggers ===") | |
| if not dry: | |
| # Get current table names to filter triggers | |
| current_tables = set(r[0] for r in cur.execute( | |
| "SELECT name FROM sqlite_master WHERE type='table'").fetchall()) | |
| restored = 0 | |
| skipped = 0 | |
| errors = 0 | |
| for tname, tsql, ttbl in triggers: | |
| if tsql is None: | |
| skipped += 1 | |
| continue | |
| # Skip triggers for dropped tables | |
| if ttbl not in current_tables: | |
| skipped += 1 | |
| continue | |
| # Fix triggers that reference a1_entries (now a view that doesn't exist yet) | |
| # These will be recreated after views are recreated | |
| if 'a1_entries' in tsql and 'a1_entries' not in current_tables: | |
| # Replace a1_entries reference with entries table | |
| tsql = tsql.replace('a1_entries', 'entries') | |
| # Fix triggers with NEW.relationship (column doesn't exist) | |
| if 'NEW.relationship' in tsql: | |
| skipped += 1 | |
| continue | |
| try: | |
| cur.execute(tsql) | |
| restored += 1 | |
| except Exception as e: | |
| errors += 1 | |
| if errors <= 5: | |
| print(f" Trigger error {tname}: {e}") | |
| print(f" Restored {restored} triggers, skipped {skipped}, errors {errors}") | |
| else: | |
| print(f" [DRY] Would restore valid triggers from {len(triggers)} saved") | |
| # βββ Phase 9: Commit and verify βββ | |
| print("\n=== Phase 9: Verify ===") | |
| if not dry: | |
| conn.commit() | |
| final_tables = cur.execute("SELECT COUNT(*) FROM sqlite_master WHERE type='table'").fetchone()[0] | |
| final_views = cur.execute("SELECT COUNT(*) FROM sqlite_master WHERE type='view'").fetchone()[0] | |
| final_triggers = cur.execute("SELECT COUNT(*) FROM sqlite_master WHERE type='trigger'").fetchone()[0] | |
| print(f" Tables: 186 β {final_tables} (dropped {len(dropped_tables)})") | |
| print(f" Views: {final_views}") | |
| print(f" Triggers: {final_triggers}") | |
| print(f"\n Dropped tables: {', '.join(dropped_tables)}") | |
| # Verify key table counts | |
| checks = [ | |
| ("entries", None), ("names_of_allah", None), ("a4_derivatives", None), | |
| ("a5_cross_refs", None), ("quran_refs", None), ("country_names", None), | |
| ] | |
| print("\n Row counts:") | |
| for tbl, _ in checks: | |
| try: | |
| count = cur.execute(f"SELECT COUNT(*) FROM [{tbl}]").fetchone()[0] | |
| print(f" {tbl}: {count}") | |
| except: | |
| print(f" {tbl}: ERROR") | |
| else: | |
| print(" [DRY RUN COMPLETE β no changes made]") | |
| conn.close() | |
| print("\nDone.") | |
| if __name__ == "__main__": | |
| dry = "--dry" in sys.argv | |
| if dry: | |
| print("=== DRY RUN ===") | |
| else: | |
| print("=== LIVE RUN ===") | |
| run(dry=dry) | |