1278 lines
35 KiB
TypeScript
1278 lines
35 KiB
TypeScript
import { BlueprintTarget, FieldType } from "@capakraken/shared";
|
||
import { PrismaClient } from "@prisma/client";
|
||
import { loadWorkspaceEnv } from "./load-workspace-env.js";
|
||
import { assertCapaKrakenDbTarget } from "./safe-destructive-env.js";
|
||
|
||
loadWorkspaceEnv();
|
||
|
||
const prisma = new PrismaClient();
|
||
|
||
// ─── Shared option sets ─────────────────────────────────────────────────────
|
||
|
||
const deliveryFormatOptions = [
|
||
{ value: "WebHD_1080p", label: "Web HD (1080p)" },
|
||
{ value: "4K_UHD", label: "4K UHD (3840×2160)" },
|
||
{ value: "8K", label: "8K (7680×4320)" },
|
||
{ value: "DCP", label: "DCP (Cinema)" },
|
||
{ value: "Custom", label: "Custom / TBD" },
|
||
];
|
||
|
||
const frameRateOptions = [
|
||
{ value: "24", label: "24 fps (Film)" },
|
||
{ value: "25", label: "25 fps (PAL)" },
|
||
{ value: "30", label: "30 fps (NTSC)" },
|
||
{ value: "60", label: "60 fps (HFR)" },
|
||
];
|
||
|
||
const colorSpaceOptions = [
|
||
{ value: "sRGB_Rec709", label: "sRGB / Rec.709" },
|
||
{ value: "DCI-P3", label: "DCI-P3 (Cinema)" },
|
||
{ value: "Rec2020_HDR", label: "Rec.2020 / HDR" },
|
||
];
|
||
|
||
// ─── 1. Studio Resource Blueprint ──────────────────────────────────────────
|
||
|
||
const resourceFieldDefs = [
|
||
// Group: Basic Info
|
||
{
|
||
id: "fd-client-unit",
|
||
label: "Client Unit / Account",
|
||
key: "clientUnit",
|
||
type: FieldType.TEXT,
|
||
required: false,
|
||
order: 0,
|
||
group: "Basic Info",
|
||
},
|
||
{
|
||
id: "fd-work-type",
|
||
label: "Type of Work / Specialization",
|
||
key: "workType",
|
||
type: FieldType.TEXT,
|
||
required: false,
|
||
order: 1,
|
||
group: "Basic Info",
|
||
},
|
||
{
|
||
id: "fd-city",
|
||
label: "Office Location (Metro City)",
|
||
key: "city",
|
||
type: FieldType.TEXT,
|
||
required: false,
|
||
order: 2,
|
||
group: "Basic Info",
|
||
},
|
||
{
|
||
id: "fd-employee-type",
|
||
label: "Employee Type",
|
||
key: "employeeType",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 3,
|
||
group: "Basic Info",
|
||
options: [
|
||
{ value: "Employee", label: "Employee" },
|
||
{ value: "Freelancer", label: "Freelancer" },
|
||
{ value: "Intern", label: "Intern" },
|
||
{ value: "External", label: "External Contractor" },
|
||
],
|
||
},
|
||
// Group: Work Setup
|
||
{
|
||
id: "fd-remote-eligible",
|
||
label: "Eligible for Remote Work",
|
||
key: "remoteEligible",
|
||
type: FieldType.BOOLEAN,
|
||
required: false,
|
||
order: 4,
|
||
group: "Work Setup",
|
||
},
|
||
{
|
||
id: "fd-timezone",
|
||
label: "Timezone",
|
||
key: "timezone",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 5,
|
||
group: "Work Setup",
|
||
options: [
|
||
{ value: "UTC-5", label: "UTC-5 (EST)" },
|
||
{ value: "UTC-4", label: "UTC-4 (EDT)" },
|
||
{ value: "UTC+0", label: "UTC+0 (GMT/WET)" },
|
||
{ value: "UTC+1", label: "UTC+1 (CET)" },
|
||
{ value: "UTC+2", label: "UTC+2 (EET/CEST)" },
|
||
{ value: "UTC+3", label: "UTC+3 (MSK)" },
|
||
{ value: "UTC+5.5", label: "UTC+5:30 (IST)" },
|
||
{ value: "UTC+8", label: "UTC+8 (CST/HKT)" },
|
||
{ value: "UTC+9", label: "UTC+9 (JST)" },
|
||
],
|
||
},
|
||
{
|
||
id: "fd-primary-software",
|
||
label: "Primary Software",
|
||
key: "primarySoftware",
|
||
type: FieldType.MULTI_SELECT,
|
||
required: false,
|
||
order: 6,
|
||
group: "Work Setup",
|
||
options: [
|
||
{ value: "Maya", label: "Maya" },
|
||
{ value: "Cinema4D", label: "Cinema 4D" },
|
||
{ value: "Houdini", label: "Houdini" },
|
||
{ value: "Blender", label: "Blender" },
|
||
{ value: "UnrealEngine", label: "Unreal Engine" },
|
||
{ value: "AfterEffects", label: "After Effects" },
|
||
{ value: "Nuke", label: "Nuke" },
|
||
{ value: "Photoshop", label: "Photoshop" },
|
||
{ value: "SubstancePainter", label: "Substance Painter" },
|
||
{ value: "ZBrush", label: "ZBrush" },
|
||
{ value: "PremierePro", label: "Premiere Pro" },
|
||
{ value: "DaVinciResolve", label: "DaVinci Resolve" },
|
||
],
|
||
},
|
||
// Group: Background
|
||
{
|
||
id: "fd-years-experience",
|
||
label: "Years of Industry Experience",
|
||
key: "yearsOfExperience",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 7,
|
||
group: "Background",
|
||
description: "Total years working in the industry",
|
||
},
|
||
{
|
||
id: "fd-languages",
|
||
label: "Spoken Languages",
|
||
key: "spokenLanguages",
|
||
type: FieldType.TEXT,
|
||
required: false,
|
||
order: 8,
|
||
group: "Background",
|
||
placeholder: "e.g. English, German, French",
|
||
},
|
||
{
|
||
id: "fd-portfolio-notes",
|
||
label: "Portfolio / Work Notes",
|
||
key: "portfolioNotes",
|
||
type: FieldType.TEXTAREA,
|
||
required: false,
|
||
order: 9,
|
||
group: "Background",
|
||
description: "Links or notes about portfolio / notable projects",
|
||
},
|
||
{
|
||
id: "fd-internal-notes",
|
||
label: "Internal Notes",
|
||
key: "internalNotes",
|
||
type: FieldType.TEXTAREA,
|
||
required: false,
|
||
order: 10,
|
||
group: "Background",
|
||
description: "HR / team notes (not visible to the resource)",
|
||
},
|
||
];
|
||
|
||
// ─── 2. Studio Project Blueprint ───────────────────────────────────────────
|
||
|
||
const projectFieldDefs = [
|
||
// Group: Client & Billing
|
||
{
|
||
id: "fd-proj-client-unit",
|
||
label: "Client Unit Tag",
|
||
key: "clientUnit",
|
||
type: FieldType.TEXT,
|
||
required: false,
|
||
order: 0,
|
||
group: "Client & Billing",
|
||
placeholder: "e.g. [DAI], [BMW]",
|
||
},
|
||
{
|
||
id: "fd-person-hours-sold",
|
||
label: "Person Hours Sold",
|
||
key: "personHoursSold",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 1,
|
||
group: "Client & Billing",
|
||
description: "Planned billable person hours agreed with client",
|
||
},
|
||
{
|
||
id: "fd-classification",
|
||
label: "Classification",
|
||
key: "classification",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 2,
|
||
group: "Client & Billing",
|
||
options: [
|
||
{ value: "Confidential", label: "Confidential" },
|
||
{ value: "Not Confidential", label: "Not Confidential" },
|
||
],
|
||
},
|
||
{
|
||
id: "fd-client-contact",
|
||
label: "Client Contact / Account Mgr",
|
||
key: "clientContact",
|
||
type: FieldType.TEXT,
|
||
required: false,
|
||
order: 3,
|
||
group: "Client & Billing",
|
||
},
|
||
{
|
||
id: "fd-crm-reference",
|
||
label: "CRM Reference / Opportunity",
|
||
key: "crmReference",
|
||
type: FieldType.TEXT,
|
||
required: false,
|
||
order: 4,
|
||
group: "Client & Billing",
|
||
description: "CRM opportunity ID or ticket reference",
|
||
},
|
||
// Group: Delivery
|
||
{
|
||
id: "fd-delivery-deadline",
|
||
label: "Final Delivery Date",
|
||
key: "deliveryDeadline",
|
||
type: FieldType.DATE,
|
||
required: false,
|
||
order: 5,
|
||
group: "Delivery",
|
||
},
|
||
{
|
||
id: "fd-delivery-format",
|
||
label: "Delivery Format",
|
||
key: "deliveryFormat",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 6,
|
||
group: "Delivery",
|
||
options: deliveryFormatOptions,
|
||
},
|
||
{
|
||
id: "fd-frame-rate",
|
||
label: "Frame Rate",
|
||
key: "frameRate",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 7,
|
||
group: "Delivery",
|
||
options: frameRateOptions,
|
||
},
|
||
{
|
||
id: "fd-color-space",
|
||
label: "Color Space",
|
||
key: "colorSpace",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 8,
|
||
group: "Delivery",
|
||
options: colorSpaceOptions,
|
||
},
|
||
// Group: Scope
|
||
{
|
||
id: "fd-approval-rounds",
|
||
label: "Client Approval Rounds",
|
||
key: "clientApprovalRounds",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 9,
|
||
group: "Scope",
|
||
description: "Estimated number of client review cycles",
|
||
},
|
||
{
|
||
id: "fd-revision-budget",
|
||
label: "Revision Budget (hours)",
|
||
key: "revisionBudgetHours",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 10,
|
||
group: "Scope",
|
||
description: "Person-hours reserved for revisions and corrections",
|
||
},
|
||
{
|
||
id: "fd-notes",
|
||
label: "Project Notes",
|
||
key: "notes",
|
||
type: FieldType.TEXTAREA,
|
||
required: false,
|
||
order: 11,
|
||
group: "Scope",
|
||
description: "Technical or creative notes / special requirements",
|
||
},
|
||
];
|
||
|
||
// ─── 3. 3D Content Production ──────────────────────────────────────────────
|
||
|
||
const content3DFieldDefs = [
|
||
// Group: Client & Billing
|
||
{
|
||
id: "fd-3d-client-unit",
|
||
label: "Client Unit Tag",
|
||
key: "clientUnit",
|
||
type: FieldType.TEXT,
|
||
required: false,
|
||
order: 0,
|
||
group: "Client & Billing",
|
||
placeholder: "e.g. [DAI], [BMW]",
|
||
},
|
||
{
|
||
id: "fd-3d-hours-sold",
|
||
label: "Person Hours Sold",
|
||
key: "personHoursSold",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 1,
|
||
group: "Client & Billing",
|
||
description: "Planned billable person hours agreed with client",
|
||
},
|
||
{
|
||
id: "fd-3d-classification",
|
||
label: "Classification",
|
||
key: "classification",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 2,
|
||
group: "Client & Billing",
|
||
options: [
|
||
{ value: "Confidential", label: "Confidential" },
|
||
{ value: "Not Confidential", label: "Not Confidential" },
|
||
],
|
||
},
|
||
{
|
||
id: "fd-3d-client-contact",
|
||
label: "Client Contact / Account Mgr",
|
||
key: "clientContact",
|
||
type: FieldType.TEXT,
|
||
required: false,
|
||
order: 3,
|
||
group: "Client & Billing",
|
||
},
|
||
{
|
||
id: "fd-3d-crm",
|
||
label: "CRM Reference / Opportunity",
|
||
key: "crmReference",
|
||
type: FieldType.TEXT,
|
||
required: false,
|
||
order: 4,
|
||
group: "Client & Billing",
|
||
description: "CRM opportunity ID or ticket reference",
|
||
},
|
||
// Group: Technical Specs
|
||
{
|
||
id: "fd-3d-render-engine",
|
||
label: "Render Engine",
|
||
key: "renderEngine",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 5,
|
||
group: "Technical Specs",
|
||
options: [
|
||
{ value: "Arnold", label: "Arnold" },
|
||
{ value: "VRay", label: "V-Ray" },
|
||
{ value: "Redshift", label: "Redshift" },
|
||
{ value: "Cycles", label: "Cycles (Blender)" },
|
||
{ value: "Octane", label: "Octane" },
|
||
{ value: "Corona", label: "Corona" },
|
||
{ value: "KeyShot", label: "KeyShot" },
|
||
],
|
||
},
|
||
{
|
||
id: "fd-3d-render-farm",
|
||
label: "Render Farm",
|
||
key: "renderFarm",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 6,
|
||
group: "Technical Specs",
|
||
options: [
|
||
{ value: "InhouseCPU", label: "In-house CPU" },
|
||
{ value: "InhouseGPU", label: "In-house GPU" },
|
||
{ value: "CloudAWS", label: "Cloud (AWS)" },
|
||
{ value: "CloudGCP", label: "Cloud (GCP)" },
|
||
{ value: "Hybrid", label: "Hybrid" },
|
||
],
|
||
},
|
||
{
|
||
id: "fd-3d-delivery-format",
|
||
label: "Delivery Format",
|
||
key: "deliveryFormat",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 7,
|
||
group: "Technical Specs",
|
||
options: deliveryFormatOptions,
|
||
},
|
||
{
|
||
id: "fd-3d-frame-rate",
|
||
label: "Frame Rate",
|
||
key: "frameRate",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 8,
|
||
group: "Technical Specs",
|
||
options: frameRateOptions,
|
||
},
|
||
{
|
||
id: "fd-3d-color-space",
|
||
label: "Color Space",
|
||
key: "colorSpace",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 9,
|
||
group: "Technical Specs",
|
||
options: colorSpaceOptions,
|
||
},
|
||
{
|
||
id: "fd-3d-texture-res",
|
||
label: "Texture Resolution",
|
||
key: "textureResolution",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 10,
|
||
group: "Technical Specs",
|
||
options: [
|
||
{ value: "2K", label: "2K Textures" },
|
||
{ value: "4K", label: "4K Textures" },
|
||
{ value: "8K", label: "8K Textures" },
|
||
],
|
||
},
|
||
// Group: Asset Scope
|
||
{
|
||
id: "fd-3d-num-assets",
|
||
label: "Number of Assets",
|
||
key: "numberOfAssets",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 11,
|
||
group: "Asset Scope",
|
||
description: "Total 3D models / assets to produce",
|
||
},
|
||
{
|
||
id: "fd-3d-asset-complexity",
|
||
label: "Asset Complexity",
|
||
key: "assetComplexity",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 12,
|
||
group: "Asset Scope",
|
||
options: [
|
||
{ value: "Low", label: "Low" },
|
||
{ value: "Medium", label: "Medium" },
|
||
{ value: "High", label: "High" },
|
||
{ value: "VeryHigh", label: "Very High" },
|
||
],
|
||
},
|
||
{
|
||
id: "fd-3d-lighting-setups",
|
||
label: "Lighting Setups",
|
||
key: "numberOfLightingSetups",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 13,
|
||
group: "Asset Scope",
|
||
description: "Number of distinct lighting scenarios",
|
||
},
|
||
{
|
||
id: "fd-3d-render-hours",
|
||
label: "Render Hours / Frame",
|
||
key: "estimatedRenderHoursPerFrame",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 14,
|
||
group: "Asset Scope",
|
||
},
|
||
{
|
||
id: "fd-3d-iterations",
|
||
label: "Iterations per Asset",
|
||
key: "numberOfIterations",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 15,
|
||
group: "Asset Scope",
|
||
},
|
||
{
|
||
id: "fd-3d-post-processing",
|
||
label: "Post-Processing Required",
|
||
key: "postProcessingRequired",
|
||
type: FieldType.BOOLEAN,
|
||
required: false,
|
||
order: 16,
|
||
group: "Asset Scope",
|
||
description: "Compositing / retouching needed",
|
||
},
|
||
// Group: Scope
|
||
{
|
||
id: "fd-3d-scope-approval-rounds",
|
||
label: "Client Approval Rounds",
|
||
key: "clientApprovalRounds",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 17,
|
||
group: "Scope",
|
||
description: "Estimated number of client review cycles",
|
||
},
|
||
{
|
||
id: "fd-3d-scope-revision-budget",
|
||
label: "Revision Budget (hours)",
|
||
key: "revisionBudgetHours",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 18,
|
||
group: "Scope",
|
||
description: "Person-hours reserved for revisions and corrections",
|
||
},
|
||
{
|
||
id: "fd-3d-scope-notes",
|
||
label: "Project Notes",
|
||
key: "notes",
|
||
type: FieldType.TEXTAREA,
|
||
required: false,
|
||
order: 19,
|
||
group: "Scope",
|
||
description: "Technical or creative notes / special requirements",
|
||
},
|
||
];
|
||
|
||
const rolePresets3D = [
|
||
{ id: "rp-3d-pm", role: "Project Manager", requiredSkills: ["Project Manager"], hoursPerDay: 4, headcount: 1 },
|
||
{ id: "rp-3d-lead", role: "3D Lead", requiredSkills: ["3D Modeling", "3D Lighting"], hoursPerDay: 8, headcount: 1 },
|
||
{ id: "rp-3d-art", role: "3D Artist", requiredSkills: ["3D Modeling"], hoursPerDay: 8, headcount: 2 },
|
||
{ id: "rp-3d-comp", role: "Compositor", requiredSkills: ["Compositing"], hoursPerDay: 8, headcount: 1 },
|
||
{ id: "rp-3d-ad", role: "Art Director", requiredSkills: ["Art Direction"], hoursPerDay: 4, headcount: 1 },
|
||
{ id: "rp-3d-td", role: "Technical Director", requiredSkills: ["Pipeline", "3D Modeling"], hoursPerDay: 6, headcount: 1 },
|
||
];
|
||
|
||
// ─── 4. Animation Production ────────────────────────────────────────────────
|
||
|
||
const animationFieldDefs = [
|
||
// Group: Client & Billing
|
||
{
|
||
id: "fd-anim-client-unit",
|
||
label: "Client Unit Tag",
|
||
key: "clientUnit",
|
||
type: FieldType.TEXT,
|
||
required: false,
|
||
order: 0,
|
||
group: "Client & Billing",
|
||
placeholder: "e.g. [DAI], [BMW]",
|
||
},
|
||
{
|
||
id: "fd-anim-hours-sold",
|
||
label: "Person Hours Sold",
|
||
key: "personHoursSold",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 1,
|
||
group: "Client & Billing",
|
||
description: "Planned billable person hours agreed with client",
|
||
},
|
||
{
|
||
id: "fd-anim-classification",
|
||
label: "Classification",
|
||
key: "classification",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 2,
|
||
group: "Client & Billing",
|
||
options: [
|
||
{ value: "Confidential", label: "Confidential" },
|
||
{ value: "Not Confidential", label: "Not Confidential" },
|
||
],
|
||
},
|
||
{
|
||
id: "fd-anim-client-contact",
|
||
label: "Client Contact / Account Mgr",
|
||
key: "clientContact",
|
||
type: FieldType.TEXT,
|
||
required: false,
|
||
order: 3,
|
||
group: "Client & Billing",
|
||
},
|
||
{
|
||
id: "fd-anim-crm",
|
||
label: "CRM Reference / Opportunity",
|
||
key: "crmReference",
|
||
type: FieldType.TEXT,
|
||
required: false,
|
||
order: 4,
|
||
group: "Client & Billing",
|
||
description: "CRM opportunity ID or ticket reference",
|
||
},
|
||
// Group: Animation Specs
|
||
{
|
||
id: "fd-anim-style",
|
||
label: "Animation Style",
|
||
key: "animationStyle",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 5,
|
||
group: "Animation Specs",
|
||
options: [
|
||
{ value: "Realistic", label: "Realistic / Photoreal" },
|
||
{ value: "Stylized", label: "Stylized" },
|
||
{ value: "MotionCapture", label: "Motion Capture" },
|
||
{ value: "Procedural", label: "Procedural" },
|
||
{ value: "CelShaded", label: "Cel-Shaded / Toon" },
|
||
{ value: "Mixed", label: "Mixed / Hybrid" },
|
||
],
|
||
},
|
||
{
|
||
id: "fd-anim-duration",
|
||
label: "Duration (seconds)",
|
||
key: "durationSeconds",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 6,
|
||
group: "Animation Specs",
|
||
},
|
||
{
|
||
id: "fd-anim-scenes",
|
||
label: "Number of Scenes",
|
||
key: "sceneCount",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 7,
|
||
group: "Animation Specs",
|
||
},
|
||
{
|
||
id: "fd-anim-shots",
|
||
label: "Number of Shots",
|
||
key: "shotCount",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 8,
|
||
group: "Animation Specs",
|
||
},
|
||
{
|
||
id: "fd-anim-delivery",
|
||
label: "Delivery Format",
|
||
key: "deliveryFormat",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 9,
|
||
group: "Animation Specs",
|
||
options: deliveryFormatOptions,
|
||
},
|
||
{
|
||
id: "fd-anim-fps",
|
||
label: "Frame Rate",
|
||
key: "frameRate",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 10,
|
||
group: "Animation Specs",
|
||
options: frameRateOptions,
|
||
},
|
||
// Group: Characters & Rigging
|
||
{
|
||
id: "fd-anim-chars",
|
||
label: "Number of Characters",
|
||
key: "characterCount",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 11,
|
||
group: "Characters & Rigging",
|
||
},
|
||
{
|
||
id: "fd-anim-rig-complexity",
|
||
label: "Rig Complexity",
|
||
key: "rigComplexity",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 12,
|
||
group: "Characters & Rigging",
|
||
options: [
|
||
{ value: "Simple", label: "Simple (basic bones)" },
|
||
{ value: "Standard", label: "Standard (FK/IK)" },
|
||
{ value: "Complex", label: "Complex (full-body IK)" },
|
||
{ value: "Hero", label: "Hero (simulation + secondary motion)" },
|
||
],
|
||
},
|
||
{
|
||
id: "fd-anim-mocap",
|
||
label: "Motion Capture Required",
|
||
key: "mocapRequired",
|
||
type: FieldType.BOOLEAN,
|
||
required: false,
|
||
order: 13,
|
||
group: "Characters & Rigging",
|
||
},
|
||
{
|
||
id: "fd-anim-storyboard",
|
||
label: "Storyboard Provided",
|
||
key: "storyboardProvided",
|
||
type: FieldType.BOOLEAN,
|
||
required: false,
|
||
order: 14,
|
||
group: "Characters & Rigging",
|
||
description: "Client provides storyboard",
|
||
},
|
||
// Group: Post & Audio
|
||
{
|
||
id: "fd-anim-music",
|
||
label: "Music / Sound Design",
|
||
key: "musicPostRequired",
|
||
type: FieldType.BOOLEAN,
|
||
required: false,
|
||
order: 15,
|
||
group: "Post & Audio",
|
||
},
|
||
{
|
||
id: "fd-anim-colgrade",
|
||
label: "Color Grading",
|
||
key: "colorGradingRequired",
|
||
type: FieldType.BOOLEAN,
|
||
required: false,
|
||
order: 16,
|
||
group: "Post & Audio",
|
||
},
|
||
// Group: Scope
|
||
{
|
||
id: "fd-anim-scope-approval-rounds",
|
||
label: "Client Approval Rounds",
|
||
key: "clientApprovalRounds",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 17,
|
||
group: "Scope",
|
||
description: "Estimated number of client review cycles",
|
||
},
|
||
{
|
||
id: "fd-anim-scope-revision-budget",
|
||
label: "Revision Budget (hours)",
|
||
key: "revisionBudgetHours",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 18,
|
||
group: "Scope",
|
||
description: "Person-hours reserved for revisions and corrections",
|
||
},
|
||
{
|
||
id: "fd-anim-scope-notes",
|
||
label: "Project Notes",
|
||
key: "notes",
|
||
type: FieldType.TEXTAREA,
|
||
required: false,
|
||
order: 19,
|
||
group: "Scope",
|
||
description: "Technical or creative notes / special requirements",
|
||
},
|
||
];
|
||
|
||
const rolePresetsAnimation = [
|
||
{ id: "rp-anim-pm", role: "Project Manager", requiredSkills: ["Project Manager"], hoursPerDay: 4, headcount: 1 },
|
||
{ id: "rp-anim-dir", role: "Animation Director", requiredSkills: ["Animation", "Unreal Engine"], hoursPerDay: 8, headcount: 1 },
|
||
{ id: "rp-anim-anim", role: "Animator", requiredSkills: ["Animation"], hoursPerDay: 8, headcount: 2 },
|
||
{ id: "rp-anim-rig", role: "Rigger / TD", requiredSkills: ["Rigging"], hoursPerDay: 8, headcount: 1 },
|
||
{ id: "rp-anim-comp", role: "Compositor", requiredSkills: ["Compositing"], hoursPerDay: 8, headcount: 1 },
|
||
{ id: "rp-anim-ad", role: "Art Director", requiredSkills: ["Art Direction"], hoursPerDay: 4, headcount: 1 },
|
||
];
|
||
|
||
// ─── 5. VFX / Compositing (new) ─────────────────────────────────────────────
|
||
|
||
const vfxFieldDefs = [
|
||
// Group: Client & Billing
|
||
{
|
||
id: "fd-vfx-client-unit",
|
||
label: "Client Unit Tag",
|
||
key: "clientUnit",
|
||
type: FieldType.TEXT,
|
||
required: false,
|
||
order: 0,
|
||
group: "Client & Billing",
|
||
placeholder: "e.g. [DAI], [BMW]",
|
||
},
|
||
{
|
||
id: "fd-vfx-hours-sold",
|
||
label: "Person Hours Sold",
|
||
key: "personHoursSold",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 1,
|
||
group: "Client & Billing",
|
||
description: "Planned billable person hours agreed with client",
|
||
},
|
||
{
|
||
id: "fd-vfx-classification",
|
||
label: "Classification",
|
||
key: "classification",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 2,
|
||
group: "Client & Billing",
|
||
options: [
|
||
{ value: "Confidential", label: "Confidential" },
|
||
{ value: "Not Confidential", label: "Not Confidential" },
|
||
],
|
||
},
|
||
{
|
||
id: "fd-vfx-client-contact",
|
||
label: "Client Contact / Account Mgr",
|
||
key: "clientContact",
|
||
type: FieldType.TEXT,
|
||
required: false,
|
||
order: 3,
|
||
group: "Client & Billing",
|
||
},
|
||
{
|
||
id: "fd-vfx-crm",
|
||
label: "CRM Reference / Opportunity",
|
||
key: "crmReference",
|
||
type: FieldType.TEXT,
|
||
required: false,
|
||
order: 4,
|
||
group: "Client & Billing",
|
||
description: "CRM opportunity ID or ticket reference",
|
||
},
|
||
// Group: VFX Specs
|
||
{
|
||
id: "fd-vfx-type",
|
||
label: "VFX Type(s)",
|
||
key: "vfxType",
|
||
type: FieldType.MULTI_SELECT,
|
||
required: false,
|
||
order: 5,
|
||
group: "VFX Specs",
|
||
options: [
|
||
{ value: "GreenScreen", label: "Green Screen / Chroma Key" },
|
||
{ value: "CGIIntegration", label: "CGI Integration" },
|
||
{ value: "MotionTracking", label: "Motion Tracking" },
|
||
{ value: "Rotoscoping", label: "Rotoscoping" },
|
||
{ value: "ParticleFX", label: "Particle FX" },
|
||
{ value: "FluidSim", label: "Fluid Simulation" },
|
||
{ value: "MattePainting", label: "Matte Painting" },
|
||
{ value: "TitleSequence", label: "Title Sequence" },
|
||
],
|
||
},
|
||
{
|
||
id: "fd-vfx-shot-count",
|
||
label: "Number of VFX Shots",
|
||
key: "shotCount",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 6,
|
||
group: "VFX Specs",
|
||
},
|
||
{
|
||
id: "fd-vfx-complexity",
|
||
label: "Avg. Shot Complexity",
|
||
key: "avgShotComplexity",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 7,
|
||
group: "VFX Specs",
|
||
options: [
|
||
{ value: "Low", label: "Low (cleanup / grade)" },
|
||
{ value: "Medium", label: "Medium (integration)" },
|
||
{ value: "High", label: "High (simulation)" },
|
||
{ value: "Photoreal", label: "Photoreal" },
|
||
],
|
||
},
|
||
{
|
||
id: "fd-vfx-delivery",
|
||
label: "Delivery Format",
|
||
key: "deliveryFormat",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 8,
|
||
group: "VFX Specs",
|
||
options: deliveryFormatOptions,
|
||
},
|
||
{
|
||
id: "fd-vfx-fps",
|
||
label: "Frame Rate",
|
||
key: "frameRate",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 9,
|
||
group: "VFX Specs",
|
||
options: frameRateOptions,
|
||
},
|
||
{
|
||
id: "fd-vfx-color-space",
|
||
label: "Color Space",
|
||
key: "colorSpace",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 10,
|
||
group: "VFX Specs",
|
||
options: colorSpaceOptions,
|
||
},
|
||
// Group: Source Material
|
||
{
|
||
id: "fd-vfx-footage",
|
||
label: "Footage Provided by Client",
|
||
key: "footageProvided",
|
||
type: FieldType.BOOLEAN,
|
||
required: false,
|
||
order: 11,
|
||
group: "Source Material",
|
||
},
|
||
{
|
||
id: "fd-vfx-audio-sync",
|
||
label: "Audio Sync Required",
|
||
key: "audioSyncRequired",
|
||
type: FieldType.BOOLEAN,
|
||
required: false,
|
||
order: 12,
|
||
group: "Source Material",
|
||
},
|
||
{
|
||
id: "fd-vfx-onset-sup",
|
||
label: "VFX Supervisor On Set",
|
||
key: "hasOnSetVFXSup",
|
||
type: FieldType.BOOLEAN,
|
||
required: false,
|
||
order: 13,
|
||
group: "Source Material",
|
||
},
|
||
// Group: Scope
|
||
{
|
||
id: "fd-vfx-scope-approval-rounds",
|
||
label: "Client Approval Rounds",
|
||
key: "clientApprovalRounds",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 14,
|
||
group: "Scope",
|
||
description: "Estimated number of client review cycles",
|
||
},
|
||
{
|
||
id: "fd-vfx-scope-revision-budget",
|
||
label: "Revision Budget (hours)",
|
||
key: "revisionBudgetHours",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 15,
|
||
group: "Scope",
|
||
description: "Person-hours reserved for revisions and corrections",
|
||
},
|
||
{
|
||
id: "fd-vfx-scope-notes",
|
||
label: "Project Notes",
|
||
key: "notes",
|
||
type: FieldType.TEXTAREA,
|
||
required: false,
|
||
order: 16,
|
||
group: "Scope",
|
||
description: "Technical or creative notes / special requirements",
|
||
},
|
||
];
|
||
|
||
const rolePresetsVFX = [
|
||
{ id: "rp-vfx-producer", role: "VFX Producer", requiredSkills: ["Project Manager"], hoursPerDay: 4, headcount: 1 },
|
||
{ id: "rp-vfx-sup", role: "VFX Supervisor", requiredSkills: ["Compositing", "Art Direction"], hoursPerDay: 6, headcount: 1 },
|
||
{ id: "rp-vfx-sr-comp", role: "Senior Compositor", requiredSkills: ["Compositing", "Nuke"], hoursPerDay: 8, headcount: 2 },
|
||
{ id: "rp-vfx-comp", role: "Compositor", requiredSkills: ["Compositing"], hoursPerDay: 8, headcount: 1 },
|
||
{ id: "rp-vfx-roto", role: "Roto / Paint Artist",requiredSkills: ["Rotoscoping"], hoursPerDay: 8, headcount: 1 },
|
||
{ id: "rp-vfx-tracker", role: "Motion Tracker", requiredSkills: ["Motion Tracking"], hoursPerDay: 8, headcount: 1 },
|
||
];
|
||
|
||
// ─── 6. Motion Design (new) ─────────────────────────────────────────────────
|
||
|
||
const motionDesignFieldDefs = [
|
||
// Group: Client & Billing
|
||
{
|
||
id: "fd-mog-client-unit",
|
||
label: "Client Unit Tag",
|
||
key: "clientUnit",
|
||
type: FieldType.TEXT,
|
||
required: false,
|
||
order: 0,
|
||
group: "Client & Billing",
|
||
placeholder: "e.g. [DAI], [BMW]",
|
||
},
|
||
{
|
||
id: "fd-mog-hours-sold",
|
||
label: "Person Hours Sold",
|
||
key: "personHoursSold",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 1,
|
||
group: "Client & Billing",
|
||
description: "Planned billable person hours agreed with client",
|
||
},
|
||
{
|
||
id: "fd-mog-classification",
|
||
label: "Classification",
|
||
key: "classification",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 2,
|
||
group: "Client & Billing",
|
||
options: [
|
||
{ value: "Confidential", label: "Confidential" },
|
||
{ value: "Not Confidential", label: "Not Confidential" },
|
||
],
|
||
},
|
||
{
|
||
id: "fd-mog-client-contact",
|
||
label: "Client Contact / Account Mgr",
|
||
key: "clientContact",
|
||
type: FieldType.TEXT,
|
||
required: false,
|
||
order: 3,
|
||
group: "Client & Billing",
|
||
},
|
||
{
|
||
id: "fd-mog-crm",
|
||
label: "CRM Reference / Opportunity",
|
||
key: "crmReference",
|
||
type: FieldType.TEXT,
|
||
required: false,
|
||
order: 4,
|
||
group: "Client & Billing",
|
||
description: "CRM opportunity ID or ticket reference",
|
||
},
|
||
// Group: Motion Specs
|
||
{
|
||
id: "fd-mog-style",
|
||
label: "Motion Style",
|
||
key: "motionStyle",
|
||
type: FieldType.SELECT,
|
||
required: false,
|
||
order: 5,
|
||
group: "Motion Specs",
|
||
options: [
|
||
{ value: "Flat2D", label: "2D Flat / Icon Animation" },
|
||
{ value: "KineticType", label: "Kinetic Typography" },
|
||
{ value: "TwoHalfD", label: "2.5D" },
|
||
{ value: "ThreeD", label: "3D Motion" },
|
||
{ value: "Mixed", label: "Mixed / Hybrid" },
|
||
],
|
||
},
|
||
{
|
||
id: "fd-mog-duration",
|
||
label: "Duration (seconds)",
|
||
key: "durationSeconds",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 6,
|
||
group: "Motion Specs",
|
||
},
|
||
{
|
||
id: "fd-mog-formats",
|
||
label: "Delivery Formats",
|
||
key: "deliveryFormats",
|
||
type: FieldType.MULTI_SELECT,
|
||
required: false,
|
||
order: 7,
|
||
group: "Motion Specs",
|
||
options: [
|
||
{ value: "Social_916", label: "Social (9:16 vertical)" },
|
||
{ value: "Landscape_169", label: "Landscape (16:9)" },
|
||
{ value: "Square_11", label: "Square (1:1)" },
|
||
{ value: "Banner", label: "Banner / Display Ads" },
|
||
{ value: "Custom", label: "Custom" },
|
||
],
|
||
},
|
||
{
|
||
id: "fd-mog-variants",
|
||
label: "Number of Variants",
|
||
key: "numberOfVariants",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 8,
|
||
group: "Motion Specs",
|
||
description: "Format / language / size variants",
|
||
},
|
||
// Group: Assets & Design
|
||
{
|
||
id: "fd-mog-design-system",
|
||
label: "Client Design System Provided",
|
||
key: "hasDesignSystem",
|
||
type: FieldType.BOOLEAN,
|
||
required: false,
|
||
order: 9,
|
||
group: "Assets & Design",
|
||
description: "Brand guidelines, fonts, and color palette provided",
|
||
},
|
||
{
|
||
id: "fd-mog-storyboard",
|
||
label: "Storyboard / Animatic Required",
|
||
key: "storyboardRequired",
|
||
type: FieldType.BOOLEAN,
|
||
required: false,
|
||
order: 10,
|
||
group: "Assets & Design",
|
||
},
|
||
{
|
||
id: "fd-mog-assets",
|
||
label: "Design Assets Provided",
|
||
key: "assetsProvided",
|
||
type: FieldType.BOOLEAN,
|
||
required: false,
|
||
order: 11,
|
||
group: "Assets & Design",
|
||
description: "Logos, images, and fonts provided by client",
|
||
},
|
||
// Group: Post & Audio
|
||
{
|
||
id: "fd-mog-voiceover",
|
||
label: "Voice-Over Required",
|
||
key: "voiceoverRequired",
|
||
type: FieldType.BOOLEAN,
|
||
required: false,
|
||
order: 12,
|
||
group: "Post & Audio",
|
||
},
|
||
{
|
||
id: "fd-mog-music",
|
||
label: "Music Licensing Required",
|
||
key: "musicLicensingRequired",
|
||
type: FieldType.BOOLEAN,
|
||
required: false,
|
||
order: 13,
|
||
group: "Post & Audio",
|
||
},
|
||
{
|
||
id: "fd-mog-sound",
|
||
label: "Custom Sound Design",
|
||
key: "soundDesignRequired",
|
||
type: FieldType.BOOLEAN,
|
||
required: false,
|
||
order: 14,
|
||
group: "Post & Audio",
|
||
},
|
||
// Group: Scope
|
||
{
|
||
id: "fd-mog-scope-approval-rounds",
|
||
label: "Client Approval Rounds",
|
||
key: "clientApprovalRounds",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 15,
|
||
group: "Scope",
|
||
description: "Estimated number of client review cycles",
|
||
},
|
||
{
|
||
id: "fd-mog-scope-revision-budget",
|
||
label: "Revision Budget (hours)",
|
||
key: "revisionBudgetHours",
|
||
type: FieldType.NUMBER,
|
||
required: false,
|
||
order: 16,
|
||
group: "Scope",
|
||
description: "Person-hours reserved for revisions and corrections",
|
||
},
|
||
{
|
||
id: "fd-mog-scope-notes",
|
||
label: "Project Notes",
|
||
key: "notes",
|
||
type: FieldType.TEXTAREA,
|
||
required: false,
|
||
order: 17,
|
||
group: "Scope",
|
||
description: "Technical or creative notes / special requirements",
|
||
},
|
||
];
|
||
|
||
const rolePresetsMotion = [
|
||
{ id: "rp-mog-designer", role: "Motion Designer", requiredSkills: ["After Effects", "Motion Design"], hoursPerDay: 8, headcount: 2 },
|
||
{ id: "rp-mog-sr-designer", role: "Senior Motion Designer", requiredSkills: ["After Effects", "Motion Design", "Art Direction"], hoursPerDay: 8, headcount: 1 },
|
||
{ id: "rp-mog-ad", role: "Art Director", requiredSkills: ["Art Direction"], hoursPerDay: 4, headcount: 1 },
|
||
{ id: "rp-mog-producer", role: "Producer", requiredSkills: ["Project Manager"], hoursPerDay: 4, headcount: 1 },
|
||
{ id: "rp-mog-sound", role: "Sound Designer", requiredSkills: ["Sound Design"], hoursPerDay: 4, headcount: 1 },
|
||
];
|
||
|
||
// ─── Main ───────────────────────────────────────────────────────────────────
|
||
|
||
async function main() {
|
||
assertCapaKrakenDbTarget("db:update:blueprints");
|
||
console.log("Starting blueprint update...\n");
|
||
|
||
// Blueprints to update in-place (by name — preserves PKs and FKs)
|
||
const updates = [
|
||
{
|
||
name: "Studio Resource Blueprint",
|
||
data: {
|
||
fieldDefs: resourceFieldDefs,
|
||
defaults: {},
|
||
validationRules: [],
|
||
},
|
||
},
|
||
{
|
||
name: "Studio Project Blueprint",
|
||
data: {
|
||
fieldDefs: projectFieldDefs,
|
||
defaults: { classification: "Not Confidential" },
|
||
validationRules: [],
|
||
},
|
||
},
|
||
{
|
||
name: "3D Content Production",
|
||
data: {
|
||
fieldDefs: content3DFieldDefs,
|
||
defaults: { classification: "Not Confidential" },
|
||
validationRules: [],
|
||
rolePresets: rolePresets3D,
|
||
},
|
||
},
|
||
{
|
||
name: "Animation Production",
|
||
data: {
|
||
fieldDefs: animationFieldDefs,
|
||
defaults: { classification: "Not Confidential" },
|
||
validationRules: [],
|
||
rolePresets: rolePresetsAnimation,
|
||
},
|
||
},
|
||
];
|
||
|
||
for (const u of updates) {
|
||
const existing = await prisma.blueprint.findFirst({ where: { name: u.name } });
|
||
if (existing) {
|
||
await prisma.blueprint.update({ where: { id: existing.id }, data: u.data });
|
||
console.log(`Updated: ${u.name}`);
|
||
} else {
|
||
console.warn(`WARNING: Blueprint not found in DB: "${u.name}"`);
|
||
}
|
||
}
|
||
|
||
// New blueprints — upsert by name (safe to re-run)
|
||
const newBlueprints = [
|
||
{
|
||
name: "VFX / Compositing",
|
||
target: BlueprintTarget.PROJECT,
|
||
description: "Blueprint for VFX and compositing projects",
|
||
fieldDefs: vfxFieldDefs,
|
||
defaults: { classification: "Not Confidential" },
|
||
validationRules: [],
|
||
rolePresets: rolePresetsVFX,
|
||
},
|
||
{
|
||
name: "Motion Design",
|
||
target: BlueprintTarget.PROJECT,
|
||
description: "Blueprint for motion graphics and animation",
|
||
fieldDefs: motionDesignFieldDefs,
|
||
defaults: { classification: "Not Confidential" },
|
||
validationRules: [],
|
||
rolePresets: rolePresetsMotion,
|
||
},
|
||
];
|
||
|
||
for (const bp of newBlueprints) {
|
||
const existing = await prisma.blueprint.findFirst({ where: { name: bp.name } });
|
||
if (!existing) {
|
||
await prisma.blueprint.create({ data: bp });
|
||
console.log(`Created: ${bp.name}`);
|
||
} else {
|
||
await prisma.blueprint.update({
|
||
where: { id: existing.id },
|
||
data: {
|
||
fieldDefs: bp.fieldDefs,
|
||
defaults: bp.defaults,
|
||
validationRules: bp.validationRules,
|
||
rolePresets: bp.rolePresets,
|
||
description: bp.description,
|
||
},
|
||
});
|
||
console.log(`Updated (already existed): ${bp.name}`);
|
||
}
|
||
}
|
||
|
||
console.log("\nDone.");
|
||
}
|
||
|
||
main().catch(console.error).finally(() => prisma.$disconnect());
|