satdetect-dev / app /dda /bootstrap.py
coderuday21's picture
Improve detection accuracy with full-res tiling, config flags, and evaluation.
99e1f27
Raw
History Blame Contribute Delete
6.82 kB
import logging
from fastapi import FastAPI
from sqlalchemy import text as sa_text
from ..database import engine
from .config import IS_DDA_MODE, ensure_library_dirs, get_storage_root, is_hf_hosted
from .admin_routes import router as admin_router
from .jobs_routes import router as jobs_router
from .library_routes import router as library_router
from .local_routes import router as local_router
from .reports_routes import router as reports_router
from .review_routes import router as review_router
from .training_routes import router as training_router
from .tree.routes import router as tree_router
from .dda_auth import seed_dda_admin
logger = logging.getLogger(__name__)
def _migrate_detection_jobs_nullable() -> None:
"""SQLite legacy schema required image asset FKs; tree library jobs use paths only."""
try:
with engine.connect() as conn:
rows = conn.execute(sa_text("PRAGMA table_info(dda_detection_jobs)")).fetchall()
if not rows:
return
by_name = {r[1]: r for r in rows}
base_col = by_name.get("base_image_id")
if not base_col or base_col[3] == 0:
return
logger.info("Migrating dda_detection_jobs to allow NULL image IDs for path-based jobs")
conn.execute(sa_text("PRAGMA foreign_keys=OFF"))
conn.execute(sa_text("""
CREATE TABLE dda_detection_jobs_new (
id INTEGER NOT NULL PRIMARY KEY,
status VARCHAR(32),
base_image_id INTEGER,
comparison_image_id INTEGER,
method VARCHAR(64),
params_json TEXT,
run_id INTEGER,
error_message TEXT,
notify_email VARCHAR(255),
created_by INTEGER,
started_at DATETIME,
completed_at DATETIME,
created_at DATETIME,
FOREIGN KEY(base_image_id) REFERENCES dda_image_assets (id),
FOREIGN KEY(comparison_image_id) REFERENCES dda_image_assets (id),
FOREIGN KEY(run_id) REFERENCES detection_runs (id),
FOREIGN KEY(created_by) REFERENCES users (id)
)
"""))
conn.execute(sa_text("""
INSERT INTO dda_detection_jobs_new (
id, status, base_image_id, comparison_image_id, method, params_json,
run_id, error_message, notify_email, created_by,
started_at, completed_at, created_at
)
SELECT
id, status, base_image_id, comparison_image_id, method, params_json,
run_id, error_message, notify_email, created_by,
started_at, completed_at, created_at
FROM dda_detection_jobs
"""))
conn.execute(sa_text("DROP TABLE dda_detection_jobs"))
conn.execute(sa_text("ALTER TABLE dda_detection_jobs_new RENAME TO dda_detection_jobs"))
conn.execute(sa_text("PRAGMA foreign_keys=ON"))
conn.commit()
except Exception as exc:
logger.warning("Detection jobs nullable migration skipped: %s", exc)
def init_dda_database():
"""Run DDA-specific startup tasks (dirs, seed, migrations)."""
if not IS_DDA_MODE:
return
ensure_library_dirs()
get_storage_root().mkdir(parents=True, exist_ok=True)
try:
with engine.connect() as conn:
for stmt in (
"ALTER TABLE users ADD COLUMN role VARCHAR(32) DEFAULT 'analyst'",
"ALTER TABLE detection_runs ADD COLUMN after_full_path VARCHAR(512) DEFAULT ''",
):
try:
conn.execute(sa_text(stmt))
conn.commit()
except Exception:
conn.rollback()
try:
conn.execute(sa_text(
"CREATE UNIQUE INDEX IF NOT EXISTS ix_dda_region_reviews_run_region "
"ON dda_region_reviews (run_id, region_id)"
))
conn.commit()
except Exception:
conn.rollback()
_migrate_detection_jobs_nullable()
except Exception as exc:
logger.warning("DDA schema migration skipped: %s", exc)
from ..database import Base, SessionLocal
from .tree.models import AuditLog, ImageLibrary, TreeNode # noqa: F401
try:
Base.metadata.create_all(bind=engine, tables=[
TreeNode.__table__,
ImageLibrary.__table__,
AuditLog.__table__,
])
except Exception as exc:
logger.warning("Tree table create skipped: %s", exc)
db = SessionLocal()
try:
seed_dda_admin(db)
from .tree.migration import run_tree_migration
mig = run_tree_migration(db)
logger.info("Tree migration: %s", mig)
try:
from .tree.sync_service import sync_from_filesystem
sync_stats = sync_from_filesystem(db)
logger.info("Filesystem sync at startup: %s", sync_stats)
except Exception as exc:
logger.warning("Filesystem sync at startup failed: %s", exc)
from .job_runner import reconcile_stale_jobs
reconcile_stale_jobs(db)
finally:
db.close()
try:
from .tree.image_service import list_all_images
from ..database import SessionLocal as SL
sdb = SL()
try:
total = len(list_all_images(sdb))
finally:
sdb.close()
logger.info(
"DDA tree library ready (hosted=%s): %d images, storage=%s",
is_hf_hosted(),
total,
get_storage_root(),
)
except Exception as exc:
logger.warning("Tree library scan at startup failed: %s", exc)
def setup_dda(app: FastAPI) -> None:
if not IS_DDA_MODE:
logger.info("APP_MODE=legacy — DDA routes disabled")
return
app.include_router(tree_router, prefix="/api/dda", tags=["dda-tree"])
app.include_router(library_router, prefix="/api/dda", tags=["dda"])
app.include_router(jobs_router, prefix="/api/dda", tags=["dda-jobs"])
app.include_router(reports_router, prefix="/api/dda", tags=["dda-reports"])
app.include_router(review_router, prefix="/api/dda", tags=["dda-review"])
app.include_router(training_router, prefix="/api/dda", tags=["dda-training"])
app.include_router(admin_router, prefix="/api/dda", tags=["dda-admin"])
app.include_router(local_router, prefix="/api/dda", tags=["dda-local"])
logger.info("APP_MODE=dda — DDA routes enabled (tree, library, jobs, reports, review, training, admin, local)")