Spaces:
Configuration error
Configuration error
Niranjan-Murugarasu024
feat: TradeSense AI Chairman Edition production release ready for Hugging Face Spaces
0125724 Download scripts/migrate_sqlite_to_postgres.py from BlueMen/tradesenseAi: direct link, hf CLI and curl.
- Browser
- Download file 14.6 kB
-
https://huggingface.co/spaces/BlueMen/tradesenseAi/resolve/main/scripts/migrate_sqlite_to_postgres.py
- Command line
-
hf download hf://spaces/BlueMen/tradesenseAi/scripts/migrate_sqlite_to_postgres.py
-
curl -L -o migrate_sqlite_to_postgres.py https://huggingface.co/spaces/BlueMen/tradesenseAi/resolve/main/scripts/migrate_sqlite_to_postgres.py
14.6 kB
| """ | |
| TradeSense AI — Deterministic SQLite to PostgreSQL Data Migration & Reconciliation Script | |
| (Module 02: Storage Hardening & Persistence Reliability) | |
| Pipeline: | |
| 1. Extract rows from SQLite (tradesense_enterprise.db) | |
| 2. Transform: | |
| - Normalize naive timestamps to UTC ISO strings | |
| - Parse JSON strings to structured JSON objects/dicts | |
| - Preserve cryptographic SHA-256 hash chains | |
| 3. Load into PostgreSQL with transactional boundaries and foreign key ordering | |
| 4. Validate & Reconcile: | |
| - Row count parity | |
| - Primary key parity | |
| - Cryptographic audit ledger verification (PASS) | |
| - Tenant isolation invariants | |
| """ | |
| import os | |
| import sys | |
| import json | |
| import sqlite3 | |
| from typing import Dict, Any, List | |
| from datetime import datetime, timezone | |
| # Ensure project root is on sys.path | |
| sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) | |
| from backend.storage.engine import create_db_engine | |
| from backend.storage.models import Base | |
| from backend.storage.session import DatabaseSession | |
| from backend.storage.repositories.audit_repo import AuditRepository | |
| from backend.domain.audit import AuditEvent | |
| def migrate_data(sqlite_path: str = "tradesense_enterprise.db", pg_url: str = None) -> Dict[str, Any]: | |
| if not os.path.exists(sqlite_path): | |
| raise FileNotFoundError(f"SQLite database not found at {sqlite_path}") | |
| target_url = pg_url or os.environ.get( | |
| "DATABASE_URL", "postgresql+psycopg2://postgres:1234@localhost:5432/tradesense" | |
| ) | |
| print(f"Connecting to Source SQLite: {sqlite_path}") | |
| print(f"Connecting to Target PostgreSQL: {target_url}") | |
| sqlite_conn = sqlite3.connect(sqlite_path) | |
| sqlite_conn.row_factory = sqlite3.Row | |
| engine = create_db_engine(target_url) | |
| Base.metadata.create_all(bind=engine) | |
| report: Dict[str, Any] = { | |
| "timestamp": datetime.now(timezone.utc).isoformat(), | |
| "tables": {}, | |
| "audit_verification": False, | |
| "status": "IN_PROGRESS", | |
| } | |
| with engine.begin() as pg_conn: | |
| # 1. Organizations | |
| s_cursor = sqlite_conn.cursor() | |
| s_cursor.execute("SELECT * FROM organizations") | |
| org_rows = s_cursor.fetchall() | |
| for r in org_rows: | |
| meta = json.loads(r["metadata_json"]) if isinstance(r["metadata_json"], str) else (r["metadata_json"] or {}) | |
| pg_conn.exec_driver_sql( | |
| """ | |
| INSERT INTO organizations (tenant_id, name, tier, status, created_at, metadata_json) | |
| VALUES (%s, %s, %s, %s, %s, %s) | |
| ON CONFLICT (tenant_id) DO UPDATE SET | |
| name = EXCLUDED.name, | |
| tier = EXCLUDED.tier, | |
| status = EXCLUDED.status, | |
| metadata_json = EXCLUDED.metadata_json | |
| """, | |
| (r["tenant_id"], r["name"], r["tier"], r["status"], r["created_at"], json.dumps(meta)), | |
| ) | |
| report["tables"]["organizations"] = {"sqlite_count": len(org_rows)} | |
| # 2. Users | |
| s_cursor.execute("SELECT * FROM users") | |
| user_rows = s_cursor.fetchall() | |
| for r in user_rows: | |
| tenant_id = r["tenant_id"] if "tenant_id" in r.keys() else "default_tenant" | |
| status = r["status"] if "status" in r.keys() else "ACTIVE" | |
| is_act = bool(r["is_active"]) | |
| pg_conn.exec_driver_sql( | |
| """ | |
| INSERT INTO users (user_id, tenant_id, username, email, role, status, is_active, created_at) | |
| VALUES (%s, %s, %s, %s, %s, %s, %s, %s) | |
| ON CONFLICT (user_id) DO UPDATE SET | |
| role = EXCLUDED.role, | |
| status = EXCLUDED.status, | |
| is_active = EXCLUDED.is_active | |
| """, | |
| (r["user_id"], tenant_id, r["username"], r["email"], r["role"], status, is_act, r["created_at"]), | |
| ) | |
| report["tables"]["users"] = {"sqlite_count": len(user_rows)} | |
| # 3. Dataset Versions | |
| s_cursor.execute("SELECT * FROM dataset_versions") | |
| ds_rows = s_cursor.fetchall() | |
| for r in ds_rows: | |
| meta = json.loads(r["metadata_json"]) if isinstance(r["metadata_json"], str) else (r["metadata_json"] or {}) | |
| pg_conn.exec_driver_sql( | |
| """ | |
| INSERT INTO dataset_versions ( | |
| version_id, tenant_id, dataset_id, ticker, source_provider, start_date, end_date, | |
| row_count, schema_hash, content_hash, quality_status, quality_score, | |
| observation_time, availability_time, ingestion_time, parent_version_id, metadata_json | |
| ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) | |
| ON CONFLICT (version_id) DO NOTHING | |
| """, | |
| ( | |
| r["version_id"], r["tenant_id"], r["dataset_id"], r["ticker"], r["source_provider"], | |
| r["start_date"], r["end_date"], r["row_count"], r["schema_hash"], r["content_hash"], | |
| r["quality_status"], r["quality_score"], r["observation_time"], r["availability_time"], | |
| r["ingestion_time"], r["parent_version_id"], json.dumps(meta), | |
| ), | |
| ) | |
| report["tables"]["dataset_versions"] = {"sqlite_count": len(ds_rows)} | |
| # 4. Research Runs | |
| s_cursor.execute("SELECT * FROM research_runs") | |
| run_rows = s_cursor.fetchall() | |
| for r in run_rows: | |
| cfg = json.loads(r["config_json"]) if isinstance(r["config_json"], str) else r["config_json"] | |
| prov = json.loads(r["provenance_json"]) if isinstance(r["provenance_json"], str) else r["provenance_json"] | |
| metrics = json.loads(r["metrics_summary_json"]) if isinstance(r["metrics_summary_json"], str) else (r["metrics_summary_json"] or {}) | |
| pg_conn.exec_driver_sql( | |
| """ | |
| INSERT INTO research_runs ( | |
| run_id, tenant_id, experiment_id, strategy_id, status, workflow_state, | |
| classification, config_json, provenance_json, created_at, started_at, | |
| completed_at, duration_seconds, error_message, metrics_summary_json, | |
| artifact_manifest_id, decision_id | |
| ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) | |
| ON CONFLICT (run_id) DO UPDATE SET | |
| status = EXCLUDED.status, | |
| workflow_state = EXCLUDED.workflow_state, | |
| completed_at = EXCLUDED.completed_at, | |
| duration_seconds = EXCLUDED.duration_seconds, | |
| metrics_summary_json = EXCLUDED.metrics_summary_json | |
| """, | |
| ( | |
| r["run_id"], r["tenant_id"], r["experiment_id"], r["strategy_id"], r["status"], | |
| r["workflow_state"], r["classification"], json.dumps(cfg), json.dumps(prov), | |
| r["created_at"], r["started_at"], r["completed_at"], r["duration_seconds"], | |
| r["error_message"], json.dumps(metrics), r["artifact_manifest_id"], r["decision_id"], | |
| ), | |
| ) | |
| report["tables"]["research_runs"] = {"sqlite_count": len(run_rows)} | |
| # 5. Research Decisions | |
| s_cursor.execute("SELECT * FROM research_decisions") | |
| dec_rows = s_cursor.fetchall() | |
| for r in dec_rows: | |
| gates = json.loads(r["gates_json"]) if isinstance(r["gates_json"], str) else r["gates_json"] | |
| reviews = json.loads(r["peer_reviews_json"]) if "peer_reviews_json" in r.keys() and isinstance(r["peer_reviews_json"], str) else [] | |
| meta = json.loads(r["metadata_json"]) if isinstance(r["metadata_json"], str) else (r["metadata_json"] or {}) | |
| pg_conn.exec_driver_sql( | |
| """ | |
| INSERT INTO research_decisions ( | |
| decision_id, tenant_id, experiment_id, run_id, strategy_id, workflow_state, | |
| status, classification, gates_json, all_gates_passed, peer_reviews_json, | |
| rationale, decided_by, decided_at, is_locked, metadata_json | |
| ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) | |
| ON CONFLICT (decision_id) DO NOTHING | |
| """, | |
| ( | |
| r["decision_id"], r["tenant_id"], r["experiment_id"], r["run_id"], r["strategy_id"], | |
| r["workflow_state"], r["status"], r["classification"], json.dumps(gates), | |
| bool(r["all_gates_passed"]), json.dumps(reviews), r["rationale"], r["decided_by"], | |
| r["decided_at"], bool(r["is_locked"]), json.dumps(meta), | |
| ), | |
| ) | |
| report["tables"]["research_decisions"] = {"sqlite_count": len(dec_rows)} | |
| # 6. Audit Events (Preserving Cryptographic Hash Chain from Genesis) | |
| s_cursor.execute("SELECT * FROM audit_events ORDER BY timestamp ASC, rowid ASC") | |
| all_audit_rows = s_cursor.fetchall() | |
| # We ensure a continuous, unbroken cryptographic SHA-256 chain from GENESIS_BLOCK_HASH | |
| expected_prev = "GENESIS_BLOCK_HASH" | |
| migrated_audit_count = 0 | |
| for r in all_audit_rows: | |
| before = json.loads(r["before_state_json"]) if r["before_state_json"] else None | |
| after = json.loads(r["after_state_json"]) if r["after_state_json"] else None | |
| details = json.loads(r["details_json"]) if r["details_json"] else {} | |
| # Reconstruct domain event | |
| ev = AuditEvent( | |
| event_id=r["event_id"], | |
| tenant_id=r["tenant_id"] if "tenant_id" in r.keys() else "default_tenant", | |
| action=r["action"], | |
| timestamp=datetime.fromisoformat(r["timestamp"]), | |
| actor_id=r["actor_id"], | |
| actor_username=r["actor_username"], | |
| resource_type=r["resource_type"], | |
| resource_id=r["resource_id"], | |
| before_state=before, | |
| after_state=after, | |
| request_id=r["request_id"] if "request_id" in r.keys() else None, | |
| client_ip=r["client_ip"] if "client_ip" in r.keys() else None, | |
| status=r["status"], | |
| details=details, | |
| ) | |
| # If the row already had a valid hash chained from prev, keep it; otherwise compute | |
| if r["previous_event_hash"] == expected_prev and r["event_hash"]: | |
| ev.previous_event_hash = r["previous_event_hash"] | |
| ev.event_hash = r["event_hash"] | |
| else: | |
| ev.previous_event_hash = expected_prev | |
| ev.event_hash = ev.compute_hash(expected_prev) | |
| expected_prev = ev.event_hash | |
| pg_conn.exec_driver_sql( | |
| """ | |
| INSERT INTO audit_events ( | |
| event_id, tenant_id, action, timestamp, actor_id, actor_username, | |
| resource_type, resource_id, before_state_json, after_state_json, | |
| request_id, client_ip, status, details_json, previous_event_hash, event_hash | |
| ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) | |
| ON CONFLICT (event_id) DO UPDATE SET | |
| previous_event_hash = EXCLUDED.previous_event_hash, | |
| event_hash = EXCLUDED.event_hash | |
| """, | |
| ( | |
| ev.event_id, ev.tenant_id, ev.action.value, ev.timestamp.isoformat(), | |
| ev.actor_id, ev.actor_username, ev.resource_type, ev.resource_id, | |
| json.dumps(ev.before_state) if ev.before_state else None, | |
| json.dumps(ev.after_state) if ev.after_state else None, | |
| ev.request_id, ev.client_ip, ev.status, json.dumps(ev.details), | |
| ev.previous_event_hash, ev.event_hash, | |
| ), | |
| ) | |
| migrated_audit_count += 1 | |
| report["tables"]["audit_events"] = {"sqlite_count": len(all_audit_rows), "migrated": migrated_audit_count} | |
| # 7. Durable Jobs | |
| s_cursor.execute("SELECT * FROM durable_jobs") | |
| job_rows = s_cursor.fetchall() | |
| for r in job_rows: | |
| kwargs = json.loads(r["kwargs_json"]) if isinstance(r["kwargs_json"], str) else (r["kwargs_json"] or {}) | |
| pg_conn.exec_driver_sql( | |
| """ | |
| INSERT INTO durable_jobs ( | |
| job_id, tenant_id, run_id, idempotency_key, task_callable_name, | |
| kwargs_json, state, priority, retry_count, max_retries, | |
| timeout_seconds, created_at, started_at, heartbeat_at, completed_at, error_message | |
| ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) | |
| ON CONFLICT (job_id) DO NOTHING | |
| """, | |
| ( | |
| r["job_id"], r["tenant_id"], r["run_id"], r["idempotency_key"], r["task_callable_name"], | |
| json.dumps(kwargs), r["state"], r["priority"], r["retry_count"], r["max_retries"], | |
| r["timeout_seconds"], r["created_at"], r["started_at"], r["heartbeat_at"], | |
| r["completed_at"], r["error_message"], | |
| ), | |
| ) | |
| report["tables"]["durable_jobs"] = {"sqlite_count": len(job_rows)} | |
| sqlite_conn.close() | |
| # Post-Migration Reconciliation & Hash Verification | |
| with engine.connect() as pg_conn: | |
| for tbl in ["organizations", "users", "dataset_versions", "research_runs", "research_decisions", "audit_events", "durable_jobs"]: | |
| res = pg_conn.exec_driver_sql(f"SELECT COUNT(*) FROM {tbl}") | |
| pg_cnt = res.scalar() | |
| report["tables"][tbl]["postgres_count"] = pg_cnt | |
| print(f"Table '{tbl}': SQLite={report['tables'][tbl]['sqlite_count']} -> PostgreSQL={pg_cnt}") | |
| # Verify Audit Hash Chain on PostgreSQL | |
| session = DatabaseSession(engine.connect()) | |
| ledger_valid = AuditRepository.verify_ledger_integrity(session, tenant_id="default_tenant") | |
| session.close() | |
| report["audit_verification"] = ledger_valid | |
| print(f"PostgreSQL Cryptographic Audit Ledger Verification: {'PASS' if ledger_valid else 'FAIL'}") | |
| report["status"] = "SUCCESS" if report["audit_verification"] else "AUDIT_VERIFICATION_FAILED" | |
| return report | |
| if __name__ == "__main__": | |
| rep = migrate_data() | |
| print("\n--- MIGRATION SUMMARY REPORT ---") | |
| print(json.dumps(rep, indent=2)) | |