nilometer 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/LICENSE +21 -0
- package/README.md +462 -2
- package/core/pricing/prices.json +348 -0
- package/core/schema/001_ingestion.sql +57 -0
- package/core/schema/002_line_count.sql +7 -0
- package/core/schema/003_derived.sql +70 -0
- package/core/schema/004_requests_dedup.sql +50 -0
- package/core/schema/005_repositories.sql +25 -0
- package/core/schema/006_status_readings.sql +33 -0
- package/core/schema/007_pricing.sql +95 -0
- package/core/schema/008_interruptions.sql +263 -0
- package/core/schema/009_headroom.sql +84 -0
- package/core/schema/010_attribution.sql +106 -0
- package/core/schema/011_projections.sql +83 -0
- package/core/schema/012_unsplit_cache_writes.sql +49 -0
- package/core/schema/013_repository_revalidation.sql +104 -0
- package/core/schema/014_observed_readings.sql +204 -0
- package/core/schema/015_response_observations.sql +127 -0
- package/core/schema/016_windows_repository_paths.sql +41 -0
- package/core/schema/017_unattributed_usage_once.sql +47 -0
- package/core/schema/018_request_time_index.sql +21 -0
- package/core/schema/019_dedup_earliest_of_equals.sql +61 -0
- package/core/schema/020_limit_quota_fields.sql +131 -0
- package/core/schema/021_plan_periods.sql +121 -0
- package/core/schema/022_attachment_walk.sql +223 -0
- package/core/schema/023_auto_continuations.sql +129 -0
- package/core/schema/024_counted_limit_groups.sql +29 -0
- package/core/schema/025_model_fallback.sql +77 -0
- package/core/schema/026_changed_readings.sql +106 -0
- package/core/schema/027_weekly_crossings.sql +32 -0
- package/core/schema/028_stale_readings.sql +196 -0
- package/core/schema/029_lockout_sessions.sql +68 -0
- package/core/schema/030_repository_aliases.sql +81 -0
- package/core/schema/031_cte_cursor_budget.sql +153 -0
- package/dist/cli/main.cli.js +39 -0
- package/dist/cli/program.js +801 -0
- package/dist/core/db/database.js +182 -0
- package/dist/core/db/fingerprint.js +71 -0
- package/dist/core/ingest/attribution.js +131 -0
- package/dist/core/ingest/classify.js +382 -0
- package/dist/core/ingest/command.js +95 -0
- package/dist/core/ingest/derive.js +212 -0
- package/dist/core/ingest/discover.js +126 -0
- package/dist/core/ingest/ingest.js +160 -0
- package/dist/core/ingest/reader.js +89 -0
- package/dist/core/ingest/reset-time.js +97 -0
- package/dist/core/ingest/results.js +201 -0
- package/dist/core/ingest/spool.js +165 -0
- package/dist/core/ingest/store.js +99 -0
- package/dist/core/install/install.js +185 -0
- package/dist/core/install/locations.js +89 -0
- package/dist/core/install/private-files.js +259 -0
- package/dist/core/install/record.js +103 -0
- package/dist/core/plans/plan-prices.js +128 -0
- package/dist/core/plans/repository-aliases.js +136 -0
- package/dist/core/pricing/prices.js +147 -0
- package/dist/core/settings/settings-file.js +215 -0
- package/dist/core/settings/statusline.js +240 -0
- package/dist/core/util/commands.js +91 -0
- package/dist/core/util/node-version.js +58 -0
- package/dist/core/util/open-page.js +69 -0
- package/dist/core/util/paths.js +36 -0
- package/dist/core/verify/compare.js +77 -0
- package/dist/core/verify/verify.js +220 -0
- package/dist/viewer/explain.js +574 -0
- package/dist/viewer/format.js +150 -0
- package/dist/viewer/html-charts.js +1292 -0
- package/dist/viewer/html-contract.js +2 -0
- package/dist/viewer/html-data.js +718 -0
- package/dist/viewer/html.js +1654 -0
- package/dist/viewer/queries.js +128 -0
- package/dist/viewer/render.js +602 -0
- package/dist/viewer/report.js +120 -0
- package/dist/viewer/save.js +110 -0
- package/docs/requirements.md +113 -0
- package/hooks/statusline.sh +104 -0
- package/package.json +61 -4
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
-- 012_unsplit_cache_writes: unsplit cache writes sum to 0, not NULL, when every request has the split.
|
|
2
|
+
--
|
|
3
|
+
-- In 010, SUM over cache_write_unsplit_tokens returned NULL for a model or repository whose requests
|
|
4
|
+
-- all recorded the 5m/1h split, and the viewer showed "unknown" for tokens that were known to be 0.
|
|
5
|
+
-- A NULL in the 5m and 1h columns still means unknown: those tokens exist, but their duration wasn't
|
|
6
|
+
-- recorded. Views are recreated rather than editing 010, which existing databases have applied.
|
|
7
|
+
|
|
8
|
+
DROP VIEW obs_usage_by_model;
|
|
9
|
+
DROP VIEW obs_usage_by_repo;
|
|
10
|
+
|
|
11
|
+
-- Observed: tokens per model per token type, with request counts and unknowns.
|
|
12
|
+
CREATE VIEW obs_usage_by_model AS
|
|
13
|
+
SELECT
|
|
14
|
+
model,
|
|
15
|
+
COUNT(*) AS requests,
|
|
16
|
+
SUM(input_tokens) AS input_tokens,
|
|
17
|
+
SUM(output_tokens) AS output_tokens,
|
|
18
|
+
SUM(cache_read_tokens) AS cache_read_tokens,
|
|
19
|
+
SUM(cache_write_5m_tokens) AS cache_write_5m_tokens,
|
|
20
|
+
SUM(cache_write_1h_tokens) AS cache_write_1h_tokens,
|
|
21
|
+
-- NULL here means every request recorded the 5m/1h split, so there were no unsplit writes: 0, not unknown.
|
|
22
|
+
COALESCE(SUM(cache_write_unsplit_tokens), 0) AS cache_write_unsplit_tokens,
|
|
23
|
+
COUNT(*) FILTER (WHERE dedup_key IS NULL) AS unkeyed_requests,
|
|
24
|
+
COUNT(*) FILTER (WHERE timestamp_utc IS NULL) AS requests_without_timestamp,
|
|
25
|
+
MIN(timestamp_utc) AS first_request_at_utc,
|
|
26
|
+
MAX(timestamp_utc) AS last_request_at_utc,
|
|
27
|
+
(SELECT covers_from FROM source_coverage WHERE source = 'session_logs') AS covers_from,
|
|
28
|
+
(SELECT covers_to FROM source_coverage WHERE source = 'session_logs') AS covers_to
|
|
29
|
+
FROM obs_usage_events
|
|
30
|
+
GROUP BY model;
|
|
31
|
+
|
|
32
|
+
-- Observed: tokens per repository (git root, or the working directory outside git; NULL when no cwd) per token type.
|
|
33
|
+
CREATE VIEW obs_usage_by_repo AS
|
|
34
|
+
SELECT
|
|
35
|
+
repository,
|
|
36
|
+
repo_kind,
|
|
37
|
+
COUNT(*) AS requests,
|
|
38
|
+
SUM(input_tokens) AS input_tokens,
|
|
39
|
+
SUM(output_tokens) AS output_tokens,
|
|
40
|
+
SUM(cache_read_tokens) AS cache_read_tokens,
|
|
41
|
+
SUM(cache_write_5m_tokens) AS cache_write_5m_tokens,
|
|
42
|
+
SUM(cache_write_1h_tokens) AS cache_write_1h_tokens,
|
|
43
|
+
-- NULL here means every request recorded the 5m/1h split, so there were no unsplit writes: 0, not unknown.
|
|
44
|
+
COALESCE(SUM(cache_write_unsplit_tokens), 0) AS cache_write_unsplit_tokens,
|
|
45
|
+
COUNT(*) FILTER (WHERE timestamp_utc IS NULL) AS requests_without_timestamp,
|
|
46
|
+
(SELECT covers_from FROM source_coverage WHERE source = 'session_logs') AS covers_from,
|
|
47
|
+
(SELECT covers_to FROM source_coverage WHERE source = 'session_logs') AS covers_to
|
|
48
|
+
FROM obs_usage_events
|
|
49
|
+
GROUP BY repository, repo_kind;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
-- 013_repository_revalidation: repositories remembers how each working directory resolved and whether its root exists (D-028).
|
|
2
|
+
--
|
|
3
|
+
-- 005 cached one resolution per working directory forever, so a renamed or deleted folder kept its
|
|
4
|
+
-- "git repository" label, and a subfolder deleted before its first ingest couldn't be placed in its
|
|
5
|
+
-- repository. The table is rebuilt because SQLite can't change CHECK constraints in place, and the
|
|
6
|
+
-- views that read it are recreated around the rebuild. Only 'repo' and 'not_git' rows are carried
|
|
7
|
+
-- over: 'missing' rows are dropped so the next ingest resolves them again with the parent-folder
|
|
8
|
+
-- lookup. Every carried row is re-checked against the filesystem on that ingest too.
|
|
9
|
+
|
|
10
|
+
DROP VIEW obs_usage_by_model;
|
|
11
|
+
DROP VIEW obs_usage_by_repo;
|
|
12
|
+
DROP VIEW obs_usage_events;
|
|
13
|
+
DROP VIEW request_repositories;
|
|
14
|
+
|
|
15
|
+
CREATE TABLE repositories_013 (
|
|
16
|
+
cwd TEXT PRIMARY KEY, -- source: cwd from log lines
|
|
17
|
+
kind TEXT NOT NULL, -- derived: the working directory at the last check: 'repo', 'not_git', or 'missing'
|
|
18
|
+
repo_root TEXT, -- derived: git's root for 'repo'; for 'missing', the last-known or nearest existing parent's root; else NULL
|
|
19
|
+
root_exists INTEGER, -- derived: 1 if repo_root existed at the last check, 0 if not; NULL without repo_root
|
|
20
|
+
resolved_via TEXT NOT NULL, -- derived: 'git' (run in the directory), 'last_known', 'parent', or 'none'
|
|
21
|
+
resolved_at TEXT NOT NULL, -- derived: ISO-8601 UTC time of the last resolution
|
|
22
|
+
CHECK (kind IN ('repo', 'not_git', 'missing')),
|
|
23
|
+
CHECK (resolved_via IN ('git', 'last_known', 'parent', 'none')),
|
|
24
|
+
CHECK (root_exists IN (0, 1)),
|
|
25
|
+
CHECK ((repo_root IS NULL) = (root_exists IS NULL)),
|
|
26
|
+
CHECK (kind <> 'repo' OR (repo_root IS NOT NULL AND resolved_via = 'git')),
|
|
27
|
+
CHECK (kind <> 'not_git' OR (repo_root IS NULL AND resolved_via = 'git')),
|
|
28
|
+
CHECK (kind <> 'missing' OR (resolved_via = 'none') = (repo_root IS NULL)),
|
|
29
|
+
CHECK (kind = 'missing' OR resolved_via = 'git')
|
|
30
|
+
) STRICT;
|
|
31
|
+
|
|
32
|
+
INSERT INTO repositories_013 (cwd, kind, repo_root, root_exists, resolved_via, resolved_at)
|
|
33
|
+
SELECT cwd, kind, repo_root, CASE WHEN repo_root IS NULL THEN NULL ELSE 1 END, 'git', resolved_at
|
|
34
|
+
FROM repositories
|
|
35
|
+
WHERE kind IN ('repo', 'not_git');
|
|
36
|
+
|
|
37
|
+
DROP TABLE repositories;
|
|
38
|
+
ALTER TABLE repositories_013 RENAME TO repositories;
|
|
39
|
+
|
|
40
|
+
-- Observed: each request's repository and how it was attributed (D-010, D-028); a missing folder whose repository still exists counts as that repository.
|
|
41
|
+
CREATE VIEW request_repositories AS
|
|
42
|
+
SELECT
|
|
43
|
+
d.raw_line_id,
|
|
44
|
+
d.cwd,
|
|
45
|
+
CASE
|
|
46
|
+
WHEN r.cwd IS NULL THEN 'unresolved'
|
|
47
|
+
WHEN r.repo_root IS NOT NULL AND r.root_exists = 1 THEN 'repo'
|
|
48
|
+
WHEN r.repo_root IS NOT NULL THEN 'missing'
|
|
49
|
+
ELSE r.kind
|
|
50
|
+
END AS repo_kind,
|
|
51
|
+
COALESCE(r.repo_root, d.cwd) AS repository,
|
|
52
|
+
r.resolved_via
|
|
53
|
+
FROM requests_dedup d
|
|
54
|
+
LEFT JOIN repositories r ON r.cwd = d.cwd;
|
|
55
|
+
|
|
56
|
+
-- Observed: every deduplicated request with the model and repository it's attributed to.
|
|
57
|
+
CREATE VIEW obs_usage_events AS
|
|
58
|
+
SELECT
|
|
59
|
+
d.raw_line_id, d.dedup_key, d.session_id, d.timestamp_utc, d.model,
|
|
60
|
+
r.repository, r.repo_kind,
|
|
61
|
+
d.input_tokens, d.output_tokens, d.cache_read_tokens,
|
|
62
|
+
d.cache_write_5m_tokens, d.cache_write_1h_tokens, d.cache_write_unsplit_tokens
|
|
63
|
+
FROM requests_dedup d
|
|
64
|
+
JOIN request_repositories r ON r.raw_line_id = d.raw_line_id;
|
|
65
|
+
|
|
66
|
+
-- Observed: tokens per model per token type, with request counts and unknowns.
|
|
67
|
+
CREATE VIEW obs_usage_by_model AS
|
|
68
|
+
SELECT
|
|
69
|
+
model,
|
|
70
|
+
COUNT(*) AS requests,
|
|
71
|
+
SUM(input_tokens) AS input_tokens,
|
|
72
|
+
SUM(output_tokens) AS output_tokens,
|
|
73
|
+
SUM(cache_read_tokens) AS cache_read_tokens,
|
|
74
|
+
SUM(cache_write_5m_tokens) AS cache_write_5m_tokens,
|
|
75
|
+
SUM(cache_write_1h_tokens) AS cache_write_1h_tokens,
|
|
76
|
+
-- NULL here means every request recorded the 5m/1h split, so there were no unsplit writes: 0, not unknown.
|
|
77
|
+
COALESCE(SUM(cache_write_unsplit_tokens), 0) AS cache_write_unsplit_tokens,
|
|
78
|
+
COUNT(*) FILTER (WHERE dedup_key IS NULL) AS unkeyed_requests,
|
|
79
|
+
COUNT(*) FILTER (WHERE timestamp_utc IS NULL) AS requests_without_timestamp,
|
|
80
|
+
MIN(timestamp_utc) AS first_request_at_utc,
|
|
81
|
+
MAX(timestamp_utc) AS last_request_at_utc,
|
|
82
|
+
(SELECT covers_from FROM source_coverage WHERE source = 'session_logs') AS covers_from,
|
|
83
|
+
(SELECT covers_to FROM source_coverage WHERE source = 'session_logs') AS covers_to
|
|
84
|
+
FROM obs_usage_events
|
|
85
|
+
GROUP BY model;
|
|
86
|
+
|
|
87
|
+
-- Observed: tokens per repository (git root, or the working directory outside git; NULL when no cwd) per token type.
|
|
88
|
+
CREATE VIEW obs_usage_by_repo AS
|
|
89
|
+
SELECT
|
|
90
|
+
repository,
|
|
91
|
+
repo_kind,
|
|
92
|
+
COUNT(*) AS requests,
|
|
93
|
+
SUM(input_tokens) AS input_tokens,
|
|
94
|
+
SUM(output_tokens) AS output_tokens,
|
|
95
|
+
SUM(cache_read_tokens) AS cache_read_tokens,
|
|
96
|
+
SUM(cache_write_5m_tokens) AS cache_write_5m_tokens,
|
|
97
|
+
SUM(cache_write_1h_tokens) AS cache_write_1h_tokens,
|
|
98
|
+
-- NULL here means every request recorded the 5m/1h split, so there were no unsplit writes: 0, not unknown.
|
|
99
|
+
COALESCE(SUM(cache_write_unsplit_tokens), 0) AS cache_write_unsplit_tokens,
|
|
100
|
+
COUNT(*) FILTER (WHERE timestamp_utc IS NULL) AS requests_without_timestamp,
|
|
101
|
+
(SELECT covers_from FROM source_coverage WHERE source = 'session_logs') AS covers_from,
|
|
102
|
+
(SELECT covers_to FROM source_coverage WHERE source = 'session_logs') AS covers_to
|
|
103
|
+
FROM obs_usage_events
|
|
104
|
+
GROUP BY repository, repo_kind;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
-- 014_observed_readings: date status line readings by the API response they came from, not by capture (D-044).
|
|
2
|
+
--
|
|
3
|
+
-- A payload's rate_limits are the numbers from the latest API response in that session; re-running
|
|
4
|
+
-- the status line (a refresh timer, a window reset, /compact) repeats them. So a reading is observed
|
|
5
|
+
-- at the latest request line in its session at or before the capture (+1 s for whole-second
|
|
6
|
+
-- captures), within the window it reports, and readings of the same response collapse to one
|
|
7
|
+
-- observation. Every view that ordered, dated, counted, or excluded readings by capture time is
|
|
8
|
+
-- recreated here to use the observed time. Dependent views keep their column names and need no change.
|
|
9
|
+
|
|
10
|
+
DROP VIEW obs_unattributed_usage;
|
|
11
|
+
DROP VIEW obs_unattributed_usage_events;
|
|
12
|
+
DROP VIEW window_reading_pairs;
|
|
13
|
+
DROP VIEW obs_window_peak;
|
|
14
|
+
DROP VIEW obs_window_headroom;
|
|
15
|
+
DROP VIEW window_instances;
|
|
16
|
+
DROP VIEW status_limit_groups;
|
|
17
|
+
DROP VIEW source_coverage;
|
|
18
|
+
DROP VIEW window_readings;
|
|
19
|
+
|
|
20
|
+
-- Observed: one row per observation of a plan window: the latest capture of each API response's numbers, with when they were observed (D-024, D-044).
|
|
21
|
+
CREATE VIEW window_readings AS
|
|
22
|
+
WITH readings AS (
|
|
23
|
+
SELECT
|
|
24
|
+
w.raw_line_id, w.window, w.resets_at, w.used_percentage, s.session_id, s.captured_at_s,
|
|
25
|
+
-- Only a request inside this window can have produced numbers for it; +1 s covers whole-second captures.
|
|
26
|
+
(SELECT MAX(p.timestamp_utc) FROM parsed_lines p
|
|
27
|
+
WHERE p.session_id = s.session_id AND p.class = 'request'
|
|
28
|
+
AND p.timestamp_utc > strftime('%Y-%m-%dT%H:%M:%fZ',
|
|
29
|
+
w.resets_at - CASE w.window WHEN 'five_hour' THEN 5 * 3600 ELSE 7 * 86400 END, 'unixepoch')
|
|
30
|
+
AND p.timestamp_utc < strftime('%Y-%m-%dT%H:%M:%fZ', s.captured_at_s + 1, 'unixepoch')) AS last_request_utc
|
|
31
|
+
FROM rate_limit_windows w
|
|
32
|
+
JOIN status_readings s ON s.raw_line_id = w.raw_line_id
|
|
33
|
+
WHERE s.status = 'ok' AND w.validity = 'valid' AND w.window IN ('five_hour', 'seven_day')
|
|
34
|
+
),
|
|
35
|
+
observed AS (
|
|
36
|
+
SELECT
|
|
37
|
+
r.*,
|
|
38
|
+
COALESCE(r.last_request_utc, strftime('%Y-%m-%dT%H:%M:%fZ', r.captured_at_s, 'unixepoch')) AS observed_at_utc,
|
|
39
|
+
ROW_NUMBER() OVER (
|
|
40
|
+
-- A capture-time fallback is its own observation: NULL request times must not merge.
|
|
41
|
+
PARTITION BY r.window, r.resets_at, r.session_id, r.last_request_utc,
|
|
42
|
+
CASE WHEN r.last_request_utc IS NULL THEN r.raw_line_id END
|
|
43
|
+
ORDER BY r.captured_at_s DESC, r.raw_line_id DESC
|
|
44
|
+
) AS copy_rank
|
|
45
|
+
FROM readings r
|
|
46
|
+
)
|
|
47
|
+
SELECT
|
|
48
|
+
raw_line_id,
|
|
49
|
+
window,
|
|
50
|
+
resets_at,
|
|
51
|
+
strftime('%Y-%m-%dT%H:%M:%fZ', resets_at, 'unixepoch') AS reset_at_utc,
|
|
52
|
+
used_percentage,
|
|
53
|
+
session_id,
|
|
54
|
+
captured_at_s,
|
|
55
|
+
strftime('%Y-%m-%dT%H:%M:%fZ', captured_at_s, 'unixepoch') AS captured_at_utc,
|
|
56
|
+
observed_at_utc,
|
|
57
|
+
unixepoch(observed_at_utc, 'subsec') AS observed_at_s,
|
|
58
|
+
CASE WHEN last_request_utc IS NULL THEN 'capture' ELSE 'last_request' END AS observed_via,
|
|
59
|
+
unixepoch(observed_at_utc, 'subsec') > resets_at AS after_reset
|
|
60
|
+
FROM observed
|
|
61
|
+
WHERE copy_rank = 1;
|
|
62
|
+
|
|
63
|
+
-- Observed: the time span each source's data covers, from the data present (README principle 5); status line spans observations (D-044).
|
|
64
|
+
CREATE VIEW source_coverage AS
|
|
65
|
+
SELECT 'session_logs' AS source, MIN(timestamp_utc) AS covers_from, MAX(timestamp_utc) AS covers_to
|
|
66
|
+
FROM parsed_lines
|
|
67
|
+
WHERE timestamp_utc IS NOT NULL
|
|
68
|
+
UNION ALL
|
|
69
|
+
SELECT 'status_line', MIN(observed_at_utc), MAX(observed_at_utc)
|
|
70
|
+
FROM window_readings;
|
|
71
|
+
|
|
72
|
+
-- Observed: status line windows observed at 100%, one row per (window, resets_at), from init onward (D-023, D-044).
|
|
73
|
+
CREATE VIEW status_limit_groups AS
|
|
74
|
+
WITH at_limit AS (
|
|
75
|
+
SELECT
|
|
76
|
+
r.raw_line_id, r.window, r.resets_at, r.session_id, r.observed_at_utc,
|
|
77
|
+
ROW_NUMBER() OVER (PARTITION BY r.window, r.resets_at ORDER BY r.observed_at_s, r.raw_line_id) AS reading_rank,
|
|
78
|
+
COUNT(*) OVER (PARTITION BY r.window, r.resets_at) AS readings_at_limit
|
|
79
|
+
FROM window_readings r
|
|
80
|
+
WHERE r.used_percentage >= 100
|
|
81
|
+
)
|
|
82
|
+
SELECT
|
|
83
|
+
raw_line_id, window, resets_at, session_id, readings_at_limit,
|
|
84
|
+
observed_at_utc AS first_at_utc,
|
|
85
|
+
strftime('%Y-%m-%dT%H:%M:%fZ', resets_at, 'unixepoch') AS reset_at_utc,
|
|
86
|
+
CASE window WHEN 'five_hour' THEN 5 * 3600 ELSE 7 * 86400 END AS window_seconds
|
|
87
|
+
FROM at_limit
|
|
88
|
+
WHERE reading_rank = 1;
|
|
89
|
+
|
|
90
|
+
-- Observed: per window instance, observation counts, whether it is still open, and the status line coverage.
|
|
91
|
+
CREATE VIEW window_instances AS
|
|
92
|
+
SELECT
|
|
93
|
+
window,
|
|
94
|
+
resets_at,
|
|
95
|
+
reset_at_utc,
|
|
96
|
+
COUNT(*) FILTER (WHERE after_reset = 0) AS readings,
|
|
97
|
+
COUNT(*) FILTER (WHERE after_reset = 1) AS readings_after_reset,
|
|
98
|
+
MIN(observed_at_utc) FILTER (WHERE after_reset = 0) AS first_reading_at_utc,
|
|
99
|
+
-- Open: no status line reading at all was captured at or after this reset, so later usage may still come.
|
|
100
|
+
resets_at > (SELECT MAX(captured_at_s) FROM status_readings WHERE status = 'ok') AS window_open,
|
|
101
|
+
(SELECT covers_from FROM source_coverage WHERE source = 'status_line') AS covers_from,
|
|
102
|
+
(SELECT covers_to FROM source_coverage WHERE source = 'status_line') AS covers_to
|
|
103
|
+
FROM window_readings
|
|
104
|
+
GROUP BY window, resets_at;
|
|
105
|
+
|
|
106
|
+
-- Observed: last observed usage before each window's reset, with when it was observed (a lower bound on final usage; D-024, D-044).
|
|
107
|
+
CREATE VIEW obs_window_headroom AS
|
|
108
|
+
WITH ranked AS (
|
|
109
|
+
SELECT
|
|
110
|
+
r.*,
|
|
111
|
+
ROW_NUMBER() OVER (PARTITION BY r.window, r.resets_at ORDER BY r.observed_at_s DESC, r.raw_line_id DESC) AS latest_rank
|
|
112
|
+
FROM window_readings r
|
|
113
|
+
WHERE r.after_reset = 0
|
|
114
|
+
)
|
|
115
|
+
SELECT
|
|
116
|
+
i.window,
|
|
117
|
+
i.reset_at_utc,
|
|
118
|
+
r.used_percentage AS last_used_percentage,
|
|
119
|
+
r.observed_at_utc AS last_reading_at_utc,
|
|
120
|
+
r.raw_line_id AS last_reading_raw_line_id,
|
|
121
|
+
i.readings,
|
|
122
|
+
i.readings_after_reset,
|
|
123
|
+
i.first_reading_at_utc,
|
|
124
|
+
i.window_open,
|
|
125
|
+
i.covers_from,
|
|
126
|
+
i.covers_to
|
|
127
|
+
FROM window_instances i
|
|
128
|
+
LEFT JOIN ranked r ON r.window = i.window AND r.resets_at = i.resets_at AND r.latest_rank = 1;
|
|
129
|
+
|
|
130
|
+
-- Observed: highest observed usage in each window, with the observation it came from (D-044).
|
|
131
|
+
CREATE VIEW obs_window_peak AS
|
|
132
|
+
WITH ranked AS (
|
|
133
|
+
SELECT
|
|
134
|
+
r.*,
|
|
135
|
+
ROW_NUMBER() OVER (PARTITION BY r.window, r.resets_at ORDER BY r.used_percentage DESC, r.observed_at_s, r.raw_line_id) AS peak_rank
|
|
136
|
+
FROM window_readings r
|
|
137
|
+
WHERE r.after_reset = 0
|
|
138
|
+
)
|
|
139
|
+
SELECT
|
|
140
|
+
i.window,
|
|
141
|
+
i.reset_at_utc,
|
|
142
|
+
r.used_percentage AS peak_used_percentage,
|
|
143
|
+
r.observed_at_utc AS peak_reading_at_utc,
|
|
144
|
+
r.raw_line_id AS peak_reading_raw_line_id,
|
|
145
|
+
i.readings,
|
|
146
|
+
i.readings_after_reset,
|
|
147
|
+
i.window_open,
|
|
148
|
+
i.covers_from,
|
|
149
|
+
i.covers_to
|
|
150
|
+
FROM window_instances i
|
|
151
|
+
LEFT JOIN ranked r ON r.window = i.window AND r.resets_at = i.resets_at AND r.peak_rank = 1;
|
|
152
|
+
|
|
153
|
+
-- Observed: consecutive observations of one window, the change between them, and Claude Code requests in between (D-025, D-044).
|
|
154
|
+
CREATE VIEW window_reading_pairs AS
|
|
155
|
+
WITH ordered AS (
|
|
156
|
+
SELECT
|
|
157
|
+
r.*,
|
|
158
|
+
LAG(r.raw_line_id) OVER w AS previous_raw_line_id,
|
|
159
|
+
LAG(r.used_percentage) OVER w AS previous_used_percentage,
|
|
160
|
+
LAG(r.observed_at_utc) OVER w AS previous_observed_at_utc
|
|
161
|
+
FROM window_readings r
|
|
162
|
+
WHERE r.after_reset = 0
|
|
163
|
+
WINDOW w AS (PARTITION BY r.window, r.resets_at ORDER BY r.observed_at_s, r.raw_line_id)
|
|
164
|
+
)
|
|
165
|
+
SELECT
|
|
166
|
+
o.window,
|
|
167
|
+
o.reset_at_utc,
|
|
168
|
+
o.previous_raw_line_id,
|
|
169
|
+
o.raw_line_id,
|
|
170
|
+
o.previous_observed_at_utc,
|
|
171
|
+
o.observed_at_utc,
|
|
172
|
+
o.used_percentage - o.previous_used_percentage AS change_percentage_points,
|
|
173
|
+
-- Include the previous observation's instant, and one second past this one (whole-second captures).
|
|
174
|
+
(SELECT COUNT(*) FROM requests_dedup d
|
|
175
|
+
WHERE d.timestamp_utc >= o.previous_observed_at_utc
|
|
176
|
+
AND d.timestamp_utc < strftime('%Y-%m-%dT%H:%M:%fZ', o.observed_at_s + 1, 'unixepoch')) AS requests_between
|
|
177
|
+
FROM ordered o
|
|
178
|
+
WHERE o.previous_raw_line_id IS NOT NULL;
|
|
179
|
+
|
|
180
|
+
-- Observed: observation pairs whose usage rose with no Claude Code request in between (D-025; see D-044 on why real data rarely has any).
|
|
181
|
+
CREATE VIEW obs_unattributed_usage_events AS
|
|
182
|
+
SELECT * FROM window_reading_pairs
|
|
183
|
+
WHERE requests_between = 0 AND change_percentage_points > 0;
|
|
184
|
+
|
|
185
|
+
-- Observed: per window, percentage points consumed with no Claude Code request in between (a lower bound), beside pair counts.
|
|
186
|
+
CREATE VIEW obs_unattributed_usage AS
|
|
187
|
+
SELECT
|
|
188
|
+
i.window,
|
|
189
|
+
i.reset_at_utc,
|
|
190
|
+
i.readings,
|
|
191
|
+
(SELECT COUNT(*) FROM window_reading_pairs p WHERE p.window = i.window AND p.reset_at_utc = i.reset_at_utc) AS pairs,
|
|
192
|
+
(SELECT COUNT(*) FROM window_reading_pairs p
|
|
193
|
+
WHERE p.window = i.window AND p.reset_at_utc = i.reset_at_utc AND p.requests_between = 0) AS pairs_without_requests,
|
|
194
|
+
(SELECT COUNT(*) FROM window_reading_pairs p
|
|
195
|
+
WHERE p.window = i.window AND p.reset_at_utc = i.reset_at_utc AND p.change_percentage_points < 0) AS decreasing_pairs,
|
|
196
|
+
-- Fewer than two observations means no pair was observed: unknown, not 0.
|
|
197
|
+
CASE WHEN i.readings >= 2 THEN
|
|
198
|
+
(SELECT TOTAL(e.change_percentage_points) FROM obs_unattributed_usage_events e
|
|
199
|
+
WHERE e.window = i.window AND e.reset_at_utc = i.reset_at_utc)
|
|
200
|
+
END AS unattributed_percentage_points,
|
|
201
|
+
i.window_open,
|
|
202
|
+
i.covers_from,
|
|
203
|
+
i.covers_to
|
|
204
|
+
FROM window_instances i;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
-- 015_response_observations: one status line observation per API response, dated by its first log line (D-045).
|
|
2
|
+
--
|
|
3
|
+
-- 014 keyed observations by the latest request line, but one response is several streaming snapshot
|
|
4
|
+
-- lines with their own timestamps (D-001), so readings taken mid-stream became separate observations
|
|
5
|
+
-- of one response. Here the key is the response (its D-001 dedup key, or the line itself when
|
|
6
|
+
-- unkeyed), dated by the response's first line, and reading pairs count distinct responses with any
|
|
7
|
+
-- line in the interval. Dependent views keep their column names.
|
|
8
|
+
|
|
9
|
+
DROP VIEW obs_unattributed_usage;
|
|
10
|
+
DROP VIEW obs_unattributed_usage_events;
|
|
11
|
+
DROP VIEW window_reading_pairs;
|
|
12
|
+
DROP VIEW window_readings;
|
|
13
|
+
|
|
14
|
+
-- Observed: one row per observation of a plan window: the latest capture of each API response's numbers, dated by that response's first line (D-044, D-045).
|
|
15
|
+
CREATE VIEW window_readings AS
|
|
16
|
+
WITH readings AS (
|
|
17
|
+
SELECT
|
|
18
|
+
w.raw_line_id, w.window, w.resets_at, w.used_percentage, s.session_id, s.captured_at_s,
|
|
19
|
+
-- The response behind the latest request line in this session, inside this window, at or before
|
|
20
|
+
-- the capture (+1 s for whole-second captures). An unkeyed line is its own response.
|
|
21
|
+
(SELECT COALESCE(rq.dedup_key, 'line:' || rq.raw_line_id)
|
|
22
|
+
FROM parsed_lines p
|
|
23
|
+
JOIN requests rq ON rq.raw_line_id = p.raw_line_id
|
|
24
|
+
WHERE p.session_id = s.session_id AND p.class = 'request'
|
|
25
|
+
AND p.timestamp_utc > strftime('%Y-%m-%dT%H:%M:%fZ',
|
|
26
|
+
w.resets_at - CASE w.window WHEN 'five_hour' THEN 5 * 3600 ELSE 7 * 86400 END, 'unixepoch')
|
|
27
|
+
AND p.timestamp_utc < strftime('%Y-%m-%dT%H:%M:%fZ', s.captured_at_s + 1, 'unixepoch')
|
|
28
|
+
ORDER BY p.timestamp_utc DESC, p.raw_line_id DESC
|
|
29
|
+
LIMIT 1) AS response_key
|
|
30
|
+
FROM rate_limit_windows w
|
|
31
|
+
JOIN status_readings s ON s.raw_line_id = w.raw_line_id
|
|
32
|
+
WHERE s.status = 'ok' AND w.validity = 'valid' AND w.window IN ('five_hour', 'seven_day')
|
|
33
|
+
),
|
|
34
|
+
dated AS (
|
|
35
|
+
SELECT
|
|
36
|
+
r.*,
|
|
37
|
+
-- The response's first line: when its numbers first existed.
|
|
38
|
+
(SELECT MIN(p.timestamp_utc)
|
|
39
|
+
FROM requests rq
|
|
40
|
+
JOIN parsed_lines p ON p.raw_line_id = rq.raw_line_id
|
|
41
|
+
WHERE (r.response_key LIKE 'line:%' AND rq.raw_line_id = CAST(substr(r.response_key, 6) AS INTEGER))
|
|
42
|
+
OR rq.dedup_key = r.response_key) AS response_first_utc
|
|
43
|
+
FROM readings r
|
|
44
|
+
),
|
|
45
|
+
observed AS (
|
|
46
|
+
SELECT
|
|
47
|
+
d.*,
|
|
48
|
+
COALESCE(d.response_first_utc, strftime('%Y-%m-%dT%H:%M:%fZ', d.captured_at_s, 'unixepoch')) AS observed_at_utc,
|
|
49
|
+
ROW_NUMBER() OVER (
|
|
50
|
+
-- A capture-time fallback is its own observation: NULL response keys must not merge.
|
|
51
|
+
PARTITION BY d.window, d.resets_at, d.session_id, d.response_key,
|
|
52
|
+
CASE WHEN d.response_key IS NULL THEN d.raw_line_id END
|
|
53
|
+
ORDER BY d.captured_at_s DESC, d.raw_line_id DESC
|
|
54
|
+
) AS copy_rank
|
|
55
|
+
FROM dated d
|
|
56
|
+
)
|
|
57
|
+
SELECT
|
|
58
|
+
raw_line_id,
|
|
59
|
+
window,
|
|
60
|
+
resets_at,
|
|
61
|
+
strftime('%Y-%m-%dT%H:%M:%fZ', resets_at, 'unixepoch') AS reset_at_utc,
|
|
62
|
+
used_percentage,
|
|
63
|
+
session_id,
|
|
64
|
+
captured_at_s,
|
|
65
|
+
strftime('%Y-%m-%dT%H:%M:%fZ', captured_at_s, 'unixepoch') AS captured_at_utc,
|
|
66
|
+
observed_at_utc,
|
|
67
|
+
unixepoch(observed_at_utc, 'subsec') AS observed_at_s,
|
|
68
|
+
CASE WHEN response_key IS NULL THEN 'capture' ELSE 'last_request' END AS observed_via,
|
|
69
|
+
unixepoch(observed_at_utc, 'subsec') > resets_at AS after_reset
|
|
70
|
+
FROM observed
|
|
71
|
+
WHERE copy_rank = 1;
|
|
72
|
+
|
|
73
|
+
-- Observed: consecutive observations of one window, the change between them, and Claude Code responses in between (D-025, D-045).
|
|
74
|
+
CREATE VIEW window_reading_pairs AS
|
|
75
|
+
WITH ordered AS (
|
|
76
|
+
SELECT
|
|
77
|
+
r.*,
|
|
78
|
+
LAG(r.raw_line_id) OVER w AS previous_raw_line_id,
|
|
79
|
+
LAG(r.used_percentage) OVER w AS previous_used_percentage,
|
|
80
|
+
LAG(r.observed_at_utc) OVER w AS previous_observed_at_utc
|
|
81
|
+
FROM window_readings r
|
|
82
|
+
WHERE r.after_reset = 0
|
|
83
|
+
WINDOW w AS (PARTITION BY r.window, r.resets_at ORDER BY r.observed_at_s, r.raw_line_id)
|
|
84
|
+
)
|
|
85
|
+
SELECT
|
|
86
|
+
o.window,
|
|
87
|
+
o.reset_at_utc,
|
|
88
|
+
o.previous_raw_line_id,
|
|
89
|
+
o.raw_line_id,
|
|
90
|
+
o.previous_observed_at_utc,
|
|
91
|
+
o.observed_at_utc,
|
|
92
|
+
o.used_percentage - o.previous_used_percentage AS change_percentage_points,
|
|
93
|
+
-- Distinct responses with any line from the previous observation up to one second past this one.
|
|
94
|
+
(SELECT COUNT(DISTINCT COALESCE(rq.dedup_key, 'line:' || rq.raw_line_id))
|
|
95
|
+
FROM parsed_lines p
|
|
96
|
+
JOIN requests rq ON rq.raw_line_id = p.raw_line_id
|
|
97
|
+
WHERE p.class = 'request'
|
|
98
|
+
AND p.timestamp_utc >= o.previous_observed_at_utc
|
|
99
|
+
AND p.timestamp_utc < strftime('%Y-%m-%dT%H:%M:%fZ', o.observed_at_s + 1, 'unixepoch')) AS requests_between
|
|
100
|
+
FROM ordered o
|
|
101
|
+
WHERE o.previous_raw_line_id IS NOT NULL;
|
|
102
|
+
|
|
103
|
+
-- Observed: observation pairs whose usage rose with no Claude Code request in between (D-025; see D-044 on why real data rarely has any).
|
|
104
|
+
CREATE VIEW obs_unattributed_usage_events AS
|
|
105
|
+
SELECT * FROM window_reading_pairs
|
|
106
|
+
WHERE requests_between = 0 AND change_percentage_points > 0;
|
|
107
|
+
|
|
108
|
+
-- Observed: per window, percentage points consumed with no Claude Code request in between (a lower bound), beside pair counts.
|
|
109
|
+
CREATE VIEW obs_unattributed_usage AS
|
|
110
|
+
SELECT
|
|
111
|
+
i.window,
|
|
112
|
+
i.reset_at_utc,
|
|
113
|
+
i.readings,
|
|
114
|
+
(SELECT COUNT(*) FROM window_reading_pairs p WHERE p.window = i.window AND p.reset_at_utc = i.reset_at_utc) AS pairs,
|
|
115
|
+
(SELECT COUNT(*) FROM window_reading_pairs p
|
|
116
|
+
WHERE p.window = i.window AND p.reset_at_utc = i.reset_at_utc AND p.requests_between = 0) AS pairs_without_requests,
|
|
117
|
+
(SELECT COUNT(*) FROM window_reading_pairs p
|
|
118
|
+
WHERE p.window = i.window AND p.reset_at_utc = i.reset_at_utc AND p.change_percentage_points < 0) AS decreasing_pairs,
|
|
119
|
+
-- Fewer than two observations means no pair was observed: unknown, not 0.
|
|
120
|
+
CASE WHEN i.readings >= 2 THEN
|
|
121
|
+
(SELECT TOTAL(e.change_percentage_points) FROM obs_unattributed_usage_events e
|
|
122
|
+
WHERE e.window = i.window AND e.reset_at_utc = i.reset_at_utc)
|
|
123
|
+
END AS unattributed_percentage_points,
|
|
124
|
+
i.window_open,
|
|
125
|
+
i.covers_from,
|
|
126
|
+
i.covers_to
|
|
127
|
+
FROM window_instances i;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
-- 016_windows_repository_paths: a Windows repository path is written one way, whatever its source (D-049).
|
|
2
|
+
--
|
|
3
|
+
-- Observed on a Windows PC (2026-09-17, counts and structure only): Claude Code logged working
|
|
4
|
+
-- directories with `\` separators and with the drive letter both as `C:` and `c:`, while git returns
|
|
5
|
+
-- repository roots with `/` separators. For a folder that still exists, git resolved both spellings to
|
|
6
|
+
-- one root. A deleted folder with no known root is its own repository, so one such folder appeared as
|
|
7
|
+
-- two rows. Here a repository that starts with a drive letter gets an uppercase letter and `/`
|
|
8
|
+
-- separators. Windows paths are case-insensitive for the drive, and a POSIX repository is an absolute
|
|
9
|
+
-- path starting with `/`, so no POSIX value changes. The stored cwd and repo_root keep their source
|
|
10
|
+
-- spelling; only the attributed repository is normalized.
|
|
11
|
+
|
|
12
|
+
DROP VIEW request_repositories;
|
|
13
|
+
|
|
14
|
+
-- Observed: each request's repository and how it was attributed (D-010, D-028); a missing folder whose repository still exists counts as that repository; a Windows path is written with an uppercase drive letter and `/` separators (D-049).
|
|
15
|
+
CREATE VIEW request_repositories AS
|
|
16
|
+
WITH attributed AS (
|
|
17
|
+
SELECT
|
|
18
|
+
d.raw_line_id,
|
|
19
|
+
d.cwd,
|
|
20
|
+
CASE
|
|
21
|
+
WHEN r.cwd IS NULL THEN 'unresolved'
|
|
22
|
+
WHEN r.repo_root IS NOT NULL AND r.root_exists = 1 THEN 'repo'
|
|
23
|
+
WHEN r.repo_root IS NOT NULL THEN 'missing'
|
|
24
|
+
ELSE r.kind
|
|
25
|
+
END AS repo_kind,
|
|
26
|
+
COALESCE(r.repo_root, d.cwd) AS repository,
|
|
27
|
+
r.resolved_via
|
|
28
|
+
FROM requests_dedup d
|
|
29
|
+
LEFT JOIN repositories r ON r.cwd = d.cwd
|
|
30
|
+
)
|
|
31
|
+
SELECT
|
|
32
|
+
raw_line_id,
|
|
33
|
+
cwd,
|
|
34
|
+
repo_kind,
|
|
35
|
+
CASE
|
|
36
|
+
WHEN repository GLOB '[A-Za-z]:[\/]*'
|
|
37
|
+
THEN upper(substr(repository, 1, 1)) || replace(substr(repository, 2), char(92), '/')
|
|
38
|
+
ELSE repository
|
|
39
|
+
END AS repository,
|
|
40
|
+
resolved_via
|
|
41
|
+
FROM attributed;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
-- 017_unattributed_usage_once: compute the reading pairs once per window instead of four times.
|
|
2
|
+
--
|
|
3
|
+
-- obs_unattributed_usage asked window_reading_pairs four separate correlated questions per window
|
|
4
|
+
-- instance, and window_reading_pairs answers each one by running its own correlated subquery over
|
|
5
|
+
-- parsed_lines for every pair. Reading the view took about 8.9 seconds on a real database, 55% of
|
|
6
|
+
-- the whole report, to return one row per window. The numbers were right; they were just computed
|
|
7
|
+
-- many times over.
|
|
8
|
+
--
|
|
9
|
+
-- The rewrite aggregates the pairs once, grouped by window instance, and joins that to the
|
|
10
|
+
-- instances. No column, value, or row changes: a window with no pairs reported zeros through the
|
|
11
|
+
-- subqueries and reports them through COALESCE here, and TOTAL() over no rows is 0.0, which the
|
|
12
|
+
-- COALESCE reproduces. Source: no API field; this view is built from status line readings (D-025,
|
|
13
|
+
-- D-044).
|
|
14
|
+
DROP VIEW obs_unattributed_usage;
|
|
15
|
+
|
|
16
|
+
-- Observed: per window, percentage points consumed with no Claude Code request in between (a lower bound), beside pair counts.
|
|
17
|
+
CREATE VIEW obs_unattributed_usage AS
|
|
18
|
+
WITH pair_totals AS (
|
|
19
|
+
SELECT
|
|
20
|
+
p.window,
|
|
21
|
+
p.reset_at_utc,
|
|
22
|
+
COUNT(*) AS pairs,
|
|
23
|
+
COUNT(*) FILTER (WHERE p.requests_between = 0) AS pairs_without_requests,
|
|
24
|
+
COUNT(*) FILTER (WHERE p.change_percentage_points < 0) AS decreasing_pairs,
|
|
25
|
+
-- The same rows obs_unattributed_usage_events selects: rose, with no request in between.
|
|
26
|
+
TOTAL(
|
|
27
|
+
CASE WHEN p.requests_between = 0 AND p.change_percentage_points > 0
|
|
28
|
+
THEN p.change_percentage_points END
|
|
29
|
+
) AS unattributed_percentage_points
|
|
30
|
+
FROM window_reading_pairs p
|
|
31
|
+
GROUP BY p.window, p.reset_at_utc
|
|
32
|
+
)
|
|
33
|
+
SELECT
|
|
34
|
+
i.window,
|
|
35
|
+
i.reset_at_utc,
|
|
36
|
+
i.readings,
|
|
37
|
+
COALESCE(t.pairs, 0) AS pairs,
|
|
38
|
+
COALESCE(t.pairs_without_requests, 0) AS pairs_without_requests,
|
|
39
|
+
COALESCE(t.decreasing_pairs, 0) AS decreasing_pairs,
|
|
40
|
+
-- Fewer than two observations means no pair was observed: unknown, not 0.
|
|
41
|
+
CASE WHEN i.readings >= 2 THEN COALESCE(t.unattributed_percentage_points, 0.0) END
|
|
42
|
+
AS unattributed_percentage_points,
|
|
43
|
+
i.window_open,
|
|
44
|
+
i.covers_from,
|
|
45
|
+
i.covers_to
|
|
46
|
+
FROM window_instances i
|
|
47
|
+
LEFT JOIN pair_totals t ON t.window = i.window AND t.reset_at_utc = i.reset_at_utc;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
-- 018_request_time_index: index request lines by time, so counting the responses between two status
|
|
2
|
+
-- line readings is a range scan rather than a table scan.
|
|
3
|
+
--
|
|
4
|
+
-- window_reading_pairs counts distinct responses in an interval, once per pair. That subquery
|
|
5
|
+
-- filters parsed_lines by class and a timestamp range with no session, and every existing index
|
|
6
|
+
-- leads with session_id, so each of the hundreds of pairs scanned the whole table.
|
|
7
|
+
--
|
|
8
|
+
-- The index is PARTIAL on purpose. A plain index on (class, timestamp_utc) fixed this query but
|
|
9
|
+
-- tempted the planner into using it for the window views, which filter by session and time. Measured
|
|
10
|
+
-- on a real database, as medians of three runs:
|
|
11
|
+
-- view no index full index partial index
|
|
12
|
+
-- obs_unattributed_usage 9347 1794 1236
|
|
13
|
+
-- obs_lockout_time 2296 3391 2313
|
|
14
|
+
-- obs_sessions_not_resumed 1157 1740 1167
|
|
15
|
+
-- every report view (total) 17281 13633 9285
|
|
16
|
+
-- A partial index can only be used by a query carrying the same WHERE clause, so it serves this
|
|
17
|
+
-- subquery and is invisible to the rest. Every shape returned identical rows; only the time changed.
|
|
18
|
+
--
|
|
19
|
+
-- The cost is one small index and slightly slower inserts during ingest, which writes each line once
|
|
20
|
+
-- and reads these views on every report.
|
|
21
|
+
CREATE INDEX parsed_lines_request_time ON parsed_lines (timestamp_utc) WHERE class = 'request';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
-- 019_dedup_earliest_of_equals: when the dedup rule cannot choose, take the earliest line.
|
|
2
|
+
--
|
|
3
|
+
-- One API response is written as several lines and the largest output_tokens wins (D-001). When
|
|
4
|
+
-- every line carries the SAME counts the rule selects nothing, and the tie-breaks that followed it
|
|
5
|
+
-- ended on `line_number DESC` — the last line — which decided nothing except, at a day boundary,
|
|
6
|
+
-- which day the response landed on. Observed on a real log set: one response written three times
|
|
7
|
+
-- across 23:59:57 to 00:00:01, counted on the later day here and the earlier one by ccusage.
|
|
8
|
+
--
|
|
9
|
+
-- The rule this project already states for the same problem is D-045: collapse duplicates of one
|
|
10
|
+
-- response and date it by its first line, "when its numbers first existed". That is applied here.
|
|
11
|
+
-- Nothing changes where output_tokens grow, because the largest still wins before this is reached.
|
|
12
|
+
-- Source: no API field; this is a choice among lines the API produced (D-001, D-045, D-065).
|
|
13
|
+
DROP VIEW requests_dedup;
|
|
14
|
+
|
|
15
|
+
-- Observed: one row per distinct API response (winners) plus every unkeyed request line.
|
|
16
|
+
CREATE VIEW requests_dedup AS
|
|
17
|
+
WITH ranked AS (
|
|
18
|
+
SELECT
|
|
19
|
+
r.raw_line_id,
|
|
20
|
+
r.dedup_key,
|
|
21
|
+
r.message_id,
|
|
22
|
+
r.request_id,
|
|
23
|
+
r.model,
|
|
24
|
+
r.input_tokens,
|
|
25
|
+
r.output_tokens,
|
|
26
|
+
r.cache_read_tokens,
|
|
27
|
+
r.cache_write_5m_tokens,
|
|
28
|
+
r.cache_write_1h_tokens,
|
|
29
|
+
r.cache_write_unsplit_tokens,
|
|
30
|
+
r.speed,
|
|
31
|
+
p.session_id,
|
|
32
|
+
p.timestamp_raw,
|
|
33
|
+
p.timestamp_utc,
|
|
34
|
+
p.cwd,
|
|
35
|
+
p.is_sidechain,
|
|
36
|
+
f.root,
|
|
37
|
+
f.relative_path,
|
|
38
|
+
l.first_run_id,
|
|
39
|
+
l.line_number,
|
|
40
|
+
ROW_NUMBER() OVER (
|
|
41
|
+
PARTITION BY r.dedup_key
|
|
42
|
+
ORDER BY r.output_tokens DESC, p.is_sidechain ASC,
|
|
43
|
+
-- Among lines tied on output, the earliest: when those numbers first existed
|
|
44
|
+
-- (D-065, the same rule D-045 applies to readings). NULLs last, so an unparsed
|
|
45
|
+
-- timestamp never wins.
|
|
46
|
+
(p.timestamp_utc IS NULL) ASC, p.timestamp_utc ASC,
|
|
47
|
+
f.root DESC, f.relative_path DESC, l.first_run_id DESC, l.line_number DESC
|
|
48
|
+
) AS candidate_rank
|
|
49
|
+
FROM requests r
|
|
50
|
+
JOIN parsed_lines p ON p.raw_line_id = r.raw_line_id
|
|
51
|
+
JOIN raw_lines l ON l.id = r.raw_line_id
|
|
52
|
+
JOIN source_files f ON f.id = l.source_file_id
|
|
53
|
+
)
|
|
54
|
+
SELECT
|
|
55
|
+
raw_line_id, dedup_key, message_id, request_id, model,
|
|
56
|
+
input_tokens, output_tokens, cache_read_tokens,
|
|
57
|
+
cache_write_5m_tokens, cache_write_1h_tokens, cache_write_unsplit_tokens, speed,
|
|
58
|
+
session_id, timestamp_raw, timestamp_utc, cwd, is_sidechain,
|
|
59
|
+
root, relative_path, first_run_id, line_number
|
|
60
|
+
FROM ranked
|
|
61
|
+
WHERE dedup_key IS NULL OR candidate_rank = 1;
|