dow-prod-tracker/prisma/seed.ts
Leivur Djurhuus e5b398d7da feat: Implement automation event bus and rule engine
- Add event bus for dispatching automation events with handlers.
- Create rule engine to evaluate events against defined triggers.
- Introduce chat provider to interface with Claude API and Ollama fallback.
- Define tool schemas for Claude-compatible operations.
- Implement tool executor to map tool calls to service layer functions.
- Develop automation service for CRUD operations on rules and event handling.
2026-03-12 11:20:21 -05:00

521 lines
20 KiB
TypeScript

import "dotenv/config";
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "../src/generated/prisma/client";
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL! });
const prisma = new PrismaClient({ adapter });
const STAGES = [
{
name: "Brief Intake",
slug: "brief-intake",
order: 1,
isCriticalGate: false,
isOptional: false,
description: "Receive and review the project brief from HP",
estimatedDays: 1,
},
{
name: "File Delivery",
slug: "file-delivery",
order: 2,
isCriticalGate: false,
isOptional: false,
description: "Receive source files (CAD, textures, reference materials)",
estimatedDays: 1,
},
{
name: "Model Prep",
slug: "model-prep",
order: 3,
isCriticalGate: true,
isOptional: false,
description: "Prepare 3D models for rendering pipeline",
estimatedDays: 5,
},
{
name: "Early Images",
slug: "early-images",
order: 4,
isCriticalGate: false,
isOptional: true,
description: "Optional early preview renders for client feedback",
estimatedDays: 1,
},
{
name: "Catalog Images",
slug: "catalog-images",
order: 5,
isCriticalGate: true,
isOptional: false,
description: "Standard catalog product imagery",
estimatedDays: 1,
},
{
name: "Hero Images",
slug: "hero-images",
order: 6,
isCriticalGate: false,
isOptional: false,
description: "High-impact hero product shots",
estimatedDays: 2,
},
{
name: "Packaging Images",
slug: "packaging-images",
order: 7,
isCriticalGate: false,
isOptional: false,
description: "Product packaging renders",
estimatedDays: 2,
},
{
name: "Photocomps",
slug: "photocomps",
order: 8,
isCriticalGate: false,
isOptional: false,
description: "Photo composite renders with lifestyle backgrounds",
estimatedDays: 3,
},
{
name: "360 Spin Animations",
slug: "360-spin-animations",
order: 9,
isCriticalGate: false,
isOptional: false,
description: "Interactive 360-degree product spin animations",
estimatedDays: 3.5,
},
{
name: "Dynamic Spin",
slug: "dynamic-spin",
order: 10,
isCriticalGate: false,
isOptional: false,
description: "Dynamic animated product spins with effects",
estimatedDays: 7,
},
];
// Dependencies: [stageSlug, prerequisiteSlug]
const DEPENDENCIES: [string, string][] = [
// File Delivery depends on Brief Intake
["file-delivery", "brief-intake"],
// Model Prep depends on Brief Intake and File Delivery
["model-prep", "brief-intake"],
["model-prep", "file-delivery"],
// Early Images depends on Model Prep (optional stage)
["early-images", "model-prep"],
// Catalog Images depends on Model Prep (critical gate)
["catalog-images", "model-prep"],
// Stages 6-10 all depend on Catalog Images (critical gate)
["hero-images", "catalog-images"],
["packaging-images", "catalog-images"],
["photocomps", "catalog-images"],
["360-spin-animations", "catalog-images"],
["dynamic-spin", "catalog-images"],
];
async function main() {
console.log("Seeding pipeline stage templates...");
// Upsert stages
const stageMap = new Map<string, string>();
for (const stage of STAGES) {
const created = await prisma.pipelineStageTemplate.upsert({
where: { slug: stage.slug },
update: stage,
create: stage,
});
stageMap.set(created.slug, created.id);
}
console.log(`Created/updated ${STAGES.length} pipeline stages`);
// Clear existing dependencies and recreate
await prisma.pipelineStageDependency.deleteMany();
for (const [stageSlug, prerequisiteSlug] of DEPENDENCIES) {
const stageId = stageMap.get(stageSlug)!;
const prerequisiteId = stageMap.get(prerequisiteSlug)!;
await prisma.pipelineStageDependency.create({
data: { stageId, prerequisiteId },
});
}
console.log(`Created ${DEPENDENCIES.length} stage dependencies`);
// Seed dev organization and user (for local development with DEV_BYPASS_AUTH)
const devOrg = await prisma.organization.upsert({
where: { id: "dev-org-001" },
update: {},
create: {
id: "dev-org-001",
name: "Dev Organization",
domain: "dev.localhost",
},
});
await prisma.user.upsert({
where: { id: "dev-user-001" },
update: { department: "CG Production", role: "ADMIN" },
create: {
id: "dev-user-001",
name: "Dev User",
email: "dev@localhost",
role: "ADMIN",
department: "CG Production",
organizationId: devOrg.id,
},
});
// ─── Phase 6: Seed Team Members ───────────────────────
console.log("Seeding team members...");
const TEAM_MEMBERS: {
id: string;
name: string;
email: string;
role: "ADMIN" | "PRODUCER" | "ARTIST";
department: string;
maxCapacity: number;
}[] = [
// Producers
{ id: "user-producer-001", name: "Sarah Chen", email: "sarah.chen@oliver.agency", role: "PRODUCER", department: "CG Production", maxCapacity: 8 },
{ id: "user-producer-002", name: "Marcus Johnson", email: "marcus.johnson@oliver.agency", role: "PRODUCER", department: "CG Production", maxCapacity: 8 },
// CGI Stills Artists
{ id: "user-artist-001", name: "Aditya Varma", email: "aditya.varma@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 6 },
{ id: "user-artist-002", name: "Ameya Bhagwat", email: "ameya.bhagwat@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 6 },
{ id: "user-artist-004", name: "Amit Sharma", email: "amit.sharma@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 5 },
{ id: "user-artist-006", name: "Ankit Kumar", email: "ankit.kumar@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 6 },
{ id: "user-artist-010", name: "Bharat Bhushan", email: "bharat.bhushan@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 6 },
{ id: "user-artist-011", name: "Eric Rodriguez", email: "eric.rodriguez@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 8 },
{ id: "user-artist-013", name: "Ishan Aneja", email: "ishan.aneja@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 5 },
{ id: "user-artist-014", name: "Jinesh Thacker", email: "jinesh.thacker@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 6 },
{ id: "user-artist-015", name: "Juan Garcia", email: "juan.garcia@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 6 },
{ id: "user-artist-016", name: "Krishna Nand", email: "krishna.nand@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 4 },
{ id: "user-artist-019", name: "Nizam P", email: "nizam.p@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 5 },
{ id: "user-artist-021", name: "Prateek Kaushik", email: "prateek.kaushik@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 8 },
{ id: "user-artist-025", name: "Xavier Plasso", email: "xavier.plasso@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 6 },
{ id: "user-artist-026", name: "Yash Vaidya", email: "yash.vaidya@oliver.agency", role: "ARTIST", department: "CGI Stills", maxCapacity: 4 },
// CGI Animation Artists
{ id: "user-artist-003", name: "Ameya Kandivkar", email: "ameya.kandivkar@oliver.agency", role: "ARTIST", department: "CGI Animation", maxCapacity: 5 },
{ id: "user-artist-007", name: "Ankit Kumar Gupta", email: "ankit.gupta@oliver.agency", role: "ARTIST", department: "CGI Animation", maxCapacity: 5 },
{ id: "user-artist-009", name: "Babon Ghosh", email: "babon.ghosh@oliver.agency", role: "ARTIST", department: "CGI Animation", maxCapacity: 5 },
{ id: "user-artist-012", name: "Hujef Bagwan", email: "hujef.bagwan@oliver.agency", role: "ARTIST", department: "CGI Animation", maxCapacity: 5 },
{ id: "user-artist-018", name: "Niteen Veer", email: "niteen.veer@oliver.agency", role: "ARTIST", department: "CGI Animation", maxCapacity: 5 },
{ id: "user-artist-020", name: "Pankaj Duragkar", email: "pankaj.duragkar@oliver.agency", role: "ARTIST", department: "CGI Animation", maxCapacity: 5 },
{ id: "user-artist-022", name: "Sandeep Sidhu", email: "sandeep.sidhu@oliver.agency", role: "ARTIST", department: "CGI Animation", maxCapacity: 8 },
{ id: "user-artist-024", name: "Sonu Kumar", email: "sonu.kumar@oliver.agency", role: "ARTIST", department: "CGI Animation", maxCapacity: 5 },
// Model Prep Artists
{ id: "user-artist-005", name: "Anantha Krishnan", email: "anantha.krishnan@oliver.agency", role: "ARTIST", department: "Model Prep", maxCapacity: 8 },
{ id: "user-artist-008", name: "Arun Prakash", email: "arun.prakash@oliver.agency", role: "ARTIST", department: "Model Prep", maxCapacity: 5 },
{ id: "user-artist-017", name: "Nijil Rajithan", email: "nijil.rajithan@oliver.agency", role: "ARTIST", department: "Model Prep", maxCapacity: 5 },
{ id: "user-artist-023", name: "Soham Baviskar", email: "soham.baviskar@oliver.agency", role: "ARTIST", department: "Model Prep", maxCapacity: 4 },
];
for (const member of TEAM_MEMBERS) {
await prisma.user.upsert({
where: { id: member.id },
update: { name: member.name, department: member.department, maxCapacity: member.maxCapacity, role: member.role },
create: {
id: member.id,
name: member.name,
email: member.email,
role: member.role,
department: member.department,
maxCapacity: member.maxCapacity,
organizationId: devOrg.id,
},
});
}
console.log(`Created/updated ${TEAM_MEMBERS.length} team members`);
console.log("Created dev organization and users");
// ─── Phase 6: Seed Skills ─────────────────────────────
console.log("Seeding skills...");
const SKILLS = [
"Modeling",
"UV Mapping",
"Texturing",
"Lighting",
"Rendering",
"Compositing",
"Retouching",
"Photography",
"Animation",
"Rigging",
];
const skillMap = new Map<string, string>();
for (const name of SKILLS) {
const skill = await prisma.skill.upsert({
where: { name },
update: {},
create: { name },
});
skillMap.set(skill.name, skill.id);
}
console.log(`Created/updated ${SKILLS.length} skills`);
// Map stage templates to recommended skills
// Format: [stageSlug, [skillName, importance (1-3)]]
const STAGE_SKILL_MAP: [string, [string, number][]][] = [
["brief-intake", []],
["file-delivery", []],
[
"model-prep",
[
["Modeling", 3],
["UV Mapping", 2],
["Texturing", 1],
],
],
[
"early-images",
[
["Lighting", 2],
["Rendering", 2],
["Compositing", 1],
],
],
[
"catalog-images",
[
["Lighting", 3],
["Rendering", 3],
["Compositing", 2],
["Retouching", 1],
],
],
[
"hero-images",
[
["Lighting", 3],
["Rendering", 3],
["Compositing", 3],
["Retouching", 2],
],
],
[
"packaging-images",
[
["Lighting", 2],
["Rendering", 2],
["Compositing", 2],
["Retouching", 2],
],
],
[
"photocomps",
[
["Compositing", 3],
["Retouching", 3],
["Photography", 2],
["Lighting", 1],
],
],
[
"360-spin-animations",
[
["Lighting", 2],
["Rendering", 3],
["Animation", 3],
],
],
[
"dynamic-spin",
[
["Animation", 3],
["Lighting", 2],
["Rendering", 3],
["Compositing", 2],
["Rigging", 1],
],
],
];
// Clear existing stage skill requirements
await prisma.stageSkillRequirement.deleteMany();
for (const [slug, skillReqs] of STAGE_SKILL_MAP) {
const templateId = stageMap.get(slug);
if (!templateId || skillReqs.length === 0) continue;
for (const [skillName, importance] of skillReqs) {
const skillId = skillMap.get(skillName);
if (!skillId) continue;
await prisma.stageSkillRequirement.create({
data: { stageTemplateId: templateId, skillId, importance },
});
}
}
const totalReqs = STAGE_SKILL_MAP.reduce((sum, [, reqs]) => sum + reqs.length, 0);
console.log(`Created ${totalReqs} stage skill requirements`);
// ─── Phase 6: Assign Skills to Team Members ───────────
console.log("Assigning skills to team members...");
// Clear existing user skills
await prisma.userSkill.deleteMany();
// [userId, [skillName, level]]
const USER_SKILLS: [string, [string, "JUNIOR" | "INTERMEDIATE" | "SENIOR" | "LEAD"][]][] = [
// ── CGI Stills ────────────────────────────────────────
// Aditya Varma — Senior, Photocomps specialty
["user-artist-001", [["Lighting", "SENIOR"], ["Rendering", "SENIOR"], ["Compositing", "SENIOR"], ["Retouching", "INTERMEDIATE"], ["Photography", "INTERMEDIATE"]]],
// Ameya Bhagwat — Senior
["user-artist-002", [["Lighting", "SENIOR"], ["Rendering", "SENIOR"], ["Compositing", "INTERMEDIATE"], ["Retouching", "JUNIOR"]]],
// Amit Sharma — Intermediate
["user-artist-004", [["Lighting", "INTERMEDIATE"], ["Rendering", "INTERMEDIATE"], ["Compositing", "JUNIOR"]]],
// Ankit Kumar — Senior
["user-artist-006", [["Lighting", "SENIOR"], ["Rendering", "SENIOR"], ["Compositing", "INTERMEDIATE"], ["Retouching", "JUNIOR"]]],
// Bharat Bhushan — Senior
["user-artist-010", [["Lighting", "SENIOR"], ["Rendering", "SENIOR"], ["Compositing", "INTERMEDIATE"], ["Retouching", "JUNIOR"]]],
// Eric Rodriguez — Stills Team Lead
["user-artist-011", [["Lighting", "LEAD"], ["Rendering", "SENIOR"], ["Compositing", "SENIOR"], ["Retouching", "INTERMEDIATE"]]],
// Ishan Aneja — Intermediate
["user-artist-013", [["Lighting", "INTERMEDIATE"], ["Rendering", "INTERMEDIATE"], ["Compositing", "JUNIOR"]]],
// Jinesh Thacker — Senior
["user-artist-014", [["Lighting", "SENIOR"], ["Rendering", "SENIOR"], ["Compositing", "INTERMEDIATE"], ["Retouching", "JUNIOR"]]],
// Juan Garcia — Senior
["user-artist-015", [["Lighting", "SENIOR"], ["Rendering", "SENIOR"], ["Compositing", "INTERMEDIATE"], ["Retouching", "JUNIOR"]]],
// Krishna Nand — Entry/Junior
["user-artist-016", [["Lighting", "JUNIOR"], ["Rendering", "JUNIOR"]]],
// Nizam P — Intermediate
["user-artist-019", [["Lighting", "INTERMEDIATE"], ["Rendering", "INTERMEDIATE"], ["Compositing", "JUNIOR"]]],
// Prateek Kaushik — Stills Team Lead
["user-artist-021", [["Lighting", "LEAD"], ["Rendering", "SENIOR"], ["Compositing", "SENIOR"], ["Retouching", "INTERMEDIATE"]]],
// Xavier Plasso — Senior
["user-artist-025", [["Lighting", "SENIOR"], ["Rendering", "SENIOR"], ["Compositing", "INTERMEDIATE"], ["Retouching", "JUNIOR"]]],
// Yash Vaidya — Entry/Junior
["user-artist-026", [["Lighting", "JUNIOR"], ["Rendering", "JUNIOR"]]],
// ── CGI Animation ─────────────────────────────────────
// Ameya Kandivkar — Intermediate
["user-artist-003", [["Animation", "INTERMEDIATE"], ["Rigging", "JUNIOR"], ["Rendering", "JUNIOR"]]],
// Ankit Kumar Gupta — Intermediate
["user-artist-007", [["Animation", "INTERMEDIATE"], ["Rigging", "JUNIOR"], ["Rendering", "JUNIOR"]]],
// Babon Ghosh — Intermediate
["user-artist-009", [["Animation", "INTERMEDIATE"], ["Rigging", "JUNIOR"], ["Rendering", "JUNIOR"]]],
// Hujef Bagwan — Intermediate
["user-artist-012", [["Animation", "INTERMEDIATE"], ["Rigging", "JUNIOR"], ["Rendering", "JUNIOR"]]],
// Niteen Veer — Intermediate
["user-artist-018", [["Animation", "INTERMEDIATE"], ["Rigging", "JUNIOR"], ["Rendering", "JUNIOR"]]],
// Pankaj Duragkar — Intermediate
["user-artist-020", [["Animation", "INTERMEDIATE"], ["Rigging", "JUNIOR"], ["Rendering", "JUNIOR"]]],
// Sandeep Sidhu — Animation Team Lead
["user-artist-022", [["Animation", "LEAD"], ["Rigging", "SENIOR"], ["Rendering", "INTERMEDIATE"]]],
// Sonu Kumar — Intermediate
["user-artist-024", [["Animation", "INTERMEDIATE"], ["Rigging", "JUNIOR"], ["Rendering", "JUNIOR"]]],
// ── Model Prep ────────────────────────────────────────
// Anantha Krishnan — Model Prep Team Lead
["user-artist-005", [["Modeling", "LEAD"], ["UV Mapping", "LEAD"], ["Texturing", "SENIOR"]]],
// Arun Prakash — Intermediate
["user-artist-008", [["Modeling", "INTERMEDIATE"], ["UV Mapping", "INTERMEDIATE"], ["Texturing", "JUNIOR"]]],
// Nijil Rajithan — Intermediate
["user-artist-017", [["Modeling", "INTERMEDIATE"], ["UV Mapping", "INTERMEDIATE"], ["Texturing", "JUNIOR"]]],
// Soham Baviskar — Entry/Junior
["user-artist-023", [["Modeling", "JUNIOR"], ["UV Mapping", "JUNIOR"]]],
];
let userSkillCount = 0;
for (const [userId, skills] of USER_SKILLS) {
for (const [skillName, level] of skills) {
const skillId = skillMap.get(skillName);
if (!skillId) continue;
await prisma.userSkill.create({
data: { userId, skillId, level },
});
userSkillCount++;
}
}
console.log(`Created ${userSkillCount} user skill assignments`);
// ─── Phase 6: Seed Sample Assignments ─────────────────
console.log("Seeding sample stage assignments...");
// Get active project stages to assign
const activeStages = await prisma.deliverableStage.findMany({
where: {
status: { in: ["IN_PROGRESS", "IN_REVIEW", "NOT_STARTED"] },
deliverable: { project: { organizationId: devOrg.id, status: "ACTIVE" } },
},
include: { template: true },
take: 60, // grab a decent sample
orderBy: { createdAt: "asc" },
});
// Map stages to appropriate artists by stage type
const stageToArtists: Record<string, string[]> = {
"model-prep": ["user-artist-005", "user-artist-008", "user-artist-017", "user-artist-023"],
"early-images": ["user-artist-002", "user-artist-006", "user-artist-010"],
"catalog-images": ["user-artist-011", "user-artist-021", "user-artist-014", "user-artist-015", "user-artist-002"],
"hero-images": ["user-artist-011", "user-artist-021", "user-artist-001", "user-artist-002"],
"packaging-images": ["user-artist-006", "user-artist-010", "user-artist-013", "user-artist-019"],
"photocomps": ["user-artist-001", "user-artist-015", "user-artist-014"],
"360-spin-animations": ["user-artist-022", "user-artist-003", "user-artist-009", "user-artist-024"],
"dynamic-spin": ["user-artist-022", "user-artist-020", "user-artist-018", "user-artist-007"],
};
// Clear existing assignments (to allow re-runs)
await prisma.stageAssignment.deleteMany();
let assignmentCount = 0;
const artistAssignmentCounts = new Map<string, number>();
for (const stage of activeStages) {
const candidates = stageToArtists[stage.template.slug];
if (!candidates || candidates.length === 0) continue;
// Pick the artist with the least assignments so far
let bestArtist = candidates[0];
let lowestCount = artistAssignmentCounts.get(bestArtist) || 0;
for (const candidate of candidates) {
const count = artistAssignmentCounts.get(candidate) || 0;
if (count < lowestCount) {
bestArtist = candidate;
lowestCount = count;
}
}
// Don't exceed maxCapacity (roughly)
const member = TEAM_MEMBERS.find((m) => m.id === bestArtist);
if (member && lowestCount >= member.maxCapacity) continue;
try {
await prisma.stageAssignment.create({
data: {
deliverableStageId: stage.id,
userId: bestArtist,
role: "LEAD",
},
});
artistAssignmentCounts.set(bestArtist, lowestCount + 1);
assignmentCount++;
} catch {
// Skip duplicate assignments
}
}
console.log(`Created ${assignmentCount} stage assignments`);
console.log("Seed complete!");
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});