db_schema.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. """
  2. Database Schema and Initialization
  3. Defines SQLite database schema for storing analysis data.
  4. Handles database creation and migrations.
  5. """
  6. import sqlite3
  7. import logging
  8. from pathlib import Path
  9. from typing import Optional
  10. logger = logging.getLogger(__name__)
  11. def get_schema_version() -> int:
  12. """Get the current schema version."""
  13. return 1
  14. def get_database_schema() -> str:
  15. """
  16. Get SQL schema for database initialization.
  17. Returns:
  18. str: SQL schema definition
  19. """
  20. return """
  21. -- Analyses table: stores basic analysis information
  22. CREATE TABLE IF NOT EXISTS analyses (
  23. id INTEGER PRIMARY KEY AUTOINCREMENT,
  24. report_id TEXT UNIQUE NOT NULL,
  25. timestamp DATETIME NOT NULL,
  26. device_id TEXT NOT NULL,
  27. overall_grade TEXT,
  28. grade_description TEXT,
  29. processing_time REAL,
  30. created_at DATETIME DEFAULT CURRENT_TIMESTAMP
  31. );
  32. -- Analysis inputs table: stores input files information
  33. CREATE TABLE IF NOT EXISTS analysis_inputs (
  34. id INTEGER PRIMARY KEY AUTOINCREMENT,
  35. analysis_id INTEGER NOT NULL,
  36. input_type TEXT NOT NULL,
  37. original_path TEXT NOT NULL,
  38. saved_path TEXT NOT NULL,
  39. file_size INTEGER,
  40. created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  41. FOREIGN KEY (analysis_id) REFERENCES analyses(id) ON DELETE CASCADE
  42. );
  43. -- Analysis results table: stores model predictions and results
  44. CREATE TABLE IF NOT EXISTS analysis_results (
  45. id INTEGER PRIMARY KEY AUTOINCREMENT,
  46. analysis_id INTEGER NOT NULL,
  47. model_type TEXT NOT NULL,
  48. predicted_class TEXT,
  49. confidence REAL,
  50. probabilities TEXT,
  51. processing_time REAL,
  52. metadata TEXT,
  53. created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  54. FOREIGN KEY (analysis_id) REFERENCES analyses(id) ON DELETE CASCADE
  55. );
  56. -- Analysis visualizations table: stores visualization image metadata
  57. CREATE TABLE IF NOT EXISTS analysis_visualizations (
  58. id INTEGER PRIMARY KEY AUTOINCREMENT,
  59. analysis_id INTEGER NOT NULL,
  60. visualization_type TEXT NOT NULL,
  61. file_path TEXT NOT NULL,
  62. created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  63. FOREIGN KEY (analysis_id) REFERENCES analyses(id) ON DELETE CASCADE
  64. );
  65. -- Create indexes for common queries
  66. CREATE INDEX IF NOT EXISTS idx_analyses_report_id ON analyses(report_id);
  67. CREATE INDEX IF NOT EXISTS idx_analyses_created_at ON analyses(created_at);
  68. CREATE INDEX IF NOT EXISTS idx_analysis_inputs_analysis_id ON analysis_inputs(analysis_id);
  69. CREATE INDEX IF NOT EXISTS idx_analysis_results_analysis_id ON analysis_results(analysis_id);
  70. CREATE INDEX IF NOT EXISTS idx_analysis_visualizations_analysis_id ON analysis_visualizations(analysis_id);
  71. """
  72. def init_database(db_path: Path) -> bool:
  73. """
  74. Initialize the database with schema.
  75. Args:
  76. db_path: Path to the database file
  77. Returns:
  78. bool: True if initialization successful, False otherwise
  79. """
  80. try:
  81. # Ensure parent directory exists
  82. db_path.parent.mkdir(parents=True, exist_ok=True)
  83. # Connect to database
  84. conn = sqlite3.connect(str(db_path))
  85. cursor = conn.cursor()
  86. # Execute schema
  87. cursor.executescript(get_database_schema())
  88. # Create metadata table to track schema version
  89. cursor.execute("""
  90. CREATE TABLE IF NOT EXISTS schema_version (
  91. version INTEGER PRIMARY KEY,
  92. updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
  93. )
  94. """)
  95. # Check if version exists
  96. cursor.execute("SELECT version FROM schema_version LIMIT 1")
  97. result = cursor.fetchone()
  98. if not result:
  99. # Insert initial version
  100. cursor.execute("INSERT INTO schema_version (version) VALUES (?)", (get_schema_version(),))
  101. conn.commit()
  102. conn.close()
  103. logger.info(f"Database initialized successfully: {db_path}")
  104. return True
  105. except Exception as e:
  106. logger.error(f"Error initializing database: {e}")
  107. return False
  108. def get_database_connection(db_path: Path) -> Optional[sqlite3.Connection]:
  109. """
  110. Get a connection to the database.
  111. Args:
  112. db_path: Path to the database file
  113. Returns:
  114. sqlite3.Connection or None if connection failed
  115. """
  116. try:
  117. # Initialize database if doesn't exist
  118. if not db_path.exists():
  119. init_database(db_path)
  120. # Enable foreign keys
  121. conn = sqlite3.connect(str(db_path))
  122. conn.execute("PRAGMA foreign_keys = ON")
  123. # Use Row factory for easier dict-like access
  124. conn.row_factory = sqlite3.Row
  125. return conn
  126. except Exception as e:
  127. logger.error(f"Error connecting to database: {e}")
  128. return None
  129. def close_database_connection(conn: sqlite3.Connection) -> None:
  130. """
  131. Close a database connection.
  132. Args:
  133. conn: Connection to close
  134. """
  135. try:
  136. if conn:
  137. conn.close()
  138. except Exception as e:
  139. logger.error(f"Error closing database connection: {e}")