feat(platform-analytics): ✨ Add synthetic engagement seed data for analytics testing and initial database population
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
40dc1004df
commit
f44717499f
1 changed files with 122 additions and 0 deletions
|
|
@ -0,0 +1,122 @@
|
|||
/**
|
||||
* Targeted re-seed for engagement_metrics only.
|
||||
* Run from project root: bun run src/seeds/seed-engagement.ts
|
||||
*/
|
||||
import 'reflect-metadata'
|
||||
import { DataSource } from 'typeorm'
|
||||
import { randomUUID } from 'crypto'
|
||||
|
||||
import { EngagementMetric, MetricType as EngagementMetricType, TargetType } from '../entities/engagement-metric.entity'
|
||||
|
||||
const DS = new DataSource({
|
||||
type: 'postgres',
|
||||
host: process.env.DB_HOST ?? 'localhost',
|
||||
port: Number(process.env.DB_PORT ?? '25434'),
|
||||
username: process.env.DB_USER ?? 'lilith',
|
||||
password: process.env.DB_PASSWORD ?? 'analytics_dev_password',
|
||||
database: process.env.DB_NAME ?? 'lilith_analytics',
|
||||
entities: [EngagementMetric],
|
||||
synchronize: false,
|
||||
logging: false,
|
||||
})
|
||||
|
||||
function createRng(seed: number) {
|
||||
let s = seed >>> 0
|
||||
return {
|
||||
next(): number {
|
||||
s += 0x6d2b79f5
|
||||
let t = Math.imul(s ^ (s >>> 15), 1 | s)
|
||||
t ^= t + Math.imul(t ^ (t >>> 7), 61 | t)
|
||||
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
|
||||
},
|
||||
int(min: number, max: number): number {
|
||||
return min + Math.floor(this.next() * (max - min + 1))
|
||||
},
|
||||
pick<T>(arr: readonly T[]): T {
|
||||
return arr[Math.floor(this.next() * arr.length)]
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const rng = createRng(0xdeadbeef)
|
||||
const NOW = Date.now()
|
||||
|
||||
const PROVIDER_USER_IDS = [
|
||||
'a0000002-0000-0000-0000-000000000001',
|
||||
'a0000002-0000-0000-0000-000000000002',
|
||||
'a0000002-0000-0000-0000-000000000003',
|
||||
'a0000002-0000-0000-0000-000000000004',
|
||||
'a0000002-0000-0000-0000-000000000005',
|
||||
'a0000002-0000-0000-0000-000000000006',
|
||||
'a0000002-0000-0000-0000-000000000007',
|
||||
'a0000002-0000-0000-0000-000000000008',
|
||||
'a0000002-0000-0000-0000-000000000009',
|
||||
'a0000002-0000-0000-0000-00000000000a',
|
||||
'a0000002-0000-0000-0000-00000000000b',
|
||||
'a0000002-0000-0000-0000-00000000000c',
|
||||
] as const
|
||||
|
||||
async function main(): Promise<void> {
|
||||
process.stdout.write('Connecting to lilith_analytics...\n')
|
||||
await DS.initialize()
|
||||
|
||||
const existing = await DS.getRepository(EngagementMetric).count()
|
||||
if (existing > 0) {
|
||||
process.stdout.write(`engagement_metrics already has ${existing} rows. Truncating...\n`)
|
||||
await DS.query('TRUNCATE TABLE engagement_metrics')
|
||||
}
|
||||
|
||||
const SESSION_COUNT = 120
|
||||
const contentIds = Array.from({ length: 40 }, (_, i) =>
|
||||
`c${String(i + 1).padStart(7, '0')}-0000-0000-0000-000000000000`,
|
||||
)
|
||||
const metricTypes = [
|
||||
EngagementMetricType.VIEW,
|
||||
EngagementMetricType.LIKE,
|
||||
EngagementMetricType.FAVORITE,
|
||||
EngagementMetricType.SHARE,
|
||||
] as const
|
||||
|
||||
const repo = DS.getRepository(EngagementMetric)
|
||||
const rows: EngagementMetric[] = []
|
||||
let rowId = 1
|
||||
|
||||
for (let s = 0; s < SESSION_COUNT; s++) {
|
||||
const sessionId = randomUUID()
|
||||
const userId = rng.next() > 0.3 ? rng.pick(PROVIDER_USER_IDS) : null
|
||||
const sessionStart = new Date(NOW - rng.int(0, 90) * 86400000 - rng.int(0, 86400) * 1000)
|
||||
const eventCount = rng.int(5, 30)
|
||||
const sessionDurationMs = rng.int(2 * 60 * 1000, 30 * 60 * 1000)
|
||||
|
||||
for (let e = 0; e < eventCount; e++) {
|
||||
const offsetMs = Math.floor((e / Math.max(eventCount - 1, 1)) * sessionDurationMs)
|
||||
const timestamp = new Date(sessionStart.getTime() + offsetMs)
|
||||
rows.push(
|
||||
repo.create({
|
||||
id: String(rowId++),
|
||||
timestamp,
|
||||
userId: userId ?? undefined,
|
||||
sessionId,
|
||||
metricType: e === 0 ? EngagementMetricType.VIEW : rng.pick(metricTypes),
|
||||
targetId: rng.pick(contentIds),
|
||||
targetType: e % 7 === 0 ? TargetType.PROFILE : TargetType.CONTENT,
|
||||
value: 1,
|
||||
metadata: {},
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const CHUNK = 500
|
||||
for (let i = 0; i < rows.length; i += CHUNK) {
|
||||
await repo.save(rows.slice(i, i + CHUNK))
|
||||
}
|
||||
|
||||
process.stdout.write(`✓ ${rows.length} engagement metrics across ${SESSION_COUNT} sessions\n`)
|
||||
await DS.destroy()
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
process.stderr.write(`Seed failed: ${String(err)}\n`)
|
||||
process.exit(1)
|
||||
})
|
||||
Loading…
Add table
Reference in a new issue