""" seed.py — Dados de demonstração para o mapa de Brasília, DF. Chamado no startup do app.py. Garante que os 6 animais fixos estão sempre no banco. """ import json import logging import shutil from datetime import datetime, timedelta from pathlib import Path import numpy as np log = logging.getLogger(__name__) _SEED_ANIMALS = [ { "species": "dog", "name": "Pretão", "photo_filename": "pretao.jpg", "description": { "species": "dog", "breed_estimate": "SRD", "size": "médio", "primary_color": "preto", "secondary_colors": ["caramelo"], "distinctive_marks": ["focinho caramelo", "patas caramelo"], "condition": "saudável", "description_text": "medium black dog with caramel markings on face and paws, calm and friendly", "_ai_success": True, }, "sightings": [ {"lat": -15.7560, "lng": -47.8920, "days_ago": 10, "notes": "Deitado na calçada da 406 Norte, aceitou carinho"}, {"lat": -15.7555, "lng": -47.8912, "days_ago": 3, "notes": "Voltou pro mesmo ponto, parece ter território fixo"}, ], }, { "species": "dog", "name": "Mel", "photo_filename": "mel.webp", "description": { "species": "dog", "breed_estimate": "SRD", "size": "médio", "primary_color": "branco", "secondary_colors": ["caramelo"], "distinctive_marks": ["manchas caramelo nas costas", "orelhas caramelo"], "condition": "saudável", "description_text": "medium white dog with caramel patches, resting near closed shop", "_ai_success": True, }, "sightings": [ {"lat": -15.8450, "lng": -47.8700, "days_ago": 20, "notes": "Próximo à feira do Lago Sul, dócil"}, {"lat": -15.8442, "lng": -47.8692, "days_ago": 8, "notes": "Mesma área, aparenta bem alimentada"}, {"lat": -15.8438, "lng": -47.8705, "days_ago": 1, "notes": "Vista descansando na entrada de um comércio"}, ], }, { "species": "dog", "name": "Caramelo", "photo_filename": "caramelo.jpg", "description": { "species": "dog", "breed_estimate": "SRD", "size": "grande", "primary_color": "caramelo", "secondary_colors": [], "distinctive_marks": ["pelagem densa", "cauda enrolada"], "condition": "saudável", "description_text": "large golden caramel mixed breed dog, standing alert near road in Taguatinga", "_ai_success": True, }, "sightings": [ {"lat": -15.8300, "lng": -47.9900, "days_ago": 45, "notes": "Visto na marginal de Taguatinga, sozinho"}, {"lat": -15.8312, "lng": -47.9888, "days_ago": 15, "notes": "Ainda na área, precisa de atenção"}, ], }, { "species": "dog", "name": "Manchinha", "photo_filename": "manchinha.jpg", "description": { "species": "dog", "breed_estimate": "SRD", "size": "médio", "primary_color": "branco", "secondary_colors": ["preto"], "distinctive_marks": ["manchas pretas na cabeça", "focinho malhado", "coleira verde"], "condition": "saudável", "description_text": "medium white dog with black patches on head, spotted muzzle, wearing green collar, very friendly", "_ai_success": True, }, "sightings": [ {"lat": -15.8010, "lng": -47.8960, "days_ago": 5, "notes": "Super dócil, tem coleira mas sem identificação — 308 Sul"}, {"lat": -15.8005, "lng": -47.8950, "days_ago": 1, "notes": "Brincando com passantes perto do comércio"}, ], }, { "species": "cat", "name": "Cinzinha", "photo_filename": "cinzinha.jpg", "description": { "species": "cat", "breed_estimate": "SRD", "size": "pequeno", "primary_color": "cinza", "secondary_colors": [], "distinctive_marks": ["listras tigradas", "olhos amarelos grandes"], "condition": "saudável", "description_text": "small grey tabby kitten with large yellow eyes, timid but curious", "_ai_success": True, }, "sightings": [ {"lat": -15.7620, "lng": -47.8875, "days_ago": 2, "notes": "Filhote na W3 Norte, aceita comida com cuidado"}, ], }, { "species": "cat", "name": "Laranja", "photo_filename": "laranja.jpg", "description": { "species": "cat", "breed_estimate": "SRD", "size": "médio", "primary_color": "laranja", "secondary_colors": [], "distinctive_marks": ["pelagem totalmente ruiva", "parte de colônia"], "condition": "saudável", "description_text": "medium orange tabby cat, part of a feral colony near dumpsters in Ceilândia", "_ai_success": True, }, "sightings": [ {"lat": -15.8170, "lng": -48.1080, "days_ago": 7, "notes": "Colônia de gatos ruivos perto da feira de Ceilândia"}, {"lat": -15.8165, "lng": -48.1072, "days_ago": 0, "notes": "Dois gatos laranja, parecem saudáveis"}, ], }, ] def _random_embedding(): v = np.random.randn(384).astype(np.float32) v /= np.linalg.norm(v) return v.tobytes() def _copy_seed_photos(): """Copia fotos de static/seed-photos/ (no repo) para DATA_DIR/photos/seed/ (runtime).""" from core.database import DATA_DIR, PHOTOS_DIR src_dir = Path(__file__).parent.parent / "static" / "seed-photos" dst_dir = PHOTOS_DIR / "seed" dst_dir.mkdir(parents=True, exist_ok=True) for src in src_dir.iterdir(): dst = dst_dir / src.name if not dst.exists(): shutil.copy2(src, dst) log.info("Foto copiada: %s", src.name) def seed_if_empty(db): """Garante que os animais fixos estão sempre no banco. Insere apenas os que ainda nao existem (por nome). Dados reais dos usuarios nunca sao removidos. """ with db._conn() as conn: existing_names = { row[0] for row in conn.execute( "SELECT name FROM animals WHERE name IS NOT NULL" ) } # Sempre copia fotos (idempotente) — garante que estao disponiveis apos restart _copy_seed_photos() to_insert = [a for a in _SEED_ANIMALS if a["name"] not in existing_names] if not to_insert: log.info("Animais fixos ja presentes — seed ignorado.") return log.info("Inserindo %d animais fixos de demonstracao...", len(to_insert)) now = datetime.utcnow() with db._conn() as conn: for animal_data in to_insert: sightings = animal_data["sightings"] desc = animal_data["description"] photo_path = "photos/seed/" + animal_data["photo_filename"] most_recent_days = min(s["days_ago"] for s in sightings) last_seen = now - timedelta(days=most_recent_days) first_seen = now - timedelta(days=max(s["days_ago"] for s in sightings)) cur = conn.execute( "INSERT INTO animals" " (species, name, description, embedding, first_seen, last_seen, sighting_count)" " VALUES (?, ?, ?, ?, ?, ?, ?)", ( animal_data["species"], animal_data["name"], json.dumps(desc, ensure_ascii=False), _random_embedding(), first_seen.strftime("%Y-%m-%d %H:%M:%S"), last_seen.strftime("%Y-%m-%d %H:%M:%S"), len(sightings), ), ) animal_id = cur.lastrowid for i, s in enumerate(sightings): created_at = now - timedelta(days=s["days_ago"]) s_photo = photo_path if i == len(sightings) - 1 else None conn.execute( "INSERT INTO sightings" " (animal_id, photo_path, latitude, longitude, notes, created_at)" " VALUES (?, ?, ?, ?, ?, ?)", ( animal_id, s_photo, s["lat"], s["lng"], s.get("notes", ""), created_at.strftime("%Y-%m-%d %H:%M:%S"), ), ) log.info("Seed concluido: %d animais inseridos (%d avistamentos).", len(to_insert), sum(len(a["sightings"]) for a in to_insert))