@ansvar/eu-regulations-mcp 1.0.0 → 1.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/README.md +60 -22
- package/data/regulations.db +0 -0
- package/dist/database/sqlite-adapter.d.ts +2 -2
- package/dist/database/sqlite-adapter.d.ts.map +1 -1
- package/dist/database/sqlite-adapter.js.map +1 -1
- package/dist/http-server.js +27 -5
- package/dist/http-server.js.map +1 -1
- package/dist/index.js +27 -4
- package/dist/index.js.map +1 -1
- package/dist/tools/about.d.ts +40 -0
- package/dist/tools/about.d.ts.map +1 -0
- package/dist/tools/about.js +61 -0
- package/dist/tools/about.js.map +1 -0
- package/dist/tools/list.d.ts +7 -0
- package/dist/tools/list.d.ts.map +1 -1
- package/dist/tools/list.js +73 -8
- package/dist/tools/list.js.map +1 -1
- package/dist/tools/registry.d.ts +11 -1
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +56 -4
- package/dist/tools/registry.js.map +1 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +17 -5
- package/dist/worker.js.map +1 -1
- package/package.json +6 -5
- package/scripts/add-cross-references.sql +0 -200
- package/scripts/analyze-survey-responses.ts +0 -285
- package/scripts/build-db.ts +0 -421
- package/scripts/bulk-reingest-all.ts +0 -331
- package/scripts/check-updates.ts +0 -294
- package/scripts/extract-eprivacy-recitals.ts +0 -98
- package/scripts/ingest-eurlex-browser.ts +0 -113
- package/scripts/ingest-eurlex.ts +0 -349
- package/scripts/ingest-unece.ts +0 -382
- package/scripts/migrate-postgres.ts +0 -445
- package/scripts/migrate-to-postgres.ts +0 -353
- package/scripts/reingest-all-with-recitals.sh +0 -81
- package/scripts/sync-versions.ts +0 -206
- package/scripts/test-cross-refs.js +0 -26
- package/scripts/test-postgres-adapter.ts +0 -146
- package/scripts/update-dora-rts-metadata.ts +0 -112
- package/src/database/postgres-adapter.ts +0 -84
- package/src/database/sqlite-adapter.ts +0 -44
- package/src/database/types.ts +0 -10
- package/src/http-server.ts +0 -149
- package/src/index.ts +0 -61
- package/src/middleware/rate-limit.ts +0 -104
- package/src/tools/applicability.ts +0 -167
- package/src/tools/article.ts +0 -81
- package/src/tools/compare.ts +0 -217
- package/src/tools/definitions.ts +0 -49
- package/src/tools/evidence.ts +0 -84
- package/src/tools/list.ts +0 -124
- package/src/tools/map.ts +0 -86
- package/src/tools/recital.ts +0 -60
- package/src/tools/registry.ts +0 -311
- package/src/tools/search.ts +0 -297
- package/src/worker.ts +0 -708
|
@@ -1,445 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env npx tsx
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Migrate SQLite database to PostgreSQL
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* DATABASE_URL=postgresql://... npx tsx scripts/migrate-postgres.ts
|
|
8
|
-
*
|
|
9
|
-
* Options:
|
|
10
|
-
* --drop Drop existing tables before creating (DESTRUCTIVE)
|
|
11
|
-
* --verify Verify migration without making changes
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import Database from 'better-sqlite3';
|
|
15
|
-
import pg from 'pg';
|
|
16
|
-
import { fileURLToPath } from 'url';
|
|
17
|
-
import { dirname, join } from 'path';
|
|
18
|
-
|
|
19
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
20
|
-
const __dirname = dirname(__filename);
|
|
21
|
-
const SQLITE_PATH = join(__dirname, '../data/regulations.db');
|
|
22
|
-
|
|
23
|
-
// Parse command line args
|
|
24
|
-
const args = process.argv.slice(2);
|
|
25
|
-
const DROP_TABLES = args.includes('--drop');
|
|
26
|
-
const VERIFY_ONLY = args.includes('--verify');
|
|
27
|
-
|
|
28
|
-
interface MigrationStats {
|
|
29
|
-
regulations: number;
|
|
30
|
-
articles: number;
|
|
31
|
-
recitals: number;
|
|
32
|
-
definitions: number;
|
|
33
|
-
control_mappings: number;
|
|
34
|
-
applicability_rules: number;
|
|
35
|
-
source_registry: number;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async function main() {
|
|
39
|
-
console.log('🚀 PostgreSQL Migration Tool\n');
|
|
40
|
-
|
|
41
|
-
// Get connection string
|
|
42
|
-
const connectionString = process.env.DATABASE_URL;
|
|
43
|
-
if (!connectionString) {
|
|
44
|
-
console.error('❌ DATABASE_URL environment variable is required');
|
|
45
|
-
process.exit(1);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
console.log(`📊 SQLite Source: ${SQLITE_PATH}`);
|
|
49
|
-
console.log(`🐘 PostgreSQL Target: ${connectionString.replace(/:[^:@]+@/, ':***@')}\n`);
|
|
50
|
-
|
|
51
|
-
if (VERIFY_ONLY) {
|
|
52
|
-
console.log('🔍 Verification mode - no changes will be made\n');
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (DROP_TABLES) {
|
|
56
|
-
console.log('⚠️ DROP MODE - All existing data will be deleted!\n');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Connect to databases
|
|
60
|
-
console.log('Connecting to databases...');
|
|
61
|
-
const sqlite = new Database(SQLITE_PATH, { readonly: true });
|
|
62
|
-
const pgPool = new pg.Pool({ connectionString });
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
// Test PostgreSQL connection
|
|
66
|
-
await pgPool.query('SELECT 1');
|
|
67
|
-
console.log('✅ Connected to PostgreSQL\n');
|
|
68
|
-
|
|
69
|
-
if (VERIFY_ONLY) {
|
|
70
|
-
await verifyMigration(sqlite, pgPool);
|
|
71
|
-
} else {
|
|
72
|
-
await migrate(sqlite, pgPool, DROP_TABLES);
|
|
73
|
-
}
|
|
74
|
-
} catch (error) {
|
|
75
|
-
console.error('❌ Migration failed:', error);
|
|
76
|
-
process.exit(1);
|
|
77
|
-
} finally {
|
|
78
|
-
sqlite.close();
|
|
79
|
-
await pgPool.end();
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async function migrate(sqlite: Database.Database, pgPool: pg.Pool, dropTables: boolean) {
|
|
84
|
-
console.log('='.repeat(60));
|
|
85
|
-
console.log('STARTING MIGRATION');
|
|
86
|
-
console.log('='.repeat(60));
|
|
87
|
-
console.log();
|
|
88
|
-
|
|
89
|
-
const client = await pgPool.connect();
|
|
90
|
-
try {
|
|
91
|
-
await client.query('BEGIN');
|
|
92
|
-
|
|
93
|
-
// Step 1: Drop tables if requested
|
|
94
|
-
if (dropTables) {
|
|
95
|
-
console.log('🗑️ Dropping existing tables...');
|
|
96
|
-
await dropAllTables(client);
|
|
97
|
-
console.log('✅ Tables dropped\n');
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Step 2: Create schema
|
|
101
|
-
console.log('📐 Creating PostgreSQL schema...');
|
|
102
|
-
await createSchema(client);
|
|
103
|
-
console.log('✅ Schema created\n');
|
|
104
|
-
|
|
105
|
-
// Step 3: Copy data
|
|
106
|
-
console.log('📦 Copying data from SQLite...');
|
|
107
|
-
const stats = await copyData(sqlite, client);
|
|
108
|
-
console.log('✅ Data copied\n');
|
|
109
|
-
|
|
110
|
-
// Step 4: Create indexes
|
|
111
|
-
console.log('🔍 Creating indexes...');
|
|
112
|
-
await createIndexes(client);
|
|
113
|
-
console.log('✅ Indexes created\n');
|
|
114
|
-
|
|
115
|
-
// Step 5: Create FTS
|
|
116
|
-
console.log('🔎 Creating full-text search indexes...');
|
|
117
|
-
await createFullTextSearch(client);
|
|
118
|
-
console.log('✅ Full-text search ready\n');
|
|
119
|
-
|
|
120
|
-
await client.query('COMMIT');
|
|
121
|
-
|
|
122
|
-
// Step 6: Print summary
|
|
123
|
-
console.log('='.repeat(60));
|
|
124
|
-
console.log('MIGRATION COMPLETE');
|
|
125
|
-
console.log('='.repeat(60));
|
|
126
|
-
console.log('\nMigrated records:');
|
|
127
|
-
console.log(` Regulations: ${stats.regulations}`);
|
|
128
|
-
console.log(` Articles: ${stats.articles}`);
|
|
129
|
-
console.log(` Recitals: ${stats.recitals}`);
|
|
130
|
-
console.log(` Definitions: ${stats.definitions}`);
|
|
131
|
-
console.log(` Control Mappings: ${stats.control_mappings}`);
|
|
132
|
-
console.log(` Applicability Rules:${stats.applicability_rules}`);
|
|
133
|
-
console.log(` Source Registry: ${stats.source_registry}`);
|
|
134
|
-
console.log(`\n✅ Total: ${Object.values(stats).reduce((a, b) => a + b, 0)} records\n`);
|
|
135
|
-
} catch (error) {
|
|
136
|
-
await client.query('ROLLBACK');
|
|
137
|
-
throw error;
|
|
138
|
-
} finally {
|
|
139
|
-
client.release();
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async function dropAllTables(client: pg.PoolClient) {
|
|
144
|
-
const tables = [
|
|
145
|
-
'applicability_rules',
|
|
146
|
-
'control_mappings',
|
|
147
|
-
'definitions',
|
|
148
|
-
'recitals',
|
|
149
|
-
'articles',
|
|
150
|
-
'source_registry',
|
|
151
|
-
'regulations'
|
|
152
|
-
];
|
|
153
|
-
|
|
154
|
-
for (const table of tables) {
|
|
155
|
-
await client.query(`DROP TABLE IF EXISTS ${table} CASCADE`);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async function createSchema(client: pg.PoolClient) {
|
|
160
|
-
// Regulations table
|
|
161
|
-
await client.query(`
|
|
162
|
-
CREATE TABLE IF NOT EXISTS regulations (
|
|
163
|
-
id TEXT PRIMARY KEY,
|
|
164
|
-
full_name TEXT NOT NULL,
|
|
165
|
-
celex_id TEXT NOT NULL,
|
|
166
|
-
effective_date TEXT,
|
|
167
|
-
last_amended TEXT,
|
|
168
|
-
eur_lex_url TEXT
|
|
169
|
-
)
|
|
170
|
-
`);
|
|
171
|
-
|
|
172
|
-
// Source registry table
|
|
173
|
-
await client.query(`
|
|
174
|
-
CREATE TABLE IF NOT EXISTS source_registry (
|
|
175
|
-
regulation TEXT PRIMARY KEY REFERENCES regulations(id),
|
|
176
|
-
celex_id TEXT NOT NULL,
|
|
177
|
-
eur_lex_version TEXT,
|
|
178
|
-
last_fetched TEXT,
|
|
179
|
-
articles_expected INTEGER,
|
|
180
|
-
articles_parsed INTEGER,
|
|
181
|
-
quality_status TEXT CHECK(quality_status IN ('complete', 'review', 'incomplete')),
|
|
182
|
-
notes TEXT
|
|
183
|
-
)
|
|
184
|
-
`);
|
|
185
|
-
|
|
186
|
-
// Articles table
|
|
187
|
-
await client.query(`
|
|
188
|
-
CREATE TABLE IF NOT EXISTS articles (
|
|
189
|
-
id SERIAL PRIMARY KEY,
|
|
190
|
-
regulation TEXT NOT NULL REFERENCES regulations(id),
|
|
191
|
-
article_number TEXT NOT NULL,
|
|
192
|
-
title TEXT,
|
|
193
|
-
text TEXT NOT NULL,
|
|
194
|
-
chapter TEXT,
|
|
195
|
-
recitals TEXT,
|
|
196
|
-
cross_references TEXT,
|
|
197
|
-
search_vector tsvector,
|
|
198
|
-
UNIQUE(regulation, article_number)
|
|
199
|
-
)
|
|
200
|
-
`);
|
|
201
|
-
|
|
202
|
-
// Recitals table
|
|
203
|
-
await client.query(`
|
|
204
|
-
CREATE TABLE IF NOT EXISTS recitals (
|
|
205
|
-
id SERIAL PRIMARY KEY,
|
|
206
|
-
regulation TEXT NOT NULL REFERENCES regulations(id),
|
|
207
|
-
recital_number INTEGER NOT NULL,
|
|
208
|
-
text TEXT NOT NULL,
|
|
209
|
-
related_articles TEXT,
|
|
210
|
-
search_vector tsvector,
|
|
211
|
-
UNIQUE(regulation, recital_number)
|
|
212
|
-
)
|
|
213
|
-
`);
|
|
214
|
-
|
|
215
|
-
// Definitions table
|
|
216
|
-
await client.query(`
|
|
217
|
-
CREATE TABLE IF NOT EXISTS definitions (
|
|
218
|
-
id SERIAL PRIMARY KEY,
|
|
219
|
-
regulation TEXT NOT NULL REFERENCES regulations(id),
|
|
220
|
-
term TEXT NOT NULL,
|
|
221
|
-
definition TEXT NOT NULL,
|
|
222
|
-
article TEXT NOT NULL,
|
|
223
|
-
UNIQUE(regulation, term)
|
|
224
|
-
)
|
|
225
|
-
`);
|
|
226
|
-
|
|
227
|
-
// Control mappings table
|
|
228
|
-
await client.query(`
|
|
229
|
-
CREATE TABLE IF NOT EXISTS control_mappings (
|
|
230
|
-
id SERIAL PRIMARY KEY,
|
|
231
|
-
framework TEXT NOT NULL DEFAULT 'ISO27001',
|
|
232
|
-
control_id TEXT NOT NULL,
|
|
233
|
-
control_name TEXT NOT NULL,
|
|
234
|
-
regulation TEXT NOT NULL REFERENCES regulations(id),
|
|
235
|
-
articles TEXT NOT NULL,
|
|
236
|
-
coverage TEXT CHECK(coverage IN ('full', 'partial', 'related')),
|
|
237
|
-
notes TEXT
|
|
238
|
-
)
|
|
239
|
-
`);
|
|
240
|
-
|
|
241
|
-
// Applicability rules table
|
|
242
|
-
await client.query(`
|
|
243
|
-
CREATE TABLE IF NOT EXISTS applicability_rules (
|
|
244
|
-
id SERIAL PRIMARY KEY,
|
|
245
|
-
regulation TEXT NOT NULL REFERENCES regulations(id),
|
|
246
|
-
sector TEXT NOT NULL,
|
|
247
|
-
subsector TEXT,
|
|
248
|
-
applies INTEGER NOT NULL,
|
|
249
|
-
confidence TEXT CHECK(confidence IN ('definite', 'likely', 'possible')),
|
|
250
|
-
basis_article TEXT,
|
|
251
|
-
notes TEXT
|
|
252
|
-
)
|
|
253
|
-
`);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
async function copyData(sqlite: Database.Database, client: pg.PoolClient): Promise<MigrationStats> {
|
|
257
|
-
const stats: MigrationStats = {
|
|
258
|
-
regulations: 0,
|
|
259
|
-
articles: 0,
|
|
260
|
-
recitals: 0,
|
|
261
|
-
definitions: 0,
|
|
262
|
-
control_mappings: 0,
|
|
263
|
-
applicability_rules: 0,
|
|
264
|
-
source_registry: 0
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
// Copy regulations
|
|
268
|
-
const regulations = sqlite.prepare('SELECT * FROM regulations').all();
|
|
269
|
-
for (const reg of regulations) {
|
|
270
|
-
await client.query(
|
|
271
|
-
'INSERT INTO regulations (id, full_name, celex_id, effective_date, last_amended, eur_lex_url) VALUES ($1, $2, $3, $4, $5, $6)',
|
|
272
|
-
[reg.id, reg.full_name, reg.celex_id, reg.effective_date, reg.last_amended, reg.eur_lex_url]
|
|
273
|
-
);
|
|
274
|
-
stats.regulations++;
|
|
275
|
-
}
|
|
276
|
-
console.log(` ✓ Copied ${stats.regulations} regulations`);
|
|
277
|
-
|
|
278
|
-
// Copy source registry
|
|
279
|
-
const sourceRegistry = sqlite.prepare('SELECT * FROM source_registry').all();
|
|
280
|
-
for (const sr of sourceRegistry) {
|
|
281
|
-
await client.query(
|
|
282
|
-
'INSERT INTO source_registry (regulation, celex_id, eur_lex_version, last_fetched, articles_expected, articles_parsed, quality_status, notes) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)',
|
|
283
|
-
[sr.regulation, sr.celex_id, sr.eur_lex_version, sr.last_fetched, sr.articles_expected, sr.articles_parsed, sr.quality_status, sr.notes]
|
|
284
|
-
);
|
|
285
|
-
stats.source_registry++;
|
|
286
|
-
}
|
|
287
|
-
console.log(` ✓ Copied ${stats.source_registry} source registry entries`);
|
|
288
|
-
|
|
289
|
-
// Copy articles
|
|
290
|
-
const articles = sqlite.prepare('SELECT * FROM articles').all();
|
|
291
|
-
for (const art of articles) {
|
|
292
|
-
await client.query(
|
|
293
|
-
'INSERT INTO articles (regulation, article_number, title, text, chapter, recitals, cross_references) VALUES ($1, $2, $3, $4, $5, $6, $7)',
|
|
294
|
-
[art.regulation, art.article_number, art.title, art.text, art.chapter, art.recitals, art.cross_references]
|
|
295
|
-
);
|
|
296
|
-
stats.articles++;
|
|
297
|
-
}
|
|
298
|
-
console.log(` ✓ Copied ${stats.articles} articles`);
|
|
299
|
-
|
|
300
|
-
// Copy recitals
|
|
301
|
-
const recitals = sqlite.prepare('SELECT * FROM recitals').all();
|
|
302
|
-
for (const rec of recitals) {
|
|
303
|
-
await client.query(
|
|
304
|
-
'INSERT INTO recitals (regulation, recital_number, text, related_articles) VALUES ($1, $2, $3, $4)',
|
|
305
|
-
[rec.regulation, rec.recital_number, rec.text, rec.related_articles]
|
|
306
|
-
);
|
|
307
|
-
stats.recitals++;
|
|
308
|
-
}
|
|
309
|
-
console.log(` ✓ Copied ${stats.recitals} recitals`);
|
|
310
|
-
|
|
311
|
-
// Copy definitions
|
|
312
|
-
const definitions = sqlite.prepare('SELECT * FROM definitions').all();
|
|
313
|
-
for (const def of definitions) {
|
|
314
|
-
await client.query(
|
|
315
|
-
'INSERT INTO definitions (regulation, term, definition, article) VALUES ($1, $2, $3, $4)',
|
|
316
|
-
[def.regulation, def.term, def.definition, def.article]
|
|
317
|
-
);
|
|
318
|
-
stats.definitions++;
|
|
319
|
-
}
|
|
320
|
-
console.log(` ✓ Copied ${stats.definitions} definitions`);
|
|
321
|
-
|
|
322
|
-
// Copy control mappings
|
|
323
|
-
const mappings = sqlite.prepare('SELECT * FROM control_mappings').all();
|
|
324
|
-
for (const map of mappings) {
|
|
325
|
-
await client.query(
|
|
326
|
-
'INSERT INTO control_mappings (framework, control_id, control_name, regulation, articles, coverage, notes) VALUES ($1, $2, $3, $4, $5, $6, $7)',
|
|
327
|
-
[map.framework, map.control_id, map.control_name, map.regulation, map.articles, map.coverage, map.notes]
|
|
328
|
-
);
|
|
329
|
-
stats.control_mappings++;
|
|
330
|
-
}
|
|
331
|
-
console.log(` ✓ Copied ${stats.control_mappings} control mappings`);
|
|
332
|
-
|
|
333
|
-
// Copy applicability rules
|
|
334
|
-
const rules = sqlite.prepare('SELECT * FROM applicability_rules').all();
|
|
335
|
-
for (const rule of rules) {
|
|
336
|
-
await client.query(
|
|
337
|
-
'INSERT INTO applicability_rules (regulation, sector, subsector, applies, confidence, basis_article, notes) VALUES ($1, $2, $3, $4, $5, $6, $7)',
|
|
338
|
-
[rule.regulation, rule.sector, rule.subsector, rule.applies, rule.confidence, rule.basis_article, rule.notes]
|
|
339
|
-
);
|
|
340
|
-
stats.applicability_rules++;
|
|
341
|
-
}
|
|
342
|
-
console.log(` ✓ Copied ${stats.applicability_rules} applicability rules`);
|
|
343
|
-
|
|
344
|
-
return stats;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
async function createIndexes(client: pg.PoolClient) {
|
|
348
|
-
await client.query('CREATE INDEX IF NOT EXISTS idx_articles_regulation ON articles(regulation)');
|
|
349
|
-
await client.query('CREATE INDEX IF NOT EXISTS idx_recitals_regulation ON recitals(regulation)');
|
|
350
|
-
await client.query('CREATE INDEX IF NOT EXISTS idx_definitions_regulation ON definitions(regulation)');
|
|
351
|
-
await client.query('CREATE INDEX IF NOT EXISTS idx_definitions_term ON definitions(term)');
|
|
352
|
-
await client.query('CREATE INDEX IF NOT EXISTS idx_control_mappings_framework ON control_mappings(framework, regulation)');
|
|
353
|
-
await client.query('CREATE INDEX IF NOT EXISTS idx_applicability_sector ON applicability_rules(sector, regulation)');
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
async function createFullTextSearch(client: pg.PoolClient) {
|
|
357
|
-
// Update search_vector for articles
|
|
358
|
-
await client.query(`
|
|
359
|
-
UPDATE articles
|
|
360
|
-
SET search_vector = to_tsvector('english', coalesce(title, '') || ' ' || text)
|
|
361
|
-
`);
|
|
362
|
-
|
|
363
|
-
// Create GIN index for articles
|
|
364
|
-
await client.query('CREATE INDEX IF NOT EXISTS idx_articles_search ON articles USING gin(search_vector)');
|
|
365
|
-
|
|
366
|
-
// Update search_vector for recitals
|
|
367
|
-
await client.query(`
|
|
368
|
-
UPDATE recitals
|
|
369
|
-
SET search_vector = to_tsvector('english', text)
|
|
370
|
-
`);
|
|
371
|
-
|
|
372
|
-
// Create GIN index for recitals
|
|
373
|
-
await client.query('CREATE INDEX IF NOT EXISTS idx_recitals_search ON recitals USING gin(search_vector)');
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
async function verifyMigration(sqlite: Database.Database, pgPool: pg.Pool) {
|
|
377
|
-
console.log('Verifying migration...\n');
|
|
378
|
-
|
|
379
|
-
const sqliteCounts = {
|
|
380
|
-
regulations: sqlite.prepare('SELECT COUNT(*) as count FROM regulations').get().count,
|
|
381
|
-
articles: sqlite.prepare('SELECT COUNT(*) as count FROM articles').get().count,
|
|
382
|
-
recitals: sqlite.prepare('SELECT COUNT(*) as count FROM recitals').get().count,
|
|
383
|
-
definitions: sqlite.prepare('SELECT COUNT(*) as count FROM definitions').get().count,
|
|
384
|
-
control_mappings: sqlite.prepare('SELECT COUNT(*) as count FROM control_mappings').get().count,
|
|
385
|
-
applicability_rules: sqlite.prepare('SELECT COUNT(*) as count FROM applicability_rules').get().count
|
|
386
|
-
};
|
|
387
|
-
|
|
388
|
-
const pgCounts = {
|
|
389
|
-
regulations: (await pgPool.query('SELECT COUNT(*) as count FROM regulations')).rows[0].count,
|
|
390
|
-
articles: (await pgPool.query('SELECT COUNT(*) as count FROM articles')).rows[0].count,
|
|
391
|
-
recitals: (await pgPool.query('SELECT COUNT(*) as count FROM recitals')).rows[0].count,
|
|
392
|
-
definitions: (await pgPool.query('SELECT COUNT(*) as count FROM definitions')).rows[0].count,
|
|
393
|
-
control_mappings: (await pgPool.query('SELECT COUNT(*) as count FROM control_mappings')).rows[0].count,
|
|
394
|
-
applicability_rules: (await pgPool.query('SELECT COUNT(*) as count FROM applicability_rules')).rows[0].count
|
|
395
|
-
};
|
|
396
|
-
|
|
397
|
-
console.log('Record counts:');
|
|
398
|
-
console.log('Table | SQLite | PostgreSQL | Status');
|
|
399
|
-
console.log('----------------------|--------|------------|-------');
|
|
400
|
-
|
|
401
|
-
let allMatch = true;
|
|
402
|
-
for (const table of Object.keys(sqliteCounts)) {
|
|
403
|
-
const sqliteCount = sqliteCounts[table];
|
|
404
|
-
const pgCount = parseInt(pgCounts[table]);
|
|
405
|
-
const match = sqliteCount === pgCount;
|
|
406
|
-
allMatch = allMatch && match;
|
|
407
|
-
const status = match ? '✅' : '❌';
|
|
408
|
-
console.log(`${table.padEnd(21)} | ${String(sqliteCount).padStart(6)} | ${String(pgCount).padStart(10)} | ${status}`);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
console.log();
|
|
412
|
-
if (allMatch) {
|
|
413
|
-
console.log('✅ All tables match!\n');
|
|
414
|
-
} else {
|
|
415
|
-
console.log('❌ Some tables do not match\n');
|
|
416
|
-
process.exit(1);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// Test FTS
|
|
420
|
-
console.log('Testing full-text search...');
|
|
421
|
-
const ftsResult = await pgPool.query(`
|
|
422
|
-
SELECT regulation, article_number, title
|
|
423
|
-
FROM articles
|
|
424
|
-
WHERE search_vector @@ to_tsquery('english', 'incident')
|
|
425
|
-
LIMIT 3
|
|
426
|
-
`);
|
|
427
|
-
|
|
428
|
-
if (ftsResult.rows.length > 0) {
|
|
429
|
-
console.log(`✅ FTS working (found ${ftsResult.rows.length} results for "incident")\n`);
|
|
430
|
-
ftsResult.rows.forEach(row => {
|
|
431
|
-
console.log(` ${row.regulation} Article ${row.article_number}: ${row.title}`);
|
|
432
|
-
});
|
|
433
|
-
console.log();
|
|
434
|
-
} else {
|
|
435
|
-
console.log('❌ FTS not working\n');
|
|
436
|
-
process.exit(1);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
console.log('✅ Verification complete\n');
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
main().catch(error => {
|
|
443
|
-
console.error('Fatal error:', error);
|
|
444
|
-
process.exit(1);
|
|
445
|
-
});
|