Spaces:
Running
Running
| """ | |
| 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)) | |