● Zion Boggan
repos/Sightline/backend/seed.py
zionboggan.com ↗
99 lines · python
History for this file →
1
"""Idempotent seed loader. Creates an admin user, a source, a watchlist, ingests the
2
bundled sample exports, and stands up a demo investigation with sightings attached.
3
Safe to run repeatedly (uses get-or-create + content-hash import dedup)."""
4
from __future__ import annotations
5
 
6
import hashlib
7
import os
8
 
9
from sqlalchemy import select
10
 
11
from app.core.security import hash_password
12
from app.db.session import SessionLocal
13
from app.models import (
14
    Import, Investigation, Sighting, Source, User, Watchlist, WatchlistEntry, PlateRead,
15
)
16
from app.services.ingest import process_import
17
 
18
SAMPLE_DIR = os.path.join(os.path.dirname(__file__), "sample_data")
19
 
20
 
21
def get_or_create(db, model, defaults=None, **filters):
22
    obj = db.scalar(select(model).filter_by(**filters))
23
    if obj:
24
        return obj, False
25
    obj = model(**filters, **(defaults or {}))
26
    db.add(obj)
27
    db.flush()
28
    return obj, True
29
 
30
 
31
def ingest_file(db, source, path):
32
    with open(path, "rb") as fh:
33
        data = fh.read()
34
    sha = hashlib.sha256(data).hexdigest()
35
    existing = db.scalar(select(Import).where(Import.sha256 == sha))
36
    if existing:
37
        return existing.id, "already imported"
38
    ctype = ("text/csv" if path.endswith(".csv")
39
             else "application/pdf" if path.endswith(".pdf") else "application/json")
40
    imp = Import(source_id=source.id, filename=os.path.basename(path),
41
                 content_type=ctype, sha256=sha, raw_blob_ref=path, status="received")
42
    db.add(imp)
43
    db.commit()
44
    summary = process_import(db, imp.id)
45
    return imp.id, summary
46
 
47
 
48
def main():
49
    db = SessionLocal()
50
    try:
51
        admin, created = get_or_create(
52
            db, User, email="admin@sightline.app",
53
            defaults={"hashed_password": hash_password("changeme123"),
54
                      "full_name": "Demo Admin", "role": "admin"})
55
        if created:
56
            print("created admin: admin@sightline.app / changeme123")
57
 
58
        source, _ = get_or_create(
59
            db, Source, name="Demo Flock Export",
60
            defaults={"system_type": "flock", "organization": "default",
61
                      "jurisdiction": "Shelby County, TN", "trust_level": 0.9})
62
 
63
        wl, _ = get_or_create(db, Watchlist, name="Stolen Vehicles",
64
                              defaults={"kind": "plate", "organization": "default",
65
                                        "created_by": admin.id})
66
        if not db.scalar(select(WatchlistEntry).where(WatchlistEntry.plate_norm == "XYZ7788")):
67
            db.add(WatchlistEntry(watchlist_id=wl.id, plate_norm="XYZ7788",
68
                                  reason="reported stolen (demo)"))
69
        db.commit()
70
 
71
        for fname in ("flock_sample.csv", "flock_sample.json", "flock_report.pdf"):
72
            path = os.path.join(SAMPLE_DIR, fname)
73
            if os.path.exists(path):
74
                _id, summary = ingest_file(db, source, path)
75
                print(f"ingested {fname}: {summary}")
76
 
77
        inv, created = get_or_create(
78
            db, Investigation, title="Demo: Downtown Corridor Activity",
79
            defaults={"summary": "Sample investigation tracking vehicles through the "
80
                                  "downtown/Poplar corridor.", "priority": "high",
81
                      "status": "active", "organization": "default", "lead_user_id": admin.id})
82
        if created:
83
            for plate in ("ABC1234", "XYZ7788", "GHI5566"):
84
                reads = db.scalars(select(PlateRead).where(PlateRead.plate_norm == plate)).all()
85
                for r in reads:
86
                    s = db.scalar(select(Sighting).where(Sighting.plate_read_id == r.id))
87
                    if s:
88
                        s.investigation_id = inv.id
89
            db.commit()
90
            print(f"created investigation: {inv.title} ({inv.id})")
91
 
92
        total = db.scalar(select(PlateRead).limit(1))
93
        print("seed complete." if total else "seed complete (no reads - check sample data).")
94
    finally:
95
        db.close()
96
 
97
 
98
if __name__ == "__main__":
99
    main()