"""Catálogo de queries con nombre del service osint_db. Cada entrada mapea un nombre estable a {sql, description}. El plugin de Obsidian (y /api/render/note) las invoca por nombre via POST /api/query/named, así el SQL vive en un solo sitio y el cliente no necesita conocer el schema. Todas son SELECT puros: se ejecutan por duckdb_query_readonly. """ NAMED_QUERIES = { "personas_por_contexto": { "description": "Personas agrupadas por contexto (círculo de origen), de mayor a menor.", "sql": ( "SELECT coalesce(contexto, '(sin)') AS contexto, COUNT(*) AS personas " "FROM persons GROUP BY 1 ORDER BY personas DESC, contexto" ), }, "personas_recientes": { "description": "Últimas 50 fichas de persona tocadas en el vault (por mtime de la nota).", "sql": ( "SELECT slug, nombre, contexto, note_path, updated_at " "FROM persons ORDER BY updated_at DESC LIMIT 50" ), }, "eventos_proximos": { "description": "Próximos 50 eventos del calendario a partir de hoy, en orden cronológico.", "sql": ( "SELECT uid, calendar, dtstart, dtend, all_day, summary, location " "FROM events WHERE dtstart >= strftime(now(), '%Y-%m-%d') " "ORDER BY dtstart LIMIT 50" ), }, "contactos_sin_nota": { "description": "Contactos CardDAV que no enlazan con ninguna ficha del vault (note_path IS NULL).", "sql": ( "SELECT uid, fn, tels, emails FROM contacts " "WHERE note_path IS NULL ORDER BY fn" ), }, "stats_personas": { "description": "Agregados de personas por dimensión (contexto, país, tag) desde derived.person_stats.", "sql": ( "SELECT dimension, valor, n FROM derived.person_stats " "ORDER BY dimension, n DESC, valor" ), }, "calidad_enlace_contactos": { "description": "Resumen de contactos enlazados a persona vs sin enlazar (derived.contact_link_quality).", "sql": "SELECT total, linked, unlinked FROM derived.contact_link_quality", }, "eventos_por_mes": { "description": "Conteo de eventos por calendario y mes (derived.event_monthly).", "sql": ( "SELECT calendar, month, n FROM derived.event_monthly " "ORDER BY month DESC, calendar" ), }, }