Spaces:
Runtime error
Runtime error
File size: 7,638 Bytes
cd8bd0a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 | import { getDbInstance } from "./core";
/**
* Aggregation queries over `call_logs` extracted from route handlers.
*
* Hard Rule #5: routes must not embed raw SQL β these queries live here so the
* /api/provider-metrics, /api/search/stats, and /api/v1/search/analytics routes
* can delegate. Read-only aggregation; no writes.
*
* Sliced out of #3500 (call_logs cluster).
*/
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
export interface ProviderMetricRow {
provider: string;
totalRequests: number;
totalSuccesses: number;
avgLatencyMs: number;
lastRequestAt: string | null;
lastErrorAt: string | null;
lastStatus: number | null;
lastErrorStatus: number | null;
}
export interface SearchProviderStatRow {
provider: string;
requests: number;
avg_latency_ms: number;
}
export interface SearchRecentRow {
request_summary: string | null;
provider: string;
timestamp: string;
}
export interface SearchAggregateStats {
total: number;
today: number;
errors: number;
avg_duration: number | null;
cached: number;
}
export interface SearchProviderCountRow {
provider: string;
cnt: number;
}
// ---------------------------------------------------------------------------
// /api/provider-metrics β aggregate per-provider stats
// ---------------------------------------------------------------------------
/**
* Returns one row per provider with call-level aggregates plus last-status
* subselects. Excludes rows where provider is NULL or '-'.
*/
export function getProviderMetrics(): ProviderMetricRow[] {
const db = getDbInstance();
return db
.prepare(
`SELECT
c.provider,
COUNT(*) as totalRequests,
SUM(CASE WHEN status >= 200 AND status < 400 THEN 1 ELSE 0 END) as totalSuccesses,
ROUND(AVG(duration)) as avgLatencyMs,
MAX(timestamp) as lastRequestAt,
MAX(
CASE
WHEN (status IS NOT NULL AND (status < 200 OR status >= 400))
OR error_summary IS NOT NULL
THEN timestamp
ELSE NULL
END
) as lastErrorAt,
(
SELECT c2.status
FROM call_logs c2
WHERE c2.provider = c.provider
ORDER BY c2.timestamp DESC, c2.id DESC
LIMIT 1
) as lastStatus,
(
SELECT c3.status
FROM call_logs c3
WHERE c3.provider = c.provider
AND (
(c3.status IS NOT NULL AND (c3.status < 200 OR c3.status >= 400))
OR c3.error_summary IS NOT NULL
)
ORDER BY c3.timestamp DESC, c3.id DESC
LIMIT 1
) as lastErrorStatus
FROM call_logs c
WHERE c.provider IS NOT NULL AND c.provider != '-'
GROUP BY c.provider`
)
.all() as ProviderMetricRow[];
}
// ---------------------------------------------------------------------------
// /api/search/stats β search provider aggregates + recent entries
// ---------------------------------------------------------------------------
/**
* Per-provider request count and average latency for search requests.
*/
export function getSearchProviderStats(): SearchProviderStatRow[] {
const db = getDbInstance();
return db
.prepare(
`
SELECT provider, COUNT(*) as requests,
CAST(AVG(duration) AS INTEGER) as avg_latency_ms
FROM call_logs
WHERE request_type = 'search'
GROUP BY provider
`
)
.all() as SearchProviderStatRow[];
}
/**
* Most recent 10 search entries (request_summary + provider + timestamp).
*/
export function getRecentSearchLogs(): SearchRecentRow[] {
const db = getDbInstance();
return db
.prepare(
`
SELECT request_summary, provider, timestamp
FROM call_logs
WHERE request_type = 'search'
ORDER BY timestamp DESC
LIMIT 10
`
)
.all() as SearchRecentRow[];
}
// ---------------------------------------------------------------------------
// /api/v1/search/analytics β aggregated search analytics
// ---------------------------------------------------------------------------
/**
* Single-pass scalar aggregations for all search entries since `todayIso`.
* `todayIso` is the ISO-8601 UTC start-of-day string used for the "today" count.
*/
export function getSearchAggregateStats(todayIso: string): SearchAggregateStats {
const db = getDbInstance();
const row = db
.prepare(
`SELECT
COUNT(*) as total,
COALESCE(SUM(CASE WHEN timestamp >= ? THEN 1 ELSE 0 END), 0) as today,
COALESCE(SUM(CASE WHEN status >= 400 OR error_summary IS NOT NULL THEN 1 ELSE 0 END), 0) as errors,
AVG(CASE WHEN duration > 0 THEN duration END) as avg_duration,
COALESCE(SUM(CASE WHEN duration > 0 AND duration < 5 THEN 1 ELSE 0 END), 0) as cached
FROM call_logs
WHERE request_type = 'search'`
)
.get(todayIso) as SearchAggregateStats | undefined;
return row ?? { total: 0, today: 0, errors: 0, avg_duration: null, cached: 0 };
}
/**
* Per-provider request count for search entries, ordered by count descending.
*/
export function getSearchProviderCounts(): SearchProviderCountRow[] {
const db = getDbInstance();
return db
.prepare(
`SELECT provider, COUNT(*) as cnt
FROM call_logs WHERE request_type = 'search'
GROUP BY provider ORDER BY cnt DESC`
)
.all() as SearchProviderCountRow[];
}
// ---------------------------------------------------------------------------
// /api/usage/analytics β fallback-rate aggregates over call_logs
// ---------------------------------------------------------------------------
export interface FallbackStatsRow {
total: number;
with_requested: number;
fallback_eligible: number;
fallbacks: number;
}
/**
* Scalar fallback-rate stats over `call_logs` for the usage analytics endpoint.
*
* @param whereClause - SQL WHERE clause (may be empty string) using the same
* named params as the usage_history queries.
* @param params - Named params object (string values).
*/
export function getFallbackStats(
whereClause: string,
params: Record<string, string>
): FallbackStatsRow {
const db = getDbInstance();
const row = db
.prepare(
`
SELECT
SUM(CASE WHEN (combo_name IS NULL OR combo_name = '') THEN 1 ELSE 0 END) as total,
SUM(CASE WHEN requested_model IS NOT NULL AND requested_model != '' AND (combo_name IS NULL OR combo_name = '') THEN 1 ELSE 0 END) as with_requested,
SUM(CASE
WHEN (combo_name IS NULL OR combo_name = '')
AND requested_model IS NOT NULL
AND requested_model != ''
AND model IS NOT NULL
AND model != ''
THEN 1 ELSE 0 END
) as fallback_eligible,
SUM(CASE
WHEN (combo_name IS NULL OR combo_name = '')
AND requested_model IS NOT NULL
AND requested_model != ''
AND model IS NOT NULL
AND model != ''
AND LOWER(CASE WHEN instr(requested_model, '/') > 0 THEN substr(requested_model, instr(requested_model, '/') + 1) ELSE requested_model END) != LOWER(model)
THEN 1 ELSE 0 END
) as fallbacks
FROM call_logs
${whereClause}
`
)
.get(params) as FallbackStatsRow | undefined;
return row ?? { total: 0, with_requested: 0, fallback_eligible: 0, fallbacks: 0 };
}
|