bae705aa97
- Add NFC ePassport roadmap (ICAO 9303, eIDAS) - Add TensorFlow.js edge face detection (BlazeFace) - Add structured audit logger (GDPR-compliant) - Risk scoring support Part of KYC Apple Native UX v1.1.0
233 lines
7.7 KiB
JavaScript
233 lines
7.7 KiB
JavaScript
/**
|
|
* QUIXZOOM Test Data Export to PostgreSQL
|
|
*
|
|
* Exporterar simulerad stad till PostgreSQL med batching.
|
|
*/
|
|
|
|
const { Pool } = require('pg');
|
|
const SyntheticCity = require('./synthetic-city');
|
|
|
|
class PostgresExporter {
|
|
constructor(connectionConfig) {
|
|
this.pool = new Pool(connectionConfig);
|
|
this.batchSize = 1000;
|
|
}
|
|
|
|
async export(city) {
|
|
console.log('[EXPORT] Starting export to PostgreSQL...');
|
|
const startTime = Date.now();
|
|
|
|
try {
|
|
// 1. Zoomers
|
|
await this.exportZoomers(city.zoomers);
|
|
|
|
// 2. Objects
|
|
await this.exportObjects(city.objects);
|
|
|
|
// 3. Observations
|
|
await this.exportObservations(city.observations);
|
|
|
|
// 4. Evidence (subset)
|
|
await this.exportEvidence(city.observations.slice(0, 100000));
|
|
|
|
// 5. Events (subset)
|
|
await this.exportEvents(city.observations.slice(0, 50000));
|
|
|
|
const duration = Date.now() - startTime;
|
|
console.log(`[EXPORT] Complete in ${duration}ms`);
|
|
|
|
// Verifiera
|
|
await this.verify();
|
|
|
|
} catch (error) {
|
|
console.error('[EXPORT] Error:', error.message);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async exportZoomers(zoomers) {
|
|
console.log(`[EXPORT] Exporting ${zoomers.length} zoomers...`);
|
|
|
|
const columns = ['id', 'name', 'email', 'city', 'level', 'rating', 'missions_completed', 'joined_at', 'device', 'status'];
|
|
|
|
for (let i = 0; i < zoomers.length; i += this.batchSize) {
|
|
const batch = zoomers.slice(i, i + this.batchSize);
|
|
const values = batch.map((z, idx) => {
|
|
const offset = idx * columns.length;
|
|
return `(${columns.map((_, ci) => `$${offset + ci + 1}`).join(', ')})`;
|
|
}).join(', ');
|
|
|
|
const flatValues = batch.flatMap(z => [
|
|
z.id, z.name || `Zoomer ${z.id}`, z.email || `${z.id}@example.com`, z.city || 'Stockholm',
|
|
z.level, z.rating || 4.0, z.missionsCompleted || 0, new Date(),
|
|
JSON.stringify(z.device), 'active',
|
|
]);
|
|
|
|
await this.pool.query(`
|
|
INSERT INTO zoomers (${columns.join(', ')})
|
|
VALUES ${values}
|
|
ON CONFLICT (id) DO NOTHING
|
|
`, flatValues);
|
|
|
|
console.log(`[EXPORT] Zoomers: ${Math.min(i + this.batchSize, zoomers.length)}/${zoomers.length}`);
|
|
}
|
|
}
|
|
|
|
async exportObjects(objects) {
|
|
console.log(`[EXPORT] Exporting ${objects.length} objects...`);
|
|
|
|
for (let i = 0; i < objects.length; i += this.batchSize) {
|
|
const batch = objects.slice(i, i + this.batchSize);
|
|
const values = batch.map((o, idx) => {
|
|
const offset = idx * 10;
|
|
return `($${offset + 1}, $${offset + 2}, ST_SetSRID(ST_MakePoint($${offset + 3}, $${offset + 4}), 4326), $${offset + 5}, $${offset + 6}, $${offset + 7}, $${offset + 8}, $${offset + 9}, $${offset + 10}, $${offset + 11})`;
|
|
}).join(', ');
|
|
|
|
const flatValues = batch.flatMap(o => [
|
|
o.id, o.type, o.location.lng, o.location.lat, 15 + Math.random() * 35,
|
|
JSON.stringify(o.attributes), new Date(2023, 0, 1), new Date(),
|
|
0, 0.8 + Math.random() * 0.2, 'ok',
|
|
]);
|
|
|
|
await this.pool.query(`
|
|
INSERT INTO objects (id, type, location, altitude, attributes, first_seen, last_seen, observation_count, health, status)
|
|
VALUES ${values}
|
|
ON CONFLICT (id) DO NOTHING
|
|
`, flatValues);
|
|
|
|
if (i % 10000 === 0) {
|
|
console.log(`[EXPORT] Objects: ${Math.min(i + this.batchSize, objects.length)}/${objects.length}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
async exportObservations(observations) {
|
|
console.log(`[EXPORT] Exporting ${observations.length} observations...`);
|
|
|
|
for (let i = 0; i < observations.length; i += this.batchSize) {
|
|
const batch = observations.slice(i, i + this.batchSize);
|
|
const values = batch.map((o, idx) => {
|
|
const offset = idx * 10;
|
|
return `($${offset + 1}, $${offset + 2}, $${offset + 3}, ST_SetSRID(ST_MakePoint($${offset + 4}, $${offset + 5}), 4326), $${offset + 6}, $${offset + 7}, $${offset + 8}, $${offset + 9}, $${offset + 10}, $${offset + 11})`;
|
|
}).join(', ');
|
|
|
|
const flatValues = batch.flatMap(o => [
|
|
o.id, o.objectId, o.zoomerId || 'zoomer_001', o.location.lng, o.location.lat,
|
|
15 + Math.random() * 35, o.gpsAccuracy || 3, o.timestamp || new Date(),
|
|
JSON.stringify(o.attributes), JSON.stringify(o.quality || {}), o.weather || 'clear',
|
|
]);
|
|
|
|
await this.pool.query(`
|
|
INSERT INTO observations (id, object_id, zoomer_id, location, altitude, gps_accuracy, timestamp, attributes, quality, weather)
|
|
VALUES ${values}
|
|
ON CONFLICT (id) DO NOTHING
|
|
`, flatValues);
|
|
|
|
if (i % 50000 === 0) {
|
|
console.log(`[EXPORT] Observations: ${Math.min(i + this.batchSize, observations.length)}/${observations.length}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
async exportEvidence(observations) {
|
|
console.log(`[EXPORT] Exporting evidence for ${observations.length} observations...`);
|
|
|
|
for (let i = 0; i < observations.length; i += this.batchSize) {
|
|
const batch = observations.slice(i, i + this.batchSize);
|
|
const values = batch.map((o, idx) => {
|
|
const offset = idx * 7;
|
|
return `($${offset + 1}, $${offset + 2}, $${offset + 3}, $${offset + 4}, $${offset + 5}, $${offset + 6}, $${offset + 7})`;
|
|
}).join(', ');
|
|
|
|
const flatValues = batch.flatMap(o => [
|
|
`ev_${o.id}`, o.id, o.objectId, 'image', o.zoomerId || 'zoomer_001',
|
|
o.timestamp || new Date(), 0.5 + Math.random() * 0.5,
|
|
]);
|
|
|
|
await this.pool.query(`
|
|
INSERT INTO evidence (id, observation_id, object_id, type, source, timestamp, confidence)
|
|
VALUES ${values}
|
|
ON CONFLICT (id) DO NOTHING
|
|
`, flatValues);
|
|
|
|
if (i % 50000 === 0) {
|
|
console.log(`[EXPORT] Evidence: ${Math.min(i + this.batchSize, observations.length)}/${observations.length}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
async exportEvents(observations) {
|
|
console.log(`[EXPORT] Exporting events for ${observations.length} observations...`);
|
|
|
|
for (let i = 0; i < observations.length; i += this.batchSize) {
|
|
const batch = observations.slice(i, i + this.batchSize);
|
|
const values = batch.map((o, idx) => {
|
|
const offset = idx * 4;
|
|
return `($${offset + 1}, $${offset + 2}, $${offset + 3}, $${offset + 4})`;
|
|
}).join(', ');
|
|
|
|
const flatValues = batch.flatMap(o => [
|
|
`evt_${o.id}`, 'observation_created', o.timestamp || new Date(),
|
|
JSON.stringify({ observationId: o.id, objectId: o.objectId }),
|
|
]);
|
|
|
|
await this.pool.query(`
|
|
INSERT INTO events (id, type, timestamp, payload)
|
|
VALUES ${values}
|
|
ON CONFLICT (id) DO NOTHING
|
|
`, flatValues);
|
|
|
|
if (i % 50000 === 0) {
|
|
console.log(`[EXPORT] Events: ${Math.min(i + this.batchSize, observations.length)}/${observations.length}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
async verify() {
|
|
console.log('[EXPORT] Verifying...');
|
|
|
|
const tables = ['zoomers', 'objects', 'observations', 'evidence', 'events'];
|
|
for (const table of tables) {
|
|
const result = await this.pool.query(`SELECT COUNT(*) FROM ${table}`);
|
|
console.log(`[EXPORT] ${table}: ${result.rows[0].count} rows`);
|
|
}
|
|
}
|
|
|
|
async close() {
|
|
await this.pool.end();
|
|
}
|
|
}
|
|
|
|
// Exportera
|
|
module.exports = PostgresExporter;
|
|
|
|
// Demo
|
|
if (require.main === module) {
|
|
async function runExport() {
|
|
console.log('=== POSTGRES EXPORT ===\n');
|
|
|
|
// Generera stad
|
|
const city = new SyntheticCity('Stockholm', 5);
|
|
city.generate();
|
|
|
|
// Anslut till PostgreSQL
|
|
const exporter = new PostgresExporter({
|
|
host: 'localhost',
|
|
port: 5433,
|
|
database: 'quixzoom',
|
|
user: 'quixzoom',
|
|
password: 'quixzoom_secure_2026',
|
|
});
|
|
|
|
// Exportera
|
|
await exporter.export(city);
|
|
|
|
// Stäng
|
|
await exporter.close();
|
|
|
|
console.log('\nExport complete!');
|
|
}
|
|
|
|
runExport().catch(console.error);
|
|
}
|