feat(observabilidade): adiciona flag --backfill ao sessions-patterns
Itera semanas ISO desde a primeira sessão até (excluindo) a semana corrente, detectando padrões e fazendo upsert com consecutive_weeks acumulado. Nunca publica comentários nem abre tickets — apenas povoa a tabela patterns com histórico. Output JSON por semana e sumário final. Permite popular retroactivamente a tabela patterns após nova instalação ou reset, dando base imediata ao detector de padrões persistentes.
This commit is contained in:
@@ -4,6 +4,12 @@
|
|||||||
*
|
*
|
||||||
* Uso:
|
* Uso:
|
||||||
* sessions-patterns.ts [--week YYYY-Www] [--publish] [--force]
|
* sessions-patterns.ts [--week YYYY-Www] [--publish] [--force]
|
||||||
|
* sessions-patterns.ts --backfill
|
||||||
|
*
|
||||||
|
* Backfill:
|
||||||
|
* Itera todas as semanas ISO desde a primeira sessão na BD até (excluindo)
|
||||||
|
* a semana corrente, detecta padrões e faz upsert. Nunca publica nem abre
|
||||||
|
* tickets. Output: linha JSON por semana + sumário total no fim.
|
||||||
*
|
*
|
||||||
* Fluxo:
|
* Fluxo:
|
||||||
* 1. Resolver intervalo da semana (segunda 00:00 UTC → domingo 23:59 UTC)
|
* 1. Resolver intervalo da semana (segunda 00:00 UTC → domingo 23:59 UTC)
|
||||||
@@ -18,11 +24,12 @@
|
|||||||
* MCP_GATEWAY_TOKEN Bearer token do gateway MCP
|
* MCP_GATEWAY_TOKEN Bearer token do gateway MCP
|
||||||
* MCP_GATEWAY_URL URL do MCP desk-crm (default https://gateway.descomplicar.pt/v1/desk-crm/mcp)
|
* MCP_GATEWAY_URL URL do MCP desk-crm (default https://gateway.descomplicar.pt/v1/desk-crm/mcp)
|
||||||
*/
|
*/
|
||||||
import { openSessionsDb, type PatternRecord } from '../services/sessions/db.js'
|
import { openSessionsDb, type PatternRecord, type SessionsDb } from '../services/sessions/db.js'
|
||||||
import { DEFAULT_DB_PATH } from '../services/sessions/indexer.js'
|
import { DEFAULT_DB_PATH } from '../services/sessions/indexer.js'
|
||||||
import {
|
import {
|
||||||
detectPatterns,
|
detectPatterns,
|
||||||
toPatternRecord,
|
toPatternRecord,
|
||||||
|
weekIso,
|
||||||
weekRange,
|
weekRange,
|
||||||
type Pattern,
|
type Pattern,
|
||||||
} from '../services/sessions/patterns.js'
|
} from '../services/sessions/patterns.js'
|
||||||
@@ -31,6 +38,7 @@ interface Args {
|
|||||||
week?: string
|
week?: string
|
||||||
publish: boolean
|
publish: boolean
|
||||||
force: boolean
|
force: boolean
|
||||||
|
backfill: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MCPToolCallResult {
|
interface MCPToolCallResult {
|
||||||
@@ -46,15 +54,104 @@ const OBSERVABILIDADE_DISCUSSION_ID = 32
|
|||||||
const OBSERVABILIDADE_DEPARTMENT_ID = 1
|
const OBSERVABILIDADE_DEPARTMENT_ID = 1
|
||||||
|
|
||||||
function parseArgs(argv: string[]): Args {
|
function parseArgs(argv: string[]): Args {
|
||||||
const a: Args = { publish: false, force: false }
|
const a: Args = { publish: false, force: false, backfill: false }
|
||||||
for (let i = 0; i < argv.length; i++) {
|
for (let i = 0; i < argv.length; i++) {
|
||||||
if (argv[i] === '--week') a.week = argv[++i]
|
if (argv[i] === '--week') a.week = argv[++i]
|
||||||
else if (argv[i] === '--publish') a.publish = true
|
else if (argv[i] === '--publish') a.publish = true
|
||||||
else if (argv[i] === '--force') a.force = true
|
else if (argv[i] === '--force') a.force = true
|
||||||
|
else if (argv[i] === '--backfill') a.backfill = true
|
||||||
}
|
}
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Detecta e persiste padrões para uma única semana (sem publicar). */
|
||||||
|
function processWeek(db: SessionsDb, monday: Date): {
|
||||||
|
week_iso: string
|
||||||
|
detected: number
|
||||||
|
by_severity: { action: number; warning: number; info: number }
|
||||||
|
} {
|
||||||
|
const range = weekRange(monday)
|
||||||
|
const weekIsoStr = range.iso
|
||||||
|
const detected: Pattern[] = detectPatterns(db, range.start, range.end)
|
||||||
|
const records: PatternRecord[] = []
|
||||||
|
for (const p of detected) {
|
||||||
|
const tmpRec = toPatternRecord(p, weekIsoStr, 1)
|
||||||
|
db.upsertPattern(tmpRec)
|
||||||
|
const consecutive = db.getConsecutiveWeeks(p.pattern_key, weekIsoStr)
|
||||||
|
const rec = toPatternRecord(p, weekIsoStr, consecutive)
|
||||||
|
db.upsertPattern(rec)
|
||||||
|
records.push(rec)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
week_iso: weekIsoStr,
|
||||||
|
detected: records.length,
|
||||||
|
by_severity: {
|
||||||
|
action: records.filter((r) => r.severity === 'action').length,
|
||||||
|
warning: records.filter((r) => r.severity === 'warning').length,
|
||||||
|
info: records.filter((r) => r.severity === 'info').length,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Executa backfill desde a primeira sessão até (excl.) semana corrente. */
|
||||||
|
function runBackfill(db: SessionsDb, dbPath: string): void {
|
||||||
|
const raw = db.rawDb()
|
||||||
|
const row = raw.prepare('SELECT MIN(started_at) as min_started FROM sessions').get() as
|
||||||
|
| { min_started: string | null }
|
||||||
|
| undefined
|
||||||
|
if (!row || !row.min_started) {
|
||||||
|
console.error('[patterns] backfill: BD sem sessões. Nada a fazer.')
|
||||||
|
console.log(JSON.stringify({ backfill: true, weeks_processed: 0, total_detected: 0 }))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const firstStart = new Date(row.min_started)
|
||||||
|
const firstRange = weekRange(firstStart)
|
||||||
|
const currentRange = weekRange(new Date())
|
||||||
|
const currentIso = currentRange.iso
|
||||||
|
|
||||||
|
console.error(
|
||||||
|
`[patterns] backfill: primeira sessão ${row.min_started}, semana inicial ${firstRange.iso}, ` +
|
||||||
|
`semana corrente ${currentIso} (excluída) db=${dbPath}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
let cursor = new Date(firstRange.start)
|
||||||
|
let weeksProcessed = 0
|
||||||
|
let totalDetected = 0
|
||||||
|
const bySev = { action: 0, warning: 0, info: 0 }
|
||||||
|
const perWeek: Array<{ week_iso: string; detected: number }> = []
|
||||||
|
|
||||||
|
// Iteração semanal: cursor é sempre segunda 00:00 UTC
|
||||||
|
while (weekIso(cursor) !== currentIso) {
|
||||||
|
const summary = processWeek(db, cursor)
|
||||||
|
console.log(JSON.stringify({ backfill: true, ...summary }))
|
||||||
|
weeksProcessed++
|
||||||
|
totalDetected += summary.detected
|
||||||
|
bySev.action += summary.by_severity.action
|
||||||
|
bySev.warning += summary.by_severity.warning
|
||||||
|
bySev.info += summary.by_severity.info
|
||||||
|
perWeek.push({ week_iso: summary.week_iso, detected: summary.detected })
|
||||||
|
// Avançar 7 dias
|
||||||
|
cursor = new Date(cursor)
|
||||||
|
cursor.setUTCDate(cursor.getUTCDate() + 7)
|
||||||
|
// Safety: evitar loop infinito em caso de bug
|
||||||
|
if (weeksProcessed > 520) {
|
||||||
|
console.error('[patterns] backfill: safety break após 520 semanas')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
JSON.stringify({
|
||||||
|
backfill_summary: true,
|
||||||
|
weeks_processed: weeksProcessed,
|
||||||
|
total_detected: totalDetected,
|
||||||
|
by_severity: bySev,
|
||||||
|
first_week: perWeek[0]?.week_iso ?? null,
|
||||||
|
last_week: perWeek[perWeek.length - 1]?.week_iso ?? null,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/** Converte YYYY-Www em intervalo {start,end} UTC. */
|
/** Converte YYYY-Www em intervalo {start,end} UTC. */
|
||||||
function weekIsoToRange(weekIsoStr: string): { start: Date; end: Date; iso: string } {
|
function weekIsoToRange(weekIsoStr: string): { start: Date; end: Date; iso: string } {
|
||||||
const m = weekIsoStr.match(/^(\d{4})-W(\d{2})$/)
|
const m = weekIsoStr.match(/^(\d{4})-W(\d{2})$/)
|
||||||
@@ -198,6 +295,16 @@ async function main(): Promise<void> {
|
|||||||
const dbPath = process.env.OBSERVABILIDADE_DB ?? DEFAULT_DB_PATH
|
const dbPath = process.env.OBSERVABILIDADE_DB ?? DEFAULT_DB_PATH
|
||||||
const db = openSessionsDb(dbPath)
|
const db = openSessionsDb(dbPath)
|
||||||
|
|
||||||
|
if (args.backfill) {
|
||||||
|
if (args.publish) {
|
||||||
|
console.error('[patterns] --backfill é incompatível com --publish. Aborta.')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
runBackfill(db, dbPath)
|
||||||
|
db.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const range = args.week ? weekIsoToRange(args.week) : weekRange(new Date())
|
const range = args.week ? weekIsoToRange(args.week) : weekRange(new Date())
|
||||||
const weekIso = args.week ?? range.iso
|
const weekIso = args.week ?? range.iso
|
||||||
console.error(`[patterns] semana ${weekIso} range=${range.start.toISOString()}..${range.end.toISOString()} db=${dbPath} publish=${args.publish}`)
|
console.error(`[patterns] semana ${weekIso} range=${range.start.toISOString()}..${range.end.toISOString()} db=${dbPath} publish=${args.publish}`)
|
||||||
|
|||||||
Reference in New Issue
Block a user