vigilar/vigilar/web/blueprints/wildlife.py
Aaron D. Lee 8bf7900324 feat(Q3): wildlife journal blueprint with API routes and template
Add wildlife_bp with sightings, stats, frequency, and CSV export
endpoints; Bootstrap 5 dark journal template with live-updating
summary cards, species bars, time-of-day frequency chart, and
paginated/filterable sightings table.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 18:46:32 -04:00

73 lines
2.5 KiB
Python

"""Wildlife journal blueprint — sighting log, stats, export."""
import csv
import io
from flask import Blueprint, Response, current_app, jsonify, render_template, request
wildlife_bp = Blueprint("wildlife", __name__, url_prefix="/wildlife")
def _engine():
return current_app.config.get("DB_ENGINE")
@wildlife_bp.route("/")
def journal():
return render_template("wildlife/journal.html")
@wildlife_bp.route("/api/sightings")
def sightings_api():
engine = _engine()
if engine is None:
return jsonify({"sightings": []})
from vigilar.storage.queries import get_wildlife_sightings_paginated
sightings = get_wildlife_sightings_paginated(
engine,
species=request.args.get("species"),
threat_level=request.args.get("threat_level"),
camera_id=request.args.get("camera_id"),
since_ts=request.args.get("since", type=float),
until_ts=request.args.get("until", type=float),
limit=min(request.args.get("limit", 50, type=int), 500),
offset=request.args.get("offset", 0, type=int),
)
return jsonify({"sightings": sightings})
@wildlife_bp.route("/api/stats")
def stats_api():
engine = _engine()
if engine is None:
return jsonify({"total": 0, "species_count": 0, "per_species": {}})
from vigilar.storage.queries import get_wildlife_stats
return jsonify(get_wildlife_stats(engine))
@wildlife_bp.route("/api/frequency")
def frequency_api():
engine = _engine()
if engine is None:
return jsonify({})
from vigilar.storage.queries import get_wildlife_frequency
return jsonify(get_wildlife_frequency(engine))
@wildlife_bp.route("/api/export")
def export_csv():
engine = _engine()
if engine is None:
return Response("No data", mimetype="text/csv")
from vigilar.storage.queries import get_wildlife_sightings_paginated
sightings = get_wildlife_sightings_paginated(engine, limit=10000, offset=0)
output = io.StringIO()
writer = csv.writer(output)
writer.writerow(["id", "timestamp", "species", "threat_level", "camera_id",
"confidence", "temperature_c", "conditions"])
for s in sightings:
writer.writerow([s["id"], s["ts"], s["species"], s["threat_level"],
s["camera_id"], s.get("confidence"), s.get("temperature_c"), s.get("conditions")])
return Response(output.getvalue(), mimetype="text/csv",
headers={"Content-Disposition": "attachment; filename=wildlife_sightings.csv"})