● Zion Boggan
repos/Sightline/backend/seed_real.py
zionboggan.com ↗
327 lines · python
History for this file →
1
"""Fast bulk seeder for the nationwide POC built on REAL ALPR camera locations
2
(sample_data/real_cameras.json from fetch_cameras.py / OpenStreetMap-DeFlock).
3
 
4
Loads the real cameras, then bulk-generates a realistic intelligence dataset on top of them:
5
vehicles + plates (all 50 states), a roster of named persons (some wanted), plate reads with
6
commute/scenario patterns, sightings, and person detections carrying clothing/article descriptors
7
so name/outfit search works. Uses core bulk inserts - loads ~100k rows in seconds, not minutes.
8
 
9
Real = camera locations/metadata (operator, direction, zone, coordinates).
10
Synthetic = plate reads, vehicles, persons, detections (this data is not public; it is generated
11
realistically against the real camera positions for the proof of concept).
12
"""
13
from __future__ import annotations
14
 
15
import json
16
import os
17
import random
18
import string
19
from datetime import datetime, timedelta, timezone
20
 
21
from sqlalchemy import delete, func, insert, select
22
 
23
from app.core.security import hash_password
24
from app.db.session import SessionLocal, engine
25
from app.models import (
26
    Camera, CameraGroup, Detection, Investigation, LicensePlate, Location, Person, PlateRead,
27
    Sighting, Source, User, Vehicle, Watchlist, WatchlistEntry, WatchlistHit,
28
)
29
from app.services.normalize import dedup_hash
30
 
31
random.seed(2026)
32
CAMS_JSON = os.path.join(os.path.dirname(__file__), "sample_data", "real_cameras.json")
33
 
34
DAYS = 14
35
END = datetime(2026, 6, 10, tzinfo=timezone.utc)
36
START = END - timedelta(days=DAYS)
37
 
38
MAKES = [("Toyota", ["Camry", "Corolla", "RAV4", "Tacoma"]), ("Honda", ["Civic", "Accord", "CR-V"]),
39
         ("Ford", ["F-150", "Escape", "Explorer", "Mustang"]), ("Chevrolet", ["Malibu", "Equinox", "Silverado", "Tahoe"]),
40
         ("Nissan", ["Altima", "Rogue", "Sentra"]), ("Jeep", ["Grand Cherokee", "Wrangler"]),
41
         ("Dodge", ["Charger", "Challenger", "Ram"]), ("Hyundai", ["Elantra", "Sonata", "Tucson"]),
42
         ("Tesla", ["Model 3", "Model Y"]), ("GMC", ["Sierra", "Yukon"]), ("BMW", ["3 Series", "X5"]),
43
         ("Kia", ["Optima", "Sorento", "Telluride"])]
44
BODY = {"Camry": "Sedan", "Corolla": "Sedan", "RAV4": "SUV", "Tacoma": "Pickup", "Civic": "Sedan",
45
        "Accord": "Sedan", "CR-V": "SUV", "F-150": "Pickup", "Escape": "SUV", "Explorer": "SUV",
46
        "Mustang": "Coupe", "Malibu": "Sedan", "Equinox": "SUV", "Silverado": "Pickup", "Tahoe": "SUV",
47
        "Altima": "Sedan", "Rogue": "SUV", "Sentra": "Sedan", "Grand Cherokee": "SUV", "Wrangler": "SUV",
48
        "Charger": "Sedan", "Challenger": "Coupe", "Ram": "Pickup", "Elantra": "Sedan", "Sonata": "Sedan",
49
        "Tucson": "SUV", "Model 3": "Sedan", "Model Y": "SUV", "Sierra": "Pickup", "Yukon": "SUV",
50
        "3 Series": "Sedan", "X5": "SUV", "Optima": "Sedan", "Sorento": "SUV", "Telluride": "SUV"}
51
COLORS = ["White", "Black", "Silver", "Gray", "Blue", "Red", "Green", "Tan", "Maroon"]
52
 
53
FIRST = ["James", "Maria", "Marcus", "Aisha", "David", "Elena", "Tyler", "Fatima", "Brandon",
54
         "Sofia", "Andre", "Nina", "Carlos", "Grace", "Dmitri", "Layla", "Hassan", "Chloe",
55
         "Victor", "Priya", "Omar", "Renee", "Jamal", "Tara", "Sean", "Yuki", "Diego", "Hannah"]
