212 lines
4.7 KiB
TypeScript
212 lines
4.7 KiB
TypeScript
/**
|
|
* Stack Observer Service
|
|
* Reads divergences from observer.db (SQLite)
|
|
* @author Descomplicar® | @link descomplicar.pt | @copyright 2026
|
|
*/
|
|
import sqlite3 from 'sqlite3'
|
|
import { open } from 'sqlite'
|
|
import path from 'path'
|
|
import { fileURLToPath } from 'url'
|
|
import os from 'os'
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
|
|
interface Evaluation {
|
|
id: number
|
|
source: string
|
|
entity_id: string
|
|
site_sig: string
|
|
result_real: string
|
|
result_nominal: string
|
|
tokens: number
|
|
reincidence_count: number
|
|
judge_method: string
|
|
confidence: number
|
|
evaluated_at: number
|
|
category: string
|
|
}
|
|
|
|
interface DivergenceStats {
|
|
total_divergences: number
|
|
by_category: Record<string, number>
|
|
by_judge: Record<string, number>
|
|
recent_24h: number
|
|
high_confidence: number
|
|
}
|
|
|
|
/**
|
|
* Open observer.db connection
|
|
* DB is located at ~/Dados/Dev/stack-observer/observer.db
|
|
*/
|
|
async function openObserverDb() {
|
|
const dbPath = path.join(
|
|
os.homedir(),
|
|
'Dados/Dev/stack-observer/observer.db'
|
|
)
|
|
|
|
return open({
|
|
filename: dbPath,
|
|
driver: sqlite3.Database
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Get all divergences (evaluations)
|
|
* Filters for result_real != result_nominal
|
|
*/
|
|
export async function getDivergences() {
|
|
const db = await openObserverDb()
|
|
|
|
try {
|
|
// Get divergences with divergence flag
|
|
const divergences = await db.all<Evaluation[]>(`
|
|
SELECT
|
|
id, source, entity_id, site_sig,
|
|
result_real, result_nominal,
|
|
tokens, reincidence_count,
|
|
judge_method, confidence, evaluated_at, category
|
|
FROM evaluations
|
|
WHERE result_real IS NOT NULL
|
|
AND category IN ('automation-liveness', 'automation-failure')
|
|
ORDER BY evaluated_at DESC
|
|
LIMIT 100
|
|
`)
|
|
|
|
// Calculate stats
|
|
const now = Math.floor(Date.now() / 1000)
|
|
const day_ago = now - 86400
|
|
|
|
const stats: DivergenceStats = {
|
|
total_divergences: 0,
|
|
by_category: {},
|
|
by_judge: {},
|
|
recent_24h: 0,
|
|
high_confidence: 0
|
|
}
|
|
|
|
if (divergences) {
|
|
stats.total_divergences = divergences.length
|
|
|
|
for (const div of divergences) {
|
|
// Count by category
|
|
stats.by_category[div.category] = (stats.by_category[div.category] ?? 0) + 1
|
|
|
|
// Count by judge method
|
|
stats.by_judge[div.judge_method] = (stats.by_judge[div.judge_method] ?? 0) + 1
|
|
|
|
// Count recent
|
|
if (div.evaluated_at >= day_ago) {
|
|
stats.recent_24h++
|
|
}
|
|
|
|
// Count high confidence
|
|
if (div.confidence >= 0.9) {
|
|
stats.high_confidence++
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
divergences: divergences ?? [],
|
|
stats,
|
|
generated_at: new Date().toISOString()
|
|
}
|
|
} finally {
|
|
await db.close()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get single divergence by ID
|
|
*/
|
|
export async function getDivergenceById(id: string) {
|
|
const db = await openObserverDb()
|
|
|
|
try {
|
|
const evaluation = await db.get<Evaluation>(`
|
|
SELECT * FROM evaluations
|
|
WHERE id = ?
|
|
`, [id])
|
|
|
|
return evaluation ?? null
|
|
} finally {
|
|
await db.close()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get divergence trend (last 7 days)
|
|
*/
|
|
export async function getDivergenceTrend() {
|
|
const db = await openObserverDb()
|
|
|
|
try {
|
|
interface TrendRow {
|
|
date: string
|
|
count: number
|
|
}
|
|
|
|
const trend = await db.all<TrendRow[]>(`
|
|
SELECT
|
|
DATE(evaluated_at, 'unixepoch') as date,
|
|
COUNT(*) as count
|
|
FROM evaluations
|
|
WHERE result_real IS NOT NULL
|
|
AND category IN ('automation-liveness', 'automation-failure')
|
|
AND evaluated_at >= (unixepoch() - 604800)
|
|
GROUP BY date
|
|
ORDER BY date DESC
|
|
`)
|
|
|
|
return trend ?? []
|
|
} finally {
|
|
await db.close()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get list of automations (hermes_jobs)
|
|
* F1b: Colector lê de ~/.hermes/data/action-log.db
|
|
*/
|
|
export async function getAutomations() {
|
|
const db = await openObserverDb()
|
|
|
|
try {
|
|
const automations = await db.all(`
|
|
SELECT
|
|
action_id, who, what, where_field, when_ts,
|
|
result, result_real, why, how, detail, session_id, created_at,
|
|
ingested_at
|
|
FROM hermes_jobs
|
|
ORDER BY created_at DESC
|
|
LIMIT 100
|
|
`)
|
|
|
|
return automations ?? []
|
|
} finally {
|
|
await db.close()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get list of gate events
|
|
* F1a: Colector lê de /tmp/stack-observer/gate-events.queue
|
|
*/
|
|
export async function getGateEvents() {
|
|
const db = await openObserverDb()
|
|
|
|
try {
|
|
const events = await db.all(`
|
|
SELECT
|
|
id, gate_id, event_type, payload, timestamp, ingested_at
|
|
FROM gate_events
|
|
ORDER BY timestamp DESC
|
|
LIMIT 100
|
|
`)
|
|
|
|
return events ?? []
|
|
} finally {
|
|
await db.close()
|
|
}
|
|
}
|