/** * historical-reset.mjs * Run with: node --env-file=.env scripts/historical-reset.mjs * * 1. Deletes all tasks + assignments * 2. Deletes all policy groups (unlinks policies) * 3. Runs recommendation engine per Shape client → creates groups * 4. Generates tasks from templates (POLICY, RENEWAL_GROUP/BOTH, CLIENT) * 5. Assigns tasks to existing claimsAdvocateId */ import { PrismaClient } from '@prisma/client' import { PrismaPg } from '@prisma/adapter-pg' import pg from 'pg' import { readFileSync } from 'fs' import { resolve, dirname } from 'path' import { fileURLToPath } from 'url' // Load .env manually since --env-file may not work with all node versions const __dirname = dirname(fileURLToPath(import.meta.url)) const envPath = resolve(__dirname, '../.env') try { const envFile = readFileSync(envPath, 'utf8') for (const line of envFile.split('\n')) { const match = line.match(/^([^#=]+)=(.*)$/) if (match) process.env[match[1].trim()] = match[2].trim().replace(/^["']|["']$/g, '') } } catch {} const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL }) const adapter = new PrismaPg(pool) const prisma = new PrismaClient({ adapter }) const WINDOW_DAYS = 30 const RULE = 'nearest-to-year-start' // ── Recommendation engine (inline, mirrors renewal-group-recommendations.ts) ─ function addDays(date, days) { const d = new Date(date) d.setDate(d.getDate() + days) return d } function dayOfYear(date) { const start = new Date(date.getFullYear(), 0, 0) return Math.floor((date - start) / 86400000) } function computeRenewalDate(policies, rule) { const dates = policies.map((p) => p.expirationDate) let picked if (rule === 'earliest') { picked = dates.reduce((a, b) => (a < b ? a : b)) } else if (rule === 'latest') { picked = dates.reduce((a, b) => (a > b ? a : b)) } else { picked = dates.reduce((a, b) => (dayOfYear(a) <= dayOfYear(b) ? a : b)) } return addDays(picked, 1) } function recommendGroups(policies, { windowDays, rule }) { const groupable = policies .filter((p) => p.expirationDate) .sort((a, b) => a.expirationDate - b.expirationDate) const ungroupable = policies.filter((p) => !p.expirationDate) if (groupable.length === 0) return { groups: [], ungroupable } const clusters = [] let current = [groupable[0]] let windowStart = groupable[0].expirationDate for (let i = 1; i < groupable.length; i++) { const diffDays = (groupable[i].expirationDate - windowStart) / 86400000 if (diffDays <= windowDays) { current.push(groupable[i]) } else { clusters.push(current) current = [groupable[i]] windowStart = groupable[i].expirationDate } } clusters.push(current) const groups = clusters.map((cluster) => ({ policies: cluster, proposedRenewalDate: computeRenewalDate(cluster, rule), })) return { groups, ungroupable } } // ── Main ───────────────────────────────────────────────────────────────────── async function main() { console.log('=== Historical Reset ===\n') // 1. Delete tasks + assignments const delAssign = await prisma.taskAssignment.deleteMany({}) const delTasks = await prisma.task.deleteMany({}) console.log(`✓ Deleted ${delAssign.count} assignments, ${delTasks.count} tasks`) // 2. Unlink policies from groups, then delete groups await prisma.policy.updateMany({ where: { policyGroupId: { not: null } }, data: { policyGroupId: null }, }) const delGroups = await prisma.policyGroup.deleteMany({}) console.log(`✓ Deleted ${delGroups.count} policy groups`) // 3. Reset setupCompletedAt on all clients await prisma.client.updateMany({ data: { setupCompletedAt: null, renewalDate: null } }) console.log('✓ Reset setupCompletedAt on all clients\n') // 4. Fetch Shape clients with active future policies const today = new Date() today.setHours(0, 0, 0, 0) const clients = await prisma.client.findMany({ where: { designation: { name: { in: ['Shape', 'Shape 2'] } }, }, select: { id: true, name: true, claimsAdvocateId: true, designationId: true, designation2Id: true, policies: { where: { expirationDate: { gte: today }, status: { notIn: ['Cancelled', 'Expired', 'Non-Renewed', 'Rewritten', 'Not taken'] }, }, select: { id: true, expirationDate: true }, }, }, }) console.log(`Processing ${clients.length} Shape clients...\n`) let groupsCreated = 0 let clientsSetup = 0 // 5. Re-run recommendations and save groups for (const client of clients) { if (client.policies.length === 0) continue const inputs = client.policies.map((p) => ({ policyId: p.id, expirationDate: new Date(p.expirationDate), })) const { groups } = recommendGroups(inputs, { windowDays: WINDOW_DAYS, rule: RULE }) if (groups.length === 0) continue await prisma.$transaction(async (tx) => { for (let i = 0; i < groups.length; i++) { const g = groups[i] const created = await tx.policyGroup.create({ data: { clientId: client.id, name: `Group ${i + 1}`, renewalDate: g.proposedRenewalDate, }, }) groupsCreated++ if (g.policies.length > 0) { await tx.policy.updateMany({ where: { id: { in: g.policies.map((p) => p.policyId) }, clientId: client.id }, data: { policyGroupId: created.id }, }) } } await tx.client.update({ where: { id: client.id }, data: { renewalDate: groups[0].proposedRenewalDate, setupCompletedAt: new Date(), }, }) }) clientsSetup++ } console.log(`✓ Created ${groupsCreated} groups across ${clientsSetup} clients\n`) // 6. Generate tasks ───────────────────────────────────────────────────────── let groupTasksCreated = 0 let policyTasksCreated = 0 let clientTasksCreated = 0 let errors = 0 // 6a. Group-level tasks const allGroups = await prisma.policyGroup.findMany({ where: { policies: { some: {} } }, include: { client: { select: { designationId: true, designation2Id: true, claimsAdvocateId: true } }, }, }) for (const group of allGroups) { try { const desIds = [group.client.designationId, group.client.designation2Id].filter(Boolean) const templates = await prisma.taskTemplate.findMany({ where: { isActive: true, level: { in: ['BOTH', 'RENEWAL_GROUP'] }, OR: [{ designationId: null }, ...(desIds.length ? [{ designationId: { in: desIds } }] : [])], }, }) if (templates.length === 0) continue const renewalDate = new Date(group.renewalDate) const tasksToCreate = templates.map((t) => { const dueDate = new Date(renewalDate) dueDate.setDate(dueDate.getDate() + t.daysOffset) return { title: t.name, description: t.description, department: t.department, timing: t.timing, daysOffset: t.daysOffset, dueDate, status: 'NOT_STARTED', priority: t.defaultPriority, clientId: group.clientId, policyGroupId: group.id, templateId: t.id, } }) const created = await prisma.task.createMany({ data: tasksToCreate, skipDuplicates: true }) groupTasksCreated += created.count if (group.client.claimsAdvocateId && created.count > 0) { const newTasks = await prisma.task.findMany({ where: { policyGroupId: group.id, templateId: { in: templates.map((t) => t.id) } }, select: { id: true }, }) await prisma.taskAssignment.createMany({ data: newTasks.map((t) => ({ taskId: t.id, userId: group.client.claimsAdvocateId })), skipDuplicates: true, }) } } catch (e) { console.error(`Group ${group.id}: ${e.message}`) errors++ } } // 6b. Policy-level tasks (ungrouped policies only) const ungroupedPolicies = await prisma.policy.findMany({ where: { policyGroupId: null, expirationDate: { gte: today }, status: { notIn: ['Cancelled', 'Expired', 'Non-Renewed', 'Rewritten', 'Not taken'] }, client: { designation: { name: { in: ['Shape', 'Shape 2'] } } }, }, select: { id: true, expirationDate: true, clientId: true, client: { select: { designationId: true, designation2Id: true, claimsAdvocateId: true } }, }, }) for (const policy of ungroupedPolicies) { try { const desIds = [policy.client.designationId, policy.client.designation2Id].filter(Boolean) const templates = await prisma.taskTemplate.findMany({ where: { isActive: true, level: { in: ['BOTH', 'POLICY'] }, OR: [{ designationId: null }, ...(desIds.length ? [{ designationId: { in: desIds } }] : [])], }, }) if (templates.length === 0) continue const anchorDate = new Date(policy.expirationDate) anchorDate.setDate(anchorDate.getDate() + 1) const tasksToCreate = templates.map((t) => { const dueDate = new Date(anchorDate) dueDate.setDate(dueDate.getDate() + t.daysOffset) return { title: t.name, description: t.description, department: t.department, timing: t.timing, daysOffset: t.daysOffset, dueDate, status: 'NOT_STARTED', priority: t.defaultPriority, clientId: policy.clientId, policyId: policy.id, templateId: t.id, } }) const created = await prisma.task.createMany({ data: tasksToCreate, skipDuplicates: true }) policyTasksCreated += created.count if (policy.client.claimsAdvocateId && created.count > 0) { const newTasks = await prisma.task.findMany({ where: { policyId: policy.id, templateId: { in: templates.map((t) => t.id) } }, select: { id: true }, }) await prisma.taskAssignment.createMany({ data: newTasks.map((t) => ({ taskId: t.id, userId: policy.client.claimsAdvocateId })), skipDuplicates: true, }) } } catch (e) { console.error(`Policy ${policy.id}: ${e.message}`) errors++ } } // 6c. CLIENT-level tasks (once per client) const allClientIds = [...new Set(allGroups.map((g) => g.clientId))] for (const clientId of allClientIds) { try { const clientRecord = await prisma.client.findUnique({ where: { id: clientId }, select: { designationId: true, designation2Id: true, claimsAdvocateId: true, policyGroups: { select: { renewalDate: true } }, policies: { where: { policyGroupId: null }, select: { expirationDate: true } }, }, }) if (!clientRecord) continue const desIds = [clientRecord.designationId, clientRecord.designation2Id].filter(Boolean) const clientTemplates = await prisma.taskTemplate.findMany({ where: { isActive: true, level: 'CLIENT', OR: [{ designationId: null }, ...(desIds.length ? [{ designationId: { in: desIds } }] : [])], }, }) if (clientTemplates.length === 0) continue const groupDates = clientRecord.policyGroups.map((g) => new Date(g.renewalDate).getTime()) const policyDates = clientRecord.policies.map((p) => { const d = new Date(p.expirationDate) d.setDate(d.getDate() + 1) return d.getTime() }) const allDates = [...groupDates, ...policyDates] if (allDates.length === 0) continue const anchorDate = new Date(Math.min(...allDates)) for (const template of clientTemplates) { const dueDate = new Date(anchorDate) dueDate.setDate(dueDate.getDate() + template.daysOffset) const created = await prisma.task.create({ data: { title: template.name, description: template.description, department: template.department, timing: template.timing, daysOffset: template.daysOffset, dueDate, status: 'NOT_STARTED', priority: template.defaultPriority, clientId, templateId: template.id, }, select: { id: true }, }) clientTasksCreated++ if (clientRecord.claimsAdvocateId) { await prisma.taskAssignment.create({ data: { taskId: created.id, userId: clientRecord.claimsAdvocateId }, }) } } } catch (e) { console.error(`Client ${clientId} (CLIENT tasks): ${e.message}`) errors++ } } console.log(`✓ Tasks created:`) console.log(` Group-level: ${groupTasksCreated}`) console.log(` Policy-level: ${policyTasksCreated}`) console.log(` Client-level: ${clientTasksCreated}`) console.log(` Total: ${groupTasksCreated + policyTasksCreated + clientTasksCreated}`) if (errors) console.log(` Errors: ${errors}`) console.log('\n=== Done ===') } main() .catch((e) => { console.error(e); process.exit(1) }) .finally(() => prisma.$disconnect())