56
LAST = ["Delgado", "Okafor", "Nguyen", "Reyes", "Carter", "Petrov", "Hassan", "Bauer", "Romano",
57
        "Cole", "Mbeki", "Larsen", "Castillo", "Fischer", "Abadi", "Walsh", "Kim", "Vega",
58
        "Donovan", "Sharma", "Cross", "Ibrahim", "Sokolov", "Tanaka", "Mercer", "Quinn"]
59
TOPS = ["black hoodie", "red t-shirt", "blue windbreaker", "gray sweatshirt", "white polo",
60
        "camouflage jacket", "yellow rain jacket", "green flannel shirt", "navy peacoat",
61
        "orange hi-vis vest", "leather jacket", "purple track top"]
62
BOTTOMS = ["blue jeans", "black sweatpants", "khaki shorts", "tactical cargo pants", "gray slacks",
63
           "dark wash jeans", "athletic leggings", "carpenter pants"]
64
ARTICLES = ["backpack", "baseball cap", "sunglasses", "duffel bag", "face mask", "beanie",
65
            "crossbody bag", "work boots", "gloves", "messenger bag", "umbrella", "lanyard"]
66
HAIR = ["black", "brown", "blonde", "gray", "red", "bald", "shaved"]
67
BUILD = ["slim", "medium", "athletic", "heavy", "tall/lean"]
68
 
69
 
70
def plate():
71
    n = random.choice([3, 3, 4])
72
    return "".join(random.choices(string.ascii_uppercase, k=n)) + "".join(random.choices("0123456789", k=7 - n))
73
 
74
 
75
def main():
76
    if not os.path.exists(CAMS_JSON):
77
        raise SystemExit(f"missing {CAMS_JSON} - run fetch_cameras.py first")
78
    with open(CAMS_JSON) as fh:
79
        real = [c for c in json.load(fh) if c.get("lat") and c.get("lon")]
80
    print(f"loaded {len(real)} real ALPR cameras")
81
 
82
    db = SessionLocal()
83
    org = "default"
84
 
85
    for tbl in (Detection, WatchlistHit, Sighting, PlateRead, WatchlistEntry, Watchlist,
86
                Investigation, Person, Vehicle, LicensePlate, Camera, Location, CameraGroup):
87
        db.execute(delete(tbl))
88
    db.commit()
89
 
90
    for email, pw, name, role in [("admin@sightline.app", "changeme123", "Lead Analyst", "admin"),
91
                                  ("analyst@sightline.app", "analyst123", "Field Analyst", "analyst"),
92
                                  ("viewer@sightline.app", "viewer1234", "Watch Desk", "viewer")]:
93
        if not db.scalar(select(User).where(User.email == email)):
94
            db.add(User(email=email, hashed_password=hash_password(pw), full_name=name, role=role))
95
    db.commit()
96
    admin = db.scalar(select(User).where(User.email == "admin@sightline.app"))
97
    source, _ = (db.scalar(select(Source).where(Source.name == "National ALPR Aggregate")), None)
98
    if not source:
99
        source = Source(name="National ALPR Aggregate", system_type="flock", organization=org,
100
                        jurisdiction="Multi-state (US)", trust_level=0.9)
101
        db.add(source); db.commit()
102
 
103
    import uuid
104
    states = sorted({c["state"] for c in real})
105
    groups = {st: str(uuid.uuid4()) for st in states}
106
    db.execute(insert(CameraGroup), [{"id": gid, "name": f"{st} - Statewide",
107
                "organization": org} for st, gid in groups.items()])
108
 
109
    loc_rows, cam_rows = [], []
110
    cams_by_state: dict[str, list[dict]] = {}
111
    for c in real:
112
        lid, cid = str(uuid.uuid4()), str(uuid.uuid4())
113
        loc_rows.append({"id": lid, "label": f"{c['operator']} ALPR ({c['state']})",
114
                         "lat": c["lat"], "lon": c["lon"], "jurisdiction": c["state"]})
115
        name = f"{c['operator']} - {c['state']}" + (f" · {c['zone']}" if c.get("zone") else "")
116
        cam = {"id": cid, "source_id": source.id, "external_camera_id": f"OSM-{c['osm_id']}",
117
               "name": name, "direction_facing": (c.get("direction") or None),
118
               "operator": c["operator"], "manufacturer": c.get("manufacturer"),
119
               "zone": c.get("zone"), "state": c["state"], "osm_id": str(c["osm_id"]),
120
               "location_id": lid, "camera_group_id": groups[c["state"]], "metadata": {}}
121
        cam_rows.append(cam)
122
        cams_by_state.setdefault(c["state"], []).append({"id": cid, "lat": c["lat"], "lon": c["lon"]})
123
    db.execute(insert(Location), loc_rows)
