2026-02-14 15:26:42 +01:00
import {
PrismaClient ,
UserRole ,
UserStatus ,
ProgramStatus ,
SettingType ,
SettingCategory ,
CompetitionCategory ,
OceanIssue ,
ProjectStatus ,
SubmissionSource ,
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
// Competition architecture enums
CompetitionStatus ,
RoundType ,
RoundStatus ,
CapMode ,
JuryGroupMemberRole ,
AdvancementRuleType ,
2026-02-14 15:26:42 +01:00
} from '@prisma/client'
import bcrypt from 'bcryptjs'
2026-02-15 23:30:01 +01:00
import { defaultRoundConfig } from '../src/types/competition-configs.js'
2026-02-14 15:26:42 +01:00
import { readFileSync } from 'fs'
import { parse } from 'csv-parse/sync'
import { join , dirname } from 'path'
import { fileURLToPath } from 'url'
const __filename = fileURLToPath ( import . meta . url )
const __dirname = dirname ( __filename )
const prisma = new PrismaClient ( )
// =============================================================================
// CSV Column Mapping Helpers
// =============================================================================
const categoryMap : Record < string , CompetitionCategory > = {
'the « Start-ups » category' : CompetitionCategory . STARTUP ,
'the « Business concepts » category' : CompetitionCategory . BUSINESS_CONCEPT ,
}
const issueMap : Record < string , OceanIssue > = {
'Reduction of pollution' : OceanIssue . POLLUTION_REDUCTION ,
'Mitigation of climate change' : OceanIssue . CLIMATE_MITIGATION ,
'Technology & innovations' : OceanIssue . TECHNOLOGY_INNOVATION ,
'Sustainable shipping' : OceanIssue . SUSTAINABLE_SHIPPING ,
'Blue Carbon' : OceanIssue . BLUE_CARBON ,
'Restoration of marine' : OceanIssue . HABITAT_RESTORATION ,
'Capacity building' : OceanIssue . COMMUNITY_CAPACITY ,
'Sustainable fishing' : OceanIssue . SUSTAINABLE_FISHING ,
'Consumer awareness' : OceanIssue . CONSUMER_AWARENESS ,
'Mitigation of ocean acidification' : OceanIssue . OCEAN_ACIDIFICATION ,
'Other' : OceanIssue . OTHER ,
}
2026-02-15 14:25:05 +01:00
function normalizeSpaces ( s : string ) : string {
// Replace non-breaking spaces (U+00A0) and other whitespace variants with regular spaces
return s . replace ( /\u00A0/g , ' ' )
}
2026-02-14 15:26:42 +01:00
function mapCategory ( raw : string | undefined ) : CompetitionCategory | null {
if ( ! raw ) return null
2026-02-15 14:25:05 +01:00
const trimmed = normalizeSpaces ( raw . trim ( ) )
2026-02-14 15:26:42 +01:00
for ( const [ prefix , value ] of Object . entries ( categoryMap ) ) {
if ( trimmed . startsWith ( prefix ) ) return value
}
return null
}
function mapIssue ( raw : string | undefined ) : OceanIssue | null {
if ( ! raw ) return null
2026-02-15 14:25:05 +01:00
const trimmed = normalizeSpaces ( raw . trim ( ) )
2026-02-14 15:26:42 +01:00
for ( const [ prefix , value ] of Object . entries ( issueMap ) ) {
if ( trimmed . startsWith ( prefix ) ) return value
}
return OceanIssue . OTHER
}
function parseFoundedDate ( raw : string | undefined ) : Date | null {
if ( ! raw ) return null
const trimmed = raw . trim ( )
if ( ! trimmed ) return null
const d = new Date ( trimmed )
return isNaN ( d . getTime ( ) ) ? null : d
}
2026-02-15 14:25:05 +01:00
function isEmptyRow ( row : Record < string , string > ) : boolean {
2026-02-14 15:26:42 +01:00
const name = ( row [ 'Full name' ] || '' ) . trim ( )
const email = ( row [ 'E-mail' ] || '' ) . trim ( )
2026-02-15 14:25:05 +01:00
const project = ( row [ "Project's name" ] || '' ) . trim ( )
return ! name && ! email && ! project
2026-02-14 15:26:42 +01:00
}
// =============================================================================
// Main Seed Function
// =============================================================================
async function main() {
console . log ( '🌱 Seeding database with MOPC 2026 real data...\n' )
// ==========================================================================
// 1. System Settings
// ==========================================================================
console . log ( '📋 Creating system settings...' )
const settings = [
{ key : 'ai_enabled' , value : 'false' , type : SettingType . BOOLEAN , category : SettingCategory.AI , description : 'Enable AI-powered jury assignment suggestions' } ,
{ key : 'ai_provider' , value : 'openai' , type : SettingType . STRING , category : SettingCategory.AI , description : 'AI provider for smart assignment (openai)' } ,
{ key : 'ai_model' , value : 'gpt-4o' , type : SettingType . STRING , category : SettingCategory.AI , description : 'OpenAI model to use for suggestions' } ,
{ key : 'ai_send_descriptions' , value : 'false' , type : SettingType . BOOLEAN , category : SettingCategory.AI , description : 'Send anonymized project descriptions to AI' } ,
{ key : 'platform_name' , value : 'Monaco Ocean Protection Challenge' , type : SettingType . STRING , category : SettingCategory.BRANDING , description : 'Platform display name' } ,
{ key : 'primary_color' , value : '#de0f1e' , type : SettingType . STRING , category : SettingCategory.BRANDING , description : 'Primary brand color (hex)' } ,
{ key : 'secondary_color' , value : '#053d57' , type : SettingType . STRING , category : SettingCategory.BRANDING , description : 'Secondary brand color (hex)' } ,
{ key : 'accent_color' , value : '#557f8c' , type : SettingType . STRING , category : SettingCategory.BRANDING , description : 'Accent color (hex)' } ,
{ key : 'session_duration_hours' , value : '24' , type : SettingType . NUMBER , category : SettingCategory.SECURITY , description : 'Session duration in hours' } ,
{ key : 'magic_link_expiry_minutes' , value : '15' , type : SettingType . NUMBER , category : SettingCategory.SECURITY , description : 'Magic link expiry time in minutes' } ,
{ key : 'rate_limit_requests_per_minute' , value : '60' , type : SettingType . NUMBER , category : SettingCategory.SECURITY , description : 'API rate limit per minute' } ,
{ key : 'storage_provider' , value : 's3' , type : SettingType . STRING , category : SettingCategory.STORAGE , description : 'Storage provider: s3 (MinIO) or local (filesystem)' } ,
{ key : 'local_storage_path' , value : './uploads' , type : SettingType . STRING , category : SettingCategory.STORAGE , description : 'Base path for local file storage' } ,
{ key : 'max_file_size_mb' , value : '500' , type : SettingType . NUMBER , category : SettingCategory.STORAGE , description : 'Maximum file upload size in MB' } ,
{ key : 'avatar_max_size_mb' , value : '5' , type : SettingType . NUMBER , category : SettingCategory.STORAGE , description : 'Maximum avatar image size in MB' } ,
{ key : 'allowed_file_types' , value : JSON.stringify ( [ 'application/pdf' , 'video/mp4' , 'video/quicktime' , 'image/png' , 'image/jpeg' ] ) , type : SettingType . JSON , category : SettingCategory.STORAGE , description : 'Allowed MIME types for file uploads' } ,
{ key : 'allowed_image_types' , value : JSON.stringify ( [ 'image/png' , 'image/jpeg' , 'image/webp' ] ) , type : SettingType . JSON , category : SettingCategory.STORAGE , description : 'Allowed MIME types for avatar/logo uploads' } ,
{ key : 'default_timezone' , value : 'Europe/Monaco' , type : SettingType . STRING , category : SettingCategory.DEFAULTS , description : 'Default timezone for date displays' } ,
{ key : 'default_page_size' , value : '20' , type : SettingType . NUMBER , category : SettingCategory.DEFAULTS , description : 'Default pagination size' } ,
{ key : 'autosave_interval_seconds' , value : '30' , type : SettingType . NUMBER , category : SettingCategory.DEFAULTS , description : 'Autosave interval for evaluation forms' } ,
{ key : 'whatsapp_enabled' , value : 'false' , type : SettingType . BOOLEAN , category : SettingCategory.WHATSAPP , description : 'Enable WhatsApp notifications' } ,
{ key : 'whatsapp_provider' , value : 'META' , type : SettingType . STRING , category : SettingCategory.WHATSAPP , description : 'WhatsApp provider (META or TWILIO)' } ,
{ key : 'openai_api_key' , value : '' , type : SettingType . SECRET , category : SettingCategory.AI , description : 'OpenAI API Key for AI-powered features' , isSecret : true } ,
]
for ( const setting of settings ) {
await prisma . systemSettings . upsert ( {
where : { key : setting.key } ,
update : { } ,
create : setting ,
} )
}
console . log ( ` Created ${ settings . length } settings ` )
// ==========================================================================
// 1b. Expertise Tags
// ==========================================================================
console . log ( '\n🏷️ Creating expertise tags...' )
const tagGroups = [
{
category : 'Pollution Reduction' ,
color : '#dc2626' ,
tags : [
{ name : 'Marine Plastic & Ghost Gear Cleanup' , description : 'Collection and processing of plastic waste, fishing nets, and marine debris from coastal and ocean environments' } ,
{ name : 'Industrial & Wastewater Marine Protection' , description : 'Systems reducing chemical discharge, nutrient runoff, and wastewater pollution before ocean impact' } ,
{ name : 'Circular Materials from Marine Waste' , description : 'Transformation of algae, fishery byproducts, and recovered ocean waste into useful products' } ,
] ,
} ,
{
category : 'Climate Mitigation' ,
color : '#0284c7' ,
tags : [
{ name : 'Low-Carbon Blue Supply Chains' , description : 'Solutions reducing emissions in seafood logistics, cooling, and marine value chains' } ,
{ name : 'Ocean Renewable Energy' , description : 'Wave, tidal, offshore, and hybrid marine energy technologies' } ,
{ name : 'Marine Carbon Removal & Sequestration' , description : 'Approaches that remove and store carbon through ocean-linked biological or mineral pathways' } ,
] ,
} ,
{
category : 'Technology & Innovation' ,
color : '#7c3aed' ,
tags : [
{ name : 'Marine Robotics & Autonomous Systems' , description : 'ROVs, AUVs, and marine drones used for restoration, monitoring, and intervention' } ,
{ name : 'AI Ocean Intelligence' , description : 'Machine learning and advanced analytics for ocean health, biodiversity, or operations optimization' } ,
{ name : 'Marine Ecotoxicology & Environmental Testing' , description : 'Testing platforms that evaluate product or discharge impacts on marine ecosystems' } ,
] ,
} ,
{
category : 'Sustainable Shipping' ,
color : '#053d57' ,
tags : [
{ name : 'Cleaner Maritime Operations' , description : 'Operational innovations that reduce emissions, waste, and fuel intensity in maritime transport' } ,
{ name : 'Port Environmental Performance' , description : 'Technologies and practices that improve sustainability outcomes in ports and harbors' } ,
{ name : 'Marine Noise & Vessel Impact Reduction' , description : 'Solutions that mitigate underwater noise and ecological disturbance from vessel activity' } ,
] ,
} ,
{
category : 'Blue Carbon' ,
color : '#0ea5a4' ,
tags : [
{ name : 'Seagrass & Mangrove Carbon Projects' , description : 'Restoration and protection programs for key blue carbon habitats' } ,
{ name : 'Blue Carbon Measurement & Verification' , description : 'Monitoring and MRV tools for quantifying carbon outcomes in marine ecosystems' } ,
{ name : 'Financing Blue Carbon Conservation' , description : 'Financial models enabling scalable protection and restoration of blue carbon assets' } ,
] ,
} ,
{
category : 'Habitat Restoration' ,
color : '#16a34a' ,
tags : [
{ name : 'Coral Restoration & Reef Resilience' , description : 'Propagation, outplanting, and resilience strategies for coral ecosystems' } ,
{ name : 'Coastal Habitat Regeneration' , description : 'Recovery of dunes, wetlands, estuaries, and nearshore biodiversity hotspots' } ,
{ name : 'Biodiversity Threat Mitigation' , description : 'Targeted interventions for invasive species, habitat degradation, and species decline' } ,
] ,
} ,
{
category : 'Community Capacity' ,
color : '#ea580c' ,
tags : [
{ name : 'Coastal Livelihood & Inclusion Models' , description : 'Community-led business models that improve income while protecting marine ecosystems' } ,
{ name : 'Women-Led Blue Economy Initiatives' , description : 'Programs that strengthen women leadership and participation in sustainable marine enterprises' } ,
{ name : 'Ocean Skills & Entrepreneurship Training' , description : 'Capacity-building and startup enablement for students and coastal entrepreneurs' } ,
] ,
} ,
{
category : 'Sustainable Fishing' ,
color : '#059669' ,
tags : [
{ name : 'Regenerative Aquaculture' , description : 'Aquaculture systems integrating ecological restoration, animal welfare, and reduced environmental pressure' } ,
{ name : 'Seaweed & Algae Value Chains' , description : 'Cultivation and commercialization of algae or seaweed for food, feed, and biomaterials' } ,
{ name : 'Cold Chain & Post-Harvest Seafood Efficiency' , description : 'Technologies reducing fish loss and waste through sustainable preservation and handling' } ,
] ,
} ,
{
category : 'Consumer Awareness' ,
color : '#f59e0b' ,
tags : [
{ name : 'Ocean Literacy Platforms' , description : 'Digital or physical tools that increase public understanding of ocean health issues' } ,
{ name : 'Behavior Change for Ocean Protection' , description : 'Campaigns and products that help consumers reduce harmful marine impact' } ,
{ name : 'Traceability & Sustainable Choice Tools' , description : 'Interfaces helping buyers identify responsible seafood and ocean-positive products' } ,
] ,
} ,
{
category : 'Ocean Acidification' ,
color : '#2563eb' ,
tags : [
{ name : 'Acidification Monitoring & Forecasting' , description : 'Sensors and models tracking pH dynamics and acidification risk in marine environments' } ,
{ name : 'Alkalinity & Buffering Interventions' , description : 'Interventions designed to reduce acidification pressure on vulnerable marine systems' } ,
{ name : 'Acidification-Resilient Aquaculture' , description : 'Farming approaches and species strategies resilient to changing ocean chemistry' } ,
] ,
} ,
] as const
const expertiseTags = tagGroups . flatMap ( ( group , groupIndex ) = >
group . tags . map ( ( tag , tagIndex ) = > ( {
name : tag.name ,
description : tag.description ,
category : group.category ,
color : group.color ,
sortOrder : groupIndex * 10 + tagIndex ,
} ) )
)
for ( const tag of expertiseTags ) {
await prisma . expertiseTag . upsert ( {
where : { name : tag.name } ,
update : {
description : tag.description ,
category : tag.category ,
color : tag.color ,
sortOrder : tag.sortOrder ,
isActive : true ,
} ,
create : {
name : tag.name ,
description : tag.description ,
category : tag.category ,
color : tag.color ,
sortOrder : tag.sortOrder ,
isActive : true ,
} ,
} )
}
console . log ( ` Created ${ expertiseTags . length } expertise tags across ${ new Set ( expertiseTags . map ( t = > t . category ) ) . size } categories ` )
// ==========================================================================
// 2. Admin/Staff Users
// ==========================================================================
console . log ( '\n👤 Creating admin & staff users...' )
const staffAccounts = [
{ email : 'matt@monaco-opc.com' , name : 'Matt' , role : UserRole.SUPER_ADMIN , password : '195260Mp!' } ,
{ email : 'admin@monaco-opc.com' , name : 'Admin' , role : UserRole.PROGRAM_ADMIN , password : 'Admin123!' } ,
{ email : 'awards@monaco-opc.com' , name : 'Award Director' , role : UserRole.AWARD_MASTER , password : 'Awards123!' } ,
]
const staffUsers : Record < string , string > = { }
for ( const account of staffAccounts ) {
const passwordHash = await bcrypt . hash ( account . password , 12 )
const isSuperAdmin = account . role === UserRole . SUPER_ADMIN
const user = await prisma . user . upsert ( {
where : { email : account.email } ,
update : isSuperAdmin
? {
status : UserStatus.ACTIVE ,
passwordHash ,
mustSetPassword : false ,
passwordSetAt : new Date ( ) ,
onboardingCompletedAt : new Date ( ) ,
}
: {
status : UserStatus.NONE ,
passwordHash : null ,
mustSetPassword : true ,
passwordSetAt : null ,
onboardingCompletedAt : null ,
inviteToken : null ,
inviteTokenExpiresAt : null ,
} ,
create : {
email : account.email ,
name : account.name ,
role : account.role ,
status : isSuperAdmin ? UserStatus.ACTIVE : UserStatus.NONE ,
passwordHash : isSuperAdmin ? passwordHash : null ,
mustSetPassword : ! isSuperAdmin ,
passwordSetAt : isSuperAdmin ? new Date ( ) : null ,
onboardingCompletedAt : isSuperAdmin ? new Date ( ) : null ,
} ,
} )
staffUsers [ account . email ] = user . id
console . log ( ` ✓ ${ account . role } : ${ account . email } ` )
}
// ==========================================================================
// 3. Jury Members (8 fictional)
// ==========================================================================
console . log ( '\n⚖️ Creating jury members...' )
const juryMembers = [
{ email : 'jury1@monaco-opc.com' , name : 'Dr. Sophie Laurent' , country : 'France' , tags : [ 'marine-biology' , 'coral-restoration' , 'biodiversity' ] } ,
{ email : 'jury2@monaco-opc.com' , name : 'Prof. Marco Bianchi' , country : 'Italy' , tags : [ 'ocean-engineering' , 'renewable-energy' , 'desalination' ] } ,
{ email : 'jury3@monaco-opc.com' , name : 'Dr. Aisha Patel' , country : 'United Kingdom' , tags : [ 'sustainability' , 'circular-economy' , 'waste-management' ] } ,
{ email : 'jury4@monaco-opc.com' , name : 'Dr. Kenji Tanaka' , country : 'Japan' , tags : [ 'aquaculture' , 'sustainable-fishing' , 'marine-technology' ] } ,
{ email : 'jury5@monaco-opc.com' , name : 'Prof. Elena Volkov' , country : 'Germany' , tags : [ 'climate-science' , 'ocean-acidification' , 'blue-carbon' ] } ,
{ email : 'jury6@monaco-opc.com' , name : 'Dr. Amara Diallo' , country : 'Senegal' , tags : [ 'community-development' , 'capacity-building' , 'coastal-management' ] } ,
{ email : 'jury7@monaco-opc.com' , name : 'Dr. Carlos Rivera' , country : 'Spain' , tags : [ 'blue-economy' , 'maritime-policy' , 'shipping' ] } ,
{ email : 'jury8@monaco-opc.com' , name : 'Prof. Lin Wei' , country : 'Singapore' , tags : [ 'marine-biotech' , 'pollution-monitoring' , 'AI-ocean' ] } ,
]
const juryUserIds : string [ ] = [ ]
for ( const j of juryMembers ) {
const user = await prisma . user . upsert ( {
where : { email : j.email } ,
update : {
status : UserStatus.NONE ,
} ,
create : {
email : j.email ,
name : j.name ,
role : UserRole.JURY_MEMBER ,
status : UserStatus.NONE ,
country : j.country ,
expertiseTags : j.tags ,
bio : ` Expert in ${ j . tags . join ( ', ' ) } ` ,
} ,
} )
juryUserIds . push ( user . id )
console . log ( ` ✓ Jury: ${ j . name } ( ${ j . country } ) ` )
}
// ==========================================================================
// 4. Mentors (3 fictional)
// ==========================================================================
console . log ( '\n🧑 🏫 Creating mentors...' )
const mentors = [
{ email : 'mentor1@monaco-opc.com' , name : 'Marie Dubois' , country : 'Monaco' , tags : [ 'startup-coaching' , 'ocean-conservation' ] } ,
{ email : 'mentor2@monaco-opc.com' , name : 'James Cooper' , country : 'United States' , tags : [ 'venture-capital' , 'cleantech' ] } ,
{ email : 'mentor3@monaco-opc.com' , name : 'Fatima Al-Rashid' , country : 'UAE' , tags : [ 'impact-investing' , 'sustainability-strategy' ] } ,
]
for ( const m of mentors ) {
await prisma . user . upsert ( {
where : { email : m.email } ,
update : {
status : UserStatus.NONE ,
} ,
create : {
email : m.email ,
name : m.name ,
role : UserRole.MENTOR ,
status : UserStatus.NONE ,
country : m.country ,
expertiseTags : m.tags ,
} ,
} )
console . log ( ` ✓ Mentor: ${ m . name } ` )
}
// ==========================================================================
// 5. Observers (2 fictional)
// ==========================================================================
console . log ( '\n👁️ Creating observers...' )
const observers = [
{ email : 'observer1@monaco-opc.com' , name : 'Pierre Martin' , country : 'Monaco' } ,
{ email : 'observer2@monaco-opc.com' , name : 'Sarah Chen' , country : 'Canada' } ,
]
for ( const o of observers ) {
await prisma . user . upsert ( {
where : { email : o.email } ,
update : {
status : UserStatus.NONE ,
} ,
create : {
email : o.email ,
name : o.name ,
role : UserRole.OBSERVER ,
status : UserStatus.NONE ,
country : o.country ,
} ,
} )
console . log ( ` ✓ Observer: ${ o . name } ` )
}
// ==========================================================================
// 6. Program
// ==========================================================================
console . log ( '\n📁 Creating program...' )
const program = await prisma . program . upsert ( {
where : { name_year : { name : 'Monaco Ocean Protection Challenge' , year : 2026 } } ,
update : { } ,
create : {
name : 'Monaco Ocean Protection Challenge' ,
year : 2026 ,
status : ProgramStatus.ACTIVE ,
description : 'Annual ocean conservation startup competition supporting innovative solutions for ocean protection.' ,
} ,
} )
console . log ( ` ✓ Program: ${ program . name } ${ program . year } ` )
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
// Legacy Pipeline/Track/Stage system removed - Competition/Round architecture now in use
2026-02-14 15:26:42 +01:00
// ==========================================================================
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
// 7. Parse CSV & Create Applicants + Projects
2026-02-14 15:26:42 +01:00
// ==========================================================================
console . log ( '\n📄 Checking for existing projects...' )
const existingProjectCount = await prisma . project . count ( { where : { programId : program.id } } )
let projectCount = 0
if ( existingProjectCount > 0 ) {
projectCount = existingProjectCount
console . log ( ` ⏭️ ${ existingProjectCount } projects already exist, skipping CSV import ` )
} else {
console . log ( ' Parsing Candidatures2026.csv...' )
const csvPath = join ( __dirname , '..' , 'docs' , 'Candidatures2026.csv' )
const csvContent = readFileSync ( csvPath , 'utf-8' )
// Remove BOM if present
const cleanContent = csvContent . replace ( /^\uFEFF/ , '' )
const records : Record < string , string > [ ] = parse ( cleanContent , {
columns : true ,
skip_empty_lines : true ,
relax_column_count : true ,
trim : true ,
} )
console . log ( ` Raw CSV rows: ${ records . length } ` )
2026-02-15 14:25:05 +01:00
// Skip only completely empty rows (no name, no email, no project)
const validRecords = records . filter ( ( row : Record < string , string > ) = > ! isEmptyRow ( row ) )
console . log ( ` Entries to seed: ${ validRecords . length } ` )
2026-02-14 15:26:42 +01:00
// Create applicant users and projects
console . log ( '\n🚀 Creating applicant users and projects...' )
2026-02-15 14:25:05 +01:00
let skippedNoEmail = 0
for ( let rowIdx = 0 ; rowIdx < validRecords . length ; rowIdx ++ ) {
const row = validRecords [ rowIdx ]
2026-02-14 15:26:42 +01:00
const email = ( row [ 'E-mail' ] || '' ) . trim ( ) . toLowerCase ( )
const name = ( row [ 'Full name' ] || '' ) . trim ( )
const phone = ( row [ 'Téléphone' ] || '' ) . trim ( ) || null
const country = ( row [ 'Country' ] || '' ) . trim ( ) || null
const zone = ( row [ 'Tri par zone' ] || '' ) . trim ( ) || null
const university = ( row [ 'University' ] || '' ) . trim ( ) || null
const projectName = ( row [ "Project's name" ] || '' ) . trim ( )
const teamMembers = ( row [ 'Team members' ] || '' ) . trim ( ) || null
const category = mapCategory ( row [ 'Category' ] )
const issue = mapIssue ( row [ 'Issue' ] )
const comment = ( row [ 'Comment' ] || row [ 'Comment ' ] || '' ) . trim ( ) || null
const mentorship = ( row [ 'Mentorship' ] || '' ) . trim ( ) . toLowerCase ( ) === 'true'
const referral = ( row [ 'How did you hear about MOPC?' ] || '' ) . trim ( ) || null
const appStatus = ( row [ 'Application status' ] || '' ) . trim ( ) || null
const phase1Url = ( row [ 'PHASE 1 - Submission' ] || '' ) . trim ( ) || null
const phase2Url = ( row [ 'PHASE 2 - Submission' ] || '' ) . trim ( ) || null
const foundedAt = parseFoundedDate ( row [ 'Date of creation' ] )
2026-02-15 14:25:05 +01:00
// Skip rows with no usable email (can't create user without one)
if ( ! email || ! email . includes ( '@' ) ) {
skippedNoEmail ++
console . log ( ` ⚠ Row ${ rowIdx + 2 } : skipped (no valid email) ` )
continue
}
// Create or get applicant user (upsert handles duplicate emails)
2026-02-14 15:26:42 +01:00
const user = await prisma . user . upsert ( {
where : { email } ,
update : {
status : UserStatus.NONE ,
mustSetPassword : true ,
} ,
create : {
email ,
2026-02-15 14:25:05 +01:00
name : name || ` Applicant ${ rowIdx + 1 } ` ,
2026-02-14 15:26:42 +01:00
role : UserRole.APPLICANT ,
status : UserStatus.NONE ,
phoneNumber : phone ,
country ,
metadataJson : university ? { institution : university } : undefined ,
mustSetPassword : true ,
} ,
} )
// Create project
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
await prisma . project . create ( {
2026-02-14 15:26:42 +01:00
data : {
programId : program.id ,
title : projectName || ` Project by ${ name } ` ,
description : comment ,
competitionCategory : category ,
oceanIssue : issue ,
country ,
geographicZone : zone ,
institution : university ,
wantsMentorship : mentorship ,
foundedAt ,
phase1SubmissionUrl : phase1Url ,
phase2SubmissionUrl : phase2Url ,
referralSource : referral ,
applicationStatus : appStatus ,
submissionSource : SubmissionSource.CSV ,
submittedByUserId : user.id ,
submittedByEmail : email ,
submittedAt : new Date ( ) ,
status : ProjectStatus.SUBMITTED ,
metadataJson : teamMembers ? { teamMembers } : undefined ,
} ,
} )
projectCount ++
if ( projectCount % 50 === 0 ) {
console . log ( ` ... ${ projectCount } projects created ` )
}
}
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
console . log ( ` ✓ Created ${ projectCount } projects ` )
2026-02-15 14:25:05 +01:00
if ( skippedNoEmail > 0 ) {
console . log ( ` ⚠ Skipped ${ skippedNoEmail } rows with no valid email ` )
}
2026-02-14 15:26:42 +01:00
}
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
// Legacy evaluation forms and special awards removed - Competition/Round architecture now in use
2026-02-14 15:26:42 +01:00
// ==========================================================================
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
// 8. Competition Architecture
2026-02-14 15:26:42 +01:00
// ==========================================================================
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
console . log ( '\n🏗️ Creating competition architecture...' )
2026-02-14 15:26:42 +01:00
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
const competition = await prisma . competition . upsert ( {
where : { slug : 'mopc-2026' } ,
2026-02-14 15:26:42 +01:00
update : { } ,
create : {
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
programId : program.id ,
name : 'MOPC 2026' ,
slug : 'mopc-2026' ,
status : CompetitionStatus.ACTIVE ,
categoryMode : 'SHARED' ,
startupFinalistCount : 3 ,
conceptFinalistCount : 3 ,
notifyOnRoundAdvance : true ,
notifyOnDeadlineApproach : true ,
deadlineReminderDays : [ 7 , 3 , 1 ] ,
} ,
} )
console . log ( ` ✓ Competition: ${ competition . name } ` )
// --- Jury Groups ---
const juryGroup1 = await prisma . juryGroup . upsert ( {
where : { competitionId_slug : { competitionId : competition.id , slug : 'screening-jury' } } ,
update : { } ,
create : {
competitionId : competition.id ,
name : 'Screening Jury' ,
slug : 'screening-jury' ,
sortOrder : 0 ,
defaultMaxAssignments : 30 ,
defaultCapMode : CapMode.SOFT ,
softCapBuffer : 5 ,
categoryQuotasEnabled : false ,
} ,
} )
const juryGroup2 = await prisma . juryGroup . upsert ( {
where : { competitionId_slug : { competitionId : competition.id , slug : 'expert-jury' } } ,
update : { } ,
create : {
competitionId : competition.id ,
name : 'Expert Jury' ,
slug : 'expert-jury' ,
sortOrder : 1 ,
defaultMaxAssignments : 20 ,
defaultCapMode : CapMode.SOFT ,
softCapBuffer : 2 ,
categoryQuotasEnabled : true ,
defaultCategoryQuotas : {
STARTUP : { min : 5 , max : 15 } ,
BUSINESS_CONCEPT : { min : 3 , max : 10 } ,
2026-02-14 15:26:42 +01:00
} ,
} ,
} )
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
const juryGroup3 = await prisma . juryGroup . upsert ( {
where : { competitionId_slug : { competitionId : competition.id , slug : 'finals-jury' } } ,
update : { } ,
create : {
competitionId : competition.id ,
name : 'Finals Jury' ,
slug : 'finals-jury' ,
sortOrder : 2 ,
defaultMaxAssignments : 10 ,
defaultCapMode : CapMode.HARD ,
softCapBuffer : 0 ,
categoryQuotasEnabled : false ,
} ,
} )
console . log ( ' ✓ Jury Groups: Screening, Expert, Finals' )
// --- Add jury members to groups ---
// Split 8 jurors: 4 in screening, 6 in expert (some overlap), all 8 in finals
const juryGroupAssignments = [
{ groupId : juryGroup1.id , userIds : juryUserIds.slice ( 0 , 4 ) , role : JuryGroupMemberRole.MEMBER } ,
{ groupId : juryGroup2.id , userIds : juryUserIds.slice ( 0 , 6 ) , role : JuryGroupMemberRole.MEMBER } ,
{ groupId : juryGroup3.id , userIds : juryUserIds , role : JuryGroupMemberRole.MEMBER } ,
]
let memberCount = 0
for ( const assignment of juryGroupAssignments ) {
for ( let i = 0 ; i < assignment . userIds . length ; i ++ ) {
const userId = assignment . userIds [ i ]
await prisma . juryGroupMember . upsert ( {
where : {
juryGroupId_userId : { juryGroupId : assignment.groupId , userId } ,
} ,
update : { } ,
create : {
juryGroupId : assignment.groupId ,
userId ,
role : i === 0 ? JuryGroupMemberRole.CHAIR : assignment.role ,
} ,
} )
memberCount ++
}
}
console . log ( ` ✓ ${ memberCount } jury group memberships created ` )
// --- Demo self-service preferences ---
// Enable self-service on the Expert Panel and set preferences for first 2 members
await prisma . juryGroup . update ( {
where : { id : juryGroup2.id } ,
data : { allowJurorCapAdjustment : true , allowJurorRatioAdjustment : true } ,
} )
// Juror 0 sets a lower cap and prefers startups
const selfServiceMember1 = await prisma . juryGroupMember . findUnique ( {
where : { juryGroupId_userId : { juryGroupId : juryGroup2.id , userId : juryUserIds [ 0 ] } } ,
} )
if ( selfServiceMember1 ) {
await prisma . juryGroupMember . update ( {
where : { id : selfServiceMember1.id } ,
data : { selfServiceCap : 12 , selfServiceRatio : 0.7 } ,
} )
}
2026-02-14 15:26:42 +01:00
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
// Juror 1 sets a moderate ratio preference
const selfServiceMember2 = await prisma . juryGroupMember . findUnique ( {
where : { juryGroupId_userId : { juryGroupId : juryGroup2.id , userId : juryUserIds [ 1 ] } } ,
} )
if ( selfServiceMember2 ) {
await prisma . juryGroupMember . update ( {
where : { id : selfServiceMember2.id } ,
data : { selfServiceRatio : 0.4 } ,
} )
}
console . log ( ' ✓ Self-service preferences: 2 jurors in Expert Panel' )
// --- Submission Windows ---
const submissionWindow1 = await prisma . submissionWindow . upsert ( {
where : { competitionId_slug : { competitionId : competition.id , slug : 'r1-application-docs' } } ,
2026-02-14 15:26:42 +01:00
update : { } ,
create : {
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
competitionId : competition.id ,
name : 'R1 Application Documents' ,
slug : 'r1-application-docs' ,
roundNumber : 1 ,
sortOrder : 0 ,
windowOpenAt : new Date ( '2026-01-01' ) ,
windowCloseAt : new Date ( '2026-01-31' ) ,
isLocked : true ,
2026-02-14 15:26:42 +01:00
} ,
} )
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
const submissionWindow2 = await prisma . submissionWindow . upsert ( {
where : { competitionId_slug : { competitionId : competition.id , slug : 'r4-semifinal-docs' } } ,
2026-02-14 15:26:42 +01:00
update : { } ,
create : {
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
competitionId : competition.id ,
name : 'R4 Semi-Finalist Documents' ,
slug : 'r4-semifinal-docs' ,
roundNumber : 4 ,
sortOrder : 1 ,
windowOpenAt : new Date ( '2026-04-01' ) ,
windowCloseAt : new Date ( '2026-04-30' ) ,
isLocked : false ,
} ,
} )
console . log ( ' ✓ Submission Windows: R1 Application, R4 Semi-finalist' )
// --- File Requirements ---
await prisma . submissionFileRequirement . upsert ( {
where : { submissionWindowId_slug : { submissionWindowId : submissionWindow1.id , slug : 'executive-summary' } } ,
update : { } ,
create : {
submissionWindowId : submissionWindow1.id ,
label : 'Executive Summary' ,
slug : 'executive-summary' ,
description : 'PDF document summarizing the project' ,
mimeTypes : [ 'application/pdf' ] ,
maxSizeMb : 50 ,
required : true ,
sortOrder : 0 ,
} ,
} )
await prisma . submissionFileRequirement . upsert ( {
where : { submissionWindowId_slug : { submissionWindowId : submissionWindow1.id , slug : 'video-pitch' } } ,
update : { } ,
create : {
submissionWindowId : submissionWindow1.id ,
label : 'Video Pitch' ,
slug : 'video-pitch' ,
description : 'Short video pitching the project (max 5 minutes)' ,
mimeTypes : [ 'video/mp4' , 'video/quicktime' ] ,
maxSizeMb : 500 ,
required : false ,
sortOrder : 1 ,
} ,
} )
await prisma . submissionFileRequirement . upsert ( {
where : { submissionWindowId_slug : { submissionWindowId : submissionWindow2.id , slug : 'updated-business-plan' } } ,
update : { } ,
create : {
submissionWindowId : submissionWindow2.id ,
label : 'Updated Business Plan' ,
slug : 'updated-business-plan' ,
description : 'Updated business plan with financials' ,
mimeTypes : [ 'application/pdf' ] ,
maxSizeMb : 50 ,
required : true ,
sortOrder : 0 ,
2026-02-14 15:26:42 +01:00
} ,
} )
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
console . log ( ' ✓ File Requirements: Exec Summary, Video Pitch, Business Plan' )
// --- Rounds (8-round Monaco flow) ---
const roundDefs = [
{ name : 'R1 - Application Intake' , slug : 'r1-intake' , roundType : RoundType.INTAKE , sortOrder : 0 , status : RoundStatus.ROUND_CLOSED , juryGroupId : null , submissionWindowId : submissionWindow1.id } ,
{ name : 'R2 - AI Screening' , slug : 'r2-screening' , roundType : RoundType.FILTERING , sortOrder : 1 , status : RoundStatus.ROUND_ACTIVE , juryGroupId : juryGroup1.id , submissionWindowId : null } ,
{ name : 'R3 - Expert Evaluation' , slug : 'r3-evaluation' , roundType : RoundType.EVALUATION , sortOrder : 2 , status : RoundStatus.ROUND_DRAFT , juryGroupId : juryGroup2.id , submissionWindowId : null } ,
{ name : 'R4 - Document Submission' , slug : 'r4-submission' , roundType : RoundType.SUBMISSION , sortOrder : 3 , status : RoundStatus.ROUND_DRAFT , juryGroupId : null , submissionWindowId : submissionWindow2.id } ,
{ name : 'R5 - Semi-Final Evaluation' , slug : 'r5-semi-eval' , roundType : RoundType.EVALUATION , sortOrder : 4 , status : RoundStatus.ROUND_DRAFT , juryGroupId : juryGroup2.id , submissionWindowId : null } ,
{ name : 'R6 - Mentoring' , slug : 'r6-mentoring' , roundType : RoundType.MENTORING , sortOrder : 5 , status : RoundStatus.ROUND_DRAFT , juryGroupId : null , submissionWindowId : null } ,
{ name : 'R7 - Grand Final' , slug : 'r7-grand-final' , roundType : RoundType.LIVE_FINAL , sortOrder : 6 , status : RoundStatus.ROUND_DRAFT , juryGroupId : juryGroup3.id , submissionWindowId : null } ,
{ name : 'R8 - Deliberation' , slug : 'r8-deliberation' , roundType : RoundType.DELIBERATION , sortOrder : 7 , status : RoundStatus.ROUND_DRAFT , juryGroupId : juryGroup3.id , submissionWindowId : null } ,
]
const rounds = [ ]
for ( const def of roundDefs ) {
const config = defaultRoundConfig ( def . roundType )
const round = await prisma . round . upsert ( {
where : { competitionId_slug : { competitionId : competition.id , slug : def.slug } } ,
update : { } ,
create : {
competitionId : competition.id ,
name : def.name ,
slug : def.slug ,
roundType : def.roundType ,
status : def.status ,
sortOrder : def.sortOrder ,
configJson : config as object ,
juryGroupId : def.juryGroupId ,
submissionWindowId : def.submissionWindowId ,
} ,
} )
rounds . push ( round )
}
console . log ( ` ✓ ${ rounds . length } rounds created (R1-R8) ` )
// --- Advancement Rules (auto-advance between rounds) ---
for ( let i = 0 ; i < rounds . length - 1 ; i ++ ) {
await prisma . advancementRule . upsert ( {
where : {
roundId_sortOrder : { roundId : rounds [ i ] . id , sortOrder : 0 } ,
} ,
update : { } ,
create : {
roundId : rounds [ i ] . id ,
ruleType : AdvancementRuleType.AUTO_ADVANCE ,
sortOrder : 0 ,
targetRoundId : rounds [ i + 1 ] . id ,
configJson : { } ,
} ,
} )
}
console . log ( ` ✓ ${ rounds . length - 1 } advancement rules created ` )
// --- Round-Submission Visibility (which rounds can see which submission windows) ---
// R2 and R3 can see R1 docs, R5 can see R4 docs
const visibilityLinks = [
{ roundId : rounds [ 1 ] . id , submissionWindowId : submissionWindow1.id } , // R2 sees R1 docs
{ roundId : rounds [ 2 ] . id , submissionWindowId : submissionWindow1.id } , // R3 sees R1 docs
{ roundId : rounds [ 4 ] . id , submissionWindowId : submissionWindow1.id } , // R5 sees R1 docs
{ roundId : rounds [ 4 ] . id , submissionWindowId : submissionWindow2.id } , // R5 sees R4 docs
]
for ( const link of visibilityLinks ) {
await prisma . roundSubmissionVisibility . upsert ( {
where : {
roundId_submissionWindowId : {
roundId : link.roundId ,
submissionWindowId : link.submissionWindowId ,
} ,
} ,
update : { } ,
create : link ,
} )
}
console . log ( ` ✓ ${ visibilityLinks . length } submission visibility links created ` )
// --- Feature flag: enable competition model ---
await prisma . systemSettings . upsert ( {
where : { key : 'feature.useCompetitionModel' } ,
update : { value : 'true' } ,
create : {
key : 'feature.useCompetitionModel' ,
value : 'true' ,
type : SettingType . BOOLEAN ,
category : SettingCategory.FEATURE_FLAGS ,
description : 'Use Competition/Round model (legacy Pipeline system removed)' ,
} ,
} )
console . log ( ' ✓ Feature flag: feature.useCompetitionModel = true' )
2026-02-14 15:26:42 +01:00
// ==========================================================================
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
// 9. Notification Email Settings
2026-02-14 15:26:42 +01:00
// ==========================================================================
console . log ( '\n🔔 Creating notification email settings...' )
const notificationSettings = [
// Team / Applicant notifications
{ notificationType : 'APPLICATION_SUBMITTED' , category : 'team' , label : 'Application Submitted' , description : 'When a team submits their application' , sendEmail : true } ,
{ notificationType : 'TEAM_INVITE_RECEIVED' , category : 'team' , label : 'Team Invitation Received' , description : 'When someone is invited to join a team' , sendEmail : true } ,
{ notificationType : 'TEAM_MEMBER_JOINED' , category : 'team' , label : 'Team Member Joined' , description : 'When a new member joins the team' , sendEmail : false } ,
{ notificationType : 'ADVANCED_SEMIFINAL' , category : 'team' , label : 'Advanced to Semi-Finals' , description : 'When a project advances to semi-finals' , sendEmail : true } ,
{ notificationType : 'ADVANCED_FINAL' , category : 'team' , label : 'Selected as Finalist' , description : 'When a project is selected as a finalist' , sendEmail : true } ,
{ notificationType : 'MENTOR_ASSIGNED' , category : 'team' , label : 'Mentor Assigned' , description : 'When a mentor is assigned to the team' , sendEmail : true } ,
{ notificationType : 'NOT_SELECTED' , category : 'team' , label : 'Not Selected' , description : 'When a project is not selected for the next round' , sendEmail : true } ,
{ notificationType : 'FEEDBACK_AVAILABLE' , category : 'team' , label : 'Feedback Available' , description : 'When jury feedback becomes available' , sendEmail : true } ,
{ notificationType : 'WINNER_ANNOUNCEMENT' , category : 'team' , label : 'Winner Announcement' , description : 'When a project wins an award' , sendEmail : true } ,
// Jury notifications
{ notificationType : 'ASSIGNED_TO_PROJECT' , category : 'jury' , label : 'Assigned to Project' , description : 'When a jury member is assigned to a project' , sendEmail : true } ,
{ notificationType : 'BATCH_ASSIGNED' , category : 'jury' , label : 'Batch Assignment' , description : 'When multiple projects are assigned at once' , sendEmail : true } ,
{ notificationType : 'ROUND_NOW_OPEN' , category : 'jury' , label : 'Round Now Open' , description : 'When a round opens for evaluation' , sendEmail : true } ,
{ notificationType : 'REMINDER_24H' , category : 'jury' , label : 'Reminder (24h)' , description : 'Reminder 24 hours before deadline' , sendEmail : true } ,
{ notificationType : 'REMINDER_1H' , category : 'jury' , label : 'Reminder (1h)' , description : 'Urgent reminder 1 hour before deadline' , sendEmail : true } ,
{ notificationType : 'ROUND_CLOSED' , category : 'jury' , label : 'Round Closed' , description : 'When a round closes' , sendEmail : false } ,
{ notificationType : 'AWARD_VOTING_OPEN' , category : 'jury' , label : 'Award Voting Open' , description : 'When special award voting opens' , sendEmail : true } ,
// Mentor notifications
{ notificationType : 'MENTEE_ASSIGNED' , category : 'mentor' , label : 'Mentee Assigned' , description : 'When assigned as mentor to a project' , sendEmail : true } ,
{ notificationType : 'MENTEE_UPLOADED_DOCS' , category : 'mentor' , label : 'Mentee Documents Updated' , description : 'When a mentee uploads new documents' , sendEmail : false } ,
{ notificationType : 'MENTEE_ADVANCED' , category : 'mentor' , label : 'Mentee Advanced' , description : 'When a mentee advances to the next round' , sendEmail : true } ,
{ notificationType : 'MENTEE_FINALIST' , category : 'mentor' , label : 'Mentee is Finalist' , description : 'When a mentee is selected as finalist' , sendEmail : true } ,
{ notificationType : 'MENTEE_WON' , category : 'mentor' , label : 'Mentee Won' , description : 'When a mentee wins an award' , sendEmail : true } ,
// Observer notifications
{ notificationType : 'ROUND_STARTED' , category : 'observer' , label : 'Round Started' , description : 'When a new round begins' , sendEmail : false } ,
{ notificationType : 'ROUND_COMPLETED' , category : 'observer' , label : 'Round Completed' , description : 'When a round is completed' , sendEmail : true } ,
{ notificationType : 'FINALISTS_ANNOUNCED' , category : 'observer' , label : 'Finalists Announced' , description : 'When finalists are announced' , sendEmail : true } ,
{ notificationType : 'WINNERS_ANNOUNCED' , category : 'observer' , label : 'Winners Announced' , description : 'When winners are announced' , sendEmail : true } ,
// Admin notifications
{ notificationType : 'FILTERING_COMPLETE' , category : 'admin' , label : 'AI Filtering Complete' , description : 'When AI filtering job completes' , sendEmail : false } ,
{ notificationType : 'FILTERING_FAILED' , category : 'admin' , label : 'AI Filtering Failed' , description : 'When AI filtering job fails' , sendEmail : true } ,
{ notificationType : 'NEW_APPLICATION' , category : 'admin' , label : 'New Application' , description : 'When a new application is received' , sendEmail : false } ,
{ notificationType : 'SYSTEM_ERROR' , category : 'admin' , label : 'System Error' , description : 'When a system error occurs' , sendEmail : true } ,
]
for ( const setting of notificationSettings ) {
await prisma . notificationEmailSetting . upsert ( {
where : { notificationType : setting.notificationType } ,
update : {
category : setting.category ,
label : setting.label ,
description : setting.description ,
} ,
create : setting ,
} )
}
console . log ( ` ✓ Created ${ notificationSettings . length } notification email settings ` )
// ==========================================================================
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
// 10. Summary
2026-02-14 15:26:42 +01:00
// ==========================================================================
console . log ( '\n' + '=' . repeat ( 60 ) )
console . log ( '✅ SEEDING COMPLETE' )
console . log ( '=' . repeat ( 60 ) )
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
console . log ( ` \ n Program: ${ program . name } ${ program . year } \ n \ n Competition: ${ competition . name } ( ${ competition . slug } ) \ n Rounds: ${ rounds . length } (R1-R8) \ n Jury Groups: 3 (Screening, Expert, Finals) \ n Sub. Windows: 2 (R1 Application, R4 Semi-finalist) \ n \ n Projects: ${ projectCount } (from CSV) \ n Users: ${ 3 + juryMembers . length + mentors . length + observers . length + projectCount } total \ n - Admin/Staff: 3 \ n - Jury: ${ juryMembers . length } \ n - Mentors: ${ mentors . length } \ n - Observers: ${ observers . length } \ n - Applicants: ${ projectCount } \ n \ n Login: matt@monaco-opc.com / 195260Mp! \ n ` )
2026-02-14 15:26:42 +01:00
}
main ( )
. catch ( ( e ) = > {
console . error ( '❌ Seeding failed:' , e )
process . exit ( 1 )
} )
. finally ( async ( ) = > {
await prisma . $disconnect ( )
} )