@aperdomoll90/ledger-ai 1.4.0 → 1.4.2
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/dist/cli.js +177 -221
- package/dist/commands/add.js +51 -100
- package/dist/commands/backfill.js +55 -0
- package/dist/commands/backup.js +10 -10
- package/dist/commands/check.js +21 -29
- package/dist/commands/config.js +13 -12
- package/dist/commands/delete.js +22 -17
- package/dist/commands/eval-judge.js +11 -0
- package/dist/commands/eval.js +321 -0
- package/dist/commands/export.js +8 -10
- package/dist/commands/get.js +9 -0
- package/dist/commands/hunt.js +206 -0
- package/dist/commands/ingest.js +15 -14
- package/dist/commands/init.js +18 -20
- package/dist/commands/list.js +21 -7
- package/dist/commands/migrate.js +11 -11
- package/dist/commands/onboard.js +2 -2
- package/dist/commands/pull.js +3 -2
- package/dist/commands/push.js +8 -8
- package/dist/commands/restore.js +38 -38
- package/dist/commands/show.js +13 -16
- package/dist/commands/sync.js +58 -19
- package/dist/commands/tag.js +20 -14
- package/dist/commands/update.js +50 -18
- package/dist/commands/wizard.js +3 -3
- package/dist/lib/ai-search.js +163 -0
- package/dist/lib/audit.js +19 -0
- package/dist/lib/backfill.js +60 -0
- package/dist/lib/config.js +19 -2
- package/dist/lib/document-classification.js +5 -0
- package/dist/lib/document-fetching.js +77 -0
- package/dist/lib/document-operations.js +150 -0
- package/dist/lib/documents/classification.js +5 -0
- package/dist/lib/documents/fetching.js +89 -0
- package/dist/lib/documents/operations.js +304 -0
- package/dist/lib/domains.js +116 -0
- package/dist/lib/embeddings.js +190 -0
- package/dist/lib/errors.js +3 -1
- package/dist/lib/eval/eval-advanced.js +289 -0
- package/dist/lib/eval/eval-judge-session.js +233 -0
- package/dist/lib/eval/eval-store.js +105 -0
- package/dist/lib/eval/eval.js +303 -0
- package/dist/lib/file-writer.js +23 -0
- package/dist/lib/generators.js +44 -45
- package/dist/lib/hunter-db.js +235 -0
- package/dist/lib/hunter-rss.js +30 -0
- package/dist/lib/hunter-scoring.js +55 -0
- package/dist/lib/hunter-types.js +36 -0
- package/dist/lib/lint-configs.js +20 -0
- package/dist/lib/migrate.js +2 -2
- package/dist/lib/notes.js +173 -59
- package/dist/lib/observability.js +296 -0
- package/dist/lib/op-add-note-types.test.js +7 -6
- package/dist/lib/prompt.js +8 -8
- package/dist/lib/rate-limiter.js +103 -0
- package/dist/lib/search/ai-search.js +396 -0
- package/dist/lib/search/chunk-context-enrichment.js +155 -0
- package/dist/lib/search/embeddings.js +293 -0
- package/dist/lib/search/reranker.js +120 -0
- package/dist/lib/search/semantic-cache.js +53 -0
- package/dist/lib/type-registry.test.js +6 -6
- package/dist/mcp-server.js +553 -66
- package/dist/migrations/migrations/005-audit-log.sql +22 -0
- package/dist/migrations/migrations/005_opportunities.sql +48 -0
- package/dist/migrations/migrations/006-audited-operations.sql +235 -0
- package/dist/migrations/migrations/006_hunt_analytics.sql +38 -0
- package/dist/migrations/migrations/007-eval-golden-judgments.sql +119 -0
- package/dist/migrations/migrations/008-drop-expected-doc-ids.sql +9 -0
- package/dist/migrations/migrations/008-judge-helpers.sql +21 -0
- package/dist/migrations/migrations/009-semantic-cache.sql +216 -0
- package/dist/scripts/batch-grade.js +344 -0
- package/dist/scripts/benchmark-ingestion.js +376 -0
- package/dist/scripts/convert-judgments-to-graded.js +88 -0
- package/dist/scripts/diagnose-first-result.js +333 -0
- package/dist/scripts/drop-golden-query.js +53 -0
- package/dist/scripts/eval-search.js +115 -0
- package/dist/scripts/grade-unjudged-top1.js +138 -0
- package/dist/scripts/hunter-analytics.js +38 -0
- package/dist/scripts/hunter-cron.js +63 -0
- package/dist/scripts/hunter-purge.js +25 -0
- package/dist/scripts/migrate-v2.js +140 -0
- package/dist/scripts/reindex.js +74 -0
- package/dist/scripts/sync-local-docs.js +153 -0
- package/package.json +7 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
-- Migration 005: Audit Log Table
|
|
2
|
+
-- Phase 1 of v2 roadmap
|
|
3
|
+
-- Append-only log of every write operation for rollback, sync, rate limiting, and observability
|
|
4
|
+
--
|
|
5
|
+
-- Design decisions:
|
|
6
|
+
-- - No FK on note_id (audit entries must survive note deletion)
|
|
7
|
+
-- - JSONB diff column stores old values for rollback
|
|
8
|
+
-- - Indexes on note_id (lookup by note), created_at (time-range), domain (filter by domain)
|
|
9
|
+
|
|
10
|
+
CREATE TABLE IF NOT EXISTS audit_log (
|
|
11
|
+
id bigserial PRIMARY KEY,
|
|
12
|
+
note_id bigint,
|
|
13
|
+
domain text,
|
|
14
|
+
operation text NOT NULL,
|
|
15
|
+
agent text NOT NULL,
|
|
16
|
+
diff jsonb,
|
|
17
|
+
created_at timestamptz DEFAULT now()
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
CREATE INDEX IF NOT EXISTS idx_audit_note_id ON audit_log (note_id);
|
|
21
|
+
CREATE INDEX IF NOT EXISTS idx_audit_created ON audit_log (created_at);
|
|
22
|
+
CREATE INDEX IF NOT EXISTS idx_audit_domain ON audit_log (domain);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
-- 005_opportunities.sql
|
|
2
|
+
-- Hunter agent: opportunity tracking for freelance and employment leads
|
|
3
|
+
|
|
4
|
+
CREATE TABLE opportunities (
|
|
5
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
6
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
7
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
8
|
+
|
|
9
|
+
-- Source
|
|
10
|
+
url TEXT UNIQUE NOT NULL,
|
|
11
|
+
title TEXT NOT NULL,
|
|
12
|
+
source TEXT NOT NULL,
|
|
13
|
+
raw_description TEXT,
|
|
14
|
+
|
|
15
|
+
-- Classification
|
|
16
|
+
track TEXT NOT NULL CHECK (track IN ('freelance', 'employment')),
|
|
17
|
+
|
|
18
|
+
-- Scoring (nullable — raw listings stored before scoring)
|
|
19
|
+
score INTEGER,
|
|
20
|
+
score_breakdown JSONB,
|
|
21
|
+
recommendation TEXT CHECK (recommendation IN ('apply', 'ask', 'skip')),
|
|
22
|
+
reject_reasons TEXT[],
|
|
23
|
+
|
|
24
|
+
-- AI Output (nullable — populated after scoring)
|
|
25
|
+
summary TEXT,
|
|
26
|
+
key_requirements TEXT[],
|
|
27
|
+
compensation_range TEXT,
|
|
28
|
+
scored_at TIMESTAMPTZ,
|
|
29
|
+
|
|
30
|
+
-- Pipeline
|
|
31
|
+
status TEXT NOT NULL DEFAULT 'new' CHECK (status IN (
|
|
32
|
+
'new', 'reviewed', 'applied', 'interviewing', 'won', 'lost', 'rejected'
|
|
33
|
+
)),
|
|
34
|
+
status_changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
35
|
+
notes TEXT
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
-- Indexes
|
|
39
|
+
CREATE INDEX idx_opportunities_track ON opportunities(track);
|
|
40
|
+
CREATE INDEX idx_opportunities_status ON opportunities(status);
|
|
41
|
+
CREATE INDEX idx_opportunities_score ON opportunities(score DESC);
|
|
42
|
+
CREATE INDEX idx_opportunities_created ON opportunities(created_at DESC);
|
|
43
|
+
CREATE INDEX idx_opportunities_unscored ON opportunities(created_at) WHERE score IS NULL;
|
|
44
|
+
|
|
45
|
+
-- Auto-update updated_at trigger (reuse pattern from notes table)
|
|
46
|
+
CREATE TRIGGER update_opportunities_updated_at
|
|
47
|
+
BEFORE UPDATE ON opportunities
|
|
48
|
+
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
-- Migration 006: Transactional Note Operations with Audit
|
|
2
|
+
-- Every note write (create, update, delete) is wrapped in a transaction with its audit entry.
|
|
3
|
+
-- If either the note write or the audit entry fails, both roll back.
|
|
4
|
+
-- This prevents: orphaned notes without audit trails, audit entries for failed writes,
|
|
5
|
+
-- and partial chunk writes.
|
|
6
|
+
|
|
7
|
+
-- =============================================================================
|
|
8
|
+
-- 1. CREATE: Insert one or more note rows + audit entry in one transaction
|
|
9
|
+
-- =============================================================================
|
|
10
|
+
-- Accepts arrays for multi-chunk support. Single-chunk = arrays of length 1.
|
|
11
|
+
-- Returns the IDs of all inserted rows.
|
|
12
|
+
|
|
13
|
+
CREATE OR REPLACE FUNCTION note_create(
|
|
14
|
+
p_contents text[],
|
|
15
|
+
p_metadatas jsonb[],
|
|
16
|
+
p_embeddings vector(1536)[],
|
|
17
|
+
p_ids bigint[] DEFAULT NULL, -- optional: preserve specific IDs (for upsert)
|
|
18
|
+
p_created_at timestamptz DEFAULT NULL -- optional: preserve original timestamp
|
|
19
|
+
) RETURNS bigint[] AS $$
|
|
20
|
+
DECLARE
|
|
21
|
+
v_ids bigint[] := '{}';
|
|
22
|
+
v_id bigint;
|
|
23
|
+
v_ts timestamptz := COALESCE(p_created_at, now());
|
|
24
|
+
i int;
|
|
25
|
+
BEGIN
|
|
26
|
+
FOR i IN 1..array_length(p_contents, 1) LOOP
|
|
27
|
+
IF p_ids IS NOT NULL AND p_ids[i] IS NOT NULL THEN
|
|
28
|
+
INSERT INTO notes (id, content, metadata, embedding, created_at)
|
|
29
|
+
VALUES (p_ids[i], p_contents[i], p_metadatas[i], p_embeddings[i], v_ts)
|
|
30
|
+
RETURNING id INTO v_id;
|
|
31
|
+
ELSE
|
|
32
|
+
INSERT INTO notes (content, metadata, embedding, created_at)
|
|
33
|
+
VALUES (p_contents[i], p_metadatas[i], p_embeddings[i], v_ts)
|
|
34
|
+
RETURNING id INTO v_id;
|
|
35
|
+
END IF;
|
|
36
|
+
v_ids := v_ids || v_id;
|
|
37
|
+
END LOOP;
|
|
38
|
+
|
|
39
|
+
-- Audit: log creation using first chunk's metadata for domain/agent
|
|
40
|
+
INSERT INTO audit_log (note_id, domain, operation, agent, diff)
|
|
41
|
+
VALUES (
|
|
42
|
+
v_ids[1],
|
|
43
|
+
p_metadatas[1]->>'domain',
|
|
44
|
+
'create',
|
|
45
|
+
COALESCE(p_metadatas[1]->>'agent', 'unknown'),
|
|
46
|
+
NULL
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
RETURN v_ids;
|
|
50
|
+
END;
|
|
51
|
+
$$ LANGUAGE plpgsql;
|
|
52
|
+
|
|
53
|
+
-- =============================================================================
|
|
54
|
+
-- 2. UPDATE: Update a single note in-place + audit entry
|
|
55
|
+
-- =============================================================================
|
|
56
|
+
-- Reads old content/metadata before overwriting for the audit diff.
|
|
57
|
+
|
|
58
|
+
CREATE OR REPLACE FUNCTION note_update(
|
|
59
|
+
p_id bigint,
|
|
60
|
+
p_content text,
|
|
61
|
+
p_metadata jsonb,
|
|
62
|
+
p_embedding vector(1536)
|
|
63
|
+
) RETURNS void AS $$
|
|
64
|
+
DECLARE
|
|
65
|
+
v_old_content text;
|
|
66
|
+
v_old_metadata jsonb;
|
|
67
|
+
BEGIN
|
|
68
|
+
-- Read old values for audit diff
|
|
69
|
+
SELECT content, metadata INTO v_old_content, v_old_metadata
|
|
70
|
+
FROM notes WHERE id = p_id;
|
|
71
|
+
|
|
72
|
+
IF NOT FOUND THEN
|
|
73
|
+
RAISE EXCEPTION 'Note % not found', p_id;
|
|
74
|
+
END IF;
|
|
75
|
+
|
|
76
|
+
-- Update the note
|
|
77
|
+
UPDATE notes
|
|
78
|
+
SET content = p_content, metadata = p_metadata, embedding = p_embedding, updated_at = now()
|
|
79
|
+
WHERE id = p_id;
|
|
80
|
+
|
|
81
|
+
-- Audit: log update with old values for rollback
|
|
82
|
+
INSERT INTO audit_log (note_id, domain, operation, agent, diff)
|
|
83
|
+
VALUES (
|
|
84
|
+
p_id,
|
|
85
|
+
p_metadata->>'domain',
|
|
86
|
+
'update',
|
|
87
|
+
COALESCE(p_metadata->>'agent', 'unknown'),
|
|
88
|
+
jsonb_build_object('content', v_old_content, 'metadata', v_old_metadata)
|
|
89
|
+
);
|
|
90
|
+
END;
|
|
91
|
+
$$ LANGUAGE plpgsql;
|
|
92
|
+
|
|
93
|
+
-- =============================================================================
|
|
94
|
+
-- 3. REPLACE: Delete old note/chunks + insert new note/chunks + audit
|
|
95
|
+
-- =============================================================================
|
|
96
|
+
-- For chunk-count changes: atomically removes old content and inserts new.
|
|
97
|
+
-- Preserves the original note ID for the first new chunk.
|
|
98
|
+
|
|
99
|
+
CREATE OR REPLACE FUNCTION note_replace(
|
|
100
|
+
p_old_id bigint, -- original note ID to preserve
|
|
101
|
+
p_old_chunk_group text, -- NULL if old was single-chunk
|
|
102
|
+
p_contents text[],
|
|
103
|
+
p_metadatas jsonb[],
|
|
104
|
+
p_embeddings vector(1536)[],
|
|
105
|
+
p_created_at timestamptz
|
|
106
|
+
) RETURNS bigint[] AS $$
|
|
107
|
+
DECLARE
|
|
108
|
+
v_old_content text;
|
|
109
|
+
v_old_metadata jsonb;
|
|
110
|
+
v_ids bigint[] := '{}';
|
|
111
|
+
v_id bigint;
|
|
112
|
+
i int;
|
|
113
|
+
BEGIN
|
|
114
|
+
-- Read old values for audit diff (from the original note)
|
|
115
|
+
SELECT content, metadata INTO v_old_content, v_old_metadata
|
|
116
|
+
FROM notes WHERE id = p_old_id;
|
|
117
|
+
|
|
118
|
+
-- Delete old note/chunks
|
|
119
|
+
IF p_old_chunk_group IS NOT NULL THEN
|
|
120
|
+
DELETE FROM notes WHERE metadata->>'chunk_group' = p_old_chunk_group;
|
|
121
|
+
ELSE
|
|
122
|
+
DELETE FROM notes WHERE id = p_old_id;
|
|
123
|
+
END IF;
|
|
124
|
+
|
|
125
|
+
-- Insert new note/chunks
|
|
126
|
+
FOR i IN 1..array_length(p_contents, 1) LOOP
|
|
127
|
+
IF i = 1 THEN
|
|
128
|
+
-- First chunk preserves the original ID
|
|
129
|
+
INSERT INTO notes (id, content, metadata, embedding, created_at)
|
|
130
|
+
VALUES (p_old_id, p_contents[i], p_metadatas[i], p_embeddings[i], p_created_at)
|
|
131
|
+
RETURNING id INTO v_id;
|
|
132
|
+
ELSE
|
|
133
|
+
INSERT INTO notes (content, metadata, embedding, created_at)
|
|
134
|
+
VALUES (p_contents[i], p_metadatas[i], p_embeddings[i], p_created_at)
|
|
135
|
+
RETURNING id INTO v_id;
|
|
136
|
+
END IF;
|
|
137
|
+
v_ids := v_ids || v_id;
|
|
138
|
+
END LOOP;
|
|
139
|
+
|
|
140
|
+
-- Audit: log as update with old values for rollback
|
|
141
|
+
INSERT INTO audit_log (note_id, domain, operation, agent, diff)
|
|
142
|
+
VALUES (
|
|
143
|
+
p_old_id,
|
|
144
|
+
p_metadatas[1]->>'domain',
|
|
145
|
+
'update',
|
|
146
|
+
COALESCE(p_metadatas[1]->>'agent', 'unknown'),
|
|
147
|
+
jsonb_build_object('content', v_old_content, 'metadata', v_old_metadata)
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
RETURN v_ids;
|
|
151
|
+
END;
|
|
152
|
+
$$ LANGUAGE plpgsql;
|
|
153
|
+
|
|
154
|
+
-- =============================================================================
|
|
155
|
+
-- 4. DELETE: Delete note/chunks + audit entry with full content for rollback
|
|
156
|
+
-- =============================================================================
|
|
157
|
+
|
|
158
|
+
CREATE OR REPLACE FUNCTION note_delete(
|
|
159
|
+
p_id bigint,
|
|
160
|
+
p_chunk_group text, -- NULL if single-chunk note
|
|
161
|
+
p_agent text
|
|
162
|
+
) RETURNS void AS $$
|
|
163
|
+
DECLARE
|
|
164
|
+
v_content text;
|
|
165
|
+
v_metadata jsonb;
|
|
166
|
+
BEGIN
|
|
167
|
+
-- Read full content for rollback
|
|
168
|
+
SELECT content, metadata INTO v_content, v_metadata
|
|
169
|
+
FROM notes WHERE id = p_id;
|
|
170
|
+
|
|
171
|
+
IF NOT FOUND THEN
|
|
172
|
+
RAISE EXCEPTION 'Note % not found', p_id;
|
|
173
|
+
END IF;
|
|
174
|
+
|
|
175
|
+
-- Audit FIRST: store full content before deletion
|
|
176
|
+
INSERT INTO audit_log (note_id, domain, operation, agent, diff)
|
|
177
|
+
VALUES (
|
|
178
|
+
p_id,
|
|
179
|
+
v_metadata->>'domain',
|
|
180
|
+
'delete',
|
|
181
|
+
p_agent,
|
|
182
|
+
jsonb_build_object('content', v_content, 'metadata', v_metadata)
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
-- Then delete
|
|
186
|
+
IF p_chunk_group IS NOT NULL THEN
|
|
187
|
+
DELETE FROM notes WHERE metadata->>'chunk_group' = p_chunk_group;
|
|
188
|
+
ELSE
|
|
189
|
+
DELETE FROM notes WHERE id = p_id;
|
|
190
|
+
END IF;
|
|
191
|
+
END;
|
|
192
|
+
$$ LANGUAGE plpgsql;
|
|
193
|
+
|
|
194
|
+
-- =============================================================================
|
|
195
|
+
-- 5. UPDATE METADATA: Update metadata fields + audit with changed fields
|
|
196
|
+
-- =============================================================================
|
|
197
|
+
|
|
198
|
+
CREATE OR REPLACE FUNCTION note_update_metadata(
|
|
199
|
+
p_id bigint,
|
|
200
|
+
p_metadata jsonb -- fields to merge (not full replacement)
|
|
201
|
+
) RETURNS void AS $$
|
|
202
|
+
DECLARE
|
|
203
|
+
v_old_metadata jsonb;
|
|
204
|
+
v_merged jsonb;
|
|
205
|
+
v_changed jsonb;
|
|
206
|
+
BEGIN
|
|
207
|
+
SELECT metadata INTO v_old_metadata FROM notes WHERE id = p_id;
|
|
208
|
+
|
|
209
|
+
IF NOT FOUND THEN
|
|
210
|
+
RAISE EXCEPTION 'Note % not found', p_id;
|
|
211
|
+
END IF;
|
|
212
|
+
|
|
213
|
+
-- Merge: new fields override old, old fields preserved
|
|
214
|
+
v_merged := v_old_metadata || p_metadata;
|
|
215
|
+
|
|
216
|
+
-- Calculate which fields actually changed (old values of changed keys)
|
|
217
|
+
SELECT COALESCE(jsonb_object_agg(key, v_old_metadata->key), '{}'::jsonb)
|
|
218
|
+
INTO v_changed
|
|
219
|
+
FROM jsonb_each(p_metadata)
|
|
220
|
+
WHERE v_old_metadata->key IS DISTINCT FROM p_metadata->key;
|
|
221
|
+
|
|
222
|
+
-- Update
|
|
223
|
+
UPDATE notes SET metadata = v_merged, updated_at = now() WHERE id = p_id;
|
|
224
|
+
|
|
225
|
+
-- Audit
|
|
226
|
+
INSERT INTO audit_log (note_id, domain, operation, agent, diff)
|
|
227
|
+
VALUES (
|
|
228
|
+
p_id,
|
|
229
|
+
v_merged->>'domain',
|
|
230
|
+
'update_metadata',
|
|
231
|
+
COALESCE(v_merged->>'agent', 'unknown'),
|
|
232
|
+
jsonb_build_object('metadata', v_changed)
|
|
233
|
+
);
|
|
234
|
+
END;
|
|
235
|
+
$$ LANGUAGE plpgsql;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
-- 006_hunt_analytics.sql
|
|
2
|
+
-- Hunter agent: periodic analytics summaries
|
|
3
|
+
|
|
4
|
+
CREATE TABLE hunt_analytics (
|
|
5
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
6
|
+
period_start DATE NOT NULL,
|
|
7
|
+
period_end DATE NOT NULL,
|
|
8
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
9
|
+
|
|
10
|
+
-- Volume
|
|
11
|
+
total_found INTEGER NOT NULL,
|
|
12
|
+
freelance_count INTEGER NOT NULL,
|
|
13
|
+
employment_count INTEGER NOT NULL,
|
|
14
|
+
|
|
15
|
+
-- Score Distribution
|
|
16
|
+
score_80_plus INTEGER NOT NULL,
|
|
17
|
+
score_60_79 INTEGER NOT NULL,
|
|
18
|
+
score_40_59 INTEGER NOT NULL,
|
|
19
|
+
score_below_40 INTEGER NOT NULL,
|
|
20
|
+
|
|
21
|
+
-- Pipeline
|
|
22
|
+
applied_count INTEGER NOT NULL,
|
|
23
|
+
won_count INTEGER NOT NULL,
|
|
24
|
+
lost_count INTEGER NOT NULL,
|
|
25
|
+
apply_rate NUMERIC(5,2),
|
|
26
|
+
win_rate NUMERIC(5,2),
|
|
27
|
+
|
|
28
|
+
-- Patterns (JSONB)
|
|
29
|
+
top_reject_reasons JSONB,
|
|
30
|
+
top_skills_demanded JSONB,
|
|
31
|
+
avg_score_by_track JSONB,
|
|
32
|
+
compensation_ranges JSONB,
|
|
33
|
+
|
|
34
|
+
-- Insights
|
|
35
|
+
summary TEXT
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
CREATE UNIQUE INDEX idx_hunt_analytics_period ON hunt_analytics(period_start, period_end);
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
-- ============================================================
|
|
2
|
+
-- Migration 007 — Phase 4.6.2: Graded Relevance
|
|
3
|
+
--
|
|
4
|
+
-- Adds eval_golden_judgments table + 3 RPC functions for atomic writes.
|
|
5
|
+
-- Does NOT drop eval_golden_dataset.expected_doc_ids (deferred to migration 008).
|
|
6
|
+
-- ============================================================
|
|
7
|
+
|
|
8
|
+
-- ============================================================
|
|
9
|
+
-- Table
|
|
10
|
+
-- ============================================================
|
|
11
|
+
CREATE TABLE IF NOT EXISTS eval_golden_judgments (
|
|
12
|
+
id bigserial PRIMARY KEY,
|
|
13
|
+
golden_id bigint NOT NULL REFERENCES eval_golden_dataset(id) ON DELETE CASCADE,
|
|
14
|
+
document_id bigint NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
|
|
15
|
+
grade smallint NOT NULL CHECK (grade BETWEEN 0 AND 3),
|
|
16
|
+
judged_at timestamptz NOT NULL DEFAULT now(),
|
|
17
|
+
judged_by text NOT NULL DEFAULT 'adrian',
|
|
18
|
+
notes text,
|
|
19
|
+
UNIQUE (golden_id, document_id)
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
-- ============================================================
|
|
23
|
+
-- Indexes
|
|
24
|
+
-- ============================================================
|
|
25
|
+
CREATE INDEX IF NOT EXISTS idx_golden_judgments_golden_id ON eval_golden_judgments(golden_id);
|
|
26
|
+
CREATE INDEX IF NOT EXISTS idx_golden_judgments_document_id ON eval_golden_judgments(document_id);
|
|
27
|
+
CREATE INDEX IF NOT EXISTS idx_golden_judgments_grade ON eval_golden_judgments(grade);
|
|
28
|
+
|
|
29
|
+
-- ============================================================
|
|
30
|
+
-- Row-level security
|
|
31
|
+
-- ============================================================
|
|
32
|
+
ALTER TABLE eval_golden_judgments ENABLE ROW LEVEL SECURITY;
|
|
33
|
+
|
|
34
|
+
DROP POLICY IF EXISTS eval_golden_judgments_service_all ON eval_golden_judgments;
|
|
35
|
+
CREATE POLICY eval_golden_judgments_service_all
|
|
36
|
+
ON eval_golden_judgments
|
|
37
|
+
FOR ALL
|
|
38
|
+
TO service_role
|
|
39
|
+
USING (true)
|
|
40
|
+
WITH CHECK (true);
|
|
41
|
+
|
|
42
|
+
-- ============================================================
|
|
43
|
+
-- RPC: judgment_create
|
|
44
|
+
-- Inserts one judgment. Errors on duplicate (golden_id, document_id).
|
|
45
|
+
-- Returns the new row id.
|
|
46
|
+
-- ============================================================
|
|
47
|
+
CREATE OR REPLACE FUNCTION judgment_create(
|
|
48
|
+
p_golden_id bigint,
|
|
49
|
+
p_document_id bigint,
|
|
50
|
+
p_grade smallint,
|
|
51
|
+
p_judged_by text DEFAULT 'adrian',
|
|
52
|
+
p_notes text DEFAULT NULL
|
|
53
|
+
) RETURNS bigint
|
|
54
|
+
LANGUAGE plpgsql
|
|
55
|
+
AS $$
|
|
56
|
+
DECLARE
|
|
57
|
+
v_id bigint;
|
|
58
|
+
BEGIN
|
|
59
|
+
INSERT INTO eval_golden_judgments (golden_id, document_id, grade, judged_by, notes)
|
|
60
|
+
VALUES (p_golden_id, p_document_id, p_grade, p_judged_by, p_notes)
|
|
61
|
+
RETURNING id INTO v_id;
|
|
62
|
+
RETURN v_id;
|
|
63
|
+
END;
|
|
64
|
+
$$;
|
|
65
|
+
|
|
66
|
+
-- ============================================================
|
|
67
|
+
-- RPC: judgment_update
|
|
68
|
+
-- Updates grade (and optionally notes) for an existing judgment.
|
|
69
|
+
-- Bumps judged_at. Errors if no row matches.
|
|
70
|
+
-- ============================================================
|
|
71
|
+
CREATE OR REPLACE FUNCTION judgment_update(
|
|
72
|
+
p_golden_id bigint,
|
|
73
|
+
p_document_id bigint,
|
|
74
|
+
p_grade smallint,
|
|
75
|
+
p_notes text DEFAULT NULL
|
|
76
|
+
) RETURNS void
|
|
77
|
+
LANGUAGE plpgsql
|
|
78
|
+
AS $$
|
|
79
|
+
DECLARE
|
|
80
|
+
v_updated int;
|
|
81
|
+
BEGIN
|
|
82
|
+
UPDATE eval_golden_judgments
|
|
83
|
+
SET grade = p_grade,
|
|
84
|
+
notes = COALESCE(p_notes, notes),
|
|
85
|
+
judged_at = now()
|
|
86
|
+
WHERE golden_id = p_golden_id
|
|
87
|
+
AND document_id = p_document_id;
|
|
88
|
+
|
|
89
|
+
GET DIAGNOSTICS v_updated = ROW_COUNT;
|
|
90
|
+
IF v_updated = 0 THEN
|
|
91
|
+
RAISE EXCEPTION 'judgment_update: no judgment exists for (golden_id=%, document_id=%)',
|
|
92
|
+
p_golden_id, p_document_id;
|
|
93
|
+
END IF;
|
|
94
|
+
END;
|
|
95
|
+
$$;
|
|
96
|
+
|
|
97
|
+
-- ============================================================
|
|
98
|
+
-- RPC: judgment_delete
|
|
99
|
+
-- Removes a judgment. Idempotent on missing row (no error).
|
|
100
|
+
-- ============================================================
|
|
101
|
+
CREATE OR REPLACE FUNCTION judgment_delete(
|
|
102
|
+
p_golden_id bigint,
|
|
103
|
+
p_document_id bigint
|
|
104
|
+
) RETURNS void
|
|
105
|
+
LANGUAGE plpgsql
|
|
106
|
+
AS $$
|
|
107
|
+
BEGIN
|
|
108
|
+
DELETE FROM eval_golden_judgments
|
|
109
|
+
WHERE golden_id = p_golden_id
|
|
110
|
+
AND document_id = p_document_id;
|
|
111
|
+
END;
|
|
112
|
+
$$;
|
|
113
|
+
|
|
114
|
+
-- ============================================================
|
|
115
|
+
-- Grants
|
|
116
|
+
-- ============================================================
|
|
117
|
+
GRANT EXECUTE ON FUNCTION judgment_create(bigint, bigint, smallint, text, text) TO service_role;
|
|
118
|
+
GRANT EXECUTE ON FUNCTION judgment_update(bigint, bigint, smallint, text) TO service_role;
|
|
119
|
+
GRANT EXECUTE ON FUNCTION judgment_delete(bigint, bigint) TO service_role;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
-- ============================================================
|
|
2
|
+
-- Migration 008b — Phase 4.6.2 cleanup: drop legacy column
|
|
3
|
+
--
|
|
4
|
+
-- Runs AFTER the graded dataset is fully populated and run 14
|
|
5
|
+
-- has landed. The expected_doc_ids data was converted to grade-3
|
|
6
|
+
-- rows in eval_golden_judgments by convert-judgments-to-graded.ts.
|
|
7
|
+
-- ============================================================
|
|
8
|
+
|
|
9
|
+
ALTER TABLE eval_golden_dataset DROP COLUMN IF EXISTS expected_doc_ids;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
-- ============================================================
|
|
2
|
+
-- Migration 008 — Phase 4.6.2: judge helpers
|
|
3
|
+
--
|
|
4
|
+
-- Adds the count_golden_with_min_judgments() function used by the
|
|
5
|
+
-- ledger eval:judge CLI progress display.
|
|
6
|
+
-- ============================================================
|
|
7
|
+
|
|
8
|
+
CREATE OR REPLACE FUNCTION count_golden_with_min_judgments(p_min int)
|
|
9
|
+
RETURNS bigint
|
|
10
|
+
LANGUAGE sql
|
|
11
|
+
STABLE
|
|
12
|
+
AS $$
|
|
13
|
+
SELECT count(*)::bigint FROM (
|
|
14
|
+
SELECT golden_id
|
|
15
|
+
FROM eval_golden_judgments
|
|
16
|
+
GROUP BY golden_id
|
|
17
|
+
HAVING count(*) >= p_min
|
|
18
|
+
) AS qualifying;
|
|
19
|
+
$$;
|
|
20
|
+
|
|
21
|
+
GRANT EXECUTE ON FUNCTION count_golden_with_min_judgments(int) TO service_role;
|