124
    db.execute(insert(Camera), cam_rows)
125
    db.commit()
126
    print(f"inserted {len(cam_rows)} cameras, {len(states)} states")
127
 
128
    plate_rows, veh_rows = [], []
129
    vehicles: list[dict] = []
130
    used_plates = set()
131
    for st in states:
132
        for _ in range(max(8, len(cams_by_state[st]) // 6)):
133
            p = plate()
134
            while p in used_plates:
135
                p = plate()
136
            used_plates.add(p)
137
            mk, models = random.choice(MAKES); model = random.choice(models)
138
            pid, vid = str(uuid.uuid4()), str(uuid.uuid4())
139
            color = random.choice(COLORS)
140
            plate_rows.append({"id": pid, "plate_raw": p, "plate_norm": p, "state": st, "plate_type": "passenger"})
141
            veh_rows.append({"id": vid, "primary_plate_id": pid, "make": mk, "model": model,
142
                             "color": color, "body_type": BODY[model], "year": random.randint(2012, 2025)})
143
            vehicles.append({"plate": p, "state": st, "vehicle_id": vid, "plate_id": pid,
144
                             "make": mk, "model": model, "color": color})
145
    db.execute(insert(LicensePlate), plate_rows)
146
    db.execute(insert(Vehicle), veh_rows)
147
    db.commit()
148
    print(f"inserted {len(veh_rows)} vehicles/plates")
149
 
150
    veh_by_plate = {v["plate"]: v for v in vehicles}
151
 
152
    person_rows = []
153
    persons = []
154
    sample_v = random.sample(vehicles, min(450, len(vehicles)))
155
    for i, v in enumerate(sample_v):
156
        nm = f"{random.choice(FIRST)} {random.choice(LAST)}"
157
        wanted = i < 60
158
        pid = str(uuid.uuid4())
159
        person_rows.append({"id": pid, "full_name": nm,
160
                            "aliases": [f"{nm.split()[0][0]}. {nm.split()[1]}"] if random.random() < 0.4 else [],
161
                            "dob": f"19{random.randint(60,99)}-{random.randint(1,12):02d}-{random.randint(1,28):02d}",
162
                            "sex": random.choice(["M", "F"]), "height_cm": random.randint(155, 196),
163
                            "build": random.choice(BUILD), "hair": random.choice(HAIR),
164
                            "eyes": random.choice(["brown", "blue", "green", "hazel"]),
165
                            "distinguishing_marks": random.choice([None, None, "tattoo left forearm",
166
                                "scar over right eye", "neck tattoo", "limp (left leg)"]),
167
                            "associated_plate": v["plate"],
168
                            "threat_level": "high" if wanted and i < 20 else "medium" if wanted else random.choice(["none","none","low"]),
169
                            "wanted": wanted, "notes": "Subject of active inquiry." if wanted else None})
170
        persons.append({"id": pid, "plate": v["plate"], "state": v["state"]})
171
    db.execute(insert(Person), person_rows)
172
    db.commit()
173
    print(f"inserted {len(person_rows)} persons ({sum(1 for p in person_rows if p['wanted'])} wanted)")
174
 
175
    read_rows, seen_hashes = [], set()
176
 
177
    def add_read(v, cam, ts, hotlist=False, incident=""):
178
        dh = dedup_hash(org, v["plate"], cam["id"], ts, 1)
179
        if dh in seen_hashes:
180
            return None
181
        seen_hashes.add(dh)
182
        read_rows.append({"import_id": None, "source_id": source.id, "camera_id": cam["id"],
183
                          "plate_id": v["plate_id"], "plate_norm": v["plate"], "read_ts": ts,
184
                          "direction": random.choice(["N", "NE", "E", "SE", "S", "SW", "W", "NW"]),
185
                          "confidence": round(random.uniform(0.82, 0.99), 3), "lane": None,
186
                          "lat": cam["lat"], "lon": cam["lon"], "hotlist_hit": hotlist,
187
                          "incident_ref": incident or None, "burst_count": 1,
188
                          "raw_read": {}, "dedup_hash": dh})
189
        return read_rows[-1]
190
 
191
    for v in vehicles:
192
        cams = cams_by_state.get(v["state"]) or []
193
        if len(cams) < 2:
194
            continue
195
        home, work = random.sample(cams, 2)
196
        for day in range(DAYS):
197
            d = START + timedelta(days=day)
198
            if d.weekday() >= 5 or random.random() < 0.2:
199
                continue
200
            am = d.replace(hour=random.randint(6, 8), minute=random.randint(0, 59), second=random.randint(0, 59))
201
            for k, cam in enumerate([home, random.choice(cams), work]):
202
                add_read(v, cam, am + timedelta(minutes=8 * k + random.randint(0, 4)))
203
            pm = d.replace(hour=random.randint(16, 18), minute=random.randint(0, 59), second=random.randint(0, 59))
204
            for k, cam in enumerate([work, random.choice(cams), home]):
205
                add_read(v, cam, pm + timedelta(minutes=8 * k + random.randint(0, 4)))
206
 
207
    for v in random.sample(vehicles, min(40, len(vehicles))):
208
        visit = random.sample(states, k=random.randint(3, 6))
209
        day = random.randint(0, DAYS - 1)
210
        for st in visit:
211
            cams = cams_by_state.get(st) or []
212
            if not cams:
213
                continue
214
            d = START + timedelta(days=day)
215
            add_read(v, random.choice(cams), d.replace(hour=random.randint(8, 20), minute=random.randint(0, 59)))
216
            day = min(DAYS - 1, day + 1)
217
 
218
    stolen_v = {"plate": "BNDT447", "state": "TN", "plate_id": None, "vehicle_id": None,
219
                "make": "Dodge", "model": "Charger", "color": "Black"}
220
    spid, svid = str(uuid.uuid4()), str(uuid.uuid4())
221
    db.execute(insert(LicensePlate), [{"id": spid, "plate_raw": "BNDT447", "plate_norm": "BNDT447", "state": "TN", "plate_type": "passenger"}])
222
    db.execute(insert(Vehicle), [{"id": svid, "primary_plate_id": spid, "make": "Dodge", "model": "Charger", "color": "Black", "body_type": "Sedan", "year": 2021}])
223
    stolen_v["plate_id"], stolen_v["vehicle_id"] = spid, svid
224
    veh_by_plate["BNDT447"] = stolen_v
225
    for st in ["TN", "MS", "AL", "GA", "AR", "MO", "KY"]:
226
        for cam in (cams_by_state.get(st) or [])[:3]:
227
            d = START + timedelta(days=random.randint(DAYS - 7, DAYS - 1))
228
            add_read(stolen_v, cam, d.replace(hour=random.randint(0, 23), minute=random.randint(0, 59)),
229
                     hotlist=True, incident="INC-2026-0440")
230
 
231
    clone_v = {"plate": "CLN9921", "state": "CA", "plate_id": None, "vehicle_id": None}
232
    cpid, cvid = str(uuid.uuid4()), str(uuid.uuid4())
233
    db.execute(insert(LicensePlate), [{"id": cpid, "plate_raw": "CLN9921", "plate_norm": "CLN9921", "state": "CA", "plate_type": "passenger"}])
234
    db.execute(insert(Vehicle), [{"id": cvid, "primary_plate_id": cpid, "make": "Honda", "model": "Accord", "color": "White", "body_type": "Sedan", "year": 2019}])
235
    clone_v["plate_id"], clone_v["vehicle_id"] = cpid, cvid
236
    veh_by_plate["CLN9921"] = clone_v
237
    base = (START + timedelta(days=DAYS - 3)).replace(hour=14, minute=0, second=0)
238
    far = []
239
    for st in ["CA", "NY", "FL"]:
240
        cs = cams_by_state.get(st) or []
241
        if cs:
242
            far.append(cs[0])
243
    for k, cam in enumerate(far):
244
        add_read(clone_v, cam, base + timedelta(seconds=120 * k))
245
 
246
    db.execute(insert(PlateRead), read_rows)
247
    db.commit()
248
    print(f"inserted {len(read_rows)} plate reads")
249
 
250
    id_by_hash = {bytes(h): rid for rid, h in db.execute(select(PlateRead.id, PlateRead.dedup_hash)).all()}
251
    sight_rows = []
252
    for r in read_rows:
253
        rid = id_by_hash.get(r["dedup_hash"])
254
        v = veh_by_plate.get(r["plate_norm"])
255
        if rid and v:
256
            sight_rows.append({"plate_read_id": rid, "vehicle_id": v["vehicle_id"], "camera_id": r["camera_id"],
257
                               "read_ts": r["read_ts"], "confidence": r["confidence"]})
258
    db.execute(insert(Sighting), sight_rows)
259
    db.commit()
260
    print(f"inserted {len(sight_rows)} sightings")
261
 
262
    det_rows = []
263
    person_by_plate = {p["plate"]: p for p in persons}
264
    for r in random.sample(read_rows, min(9000, len(read_rows))):
265
        p = person_by_plate.get(r["plate_norm"])
266
        top, bottom = random.choice(TOPS), random.choice(BOTTOMS)
267
        arts = random.sample(ARTICLES, k=random.randint(0, 3))
268
        det_rows.append({"person_id": p["id"] if p else None, "camera_id": r["camera_id"],
269
                         "sighting_id": None, "read_ts": r["read_ts"], "plate_norm": r["plate_norm"],
270
                         "occupant_role": random.choice(["driver", "driver", "passenger"]),
271
                         "clothing_top": top, "clothing_bottom": bottom, "articles": arts,
272
                         "description": f"{random.choice(BUILD)} build, wearing {top} and {bottom}"
273
                                        + (f", carrying {', '.join(arts)}" if arts else ""),
274
                         "confidence": round(random.uniform(0.6, 0.95), 3)})
275
    marcus = db.scalar(select(Person).where(Person.wanted.is_(True)).order_by(Person.full_name).limit(1))
276
    if marcus:
277
        marcus.associated_plate = "BNDT447"; marcus.threat_level = "high"; marcus.notes = "Linked to stolen vehicle INC-2026-0440."
278
        for r in [x for x in read_rows if x["plate_norm"] == "BNDT447"][:8]:
279
            det_rows.append({"person_id": marcus.id, "camera_id": r["camera_id"], "sighting_id": None,
280
                             "read_ts": r["read_ts"], "plate_norm": "BNDT447", "occupant_role": "driver",
281
                             "clothing_top": "black hoodie", "clothing_bottom": "dark wash jeans",
282
                             "articles": ["backpack", "baseball cap"],
283
                             "description": "medium build, black hoodie, baseball cap, carrying a backpack",
284
                             "confidence": 0.88})
285
    db.execute(insert(Detection), det_rows)
286
    db.commit()
287
    print(f"inserted {len(det_rows)} detections")
288
 
289
    bolo = Watchlist(name="Stolen Vehicles - Active BOLO", kind="plate", organization=org, created_by=admin.id)
290
    poi = Watchlist(name="Persons of Interest", kind="plate", organization=org, created_by=admin.id)
291
    db.add_all([bolo, poi]); db.flush()
292
    e1 = WatchlistEntry(watchlist_id=bolo.id, plate_norm="BNDT447", reason="Reported stolen - INC-2026-0440")
293
    e2 = WatchlistEntry(watchlist_id=poi.id, plate_norm="CLN9921", reason="Suspected cloned plate")
294
    db.add_all([e1, e2]); db.flush()
295
    hit_rows = []
296
    for r in db.execute(select(PlateRead.id, PlateRead.plate_norm)).all():
297
        if r[1] == "BNDT447":
298
            hit_rows.append({"watchlist_entry_id": e1.id, "plate_read_id": r[0], "matched_on": "exact_plate"})
299
        elif r[1] == "CLN9921":
300
            hit_rows.append({"watchlist_entry_id": e2.id, "plate_read_id": r[0], "matched_on": "exact_plate"})
301
    if hit_rows:
302
        db.execute(insert(WatchlistHit), hit_rows)
303
    db.commit()
304
    print(f"watchlist hits: {len(hit_rows)}")
305
 
306
    def attach(inv_id, plate):
307
        ids = [r[0] for r in db.execute(select(PlateRead.id).where(PlateRead.plate_norm == plate)).all()]
308
        if ids:
309
            db.execute(Sighting.__table__.update().where(Sighting.plate_read_id.in_(ids)).values(investigation_id=inv_id))
310
        return len(ids)
311
 
312
    inv1 = Investigation(title="Operation Nightshade - Stolen Charger", priority="high", status="active",
313
                         organization=org, lead_user_id=admin.id,
314
                         summary="Stolen black Dodge Charger (BNDT447), INC-2026-0440, tracked multi-state.")
315
    inv2 = Investigation(title="Cloned Plate Review - CLN9921", priority="normal", status="open",
316
                         organization=org, lead_user_id=admin.id,
317
                         summary="Plate CLN9921 shows impossible cross-country transit - suspected clone.")
318
    db.add_all([inv1, inv2]); db.flush()
319
    attach(inv1.id, "BNDT447"); attach(inv2.id, "CLN9921")
320
    db.commit()
321
 
322
    n = db.scalar(select(func.count()).select_from(PlateRead))
323
    print(f"DONE. reads={n}, cameras={len(cam_rows)}, vehicles={len(veh_rows)}, persons={len(person_rows)}, detections={len(det_rows)}")
324
 
325
 
326
if __name__ == "__main__":
327
    main()