Round system redesign: Phases 1-7 complete
Build and Push Docker Image / build (push) Successful in 19m31s
Details
Build and Push Docker Image / build (push) Successful in 19m31s
Details
Full pipeline/track/stage architecture replacing the legacy round system. Schema: 11 new models (Pipeline, Track, Stage, StageTransition, ProjectStageState, RoutingRule, Cohort, CohortProject, LiveProgressCursor, OverrideAction, AudienceVoter) + 8 new enums. Backend: 9 new routers (pipeline, stage, routing, stageFiltering, stageAssignment, cohort, live, decision, award) + 6 new services (stage-engine, routing-engine, stage-filtering, stage-assignment, stage-notifications, live-control). Frontend: Pipeline wizard (17 components), jury stage pages (7), applicant pipeline pages (3), public stage pages (2), admin pipeline pages (5), shared stage components (3), SSE route, live hook. Phase 6 refit: 23 routers/services migrated from roundId to stageId, all frontend components refitted. Deleted round.ts (985 lines), roundTemplate.ts, round-helpers.ts, round-settings.ts, round-type-settings.tsx, 10 legacy admin pages, 7 legacy jury pages, 3 legacy dialogs. Phase 7 validation: 36 tests (10 unit + 8 integration files) all passing, TypeScript 0 errors, Next.js build succeeds, 13 integrity checks, legacy symbol sweep clean, auto-seed on first Docker startup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8a328357e3
commit
331b67dae0
|
|
@ -0,0 +1,78 @@
|
||||||
|
# Final Comparative Analysis of Round System Redesign Proposals
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
After a detailed review of the design documents, and with special consideration for the directive that a full architectural replacement ("nuking" the system) is an acceptable strategy, the **"Claude" proposal is unequivocally the superior plan.**
|
||||||
|
|
||||||
|
While both proposals advocate for a fundamental redesign, the "Claude" plan presents a complete, well-reasoned, and deeply detailed target architecture. The "Codex" plan, in contrast, offers a rigid and process-heavy methodology for a redesign but fails to provide the substantive architectural details of the proposed replacement.
|
||||||
|
|
||||||
|
The "Claude" plan is better not because it is less risky, but because it is a **complete and actionable engineering proposal.** The "Codex" plan is an empty process document by comparison.
|
||||||
|
|
||||||
|
The "GLM-5" proposal could not be evaluated due to corrupted and unreadable files.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Introduction
|
||||||
|
|
||||||
|
This report provides a comparative analysis of two proposals for a major redesign of the MOPC round system: "Claude" and "Codex". The analysis was guided by the principle that a complete, "rip and replace" overhaul of the existing architecture is a valid and encouraged option. The goal is to determine which plan is the most comprehensive, well-thought-through, creative, and technically impressive in its vision for this new architecture.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Philosophy of Redesign
|
||||||
|
|
||||||
|
Both plans embrace the idea of replacing the old, organic round system with a new, purpose-built architecture. They even converge on a similar conceptual model (`Pipeline -> Track -> Stage`). However, their approaches to achieving this goal are diametrically opposed.
|
||||||
|
|
||||||
|
- **"Claude" - The Architect's Approach:** This plan focuses on the **end-state architecture first.** It presents a meticulously detailed domain model and schema, explaining the "why" behind every component. It then proposes a professional, phased implementation plan to build and deploy this new architecture, using a temporary dual-write strategy to ensure a smooth and safe transition before decommissioning the old system. This is a complete replacement, executed with engineering precision.
|
||||||
|
|
||||||
|
- **"Codex" - The Process-Manager's Approach:** This plan focuses on the **process of execution first.** It builds an elaborate and rigid framework of rules, gates, and documentation requirements for the project. It advocates for a one-time, destructive cutover. However, it critically fails to define the very architecture it plans to build. Key documents like the `schema-domain-model.md` are little more than lists of names, lacking the actual schema, fields, relationships, and rationale.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Comparative Analysis
|
||||||
|
|
||||||
|
### Comprehensiveness & Technical Impressiveness
|
||||||
|
|
||||||
|
**Winner: Claude**
|
||||||
|
|
||||||
|
This is the most critical distinction. The "Claude" proposal is technically impressive because of its content and substance. The `02-schema-design.md` document is a masterclass in software architecture, providing:
|
||||||
|
- A clear `Pipeline -> Track -> Stage` hierarchy.
|
||||||
|
- Complete `prisma` schema definitions for 12 new models.
|
||||||
|
- An explicit, configurable state machine (`StageTransition`) to replace hard-coded logic.
|
||||||
|
- First-class support for parallel award tracks (`RoutingRule`).
|
||||||
|
- A robust data model for live-event management (`LiveStageControl`).
|
||||||
|
- Detailed consideration of indexes, validation, and migration.
|
||||||
|
|
||||||
|
The "Codex" proposal is comprehensive only in its process. It specifies *that* a schema should be built, but doesn't contain the schema. It specifies *that* API contracts should exist, but the substance is missing. Its technical impressiveness is superficial—an illusion created by a mountain of process formalism with no architectural core. **The "Claude" plan *is* a new architecture; the "Codex" plan is a set of instructions for *how to create* a new architecture.**
|
||||||
|
|
||||||
|
### Thoughtfulness & Strategy
|
||||||
|
|
||||||
|
**Winner: Claude**
|
||||||
|
|
||||||
|
Given that a "nuke" is acceptable, the question becomes: what is the most thoughtful way to execute it?
|
||||||
|
|
||||||
|
- The "Codex" strategy of a single, destructive cutover is brittle. It assumes perfection in execution and leaves no room for error. A single failure during the final push could render the entire platform inoperable and require a complex, stressful rollback.
|
||||||
|
- The "Claude" strategy of a phased cutover with a dual-write period is far more thoughtful. It is still a complete "nuke," as the old `Round` model is ultimately removed. However, it allows the new system to be validated in a live production environment alongside the old one before the final switch is flipped. This de-risks the deployment immensely and is the hallmark of a mature engineering organization.
|
||||||
|
|
||||||
|
### Creativity & Innovation
|
||||||
|
|
||||||
|
**Winner: Claude**
|
||||||
|
|
||||||
|
The most creative and innovative element in this entire project is the **new system architecture itself.** The `Pipeline -> Track -> Stage` model, the explicit state machine for transitions, and the flexible routing rules are the true innovations that will solve the platform's problems.
|
||||||
|
|
||||||
|
Since the "Claude" plan is the only one to actually *document* this innovative architecture in detail, it wins by default. The creativity of the "Codex" plan is confined to its rigid project management methodology, which is less a useful innovation and more of a high-risk academic exercise.
|
||||||
|
|
||||||
|
### Efficiency
|
||||||
|
|
||||||
|
**Winner: Claude**
|
||||||
|
|
||||||
|
The "Claude" plan's phased approach allows for incremental progress and testing. The development team can build, test, and validate each part of the new system in isolation. The "Codex" plan, with its massive documentation overhead and "all-or-nothing" integration at the very end, is a recipe for inefficiency, bottlenecks, and massive rework when unforeseen issues inevitably arise.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Conclusion & Recommendation
|
||||||
|
|
||||||
|
The directive to allow for a full architectural replacement does not change the final recommendation; it strengthens it. The "Codex" proposal pays lip service to the idea of a replacement but provides no actual architectural substance. It is a hollow and risky process document.
|
||||||
|
|
||||||
|
The "Claude" proposal embraces the spirit of a full redesign by presenting a truly impressive, detailed, and well-reasoned new architecture. It then pairs this excellent design with a professional and pragmatic implementation plan that respects the complexities of migrating a live system.
|
||||||
|
|
||||||
|
**The "Claude" plan is the only viable path forward. It is a complete, thoughtful, and technically superior proposal that should be adopted without reservation.**
|
||||||
|
|
@ -51,8 +51,9 @@ COPY --from=builder /app/node_modules ./node_modules
|
||||||
COPY --from=builder /app/prisma ./prisma
|
COPY --from=builder /app/prisma ./prisma
|
||||||
COPY --from=builder /app/package.json ./package.json
|
COPY --from=builder /app/package.json ./package.json
|
||||||
|
|
||||||
# Copy CSV data file for manual seeding
|
# Copy files needed for seeding (tsx needs tsconfig for path resolution)
|
||||||
COPY --from=builder /app/docs/candidatures_2026.csv ./docs/candidatures_2026.csv
|
COPY --from=builder /app/docs/Candidatures2026.csv ./docs/Candidatures2026.csv
|
||||||
|
COPY --from=builder /app/tsconfig.json ./tsconfig.json
|
||||||
|
|
||||||
# Copy entrypoint script
|
# Copy entrypoint script
|
||||||
COPY docker/docker-entrypoint.sh /app/docker-entrypoint.sh
|
COPY docker/docker-entrypoint.sh /app/docker-entrypoint.sh
|
||||||
|
|
|
||||||
|
|
@ -19,5 +19,19 @@ done
|
||||||
echo "==> Generating Prisma client..."
|
echo "==> Generating Prisma client..."
|
||||||
npx prisma generate
|
npx prisma generate
|
||||||
|
|
||||||
|
# Auto-seed on first startup: check if Users table is empty
|
||||||
|
USER_COUNT=$(node -e "
|
||||||
|
const { PrismaClient } = require('@prisma/client');
|
||||||
|
const p = new PrismaClient();
|
||||||
|
p.user.count().then(c => { console.log(c); p.\$disconnect(); }).catch(() => { console.log('0'); p.\$disconnect(); });
|
||||||
|
" 2>/dev/null || echo "0")
|
||||||
|
|
||||||
|
if [ "$USER_COUNT" = "0" ]; then
|
||||||
|
echo "==> Empty database detected — running seed..."
|
||||||
|
npx prisma db seed || echo "WARNING: Seed script failed."
|
||||||
|
else
|
||||||
|
echo "==> Database already seeded ($USER_COUNT users found), skipping seed."
|
||||||
|
fi
|
||||||
|
|
||||||
echo "==> Starting application..."
|
echo "==> Starting application..."
|
||||||
exec node server.js
|
exec node server.js
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,885 @@
|
||||||
|
Jury 1 attribués,Full name,Téléphone,Lien Whatsapp,E-mail,Project's name,Team members,Country,Tri par zone,University,Category,Date of creation,Issue,"Comment ",Mentorship,How did you hear about MOPC?,Application status,PHASE 1 - Submission,PHASE 2 - Submission
|
||||||
|
,Mutave Nelly,+254704458380,Envoyer le message,mutavenelly.mn@gmail.com,Revamp Flips,Nthatisi Lesala,"Nairobi, Kenya","Africa, Kenya",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,1997-01-24,Technology & innovations,Hrkb,true,Friend shared link,,,
|
||||||
|
,Omoding Olinga Simon,+256773351242,Envoyer le message,simonomoding.ace@gmail.com,Dagim Fisheries (U) Ltd,"Omoding Simon, Ilukat Musa, Omiel Peter, Omongole Richard, Fellista Nakatabirwa","Kampala, Ouganda","Africa, Ouganda",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-01-05,Sustainable fishing and aquaculture & blue food,"Dagim Fisheries directly advances equitable access to safe, nutritious, affordable food while improving planetary health through zero-waste processing and sustainable fishing. Our multidisciplinary approach integrates nutrition science, food engineering, supply chain management, environmental conservation, and economics. We address malnutrition, reduce waste, empower fishing communities, and protect Lake Victoria's and Kyoga's ecosystem creating regenerative food systems scalable across East Africa toward the billion-lives impact goal.",true,Through Linkedinn social media,,,
|
||||||
|
,SENI Abd-Ramane,+2290161149564,Envoyer le message,seniramane@gmail.com,OceanClean Tech,"SENI Abd-Ramane, DJIBRIL Samir, SOULÉ SEIDOU Mansoura","Banikoara, Bénin","Africa, Bénin",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2025-12-29,Reduction of pollution (plastics chemicals noise light...),"_Project Title_: OceanClean Tech
|
||||||
|
|
||||||
|
_Objective_: The OceanClean Tech project aims to reduce plastic pollution in the oceans by developing a marine plastic waste collection system. The main goal is to clean up polluted marine areas and prevent new plastic waste from entering marine ecosystems.
|
||||||
|
|
||||||
|
_Innovation_: The project's innovation lies in the use of autonomous drones equipped with artificial intelligence (AI) technologies to locate and collect plastic waste at sea. The drones are capable of navigating autonomously, identifying plastic waste using sensors and image recognition algorithms, and collecting it for transport to a treatment point.
|
||||||
|
|
||||||
|
_Impact_: The OceanClean Tech project has several expected impacts:
|
||||||
|
1. _Environmental_: Significant reduction of plastic waste in the oceans, protecting marine biodiversity and ecosystems.
|
||||||
|
2. _Social_: Raising public awareness of marine pollution and involving local communities in clean-up actions.
|
||||||
|
3. _Economic_: Creating new economic opportunities related to sustainable marine waste management and the development of clean technologies.",true,"I heard about the Monaco Ocean Protection Challenge on LinkedIn, it immediately caught my attention!",Received,https://drive.google.com/drive/folders/1Z5BgrICLZUdybRMBzxr0XXKzIJpmZOX5?usp=drive_link,
|
||||||
|
,Karl Mihhels,+358447627444,Envoyer le message,karl.mihhels@aalto.fi,Shaving the Seas,Karl Mihhels,"Espoo, Finlande du Sud, Finlande","Europe, Finland","Aalto University School of Chemical Engineering, Finland",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Blue Carbon,"The project is about converting fast-growing species of algae, with a high cellulose content (Cladophorales) into a direct replacement for wood based cellulose and cellulose products, such as paper.",true,2nd EU Algae Awareness Summit held in Berlin on October 17th 2025,Received,https://drive.google.com/drive/folders/1VAXgnkmTIOUjN3rW2d-BfUA9svvacV2G?usp=drive_link,
|
||||||
|
,Kabir Olaosebikan,+2348142123656,Envoyer le message,kabir@craftplanet.org,Craft Planet - Blue Guard,"Kabir Olaosebikan, Aminat Abdulazeez, Promise Dalero, Hanatu Abdulakeem","Kaduna, Nigéria","Africa, Nigeria",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2023-04-17,Reduction of pollution (plastics chemicals noise light...),"Craft Planet – Blue Guard for the Ocean is an integrated ocean-protection initiative that prevents plastic pollution before it reaches the sea. Using AI-enabled drones, we identify high-risk waste leakage points along riverbanks and coastal areas, enabling rapid collection of plastic waste before it enters rivers and oceans. Recovered plastics are recycled into durable construction materials—interlocking blocks, eco-bricks, floor and roof tiles—which are used to improve public school infrastructure, including classrooms, toilets, desks, and chairs. The project also builds capacity among coastal communities, teachers, and students through environmental education, waste management training, and circular economy skills, creating local ownership, green jobs, and long-term ocean stewardship.",true,Through online sustainability platforms and ocean innovation networks.,Received,https://drive.google.com/drive/folders/1jUFqGLk1zZ6afP4BysRPpp_jRXsw_9Kz?usp=drive_link,
|
||||||
|
,李涵凝,+8618618164803,Envoyer le message,xbm_0201@qq.com,Environmentally Friendly Jellyfish,李涵凝,"Pékin, Chine",Asia,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2026-01-01,Reduction of pollution (plastics chemicals noise light...),"The 10cm transparent eco-jellyfish robot carries ocean-beneficial materials, moving by mechanical legs and drifting with waves to reduce marine pollution and repair ecosystems, quietly improving ocean health",,I found out about MOPC through an online search.,Received,https://drive.google.com/drive/folders/1duMty6mbpLCOoataogbZEShA6keuK2fy?usp=drive_link,
|
||||||
|
,Faith Mutisya,+254711627836,Envoyer le message,faithmutisya56@gmail.com,Tumbe sea weed farmers,Faith Mutisya - founder and Trainer 2. Hanifa wendo- secretary/field manager 3. Mwanamisi Mwadzumba - Treasurer,"Msambweni, Kwale, Kenya","Africa, Kenya",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2023-03-02,Capacity building for coastal communities,"Tumbe sea weed farmers is a community based organization in Msambweni kwale Kenya. We focus on empowering coastal communities especially young women and youth with skills in sustainable sea weed farming. This is because as women in kwale county we face alot of challenges such as early marriages and pregnancies most especially because women are not given the same schooling privilege as men. Therefore so many young mothers don't have any skills to provide for their young ones. Therefore Tumbe sea weed farmers has taken the initiative to empower them , and through the farming they are able to support themselves financially and at the same time contribute to global efforts in fighting climate change because weed plays an important role as a carbon sink . And we also contribute to increase in biodiversity by provide nursery and nurturing bay for fish and other aquatic organisms",true,Through linked in,,,
|
||||||
|
,Nasibu Mtambo,+254742051141,Envoyer le message,mtamboduke@gmail.com,Blue EcoponicX,"Tabitha Shali, Mohammed Athman, Terry Okwanyo","Mombasa, Kenya","Africa, Kenya",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2025-05-20,Reduction of pollution (plastics chemicals noise light...),"Blue EcoponicX is a climate-tech initiative that transforms marine plastic waste into 3D printed hydroponic towers for urban farming. The project addresses two interconnected challenges; ocean plastic pollution and urban food insecurity by converting waste into smart, productive food-growing systems designed for cities.
|
||||||
|
|
||||||
|
Project Objectives
|
||||||
|
|
||||||
|
1. Reduce Marine Plastic Pollution
|
||||||
|
Collect and recycle marine plastic waste, preventing it from entering landfills or degrading in the ocean.
|
||||||
|
|
||||||
|
2. Improve Urban Food Security
|
||||||
|
Enable affordable, space-efficient food production for households, youth groups, and small-scale urban farmers.
|
||||||
|
|
||||||
|
3. Lower Urban Carbon Emissions
|
||||||
|
Reduce food miles, optimize resource use, and promote localized production using energy-efficient systems.
|
||||||
|
|
||||||
|
4. Promote Climate-Smart Agriculture
|
||||||
|
Use IoT technology to minimize water, nutrient, and energy waste while maximizing crop yields.
|
||||||
|
|
||||||
|
5. Empower Communities Through Technology
|
||||||
|
Make modern farming accessible through easy-to-use smart systems, training, and data insights.
|
||||||
|
|
||||||
|
Key Features
|
||||||
|
|
||||||
|
1. Circular Economy Design: Hydroponic towers made from recycled marine plastics
|
||||||
|
|
||||||
|
2. IoT Integration: Real-time monitoring of water, nutrients, and system health
|
||||||
|
|
||||||
|
3. Low Resource Use: Up to 90% less water than traditional farming
|
||||||
|
|
||||||
|
4. Urban-Friendly: Suitable for rooftops, balconies, schools, and community spaces
|
||||||
|
|
||||||
|
5. Scalable & Modular: Easy to expand from household to community-scale deployment
|
||||||
|
|
||||||
|
Target Beneficiaries
|
||||||
|
|
||||||
|
1. Urban small-scale farmers
|
||||||
|
|
||||||
|
2. Youth and women-led agribusinesses
|
||||||
|
|
||||||
|
3. Schools and training institutions
|
||||||
|
|
||||||
|
4. Cities seeking climate-resilient food systems
|
||||||
|
|
||||||
|
Expected Impact
|
||||||
|
|
||||||
|
1. Reduced plastic pollution in coastal and marine ecosystems
|
||||||
|
|
||||||
|
2. Increased access to fresh, nutritious food in urban areas
|
||||||
|
|
||||||
|
3. Lower carbon emissions from food transport and waste
|
||||||
|
|
||||||
|
4. Creation of green jobs in recycling, manufacturing, and urban agriculture
|
||||||
|
|
||||||
|
5. Stronger climate resilience for cities",true,Through LinkendIn,Received,https://drive.google.com/drive/folders/1cOsZVuXPTNMoygAiu5ngDbQYvumGUMO-?usp=drive_link,
|
||||||
|
,Rodrick Nyendwa,+260977339071,Envoyer le message,rodricknyendwa2016@gmail.com,"Complehensive HIV prevention ,Treatment care support","Rodrick Nyendwa,Executive Director, Mumbi Micheal - Finace Manager, Ementy Mweemba- Programme Manager, Winter Musonda - Human Resource Mnager, Josiah Ndjovu -Community Liason Officer, Simata Mate - Monitoring and Evaluation Manager , Edith Bwalya -Data Entry Officer, Brona Kapindo - Office Assistant , Sylvester Chisanga - Front office Assistant","Kapiri Mposhi, Central, Zambie","Africa, Zambia",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2023-12-12,Mitigation of climate change and sea-level rise,Mitigation about climate change and its impact and issue that community are aware .,true,Through social media on funds for NGOs,Received,https://drive.google.com/drive/folders/1RlybRQMKzhAdcU9vqg8XDZHbtpSzLOCN?usp=drive_link,
|
||||||
|
,Emeka Nwachinemere,+2348062148183,Envoyer le message,nwachinemere.emeka@gmail.com,Pelagos,"Nwachinemere Emeka, Nduka Miracle","Iseyin, Oyo, Nigéria","Africa, Nigeria","University of Nigeria, Nsukka",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Restoration of marine habitats & ecosystems,"Pelagos is developing autonomous, bio-hybrid ocean regeneration machines that restore marine ecosystems while capturing atmospheric carbon. These AI-guided ocean drones re-mineralize seawater to combat acidification, stimulate safe plankton growth to enhance blue carbon sequestration, and support coral regeneration in degraded reefs.
|
||||||
|
|
||||||
|
Objectives:
|
||||||
|
|
||||||
|
Restore ocean health and biodiversity at scale
|
||||||
|
|
||||||
|
Enhance natural blue carbon capture and climate resilience
|
||||||
|
|
||||||
|
Provide real-time ocean intelligence data
|
||||||
|
|
||||||
|
Build a commercially viable, globally scalable blue-economy solution
|
||||||
|
|
||||||
|
Pelagos aims to transform oceans into self-healing climate engines while creating measurable environmental, social, and economic value.",true,Linkedin,Received,https://drive.google.com/drive/folders/1tmf1X5srEQ8bK1sU_2bATC6MpvrTKMhu?usp=drive_link,
|
||||||
|
,Yahuza Sani Hudu,+2348146036089,Envoyer le message,ysanihudu@gmail.com,CleanUp MDC,"Abner Ayuba Atuga, Ameer Saeed","Zaria, Kaduna, Nigéria","Africa, Nigeria",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-02-26,Reduction of pollution (plastics chemicals noise light...),"CleanUp Multi Dyna mic Concept (CleanUp MDC) is a Nigeria-based social enterprise advancing inclusive climate-tech solutions within the circular economy. Our flagship innovation, JoliTrash, is a toll-free, AI-powered, voice-based recycling platform that allows households and informal waste actors to sort and sell recyclable waste using a simple AI phone call in their local language without the need for smartphones, internet access, or digital literacy. Nigeria generates about 2.5 million tons of plastic waste annually, yet less than 10% is recycled (World Bank). At the same time, over 70% of Nigerians lack easy access to recycling facilities, locations, or clear recycling processes (NESREA, 2022), and 48% of the population has poor or no internet connectivity (NCC, 2023), making most app-based recycling platforms inaccessible to low-income and marginalized communities. CleanUp MDC was created to bridge this gap by enabling users to dial a toll-free number on any basic phone (cell-phone) and interact with our AI in Hausa, Yoruba, Igbo, Pidgin, or English with no language barrier, our AI identify users location, connect with nearby verified waste collectors, and user earn income from recyclables. Our target market includes low-income households, women, youth, informal waste pickers, and underserved urban and peri-urban communities across Nigeria, as well as recycling agents and aggregators seeking reliable recyclable feedstock. To date, we have onboarded over 30,163 active users from underserved communities, 17,907 of them women, and facilitated the recovery of more than 10,000 tons of plastic waste, positioning our operations to contribute to an estimated 25,000 tons of CO₂ emissions reduction annually, equivalent to removing about 4,000 fuel-powered cars from the road each year. We partner with the Waste Pickers Association of Nigeria (WAPAN), we are scaling nationwide with the long-term goal of expanding across Africa. Our main goals are to expand access to recycli",true,"The Commissioner for Environment and Natural Resources of Kaduna State Government, Nigeria Share's the link with my startup",Received,https://drive.google.com/drive/folders/1yuSmCXSrORvgjYkHSpb5fFz9qFNe7FJD?usp=drive_link,
|
||||||
|
,Nesphory Mwambai,+254714520023,Envoyer le message,mwambai@seamo.earth,seamo.earth,"Nesphory Mwambai, Lewis Kimaru","Mombasa, Kenya","Africa, Kenya",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-08-22,Restoration of marine habitats & ecosystems,"seamo.earth initiative focused on utilizing artificial intelligence (AI) to explore, document, monitor, and preserve the mariculture and seascapes of the Pwani regions. This project aims to enhance our understanding and protection of marine environments through the development of eco-friendly and climate adaptive technologies.",true,email news letter,Received,https://drive.google.com/drive/folders/1eOyDGZwwlNNAzbwwC-CUVmJi4gM3kDLI?usp=drive_link,
|
||||||
|
,Fiona McOmish,+447722083419,Envoyer le message,fiona.mcomish@algae-scope.com,Algae Scope,Natasha Yamamura; Alejandra Noren; Farshid Pahlevani,"Venise, Vénétie, Italie","Europe, Italia",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-12-16,Technology & innovations,We replace toxic PFAS chemicals in textiles with a water- and fire-resistant coating made 100% from seaweed. We sell our high-performing solution to textile manufacturers and formulators in a 'drop-in' format.,true,LinkedIn,Received,https://drive.google.com/drive/folders/1KA4GZXWQhMCodASq_15Ksx5yzak5AwpT?usp=drive_link,
|
||||||
|
,Veronica Nzuu,+254748488312,Envoyer le message,veramichael2000@gmail.com,Furies,Angelo Mulu,"Mombasa, Kenya","Africa, Kenya",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2023-05-29,Consumer awareness and education,"My project focuses on empowering children and youth in my community to take action on plastic pollution through simple, community led learning and action. The objective is to build awareness, responsibility, and leadership by combining environmental education with practical activities such as waste segregation, plastic collection, creative upcycling, and community dialogue. By using participatory and inclusive approaches, especially for girls and marginalized youth, the project aims to strengthen community ownership of sustainability solutions and inspire long term behavior change at the local level.",true,Social Media linked in,,,
|
||||||
|
,Neville Agesa,+254796438122,Envoyer le message,agesanevil@gmail.com,Sustainable Blue Food & Livelihoods Innovation,"Robert Meya,Hannah Mathenge,JohnChaka","Mombasa, Kenya","Africa, Kenya",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2023-02-01,Sustainable fishing and aquaculture & blue food,"The Tsunza Community, located on Kenya’s South Coast in Kwale County, is a vital ecological hub linking mangrove forests, wetlands, and the Mwache River estuary. These interconnected ecosystems support fisheries, biodiversity, and local livelihoods but face increasing pressure from degradation, pollution, and declining fish stocks.
|
||||||
|
|
||||||
|
This project aims to protect and restore mangrove and wetland ecosystems while strengthening sustainable blue livelihoods. Through community-led mangrove restoration, marine pollution awareness, and youth and women engagement in sustainable fisheries and aquaculture practices, the project promotes ocean protection alongside economic resilience.
|
||||||
|
|
||||||
|
By integrating nature-based solutions, environmental education, and livelihood innovation, the initiative positions Tsunza as a scalable model for community-driven ocean conservation and sustainable development.",true,Gensea opportunities,,,
|
||||||
|
,Anshika Sarraf,+917897130506,Envoyer le message,anshika.sarraf_ug2024@ashoka.edu.in,Auralis Blue,Anshika Sarraf,"Sonipat, Haryana, Inde",Asia,Ashoka University + Sonipat,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Reduction of pollution (plastics chemicals noise light...),"Auralis Blue is tackling a problem few people see but that is harming our oceans: underwater noise pollution. Ships, ports, and offshore construction create constant sound that travels far underwater, interfering with how whales, dolphins, and fish communicate, migrate, and reproduce. Auralis Blue measures this invisible threat and turns it into clear, actionable data, helping maritime stakeholders protect marine life while continuing sustainable operations.
|
||||||
|
|
||||||
|
Underwater noise is an invisible threat, but its effects are very real: studies show that marine mammals rely on sound to survive, and high noise levels can cause stress, confusion, and even death in fish populations. Despite this, there are almost no tools that measure or manage noise systematically. Auralis Blue fills this gap, providing a science-based, scalable solution that can protect marine ecosystems worldwide.
|
||||||
|
|
||||||
|
Objectives:
|
||||||
|
1) Measure and Map noise pollution
|
||||||
|
2) Marine life protection
|
||||||
|
3) Encourage better and sustainable practices
|
||||||
|
4) Support policy, investment & encourage systemic change in blue economy",true,LinkedIn,,,
|
||||||
|
,OLUTOKI FEYISHAYO FUNMI,+2348038226106,Envoyer le message,rebugssolutions@gmail.com,Operation feed the children,"Adewuyi Feranmi, Olutoki sewafunmi victor, Ayodele joy, Babalola gbenga",Nigéria,"Africa, Nigeria",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-12-12,Consumer awareness and education,"To develop and inplement a sustainable food production (crops and animals) that specifically reduces a known threat to the Ocean (e. go, pollution, overfishing pressure, habitat destruction).
|
||||||
|
|
||||||
|
We will take measures using circular economy, by developing a system where waste products from our farm are treated and used in a way that prevent them from entering marine ecosystem
|
||||||
|
|
||||||
|
We will work on water management and pollution reduction and sustainable sourcing /supply chain",true,,Received,https://drive.google.com/drive/folders/11jiB2-6NPaJrDZgXg5q2p1yAvfmWdW2b?usp=drive_link,
|
||||||
|
,Chelsey Karbowski,+19026314362,Envoyer le message,chelsey.m.karbowski@gmail.com,Mikjikj Mniku Consulting Ltd.,Chelsey Karbowski,"Dartmouth, Halifax, NS, Canada",Canada,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2025-03-01,Other,"Mikjikj Mniku, Mi’kmaq for “Turtle Island”, is an Indigenous-led consulting firm working at the intersection of ocean protection, community governance, and workforce development.
|
||||||
|
|
||||||
|
We help governments, philanthropies, and conservation organizations design ocean and climate initiatives that last beyond funding cycles by embedding Indigenous knowledge, ethical engagement, and local stewardship from the start.
|
||||||
|
|
||||||
|
Our work focuses on strengthening Indigenous and coastal governance, building inclusive workforce pathways in fisheries, marine monitoring, and ocean-adjacent clean energy, and making social impact measurable and defensible through socio-economic and SROI frameworks.
|
||||||
|
|
||||||
|
In a global push to protect more ocean faster, we ensure protection efforts are community-supported, socially resilient, and future-proofed, because conservation only succeeds when the people closest to the ocean are empowered to carry it forward.",true,Linkedin,,,
|
||||||
|
,Rasheed Aliu,+2348160238021,Envoyer le message,rasheedofpod@gmail.com,Pod,"Rasheed Aliu, Gabriel Simon , Habeeb Lasisi and MaryJudith Chiamaka","Lagos, Nigéria","Africa, Nigeria",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2025-05-06,Reduction of pollution (plastics chemicals noise light...),"Land-based pollution is the largest contributor to ocean degradation, yet sanitation failures in coastal communities remain overlooked. In flood-prone coastal regions of Africa, fragile septic systems collapse during flooding, releasing untreated human waste into groundwater, rivers, lagoons, and ultimately the ocean.
|
||||||
|
I witnessed this firsthand in coastal Lagos, Nigeria, when flooding destroyed local sanitation systems and a neighbour’s 4-year-old daughter died from cholera, a preventable waterborne disease. This tragedy reflects a systemic failure. Over 90% of Nigerian households rely on sanitation systems that leak sewage, contributing to 117,000 annual child deaths from waterborne diseases, according to UNICEF.
|
||||||
|
In the absence of centralized wastewater infrastructure, outdated septic tanks costly to build and maintain are frequently evacuated or overflow during floods, with waste discharged into coastal waters. This drives marine pollution, eutrophication, biodiversity loss, and degradation of near-shore ecosystems critical to food security.
|
||||||
|
At Pod we design and manufacture LoopBox, LoopBox is a solar-powered, IoT-enabled, self-contained sanitation system designed for coastal and flood-prone communities. Unlike traditional soakaway pits that leak, our tech uses embedded sensors and microbial treatment to track, treat, and recycle human waste into reusable water. Through our cloud dashboard, users and local authorities can monitor sanitation performance and water quality remotely. We also provide nearby borehole treatment as a service. LoopBox is 5x more cost-effective than conventional systems, eliminates 100 dollars/year in waste evacuation costs, and requires minimal space. Built with scalable hardware and software, it is designed to be deployed across low-income, climate-vulnerable communities bringing safety, sustainability, and data-driven decision-making to sanitation in Nigeria and Africa.
|
||||||
|
The project delivers a flood-resilient, decentralized sanitation s",true,BFA Global TECA Alumni Group( Tyler),Received,https://drive.google.com/drive/folders/1Ur6FAveOOAtS77TOGXuLGbVfqTF8mG9p?usp=drive_link,
|
||||||
|
,Christian Mwijage,+255711457346,Envoyer le message,chrissmwijage@gmail.com,ECOACT Tanzania,"• Mr. Bernard Ernest, Technical Director overseeing all production activities, holds a Master of Engineering in Biochemical Engineering. Mr. Christian Mwijage, Managing Director responsible for overall operations, holds a Bachelor’s degree in Business Administration and Marketing. Ms. Elineca Ndowo, Chief Finance Officer, holds a Master’s degree in Project Management and Financing from the University of Dar es Salaam.","Dar es Salam, Tanzanie","Africa, Tanzania",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2022-12-21,Reduction of pollution (plastics chemicals noise light...),"Every year, 9 million tonnes of plastic waste enter our oceans, polluting marine ecosystems and threatening ocean life. At this rate, by 2050 the ocean could contain more plastic than fish. At the same time, the world loses over 2 billion trees annually to meet the demand for timber in the furniture and construction industries—making deforestation the second leading driver of climate change.
|
||||||
|
We address both crises through a chemical-free, energy-efficient, AI-powered technology that transforms ocean-bound plastics and post-consumer packaging waste into high-quality, sustainable materials for furniture, building, and construction applications. By converting low-value, hard-to-recycle multi-layer plastic (MLP) waste into durable products, we are advancing the circular economy and giving new life to materials that would otherwise damage the environment.
|
||||||
|
We address one of the most persistent challenges in the plastics value chain: waste streams that lack viable conventional recycling pathways. We focus specifically on two difficult-to-recycle categories - multi-layer plastics (MLP), which combine multiple plastic layers and/or aluminum foil, and mixed plastic waste that cannot be economically or efficiently segregated. Globally, an estimated 6 billion tons of plastic waste have been generated, approximately 14% of which consists of MLP. Due to technical and economic limitations, these materials are typically landfilled, incinerated, or left uncollected, contributing significantly to environmental pollution and ecosystem degradation.",true,Social Media,Received,https://drive.google.com/drive/folders/1xCJ_8EpTEdBORiJHYwIRZO22z8e54fbx?usp=drive_link,
|
||||||
|
,Olaleye Rofiat Olayinka,+2348038877293,Envoyer le message,olaleyerofiatyinka@gmail.com,Eco Heroes Nigeria limited,Olaleye Rofiat Olayinka Salaam Lateef Oladimeji Akinsanya Dorcas Olaleye Hassan Ogundairo Ganiyat,"Lagos, Nigéria","Africa, Nigeria",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2021-11-08,Reduction of pollution (plastics chemicals noise light...),"Eco Heroes is an incentive-based, tech-enabled recycling solution that prevents ocean-bound plastic waste from entering rivers and marine ecosystems. The project mobilizes communities to collect and exchange post-consumer plastic for rewards such as cash and essential services, creating a reliable supply of recovered plastic while improving livelihoods. Recovered materials are recycled and transformed into value-added products, including sewing threads, ensuring financial sustainability and scalable impact. The objective is to measurably reduce plastic pollution, create local economic value, and build a replicable model for coastal and river-connected communities.",true,I learned about the Monaco Ocean Protection Challenge through my involvement in an entrepreneurship and innovation programs focused on the blue economy and plastic pollution solutions.,,,
|
||||||
|
,Shamim Wasii Nyanda,+255764190074,Envoyer le message,shamim@sunwaveltd.com,SUNWAVE,Ridhiwan Mseya,"Dar es Salam, Tanzanie","Africa, Tanzania",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-03-01,Sustainable fishing and aquaculture & blue food,"SUNWAVE provides small-scale fishers in Tanzania with solar-powered ice-making units to reduce fish spoilage. These machines, powered by solar energy, offer a sustainable and cost-effective solution to fish preservation, especially in remote areas where access to the power grid is limited. By keeping fish fresh for longer, these units help fishers reduce spoilage, maintain higher-quality products, and increase income. The ice-making machines are operated by trained personnel to ensure proper use and efficiency.",true,It was shared by SUNWAVE's Advisory Board member.,Received,https://drive.google.com/drive/folders/1qoWF1b-GIhjylYcGloZy1xnMZRjB0XEO?usp=drive_link,
|
||||||
|
,Lorna Mudegu,+254718059337,Envoyer le message,lornaafwandi@gmail.com,WAVU,Don Okoth | Vincent Oduor | Chris Munialo | Loise Mudegu,"Kisumu, Kenya","Africa, Kenya",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-07-30,Sustainable fishing and aquaculture & blue food,"WAVU is a market and aggregation platform that connects verified aquaculture producers to buyers through organised, predictable supply chains.
|
||||||
|
|
||||||
|
In many coastal and inland markets, fish buyers source from informal channels where farmed fish and wild-caught fish are indistinguishable. This lack of separation sustains demand for capture fisheries and contributes to overfishing in already stressed marine and freshwater ecosystems. By aggregating aquaculture producers, forecasting demand, and directing buyers toward farm-based supply, WAVU helps shift market demand away from unregulated wild catch.
|
||||||
|
|
||||||
|
As more buyers rely on planned aquaculture sourcing, pressure on wild fisheries is reduced while livelihoods are supported through sustainable fish production. Each tonne of farmed fish absorbed into formal markets represents demand that would otherwise be met through extraction from natural fish stocks.
|
||||||
|
|
||||||
|
WAVU builds on ongoing operations in East Africa and offers a scalable, market-driven pathway to reducing pressure on wild fisheries in regions facing overfishing and informal fish trade.",true,LinkedIn,Received,https://drive.google.com/drive/folders/1_Y9YW-Y_kd2Tpz80fH5juc9Ol1TR7DKq?usp=drive_link,
|
||||||
|
,Francesco Ruscio,+393756436501,Envoyer le message,francesco.ruscio@ing.unipi.it,PerSEAve,"Francesco Ruscio, Simone Tani, Alessandro Gentili","Pise, Toscane, Italie","Europe, Italia","University of Pisa, Pisa, Italy",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Technology & innovations,Enhance monitoring of benthic habitats using robotics and artificial intelligence.,false,linkedin,Ignore,,
|
||||||
|
,João Manuel de Gouveia Firmino,+351969136436,Envoyer le message,9822@novalaw.unl.pt,Atlantic Fish Sauce,João Firmino / Duarte Fernandes,"Île de Madère, Portugal","Europe, Portugal",NOVA University Lisbon (Nova School of Business & Economics) / University of Madeira (Faculty of Sciences and Engineering),the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Other,"Project idea: Convert local fish discards on Madeira into a hygienic, fermented fish sauce (small-batch artisanal → scalable).
|
||||||
|
|
||||||
|
Objectives: Reduce waste; add value for fishers; create local jobs; supply restaurants/retail; position as circular blue-economy premium product.
|
||||||
|
|
||||||
|
Key details: Source = local landings; partners = fishers + certified processor + food-safety lab; compliance = HACCP/food regs; go-to-market = horeca, gourmet stores, e-commerce; pilot → scale path.",false,Through Fondation Prince Albert II de Monaco.,Received,https://drive.google.com/drive/folders/1Pbf4FwTfAfqklel_a94CYA7dZsmvPfGH?usp=drive_link,
|
||||||
|
,Jonas Wüst,+41766307924,Envoyer le message,jonas@tethys-robotics.ch,Tethys Robotics,Pragash Sivananthaguru,"Zurich, Suisse","Europe, Switzerland",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-08-15,Technology & innovations,"Tethys Robotics builds compact autonomous underwater robots that replace emission-intensive vessel operations with remote, low-impact subsea inspection. Our goal is to make offshore maintenance safer and more sustainable by reducing CO₂ emissions, preventing environmental damage through early detection, and improving the reliability of renewable marine infrastructure.",false,BRIDGE by Innosuisse forward us.,,,
|
||||||
|
,James Kalo Malau,+6787774965,Envoyer le message,malau_jk@hotmail.com,Coral Reforestation,"John Maliu, Josue Jimmy, Nalo Samuel, Manu Roy, James Sulu","Port Vila, Shefa, Vanuatu",Oceania,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2026-01-01,Sustainable fishing and aquaculture & blue food,,true,Funds for NGOs Premium,,,
|
||||||
|
,Amelia Martin,+18606824426,Envoyer le message,amelia@mudratsurf.com,Mud Rat,"Jack Tarka, Patricio Acevedo, Brian Lassy","Mansfield, CT, États-Unis",US,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2023-06-13,Reduction of pollution (plastics chemicals noise light...),We manufacture an eco-friendly alternative to marine foam (marine grade styrofoam).,true,Google!,Ignore,,
|
||||||
|
,Rasmus Borgstrøm,+4527117113,Envoyer le message,rasmus@blueplanetinnovators.com,FlowMinerals,"Rasmus Borgstrøm, Esben Jessen","Copenhague, Région de la Capitale, Danemark","Denmark, Europe",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2023-09-24,Mitigation of ocean acidification,"FlowMinerals captures CO₂ from seawater and converts it into fossil-free calcium carbonate, contributing to the mitigation of ocean acidification while reducing reliance on land-based limestone mining. The solution enables industrial decarbonization using ocean-compatible materials, with a strong focus on environmental safety and minimal marine impact.
|
||||||
|
www.FlowMinerals.com",true,LinkedIn,,,
|
||||||
|
,Fritz Noel Bayong Momha,+32467868495,Envoyer le message,fbayong@balazstudio.com,GeoCosta : Application of Geodesign to understand and Innovate in Coastal Protection Planning /Balaz Studio,Fritz Bayong,"Douala, Cameroun","Africa, Cameroun",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2021-02-07,Technology & innovations,"GeoCosta : Application of Geodesign to understand and Innovate in Coastal Protection Planning / Balaz Studio
|
||||||
|
Objectives :
|
||||||
|
-Understand the development of coastal protection in order to contribute to a concerted management focused on adaptation and coastal resilience
|
||||||
|
- Use the concepts of Geodesign and coastal resilience, landscape approach, and consultation in our diagnosis of the protective planning process
|
||||||
|
- Mapping of infrastructures and different actors will illustrate the actions and scenarios of the future vision of this site.",true,LinkedIn,Received,https://drive.google.com/drive/folders/10n1oNk59yYytGy6XUyr4sGFUU7a00wtF?usp=drive_link,
|
||||||
|
,Irina Kharitonova,+77012141077,Envoyer le message,irinakharitonova0201@gmail.com,EcoPlaton Tracker: From Land to Ocean,"Irina Kharitonova, Alexandra Kharitonova, Platon Nechayev","Karaganda, Kazakhstan",Asia,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2025-07-07,Consumer awareness and education,"EcoPlaton Tracker is a digital educational and action-oriented platform aimed at protecting oceans by addressing the root causes of pollution on land. The project helps children and families understand how everyday habits—plastic use, chemical products, water consumption, and carbon footprint—affect rivers, lakes, seas, and ultimately the oceans.
|
||||||
|
The platform combines carbon and water impact tracking, eco-challenges, audio guides, and storytelling, including stories about lakes, oceans, and industrial water pollution. It guides users from awareness to action and delivers real environmental impact: part of the project’s revenue supports reforestation and environmental initiatives, with over 1,300 trees already planted in industrial regions of Kazakhstan.
|
||||||
|
EcoPlaton Tracker integrates a Water & Ocean Impact Tracker module that visualizes the “land–water–ocean” pollution pathway and encourages measurable behavior change.",true,We learned about the Monaco Ocean Protection Challenge last year through Instagram and have been preparing our application since then.,Received,https://drive.google.com/drive/folders/12OI_9hLrNJU5Of7CiccMCIF_CvGUCUPU?usp=drive_link,
|
||||||
|
,Aki Allahgholi,+41763879261,Envoyer le message,aki@corall.eco,CORAlliance,"Chris Glaser, Peach Zwyssig, Tamaki Bieri, Dave Gulko","Zurich, Suisse","Europe, Switzerland",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2025-08-13,Restoration of marine habitats & ecosystems,"We will solve the extreme coral restoration bottleneck when it comes to outplanting. The logistical limitations of farming, transporting and outplanting cannot be overcome through the classical methods as of now. Our patented coral paint and spraying mechanism will solve that hurdle.",false,LinkedIn,Received,https://drive.google.com/drive/folders/1M8KGN87ZSTEqFP8T2eUccYOE7K7DZNrV?usp=drive_link,
|
||||||
|
,Nilas Neuhauser,+41792977194,Envoyer le message,nilas.neuhauser@aris-space.ch,Nautilus,45+ members (Management -> PM: Phillip Zenger ; DPM: Nilas Neuhauser ; SE: Matias Betschen),"Zurich, Suisse","Europe, Switzerland","ETH Zurich, Zurich",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Technology & innovations,"The NAUTILUS team is developing the latest generation, and most advanced Autonomous Underwater Glider with the goal of flexibly facilitating the collection of crucial data for aquatic research. By doing so, we seek to create a cost-effective and minimally invasive aquatic research robot.
|
||||||
|
After conducting first successful tests this year, we seek to continue testing our glider in Swiss lakes until summer and then, in September, set off for a 2 week mission to test in the Norwegian Ocean.
|
||||||
|
Find our website here: https://aris-space.ch/our-projects/nautilus/",true,from the 1000 Ocean Startups LinkedIn,,,
|
||||||
|
,Peter Teye Busumprah,+233544671951,Envoyer le message,petervegan1223@gmail.com,African Ocean Biodiversity Atlas,Mavis Essilfie,"Accra, Ghana","Africa, Ghana",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-01-01,Technology & innovations,"This initiative aims to bridge the gap in ocean data across Africa by establishing a standardized platform for accessing African Ocean Biodiversity information. The project involves developing an African Ocean Biodiversity Atlas that provides detailed data on Blue Carbon and Fisheries ecosystems, including GPS coordinates, high-resolution images, and videos illustrating the state of coastal environments throughout Africa. To ensure accessibility, we are utilizing affordable, locally developed technologies and multifunctional ocean applications to map key ecosystems such as fisheries, seaweeds, seagrasses, mangroves, and other ocean biodiversity ecosystems along the continent’s coastlines.
|
||||||
|
|
||||||
|
Our team has grown significantly from 8 to 40 members, representing 20 African nations. Currently, over 800 users are engaged, and a pilot map encompasses ten African countries. We anticipate generating approximately $240,000 annually from app downloads and technology sales, with projected monthly revenues of about $20,000. This includes $7,000 from subscriptions, $7,000 from data sales, $3,000 from licensing, and $3,000 from consulting services.
|
||||||
|
|
||||||
|
The database is designed for policymakers and academic institutions, offering precise data crucial for policy formulation, research, and publication activities. Additionally, we aim to involve private sector stakeholders who depend on reliable data to inform their investments in a sustainable blue economy.
|
||||||
|
|
||||||
|
Key features include the development of a Fisheries Atlas and a Blue Carbon Biodiversity initiative focused on Africa’s landing beaches, providing strategic recommendations for the establishment of Marine Protected Areas (MPAs). The project also promotes data sharing among local indigenous fishermen and enhances understanding aligned with the UN Ocean Decade objectives. It will create a comprehensive data repository covering various marine species, including fish, mangroves, algae, and seaweeds.
|
||||||
|
|
||||||
|
Links:
|
||||||
|
https://oceandecade.org/action",true,MOPC Linkedin.,,,
|
||||||
|
,Carol Nkawaga Moonga,+260979164462,Envoyer le message,moongacaroln@gmail.com,Kacachi General Dealers,Cathrine Kapesha,Zambie,"Africa, Zambia",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-07-11,Sustainable fishing and aquaculture & blue food,,true,I saw an advertisement on LinkedIn,Received,https://drive.google.com/drive/folders/1wEWiGREhq-dWPkFqGqmSK89PcuOjhsXX?usp=drive_link,
|
||||||
|
,Coral Bisson,+377643915342,Envoyer le message,coralbisson@icloud.com,Corali,Coral Bisson,"St Helier, Jersey","Europe, Jersey",International University of Monaco,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Reduction of pollution (plastics chemicals noise light...),- Reduction of ocean plastics through development of swimwear using recycled ocean plastics,true,University,,,
|
||||||
|
,Pavel Kartashov,+38975588771,Envoyer le message,pavel.k@wavespark.co,WaveSpark Green Marine Energies,"Pavel Kartashov, Rodrigo Caba, Francisco Perez, Glib Ivanov","Skopje, Macédoine du Nord","Europe, Macedonia",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2025-03-05,Technology & innovations,"Scalable and capital-light hybrid ocean energy platforms harvesting wave, sun and wind energy in near-shore areas for shore and offshore energy end-users",false,Social media post,Received,https://drive.google.com/drive/folders/1vdcWHlPUURdN69T-Ek7wsqOTrLNaODq0?usp=drive_link,
|
||||||
|
,Raphaëlle Guénard,+33663688277,Envoyer le message,contact@filae.eu,Filae,Raphaëlle Guénard & Killian Bossé,"Marseille, Provence-Alpes-Côte d'Azur, France","Europe, France",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2025-03-21,Reduction of pollution (plastics chemicals noise light...),"Filae transforms end-of-life fishing nets into ultra-light, modular supports for plant-based shading and greening (façades and canopies), helping cool down dense urban areas without heavy structures.
|
||||||
|
Our goal is to scale a Mediterranean circular model, from local net collection to on-site deployment, reducing waste and embodied carbon while boosting thermal comfort and biodiversity through real-world pilots.",true,"from Marine Jacq-Pietri, Coordinatrice du Monaco Ocean Protection Challenge",Received,https://drive.google.com/drive/folders/1QriiO8XKqx97efs-dAPqHDMhcSy9HjHH?usp=drive_link,
|
||||||
|
,Anastasiia,+380680650309,Envoyer le message,grozdova.anastasiia@gmail.com,Innovations in ocean environment,Darina Mitina,Ukraine,"Europe, Ukraine",,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Technology & innovations,Take technology onto another level,true,Social media marketing,,,
|
||||||
|
,Raismin Kotta,+6281342018565,Envoyer le message,raisminkotta88@gmail.com,The Pearls cultuvation & Pearls jewelry,"Raismin Kotta, aya sophia, Lalu harianza,asril junaidy","Lombok, Indonesie",Asia,"45 University, Mataram Indonesia",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Sustainable fishing and aquaculture & blue food,Sustainability fisheries and Aquaculture,true,I hear and read MOPC in website and interested to apply,,,
|
||||||
|
,Vincent Kneefel,+31622514465,Envoyer le message,vincent@vitalocean.io,Vital Ocean,Joi Danielson,"Amsterdam, Hollande Septentrionale, Pays-Bas","Europe, Netherland",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-04-16,Technology & innovations,,true,Linkedin,,,
|
||||||
|
,Maaire Gyengne Francis,+233208397960,Envoyer le message,gyengnefrancis90@gmail.com,WasteTrack,"Frank Faarkuu, Prosper Dorfiah","Accra, Ghana","Africa, Ghana",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2025-01-01,Reduction of pollution (plastics chemicals noise light...),"Problem and Solution
|
||||||
|
|
||||||
|
Urban cities across Africa face a severe plastic waste crisis driven by rapid population growth, heavy consumption of plastic-packaged products, and inadequate formal waste management infrastructure. Most households lack convenient, reliable, and affordable waste disposal options, forcing them to depend on informal collectors with limited capacity and inconsistent schedules - or resort to harmful practices such as burning, burying, or illegally dumping plastic waste in gutters, waterways, and open spaces. This results in widespread pollution, health hazards, clogged drainage systems, flooding, and the loss of valuable recyclable material that could support local and global circular economy markets. Also, recycling companies lack consistent, traceable, and high-quality access to plastic feedstock.
|
||||||
|
|
||||||
|
Our solution is to develop an AI-powered platform that helps urban households dispose of plastic waste by connecting them with local collectors through image, video, or weight-based pricing and cashless payments. It tackles severe plastic pollution in African cities caused by limited collection capacity and unsafe disposal practices. With millions of households generating increasing waste, the market potential is vast across Ghana and other rapidly urbanizing regions. Once consistent collection volumes are reached, WasteTrack will expand into a global plastic trading marketplace, enabling recyclers worldwide to buy verified, traceable plastic waste - positioning the startup as a major player in the circular plastics economy.
|
||||||
|
|
||||||
|
Our AI-driven waste management and digital payment solution is designed to make plastic disposal easy, convenient, and traceable for urban households. Key features will include photo, video, or weight-based AI analysis to estimate disposal fees; secure digital payments; GPS-linked pickup requests; and unique tracking codes for every waste package. The platform also supports community micro-dumpsites for flexible drop-off and pr",true,Google search,Received,https://drive.google.com/drive/folders/1Rv9W6h5zQESX7A68bQio5JWy5TML86rH?usp=drive_link,
|
||||||
|
,Shelby Thomas,+13866897675,Envoyer le message,admin@oceanrescuealliance.org,Coastal Resilience Solutions: WeRestore,"Dr. Shelby Thomas, Dr. David Weinstein, Lindsay Humbles,","FL, États-Unis",US,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2019-12-01,Restoration of marine habitats & ecosystems,"Ocean Rescue Alliance International, through its Coastal Resilience Solutions for-profit arm and the We Restore initiative, deploys scalable living shoreline and hybrid reef technologies to restore degraded coastal and marine ecosystems while enhancing climate resilience for vulnerable communities. The project’s objective is to deliver measurable ocean biodiversity recovery, erosion reduction, and carbon co-benefits through science-based, nature-positive infrastructure that can be replicated regionally and globally.",true,via Email Newsletter,,,
|
||||||
|
,Danail Marinov,+359895497694,Envoyer le message,dmarinov@redget.io,RedGet.io,"Danail Marinov, Dobromir Balabanov, Alexander Valchev","Sofia, Bulgarie","Bulgaria, Europe",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-12-01,Technology & innovations,"What it is (TRL 4–5): Pilot-ready, AI collaborative platform for GHG emissions Scope 1–3 monitoring, compliance reporting and forecasting for ports/terminals/shipping companies. RedGet.io was among the selected companies and participated in ADT4Blue, EY Startup Academy Germany, Blue Readiness Assistance and Green Marine Med (by Port of Barcelona) programs.
|
||||||
|
Value: Up to 60% reduction in reporting efforts and costs, emission forecasting for EU-ETS regulations, AI maritime assistant and decision-ready visibility to plan and verify decarbonization.
|
||||||
|
Status & partners: Confirmed pilot with Port of Gdynia (Jan 2026) and Port of Talling (Jan 2026); negotiations with Port of Valencia, Port of Huelva, and EY Bulgaria.",false,A friend of mine shared this opportunity to me,Received,https://drive.google.com/drive/folders/1xElP9xEg6r2R0lDElZD9n6v4FOW9CCEp?usp=drive_link,
|
||||||
|
,Mohammad Badran,+18733557575,Envoyer le message,ceo@martropic.com,MarTropic Canada Inc.,Mohammad Badran and Hala Marouf,"Aqaba, Jordanie",Asia,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-08-30,Restoration of marine habitats & ecosystems,"Vision
|
||||||
|
Our vision is to be a global supporter to marine and coastal ecosystems’ stewardship, fostering a future where tropical and subtropical marine environments thrive in harmony with human activities. We envision vibrant resilient marine ecosystems that support biodiversity, enhance climate stability, and contribute to viable sustainable development with diversified livelihoods for the local communities.
|
||||||
|
|
||||||
|
Mission
|
||||||
|
Our mission is to deliver innovative and sustainable management solutions that advance development in tropical and subtropical marine and coastal areas maintaining ecosystems’ health and resilience. We endeavor to harness broad stakeholders’ involvement, community engagement, scientific research, local knowledge, and cutting-edge technology for supporting development in tropical seas to protect and restore ecosystems’ biodiversity and functionality while achieving stakeholders’ interests and local communities’ contentment.
|
||||||
|
|
||||||
|
Approach
|
||||||
|
Our approach is to harness the local knowledge and expertise in all our projects. We will do consultancy work and target nationally, regionally and internationally supported initiatives. We will keep a small team for coordination and management, but our heavy weight will be the local performers in the field. Implementing multiple local projects, we will build an effective Platform for Global Dialogue and Exchange of Experience
|
||||||
|
|
||||||
|
Objectives
|
||||||
|
Our objectives are highly ambitious and divers. We realize the hard work ample time they need to be achieved. But we trust that our approach that counts on the local knowledge and expertise will make our mission achievable. Our objectives include:
|
||||||
|
Conservation and Restoration
|
||||||
|
o Develop and implement science-based and local knowledge strategies for conservation and restoration of critical marine habitats and the biodiversity they support, including coral reefs, mangroves, and seagrass beds.
|
||||||
|
o Monitor and assess coastal and marine ecosystems’ health and the stressors they face to gu",true,From Canada's Ocean Supercluster,,,
|
||||||
|
,Adrian Colline Odira,+254742838455,Envoyer le message,adrian@riofish.co.ke,Rio Fish Limited,"Adrian Colline Odira, Loren Edwina Odira","Nairobi, Kenya","Africa, Kenya",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2018-08-20,Sustainable fishing and aquaculture & blue food,"Our project aims to build a fully circular, climate smart aquaculture model that reduces pressure on overfished natural water bodies while empowering coastal and lakeside communities. By integrating sustainable fish production, renewable energy systems (biogas and solar), digital traceability, and community led cage farming, we create an alternative source of affordable, high quality protein that eases exploitation of lake and ocean ecosystems.
|
||||||
|
|
||||||
|
Objectives
|
||||||
|
|
||||||
|
Reduce dependence on open water fishing by scaling sustainable cage and pond aquaculture systems.
|
||||||
|
|
||||||
|
Empower women and youth with ownership of production units, fair market access, and technical training.
|
||||||
|
|
||||||
|
Increase ocean and freshwater protection by promoting regenerative practices, responsible feed use, and cold-chain efficiency to minimise post harvest loss.
|
||||||
|
|
||||||
|
Deploy digital tools to track origin, ensure transparency, and support ecosystem friendly decision making.
|
||||||
|
|
||||||
|
This approach strengthens food security, grows blue economy incomes, and protects aquatic ecosystems through a scalable, community-centered model.",true,LinkedIn,,,
|
||||||
|
,Ramsay Bader,+16468972588,Envoyer le message,Ramsay.Bader@gmail.com,PosidoniaGuard,Ramsay Bader. Caroline Hulbert.,"New York, NY, États-Unis",US,University of St Andrews. United Kingdom.,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Blue Carbon,"PosidoniaGuard is a turnkey service that helps Mediterranean marinas and coastal authorities stop anchor damage to Posidonia oceanica seagrass meadows by installing seagrass-safe “eco-moorings”, managing no-anchoring zones via a simple booking app, and quantifying the blue-carbon and biodiversity benefits for funders and regulators. Posidonia meadows are critical “blue forests” that store large amounts of carbon, support fisheries and protect coasts, but up to about 34% have already been lost, with tens of thousands of hectares damaged annually by anchoring.
|
||||||
|
|
||||||
|
Objectives:
|
||||||
|
|
||||||
|
1. Protect and restore Posidonia meadows by replacing destructive chain moorings and ad-hoc anchoring with certified eco-moorings in high-pressure bays.
|
||||||
|
|
||||||
|
2. Guide boaters away from seagrass using a digital map and reservation system that clearly marks no-anchor zones and available eco-moorings.
|
||||||
|
|
||||||
|
3. Measure and monetise impact by estimating hectares of seagrass protected and associated blue-carbon storage and ecosystem-service value, creating reporting for marinas, municipalities and impact investors.",true,Through my University.,,,
|
||||||
|
,ssentubiro billy,+256708630034,Envoyer le message,lemanfoundation16@gmail.com,schoolarships,Nakayulu Grace and ssentubiro billy,"Kampala, Ouganda","Africa, Ouganda",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2016-08-13,Capacity building for coastal communities,allow needy children access quality education,true,via social media,,,
|
||||||
|
,Hasan Noor Ahmed,+491737752964,Envoyer le message,biland.awdal.org@gmail.com,BlueGuard Africa – Community-Driven Ocean & Coastal Protection Innovation Hub,"Hasan Noor Ahmed – Chairman & Founder Amina Abdillahi Ibrahim – Program Director (Health & Nutrition) Mohamed Abdi Warsame – Finance & Administration Officer Hodan Ismail Ali – Climate & Environment Program Lead Abdirahman Yusuf Farah – Monitoring, Evaluation & Learning Officer Fardowsa Ahmed Jama – Community Outreach & Protection Coordinator","Boorama, Awdal, Somalie","Africa, Somalia",Bilan Awdal Organization – Training & Capacity Development Unit,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Capacity building for coastal communities,"The Blue Coast Guardians Initiative is a youth-led, community-centered program designed by Bilan Awdal Organization to combat coastal pollution, restore marine ecosystems, and create sustainable blue-economy opportunities along the Somaliland/Somalia coastline.
|
||||||
|
Our approach combines innovative low-cost technologies, community livelihoods, and education, enabling coastal communities to protect the ocean while improving their economic resilience.
|
||||||
|
|
||||||
|
The project targets urgent threats in the region, including plastic pollution, illegal fishing, coastal erosion, and the loss of marine biodiversity.",false,Fund for NGO,Received,https://drive.google.com/drive/folders/1Oz9lQCfhQqw818QegNj9S_SvArSQwZFw?usp=drive_link,
|
||||||
|
,Nitya Gunturu,+917680093169,Envoyer le message,nityagunturu95@gmail.com,MycoWrap,Nitya Gunturu and Avni Mishra,"Jaipur, Rajasthan, Inde",Asia,"Ashoka University, India",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Reduction of pollution (plastics chemicals noise light...),"We aim to create innovative, sustainable mycelium-based packaging materials designed to replace single-use Styrofoam and plastic in transportation and e-commerce sectors.
|
||||||
|
Problem and Solution Statements
|
||||||
|
|
||||||
|
Problem 1: Plastic Pollution (Land, Water, Air) and Non-Biodegradability
|
||||||
|
The Problem:
|
||||||
|
Over 300 million tons of plastic are produced globally each year, with around 45% being single-use packaging. Styrofoam and plastic foams take up to 500 years or more to decompose, causing persistent pollution.
|
||||||
|
|
||||||
|
Our Solution:
|
||||||
|
We develop 100% biodegradable mycelium packaging that decomposes naturally in 30 to 90 days, enabling a circular economy.
|
||||||
|
|
||||||
|
Problem 2: High Carbon Footprint of Production
|
||||||
|
The Problem:
|
||||||
|
Plastic production contributes about 3.4% of global greenhouse gas emissions, heavily reliant on fossil fuels.
|
||||||
|
|
||||||
|
Our Solution:
|
||||||
|
Our process uses renewable agricultural waste and fungal growth, reducing carbon emissions by up to 70–90% compared to plastics.
|
||||||
|
|
||||||
|
Problem 3: Less Use of Plants and Other Natural Resources
|
||||||
|
The Problem:
|
||||||
|
Conventional bio-packaging often requires dedicated crops, which leads to over-exploitation of valuable land and water resources.
|
||||||
|
|
||||||
|
Our Solution:
|
||||||
|
We convert locally sourced agricultural waste into packaging, requiring significantly less land or water resources.
|
||||||
|
|
||||||
|
Problem 4: Agricultural Waste Mismanagement
|
||||||
|
The Problem:
|
||||||
|
India produces over 500 million tons of crop residue annually, much of which is burned, causing severe air pollution impacting millions.
|
||||||
|
|
||||||
|
Our Solution:
|
||||||
|
We utilize this waste as raw material, reducing harmful burning and creating economic value for rural producers.",true,University,Received,https://drive.google.com/drive/folders/1322p0iOzB-d66xlZV85oBEOf9gWOsNkq?usp=sharing,
|
||||||
|
,Adrien BARRAU,+33646221977,Envoyer le message,adrien@seavium.com,SEAVIUM,Adrien BARRAU / Samuel DRAI,"Marseille, Provence-Alpes-Côte d'Azur, France","Europe, France",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-04-01,Technology & innovations,"Seavium is an AI platform that reduces the environmental footprint of offshore operations by eliminating unnecessary vessel movements.
|
||||||
|
Fragmented data and inefficient sourcing lead to avoidable transits, excess fuel use and emissions across the sector.
|
||||||
|
|
||||||
|
Seavium matches each offshore need with the closest, most suitable vessel in real time, using technical data and AIS availability. This optimisation cuts transit miles and fuel consumption at scale.
|
||||||
|
|
||||||
|
Early results show 18–25% fewer miles sailed and 5–12% fuel savings per operation.
|
||||||
|
With 20 000+ vessels mapped and 118 companies already engaged, the model is globally scalable.
|
||||||
|
|
||||||
|
Seavium combines a SaaS subscription with performance-based fees, ensuring that environmental impact increases with platform adoption.",true,via GreenwaterFoundation,Received,https://drive.google.com/drive/folders/1fUCrWCyXQHWEcacseTa338-RPn53KnZy?usp=drive_link,
|
||||||
|
,Laurent BUOB,+33675090543,Envoyer le message,l.buob@whisper-ef.com,Whisper eF,Vincent Lebeault,"Paris, Île-de-France, France","Europe, France",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-09-30,Sustainable shipping & yachting,"Whisper 360, a foiling boat with the performance of a thermal boat, powered by electricity: 45 knots, 100 nautical miles, zero emissions.",false,We were incubated at Monaco Tech,Doublon,,
|
||||||
|
,Achyut Karn,+916204778589,Envoyer le message,achyut.karn.2025@sse.ac.in,OceanGuardian AI,"Rishan Narula, Saanvi Mahajan","Pune, Maharashtra, Inde",Asia,Symbiosis School of Economics,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Technology & innovations,"OceanGuardian AI: Predictive Ocean Protection Through Autonomous Intelligence
|
||||||
|
|
||||||
|
The Problem We're Solving
|
||||||
|
|
||||||
|
Ocean conservation today operates in crisis mode. We discover dead zones after they form, find pollution after it spreads, and detect coral bleaching after ecosystems collapse. Current monitoring methods are expensive, sporadic, and reactive—providing data only after irreversible damage occurs. The ocean needs an early warning system, not an autopsy report.
|
||||||
|
|
||||||
|
Critical gaps in current approaches:
|
||||||
|
- Monitoring covers less than 5% of critical marine zones
|
||||||
|
- Research-grade equipment costs $50,000+ per unit, limiting deployment
|
||||||
|
- Data collection happens quarterly or annually—far too slow for dynamic threats
|
||||||
|
- No predictive capability to prevent ecosystem collapse before it happens
|
||||||
|
- Communities lack real-time information to protect their local waters
|
||||||
|
|
||||||
|
Our Innovation: The World's First Predictive Ocean Protection Network
|
||||||
|
|
||||||
|
OceanGuardian AI deploys networks of affordable, solar-powered autonomous underwater drones that create continuous, real-time monitoring of marine ecosystems. But we don't just collect data—our AI predicts threats 2-8 weeks before critical damage occurs, enabling intervention while ecosystems can still be saved.
|
||||||
|
|
||||||
|
Core Technology Components:
|
||||||
|
|
||||||
|
1. Affordable Autonomous Drones ($800/unit)
|
||||||
|
- Solar and wave-energy powered for perpetual operation
|
||||||
|
- Multi-sensor array monitors 15+ parameters simultaneously
|
||||||
|
- Computer vision and acoustic sensors for marine life tracking
|
||||||
|
- Swarm intelligence enables coordinated monitoring
|
||||||
|
- Modular design adapts for different missions
|
||||||
|
|
||||||
|
2. Predictive AI Engine
|
||||||
|
- Machine learning models trained on oceanographic data
|
||||||
|
- Predicts coral bleaching events, harmful algal blooms, oxygen depletion
|
||||||
|
- Identifies microplastic accumulation hotspots
|
||||||
|
- Detects illegal fishing and pollution incidents in real-time
|
||||||
|
- Creates digital twin models of monitored ecosystems
|
||||||
|
|
||||||
|
3. Real-Time Intervention System
|
||||||
|
- Automated alerts to authorities, NGOs, and c",true,Linkedin,,,
|
||||||
|
,Silvia Ruiz-Berdejo,+34622381855,Envoyer le message,silvia@omnivorus.com,Omnivorus Smartfood,"Silvia rui-Berdejo CEO -Cofounder , Toni Gonzalez CPO - Cofounder, Luis Pascual CFO , Jose Tornero R&D Funtional Food , Carlota Villanueva-Tobaldo R&D Nutro cosmetic","Cadix, Andalousie, Espagne","Europe, Spain",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-01-11,Other,"We are a biofoodtech startup specializing in microalgae and plant-based functional ingredients within the blue economy. Our R&D targets sectors like Functional Food Formulation, Precision Food Nutrition, and Nutricosmetics.
|
||||||
|
|
||||||
|
We develop new ingredients that replace fats, sugars, and additives in ultra-processed foods while replicating traditional textures, colors, and flavors to ease consumer transitions to healthier diets.
|
||||||
|
|
||||||
|
Our clean-label formulations support easy industrial integration and rapid scale-up for B2B clients in the food industry, health and wellness groups, innovative food brands, and sports teams. This advances sustainable functional nutrition aligned with blue economy principles",true,Linkedlin,Received,https://drive.google.com/drive/folders/1A8jzY7h4pfebbQKvCtg0Fc0AKzUE1F_q?usp=drive_link,
|
||||||
|
,James Carter-Johnson,+447899791166,Envoyer le message,james@bigkelp.com,Big Kelp,James Carter-Johnson MA MBA; Prof. Carole Llewelyn MSc PhD; Vincent Doumeizel; Carlos Vanegas MSc PhD; James Sainty BA MBA; Akhthar Swaebe BT MSc MBA; Peter Rivera MSc PhD; Alessio Massironi MSc PhD; Johannes van der Merwe ME CE PhD; Oliver Parker BSc MSc,"Londres, Angleterre, Royaume-Uni",UK,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-06-06,Mitigation of climate change and sea-level rise,"To Farm Giant Kelp at Scale, on special racks, capturing 30x more C02 than forrest per hecare. Harvest 4 times a year for oganic fertilizer, algin and materials for bio-plastics. All these replace highly polluting oil based products on land.",false,You contacted me I think.,Received,https://drive.google.com/drive/folders/1R5-IfGbETFri6ZX0RnJY8W6wan7cLoz-?usp=drive_link,
|
||||||
|
,Chaima BEN GRIRA,+393508394071,Envoyer le message,cbengrira@blueeconomy.ogs.it,Bluepsol,"Eskander ALAYA, Chaima BEN GRIRA, Nabil FOGHRI, Ahmed BACCOUCHE, Adel JELJLI","Tunis, Tunisie","Africa, Tunisia",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2023-01-19,Reduction of pollution (plastics chemicals noise light...),,false,,,,
|
||||||
|
,Ketty Shamakamba,+260971094443,Envoyer le message,ilovesolarfreezers@gmail.com,LAKE FARMS AND FISHING LODGE LIMITED,"Board and Management Team Chisanga Mambwe – Board Chairperson (Strategic oversight) Provides governance leadership, investor relations support, and high-level oversight of the executive team. Ketty Shamakamba – Chief Executive Officer (CEO) Leads overall strategy, fundraising, partnerships, gender lens work, and company growth. Oversees business development, climate initiatives, and solar cold-chain expansion. Chiozya Mwanza – Chief Operations Officer (COO) Responsible for day-to-day operations, cage management, production planning, logistics coordination, and community engagement with fishers and women traders. Hamando Hamalabbi – Chief Financial Officer (CFO) (Accountant) Manages finance, accounting, compliance, investment reporting, budgeting, and financial controls. Muzalema Zimba – Chief Marketing Officer (CMO) (Sales & Marketing Manager) Oversees sales strategy, distribution channels, branding, customer acquisition, and premium market relationships (hotels, restaurants, wholesalers). Joshua Mwanza – Chief Operations Manager / Deputy COO (Operations Manager) Supports operations, distribution logistics, procurement, cold-chain coordination, and team supervision. Micheck Chulaula – Chief Farm Manager (CFM) (Farm Manager) Oversees cage management, feeding regimes, harvesting, processing coordination, and ensuring biosecurity and aquaculture standards. Mabel Kaunda – Chief Human Resources Officer (CHRO) (HR Manager/Secretary) Manages staff welfare, recruitment, training, compliance, and gender-inclusive workforce policies. Our operations team includes experts in farm management, logistics, and business development, ensuring efficient production, processing, and distribution. The finance and technology staff oversee solar freezer leasing, mobile payments, and digital monitoring systems, enabling scalable, sustainable impact. Many team members, including the founders, have personal connections to the communities we serve, which drives our ","Siavonga, Southern, Zambie","Africa, Zambia",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2021-11-30,Capacity building for coastal communities,"Lake Farms is establishing an academy dedicated to preserving Lake Kariba and its communities. It is a center for training, innovation, and direct action.
|
||||||
|
|
||||||
|
Core Mission: Halt fish stock depletion and foster a sustainable blue economy.
|
||||||
|
|
||||||
|
Key Initiatives:
|
||||||
|
|
||||||
|
Training Hub: Equip local fishers with skills in sustainable aquaculture, ecosystem management, and cooperative business.
|
||||||
|
|
||||||
|
Innovation & Deployment: Design and deploy ethical, lake-friendly cage systems and restorative practices to rebuild wild stocks.
|
||||||
|
|
||||||
|
Community Enterprises: Launch community-owned ""Aqua-Hubs"" that provide food security, create livelihoods, and empower women.
|
||||||
|
|
||||||
|
This academy would create a lasting legacy of ecological restoration, poverty reduction, and resilience for Lake Kariba's people, directly honoring a commitment to ocean and freshwater preservation.",true,"We learned about the Monaco Ocean Protection Challenge through the communication channels of the Prince Albert II of Monaco Foundation and its associated networks, which highlight pioneering solutions for ocean and freshwater conservation.",Received,https://drive.google.com/drive/folders/1DXdpRkPzDypY6brOaIjYht5l42IGU5aY?usp=drive_link,
|
||||||
|
,Torrigiani Aurore,+33647780342,Envoyer le message,seablocksrecif@gmail.com,Sea Blocks,Olivier Meynard,"Marseille, Provence-Alpes-Côte d'Azur, France","Europe, France",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2021-02-03,Restoration of marine habitats & ecosystems,"Sea Blocks develops modular, low-tech artificial reefs designed to restore marine habitats in port environments.
|
||||||
|
|
||||||
|
Each reef is co-designed and assembled through participatory workshops involving companies, citizens and local stakeholders, then installed in partnership with ports. The modules are made from low-carbon materials and locally sourced shell waste, enhancing ecological functionality and accelerating colonisation by marine species.
|
||||||
|
|
||||||
|
The project combines ecological restoration, circular economy and awareness-raising, with scientific monitoring conducted by marine biology experts to assess biodiversity recovery and long-term impact.",true,Through professional networks and partners involved in ocean and coastal innovation.,Received,https://drive.google.com/drive/folders/1KRH7HjZVWnoNPa9gTLskVt_8SebW2lNt?usp=drive_link,
|
||||||
|
,Godfrey Noel,+254795647634,Envoyer le message,gnoel@kilimora.africa,Kilimora CLG,"Godfrey Noel, Zuhra Nagib, Matthew Muange, Hildah Gichuru, Ezra Maruti, Hildah Gichuru","Nairobi, Kenya","Africa, Kenya",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-01-04,Capacity building for coastal communities,"Project Title: Bamboo Stewardship for Mangrove Protection: Building Sustainable Livelihoods Along the East African Coast
|
||||||
|
|
||||||
|
The Problem
|
||||||
|
East African coastal mangrove ecosystems spanning from Somalia to Mozambique face catastrophic degradation, with communities harvesting mangroves for fuel and construction because alternative income sources remain unavailable. This extraction destroys critical carbon sinks, eliminates natural storm surge barriers, and collapses fish nursery habitats that sustain coastal food security. Traditional conservation approaches exclude communities from protected areas without providing viable economic alternatives, guaranteeing enforcement failure and continued ecosystem loss.
|
||||||
|
|
||||||
|
Our Solution
|
||||||
|
Kilimora, in strategic partnership with EarthLungs, is implementing a bamboo based mangrove protection system that transforms coastal communities from ecosystem exploiters into paid ecosystem stewards. We employ community members to cultivate and harvest fast growing bamboo (Bambusa species with 3 to 5 year harvest cycles and continuous regrowth capacity) in designated buffer zones adjacent to mangrove forests. This bamboo provides sustainable construction materials and biomass fuel alternatives that eliminate economic pressure on mangrove stands while generating verifiable income for participating households.
|
||||||
|
|
||||||
|
Technical Innovation
|
||||||
|
The initiative integrates drone based mangrove health monitoring with ground truth verification by community stewards, creating high resolution ecosystem data that supports both conservation management and carbon credit generation. Kilimora provides the artificial intelligence powered verification infrastructure and blockchain based transparent payment systems ensuring stewards receive direct compensation tied to measurable mangrove protection outcomes. EarthLungs contributes marine ecosystem expertise, coastal community organizing capacity, and connections to corporate blue carbon credit buyers.
|
||||||
|
|
||||||
|
Scale and Impact
|
||||||
|
The program cu",,LinkedIn network,Received,https://drive.google.com/drive/folders/1Ouz8-deBPfgUl7VYIwofxUwEYG4D9iMw?usp=drive_link,
|
||||||
|
,Hellen flavine akinyi,+27631484516,Envoyer le message,artworkspace1@gmail.com,"STOPING OCEAN PLASTICS AT THE SOURCE;DIGITAL ECO-PACKAGING SOLUTION LED BY A YOUNG AFRICA WOMAN,KENYA 2026026","KIMBERLY ADHIAMBO CONIE, MAISON JOHN &PETER WAMBURA","Nairobi, Kenya","Africa, Kenya",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2021-02-09,Consumer awareness and education,Plastic pollution from urban markets and streets flows into rivers ad ultimately into the ocean. single-use plastic paper bags are among the most common sources of marine debris.preventing plastic waste at the source is the most effective ad affordable solution than ocean multi-million clean up efforts,true,thro. funds- for- Ngo newsletters,Received,https://drive.google.com/drive/folders/1bFnRFeWaxyD2g52MG0l_Y6q_rQOCYbnc?usp=drive_link,
|
||||||
|
,Tochukwu Uwakeme,+12024255839,Envoyer le message,uwakemet@bu.edu,Pikia Marine,"Tochukwu Uwakeme, Moses Imoleyo, Ihuoma Ohaegbulam","Boston, MA, États-Unis",US,Boston University / United States,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Capacity building for coastal communities,"Coastal Blue-Skills Hubs by Pikia is a scalable, community-led training and microenterprise program that equips coastal youth and women with practical skills, tools, and starter microgrants to reduce ocean pollution and strengthen climate-resilient livelihoods through waste-to-value (plastic collection/sorting), sustainable fishing practices, and mangrove/coastal restoration. The program will run through local “Hub” partners (NGOs/co-ops/schools), a lightweight mobile curriculum, and a train-the-trainer model, paired with verified community monitoring (simple metrics + photo evidence) to prove impact and unlock blue-economy buyers and sponsors.
|
||||||
|
Objectives:
|
||||||
|
• Cut land-to-ocean leakage by organizing community collection, sorting, and resale of plastics, with tracked volumes diverted.
|
||||||
|
• Increase resilient incomes by training and supporting community micro-enterprises (waste-to-value, eco-services, sustainable seafood handling) and link them to off takers.
|
||||||
|
• Restore natural coastal defenses through mangrove/coastal habitat restoration tied to local stewardship incentives and verified survival rates.
|
||||||
|
• Create a repeatable “Hub-in-a-box” model that can scale across coastal regions quickly with clear KPIs and partner networks delivering positive, measurable ocean impact in the short to medium term, consistent with MOPC’s focus on ocean-positive business concepts",true,United Nations SDGs Newsletter.,Received,https://drive.google.com/drive/folders/1ph6DBmqeSGvSSqxQkymPx9rlU-ZmGFnr?usp=drive_link,
|
||||||
|
,Veronica Nzuu,+254748488312,Envoyer le message,veramichael2000@gmail.com,Furies,Angelo Mulu,"Mombasa, Kenya","Africa, Kenya",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2023-05-23,Consumer awareness and education,"My project focuses on community-based climate and ocean education for children and youth, using storytelling, play, and interactive learning to build awareness around plastic pollution, waste segregation, and environmental responsibility. The objective is to transform how young people and families understand and relate to plastic consumption moving from awareness to everyday action. Through games, facilitated sessions, and community learning spaces, the project empowers children to become informed advocates within their households and neighborhoods, strengthening long-term behavior change and community ownership of sustainability solutions.",true,Social media linked in,,,
|
||||||
|
,Cristiano da Silva Palma,+5511978020540,Envoyer le message,cristianospalma@yahoo.com.br,Tabernacle Space Islands,Cristiano da Silva Palma,"São José dos Campos, SP, Brésil",South America,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-08-09,Technology & innovations,"Project Idea & Scientific Context
|
||||||
|
|
||||||
|
The project develops a next-generation modular OTEC (Ocean Thermal Energy Conversion) system, combining innovative deep-ocean structures, ultra-optimized thermodynamic cycles, and AI-based monitoring to deliver continuous (24/7) clean energy, with initial pilot operation targeted from 2027. The system is designed for scalable deployment in tropical and island regions, validated through a pilot-scale OTEC unit operating with deep-water intake (~1000 m or more) and real-time intelligent control.
|
||||||
|
|
||||||
|
Sur le plan scientifique, la technologie OTEC repose sur l’exploitation de la différence de température entre les eaux de surface chaudes et les eaux profondes froides afin d’alimenter un cycle thermodynamique de production d’électricité, conformément aux analyses reconnues par la Convention-cadre des Nations Unies sur les changements climatiques (UNFCCC).
|
||||||
|
|
||||||
|
Dans les régions tropicales, où les eaux de surface peuvent dépasser 25 °C tandis que les eaux profondes se situent autour de 5 °C, le différentiel thermique (ΔT) peut excéder 20 °C, condition généralement considérée comme favorable à une application efficace de l’OTEC, comme le soulignent de nombreuses publications académiques, notamment celles de la MDPI.
|
||||||
|
|
||||||
|
En revanche, dans le bassin méditerranéen, y compris autour de la Principauté de Monaco, les données actuelles indiquent un ΔT généralement inférieur aux seuils classiques de viabilité de l’OTEC à grande échelle. Toutefois, à partir de 2027, evolving ocean temperature profiles, combined with AI-assisted thermodynamic optimization, high-efficiency working fluids, operation restricted to periods of maximum thermal contrast (summer), intake at greater depths, and high thermal-efficiency piping, may enable experimental and seasonal OTEC operation, positioning the Mediterranean as a future testbed for advanced ocean energy technologies.
|
||||||
|
|
||||||
|
By aligning scientific rigor with technological innovation, the project contributes to ocean protection",true,"I learned about the Monaco Ocean Protection Challenge through institutional email exchanges within the framework of the United Nations Framework Convention on Climate Change (UNFCCC), including communications with the UNFCCC Global Secretariat, notably Simon Stiell, Executive Secretary, as well as with UNFCCC National Focal Points in Monaco and France. These included Carl Dudek (Ministry of Foreign Affairs and Cooperation of the Principality of Monaco), Dietmar Petrausch and Wilfred Suddath-Deville (Ministry for Europe and Foreign Affairs of France), and Yue Dong and Bénédicte Jenot (French Ministry for the Ecological Transition).",Received,https://drive.google.com/drive/folders/1Toc3pgN0P3ZuTZiFGlyhOMiqKAMFwIAo?usp=drive_link,
|
||||||
|
,Titus Nyandoro,+254743378884,Envoyer le message,ktnyandoch@gmail.com,VUA SOLUTIONS,"Matthew Egessa, Titus Nyandoro","Mombasa, Kenya","Africa, Kenya",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-01-01,Technology & innovations,"We are a Kenyan-based, ocean-minded for-profit fintech venture for fishing coastal communities that are dedicated to the sustainable blue economy",true,WhatsApp,Received,https://drive.google.com/drive/folders/1twpoOtR1RIei27iSRXNquyV4iMBA9HdC?usp=drive_link,
|
||||||
|
,Mzuvukile Benayo,+27738223994,Envoyer le message,mzuvukilejames@gmail.com,Youth Innovation Programme,Zenande Mnethu,"Le Cap, Western Cape, Afrique du Sud","Africa, South Africa",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2011-07-07,Capacity building for coastal communities,Spatial Planning Collective more about engaging stakeholders and driving education.,true,FundsforNGOS email,Ignore,,
|
||||||
|
,Abdoulaye Sarr Ndour,+221775110218,Envoyer le message,ndour.ecobox@gmail.com,OceanEdu AI,"Omar Cissé Faye, Fatou Cissé, Coumba Gueye","Dakar, Sénégal","Africa, Senegal","Saint Louis Gaston Berger University, Senegal",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Technology & innovations,"AI-powered gamified learning platform for ocean conservation education (Duolingo-style for oceans). The platform features an AI tutor powered by ChatGPT-4/Claude, 4 educational modules covering Biodiversity, Climate, Threats, and Solutions, gamification elements including XP points, badges, and mini-games, plus professional certifications.
|
||||||
|
|
||||||
|
Target customers include B2C users (parents/students) paying 9.99 EUR/month subscriptions, schools paying 800-1,500 EUR/year for licenses, corporations paying 2K-50K EUR for CSR training programs, and professionals purchasing certifications for 49-299 EUR each.
|
||||||
|
|
||||||
|
Year 1 objectives: 10,000 users generating 207K EUR revenue. Year 3 objectives: 200,000 users generating 5.8M EUR revenue. Overall mission: 1 million ocean-literate people by 2030.
|
||||||
|
|
||||||
|
Tech stack: Next.js frontend, Supabase backend (PostgreSQL + Auth + Storage), OpenAI API for AI tutor functionality.
|
||||||
|
|
||||||
|
Timeline: 90 days to launch following MVP development, beta testing with 100 users, then public launch.
|
||||||
|
|
||||||
|
Initial budget required: 15-30K EUR covering development, educational content creation, and marketing expenses.",true,Linkedin,,,
|
||||||
|
,Christopher Enriquez Urban,+4915679760251,Envoyer le message,christopher@algrid.tech,Algrid,Valentina Iunosheva,"Francfort-sur-le-Main, Hesse, Allemagne","Europe, Germany","University of Leeds, Leeds, UK",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Other,"Project: AI-powered offshore infrastructure for Sargassum monitoring, harvesting, and conversion into industrial biomass.
|
||||||
|
|
||||||
|
Problem: Massive Sargassum blooms devastate Caribbean coasts but remain unused due to unpredictable availability and high logistics costs.
|
||||||
|
|
||||||
|
Solution: Neural-operator forecasting systems predict bloom movements with high accuracy, guiding automated offshore platforms that harvest and preprocess algae at sea—delivering consistent, industrial-grade feedstock.
|
||||||
|
|
||||||
|
Objectives: Create reliable supply chains for bio-based materials, reduce coastal environmental damage, generate jobs and enable circular economy applications in construction, energy, and agriculture.
|
||||||
|
|
||||||
|
Impact: Transforms environmental crisis into economic opportunity while addressing climate goals through fossil material substitution.",true,LinkedIn,Received,https://drive.google.com/drive/folders/1T_rTye_rp5F3OO7hHY3dnCtevSSeVaqG?usp=drive_link,
|
||||||
|
,Sarfraaz Khan AYAZ KHAN,+33745384992,Envoyer le message,info@poseidon-monaco.com,POSEIDON,Sarfraaz Khan AYAZ KHAN,"Monaco, Monaco","Europe, Monaco",SKEMA Business School and POLIMI Graduate School of Management,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Reduction of pollution (plastics chemicals noise light...),"Poseidon transforms ocean-recovered plastic into certified, customizable, high-end solid surface materials for architects, designers, artists, and sustainability-driven businesses.
|
||||||
|
Our advanced material development and manufacturing ensure durability, aesthetic quality, and long-term reuse as alternatives to conventional surfaces - integrating ocean plastic back into the economy. Each sheet removes approximately 15–30 kg of ocean plastic and includes a digital product passport that provides full traceability from collection to final use. Incubated at MonacoTech, Poseidon aligns with multiple UN Sustainable Development Goals and empowers creative professionals to lead eco-innovation, contributing to ocean cleanup, circularity, and measurable environmental impact.",true,"Last year MOPC competition , JCI CCE event and news letter",Received,https://drive.google.com/drive/folders/11GEc6IYyLaZnQ_rtkgNHeafVPnuOZ2bz?usp=drive_link,
|
||||||
|
,Francesca Rose Turner Prichard,+34671298357,Envoyer le message,francescaroseturner@gmail.com,Residensea,"Francesca Turner, Aoife Martin, Alberto Rangel","Palma, Iles Baléares, Espagne","Europe, Spain",Southampton Solent University,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Consumer awareness and education,"Restore society’s relationship with the ocean by building connections between women and the ocean through sport, art and conservation activities.",true,Linkedin,,,
|
||||||
|
,Brian Ochieng Aliech,+254757008417,Envoyer le message,ochiengaliech@gmail.com,NOLA AFRICA,"Brian Aliech, Charles Okutah, Hussein Hezekiah, Kevin Onsongo, Lidah Makena","Nairobi, Kenya","Africa, Kenya","University of Nairobi, Nairobi",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Reduction of pollution (plastics chemicals noise light...),"Our project seeks to address the pervasive challenge of plastic pollution that has afflicted urban centers and aquatic ecosystems across the globe.
|
||||||
|
By converting discarded plastic into durable construction materials, we aim to alleviate the strain on finite natural resources traditionally employed in the building industry, thereby reducing both costs and inefficiencies.
|
||||||
|
In addition, this initiative seeks to generate meaningful employment opportunities for young people in underserved communities, empowering them to achieve economic stability and dignity. Ultimately, our vision is nothing less than to contribute to the preservation and renewal of our planet.",true,Through a friend.,,,
|
||||||
|
,Adhithi Mugundha Kumar,+447512296331,Envoyer le message,Adhithimukhundh@gmail.com,Blue crabs,Xenia Anagnostou,"Plymouth, Angleterre, Royaume-Uni",UK,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2026-01-06,Sustainable fishing and aquaculture & blue food,We aim to provide a solution to invasive blue crabs in the Mediterranean by developing a bait-induced fishing method.,true,,Received,https://drive.google.com/drive/folders/1wAQ7xulHAa-nRAOzZrCgCWO96CW3IWuy?usp=drive_link,
|
||||||
|
,THIERRY BOUSSION,+33621220023,Envoyer le message,t.boussion@yuniboat.com,Yuniboat,Thierry Boussion,"Le Pouliguen, Pays de la Loire, France","Europe, France",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2022-06-01,Reduction of pollution (plastics chemicals noise light...),"Yuniboat develops an industrial model dedicated to the eco-reconditioning of leisure and professional boats, designed to significantly reduce the environmental impact of boating activities.
|
||||||
|
|
||||||
|
By extending the lifespan of existing boats rather than building new ones, Yuniboat directly contributes to the protection of oceans and marine ecosystems. Reconditioning avoids the extraction of new raw materials, limits fiberglass and plastic waste, and reduces emissions linked to manufacturing and end-of-life destruction.
|
||||||
|
|
||||||
|
Key Environmental Impacts
|
||||||
|
Reduction of marine pollution by preventing abandoned and end-of-life boats from becoming waste at sea or in ports.
|
||||||
|
Preservation of marine fauna and flora through lower emissions, reduced noise pollution, and cleaner propulsion systems (electric, biofuel, hybrid).
|
||||||
|
Lower pressure on natural resources, with up to 80% of boat components reused.
|
||||||
|
Decrease in carbon footprint, contributing to climate action and healthier marine ecosystems.
|
||||||
|
|
||||||
|
Project Objectives
|
||||||
|
Make boating more compatible with ocean preservation.
|
||||||
|
Support professionals (fishing, rental fleets) in meeting decarbonation goals by 2030.
|
||||||
|
Offer a sustainable, economically viable alternative to new boat construction.
|
||||||
|
Deploy a scalable industrial model capable of transforming the nautical and maritime sectors.
|
||||||
|
Yuniboat’s ambition is to position eco-reconditioning as a key lever for ocean protection, combining circular economy, innovation, and long-term impact on marine biodiversity.",true,we follow your activities on Linkedin and Instagram,,,
|
||||||
|
,Daniele Tassara,+393466376215,Envoyer le message,daniele.tassara@outlook.com,MareNetto,"Giambattista Figari, Giorgio Mussini","Santa Margherita Ligure, Ligurie, Italie","Europe, Italia","Universita di Genova, Genova",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Technology & innovations,"MareNetto is a yacht-focused climate platform that automatically calculates and offsets superyacht CO2 emissions from AIS/MMSI data, then issues verifiable certificates that owners and charter managers use for marketing and ESG compliance",true,I lived in Monaco and i knew about this project,,,
|
||||||
|
,Gaia Minopoli,+393393499607,Envoyer le message,gaia.minopoli@ogyre.com,Ogyre,Agnese Antoci Alessandro Serra Alice Casella Andrea Faldella Andrea Scatolero Antonio Augeri Chiara Maggiolini Davide Brugola Filippo Ferraris Gaia Minopoli Gian Piero Seregni Lorenzo Gastaldo Matteo Quaglio Mattia De Serio Michele Migliau Alessandro Sciarpelletti Francesco Carletto francesco notari Irene Eustazio Jurgen Ametaj Lorenzo Varas Marta Berardini Lucrezia Napoletano Gabriele Cusimano Enrica Sandigliano,"Gênes, Ligurie, Italie","Europe, Italia",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2020-01-21,Reduction of pollution (plastics chemicals noise light...),"Ogyre is a startup tackling marine plastic pollution through a Fishing for Litter model, working directly with fishing communities worldwide. Its mission is to clean the Ocean while turning plastic waste into a resource. By financially supporting fishers to recover marine litter during their daily activities, and by involving local partners for sorting and recycling, Ogyre delivers measurable environmental and social impact. The entire process is fully traceable through a blockchain-enabled platform, allowing companies to monitor progress and impact in real time. Active across Europe, South America, Africa, and Asia, Ogyre has already recovered over 800 tons of marine waste and proven a financially sustainable model—now scaling its impact globally to reach 30M kg of cumulated collection by 2030!",true,Scientific attaché of Italian Embassy in Paris,,,
|
||||||
|
,Yajaira Cristina Alquinga Salazar,+541136132787,Envoyer le message,cristinalquinga@gmail.com,Dynamics of Coastal Dune Fields in the Southwest of Buenos Aires Province,"Bsc. Yajaira Cristina Alquinga Salazar, Dr. Gerardo M. E. Perillo and Dr Sibila A. Genchi","Bahía Blanca, Buenos Aires, Argentine",South America,Universidad Nacional del Sur,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Mitigation of climate change and sea-level rise,"The general objective of this research plan is to study the dynamics of coastal dunes in the southwest of Buenos Aires Province, with special emphasis on the foredune, and its relationship with climatic, oceanographic, and anthropogenic factors. In particular, the study aims to determine the degree of influence of each of these factors, especially in areas where urban settlements have been established over the last 80 years, in comparison with adjacent sectors subjected to similar environmental conditions but without anthropogenic influence.",true,LinkedIn,Received,https://drive.google.com/drive/folders/1y8Z1QYa6a7MeN1qs7ILCELvWHY1mRfr2?usp=drive_link,
|
||||||
|
,Jovana,+381645655226,Envoyer le message,jovanaperisic059@gmail.com,EcoMath,Jovana Perišić,Kraljevo,"Europe, Serbia",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2026-01-07,Technology & innovations,"Project name: Symphony of the Blue
|
||||||
|
|
||||||
|
The Idea: Converting real-time oceanographic data (currents, temperature, pH levels) into immersive musical compositions using mathematical algorithms.
|
||||||
|
|
||||||
|
Objectives:
|
||||||
|
|
||||||
|
Emotional Data Visualization: Making the ""silent"" problems of the ocean audible to the public and investors through music.
|
||||||
|
|
||||||
|
Eco-Funding: Generating revenue for marine conservation through the sale of these unique, data-driven symphonies.
|
||||||
|
|
||||||
|
Ocean Literacy: Educating younger generations by integrating science, math, and art.",false,,Received,https://drive.google.com/drive/folders/1_pEprsBTA-AOSiNDIX0xLxr5jS7jdgNv?usp=drive_link,
|
||||||
|
,Amelia Martin,+18606824426,Envoyer le message,amelia@mudratsurf.com,Mud Rat,"Jack Tarka, Patricio Acevedo, Brian Lassy","Storrs Center, CT, États-Unis",US,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2023-06-14,Consumer awareness and education,Mud Rat is a biomaterials startup creating an eco friendly alternative to marine foams.,true,Google!,Received,https://drive.google.com/drive/folders/1GzXe6ugfJQCFdqcZxj3lZrN4H7DSMAIE?usp=drive_link,
|
||||||
|
,Mulowoza Grace,+256705620491,Envoyer le message,mulowozagrace@gmail.com,Divine youth environment initiative,"Mulowoza Grace, Nassaazi phiona, Sseruga ibraheem, Male simon , kirume Vivian Deborah","Entebbe, Wakiso, Ouganda","Africa, Ouganda",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2022-11-07,Reduction of pollution (plastics chemicals noise light...),"We are tackling plastic pollution through innovative upcyling solutions, our work is centered around four main objectives;
|
||||||
|
1. Reduce plastic pollution through innovative upcyling, we are transforming plastic waste into valuable resources.
|
||||||
|
2. Promote waste separation and proper waste management practices.
|
||||||
|
3. Raise awareness about the importance of environmental conservation.
|
||||||
|
4. Empower youth to take action in environmental conservation.
|
||||||
|
Our project, combat plastic pollution through circular economy innovation aims to reduce plastic pollution which can end up into oceans by promoting circular approaches that emphasize reduction, reuse, recycling and sustainable alternatives.
|
||||||
|
Through transforming plastic waste into economic and social opportunities our team contribute to environmental protection, green job creation and sustainable development.",true,Facebook,Received,https://drive.google.com/drive/folders/1UrRLVQdjIoRtm2A4_eZ1XBhiKwnELYaH?usp=drive_link,
|
||||||
|
,Suraj Kumar Hota,+919776476665,Envoyer le message,surajkumarhota23@gmail.com,No project,SubhaKant Dalei,"Berhampur, Odisha, Inde",Asia,Berhampur University India,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Sustainable fishing and aquaculture & blue food,Using Indian knowledge system to manage overfishing,true,,,,
|
||||||
|
,Shiva,+918529637418,Envoyer le message,shiv@gmail.com,NA,NA,Inde,Asia,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Technology & innovations,,true,Collage,Ignore,,
|
||||||
|
,Sebastian Marzetti,+33766861456,Envoyer le message,marzettisebastian@gmail.com,Intelligent Acoustics,Valentin Barchasz - Valentin Gies - Hervé Glotin,"Toulon, Provence-Alpes-Côte d'Azur, France","Europe, France",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2026-03-01,Technology & innovations,"Underwater acoustics monitoring using easy to deploy systems
|
||||||
|
Our low power systems allow real-time alerts and data for immediate action",false,Linkedin,Received,https://drive.google.com/drive/folders/1iqhIFKdILL9qbny96CMCKKSS4FA5HlXf?usp=drive_link,
|
||||||
|
,Emana Bilalović,+381656075770,Envoyer le message,emanabilalovic12@gmail.com,Ocean Asylum Certificates,"Emana Bilalović, Alzana Bajrami","Pristina, Kosovo","Europe, Kosovo",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2025-08-18,Sustainable shipping & yachting,"Ocean Asylum Certificates (OAC) establish legally protected micro-zones in the ocean by converting conservation into binding contractual commitments.
|
||||||
|
The project enables regulated no-exploitation areas that directly influence shipping and yachting behavior through enforceable restrictions, transparent monitoring, and long-term accountability.
|
||||||
|
Its objective is to embed ocean protection into maritime governance rather than rely on voluntary sustainability pledges.",true,Instagram of University of Monaco,,,
|
||||||
|
,Sabira Ayesha Bokhari,+33753635938,Envoyer le message,sabirabokhari@gmail.com,Eco-Pirates,Aimen Akhtar,"Bengaluru, Karnataka, Inde",Asia,Universidad Catholica de Valencia,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Other,A gamified app to encourage sustainable coastal tourism,true,Ocean Oppurtunities,Received,https://drive.google.com/drive/folders/1QO3N5Dd2PC5dTohn1BIjnA9MtRWEuLD7?usp=drive_link,
|
||||||
|
,Vera Emma Porcher,+61466053917,Envoyer le message,veraporcher20@gmail.com,In-Depth Innovations,"Vera Porcher, Kane Dysart and Tynan Bartolo","Darwin, NT 0800, Australie",Oceania,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2023-11-29,Restoration of marine habitats & ecosystems,"Idea:
|
||||||
|
Eco-engineered reef systems, built on circular-economy principles, repurposing surplus marine-grade concrete and recycled oyster shells into high-performance aquatic habitats that enhance and restore biodiversity and ecosystem services, support food security, and protect coastal communities and infrastructure at scale.
|
||||||
|
|
||||||
|
Objectives:
|
||||||
|
-Support long-term food security by restoring, conserving and enhancing productive marine habitats.
|
||||||
|
|
||||||
|
-Strengthen coastal protection by designing and deploying high-performance eco-structures that act as natural breakwaters, reducing wave energy and coastal erosion.
|
||||||
|
|
||||||
|
-Continuous tracking of ecosystem health in real time through automated ecological monitoring using AI-driven analysis to maximise reef performance.
|
||||||
|
|
||||||
|
- Scalable nature-inclusive designs and eco-structure integration for offshore oil & gas and offshore wind infrastructure to enhance ecological performance and biodiversity protection.
|
||||||
|
|
||||||
|
Other relevant details:
|
||||||
|
We are currently testing prototypes in Australia and developing an autonomous monitoring system, with early results showing very positive outcomes and remarkable improvements in biodiversity.",true,Linkedin,,,
|
||||||
|
,Lee patrick EKOUAGUET,+33778199372,Envoyer le message,ogoouecorpstechnologies@gmail.com,OGOOUE CORPS TECHNOLOGIES,"ANDRE BIAYOUMOU, NGABOU PASCAL XAVIER, LYNDA NGARBAHAM, DUPUIS NOUILE NICOLAS","Bordeaux, Nouvelle-Aquitaine, France","Europe, France",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2023-10-23,Technology & innovations,"OCEAN-PATCH is an intelligent, autonomous maritime safety patch designed to protect human lives at sea.
|
||||||
|
It detects critical situations (man overboard, distress, abnormal conditions) in real time and transmits alerts and data without batteries, using body or environmental energy.
|
||||||
|
|
||||||
|
The project aims to improve maritime safety while generating valuable ocean data to support prevention, monitoring, and smarter decision-making through AI.",true,Through online research and innovation platforms focused on ocean protection and blue tech.,Received,https://drive.google.com/drive/folders/1BEc9s5h5H41vf2bRxpqvz4AHBWMZS1Xm?usp=drive_link,
|
||||||
|
,Tshephiso Kola,+27671509841,Envoyer le message,kolatshepisho@gmail.com,Luminet,Tshephiso Kola,"Johannesburg, Gauteng, Afrique du Sud",Africa,"University of the Witwatersrand, Johannesburg",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Sustainable fishing and aquaculture & blue food,"LumiNet is our solution to the fishing industry’s two biggest headaches: catching the wrong fish and losing expensive gear that pollutes the ocean forever. We are replacing standard nylon nets with a smart, dual-action material that actually works with nature. First, our nets glow with a specific light underwater that sharks and turtles instinctively avoid, which keeps them out of the net while the target fish swim right in. Second, we’ve solved the ghost gear problem with a built-in fail-safe: as long as the net is used in the sun, it stays strong, but if it gets lost and sinks into the dark ocean, it rapidly breaks down and turns into fish food. Our goal is simple: to stop plastic pollution at the source and make fishing more efficient, saving marine life and money at the same time.",true,Social Media,,,
|
||||||
|
,Eric & Aurélie Viard,+33695360436,Envoyer le message,eric@biovie.fr,Algues au quotidien,"Eric Viard, Aurélie Viard","Nîmes, Occitanie, France","Europe, France",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2007-03-11,Consumer awareness and education,Use of organic edible seaweeds in daily food and gastronomy,true,We have been invited directly by Marine Jacq-Pietri to submit our project,Received,https://drive.google.com/drive/folders/1KUwDOwvZxoiXJB0cHtwVeIPjdYAYbOV0?usp=drive_link,
|
||||||
|
,BARHOUMI Nawress,+21621898617,Envoyer le message,nawressbarhoumigf@gmail.com,El Makina,"Mustapha Zoghlami, Nawress Barhoumi",Tunisie,"Africa, Tunisia",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-05-05,Technology & innovations,"The project aims to develop an autonomous intelligent robot for cleaning marine environments, specifically targeting oil spills, human hair, and other pollutants. It focuses on sustainable technology, environmental protection, and smart control systems. The robot is built using recovered and recycled plastic materials, reinforcing the project’s commitment to circular economy principles and eco-friendly engineering.",false,Newsletters,,,
|
||||||
|
,Yao Yinan,+8615221826163,Envoyer le message,yyn982715367@outlook.com,"BluePulse – Design, Protect, Inspire",Yinan Yao,Xinjiang,Asia,"Communication University of China, Nanjing(Location: Nanjing, China)",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Consumer awareness and education,"BluePulse is an innovative project that combines art, design, and technology to raise awareness about ocean pollution and marine conservation. Its main objective is to educate and inspire the public through creative visual campaigns, interactive installations, and sustainable product concepts that highlight the importance of protecting our oceans. The project also explores solutions to reduce plastic and chemical pollution, fostering a culture of environmental responsibility.",true,I found out about the Monaco Ocean Protection Challenge through the organisers listed on the UArctic Congress 2026 website: https://www.uarcticcongress.fo/about,,,
|
||||||
|
,Antalya Fadiyatullathifah,+6281110115560,Envoyer le message,Antallathifah@gmail.com,Environmental Consultant,xxx,"Jakarta, JABODETABEK, Indonesie",Asia,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-11-11,Blue Carbon,xxx,true,xxxx,,,
|
||||||
|
,Moramade Blanc,+50940809002,Envoyer le message,blamo82@yahoo.fr,« Suivi Intelligent des Récifs Coralliens et de la Pêche à Belle-Anse » « SIRECOP »,"Moramade Blanc, Wedeline Pierre, Chralens Calixte, Jacky Duvil,Ruth Catia Bernadin","Belle Anse, Haïti",Haïti,"Sorbonne Universite, France",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Technology & innovations,"Le projet SIRECOP – Suivi Intelligent des Récifs Coralliens et de la Pêche à Belle-Anse vise à renforcer la résilience des récifs coralliens du Parc Naturel National Lagon des Huîtres (PNN-LdH) et à promouvoir une pêche durable dans le Sud-Est d’Haïti.
|
||||||
|
|
||||||
|
Face aux pressions climatiques et anthropiques, il combine des technologies innovantes (capteurs environnementaux, drones, caméras sous-marines et intelligence artificielle) et une approche participative impliquant les communautés de pêcheurs.
|
||||||
|
|
||||||
|
Le projet permettra de suivre la santé des récifs, de restaurer les zones dégradées et d’améliorer la gestion des ressources halieutiques, contribuant ainsi à la conservation des écosystèmes marins, à la sécurité alimentaire et au développement durable des communautés côtières de Belle-Anse.",true,Through my university and professional networks and partnerships,,,
|
||||||
|
,Samuel Nnaji,+2348161502448,Envoyer le message,realstard247@gmail.com,Zero Ocean,Benjamin Odusanya,"Enugu, Nigéria","Africa, Nigeria",University of Nigeria,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Sustainable shipping & yachting,"Project: Zero Ocean
|
||||||
|
|
||||||
|
Idea: Digital platform for transparent, efficient, and sustainable clean fuel supply chain in maritime
|
||||||
|
|
||||||
|
Objectives:
|
||||||
|
- Optimize clean fuel procurement and reduce emissions
|
||||||
|
- Ensure compliance with global regulations
|
||||||
|
- Enhance bunkering efficiency and audit trails
|
||||||
|
Key Features: eBDN, AI-driven analytics, real-time tracking, supplier integration",true,WhatsApp,,,
|
||||||
|
,Hannah Gillespie,+447887479247,Envoyer le message,hggillespie12@gmail.com,SeaBrew Coffee,"Anne Moullier, Joseph Flynn, Hannah Gillespie, Laura Coombs, Ronan Cooney","Cambridge, Angleterre, Royaume-Uni",UK,"University of Cambridge, Cambridge, UK",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Sustainable fishing and aquaculture & blue food,"SeaBrew is an early-stage food and drink start-up developing a seaweed-reinforced coffee designed to improve micronutrient intake through an existing daily habit. Our product combines sustainably sourced seaweed with coffee to deliver nutrients such as magnesium, while maintaining taste and consumer acceptability. We have already conducted a blind taste test with positive consumer feedback and recently pitched SeaBrew to EIT Food, where we were awarded second place, which has encouraged us to progress towards more rigorous technical validation and compliance ahead of scaling.",true,The Ocean Opportunity Lab (TOOL),,,
|
||||||
|
,Rhea Thoppil,+33745764372,Envoyer le message,rmthoppil@gmail.com,phytoflight,Rhea Thoppil,"Kerala, Inde",Asia,"Sorbonne University, France",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Reduction of pollution (plastics chemicals noise light...),"PhytOFlight
|
||||||
|
Plant-based mitigation of plastic pollution in Kerala’s backwaters
|
||||||
|
|
||||||
|
PhytOFlight is a nature-based initiative that uses phytoremediation and native aquatic vegetation to mitigate plastic and microplastic pollution in Kerala’s backwaters. Inspired by the “fight or flight” response, the project uses plants as active ecological defenders that intercept, trap, and reduce plastic waste while restoring ecosystem health.
|
||||||
|
|
||||||
|
Kerala’s backwaters are ecologically and economically vital, yet increasingly threatened by plastic pollution from domestic waste, and tourism. Conventional cleanup methods are costly and short-lived. PhytOFlight offers a low-cost, sustainable, and scalable alternative that works with natural processes rather than relying solely on mechanical removal.
|
||||||
|
|
||||||
|
Objectives
|
||||||
|
Reduce macroplastic and microplastic pollution in targeted backwater zones, improve water quality and support aquatic biodiversity and engage local communities in monitoring, maintenance and environmental awareness of such areas
|
||||||
|
|
||||||
|
PhytOFlight integrates ecological restoration with pollution control, offering a cost-effective, climate-resilient solution tailored to Kerala’s backwaters in India.",true,Through my university,,,
|
||||||
|
,Ethan Jezek,+18178996766,Envoyer le message,ejezek12@gmail.com,OceanID,Ethan Jezek,"Dallas, TX, États-Unis",US,"I have started this concept myself in Dallas, Texas but I am also a PhD candidate at the University of Waikato in New Zealand",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Consumer awareness and education,"I have been developing an AI integrated app called OceanID that helps users identify marine species (vegetation, algae, and animals) by uploading photographs. By doing so, and by providing key and exciting information to users, I have ambitions of improving and better establishing community education and outreach, as well as marine networking in communities around the globe. Upon identifying an organism, users are presented with key ecological and economical information about the organism they captured on camera, recent publications, distribution, and if the species is currently a foodstuffs, will be presented with recipes, information on how to safely and sustainably harvest, and sustainable producers where a user could buy ingredients for said recipe . For higher level users, e.g. ocean users such as fishers, farmers, and researchers, information on permitting, local processors, producers, and developers is also provided (this information is provided for all users but intended to be helpful and beneficial for higher-level users).
|
||||||
|
|
||||||
|
Other functions on the app include; a database of all species the app has identified, a community tab that displays the discoveries of nearby and followed users, a map function where users can see community discoveries and the location of permit zones, and key economic players (see above) in relation to their location, and a cookbook that saves all of the recipes that a user has collected.",true,I heard of the MOPC through colleagues I have on LinkedIN,,,
|
||||||
|
,Nnaji Samuel Ebube,+2348161502448,Envoyer le message,nnajisamuel2448@gmail.com,OceanFin,"Ifeoma Odusanya, Benjamin Odusanya","Enugu, Nigéria","Africa, Nigeria",University of Nigeria Nsukka,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Capacity building for coastal communities,"🌟 *Project: OceanFin - Boosting Nigeria's Blue Economy 🌊*
|
||||||
|
- *Idea*: Empower coastal communities with digital financial services for sustainable ocean-based livelihoods 🐟
|
||||||
|
- *Objectives*:
|
||||||
|
- Increase financial services 📈
|
||||||
|
- Improve financial inclusion for fishermen, traders 💸
|
||||||
|
- Promote sustainable ocean practices 🌿
|
||||||
|
- *Key features*: Digital payments, loans, insurance, FX services, international partnerships 🌍",true,Online,Received,https://drive.google.com/drive/folders/10-Xa_exXqDL83JlnFq4-TnAjcL_jPgT-?usp=drive_link,
|
||||||
|
,Sofie Boggio Sella,+61448568796,Envoyer le message,boggiosellasofie@gmail.com,PMRF: Probabilistic Multi Reef Fusion pipeline,"Sofie Boggio Sella, Lily Lewis, Mohammad Jahanbakht","Turin, Piémont, Italie","Europe, Italia",James Cook University Australia,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Restoration of marine habitats & ecosystems,"The project develops an AI-driven system to predict where coral reefs are most likely to survive under future climate conditions. By fusing seafloor structure, reef imagery, environmental data, and biodiversity indicators into a single probabilistic model, it moves beyond mapping what exists today to forecasting where restoration and protection will be most effective tomorrow. Its objective is to identify climate-resilient “safe havens” and restoration hotspots, providing actionable, uncertainty-aware maps for scientists and conservation practitioners. This enables smarter allocation of limited resources, transforming coral conservation from reactive damage control into a proactive strategy for long-term reef resilience.",true,Linkedln,,,
|
||||||
|
,Christine Kurz,+4917622904612,Envoyer le message,christine.a.kurz@gmail.com,Xy,Xy,,,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,,,,,,,
|
||||||
|
,Antonella Bongiovanni,+393286093034,Envoyer le message,info@evebiofactory.com,EVE Biofactory,Antonella Bongiovanni - Natasa Zarovni - Mauro Manno - Paolo de Stefanis - Lorenzo Sbizzera - Gabriella Pocsfalvi - Paola Gargano,"Palerme, Sicile, Italie","Europe, Italia",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2022-09-29,Technology & innovations,"EVE Biofactory is a deep-biotech company leveraging microalgae to build the most scalable nano drug-delivery platform on the market.
|
||||||
|
Inefficient drug delivery causes treatment failure, patient harm, and up to $40B in annual losses from underperforming bioactives.
|
||||||
|
Inspired by the smallest ocean organisms, EVE develops Nanoalgosomes: naturally occurring exosomes produced from microalgae, the only delivery system that is scalable, circular, and fully biological.
|
||||||
|
Nanoalgosomes are cost-competitive, biologically active, and more efficient than synthetic nanoparticles, enabling lower drug doses and reducing the release of medicines and persistent nanomaterials into wastewater that today impact river and ocean ecosystems.",true,Our mentor Alessandro ROmano pointed out the challenge and recommended our project would be a good fit.,Received,https://drive.google.com/drive/folders/1imjoZvbTn-c-xjqgr4C9J_BP5JZz1gyX?usp=drive_link,
|
||||||
|
,Justyna Grosjean,+33685638357,Envoyer le message,justyna@cleanoceancoatings.com,Clean Ocean Coatings GmbH,"Christina Linke, Jens Deppe, Friederike Bartels, Johana Chen, Sandra Lötsch, Patricia Greim","Hambourg, Allemagne","Europe, Germany",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2021-05-11,Sustainable shipping & yachting,"The Antifouling Coating of Tomorrow.
|
||||||
|
Lower Costs. Cleaner Oceans. Decarbonating Shipping.",true,Through the Fondation Prince Albert II de Monaco,,,
|
||||||
|
,Erick Patrick dos Anjos Vilhena,+5596981337237,Envoyer le message,e.vilhena@hotmail.com,sustainable fish leather,Andria Carrilho,"Amapá, AP, Brésil",South America,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2023-01-01,Sustainable fishing and aquaculture & blue food,"Sustainable fish leather production is more than just an exotic alternative; it addresses critical issues within the fashion and food industries, as well as the environment.
|
||||||
|
|
||||||
|
Here are the main problems this solution solves:
|
||||||
|
|
||||||
|
1. Waste in the Fishing Industry (Circular Economy)
|
||||||
|
Currently, the vast majority of fish skins resulting from human consumption are discarded as organic waste.
|
||||||
|
|
||||||
|
The Problem: Thousands of tons of skins end up in landfills or are thrown back into rivers and oceans, causing pollution due to excess organic matter.
|
||||||
|
|
||||||
|
The Solution: It transforms a by-product (waste) into a high-value material, closing the loop of the circular economy.
|
||||||
|
|
||||||
|
2. Environmental Impact of Bovine Leather
|
||||||
|
Traditional (cow) leather carries a heavy ecological footprint that fish leather helps to mitigate.
|
||||||
|
|
||||||
|
Deforestation: Cattle ranching is a leading cause of deforestation. Fish production does not require new pastures.
|
||||||
|
|
||||||
|
Water Consumption: Raising cattle consumes massive volumes of water compared to existing aquaculture or artisanal fishing.
|
||||||
|
|
||||||
|
Carbon Emissions: Producing fish leather emits significantly fewer greenhouse gases than the beef industry supply chain.
|
||||||
|
|
||||||
|
3. Toxicity in Processing (Tanning)
|
||||||
|
Industrial tanning of common leathers often uses Chromium, a heavy metal that is highly polluting if disposed of incorrectly.
|
||||||
|
|
||||||
|
The Difference: Sustainable fish leather solutions focus on vegetable tanning (using tannins extracted from tree barks and plants). This eliminates toxic waste and results in a biodegradable product that is safe for both artisans and consumers.
|
||||||
|
|
||||||
|
4. Durability vs. Aesthetics
|
||||||
|
Many leather alternatives (such as ""synthetic leather"" made of plastic/PU) have low durability and pollute the environment with microplastics.
|
||||||
|
|
||||||
|
The Solution: Fish leather has a cross-fiber structure (unlike the parallel fibers in bovine leather), making it extremely strong and tear-resistant despite being thin. It solves the dilemma for those seeking a material that is delicate, durable, and eco-",true,Linkedln,,,
|
||||||
|
,Amaia Rodriguez,+34606655862,Envoyer le message,amaia@thegravitywave.com,GRAVITY WAVE,"Amaia Rodriguez, Julen Rodriguez, Naiara Lopez, Alvaro Garcia, Camila Lago, Norberto De Rodrigo, Irene Hurtado","Madrid, Communauté de Madrid, Espagne","Europe, Spain",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2020-05-18,Reduction of pollution (plastics chemicals noise light...),Clean plastic from the sea with fishermen and transform the waste into materials for construction and architecture.,true,A friend sent it to me,Received,https://drive.google.com/drive/folders/11McKvPyKzbUgYiFd2gfeWvLrlGGPhyP2?usp=sharing,
|
||||||
|
,Dr Mumthas Yahiya,+917012789400,Envoyer le message,mumthasy@gmail.com,Migratory birds,Thamanna K,Nil Yucel DDS,US,Kerala,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Mitigation of ocean acidification,"“Nature-Based Solutions for Mitigating Ocean Acidification through Coastal Blue Carbon Ecosystems - Project Idea
|
||||||
|
|
||||||
|
This project focuses on mitigating ocean acidification by enhancing and restoring blue carbon ecosystems such as mangroves, seagrasses, and salt marshes. These ecosystems absorb atmospheric CO₂, increase local alkalinity, and act as natural buffers against pH reduction in coastal waters. The study will evaluate their potential as cost-effective, climate-resilient mitigation strategies.
|
||||||
|
|
||||||
|
Objectives
|
||||||
|
|
||||||
|
To assess the role of mangroves and seagrass meadows in reducing coastal seawater acidity.
|
||||||
|
|
||||||
|
To quantify carbon sequestration and alkalinity enhancement in selected coastal habitats.
|
||||||
|
|
||||||
|
To evaluate ecosystem-based management practices as mitigation tools for ocean acidification.
|
||||||
|
|
||||||
|
To provide policy-relevant recommendations for integrating blue carbon ecosystems into coastal climate action plans.
|
||||||
|
|
||||||
|
Relevance
|
||||||
|
|
||||||
|
The project supports climate change mitigation, marine biodiversity conservation, and sustainable coastal management while addressing the growing threat of ocean acidification.",true,IUCN,Received,https://drive.google.com/drive/folders/17BJ-6Snt_OsUtZxDlg-Kj8t4rjVoE5JO?usp=drive_link,
|
||||||
|
,yvano voigt,+33652294558,Envoyer le message,yvano.voigt@gmail.com,Totem by FrenchKiss suncare,"yvano voigt, Elsa Delpace","Nice, Provence-Alpes-Côte d'Azur, France","Europe, France","Ipag business school, Nice France",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Reduction of pollution (plastics chemicals noise light...),"Reducing plastic waste
|
||||||
|
|
||||||
|
Reducing skin cancer rates
|
||||||
|
|
||||||
|
Reducing coral reef destruction",true,thanks to the oceanography museum,Received,https://drive.google.com/drive/folders/1o8Fx4Jh4D2n7iOkgqWEnybbRIe2w6Cp0?usp=drive_link,
|
||||||
|
,Lily Atussa Payton,+13015297789,Envoyer le message,lily.a.payton@gmail.com,Oyster Club NYC,"Lily Payton, Kelsey Burkin, Savannah Harker","New York, NY, États-Unis",US,N/A,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Consumer awareness and education,"Oyster Club NYC is a fledgling organization aimed at giving New Yorkers a hands-on connection to their maritime past, present, and future through the lens of ocean sustainability. Rending New Yorkers that NYC is truly their oyster, and that some of the strongest communities are built around the smallest of creatures. Specifically, we create bespoke events aimed at bringing together people to create community, discuss how making small choices can benefit our oceans, such as eating oysters, all while having fun in the process. This has manifested in a Learn-to-Shuck Holiday Party in December and a monthly oyster happy hour at various locations across the city. Our specific objectives are threefold:
|
||||||
|
- use oysters as a catalyst to expose New Yorkers to sustainable and regenerative food in a social environment,
|
||||||
|
- embed an oceans-focused mindset into an island city that often forgets its connection to the water, and
|
||||||
|
- build a climate-minded community across the five boroughs.",true,Online research,,,
|
||||||
|
,Gary Molano,+12135198233,Envoyer le message,gary@macrobreed.com,MacroBreed,"Scott Lindell, Charles Yarish, Filipe Alberto","New York, NY, États-Unis",US,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2023-07-19,Sustainable fishing and aquaculture & blue food,"We breed better seaweed using genomic breeding techniques. We started with kelp, and have achieved 4fold harvestable yield gains in 5 years of breeding, a 10x speed advancement compared to breeding efforts in Asia. We are currently targeting traits that increase the value of seaweed, such as lower iodine and higher bioactive composition (fucoidan, alginate, laminarin, etc), to help make farmed kelp more competitive with wild harvests. We also have a breeding scheme that produces ""sterile"" kelp to protect local ecosystems from farmed kelp. This sterile kelp is produced using non-GMO techniques.",true,Through the ocean exchange newsletter,,,
|
||||||
|
,Qendresa Krasniqi,+4798474602,Envoyer le message,qendresa04@gmail.com,Aegir by Navier USN,"Qendresa Krasniqi, Hedda Collin, Markus Marstad","Horten, Vestfold, Norvège","Europe, Norway",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2022-10-06,Restoration of marine habitats & ecosystems,"We are developing a specialized ROV designed to restore marine ecosystems. Coastal ecosystems, such as the Oslofjord, are currently threatened by invasive species and marine debris. Specifically, the Pacific oyster is spreading rapidly, requiring efficient and noninvasive methods for removal to protect local biodiversity. Our current prototype is part of a joint venture with 'Matfat Oslofjorden,' where it will harvest invasive oysters from the Oslo Fjord to be repurposed as a sustainable food source. Our solution is efficient, non-invasive, and fully programmable for diverse oceanic habitats and tasks. Navier USN is not starting from scratch with a proven track record in developing autonomous surface vehicles (ASVs), our startup concept expands this expertise into the underwater domain. We are a seasoned technical and commercial team with a proven track record in autonomous maritime technology, including multiple world championship titles. Supported by prominent industry partners, we have the proven competence and scale to transform maritime environmental management",true,1000 Ocean StartUps,,,
|
||||||
|
,Dorra Fadhloun,+21629508048,Envoyer le message,dorra.fadhloun@msb.tn,Oceani,Samar,"Tunis, Tunisie","Africa, Tunisia",Mediterranean School of Business,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Technology & innovations,PlastiTrack's goal is to turn citizen smartphone photos into citywide microplastic pollution heatmaps that municipalities use to prioritize cleanup investments.,true,LinkedIn,,,
|
||||||
|
,Ahamed Adhnaf,+94760270097,Envoyer le message,anaadhnaf413@gmail.com,OCEAN RENEW,"Ahamed Adhnaf , Kaveesha Gunarathna, Mohammed Rifath","Colombo, Western, Sri Lanka","Africa, Sri Lanka",National Institute of Social Development - Sri Lanka,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Capacity building for coastal communities,"AquaHorizon is a holistic ocean innovation hub that transforms ocean challenges into solutions while empowering coastal communities. Our objectives are to develop sustainable practices for marine conservation, reduce pollution, provide education and capacity building for coastal populations, and create a collaborative space where innovators can design and implement solutions for a healthier ocean and thriving communities.",true,I learned about the Monaco Ocean Protection Challenge through a friend.,,,
|
||||||
|
,Robert Kunzmann,+441751026862,Envoyer le message,robert.kunzmann@acbiode.com,AC Biode,Robert Kunzmann,Luxembourg,"Europe, Luxembourg",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2019-04-01,Reduction of pollution (plastics chemicals noise light...),"According to the UN, only 9% of 8.3 billion tons of plastic waste have been recycled over the past 65 years.
|
||||||
|
Most of the plastic waste ends up burned, burried or in the oceans. This profound impact on our environment is not fully understood yet, but micro plastics have been found in all the way from glaciers, to human placentas.
|
||||||
|
Today, recycling is not economically feasible in most situations. There are several reasons for this. Firstly, many recycling methods cannot handle mixed waste and therefore require waste to be sorted. Where chemical recycling can accept mixed plastic, the high temperature or pressure requirements lead to high costs. This is why recycling rates remain low. Plastalyst makes it possible to break down waste into core chemicals such as monomers or hydrogen and carbon monoxide (syngas for SAF and biodiesel).
|
||||||
|
Organic waste is decomposed into alcohols and syngas, whereas plastic is decomposed into methanol, alkanes or monomers. It uses only water, waste, and a reusable catalyst as input. The reaction occurred at a temperature of only 200°C. Compared to other methods, our method has significant advantages such as low energy use, which results in lower operational cost. Next, we use water as a solvent, drying of waste is not needed, therefore it will cut the cost of preparing the material. Lastly, no solvent is needed and no CO2 is emitted in the reaction. Unlike organic methods, such as biodigestion that require a lot of time and emit a lot of CO2, Plastalyst is a fast chemical method that emits no CO2.",true,Climate KIC,Received,https://drive.google.com/drive/folders/1GM1vz-uq_oHcbFEh_oRTjQsDaqi6Tap3?usp=drive_link,
|
||||||
|
,Mayoro MBAYE,+221776441916,Envoyer le message,dg@kma-international.com,MER SEA GUEDJI,Mayoro MBAYE +Mbacké SECK+Ali DOUCOURÈ,"Dakar, Sénégal","Africa, Senegal",,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Capacity building for coastal communities,"The MER SEA GUEDJI center designated according to the "" Educational Archipelago"" concept ,composed of self- contained educational modules connected by landscaped pathways . This lightweight,reversible,and bioclimatic architecture respect the public martime domain integrates harmoniously into the natural and social environment .",true,Dr Manon Aminatou,Received,https://drive.google.com/drive/folders/1KhvsOxPOzg9cKsDG0UiI9Y1lVYWGV3ta?usp=drive_link,
|
||||||
|
,Divin Arnaud KOUEBATOUKA,+242069323235,Envoyer le message,divinkoueba@gmail.com,Green Tech Africa,"Our lean startup is primarily run by a team of 3: Divin, Osvaldo, and Jessica, alongside a great supportive team of advisors. Using his skills in tech and as a civil & environmental engineer, Divin has designed and built several innovations geared towards sustainability, including the solar dryer now being used across Congo to revive the pyrethrum industry, smart roads, and smart pipes. The Central Africa Community recently awarded him the best innovator in Congo. He is the CEO. Working for L'Oréal, Osvaldo is well-versed in manufacturing and running supply chain processes. This experience, in addition to being an engineer, makes him an ideal CTO. Jessica has an extensive background in tech and finance. She has been a Microsoft Ambassador and Hult Prize coordinator. Her experience in handling projects and relationships with global agencies and customers is handy in her CFO role. She also hails from Homabay, Congo, where hyacinth surrounds the island for days and weeks at times. This blocks waterways and sometimes prevents children from going to school. The team has achieved several milestones, such as making scientific validation and proof of concept of the products, raising over CFA 3M in funding, and winning significant international awards, including the Central Africa Youth for Climate Action Award, Best Manufacturing Startup in Congo, Best Innovation in Congo by EAC, the World Engineering Day Hackathon by UNESCO, the TotalEnergies Startup of the Year, and Falling Walls Lab Brazzaville.","Brazzaville, République du Congo","Africa, Congo",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2022-07-27,Restoration of marine habitats & ecosystems,"Our project transforms invasive water hyacinth, a major threat to rivers, coastal lagoons and marine ecosystems, into 100% organic absorbent solutions used to control oil and chemical pollution.
|
||||||
|
By harvesting water hyacinth before it reaches estuaries and coastal zones, we prevent ecosystem degradation while supplying industries and ports with sustainable spill-response materials.
|
||||||
|
Our flagship product, KUKIA®, absorbs hydrocarbons efficiently and is later recycled into alternative fuel for cement plants, creating a circular, zero-waste model.
|
||||||
|
The project combines ocean and freshwater protection, industrial pollution control, and community empowerment, generating income for women-led harvesting groups while reducing marine contamination risks.
|
||||||
|
This scalable solution contributes directly to ocean conservation, blue economy resilience, and sustainable industrial practices in Africa and beyond.",false,"I learned about the Monaco Ocean Protection Challenge through professional networks and sustainability-focused opportunity monitoring platforms, including LinkedIn and grant-funding communities dedicated to ocean and climate innovation.",Received,https://drive.google.com/drive/folders/1uHEFuI-iosKap2OPSUdQsbXuih07g7n0?usp=drive_link,
|
||||||
|
,Paul Schmitzberger,+436642347890,Envoyer le message,laura@blue-planet-ecosystems.com,Blue Planet Ecosystems,"Paul Schmitzberger, Cécile Deterre, Stephan Mayrhofer, Jens Cormier, Pierre De Villiers, Stephan Sergides, Jakob Weber, Romana Zabojnikova, Laura Belz","Vienne, Autriche","Austria, Europe",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2019-11-15,Sustainable fishing and aquaculture & blue food,"We decouple the production of marine protein from the ocean by replicating aquatic ecosystems in modular, automated, plug-and-play systems. We combine hardware, software and biology in products called LARA and Vortex. These modular units are controlled and operated by AI agents under the remote supervision of our biologists and system engineers. The agents optimize the growth of microalgae, zooplankton and fish or shrimps for human consumption. Our goal is to establish large-scale, environmentally friendly aquaculture operations in diverse environments, advancing global food security and sustainability.",false,Online search,Received,https://drive.google.com/drive/folders/1mOvHJ8mVRok_Sixd2484jR6pGLbN3KyA?usp=drive_link,
|
||||||
|
,Julia Denkmayr,+393203476632,Envoyer le message,julia@nereia-coatings.com,NEREIA,Rimah Darawish,"Salzbourg, Autriche","Austria, Europe",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2026-04-30,Sustainable shipping & yachting,"NEREIA develops non-toxic antifouling technology using functional surfaces to prevent biofouling on ship hulls, reducing drag and improving fuel efficiency. Eliminating hull fouling could save the shipping industry up to $30bn in fuel costs and 200 Mt of CO2 annually.",false,"Through a Carbon 13 domain expert, Thibaut Monfort Micheo, as well as LinkedIn and other social media.",Received,https://drive.google.com/drive/folders/1vdRP4PIQBCZ3xuRqXk2NHyW-1o0JAbZ9?usp=drive_link,
|
||||||
|
,Tara Lepine,+64273401929,Envoyer le message,tlep171@aucklanduni.ac.nz,OceanSeed,Tara Lepine,"Ottawa, ON, Canada",Canada,"University of Auckland, Auckland, New Zealand",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Sustainable fishing and aquaculture & blue food,"OceanSeed deploys mobile hatchery units as emergency response infrastructure to restore marine ecosystems and fisheries after collapse, often caused by climate change. Its objectives are to rapidly produce native juvenile shellfish, rebuild ecosystems and keystone populations, protect biodiversity, and support local livelihoods. OceanSeed can be scaled globally through a network of mobile hatcheries, using aquaculture as a tool for conservation and climate adaptation.",false,Communication from our university department head.,Received,https://drive.google.com/drive/folders/1zNrkuAtAAcqyLPkiyLH8ihV675jWbF1Y?usp=drive_link,
|
||||||
|
,Reid Barnett,+19198010336,Envoyer le message,reidbarnett@ceretunellc.com,Ceretune LLC,Reid Barnett and Blake Parrish,"Mt Olive, NC, États-Unis",US,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-05-01,Reduction of pollution (plastics chemicals noise light...),"Our project is using our proprietary technology to grow plants directly on the surface of natural and manmade surface waters. This allows us to sequester carbon, nutirents, and other chemical pollutants at scale in both ocean systems and the freshwater systems upstream. After the plants are fully grown the entire system, material and biomass, can be pyrolyzed to generate carbon negative energy and lock carbon away in a stable form. This process is highly efficient and extremely inexpensive. When we pyrolyze the system we generate over three times more revenue than the total cost of the system and its operation, while opening up opportunites for blue carbon credits and creating bio-oil which can be refined for various uses.
|
||||||
|
This project is about creating an entirely new pathway for pollutants in the environment to redirect them from where they cause harm and towards where they can generate value and do good.",true,We were connected through the team at Ocean Exchange.,,,
|
||||||
|
,Ryan Borotra,+16479659526,Envoyer le message,ryan@sentrylabs.cc,Sentry Labs,"Ryan Borotra, Martin Chaperot, Andrei Bogza","Montréal, QC, Canada",Canada,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2025-10-20,Sustainable fishing and aquaculture & blue food,"Sentry Labs is developing graphene field-effect transistor (GFET)–based molecular sensors for sustainable fishing, aquaculture, and blue food systems. The project focuses on real-time, in-situ detection of biologically and chemically relevant signals in seawater to enable earlier identification of environmental and biological risks affecting farmed and wild stocks. Our objective is to provide robust, reproducible sensing systems that support healthier stocks, reduced losses, and more sustainable management of marine food production.",true,LinkedIn,,,
|
||||||
|
,Nadine Hakim,+573205421979,Envoyer le message,nadinehakimm@gmail.com,SEAMOSS COLOMBIA,Sandra Bessudo and Irene Arroyave,"Bogota, Colombie",South America,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2025-10-01,Sustainable fishing and aquaculture & blue food,"SEAMOSS is a sustainable biodesign and coastal livelihood project focused on the cultivation and transformation of sea moss (marine macroalgae) as a nature-based solution to environmental and social challenges in coastal communities. The project combines regenerative aquaculture, biomaterial development, and community-led value chains to reduce pressure on marine ecosystems while creating local economic opportunities.
|
||||||
|
|
||||||
|
The core idea is to cultivate native sea moss species using low-impact, regenerative methods and transform the biomass into biodegradable materials and functional products that can replace plastic-based alternatives, particularly in packaging, design, and everyday consumer goods.",true,,,,
|
||||||
|
,Maria Ester Faiella,+393311538952,Envoyer le message,maria.ester.faiella@gmail.com,ThermoShield,Maria Ester Faiella,"Rome, Latium, Italie","Europe, Italia",The American University of Rome,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Restoration of marine habitats & ecosystems,"ThermoShield is a modular underwater panel system that passively reduces local heat from coastal infrastructure. Its objective is to prevent thermal stress on sensitive marine ecosystems, protecting coral reefs and seagrass worldwide. The panels are easy to install, require no electricity and provide measurable local temperature reductions of 0.3–0.5°C, making the solution scalable and globally applicable.",true,LinkedIn,Received,https://drive.google.com/drive/folders/11mSSY2USGnxyypDwJa5DYiVLUlo6iyvF?usp=drive_link,
|
||||||
|
,Kumari Anushka,+919798061093,Envoyer le message,nasabutbetter@gmail.com,accore,Kumari Anushka,"Jamshedpur, Jharkhand, Inde",Asia,Ashoka University,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Restoration of marine habitats & ecosystems,"Coral reefs are collapsing - rising ocean temperatures have triggered mass bleaching, 84.6% of corals in Lakshadweep bleached recently. India has 1,439 km² of mapped coral reefs, coasts have 80 ± 33 microplastic particles per cubic meter, and ~30% of sampled market fish have microplastics. Odisha’s Bay of Bengal estuaries have elevated metal concentrations.
|
||||||
|
|
||||||
|
Each Reef Revival Pod is a solar-powered floating buoy deployed near degraded reefs with:
|
||||||
|
1. Underwater acoustics: healthy reefs produce sounds that can be played near dying reefs to attract marine life back to them. In trials, degraded patches with reef sounds saw fish population double.
|
||||||
|
2. Each pod pumps the surrounding seawater through fine filters to capture microplastic debris.
|
||||||
|
3. Water is also pumped through replaceable resin-based adsorption cartridges to bind with dissolved heavy metals in the water.
|
||||||
|
4. Onboard sensors log water quality (temperature, pH, turbidity, etc.) - collecting data for adaptive management.
|
||||||
|
|
||||||
|
After success in India’s waters, the project will be expanded to coral regions globally.
|
||||||
|
|
||||||
|
In India, the CRZ notification 2019 classifies coral reefs as ecologically sensitive (CRZ-I A) and regulates activities in coastal waters (CRZ-IV), so my revival pods should be permitted as non-invasive research/restoration infrastructure (no reef anchoring and removable).
|
||||||
|
|
||||||
|
The MoEFCC National Coastal Mission Scheme funds coral/mangrove conservation action plans, marine & coastal R&D - this would help with scaling the number of buoys deployed.
|
||||||
|
|
||||||
|
Also, the World Bank-supported Integrated Coastal Zone Management (ICZM) gives importance to science-based coastal planning; pods’ sensor data could be used for threat mapping and adaptive management in the deployed zones.
|
||||||
|
|
||||||
|
For global scaling: Australia’s Reef 2050 Plan, Indonesia’s COREMAP, and the US NOAA Coral Reef Conservation Program exist, so the project could plug into existing national funding priorities across eligible countries.",true,My university's professor,,,
|
||||||
|
,Daniela Nairita,+254717162468,Envoyer le message,nairita@yarsi.net,Yarsi Aquacycle,"Christabell Adhambo, Snyder Phoebe, Ken Lenguro, Walter Mwaluma, Zuhura Ahmed","Marsabit, Kenya","Africa, Kenya",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2023-11-22,Reduction of pollution (plastics chemicals noise light...),"YARSI Aquacycle is a circular blue economy enterprise focused on transforming aquatic waste into high-value, sustainable products. The core idea is to close resource loops in the fisheries and aquaculture sectors by recovering fish by-products and underutilized biomass and converting them into commercially viable outputs, reducing environmental pollution while creating new income streams.
|
||||||
|
Objectives:
|
||||||
|
1.Valorize aquatic waste into high-value sustainable products (fish oil, fish skin, bio-compost).
|
||||||
|
2.Reduce environmental pollution and post-harvest losses in fisheries and aquaculture.
|
||||||
|
3.Advance circular blue economy solutions that create livelihoods and scalable green enterprises.",true,Social media,Received,https://drive.google.com/drive/folders/1UHBRDWWBZSsHqcL_itb8MIm7h73qmHVU?usp=drive_link,
|
||||||
|
,B,+13065674532,Envoyer le message,brulebennett@gmail.com,f,d,,,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,5555-05-05,Mitigation of climate change and sea-level rise,,,,Ignore,,
|
||||||
|
,Yago Sierras,+34655815216,Envoyer le message,yagosierras@mediterraneanalgae.com,Mediterranean Algae Technologies,"Yago Sierras, Silvia Antón, Guillermo del Barco, Alejandro Simón, Claudia Sanchez, Tamara Terceras, .... +18 people","Alicante, Valence, Espagne","Europe, Spain",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2021-03-26,Mitigation of ocean acidification,"Mediterranean Algae builds nature-based climate infrastructure for ports and coastal industries, cleaning water in real time, reducing emissions and turning pollution into revenue.",true,Linkedin,Received,https://drive.google.com/drive/folders/1J2_F3IIPouPjJWLPwGi0GUkCOXu441ca?usp=drive_link,
|
||||||
|
,h,+12014445678,Envoyer le message,h@web.de,s,s,,,h,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Blue Carbon,d,false,g,Ignore,,
|
||||||
|
,Loup Païtard,+33783500222,Envoyer le message,louppaitard@gmail.com,Ti' Moan,"Jason Franchet, Mathis Beyre",Centre Saint-Denis de la Réunion,"Europe, France",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2026-02-28,Sustainable fishing and aquaculture & blue food,"My project aims to valorise macroalgae that naturally grow on oyster farms, particularly on oyster bags installed on tables in the Gulf of Morbihan, France. These algae, mainly Ulva intestinalis (Enteromorpha), develop without any external inputs or additives, directly linked to environmental conditions and existing shellfish farming structures.
|
||||||
|
|
||||||
|
In conventional oyster farming, the excessive growth of these algae is considered a constraint, as it limits water circulation and can affect oyster growth. As a result, they are routinely removed and discarded — a standard practice in oyster farms throughout France. By requalifying this overlooked biomass as a co-product of oyster farming, my approach turns a nationwide constraint into a scalable opportunity, without altering existing practices or generating additional environmental impact.
|
||||||
|
|
||||||
|
By harvesting algae that would otherwise be eliminated, the project contributes to improving water circulation, reducing organic accumulation on farming structures, and enhancing the overall functioning of the ecosystem. This practice supports healthier oyster production while making use of a renewable, naturally occurring resource.
|
||||||
|
|
||||||
|
In the short term, the project focuses on collecting and supplying this algae to identified outlets such as organic fertilisers, animal feed and food processing. In parallel, it aims to strengthen local food systems by making a high-quality marine resource available for nearby transformation, reducing transport distances and supporting coastal economies.
|
||||||
|
|
||||||
|
In the longer term, the project contributes to the development of integrated oyster–algae co-cultivation systems, increasing the resilience of coastal farming activities to climate and sanitary pressures, while actively participating in the sustainable growth of the seaweed sector in line with national and European marine strategies.",true,From my friends from IZALGUE,,,
|
||||||
|
,Elizabeth Okullow,+254724328171,Envoyer le message,elizabethokullow@gmail.com,Bionala,1. Sydney Badiola 2. Heloisa Bredemann 3. Elizabeth Okullow,"Kisumu, Kenya","Africa, Kenya","University of Liège (HEC Liège), Belgium",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Sustainable fishing and aquaculture & blue food,"Bionala is a circular bioeconomy venture that transforms unavoidable seafood by-products into high-value marine bio-ingredients, including protein powders, fish oil, and collagen. The project addresses pollution and environmental externalities in fisheries and aquaculture by intercepting seafood by-products before they are dumped or left to decompose, reducing harm to aquatic ecosystems. Bionala works with existing seafood value-chain actors to create a traceable, scalable sourcing model and supplies sustainable marine bio-ingredients to global food, nutrition, and cosmetic markets, contributing to more resilient and resource-efficient blue food systems.",true,LinkedIn,,,
|
||||||
|
,Maria,+51968969569,Envoyer le message,maria.moralesleguia@gmail.com,metalmas,juan morales,Peru,South America,PUCP,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Technology & innovations,ETENOLMAL,true,NOTHING,,,
|
||||||
|
,Indira Angela Luza Eyzaguirre,+5591985374539,Envoyer le message,ieyzaguirre@reinnova.org,Guardianes de la Costa: Monitoreo costero participativo para la protección de playas urbanas,"Indira Angela Luza Eyzaguirre, Leonardo Jasiel Luza Eyzaguirre, Marco Ivart Mateo Eyzaguirre, Josefina Eyzaguirre Flores","Lima, Peru",South America,"Universidad Tecnológica del Perú - UTP, Lima, Perú",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Capacity building for coastal communities,"Guardianes de la Costa is a participatory coastal monitoring initiative designed to protect urban beaches by generating reliable local data and empowering coastal communities. The project trains citizens to monitor key indicators such as erosion, pollution, and coastal biodiversity using simple, replicable protocols. The collected data supports evidence-based decision-making, early detection of coastal degradation, and the implementation of nature-based and community-led solutions. Piloted on urban beaches in Lima, the model is scalable and adaptable to other coastal cities facing similar environmental pressures.",true,In instagram,Doublon,,
|
||||||
|
,Leonardo Jasiel Luza Eyzaguirre,+51942673128,Envoyer le message,resilienciainnovadora@gmail.com,Mangrove Watchers: Data-driven conservation with shellfish and crab harvesters,"Leonardo Jasiel Luza Eyzaguirre, Indira Angela Luza Eyzaguirre, Marco Ivart Mateo Eyzaguirre, Josefina Caridad Eyzaguirre Flores","Zarumilla, Tumbes, Peru",South America,Universidad Nacional Federico Villareal,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Sustainable fishing and aquaculture & blue food,"Mangrove Watchers is a data-driven conservation initiative working with shellfish and crab harvesters to support sustainable mangrove fisheries. The project combines local ecological knowledge with simple monitoring tools to track mangrove health, shellfish and crab stocks, and human pressures. Community-generated data is used to inform sustainable harvesting practices, improve resource management, and strengthen coastal livelihoods. Piloted with artisanal harvesters in northern Peru, the model is low-cost, scalable, and adaptable to mangrove-dependent fisheries across coastal regions.",true,In website and instagram,Received,https://drive.google.com/drive/folders/1dwsY_WDvzJ6tjnDdsYVPA88TRH7gIYo7?usp=drive_link,
|
||||||
|
,Alessandro,+393293603929,Envoyer le message,alessandro.gentili@phd.unipi.it,PerSEAve,"Alessando Gentili, Simone Tani, Francesco Ruscio, Alberto Topini, Guido Lazzerini, Lorenzo Cecchi","Pise, Toscane, Italie","Europe, Italia","University of Pisa, Pisa, Italy",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Restoration of marine habitats & ecosystems,"The project proposes the use of Autonomous Marine Vehicles to enhance the monitoring, mapping, and restoration support of seagrass ecosystems. By integrating advanced sensing, AI-based perception, and autonomous decision-making, the project aims to deliver scalable, non-invasive, and cost-effective innovative solutions for long-term marine ecosystem protection.",false,Linkedin,Received,https://drive.google.com/drive/folders/1eaBXX_iTwppTSYN_JCClUbj_cncwDq3s?usp=drive_link,
|
||||||
|
,Martin Itamalo,+264813813396,Envoyer le message,martin.itamalo@greenbrinetechnologies.com,Green Brine Technologies CC,Essley Kalola,"Windhoek, Khomas, Namibie","Africa, Namibia",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2023-03-23,Other,"Our project, Green Brine Technologies, is a circular economy ""Waste-to-Value"" initiative. We intercept hazardous hypersaline brine from desalination plants—a byproduct of Namibia's growing Desalination and Green Hydrogen industry—and transform it into high-purity industrial chemicals.",true,I read about it on linkedin,Received,https://drive.google.com/drive/folders/1ex5wTDVAivyjTyCd0e9iCYImkXdIkItg?usp=drive_link,
|
||||||
|
,Oscar Crehan,+447783736934,Envoyer le message,oscar@seafuser.com,Ecofuser,Oscar Crehan,"Exeter, Angleterre, Royaume-Uni",UK,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-09-12,Mitigation of climate change and sea-level rise,"Ecofuser has created Seafuser, a simple, low-cost marine device that creates stable, localised nutrient micro-environments to support the health of corals and other vulnerable marine organisms.
|
||||||
|
|
||||||
|
Many restoration and aquaculture systems, including coral nurseries, shellfish, and seaweed farms, are increasingly affected by heat stress, degraded water quality, and nutrient instability. While small, carefully controlled nutrient inputs are known to improve growth and stress tolerance, there is currently no safe, field-ready way to deliver them locally without impacting surrounding waters.
|
||||||
|
|
||||||
|
Seafuser is a passive, in-water diffuser that releases minute quantities of nutrients directly around target organisms. It requires no power or maintenance and can be rapidly deployed by divers or farmers. By operating only at the scale of individual colonies or structures, it avoids the ecological risks associated with bulk nutrient addition.
|
||||||
|
|
||||||
|
The project’s objectives are to:
|
||||||
|
|
||||||
|
Demonstrate improved survival and growth of corals and other marine organisms under environmental stress
|
||||||
|
|
||||||
|
Validate Seafuser as a flexible tool for marine restoration and sustainable aquaculture
|
||||||
|
|
||||||
|
Enable rapid adoption through low cost, simplicity, and compatibility with existing infrastructure
|
||||||
|
|
||||||
|
If successful, Seafuser could support a wide range of ocean protection and food-security efforts, from reef restoration to low-impact aquaculture.",true,Read about it on LinkedIn,Received,https://drive.google.com/drive/folders/1t_RNRRXPLKSxELPyvcYJ_qMK4Tw-s5nH?usp=drive_link,
|
||||||
|
,DOLORES LOPEZ HERNANDEZ,+526122310939,Envoyer le message,doloreslopezhe@gmail.com,Tejiendo Mareas,"Juan Antonio Hernandez Shilon, Isamar Murillo, Raquel Segura","Baja California Sur, Barrio San Miguel, 09360 Iztapalapa, CDMX, Mexique",South America,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2025-08-01,Capacity building for coastal communities,"The formative process is designed so that individuals, youth, communities, and organizations do not only learn a technique, but also understand its meaning, its origins, and its connection to the territory. Learning is not intended to be exact repetition, but conscious adaptation: enabling each participant to bring this knowledge into their own context, transform it, and make it their own, without breaking the roots that sustain it.
|
||||||
|
|
||||||
|
Within this space, traditional knowledge regains its place as a dignified pathway to development. Trades, practices, and inherited knowledge are no longer seen as something belonging to the past, but are recognized as a legitimate source of identity, work, and economic livelihood. Learning a trade is not only about acquiring a skill; it is about reclaiming pride in who we are and where we come from.",true,Linkedln,Received,https://drive.google.com/drive/folders/1vuySyq7p-9R0gsurnKUSskLSsA0VRFe-?usp=drive_link,
|
||||||
|
,Gabriela Casuso Hernández,+573152821916,Envoyer le message,proyectoacuatica@gmail.com,Proyecto Acuática,"Gabriela Casuso, José Casuso and Aida Hernández","Barranquilla, Colombie",South America,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2018-10-08,Consumer awareness and education,"Proyecto Acuática began as a youth-led ocean awareness initiative and progressively evolved into a structured educational system. Initial outreach and educational content allowed the project to test messages, engage young audiences and identify learning gaps, which later informed the development of in-person school programs.
|
||||||
|
|
||||||
|
Over time, these programs were consolidated into Charlas El Océano as the project’s core educational component. The system was further strengthened through online and hybrid events that enable direct interaction between students and marine scientists, transforming education into dialogue and active participation in conservation thinking.
|
||||||
|
|
||||||
|
Implemented continuously for seven years, Proyecto Acuática has reached over 1,900 students through in-person school-based sessions and international youth activities. A distinctive feature of the project is the direct interaction between children, youth and marine scientists, allowing educational spaces to inform conservation thinking and, in some cases, inspire new scientific inquiry.
|
||||||
|
|
||||||
|
The current phase focuses on consolidating impact measurement and scaling the model through institutional partnerships, ensuring that ocean education translates into measurable and lasting conservation outcomes.",true,A friend and social media,,,
|
||||||
|
,Nina Lantinga,+14388630647,Envoyer le message,nina@netsfornetzero.com,Nets for Net Zero,"Purnank Shah, Shadi Khamani","Montréal, QC, Canada",Canada,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2021-03-21,Reduction of pollution (plastics chemicals noise light...),"By 2030, Nets for Net Zero will be a global leader in circular marine plastic systems, leveraging partnerships with coastal communities, fisheries, and manufacturers to advance processing technologies that create resilient, circular, net-positive economies worldwide.",true,Student On Ice Foundation,,,
|
||||||
|
,Davide Balbi,+393318344974,Envoyer le message,db@mondomigliore.eu,C.A.S.A. Marine Loop,"Santino Filippeddu, Davide Balbi, Gianluca Del Vecchio","Olbia, Sardaigne, Italie","Europe, Italia",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-09-19,Sustainable fishing and aquaculture & blue food,"C.A.S.A. Marine Loop is a modular retrofit solution for offshore fish cages, designed to operate around, inside and below cages.
|
||||||
|
It aspirates and treats water to intercept solid waste and organic loads that negatively affect fish health and surrounding marine areas.
|
||||||
|
The core technology is a stainless-steel biofiltration system with organic BSA media, already validated in land-based RAS, which does not generate microplastics, unlike many plastic-based alternatives.
|
||||||
|
The system is designed to be powered by offshore renewable energy and to enable circular economy pathways for recovered materials.
|
||||||
|
The objective is to transform offshore aquaculture into a cleaner, lower-impact and more resilient activity, contributing to ocean protection while improving farm performance.",true,"I heard about the MOPC through the SUBMARINER Network’s communications and website, where the call was featured.",Received,https://drive.google.com/drive/folders/1jSB8rvtoNf5w8cRMbd4vV_7u2N-RKr6Z?usp=drive_link,
|
||||||
|
,Emily Hannah Purnell,+4917644482080,Envoyer le message,emily.purnell@web.de,Seads,"Simona Töpfer, Matz Hüffner, Jonas Pfitzner, Leonie Schwarte, Malena Meyer","Münster, Rhénanie-du-Nord-Westphalie, Allemagne","Europe, Germany",University of Münster in Münster,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Restoration of marine habitats & ecosystems,"We restore seagrass meadows at scale.
|
||||||
|
We invented an autonomous, AI-powered seagrass seed harvester to overcome the main bottleneck in seagrass restoration: the lack of scalable and affordable seed supply. By enabling efficient seed harvesting, cultivation and replanting, we unlock large-scale seagrass restoration that was previously limited to small pilot projects. As a restoration service provider, we work with governments and companies to restore degraded marine habitats within regulated ecosystem compensation schemes. Our restored seagrass meadows store CO₂, protect coastlines from erosion, and bring biodiveristy back to our oceans.",false,We met Magnus Frohmann at the Enactus World Cup last year.,,,
|
||||||
|
,Maria Jose Castaño González,+573245654738,Envoyer le message,mjose.castano2020@gmail.com,SEMOCEA - Numerical Modeling Seedbed of the Ocean and the Atmosphere,María José Castaño González,"Turbo, Colombie",South America,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2022-10-03,Mitigation of climate change and sea-level rise,"This project is based on the Ocean and Atmospheric Numerical Modeling Research Group and focuses on the use of numerical modeling to understand coastal and oceanic hydrodynamics influenced by river discharges. The core idea is to analyze the spatio-temporal variability of ocean circulation in coastal and estuarine systems, using the Gulf of Urabá (Colombia) as a case study.
|
||||||
|
|
||||||
|
The project aims to model and analyze five years of oceanographic conditions to evaluate how river inflows affect currents, circulation patterns, and coastal dynamics. By integrating numerical models, observational data, and hydrodynamic analysis, the project seeks to generate scientific knowledge that supports coastal management, environmental protection, and climate resilience in vulnerable coastal regions.
|
||||||
|
|
||||||
|
Additionally, the project has a strong educational and capacity-building component, promoting the training of young researchers in numerical modeling of the ocean and atmosphere, and fostering the use of science-based tools for sustainable ocean protection and decision-making.",true,I heard about the MOPC through a community leaders’ group in my local area.,,,
|
||||||
|
,Athavan RASENTHIRAM,+33758298244,Envoyer le message,athavan.rasenthiram@builders-ingenieurs.fr,Investigation on modular artificial reefs to attenuate waves,Dominique Mouaze,"Caen, Normandie, France","Europe, France","University of Caen Normandy, Caen",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Restoration of marine habitats & ecosystems,"enhancement, yet the influence of reef geometry on hydrodynamic performance remains insufficiently quantified. This study presents an experimental comparison of the hydrodynamic
|
||||||
|
performance of two modular artificial reef geometries, focusing on their ability to modify wave transmission, reflection, and energy dissipation. The investigation aims to support reef-inspired design strategies that balance coastal engineering efficiency with ecological functionality.",true,LinkedIn,Received,https://drive.google.com/drive/folders/1u9uWfPyoLZgqJRUwqoxfNLjgNtqCtCTg?usp=drive_link,
|
||||||
|
,JHONATAN DANIEL PILLA ORTIZ,+593991978337,Envoyer le message,agroindustria_pilor@outlook.com,DEL CAMPO ECUADOR,JHONATAN DANIEL PILLA ORTIZ,"Galápagos, Équateur",South America,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-02-08,Reduction of pollution (plastics chemicals noise light...),"Mi empresa se dedica a la producción y comercialización de PULPAS DE FRUTAS congeladas, pero en la industria de alimentos todo es con plásticos de un solo uso. Nuestro objetivo es empezar a usar plásticos biodegradables en toda nuestras presentaciones, ser mas amigables con el medio ambiente y reducir la basura que siempre llegan a nuestras costas, mas a nuestra frágil biodiversidad en Galápagos.",true,"Encuentro Empresarial Galápagos, donde nuestras autoridades nos comparten temas y organizan eventos para promocionar los emprendimientos de las Islas Galápagos",,,
|
||||||
|
,Rocío Paola Urtubia Oyarzún,+56954072346,Envoyer le message,rocio.urtubia@gmail.com,VIOBACT,"Pamela Torres, Bárbara Morales","Coquimbo, Chili",South America,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2025-08-28,Sustainable fishing and aquaculture & blue food,"VioBact is a science-based marine biotechnology project developing natural probiotic solutions to improve the sustainability of marine fish larviculture.
|
||||||
|
The project aims to increase larval survival during first feeding, one of the most critical bottlenecks in marine aquaculture, by reducing harmful Vibrio bacteria in live feed systems (rotifers).
|
||||||
|
VioBact’s probiotic formulations are based on beneficial marine bacteria that naturally outcompete pathogens, lowering the need for antibiotics and chemical treatments.
|
||||||
|
By improving early survival and health of marine fish larvae, VioBact contributes to more efficient, resilient, and environmentally responsible aquaculture, supporting the production of sustainable blue food.",true,"I learned about the Monaco Ocean Protection Challenge through social media and international collaboration networks focused on ocean conservation, sustainability, and blue innovation.",,,
|
||||||
|
,Christian Yowel Masagati,+255621379594,Envoyer le message,chrstnyowel@gmail.com,Youth in Marine Science Tanzania,"(Christian Masagati), (Rahma Sadiki), (Revival Herman), (Prosper Mahenge), (Zahra Mazoya)","Dar es Salam, Tanzanie","Africa, Tanzania",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2025-03-23,Capacity building for coastal communities,"Tanzania’s future is intrinsically linked to the Western Indian Ocean, yet a profound ""ocean blindness"" persists within the educational system and local communities. While the government
|
||||||
|
pushes for a Blue Economy, there is a critical shortage of ocean-literate youth and educators to drive this vision. Current efforts are often limited by the small capacity of existing marine
|
||||||
|
organizations to reach the vast number of primary and secondary students in need of this education.
|
||||||
|
To address this, Youth in Marine Science Tanzania (YIMS Tanzania) presents the Kizazi Blue Initiative, a scalable, year-long program designed to bridge the gap between marine science
|
||||||
|
and community action. Unlike traditional outreach models, this project utilizes a
|
||||||
|
""Train-the-Trainer"" approach through the Blue Kizazi Fellowship.
|
||||||
|
The project operates in two main phases:
|
||||||
|
1. The Blue Kizazi Fellowship. We will recruit, train, and equip a cohort of
|
||||||
|
emerging young ocean leaders (university students and early-career youth). These fellows will co-design innovative, culturally relevant educational tools, ranging from
|
||||||
|
storytelling and arts to scientific models under the mentorship of the YIMS team.
|
||||||
|
2. Community Implementation. The fellows, working alongside YIMS, will
|
||||||
|
deploy these tools in primary and secondary schools. This phase includes
|
||||||
|
classroom-based ocean literacy education and immersive outdoor conservation activities, such as mangrove restoration and beach cleanups. By empowering a cohort of youth leaders to become educators, the Kizazi Blue Initiative solves the capacity challenge, ensuring wider reach and deeper impact. We aim to reach over 1,000 students directly, plant 2,000 mangrove seedlings, and establish a recurring cycle of leadership development that restarts annually to continuously feed the pipeline of Tanzanian ocean guardians.
|
||||||
|
|
||||||
|
Statement of Need:
|
||||||
|
Despite Tanzania's 1,424-kilometer coastline, two critical gaps hinder sustainable blue economy development:
|
||||||
|
1. The",true,From the MOPC linkedin page,,,
|
||||||
|
,Jan Maisenbacher,+41793951989,Envoyer le message,info@janmaisenbacher.com,Podcast Ocean Collaborations,"Jan Maisenbacher, Louise Cooke","Lucerne, Suisse","Europe, Switzerland",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2025-02-16,Other,"Ocean Collaborations is an English podcast (online since Feb 2025; ~2 episodes/month) sharing global ocean conservation insights through conversations with personalities leading complex ocean regeneration projects - spotlighting practical collaboration across sectors to help ocean changemakers (and newcomers) act beyond filter bubbles and misinformation. Available on Substack (https://janmaisenbacher.substack.com/), Apple Podcast (https://podcasts.apple.com/us/podcast/ocean-collaborations/id1797113400), and Spotify (https://open.spotify.com/show/4a3iqQ3Grj2rrcUNpaaEbi).",false,Linkedin In like,Received,https://drive.google.com/drive/folders/1WsnVle3wnXJsK-fpO0aaNOHvQMO7lBBJ?usp=drive_link,
|
||||||
|
,Edwin Breganza,+639061214632,Envoyer le message,eobreganza@up.edu.ph,Conservation of Pemphis acidula,"Edmar Angeles, Victorino Sandoval and Mary Grace Breganza","Los Baños, Calabarzon, Philippines",Asia,"University of the Philippines at Los Banos, Philippines",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Blue Carbon,"This conservation project addresses the urgent need to prevent extinction of Camptostemon philippinensis by generating fundamental scientific knowledge on its reproductive biology, developing germination and propagation protocols, conducting GIS-based distribution mapping, and engaging coastal communities in conservation activities. The project will establish technical foundations for ex situ conservation and restoration programs while building local capacity through training programs and information campaigns, ultimately aiming to secure legal protection for Gapas-gapas habitat as a Protected Area. Beyond species conservation, the initiative contributes to maintaining critical ecosystem services including coastal protection, carbon sequestration, and nursery habitat for commercially important species, while fulfilling the Philippines' commitments under international biodiversity agreements and providing a replicable model for community-based endangered species recovery throughout Southeast Asia.",false,from Funds for NGOs website,,,
|
||||||
|
,Nasibu Mtambo,+254742051141,Envoyer le message,mtamboduke@gmail.com,Blue EcoponicX,"1. Sharon Shali, 2. Mohammed Athman 3. Terry Okwanyo 4. Mohamed Salim","Mombasa, Kenya","Africa, Kenya",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2025-05-20,Reduction of pollution (plastics chemicals noise light...),"Blue EcoponicX is a climate-tech initiative that transforms marine plastic waste into smart hydroponic towers for urban food production. The project tackles two pressing challenges; ocean plastic pollution and urban food insecurity by converting waste into productive, sustainable farming systems. Using plastic recycling, 3D printing and sensor-based monitoring, the modular towers allow households, schools and small-scale urban farmers to grow fresh food in limited spaces while using far less water and energy than traditional agriculture.
|
||||||
|
|
||||||
|
The project aims to reduce marine plastic waste, improve access to affordable fresh produce in cities and lower carbon emissions from food transportation. Blue EcoponicX also creates green jobs in recycling, manufacturing and urban farming while contributing to more climate-resilient, self-sufficient urban communities.",true,Through LinkedIn,Ignore,,
|
||||||
|
,Maarten Berkhout,+31637655680,Envoyer le message,m.berkhout@hollandmarineprojects.com,IceSalvage,Ewoud Visser,"Emmeloord, Flevoland, Pays-Bas","Europe, Netherland",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2025-11-01,Reduction of pollution (plastics chemicals noise light...),Salvage of complex and potentially dangerous and harmful objects,true,via LinkedIn,,,
|
||||||
|
,Mathias Mondo,+237693514085,Envoyer le message,mathiasmondo35@gmail.com,Test of the website,"Team member 1, team pepbrrx","Yaoundé, Cameroun","Africa, Cameroun",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2021-07-07,,Ok,false,,Ignore,,
|
||||||
|
,Juan Esteban Perdomo Ortiz,+573024197126,Envoyer le message,estebanjperdomo@gmail.com,REDJODS,Maria Fernanda Montañez - Natalia Alexandra Barrera,"Bogota, Colombie",South America,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2020-07-20,Mitigation of climate change and sea-level rise,"REDJODS (Red de Jóvenes para el Desarrollo Social) is a youth-led network focused on strengthening civic engagement, leadership, and social innovation among young people in Latin America.
|
||||||
|
|
||||||
|
Objective: empower youth to design and implement community-driven solutions that promote social development, democratic participation, and sustainable impact.
|
||||||
|
|
||||||
|
What it does: connects young leaders, builds capacity through training and collaboration, and supports projects that address local challenges through evidence-based, participatory approaches.
|
||||||
|
|
||||||
|
More at: REDJODS — http://redjods.org/",true,"I learned about Monaco OPC through academic and civic engagement networks, as well as recommendations from peers involved in international policy and model conference programs.",,,
|
||||||
|
,Jimmy Armijos,+593999038180,Envoyer le message,munayimbabura@gmail.com,PRISMA CASTELLANA,CARLOS SILVERIO; DANIEL ARTEAGA; MARIA ALEJANDRA CUEVA,"Santa Cruz, CA, États-Unis",US,"INSTITUTO LA CASTELLANA, LOJA ECUADOR",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Capacity building for coastal communities,"1. Waste Management and Zero Waste: Microplastics in the Bay: How to prevent synthetic fibers and small fragments from ending up on Station Beach or Tortuga Bay; Local Circular Economy: Workshops for artisans and businesses on how to reuse materials carried by ocean currents; Cleaning Chemicals: Biodegradable alternatives for hotels and homes that do not harm the marine ecosystem when washed down the drain.
|
||||||
|
|
||||||
|
2. Community Education and Citizen Science
|
||||||
|
For residents and youth (""Galapagueños""):
|
||||||
|
|
||||||
|
Reef Monitoring: Basic snorkeling training to report coral bleaching or the presence of invasive species.
|
||||||
|
|
||||||
|
Coastal Gardening: Using native and endemic plants in Puerto Ayora yards to prevent erosion and sediment runoff into the sea.
|
||||||
|
|
||||||
|
The El Niño Phenomenon: How to prepare for and understand changes in sea temperature and their impact on local biodiversity.
|
||||||
|
|
||||||
|
Responsible Consumption and Resource Management
|
||||||
|
Puerto Ayora is the largest consumer center in the archipelago.
|
||||||
|
|
||||||
|
Carbon Footprint of Imported Products: Understanding the environmental cost of bringing food and supplies from the mainland versus consuming locally.
|
||||||
|
|
||||||
|
Zero Single-Use Plastics: Practical workshops on the Galapagos plastics law and local alternatives.
|
||||||
|
|
||||||
|
Freshwater Conservation: Home desalination techniques or rainwater harvesting, given that freshwater is a critical and expensive resource on the island.
|
||||||
|
|
||||||
|
3. Biodiversity and Citizen Science
|
||||||
|
Turning every citizen and tourist into a scientific observer.
|
||||||
|
|
||||||
|
Identification of Local Fauna: Differences between sea lions and fur seals, and minimum distance protocols (2 meters) to avoid habituation.
|
||||||
|
|
||||||
|
The Role of Mangroves: Why the mangroves of Laguna de las Ninfas and the area surrounding the Charles Darwin Research Station should not be cut down or polluted (protection against storms and fish nurseries).
|
||||||
|
|
||||||
|
Seabird Watching: The importance of boobies and frigatebirds as indicators of ocean health.
|
||||||
|
|
||||||
|
4. Climate Change and Local Resilience: The El Niño Phenomenon: Education on why",true,REDES SOCIALES,,,
|
||||||
|
,Achraf Bleili,+21651834344,Envoyer le message,achrefbleili@gmail.com,EcoRecy,Achraf Bleili,"Bizerte, Tunisie","Africa, Tunisia",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2026-01-01,Reduction of pollution (plastics chemicals noise light...),"EcoRecy is a greentech startup tackling plastic waste through automated recycling machines combined with a digital rewards platform. The idea is to make recycling simple, accessible, and incentivized for citizens.",true,Google,Received,https://drive.google.com/drive/folders/1Qm6ZkKcHHKnL0lDM_iCyLa1Bsii1Lks5?usp=drive_link,
|
||||||
|
,Douglas Bertin,+33601185122,Envoyer le message,dbertin@calx-sea.com,CalX,Quentin GERME and Tematuanui a Tehei HANTZ,"Gujan-Mestras, Nouvelle-Aquitaine, France","Europe, France",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-11-19,Restoration of marine habitats & ecosystems,"CalX is developing Oysteria X, a 100% natural, low-carbon marine construction material designed to replace conventional marine concrete.
|
||||||
|
Developed in partnership with IFREMER and CNRS, Oysteria X is a structurally engineered material that complies with marine infrastructure standards. It is based on recycled oyster shells used as a direct substitute for sand and traditional mineral aggregates, combined with natural mineral binders without clinker.
|
||||||
|
|
||||||
|
By reintroducing marine biominerals into underwater structures, Oysteria X naturally attracts and supports marine species, enabling faster and richer biological colonisation than conventional concrete. This bioaffinity makes the material particularly suitable for reef restoration, coastal protection, aquaculture and offshore infrastructure.
|
||||||
|
|
||||||
|
The material has undergone in situ marine testing to validate its durability, ecological and mechanical performance, and scalability in real marine conditions in the Brest harbour with IFREMER.
|
||||||
|
|
||||||
|
We are giving back to the sea what it has given us.",true,On LinkedIn and through our personal research,Received,https://drive.google.com/drive/folders/1T5ONe70hze061-C7AOnaPLBlN6AYpPfH?usp=drive_link,
|
||||||
|
,Mayra Alejandra Cuero Gonzalez,+573186629665,Envoyer le message,macuerogonzalez@gmail.com,Roots of the Sea: Ancestral Tourism for Ocean Protection in the Colombian Pacific,Waldis Natalia Conrado Gamboa,"Buenaventura, Colombie",South America,"Community-based Project – Fundación Cultura Ancestral de Juanchaco, Colombia",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Capacity building for coastal communities,"Roots of the Sea is a community-led ancestral tourism project developed with the Fundación Cultura Ancestral de Juanchaco on Colombia’s Pacific coast.
|
||||||
|
The project empowers coastal communities to protect marine and coastal ecosystems by transforming ancestral knowledge, cultural practices, and responsible tourism into a sustainable source of income.
|
||||||
|
Its main objectives are to strengthen local capacities, raise ocean awareness among visitors, reduce pollution on beaches and coastal areas, and promote long-term ocean conservation through community leadership and cultural transmission.",true,Through LinkedIn and international professional networks focused on ocean protection and sustainable innovation.,,,
|
||||||
|
,Anthony Duxell Malle,+237675306895,Envoyer le message,anthonymalle3@gmail.com,Coastal Communities for Grassroots Mangrove Restoration and Livelihoods,"Tabang David , Chiamoh Blandine, Ebune Jacques",Cameroun,"Africa, Cameroun","University of Buea , Cameroon.",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Restoration of marine habitats & ecosystems,"This project aims to scale restoration by rehabilitating 3 hectares of mangroves through planting 5,000 seedlings with active participation from three local communities ( Ekange 3, Motombolombo , Ijaw-Mabeta, supported by site surveys, GIS web maps and geographic data monitoring. It also integrates sustainable agriculture by empowering 20 local farmers to propagate 2,000 plantain and 1,000 cocoyam seedlings using natural compost, enhancing food security and reducing mangrove deforestation for firewood.
|
||||||
|
The initiative targets multiple social and environmental benefits including increased farmer income, improved nutrition, climate-resilient agricultural systems, and strengthened local institutions within Tiko and Limbe 3 municipalities. A long-term vision includes creating a mangrove ecopark (Marine Protected Area) to protect biodiversity and support community livelihoods.",true,,,,
|
||||||
|
,Tarwita Phetchaiyo,+66947484964,Envoyer le message,tarwita550@gmail.com,Mute Matter,Tarwita Phetchaiyo,"Phetchabun, 67000 Thailande",Asia,"Phetchabun Rajabhat University, Thailand",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Reduction of pollution (plastics chemicals noise light...),"Mute Matter: Turning Ocean's #1 Pollution into Profitable Acoustic Solution - Cigarette butts are the ocean's #1 microplastic polluter. Mute Matter stops this toxic waste at the source by upcycling it into premium acoustic panels. We transform hazardous litter into high-value products for the green construction market, cleaning the ocean while building a profitable circular economy",true,Internet Search,,,
|
||||||
|
,Hector Hiram Manzano Lopez,+525610111993,Envoyer le message,hecmanzano21@gmail.com,SARGASAFE ENERGY,"Noeli Bahena Sosa, Karen Bahena Sosa, Alejandro Sosa Pech, Andre Baena Adame y Wilbert Bahena Adame.","Felipe Carrillo Puerto, QRoo, Mexique",South America,"Universidad Iberoamericana de Puebla, Puebla.",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Technology & innovations,"SARGASAFE ENERGY is a modular clean-energy solution that transforms harmful sargassum blooms into renewable biogas and electricity through controlled anaerobic digestion. By removing sargassum before coastal decomposition, the project prevents marine ecosystem degradation, reduces hypoxia and nutrient pollution, and protects seagrass beds and coral reefs.
|
||||||
|
|
||||||
|
The generated biogas is used to supply local, low-carbon energy to coastal communities, reducing dependence on diesel and lowering greenhouse gas emissions. The system is designed with a “safe-by-design” approach to manage salinity and trace contaminants, ensuring environmental safety.
|
||||||
|
|
||||||
|
Piloted in the Caribbean region of Quintana Roo, Mexico, SARGASAFE ENERGY combines ocean protection, climate mitigation, and community-based innovation, with strong potential for replication across sargassum-affected coastlines worldwide.",true,Instagram,,,
|
||||||
|
,Amritha Ramadevu,+491759647144,Envoyer le message,amritha.ramadevu@edu.escp.eu,Bluezone,"Amritha Ramadevu, Ema Peres Matias, Augustin Kriegel, Madeleine Luneau, Charles Hannart, Renata Maria Gaitan Alfaro","Paris, Île-de-France, France","Europe, France","ESCP Business School, Berlin",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Other,"""Bluezone“ operates as an aggregator platform, onboarding small and medium producers who manufacture finished products using ocean plastic, algae, kelp, and other ocean-waste based materials. The platform standardizes supply, verifies impact, and connects these producers directly with companies seeking low-carbon substitutes for conventional products.
|
||||||
|
|
||||||
|
By enabling traceability, impact measurement, and ESG-aligned reporting, Bluezone allows corporate buyers to demonstrably reduce Scope 3 emissions while embedding sustainability into their procurement decisions.
|
||||||
|
|
||||||
|
Bluezone’s long-term vision is to become the go-to global platform for ocean-positive procurement, aligning economic incentives with marine regeneration. By redirecting capital toward low-carbon ocean-based solutions, Bluezone helps restore ecosystems, reduce emissions, and build resilient coastal economies, turning the ocean from a victim of climate change into a driver of climate solutions.",true,Word of mouth,,,
|
||||||
|
,Ayman El Arroubi,+212619179409,Envoyer le message,aymanelarroubi@gmail.com,Ecoplast,"Ayman El Arroubi ( co-founder , CEO)","Rabat, Rabat - Salé - Kénitra, Maroc","Africa, Morocco",École Nationale supérieure d’Arts et Métiers de Rabat,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Reduction of pollution (plastics chemicals noise light...),"The Core Idea A biodegradable, algae-based alternative to single-use marine plastics (like fishing gear, poly-bags, and coastal packaging) that dissolves harmlessly if lost at sea, or serves as ""fish food"" rather than microplastic pollution.",true,Alumni recommandation,Received,https://drive.google.com/drive/folders/1ZUabWuOlOO_xdQIwpE0GQ3AdLsg0ngek?usp=drive_link,
|
||||||
|
,Tabakova,+33749812907,Envoyer le message,maria.tabakova.06@gmail.com,Tideroom.ai,"Olivier Guillo, Jeanne Guillo, Olivier Bettati",Ville de Valbonne Sophia Antipolis,"Europe, France",International University of Monaco,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Capacity building for coastal communities,"TideRoom est une plateforme numérique (""GPS du financement climat"") qui met en relation les villes côtières africaines avec les bailleurs de fonds climat internationaux. Elle guide les collectivités de l'identification de leurs besoins jusqu'à l'obtention des financements et la mise en œuvre de solutions de résilience côtière. Le pilote cible deux villes : Dakhla (Maroc) et Grand-Bassam (Côte d'Ivoire), toutes deux vulnérables au changement climatique (érosion, montée des eaux) et dont les économies locales (pêche, tourisme) dépendent directement de la santé des écosystèmes côtiers.
|
||||||
|
Objectifs à 36 mois :
|
||||||
|
|
||||||
|
2 villes pilotes + 8-10 communes onboardées
|
||||||
|
10 projets de résilience documentés
|
||||||
|
20 M€ de financements climat facilités
|
||||||
|
15+ bailleurs intégrés à la plateforme
|
||||||
|
Guide de bonnes pratiques publié
|
||||||
|
|
||||||
|
Ambition à 5 ans : 50 villes côtières africaines connectées, 200 M€ de financements facilités.",true,Personal research,Received,https://drive.google.com/drive/folders/10oZSF5O1euwAHaUssiOM-AlugSSeSWTE?usp=drive_link,
|
||||||
|
,Laurent BUOB,+33675090543,Envoyer le message,a.calvet@ocean-owl.com,Whisper eF,"Vincent Lebeault, Samir Ouhnia, Alix Calvet, Nicolas Lebeault","Sète, Occitanie, France","Europe, France",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-09-30,Sustainable shipping & yachting,"Whisper EF is developing a new generation of high-performance electric hydrofoil boats dedicated to professional maritime uses. Our flagship model, Whisper 360, is the first zero-emission electric foiler to offer performance comparable to thermal boats, with a top speed of 45 knots and an autonomy of up to 100 nautical miles.
|
||||||
|
|
||||||
|
The project’s objective is to enable the decarbonisation of fast maritime transport and service vessels (passenger shuttles, pilot boats, surveillance and intervention units) without compromising speed, range, safety or operational efficiency. This is achieved through a patented intelligent foil system combining assisted take-off foils and actively controlled foils, coupled with an optimized electric propulsion and flight control software.
|
||||||
|
|
||||||
|
Beyond the technological development, Whisper EF aims to contribute to the emergence of sustainable, high-performance maritime mobility solutions in Europe and internationally.",false,"We learned about the MOPC through our previous participation, having applied to the programme last year.",Received,https://drive.google.com/drive/folders/1L1Gaac4QLxxXYfcUD3wsqM_mTo5vrpss?usp=drive_link,
|
||||||
|
,Amy Dzikowski,+33652328060,Envoyer le message,amy.dzikowski@monaco.edu,PEARL (Protective Ecosystem AUV for Reef Longevity),"Amy Dzikowski, Nathalie Falck, Anne Marie Medema, Rebecca Sidalova, Melissa Chera","Pereybere, Grand Baie, Maurice","Africa, Mauritius","International University of Monaco, Monaco",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Restoration of marine habitats & ecosystems,"Our project is the development of the Protective Ecosystem AUV for Reef Longevity (PEARL), an autonomous underwater vehicle (AUV) designed to locate and capture invasive lionfish. PEARL uses a precision system to identify the invasive lionfish, enabling high-yield targeted removal without damaging reefs or harming native species. Unlike human spearfishers, PEARL is not constrained by dive time or depth limitations and can operate continuously across the full depth range of known lionfish habitats. AI-driven detection systems enhance identification accuracy and ensure an environmentally responsible operation.
|
||||||
|
|
||||||
|
The primary objective of PEARL is to halt the spread of invasive lionfish in the Mediterranean Sea and beyond, protecting reef ecosystems and ocean health. To accomplish this, our key objectives are to reduce invasive lionfish populations, prevent further geographic expansion, and enable sustainable revenue through the use of lionfish byproducts. Incorporating economic viability alongside ecological impact is essential to ensuring long-term adoption and effective large-scale intervention.",true,Prof. Elena Tavella at the International University of Monaco,,,
|
||||||
|
,Kelvish Duval,+33768126798,Envoyer le message,kelvish.duval@efrei.net,Efrei grp 4,"Yassine Tenzekhti, Theotim Djelassi, El-Assad Said, Lucas Rinaudo","Villejuif, Île-de-France, France","Europe, France","Efrei, Villejuif",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Restoration of marine habitats & ecosystems,Creation of artificial habitats with adapted materials,false,No,,,
|
||||||
|
,Thamires Pontes,+5511992001931,Envoyer le message,thamires@phycolabs.com,Phycolabs,"Thamires Pontes, Wolgrand Neto, Diego Nunes, Gustavo Gonçalves","Sao Paulo, SP, Brésil",South America,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2022-06-14,Reduction of pollution (plastics chemicals noise light...),"Phycolabs is a Brazilian biomaterials startup decarbonizing fashion with textile fibers made entirely from cultivated seaweed. Our SeaweedFibers replace traditional and man-made fibers that drive CO₂ emissions and marine microplastic pollution at the source, using a fast-growing ocean feedstock that requires no land, pesticides, or freshwater.
|
||||||
|
|
||||||
|
Our patent-pending process produces continuous seaweed filaments compatible with existing spinning and weaving machinery, enabling global brands to adopt ocean-positive materials at scale while building regenerative seaweed farming value chains, contributing to marine ecosystem restoration, and strengthening coastal livelihoods.",true,We were semifinalist last year and I would like to try again in 2026 :)),,,
|
||||||
|
,Davide Balbi,+393318344974,Envoyer le message,db@mondomigliore.eu,C.A.S.A. Marine Loop,"Davide Balbi, Santino Filippeddu, Gianluca Del Vecchio","Arzachena, Sardaigne, Italie","Europe, Italia",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-09-19,Sustainable fishing and aquaculture & blue food,"C.A.S.A. Marine Loop is a modular retrofit solution for offshore fish cages, designed to operate around, inside and below cages. It aspirates and treats water to intercept solid waste and organic loads that affect fish welfare and surrounding marine areas. The core technology is a stainless-steel biofiltration system with organic BSA media, already validated in land-based RAS, designed not to generate microplastics compared to plastic-based alternatives. The system is conceived to be powered by offshore renewable energy and to enable circular economy pathways for recovered materials. Our objective is to make offshore aquaculture cleaner, more resilient and ocean-positive while improving farm performance.",true,Newsletter,Doublon,,
|
||||||
|
,Juan Carlos Saldaña Guerrero,+51932751524,Envoyer le message,juan.carlos.saldana.guerrero@gmail.com,Carlos Biologist SG Group,"Carmen Nallelí Saldaña Guerrero, Elizabeth Bridgit Saldaña Guerrero","Ica, Peru",South America,ESNECA Business School,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Other,Laboratorio de Biología Marina,true,LinkedIn,,,
|
||||||
|
,Lautaro Girones,+541151355710,Envoyer le message,lautaro.girones@uns.edu.ar,HydroTrace,Lautaro Girones; Andres Hugo Arias; Rosario Corradini; Rocio Luciana Bray; Alejandro Jose Vitale,"Bahía Blanca, Buenos Aires, Argentine",South America,"Universidad Nacional del Sur, Bahía Blanca, Argentina",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Reduction of pollution (plastics chemicals noise light...),"HydroTrace is an environmental monitoring technology platform designed to reveal hydrophobic chemical contaminants that are often missed by conventional water monitoring methods. Many industrial pollutants, including hydrocarbons, PAHs and selected unintentional persistent organic pollutants (UPOPs), are not fully captured by dissolved water measurements, leading to underestimation of real environmental exposure and risks to marine ecosystems and ocean health.
|
||||||
|
|
||||||
|
HydroTrace uses standardized passive polymer-based sensors combined with analytical workflows and exposure interpretation tools to measure time-integrated chemical exposure across aquatic environments, from industrial discharges and rivers to estuaries, ports and offshore marine waters.
|
||||||
|
|
||||||
|
By detecting hidden chemical exposure pathways along the land-to-ocean continuum, HydroTrace supports early identification of chronic pollution sources that can impact marine ecosystems, coastal biodiversity and ocean food webs. The system is designed to generate actionable environmental intelligence that supports pollution prevention strategies and evidence-based ocean protection decision making.
|
||||||
|
|
||||||
|
The technology is currently being refined through pilot monitoring applications in industrial coastal environments, demonstrating technical feasibility under real-world conditions. Initial applications focus on hydrophobic hydrocarbon fractions (C10–C40), PAHs and selected hydrophobic industrial byproducts. Future development includes evaluation of additional hydrophobic industrial and combustion-related contaminants depending on environmental behavior and monitoring needs.
|
||||||
|
|
||||||
|
HydroTrace is designed to complement existing water monitoring technologies by targeting the hydrophobic contaminant fraction that is often under-characterized in routine monitoring programs. The platform combines standardized deployment hardware, recurring replacement kits, laboratory analysis and exposure intelligence reporting, enabling scalable, standardize",true,Through a colleague working in the ocean and environmental sector,,,
|
||||||
|
,Sahil,+917488447394,Envoyer le message,sahilkumar8176xd@gmail.com,BlueVault,"Sahil Kumar , Ankit Kumar","Jehanabad, Bihar, Inde",Asia,"Magadh University,Bihar",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Blue Carbon,"BlueVault is an autonomous ""Proof-of-Ecosystem"" protocol. We deploy swarms of low-cost micro-AUVs to verify Blue Carbon assets (seagrass/reefs) with millimeter precision.
|
||||||
|
|
||||||
|
Objective: To unlock the $50B ocean finance market by replacing expensive human divers with scalable, audit-grade data.
|
||||||
|
|
||||||
|
Tech: Our proprietary C++ Edge-AI algorithms calculate biomass volume in real-time, providing the ""trust layer"" banks need to invest in ocean restoration without fear of greenwashing.",true,,Received,https://drive.google.com/drive/folders/11x14DmyXxxi8EaLG13q31QvHXYRUPAvN?usp=drive_link,
|
||||||
|
,S KAVIYA,+919042979984,Envoyer le message,sec23ec232@sairamtap.edu.in,ROBOVAC,ABIBA FATHIMA A & SIVASHANKER G,Inde,Asia,"SRI SAIRAM ENGINEERING COLLEGE , WEST TAMBARAM, CHENNAI,TAMIL NADU, INDIA",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Reduction of pollution (plastics chemicals noise light...),"This venture delivers a scalable, semi-autonomous solution for water surface cleanup by combining AI-based visual detection with geospatial analytics. The system identifies and geo-tags floating plastic waste, converting raw detections into actionable pollution hotspot intelligence for targeted, cost-efficient cleanup operations. Designed with a modular hardware architecture and cloud-enabled analytics, the platform enables data-driven, repeatable deployments for municipalities, environmental agencies, and industrial water-body operators, reducing manual effort while improving operational efficiency and environmental impact.",true,I learned about the MOPC through institutional communications and peer networks within the student innovation and project development community. I also came across it via online announcements highlighting opportunities for showcasing interdisciplinary projects.,,,
|
||||||
|
,S KAVIYA,+919042979984,Envoyer le message,sec23ec232@sairamtap.edu.in,NAUTILUS NEXUS,ABIBA FATHIMA A / SIVAHANKER G,Inde,Asia,"SRI SAIRAM ENGINEERING COLLEGE, WEST TAMBARAM , CHENNAI , TAMIL NADU , INDIA",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Reduction of pollution (plastics chemicals noise light...),"This venture delivers a scalable, semi-autonomous solution for water surface cleanup by combining AI-based visual detection with geospatial analytics. The system identifies and geo-tags floating plastic waste, converting raw detections into actionable pollution hotspot intelligence for targeted, cost-efficient cleanup operations. Designed with a modular hardware architecture and cloud-enabled analytics, the platform enables data-driven, repeatable deployments for municipalities, environmental agencies, and industrial waterbody operators, reducing manual effort while improving operational efficiency and environmental impact.",true,I learned about the MOPC through institutional communications and peer networks within the student innovation and project development community. I also came across it via online announcements highlighting opportunities for showcasing interdisciplinary projects.,Doublon,,
|
||||||
|
,Greta Puschmann,+33674308280,Envoyer le message,greta.puschmann@monaco.edu,BlueSupply ESG,Tom Döscher,"Monaco, Monaco","Europe, Monaco","International University of Monaco, Monaco",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Other,"BlueTrace ESG is a digital ESG intelligence platform designed to bring transparency to ocean-based supply chains.
|
||||||
|
The project helps companies and investors identify, measure and manage their marine environmental and social impact across sectors such as fisheries, aquaculture, fashion and maritime logistics.
|
||||||
|
By combining supply-chain data, ocean-specific ESG indicators and regulatory requirements (e.g. CSRD), BlueTrace ESG enables reliable reporting, risk assessment and decision-making.
|
||||||
|
Its objective is to make ocean impact measurable and actionable - and to redirect capital and business practices toward more sustainable, ocean-positive value chains.",true,From my university,,,
|
||||||
|
,TEMANO,+33746225522,Envoyer le message,clara.honore@temano.fr,TEMANO,Quentin DEMOULIN,"Ploemeur, Bretagne, France","Europe, France",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2022-01-17,Technology & innovations,"Development of anchors with minimal environmental impact, intended for recreational boating and renewable energy on floating bases",false,Pole Mer Bretagne Atlantique,,,
|
||||||
|
,Mathias Mondo,+237693514085,Envoyer le message,mathiasmondo@me.com,Blue Sentinel,Nguessong Donfack Marie-Ange; Tagne Wambo Elohim Junior; Bidzana Deugoue Erwin ; GUENTANG BADEFONA ODILE; Nkot-a-Nzok Etienne; Mondo Mathias; Adipauldi Sigot Sabine; Ndzana Nga Romaric; Lowe Nyat Fred,Cameroun,"Africa, Cameroun",INSTITUT UNIVERSITAIRE D'INNOVATION ET DE MANAGEMENT MARIE-ALBERT (ISIMMA),the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Technology & innovations,"Ocean pollution starts on land.
|
||||||
|
In Yoyo (Gulf of Guinea), accelerated mangrove degradation directly impacts coastal water quality, marine nurseries and coastal resilience. Despite awareness and initiatives, decisions remain fragmented, reactive and poorly governed — leaving the Ocean unprotected at its source.
|
||||||
|
|
||||||
|
BLUE SENTINEL protects the Ocean by protecting mangroves as frontline natural infrastructure.
|
||||||
|
The system combines satellite data (Sentinel/Landsat), low-cost IoT sensors, community observations and artificial intelligence into one operational platform. Environmental signals are transformed into clear decisions: protect, restore, invest. Mangroves become living dashboards for coastal governance.
|
||||||
|
|
||||||
|
Measurable Ocean impact within 12 months (pilot):
|
||||||
|
• −15% coastal water turbidity (baseline M1–M2)
|
||||||
|
• Zero net loss of mangrove cover, restoration initiated
|
||||||
|
• Protection of 1–2 critical marine nursery zones
|
||||||
|
• Average response time to alerts < 14 days. Impact is tracked through the Coastal Health Index (ISC) and aligned with SDG 14 (supported by SDGs 13, 15, 5 and 8).
|
||||||
|
|
||||||
|
How it works (in brief):
|
||||||
|
Satellites ensure continuous monitoring; IoT sensors measure turbidity, salinity and temperature; communities provide ground observations. AI generates alerts, scores and trends. A purpose-built Human–Machine Interface offers four user modes (Decision-makers, Field teams, Communities, Funders) with maps, indicators and actions.
|
||||||
|
|
||||||
|
Business model:
|
||||||
|
Designed to live beyond grants through B2G/B2NGO governance services, impact reporting (SDG/ESG/RBM), blue economy risk mapping, and responsible value chains (certification, training, women and youth empowerment).
|
||||||
|
|
||||||
|
Pilot, scalability & Monaco:
|
||||||
|
A 12-month pilot in Yoyo with a total budget of €120,000, directly linked to measurable Ocean outcomes. Designed for replication: Yoyo → Gulf of Guinea → global coastal systems.
|
||||||
|
Yoyo is the pilot. Monaco is the accelerator.",false,Via Linkedin and Mrs Manon Aminatou,Received,https://drive.google.com/drive/folders/1MsQCrGF7S9B03cEvmirWgf0K36PmjeEN?usp=drive_link,
|
||||||
|
,Alex Aldair Huanca Palomino,+51923781267,Envoyer le message,cloud953@outlook.es,Laboratorio Azul,"María Gabriela Arias, Alexandra Orue, Nicole Baca","Lima, Peru",South America,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2026-01-01,Reduction of pollution (plastics chemicals noise light...),Development of a high-performance photoprotective agent based on MAAs (Mycosporine-like amino acids) extracted from zooxanthellae (Zoanthus spp.) cultivated in photobioreactors. We replace toxic chemical filters with a 100% biodegradable and photostable alternative.,true,,,,
|
||||||
|
,Maya Bauer,+33768779375,Envoyer le message,mayabauer011@gmail.com,Veratech,Maya Bauer,"Cleveland, OH, États-Unis",US,Universite Cote D'Azur,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Technology & innovations,,true,"I heard about MOPC through my university, and I participated in 2024 with my project NEMA. I want to be transparent, just in case I can not participate again. However my new project, Veratech, is a completely different concept/project and is at the prototype stage. If anyone would like to discuss this with me further I would be happy to! My last experience at MOPC was such a pleasant learning experience, and I would greatly like to participate again if it is possible.",,,
|
||||||
|
,Charles Maher,+16174609706,Envoyer le message,charles.maher@blueshadow.dk,BlueShadow ApS,Emil Luth,"Copenhague, Région de la Capitale, Danemark","Denmark, Europe",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2025-02-24,Sustainable fishing and aquaculture & blue food,"BlueShadow protects society and the environment by delivering Maritime Security as a Service - safeguarding critical infrastructure and natural resources across the maritime domain.
|
||||||
|
|
||||||
|
Through our BlueFisheries Mission Solution, we serve as a force-multiplier for responsible authorities in the fight against illegal and destructive fishing. We fuse multi-source data into real-time, predictive intelligence that cuts through deception and exposes suspicious activity, triggering the immediate deployment of autonomous systems to investigate, document, and secure evidence - enabling decisive enforcement action to ensure violators are held accountable.",true,Referral from Victor Cobos of Avantgard Capital,,,
|
||||||
|
,Sudarsha Chanaka De Silva,+94776761788,Envoyer le message,sudarsha30384@gmail.com,Install High-Tech Water Purification Units in Fisheries Sector To Tackle Plastic Pollution in sea off Sri Lanka,"Sudarsha De Silva , Jagath Gunasekara, Madhura Delpachithra","Kudawella South, Southern, Sri Lanka","Africa, Sri Lanka",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2021-06-16,Reduction of pollution (plastics chemicals noise light...),"• Cleaner oceans and beaches: Less plastic waste entering our marine ecosystems.
|
||||||
|
• Protected marine biodiversity: Safeguarding our coral reefs and diverse marine life.
|
||||||
|
• Improved health for fishing crews of multi-day fishing boats: Access to clean, purified water on long voyages improves the hygiene and sanitary standards of fishermen
|
||||||
|
• Reduced operational costs of the fisheries sector: Fishermen save on bottled water purchases.
|
||||||
|
• Enhanced industry reputation: Sri Lanka leading the way in environmental stewardship
|
||||||
|
The project responds to the urgency to reduce plastic pollution caused by multiday fishing vessels in Sri Lanka, which is a major contribution to marine debris in the country's ocean waters. these fishing vessles spends 4-5 weeks in the sea and heavily depend on single-use plastic bottles for drinking water. with a limited waste storage and disposal infrastructure on board, much of this plastic is directly to the ocean, creating hotspots in the sea for marine litter which will result on microsplatic contamination on marine life and human body. This catostophe affects the tourism and fishries industry badly both sectors are vital for the economy of Sri Lanka. During their journey fisherman face challenges accessing safe drinking water while at sea affecting their health, hygiene and overall well-being. This highlights the importance clean water access and for reducing health impacts caused by plastic dependency. Therefore the project demonstrate the need for environment protection, ensure occupational health.",true,Through social media,,,
|
||||||
|
,Trisna Oktavia,+6289530030014,Envoyer le message,trisna.jobs@gmail.com,Seawaste Tech,"Asadurrofiq, Jean Baptiste Sugihardjanto",,,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2025-01-01,Reduction of pollution (plastics chemicals noise light...),"SeaWaste Tech is an integrated fishery waste and marine waste processing through a zero-waste system approach. In 2025, We successfully processed 50 tons of fishery waste into animal feed and organic fertilizer. We collaborate with seafood processing plants to manage and transform their production waste. However, beyond organic fishery waste, plastic packaging waste has also become a growing environmental challenge for these facilities.
|
||||||
|
Currently, our fishery waste processing operations still rely on fossil fuels for energy. Unfortunately, fossil fuels are increasingly expensive and often subject to supply shortages, which directly impacts production stability and costs. Recognizing this challenge, we see a strong opportunity to replace fossil fuel consumption with fuel derived from plastic waste generated by seafood processing factories. Through our pilot project calculations, this transition has the potential to reduce fossil fuel usage by up to 80% and lower production costs (HPP) by approximately 20%.
|
||||||
|
In addition to utilizing factory plastic waste, we also plan to collect plastic waste from coastal communities. Many of these communities lack proper waste management infrastructure, leading to common practices such as dumping plastic into the ocean or burning it both of which create further environmental pollution and threaten marine ecosystems. Our core focus is to convert plastic waste into diesel fuel as an alternative energy source and make a collection point. This solution will reduce dependence on fossil fuels, Lower production costs, Create a community-based plastic savings system that encourages behavioral change, Deliver significant environmental impact.
|
||||||
|
By transforming plastic waste into energy, we are not only solving industrial and community waste challenges but also contributing to global sustainability goals, specifically SDG 12 (Responsible Consumption and Production), SDG 13 (Climate Action), and SDG 14 (Life Below Water).",true,"I first learned about the Monaco Ocean Protection Challenge through my participation in the 2025 edition, where SeaWaste Tech was shortlisted as a finalist in the Business Concepts category. That experience deepened my commitment to refining our ocean impact model and motivated me to reapply in 2026 with a more mature, measurable solution grounded in our 2025 operational results.",,,
|
||||||
|
,Tabe Brandon Njume,+237675068360,Envoyer le message,tabebrandon50@gmail.com,TBINDS,"Anthony Duxell Malle, Forbah Sandra, Atianjoh Laris, Warri Bilson","Tiko, Cameroun","Africa, Cameroun","University of Buea, Cameroon + University of Bamenda, Cameroon",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Reduction of pollution (plastics chemicals noise light...),"My solution seeks to tackle microplastics pollution in our oceans by addressing point sources such as household and hospital diaper and surgical mask waste. To tackle this, we have engineered a circular, self-powering system that transforms waste from disposable diapers and surgical masks into TBinds, a soft, durable shoe sole.
|
||||||
|
Our process begins with a closed-loop collection system using leak-proof bins in hospitals and households to keep waste contained. To handle the biological load, we use a specialized fractionation method: waste is treated with a 2% Calcium Chloride solution to collapse the Superabsorbent Polymers (SAP), forcing them to release trapped liquids and excreta.
|
||||||
|
This organic slurry is diverted to an anaerobic digester to produce biogas, which powers our machinery. The remaining solids are sent to a wet shredder and centrifugal separator. A float-sink tank then isolates the high-grade Polypropylene (PP) and Polyethylene (PE) from the cellulose fibers. While our priority is stopping waste at the source, TBinds can process diaper waste from drains by adding a sedimentation step to wash off mud and grit.",true,,Received,https://drive.google.com/drive/folders/1pYFR66gjdcHQjRlOc24gUpstIhhmFyGO?usp=drive_link,
|
||||||
|
,Oscar Montoya,+573147802359,Envoyer le message,archivoadm@gmail.com,ISO Brick,2,"Medellin, Colombie",South America,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2022-01-01,Reduction of pollution (plastics chemicals noise light...),"The ISOBrick project aims to recover rigid polyurethane discarded during the manufacture of thermo-acoustic roof tiles and reuse that material as a filler within the cavities of clay bricks, transforming them into bricks with greater thermal and acoustic insulation capacity, reducing industrial waste and promoting more sustainable and energy-efficient construction.",true,By a research group,,,
|
||||||
|
,Anastasija Stefanovic,+33749014764,Envoyer le message,anastasija.stefanovic@univ-paris1.fr,BluePrint Reefs,"Anastasija Stefanovic, Florian Guichard","Paris, Île-de-France, France","Europe, France","Sorbonne Pantheon Paris 1, Paris",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Restoration of marine habitats & ecosystems,"We offer a high-tech, scalable solution: 3D-bioprinted modular reefs.
|
||||||
|
|
||||||
|
Innovation: Using a proprietary ""Living Ink"" (recycled calcium carbonate + bio-triggers), we print structures that mimic natural reef complexity.
|
||||||
|
|
||||||
|
Result: These reefs don't just sit on the seabed; they actively ""invite"" coral larvae to settle, accelerating biodiversity recovery by 300% compared to traditional methods.",true,I am a resident of Fondation de Monaco at Cite Internationale Universitaire de Paris where MOPC was mentioned.,,,
|
||||||
|
,Anvi Gowda,+4917623367782,Envoyer le message,anvi.gowda@myhsba.de,BlueReturn,"Jana Cisewski, Anvi Gowda, Malena Merkel & Kamar Haidar","Hambourg, Allemagne","Europe, Germany","Hamburg School of Business Administration, Hamburg",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Reduction of pollution (plastics chemicals noise light...),"With our project ""BlueReturn"", we aim to motivate people to collect trash in coastal areas and dispose of it. It is a system that consists of AI supported clean up stations, which enable correct trash seperation and are connected to an app. The app tracks the amount of trash collected by users and converts it into points, which can either be donated to ocean protection initiatives or redeemed for sustanability related rewards. Rewards include e.g. vouchers for sustainable brands or a free public transport ticket. With BlueReturn our goal is to incentivize people to turn small actions into a visible impact on ocean protection.",true,Our Professor introduced the challenge during a sustanability course at the HSBA.,Received,https://drive.google.com/drive/folders/1iac3f__cDlv03ssLpfEVRHq5TzdjtYpa?usp=drive_link,
|
||||||
|
,Nasha Afrina Binte Abdul Mutalib,+6588345209,Envoyer le message,afrinaabdul77@gmail.com,Blue Miles Index,1.Nasha Abdul 2. Cassarah Matakena 3. Moh. Aufa Dany Damario 4. Pathinettan Philemon,"Singapour, Singapour",Asia,1. Singapore institute of technology/User experience and game design 2. Universitas Indonesia/Biology 3.Universitas Indonesia/Naval Architecture-Marine Engineering 4. Nanyang Technological University/Environmental and Earth Systems Science,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Consumer awareness and education,"Blue Miles Index is a standardized decision-support framework that makes maritime transport distance visible in purchasing and procurement decisions. The project addresses a structural transparency gap in global trade: while over 80% of goods move by sea, shipping distance and its ocean impact remain invisible at the point of purchase. Blue Miles Index converts product origin and logistics data into a normalized, distance-based ocean pressure indicator that integrates into retail platforms and procurement systems. By introducing a comparable transport signal, the framework enables informed sourcing decisions without restricting choice. Its objective is to reduce cumulative vessel kilometres travelled by influencing upstream purchasing behaviour, thereby lowering emissions, underwater noise exposure, and maritime pressure on marine ecosystems. The system scales through voluntary integration and leverages existing supply chain data, requiring no new infrastructure.",true,linkedin,,,
|
||||||
|
,Ratri Maria,+6596809257,Envoyer le message,ratri@changemakr.asia,ClimaFund,"Tsamara Tsabita, Anggy Priyo, Rizal Suryawan","Singapour, Singapour",Asia,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-07-22,Sustainable fishing and aquaculture & blue food,"ClimaFund makes coastal conservation economically self-sustaining by solving the ""first mile problem""—the gap between restoration investments and verifiable, community-level impact.
|
||||||
|
|
||||||
|
The Problem: Mangrove restoration programs suffer 40-60% failure rates due to late stress detection, while fishing communities—the essential stewards—remain economically invisible to premium markets and carbon finance.
|
||||||
|
|
||||||
|
Two integrated platforms:
|
||||||
|
(1) ClimaFund MRV uses AI-powered satellite monitoring to detect mangrove stress weeks before visible decline, enabling early intervention that protects restoration investments. Our system achieves high accuracy (±80%accuracy) at $60/hectare versus industry-standard $150-250.
|
||||||
|
(2) Kartu Biru provides blockchain traceability linking verified healthy ecosystems to premium seafood markets. Each fisherman receives a cryptographic credential proving their harvest comes from protected mangrove areas—creating instant audit trails for buyers and direct premium payments to communities.
|
||||||
|
|
||||||
|
Objectives:
|
||||||
|
Increase mangrove restoration survival rates from 40% to 80%+
|
||||||
|
Connect 500+ fishing families to premium market access by 2027
|
||||||
|
Generate verifiable blue carbon credits that fund long-term community stewardship.
|
||||||
|
|
||||||
|
We're the first solution combining ecosystem verification with seafood traceability—making conservation profitable for those who protect it.
|
||||||
|
|
||||||
|
Current Traction: Operating in Indonesia's Delta Mahakam with mangrove custodians & social forestry groups, totalling more than 450 hectares, preparing Q1 2026 commercial launch.",true,linkedin,,,
|
||||||
|
,Karime Guillen Libien,+527222617967,Envoyer le message,karimeguillen@rearvora.com,Rearvora,"Estefania Vazquez Martinez, Estefania Valdes Vazquez, Juanjo Saldivar","Toluca, Edomex, Mexique",South America,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2023-01-31,Reduction of pollution (plastics chemicals noise light...),"We are a Mexican circular biotechnology company born from a simple but urgent realization: what tourism leaves behind does not disappear. It travels. It flows through rivers, drains, coastlines and eventually reaches the ocean.
|
||||||
|
|
||||||
|
In coastal and tourism destinations, millions of plastic amenities, chemically loaded personal care products, and poorly managed organic waste quietly accumulate every day. What seems harmless in a hotel room becomes microplastics in marine food chains, toxic residues in coral ecosystems, and nutrient overload that suffocates coastal waters. Organic waste, when mismanaged, releases methane in landfills and contributes to water contamination and eutrophication when it reaches marine ecosystems. These impacts are rarely associated with tourism, yet they are deeply connected.
|
||||||
|
|
||||||
|
At Rearvora, we transform organic waste into bioproducts and bio-based packaging for the tourism sector, replacing pollutant-intensive alternatives at the source. By revaluing what is discarded, we prevent plastic leakage, reduce harmful chemicals entering waterways, and redirect organic waste into regenerative cycles instead of ocean-bound streams.
|
||||||
|
|
||||||
|
Our objectives are concrete: to provide hotels with sustainable amenities that prevent plastic and chemical leakage; intervene upstream within tourism systemsbefore pollution reaches the sea. To offer collection and treatment services for organic waste generated in tourism operations; and to establish circular transformation hubs in tourism regions where local communities participate directly, generating green and innovative jobs. Through circular production hubs, sustainable amenities, and environmental education for hotels and travelers, we turn prevention into ocean protection. These hubs close material loops locally, preventing waste from reaching waterways while strengthening community resilience.
|
||||||
|
|
||||||
|
For us, protecting the ocean does not start at the shoreline. It starts with what we choose to produce, consume, and discar",true,From Instagram and Recommendation of a program name TECA,,,
|
||||||
|
,Alfonso Rodríguez,+526461512748,Envoyer le message,alfonso.rodriguez@cicese.edu.mx,UniMar,"Alfonso Rodríguez, Jeremie Bauer, Manuel Acosta & Jorge Olmos","Ensenada, BC, Mexique",South America,"Centro de Investigación Científica y de Educación Superior de Ensenada Baja California. Ensenada, Baja California. Mexico.",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Restoration of marine habitats & ecosystems,"UniMar produces a proprietary plant-based probiotic feed (zero fishmeal or macroalgae) that fattens starved barren urchins into premium gonads. Every urchin ranched removes a grazer from collapsing kelp forests, making restoration self-funding. 30 years R&D, peer-reviewed validation, only plant-based feed achieving commercial-grade results globally. Scaling from Baja California to global markets.",true,Colleagues,,,
|
||||||
|
,Aluora Annette Luttah,+22892077042,Envoyer le message,aluttah@gmail.com,EcoHarbor Services and Solutions,"Annette Luttah ALUORA, Edem ASSIGBLEY, Adeline ALOKPA, Yaovi ANDELE, Philippe AKUE-ABOSSE, Kokouvi Noviti YEHOUESSI","Lomé, Maritime, Togo","Africa, Togo",,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,2024-02-04,Reduction of pollution (plastics chemicals noise light...),"Transforming plastic ocean waste into prospects for change in Togo
|
||||||
|
1. Project Overview
|
||||||
|
EcoHarbor Services and Solutions is a nature-positive social enterprise that integrates marine plastic recovery with clean port and beach services. The initiative endeavors to reduce ocean pollution, create local jobs, and promote a circular blue economy in Togo.
|
||||||
|
The initiative targets coastal areas, lagoons, and beaches near the ports of Lomé, where plastic waste endangers marine biodiversity, tourism, and local livelihoods. By converting recovered ocean plastics into high-value products and delivering zero-pollution services, EcoHarbor delivers a practical, scalable, and market-oriented solution to ocean pollution.
|
||||||
|
JVE International leads the initiative in partnership with local NGOs, municipalities, ports, private companies, and community groups.
|
||||||
|
|
||||||
|
2. The Problem
|
||||||
|
Plastic pollution is a growing threat to Togo’s 56 km coastline, ports, lagoons, mangroves, fisheries, and tourism. Plastics represent more than 10% of municipal solid waste, while only about 48% of municipal waste is collected, creating high leakage risk into the marine environment affecting mangroves, fisheries, tourism, and coastal livelihoods.
|
||||||
|
Across coastal West Africa, around 80% of plastic waste is mismanaged, making ports and beaches major entry points for plastics into the ocean. Globally, 8–11 million tonnes of plastic enter the ocean every year, underscoring the urgency of local action.
|
||||||
|
Plastic pollution severely affects mangrove ecosystems, reducing seedling survival, blocking root respiration, and accelerating ecosystem degradation in coastal and lagoon environments. Limited waste recovery in ports, lagoons, and beaches leads to plastics entering the ocean, Currently, ports and beaches lack integrated solutions that target pollution prevention, waste recovery, and economic value creation. This project responds to a national need for locally adapted, job-creating ocean solutions rather than imported techno",true,Through a Partner,,,
|
||||||
|
,Janne Springer,+491723211337,Envoyer le message,janne.springer@myhsba.de,The BetterCatch,"Mette Wolf, Lulu Kuhlwein, Janne Springer","Hambourg, Allemagne","Europe, Germany","Hamburg School of Business Administration, Hamburg",the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Sustainable fishing and aquaculture & blue food,"The BetterCatch is a white‑label, science‑based decision system integrated into existing supermarket apps and a stand-alone app. By scanning a barcode or searching for a product, shoppers receive an instant traffic‑light rating (green/yellow/red) and a better in‑store alternative in under three seconds. The rating logic aggregates species, FAO fishing area, gear type, stock status, and additional risk factors into a clear recommendation grounded in peer‑reviewed research and established indicator frameworks.",false,University and our Sustainability Lecture,Received,https://drive.google.com/drive/folders/1lzWuGV8pyCjSyVRv1K2Ns1FhwIGfuH7O?usp=drive_link,
|
||||||
|
,Esméralda Mavrel,+46793575837,Envoyer le message,esmeralda.mavrel1@gmail.com,ROBOVAC,"Esméralda Mavrel, ABIBA FATHIMA A , S. KAVIYA & SIVASHANKER G","Goteborg, Comté de Vastra Gotaland, Suède","Europe, Sweden",University of Gothenburg,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,Reduction of pollution (plastics chemicals noise light...),"ROBOVAC is a semi-
|
||||||
|
autonomous floating robot
|
||||||
|
collecting surface plastic
|
||||||
|
pollution in rivers, lakes,
|
||||||
|
and coastal waters before
|
||||||
|
its degradation in the
|
||||||
|
environment. It's AI-enhanced to classify the debris and generate pollution hotspots maps.",false,After the EU-India Ideathon,,,
|
||||||
|
|
|
@ -1,594 +0,0 @@
|
||||||
Full name,Application status,Category,"Comment ",Country,Date of creation,E-mail,How did you hear about MOPC?,Issue,Jury 1 attribués,MOPC team comments,Mentorship,PHASE 1 - Submission,PHASE 2 - Submission,Project's name,Team members,Tri par zone,Téléphone,University
|
|
||||||
Chaima BEN GRIRA,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,,TN,2023-01-19,cbengrira@blueeconomy.ogs.it,,Reduction of pollution (plastics chemicals noise light...),,,false,,,Bluepsol,"Eskander ALAYA, Chaima BEN GRIRA, Nabil FOGHRI, Ahmed BACCOUCHE, Adel JELJLI","Africa, Tunisia",+393508394071,
|
|
||||||
James Carter-Johnson,Received,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"To Farm Giant Kelp at Scale, on special racks, capturing 30x more C02 than forrest per hecare. Harvest 4 times a year for oganic fertilizer, algin and materials for bio-plastics. All these replace highly polluting oil based products on land.",GB,2024-06-06,james@bigkelp.com,You contacted me I think.,Mitigation of climate change and sea-level rise,,,false,https://drive.google.com/drive/folders/1R5-IfGbETFri6ZX0RnJY8W6wan7cLoz-?usp=drive_link,,Big Kelp,James Carter-Johnson MA MBA; Prof. Carole Llewelyn MSc PhD; Vincent Doumeizel; Carlos Vanegas MSc PhD; James Sainty BA MBA; Akhthar Swaebe BT MSc MBA; Peter Rivera MSc PhD; Alessio Massironi MSc PhD; Johannes van der Merwe ME CE PhD; Oliver Parker BSc MSc,UK,+447899791166,
|
|
||||||
Silvia Ruiz-Berdejo,Received,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"We are a biofoodtech startup specializing in microalgae and plant-based functional ingredients within the blue economy. Our R&D targets sectors like Functional Food Formulation, Precision Food Nutrition, and Nutricosmetics.
|
|
||||||
|
|
||||||
We develop new ingredients that replace fats, sugars, and additives in ultra-processed foods while replicating traditional textures, colors, and flavors to ease consumer transitions to healthier diets.
|
|
||||||
|
|
||||||
Our clean-label formulations support easy industrial integration and rapid scale-up for B2B clients in the food industry, health and wellness groups, innovative food brands, and sports teams. This advances sustainable functional nutrition aligned with blue economy principles",ES,2024-01-11,silvia@omnivorus.com,Linkedlin,Other,,,true,https://drive.google.com/drive/folders/1A8jzY7h4pfebbQKvCtg0Fc0AKzUE1F_q?usp=drive_link,,Omnivorus Smartfood,"Silvia rui-Berdejo CEO -Cofounder , Toni Gonzalez CPO - Cofounder, Luis Pascual CFO , Jose Tornero R&D Funtional Food , Carlota Villanueva-Tobaldo R&D Nutro cosmetic","Europe, Spain",+34622381855,
|
|
||||||
Achyut Karn,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"OceanGuardian AI: Predictive Ocean Protection Through Autonomous Intelligence
|
|
||||||
|
|
||||||
The Problem We're Solving
|
|
||||||
|
|
||||||
Ocean conservation today operates in crisis mode. We discover dead zones after they form, find pollution after it spreads, and detect coral bleaching after ecosystems collapse. Current monitoring methods are expensive, sporadic, and reactive—providing data only after irreversible damage occurs. The ocean needs an early warning system, not an autopsy report.
|
|
||||||
|
|
||||||
Critical gaps in current approaches:
|
|
||||||
- Monitoring covers less than 5% of critical marine zones
|
|
||||||
- Research-grade equipment costs $50,000+ per unit, limiting deployment
|
|
||||||
- Data collection happens quarterly or annually—far too slow for dynamic threats
|
|
||||||
- No predictive capability to prevent ecosystem collapse before it happens
|
|
||||||
- Communities lack real-time information to protect their local waters
|
|
||||||
|
|
||||||
Our Innovation: The World's First Predictive Ocean Protection Network
|
|
||||||
|
|
||||||
OceanGuardian AI deploys networks of affordable, solar-powered autonomous underwater drones that create continuous, real-time monitoring of marine ecosystems. But we don't just collect data—our AI predicts threats 2-8 weeks before critical damage occurs, enabling intervention while ecosystems can still be saved.
|
|
||||||
|
|
||||||
Core Technology Components:
|
|
||||||
|
|
||||||
1. Affordable Autonomous Drones ($800/unit)
|
|
||||||
- Solar and wave-energy powered for perpetual operation
|
|
||||||
- Multi-sensor array monitors 15+ parameters simultaneously
|
|
||||||
- Computer vision and acoustic sensors for marine life tracking
|
|
||||||
- Swarm intelligence enables coordinated monitoring
|
|
||||||
- Modular design adapts for different missions
|
|
||||||
|
|
||||||
2. Predictive AI Engine
|
|
||||||
- Machine learning models trained on oceanographic data
|
|
||||||
- Predicts coral bleaching events, harmful algal blooms, oxygen depletion
|
|
||||||
- Identifies microplastic accumulation hotspots
|
|
||||||
- Detects illegal fishing and pollution incidents in real-time
|
|
||||||
- Creates digital twin models of monitored ecosystems
|
|
||||||
|
|
||||||
3. Real-Time Intervention System
|
|
||||||
- Automated alerts to authorities, NGOs, and c",IN,,achyut.karn.2025@sse.ac.in,Linkedin,Technology & innovations,,,true,,,OceanGuardian AI,"Rishan Narula, Saanvi Mahajan",Asia,+916204778589,Symbiosis School of Economics
|
|
||||||
Laurent BUOB,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Whisper 360, a foiling boat with the performance of a thermal boat, powered by electricity: 45 knots, 100 nautical miles, zero emissions.",FR,2024-09-30,l.buob@whisper-ef.com,We were incubated at Monaco Tech,Sustainable shipping & yachting,,,false,,,Whisper eF,Vincent Lebeault,"Europe, France",+33675090543,
|
|
||||||
Adrien BARRAU,Received,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Seavium is an AI platform that reduces the environmental footprint of offshore operations by eliminating unnecessary vessel movements.
|
|
||||||
Fragmented data and inefficient sourcing lead to avoidable transits, excess fuel use and emissions across the sector.
|
|
||||||
|
|
||||||
Seavium matches each offshore need with the closest, most suitable vessel in real time, using technical data and AIS availability. This optimisation cuts transit miles and fuel consumption at scale.
|
|
||||||
|
|
||||||
Early results show 18–25% fewer miles sailed and 5–12% fuel savings per operation.
|
|
||||||
With 20 000+ vessels mapped and 118 companies already engaged, the model is globally scalable.
|
|
||||||
|
|
||||||
Seavium combines a SaaS subscription with performance-based fees, ensuring that environmental impact increases with platform adoption.",FR,2024-04-01,adrien@seavium.com,via GreenwaterFoundation,Technology & innovations,,,true,https://drive.google.com/drive/folders/1fUCrWCyXQHWEcacseTa338-RPn53KnZy?usp=drive_link,,SEAVIUM,Adrien BARRAU / Samuel DRAI,"Europe, France",+33646221977,
|
|
||||||
Nitya Gunturu,Received,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"We aim to create innovative, sustainable mycelium-based packaging materials designed to replace single-use Styrofoam and plastic in transportation and e-commerce sectors.
|
|
||||||
Problem and Solution Statements
|
|
||||||
|
|
||||||
Problem 1: Plastic Pollution (Land, Water, Air) and Non-Biodegradability
|
|
||||||
The Problem:
|
|
||||||
Over 300 million tons of plastic are produced globally each year, with around 45% being single-use packaging. Styrofoam and plastic foams take up to 500 years or more to decompose, causing persistent pollution.
|
|
||||||
|
|
||||||
Our Solution:
|
|
||||||
We develop 100% biodegradable mycelium packaging that decomposes naturally in 30 to 90 days, enabling a circular economy.
|
|
||||||
|
|
||||||
Problem 2: High Carbon Footprint of Production
|
|
||||||
The Problem:
|
|
||||||
Plastic production contributes about 3.4% of global greenhouse gas emissions, heavily reliant on fossil fuels.
|
|
||||||
|
|
||||||
Our Solution:
|
|
||||||
Our process uses renewable agricultural waste and fungal growth, reducing carbon emissions by up to 70–90% compared to plastics.
|
|
||||||
|
|
||||||
Problem 3: Less Use of Plants and Other Natural Resources
|
|
||||||
The Problem:
|
|
||||||
Conventional bio-packaging often requires dedicated crops, which leads to over-exploitation of valuable land and water resources.
|
|
||||||
|
|
||||||
Our Solution:
|
|
||||||
We convert locally sourced agricultural waste into packaging, requiring significantly less land or water resources.
|
|
||||||
|
|
||||||
Problem 4: Agricultural Waste Mismanagement
|
|
||||||
The Problem:
|
|
||||||
India produces over 500 million tons of crop residue annually, much of which is burned, causing severe air pollution impacting millions.
|
|
||||||
|
|
||||||
Our Solution:
|
|
||||||
We utilize this waste as raw material, reducing harmful burning and creating economic value for rural producers.",IN,,nityagunturu95@gmail.com,University,Reduction of pollution (plastics chemicals noise light...),,,true,https://drive.google.com/drive/folders/1322p0iOzB-d66xlZV85oBEOf9gWOsNkq?usp=sharing,,MycoWrap,Nitya Gunturu and Avni Mishra,Asia,+917680093169,"Ashoka University, India"
|
|
||||||
Hasan Noor Ahmed,Received,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"The Blue Coast Guardians Initiative is a youth-led, community-centered program designed by Bilan Awdal Organization to combat coastal pollution, restore marine ecosystems, and create sustainable blue-economy opportunities along the Somaliland/Somalia coastline.
|
|
||||||
Our approach combines innovative low-cost technologies, community livelihoods, and education, enabling coastal communities to protect the ocean while improving their economic resilience.
|
|
||||||
|
|
||||||
The project targets urgent threats in the region, including plastic pollution, illegal fishing, coastal erosion, and the loss of marine biodiversity.",SO,,biland.awdal.org@gmail.com,Fund for NGO,Capacity building for coastal communities,,,false,https://drive.google.com/drive/folders/1Oz9lQCfhQqw818QegNj9S_SvArSQwZFw?usp=drive_link,,BlueGuard Africa – Community-Driven Ocean & Coastal Protection Innovation Hub,"Hasan Noor Ahmed – Chairman & Founder Amina Abdillahi Ibrahim – Program Director (Health & Nutrition) Mohamed Abdi Warsame – Finance & Administration Officer Hodan Ismail Ali – Climate & Environment Program Lead Abdirahman Yusuf Farah – Monitoring, Evaluation & Learning Officer Fardowsa Ahmed Jama – Community Outreach & Protection Coordinator","Africa, Somalia",+491737752964,Bilan Awdal Organization – Training & Capacity Development Unit
|
|
||||||
ssentubiro billy,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,allow needy children access quality education,UG,2016-08-13,lemanfoundation16@gmail.com,via social media,Capacity building for coastal communities,,,true,,,schoolarships,Nakayulu Grace and ssentubiro billy,"Africa, Ouganda",+256708630034,
|
|
||||||
Ramsay Bader,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"PosidoniaGuard is a turnkey service that helps Mediterranean marinas and coastal authorities stop anchor damage to Posidonia oceanica seagrass meadows by installing seagrass-safe “eco-moorings”, managing no-anchoring zones via a simple booking app, and quantifying the blue-carbon and biodiversity benefits for funders and regulators. Posidonia meadows are critical “blue forests” that store large amounts of carbon, support fisheries and protect coasts, but up to about 34% have already been lost, with tens of thousands of hectares damaged annually by anchoring.
|
|
||||||
|
|
||||||
Objectives:
|
|
||||||
|
|
||||||
1. Protect and restore Posidonia meadows by replacing destructive chain moorings and ad-hoc anchoring with certified eco-moorings in high-pressure bays.
|
|
||||||
|
|
||||||
2. Guide boaters away from seagrass using a digital map and reservation system that clearly marks no-anchor zones and available eco-moorings.
|
|
||||||
|
|
||||||
3. Measure and monetise impact by estimating hectares of seagrass protected and associated blue-carbon storage and ecosystem-service value, creating reporting for marinas, municipalities and impact investors.",US,,Ramsay.Bader@gmail.com,Through my University.,Blue Carbon,,,true,,,PosidoniaGuard,Ramsay Bader. Caroline Hulbert.,US,+16468972588,University of St Andrews. United Kingdom.
|
|
||||||
Adrian Colline Odira,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Our project aims to build a fully circular, climate smart aquaculture model that reduces pressure on overfished natural water bodies while empowering coastal and lakeside communities. By integrating sustainable fish production, renewable energy systems (biogas and solar), digital traceability, and community led cage farming, we create an alternative source of affordable, high quality protein that eases exploitation of lake and ocean ecosystems.
|
|
||||||
|
|
||||||
Objectives
|
|
||||||
|
|
||||||
Reduce dependence on open water fishing by scaling sustainable cage and pond aquaculture systems.
|
|
||||||
|
|
||||||
Empower women and youth with ownership of production units, fair market access, and technical training.
|
|
||||||
|
|
||||||
Increase ocean and freshwater protection by promoting regenerative practices, responsible feed use, and cold-chain efficiency to minimise post harvest loss.
|
|
||||||
|
|
||||||
Deploy digital tools to track origin, ensure transparency, and support ecosystem friendly decision making.
|
|
||||||
|
|
||||||
This approach strengthens food security, grows blue economy incomes, and protects aquatic ecosystems through a scalable, community-centered model.",KE,2018-08-20,adrian@riofish.co.ke,LinkedIn,Sustainable fishing and aquaculture & blue food,,,true,,,Rio Fish Limited,"Adrian Colline Odira, Loren Edwina Odira","Africa, Kenya",+254742838455,
|
|
||||||
Mohammad Badran,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Vision
|
|
||||||
Our vision is to be a global supporter to marine and coastal ecosystems’ stewardship, fostering a future where tropical and subtropical marine environments thrive in harmony with human activities. We envision vibrant resilient marine ecosystems that support biodiversity, enhance climate stability, and contribute to viable sustainable development with diversified livelihoods for the local communities.
|
|
||||||
|
|
||||||
Mission
|
|
||||||
Our mission is to deliver innovative and sustainable management solutions that advance development in tropical and subtropical marine and coastal areas maintaining ecosystems’ health and resilience. We endeavor to harness broad stakeholders’ involvement, community engagement, scientific research, local knowledge, and cutting-edge technology for supporting development in tropical seas to protect and restore ecosystems’ biodiversity and functionality while achieving stakeholders’ interests and local communities’ contentment.
|
|
||||||
|
|
||||||
Approach
|
|
||||||
Our approach is to harness the local knowledge and expertise in all our projects. We will do consultancy work and target nationally, regionally and internationally supported initiatives. We will keep a small team for coordination and management, but our heavy weight will be the local performers in the field. Implementing multiple local projects, we will build an effective Platform for Global Dialogue and Exchange of Experience
|
|
||||||
|
|
||||||
Objectives
|
|
||||||
Our objectives are highly ambitious and divers. We realize the hard work ample time they need to be achieved. But we trust that our approach that counts on the local knowledge and expertise will make our mission achievable. Our objectives include:
|
|
||||||
Conservation and Restoration
|
|
||||||
o Develop and implement science-based and local knowledge strategies for conservation and restoration of critical marine habitats and the biodiversity they support, including coral reefs, mangroves, and seagrass beds.
|
|
||||||
o Monitor and assess coastal and marine ecosystems’ health and the stressors they face to gu",JO,2024-08-30,ceo@martropic.com,From Canada's Ocean Supercluster,Restoration of marine habitats & ecosystems,,,true,,,MarTropic Canada Inc.,Mohammad Badran and Hala Marouf,Asia,+18733557575,
|
|
||||||
Danail Marinov,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"What it is (TRL 4–5): Pilot-ready, AI collaborative platform for GHG emissions Scope 1–3 monitoring, compliance reporting and forecasting for ports/terminals/shipping companies. RedGet.io was among the selected companies and participated in ADT4Blue, EY Startup Academy Germany, Blue Readiness Assistance and Green Marine Med (by Port of Barcelona) programs.
|
|
||||||
Value: Up to 60% reduction in reporting efforts and costs, emission forecasting for EU-ETS regulations, AI maritime assistant and decision-ready visibility to plan and verify decarbonization.
|
|
||||||
Status & partners: Confirmed pilot with Port of Gdynia (Jan 2026) and Port of Talling (Jan 2026); negotiations with Port of Valencia, Port of Huelva, and EY Bulgaria.",BG,2024-12-01,dmarinov@redget.io,A friend of mine shared this opportunity to me,Technology & innovations,,,false,,,RedGet.io,"Danail Marinov, Dobromir Balabanov, Alexander Valchev","Bulgaria, Europe",+359895497694,
|
|
||||||
Shelby Thomas,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Ocean Rescue Alliance International, through its Coastal Resilience Solutions for-profit arm and the We Restore initiative, deploys scalable living shoreline and hybrid reef technologies to restore degraded coastal and marine ecosystems while enhancing climate resilience for vulnerable communities. The project’s objective is to deliver measurable ocean biodiversity recovery, erosion reduction, and carbon co-benefits through science-based, nature-positive infrastructure that can be replicated regionally and globally.",US,2019-12-01,admin@oceanrescuealliance.org,via Email Newsletter,Restoration of marine habitats & ecosystems,,,true,,,Coastal Resilience Solutions: WeRestore,"Dr. Shelby Thomas, Dr. David Weinstein, Lindsay Humbles,",US,+13866897675,
|
|
||||||
Maaire Gyengne Francis,Received,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Problem and Solution
|
|
||||||
|
|
||||||
Urban cities across Africa face a severe plastic waste crisis driven by rapid population growth, heavy consumption of plastic-packaged products, and inadequate formal waste management infrastructure. Most households lack convenient, reliable, and affordable waste disposal options, forcing them to depend on informal collectors with limited capacity and inconsistent schedules - or resort to harmful practices such as burning, burying, or illegally dumping plastic waste in gutters, waterways, and open spaces. This results in widespread pollution, health hazards, clogged drainage systems, flooding, and the loss of valuable recyclable material that could support local and global circular economy markets. Also, recycling companies lack consistent, traceable, and high-quality access to plastic feedstock.
|
|
||||||
|
|
||||||
Our solution is to develop an AI-powered platform that helps urban households dispose of plastic waste by connecting them with local collectors through image, video, or weight-based pricing and cashless payments. It tackles severe plastic pollution in African cities caused by limited collection capacity and unsafe disposal practices. With millions of households generating increasing waste, the market potential is vast across Ghana and other rapidly urbanizing regions. Once consistent collection volumes are reached, WasteTrack will expand into a global plastic trading marketplace, enabling recyclers worldwide to buy verified, traceable plastic waste - positioning the startup as a major player in the circular plastics economy.
|
|
||||||
|
|
||||||
Our AI-driven waste management and digital payment solution is designed to make plastic disposal easy, convenient, and traceable for urban households. Key features will include photo, video, or weight-based AI analysis to estimate disposal fees; secure digital payments; GPS-linked pickup requests; and unique tracking codes for every waste package. The platform also supports community micro-dumpsites for flexible drop-off and pr",GH,2025-01-01,gyengnefrancis90@gmail.com,Google search,Reduction of pollution (plastics chemicals noise light...),,,true,https://drive.google.com/drive/folders/1Rv9W6h5zQESX7A68bQio5JWy5TML86rH?usp=drive_link,,WasteTrack,"Frank Faarkuu, Prosper Dorfiah","Africa, Ghana",+233208397960,
|
|
||||||
Vincent Kneefel,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,,NL,2024-04-16,vincent@vitalocean.io,Linkedin,Technology & innovations,,,true,,,Vital Ocean,Joi Danielson,"Europe, Netherland",+31622514465,
|
|
||||||
Raismin Kotta,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,Sustainability fisheries and Aquaculture,ID,,raisminkotta88@gmail.com,I hear and read MOPC in website and interested to apply,Sustainable fishing and aquaculture & blue food,,,true,,,The Pearls cultuvation & Pearls jewelry,"Raismin Kotta, aya sophia, Lalu harianza,asril junaidy",Asia,+6281342018565,"45 University, Mataram Indonesia"
|
|
||||||
Anastasiia,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,Take technology onto another level,UA,,grozdova.anastasiia@gmail.com,Social media marketing,Technology & innovations,,,true,,,Innovations in ocean environment,Darina Mitina,"Europe, Ukraine",+380680650309,
|
|
||||||
Raphaëlle Guénard,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Filae transforms end-of-life fishing nets into ultra-light, modular supports for plant-based shading and greening (façades and canopies), helping cool down dense urban areas without heavy structures.
|
|
||||||
Our goal is to scale a Mediterranean circular model, from local net collection to on-site deployment, reducing waste and embodied carbon while boosting thermal comfort and biodiversity through real-world pilots.",FR,2025-03-21,contact@filae.eu,"from Marine Jacq-Pietri, Coordinatrice du Monaco Ocean Protection Challenge",Reduction of pollution (plastics chemicals noise light...),,,true,,,Filae,Raphaëlle Guénard & Killian Bossé,"Europe, France",+33663688277,
|
|
||||||
Pavel Kartashov,Received,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Scalable and capital-light hybrid ocean energy platforms harvesting wave, sun and wind energy in near-shore areas for shore and offshore energy end-users",MK,2025-03-05,pavel.k@wavespark.co,Social media post,Technology & innovations,,,false,https://drive.google.com/drive/folders/1vdcWHlPUURdN69T-Ek7wsqOTrLNaODq0?usp=drive_link,,WaveSpark Green Marine Energies,"Pavel Kartashov, Rodrigo Caba, Francisco Perez, Glib Ivanov","Europe, Macedonia",+38975588771,
|
|
||||||
Coral Bisson,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,- Reduction of ocean plastics through development of swimwear using recycled ocean plastics,JE,,coralbisson@icloud.com,University,Reduction of pollution (plastics chemicals noise light...),,,true,,,Corali,Coral Bisson,"Europe, Jersey",+377643915342,International University of Monaco
|
|
||||||
Carol Nkawaga Moonga,Received,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,,ZM,2024-07-11,moongacaroln@gmail.com,I saw an advertisement on LinkedIn,Sustainable fishing and aquaculture & blue food,,,true,https://drive.google.com/drive/folders/1wEWiGREhq-dWPkFqGqmSK89PcuOjhsXX?usp=drive_link,,Kacachi General Dealers,Cathrine Kapesha,"Africa, Zambia",+260979164462,
|
|
||||||
Peter Teye Busumprah,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"This initiative aims to bridge the gap in ocean data across Africa by establishing a standardized platform for accessing African Ocean Biodiversity information. The project involves developing an African Ocean Biodiversity Atlas that provides detailed data on Blue Carbon and Fisheries ecosystems, including GPS coordinates, high-resolution images, and videos illustrating the state of coastal environments throughout Africa. To ensure accessibility, we are utilizing affordable, locally developed technologies and multifunctional ocean applications to map key ecosystems such as fisheries, seaweeds, seagrasses, mangroves, and other ocean biodiversity ecosystems along the continent’s coastlines.
|
|
||||||
|
|
||||||
Our team has grown significantly from 8 to 40 members, representing 20 African nations. Currently, over 800 users are engaged, and a pilot map encompasses ten African countries. We anticipate generating approximately $240,000 annually from app downloads and technology sales, with projected monthly revenues of about $20,000. This includes $7,000 from subscriptions, $7,000 from data sales, $3,000 from licensing, and $3,000 from consulting services.
|
|
||||||
|
|
||||||
The database is designed for policymakers and academic institutions, offering precise data crucial for policy formulation, research, and publication activities. Additionally, we aim to involve private sector stakeholders who depend on reliable data to inform their investments in a sustainable blue economy.
|
|
||||||
|
|
||||||
Key features include the development of a Fisheries Atlas and a Blue Carbon Biodiversity initiative focused on Africa’s landing beaches, providing strategic recommendations for the establishment of Marine Protected Areas (MPAs). The project also promotes data sharing among local indigenous fishermen and enhances understanding aligned with the UN Ocean Decade objectives. It will create a comprehensive data repository covering various marine species, including fish, mangroves, algae, and seaweeds.
|
|
||||||
|
|
||||||
Links:
|
|
||||||
https://oceandecade.org/action",GH,2024-01-01,petervegan1223@gmail.com,MOPC Linkedin.,Technology & innovations,,,true,,,African Ocean Biodiversity Atlas,Mavis Essilfie,"Africa, Ghana",+233544671951,
|
|
||||||
Nilas Neuhauser,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"The NAUTILUS team is developing the latest generation, and most advanced Autonomous Underwater Glider with the goal of flexibly facilitating the collection of crucial data for aquatic research. By doing so, we seek to create a cost-effective and minimally invasive aquatic research robot.
|
|
||||||
After conducting first successful tests this year, we seek to continue testing our glider in Swiss lakes until summer and then, in September, set off for a 2 week mission to test in the Norwegian Ocean.
|
|
||||||
Find our website here: https://aris-space.ch/our-projects/nautilus/",CH,,nilas.neuhauser@aris-space.ch,from the 1000 Ocean Startups LinkedIn,Technology & innovations,,,true,,,Nautilus,45+ members (Management -> PM: Phillip Zenger ; DPM: Nilas Neuhauser ; SE: Matias Betschen),"Europe, Switzerland",+41792977194,"ETH Zurich, Zurich"
|
|
||||||
Aki Allahgholi,Received,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"We will solve the extreme coral restoration bottleneck when it comes to outplanting. The logistical limitations of farming, transporting and outplanting cannot be overcome through the classical methods as of now. Our patented coral paint and spraying mechanism will solve that hurdle.",CH,2025-08-13,aki@corall.eco,LinkedIn,Restoration of marine habitats & ecosystems,,,false,https://drive.google.com/drive/folders/1M8KGN87ZSTEqFP8T2eUccYOE7K7DZNrV?usp=drive_link,,CORAlliance,"Chris Glaser, Peach Zwyssig, Tamaki Bieri, Dave Gulko","Europe, Switzerland",+41763879261,
|
|
||||||
Irina Kharitonova,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"EcoPlaton Tracker is a digital educational and action-oriented platform aimed at protecting oceans by addressing the root causes of pollution on land. The project helps children and families understand how everyday habits—plastic use, chemical products, water consumption, and carbon footprint—affect rivers, lakes, seas, and ultimately the oceans.
|
|
||||||
The platform combines carbon and water impact tracking, eco-challenges, audio guides, and storytelling, including stories about lakes, oceans, and industrial water pollution. It guides users from awareness to action and delivers real environmental impact: part of the project’s revenue supports reforestation and environmental initiatives, with over 1,300 trees already planted in industrial regions of Kazakhstan.
|
|
||||||
EcoPlaton Tracker integrates a Water & Ocean Impact Tracker module that visualizes the “land–water–ocean” pollution pathway and encourages measurable behavior change.",KZ,2025-07-07,irinakharitonova0201@gmail.com,We learned about the Monaco Ocean Protection Challenge last year through Instagram and have been preparing our application since then.,Consumer awareness and education,,,true,,,EcoPlaton Tracker: From Land to Ocean,"Irina Kharitonova, Alexandra Kharitonova, Platon Nechayev",Asia,+77012141077,
|
|
||||||
Fritz Noel Bayong Momha,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"GeoCosta : Application of Geodesign to understand and Innovate in Coastal Protection Planning / Balaz Studio
|
|
||||||
Objectives :
|
|
||||||
-Understand the development of coastal protection in order to contribute to a concerted management focused on adaptation and coastal resilience
|
|
||||||
- Use the concepts of Geodesign and coastal resilience, landscape approach, and consultation in our diagnosis of the protective planning process
|
|
||||||
- Mapping of infrastructures and different actors will illustrate the actions and scenarios of the future vision of this site.",CM,2021-02-07,fbayong@balazstudio.com,LinkedIn,Technology & innovations,,,true,,,GeoCosta : Application of Geodesign to understand and Innovate in Coastal Protection Planning /Balaz Studio,Fritz Bayong,"Africa, Cameroun",+32467868495,
|
|
||||||
Rasmus Borgstrøm,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"FlowMinerals captures CO₂ from seawater and converts it into fossil-free calcium carbonate, contributing to the mitigation of ocean acidification while reducing reliance on land-based limestone mining. The solution enables industrial decarbonization using ocean-compatible materials, with a strong focus on environmental safety and minimal marine impact.
|
|
||||||
www.FlowMinerals.com",DK,2023-09-24,rasmus@blueplanetinnovators.com,LinkedIn,Mitigation of ocean acidification,,,true,,,FlowMinerals,"Rasmus Borgstrøm, Esben Jessen","Denmark, Europe",+4527117113,
|
|
||||||
Amelia Martin,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,We manufacture an eco-friendly alternative to marine foam (marine grade styrofoam).,US,2023-06-13,amelia@mudratsurf.com,Google!,Reduction of pollution (plastics chemicals noise light...),,,true,,,Mud Rat,"Jack Tarka, Patricio Acevedo, Brian Lassy",US,+18606824426,
|
|
||||||
James Kalo Malau,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,,VU,2026-01-01,malau_jk@hotmail.com,Funds for NGOs Premium,Sustainable fishing and aquaculture & blue food,,,true,,,Coral Reforestation,"John Maliu, Josue Jimmy, Nalo Samuel, Manu Roy, James Sulu",Oceania,+6787774965,
|
|
||||||
Jonas Wüst,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Tethys Robotics builds compact autonomous underwater robots that replace emission-intensive vessel operations with remote, low-impact subsea inspection. Our goal is to make offshore maintenance safer and more sustainable by reducing CO₂ emissions, preventing environmental damage through early detection, and improving the reliability of renewable marine infrastructure.",CH,2024-08-15,jonas@tethys-robotics.ch,BRIDGE by Innosuisse forward us.,Technology & innovations,,,false,,,Tethys Robotics,Pragash Sivananthaguru,"Europe, Switzerland",+41766307924,
|
|
||||||
João Manuel de Gouveia Firmino,Received,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"Project idea: Convert local fish discards on Madeira into a hygienic, fermented fish sauce (small-batch artisanal → scalable).
|
|
||||||
|
|
||||||
Objectives: Reduce waste; add value for fishers; create local jobs; supply restaurants/retail; position as circular blue-economy premium product.
|
|
||||||
|
|
||||||
Key details: Source = local landings; partners = fishers + certified processor + food-safety lab; compliance = HACCP/food regs; go-to-market = horeca, gourmet stores, e-commerce; pilot → scale path.",PT,,9822@novalaw.unl.pt,Through Fondation Prince Albert II de Monaco.,Other,,,false,https://drive.google.com/drive/folders/1Pbf4FwTfAfqklel_a94CYA7dZsmvPfGH?usp=drive_link,,Atlantic Fish Sauce,João Firmino / Duarte Fernandes,"Europe, Portugal",+351969136436,NOVA University Lisbon (Nova School of Business & Economics) / University of Madeira (Faculty of Sciences and Engineering)
|
|
||||||
Francesco Ruscio,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,Enhance monitoring of benthic habitats using robotics and artificial intelligence.,IT,,francesco.ruscio@ing.unipi.it,linkedin,Technology & innovations,,,false,,,PerSEAve,"Francesco Ruscio, Simone Tani, Alessandro Gentili","Europe, Italia",+393756436501,"University of Pisa, Pisa, Italy"
|
|
||||||
Lorna Mudegu,Received,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"WAVU is a market and aggregation platform that connects verified aquaculture producers to buyers through organised, predictable supply chains.
|
|
||||||
|
|
||||||
In many coastal and inland markets, fish buyers source from informal channels where farmed fish and wild-caught fish are indistinguishable. This lack of separation sustains demand for capture fisheries and contributes to overfishing in already stressed marine and freshwater ecosystems. By aggregating aquaculture producers, forecasting demand, and directing buyers toward farm-based supply, WAVU helps shift market demand away from unregulated wild catch.
|
|
||||||
|
|
||||||
As more buyers rely on planned aquaculture sourcing, pressure on wild fisheries is reduced while livelihoods are supported through sustainable fish production. Each tonne of farmed fish absorbed into formal markets represents demand that would otherwise be met through extraction from natural fish stocks.
|
|
||||||
|
|
||||||
WAVU builds on ongoing operations in East Africa and offers a scalable, market-driven pathway to reducing pressure on wild fisheries in regions facing overfishing and informal fish trade.",KE,2024-07-30,lornaafwandi@gmail.com,LinkedIn,Sustainable fishing and aquaculture & blue food,,,true,https://drive.google.com/drive/folders/1_Y9YW-Y_kd2Tpz80fH5juc9Ol1TR7DKq?usp=drive_link,,WAVU,Don Okoth | Vincent Oduor | Chris Munialo | Loise Mudegu,"Africa, Kenya",+254718059337,
|
|
||||||
Shamim Wasii Nyanda,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"SUNWAVE provides small-scale fishers in Tanzania with solar-powered ice-making units to reduce fish spoilage. These machines, powered by solar energy, offer a sustainable and cost-effective solution to fish preservation, especially in remote areas where access to the power grid is limited. By keeping fish fresh for longer, these units help fishers reduce spoilage, maintain higher-quality products, and increase income. The ice-making machines are operated by trained personnel to ensure proper use and efficiency.",TZ,2024-03-01,shamim@sunwaveltd.com,It was shared by SUNWAVE's Advisory Board member.,Sustainable fishing and aquaculture & blue food,,,true,,,SUNWAVE,Ridhiwan Mseya,"Africa, Tanzania",+255764190074,
|
|
||||||
Olaleye Rofiat Olayinka,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Eco Heroes is an incentive-based, tech-enabled recycling solution that prevents ocean-bound plastic waste from entering rivers and marine ecosystems. The project mobilizes communities to collect and exchange post-consumer plastic for rewards such as cash and essential services, creating a reliable supply of recovered plastic while improving livelihoods. Recovered materials are recycled and transformed into value-added products, including sewing threads, ensuring financial sustainability and scalable impact. The objective is to measurably reduce plastic pollution, create local economic value, and build a replicable model for coastal and river-connected communities.",NG,2021-11-08,olaleyerofiatyinka@gmail.com,I learned about the Monaco Ocean Protection Challenge through my involvement in an entrepreneurship and innovation programs focused on the blue economy and plastic pollution solutions.,Reduction of pollution (plastics chemicals noise light...),,,true,,,Eco Heroes Nigeria limited,Olaleye Rofiat Olayinka Salaam Lateef Oladimeji Akinsanya Dorcas Olaleye Hassan Ogundairo Ganiyat,"Africa, Nigeria",+2348038877293,
|
|
||||||
Christian Mwijage,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Every year, 9 million tonnes of plastic waste enter our oceans, polluting marine ecosystems and threatening ocean life. At this rate, by 2050 the ocean could contain more plastic than fish. At the same time, the world loses over 2 billion trees annually to meet the demand for timber in the furniture and construction industries—making deforestation the second leading driver of climate change.
|
|
||||||
We address both crises through a chemical-free, energy-efficient, AI-powered technology that transforms ocean-bound plastics and post-consumer packaging waste into high-quality, sustainable materials for furniture, building, and construction applications. By converting low-value, hard-to-recycle multi-layer plastic (MLP) waste into durable products, we are advancing the circular economy and giving new life to materials that would otherwise damage the environment.
|
|
||||||
We address one of the most persistent challenges in the plastics value chain: waste streams that lack viable conventional recycling pathways. We focus specifically on two difficult-to-recycle categories - multi-layer plastics (MLP), which combine multiple plastic layers and/or aluminum foil, and mixed plastic waste that cannot be economically or efficiently segregated. Globally, an estimated 6 billion tons of plastic waste have been generated, approximately 14% of which consists of MLP. Due to technical and economic limitations, these materials are typically landfilled, incinerated, or left uncollected, contributing significantly to environmental pollution and ecosystem degradation.",TZ,2022-12-21,chrissmwijage@gmail.com,Social Media,Reduction of pollution (plastics chemicals noise light...),,,true,,,ECOACT Tanzania,"• Mr. Bernard Ernest, Technical Director overseeing all production activities, holds a Master of Engineering in Biochemical Engineering. Mr. Christian Mwijage, Managing Director responsible for overall operations, holds a Bachelor’s degree in Business Administration and Marketing. Ms. Elineca Ndowo, Chief Finance Officer, holds a Master’s degree in Project Management and Financing from the University of Dar es Salaam.","Africa, Tanzania",+255711457346,
|
|
||||||
Rasheed Aliu,Received,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Land-based pollution is the largest contributor to ocean degradation, yet sanitation failures in coastal communities remain overlooked. In flood-prone coastal regions of Africa, fragile septic systems collapse during flooding, releasing untreated human waste into groundwater, rivers, lagoons, and ultimately the ocean.
|
|
||||||
I witnessed this firsthand in coastal Lagos, Nigeria, when flooding destroyed local sanitation systems and a neighbour’s 4-year-old daughter died from cholera, a preventable waterborne disease. This tragedy reflects a systemic failure. Over 90% of Nigerian households rely on sanitation systems that leak sewage, contributing to 117,000 annual child deaths from waterborne diseases, according to UNICEF.
|
|
||||||
In the absence of centralized wastewater infrastructure, outdated septic tanks costly to build and maintain are frequently evacuated or overflow during floods, with waste discharged into coastal waters. This drives marine pollution, eutrophication, biodiversity loss, and degradation of near-shore ecosystems critical to food security.
|
|
||||||
At Pod we design and manufacture LoopBox, LoopBox is a solar-powered, IoT-enabled, self-contained sanitation system designed for coastal and flood-prone communities. Unlike traditional soakaway pits that leak, our tech uses embedded sensors and microbial treatment to track, treat, and recycle human waste into reusable water. Through our cloud dashboard, users and local authorities can monitor sanitation performance and water quality remotely. We also provide nearby borehole treatment as a service. LoopBox is 5x more cost-effective than conventional systems, eliminates 100 dollars/year in waste evacuation costs, and requires minimal space. Built with scalable hardware and software, it is designed to be deployed across low-income, climate-vulnerable communities bringing safety, sustainability, and data-driven decision-making to sanitation in Nigeria and Africa.
|
|
||||||
The project delivers a flood-resilient, decentralized sanitation s",NG,2025-05-06,rasheedofpod@gmail.com,BFA Global TECA Alumni Group( Tyler),Reduction of pollution (plastics chemicals noise light...),,,true,https://drive.google.com/drive/folders/1Ur6FAveOOAtS77TOGXuLGbVfqTF8mG9p?usp=drive_link,,Pod,"Rasheed Aliu, Gabriel Simon , Habeeb Lasisi and MaryJudith Chiamaka","Africa, Nigeria",+2348160238021,
|
|
||||||
Chelsey Karbowski,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Mikjikj Mniku, Mi’kmaq for “Turtle Island”, is an Indigenous-led consulting firm working at the intersection of ocean protection, community governance, and workforce development.
|
|
||||||
|
|
||||||
We help governments, philanthropies, and conservation organizations design ocean and climate initiatives that last beyond funding cycles by embedding Indigenous knowledge, ethical engagement, and local stewardship from the start.
|
|
||||||
|
|
||||||
Our work focuses on strengthening Indigenous and coastal governance, building inclusive workforce pathways in fisheries, marine monitoring, and ocean-adjacent clean energy, and making social impact measurable and defensible through socio-economic and SROI frameworks.
|
|
||||||
|
|
||||||
In a global push to protect more ocean faster, we ensure protection efforts are community-supported, socially resilient, and future-proofed, because conservation only succeeds when the people closest to the ocean are empowered to carry it forward.",CA,2025-03-01,chelsey.m.karbowski@gmail.com,Linkedin,Other,,,true,,,Mikjikj Mniku Consulting Ltd.,Chelsey Karbowski,Canada,+19026314362,
|
|
||||||
OLUTOKI FEYISHAYO FUNMI,Received,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"To develop and inplement a sustainable food production (crops and animals) that specifically reduces a known threat to the Ocean (e. go, pollution, overfishing pressure, habitat destruction).
|
|
||||||
|
|
||||||
We will take measures using circular economy, by developing a system where waste products from our farm are treated and used in a way that prevent them from entering marine ecosystem
|
|
||||||
|
|
||||||
We will work on water management and pollution reduction and sustainable sourcing /supply chain",NG,2024-12-12,rebugssolutions@gmail.com,,Consumer awareness and education,,,true,https://drive.google.com/drive/folders/1xCJ_8EpTEdBORiJHYwIRZO22z8e54fbx?usp=drive_link,,Operation feed the children,"Adewuyi Feranmi, Olutoki sewafunmi victor, Ayodele joy, Babalola gbenga","Africa, Nigeria",+2348038226106,
|
|
||||||
Anshika Sarraf,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"Auralis Blue is tackling a problem few people see but that is harming our oceans: underwater noise pollution. Ships, ports, and offshore construction create constant sound that travels far underwater, interfering with how whales, dolphins, and fish communicate, migrate, and reproduce. Auralis Blue measures this invisible threat and turns it into clear, actionable data, helping maritime stakeholders protect marine life while continuing sustainable operations.
|
|
||||||
|
|
||||||
Underwater noise is an invisible threat, but its effects are very real: studies show that marine mammals rely on sound to survive, and high noise levels can cause stress, confusion, and even death in fish populations. Despite this, there are almost no tools that measure or manage noise systematically. Auralis Blue fills this gap, providing a science-based, scalable solution that can protect marine ecosystems worldwide.
|
|
||||||
|
|
||||||
Objectives:
|
|
||||||
1) Measure and Map noise pollution
|
|
||||||
2) Marine life protection
|
|
||||||
3) Encourage better and sustainable practices
|
|
||||||
4) Support policy, investment & encourage systemic change in blue economy",IN,,anshika.sarraf_ug2024@ashoka.edu.in,LinkedIn,Reduction of pollution (plastics chemicals noise light...),,,true,,,Auralis Blue,Anshika Sarraf,Asia,+917897130506,Ashoka University + Sonipat
|
|
||||||
Neville Agesa,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"The Tsunza Community, located on Kenya’s South Coast in Kwale County, is a vital ecological hub linking mangrove forests, wetlands, and the Mwache River estuary. These interconnected ecosystems support fisheries, biodiversity, and local livelihoods but face increasing pressure from degradation, pollution, and declining fish stocks.
|
|
||||||
|
|
||||||
This project aims to protect and restore mangrove and wetland ecosystems while strengthening sustainable blue livelihoods. Through community-led mangrove restoration, marine pollution awareness, and youth and women engagement in sustainable fisheries and aquaculture practices, the project promotes ocean protection alongside economic resilience.
|
|
||||||
|
|
||||||
By integrating nature-based solutions, environmental education, and livelihood innovation, the initiative positions Tsunza as a scalable model for community-driven ocean conservation and sustainable development.",KE,2023-02-01,agesanevil@gmail.com,Gensea opportunities,Sustainable fishing and aquaculture & blue food,,,true,,,Sustainable Blue Food & Livelihoods Innovation,"Robert Meya,Hannah Mathenge,JohnChaka","Africa, Kenya",+254796438122,
|
|
||||||
Veronica Nzuu,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"My project focuses on empowering children and youth in my community to take action on plastic pollution through simple, community led learning and action. The objective is to build awareness, responsibility, and leadership by combining environmental education with practical activities such as waste segregation, plastic collection, creative upcycling, and community dialogue. By using participatory and inclusive approaches, especially for girls and marginalized youth, the project aims to strengthen community ownership of sustainability solutions and inspire long term behavior change at the local level.",KE,2023-05-29,veramichael2000@gmail.com,Social Media linked in,Consumer awareness and education,,,true,,,Furies,Angelo Mulu,"Africa, Kenya",+254748488312,
|
|
||||||
Fiona McOmish,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,We replace toxic PFAS chemicals in textiles with a water- and fire-resistant coating made 100% from seaweed. We sell our high-performing solution to textile manufacturers and formulators in a 'drop-in' format.,IT,2024-12-16,fiona.mcomish@algae-scope.com,LinkedIn,Technology & innovations,,,true,,,Algae Scope,Natasha Yamamura; Alejandra Noren; Farshid Pahlevani,"Europe, Italia",+447722083419,
|
|
||||||
Nesphory Mwambai,Received,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"seamo.earth initiative focused on utilizing artificial intelligence (AI) to explore, document, monitor, and preserve the mariculture and seascapes of the Pwani regions. This project aims to enhance our understanding and protection of marine environments through the development of eco-friendly and climate adaptive technologies.",KE,2024-08-22,mwambai@seamo.earth,email news letter,Restoration of marine habitats & ecosystems,,,true,https://drive.google.com/drive/folders/1eOyDGZwwlNNAzbwwC-CUVmJi4gM3kDLI?usp=drive_link,,seamo.earth,"Nesphory Mwambai, Lewis Kimaru","Africa, Kenya",+254714520023,
|
|
||||||
Yahuza Sani Hudu,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"CleanUp Multi Dyna mic Concept (CleanUp MDC) is a Nigeria-based social enterprise advancing inclusive climate-tech solutions within the circular economy. Our flagship innovation, JoliTrash, is a toll-free, AI-powered, voice-based recycling platform that allows households and informal waste actors to sort and sell recyclable waste using a simple AI phone call in their local language without the need for smartphones, internet access, or digital literacy. Nigeria generates about 2.5 million tons of plastic waste annually, yet less than 10% is recycled (World Bank). At the same time, over 70% of Nigerians lack easy access to recycling facilities, locations, or clear recycling processes (NESREA, 2022), and 48% of the population has poor or no internet connectivity (NCC, 2023), making most app-based recycling platforms inaccessible to low-income and marginalized communities. CleanUp MDC was created to bridge this gap by enabling users to dial a toll-free number on any basic phone (cell-phone) and interact with our AI in Hausa, Yoruba, Igbo, Pidgin, or English with no language barrier, our AI identify users location, connect with nearby verified waste collectors, and user earn income from recyclables. Our target market includes low-income households, women, youth, informal waste pickers, and underserved urban and peri-urban communities across Nigeria, as well as recycling agents and aggregators seeking reliable recyclable feedstock. To date, we have onboarded over 30,163 active users from underserved communities, 17,907 of them women, and facilitated the recovery of more than 10,000 tons of plastic waste, positioning our operations to contribute to an estimated 25,000 tons of CO₂ emissions reduction annually, equivalent to removing about 4,000 fuel-powered cars from the road each year. We partner with the Waste Pickers Association of Nigeria (WAPAN), we are scaling nationwide with the long-term goal of expanding across Africa. Our main goals are to expand access to recycli",NG,2024-02-26,ysanihudu@gmail.com,"The Commissioner for Environment and Natural Resources of Kaduna State Government, Nigeria Share's the link with my startup",Reduction of pollution (plastics chemicals noise light...),,,true,,,CleanUp MDC,"Abner Ayuba Atuga, Ameer Saeed","Africa, Nigeria",+2348146036089,
|
|
||||||
Emeka Nwachinemere,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"Pelagos is developing autonomous, bio-hybrid ocean regeneration machines that restore marine ecosystems while capturing atmospheric carbon. These AI-guided ocean drones re-mineralize seawater to combat acidification, stimulate safe plankton growth to enhance blue carbon sequestration, and support coral regeneration in degraded reefs.
|
|
||||||
|
|
||||||
Objectives:
|
|
||||||
|
|
||||||
Restore ocean health and biodiversity at scale
|
|
||||||
|
|
||||||
Enhance natural blue carbon capture and climate resilience
|
|
||||||
|
|
||||||
Provide real-time ocean intelligence data
|
|
||||||
|
|
||||||
Build a commercially viable, globally scalable blue-economy solution
|
|
||||||
|
|
||||||
Pelagos aims to transform oceans into self-healing climate engines while creating measurable environmental, social, and economic value.",NG,,nwachinemere.emeka@gmail.com,Linkedin,Restoration of marine habitats & ecosystems,,,true,,,Pelagos,"Nwachinemere Emeka, Nduka Miracle","Africa, Nigeria",+2348062148183,"University of Nigeria, Nsukka"
|
|
||||||
Rodrick Nyendwa,Received,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,Mitigation about climate change and its impact and issue that community are aware .,ZM,2023-12-12,rodricknyendwa2016@gmail.com,Through social media on funds for NGOs,Mitigation of climate change and sea-level rise,,,true,https://drive.google.com/drive/folders/1RlybRQMKzhAdcU9vqg8XDZHbtpSzLOCN?usp=drive_link,,"Complehensive HIV prevention ,Treatment care support","Rodrick Nyendwa,Executive Director, Mumbi Micheal - Finace Manager, Ementy Mweemba- Programme Manager, Winter Musonda - Human Resource Mnager, Josiah Ndjovu -Community Liason Officer, Simata Mate - Monitoring and Evaluation Manager , Edith Bwalya -Data Entry Officer, Brona Kapindo - Office Assistant , Sylvester Chisanga - Front office Assistant","Africa, Zambia",+260977339071,
|
|
||||||
Nasibu Mtambo,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Blue EcoponicX is a climate-tech initiative that transforms marine plastic waste into 3D printed hydroponic towers for urban farming. The project addresses two interconnected challenges; ocean plastic pollution and urban food insecurity by converting waste into smart, productive food-growing systems designed for cities.
|
|
||||||
|
|
||||||
Project Objectives
|
|
||||||
|
|
||||||
1. Reduce Marine Plastic Pollution
|
|
||||||
Collect and recycle marine plastic waste, preventing it from entering landfills or degrading in the ocean.
|
|
||||||
|
|
||||||
2. Improve Urban Food Security
|
|
||||||
Enable affordable, space-efficient food production for households, youth groups, and small-scale urban farmers.
|
|
||||||
|
|
||||||
3. Lower Urban Carbon Emissions
|
|
||||||
Reduce food miles, optimize resource use, and promote localized production using energy-efficient systems.
|
|
||||||
|
|
||||||
4. Promote Climate-Smart Agriculture
|
|
||||||
Use IoT technology to minimize water, nutrient, and energy waste while maximizing crop yields.
|
|
||||||
|
|
||||||
5. Empower Communities Through Technology
|
|
||||||
Make modern farming accessible through easy-to-use smart systems, training, and data insights.
|
|
||||||
|
|
||||||
Key Features
|
|
||||||
|
|
||||||
1. Circular Economy Design: Hydroponic towers made from recycled marine plastics
|
|
||||||
|
|
||||||
2. IoT Integration: Real-time monitoring of water, nutrients, and system health
|
|
||||||
|
|
||||||
3. Low Resource Use: Up to 90% less water than traditional farming
|
|
||||||
|
|
||||||
4. Urban-Friendly: Suitable for rooftops, balconies, schools, and community spaces
|
|
||||||
|
|
||||||
5. Scalable & Modular: Easy to expand from household to community-scale deployment
|
|
||||||
|
|
||||||
Target Beneficiaries
|
|
||||||
|
|
||||||
1. Urban small-scale farmers
|
|
||||||
|
|
||||||
2. Youth and women-led agribusinesses
|
|
||||||
|
|
||||||
3. Schools and training institutions
|
|
||||||
|
|
||||||
4. Cities seeking climate-resilient food systems
|
|
||||||
|
|
||||||
Expected Impact
|
|
||||||
|
|
||||||
1. Reduced plastic pollution in coastal and marine ecosystems
|
|
||||||
|
|
||||||
2. Increased access to fresh, nutritious food in urban areas
|
|
||||||
|
|
||||||
3. Lower carbon emissions from food transport and waste
|
|
||||||
|
|
||||||
4. Creation of green jobs in recycling, manufacturing, and urban agriculture
|
|
||||||
|
|
||||||
5. Stronger climate resilience for cities",KE,2025-05-20,mtamboduke@gmail.com,Through LinkendIn,Reduction of pollution (plastics chemicals noise light...),,,true,,,Blue EcoponicX,"Tabitha Shali, Mohammed Athman, Terry Okwanyo","Africa, Kenya",+254742051141,
|
|
||||||
Faith Mutisya,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Tumbe sea weed farmers is a community based organization in Msambweni kwale Kenya. We focus on empowering coastal communities especially young women and youth with skills in sustainable sea weed farming. This is because as women in kwale county we face alot of challenges such as early marriages and pregnancies most especially because women are not given the same schooling privilege as men. Therefore so many young mothers don't have any skills to provide for their young ones. Therefore Tumbe sea weed farmers has taken the initiative to empower them , and through the farming they are able to support themselves financially and at the same time contribute to global efforts in fighting climate change because weed plays an important role as a carbon sink . And we also contribute to increase in biodiversity by provide nursery and nurturing bay for fish and other aquatic organisms",KE,2023-03-02,faithmutisya56@gmail.com,Through linked in,Capacity building for coastal communities,,,true,,,Tumbe sea weed farmers,Faith Mutisya - founder and Trainer 2. Hanifa wendo- secretary/field manager 3. Mwanamisi Mwadzumba - Treasurer,"Africa, Kenya",+254711627836,
|
|
||||||
李涵凝,Received,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"The 10cm transparent eco-jellyfish robot carries ocean-beneficial materials, moving by mechanical legs and drifting with waves to reduce marine pollution and repair ecosystems, quietly improving ocean health",CN,2026-01-01,xbm_0201@qq.com,I found out about MOPC through an online search.,Reduction of pollution (plastics chemicals noise light...),,,,https://drive.google.com/drive/folders/1duMty6mbpLCOoataogbZEShA6keuK2fy?usp=drive_link,,Environmentally Friendly Jellyfish,李涵凝,Asia,+8618618164803,
|
|
||||||
Kabir Olaosebikan,Received,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Craft Planet – Blue Guard for the Ocean is an integrated ocean-protection initiative that prevents plastic pollution before it reaches the sea. Using AI-enabled drones, we identify high-risk waste leakage points along riverbanks and coastal areas, enabling rapid collection of plastic waste before it enters rivers and oceans. Recovered plastics are recycled into durable construction materials—interlocking blocks, eco-bricks, floor and roof tiles—which are used to improve public school infrastructure, including classrooms, toilets, desks, and chairs. The project also builds capacity among coastal communities, teachers, and students through environmental education, waste management training, and circular economy skills, creating local ownership, green jobs, and long-term ocean stewardship.",NG,2023-04-17,kabir@craftplanet.org,Through online sustainability platforms and ocean innovation networks.,Reduction of pollution (plastics chemicals noise light...),,,true,https://drive.google.com/drive/folders/1jUFqGLk1zZ6afP4BysRPpp_jRXsw_9Kz?usp=drive_link,,Craft Planet - Blue Guard,"Kabir Olaosebikan, Aminat Abdulazeez, Promise Dalero, Hanatu Abdulakeem","Africa, Nigeria",+2348142123656,
|
|
||||||
Karl Mihhels,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"The project is about converting fast-growing species of algae, with a high cellulose content (Cladophorales) into a direct replacement for wood based cellulose and cellulose products, such as paper.",FI,,karl.mihhels@aalto.fi,2nd EU Algae Awareness Summit held in Berlin on October 17th 2025,Blue Carbon,,,true,,,Shaving the Seas,Karl Mihhels,"Europe, Finland",+358447627444,"Aalto University School of Chemical Engineering, Finland"
|
|
||||||
SENI Abd-Ramane,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"_Project Title_: OceanClean Tech
|
|
||||||
|
|
||||||
_Objective_: The OceanClean Tech project aims to reduce plastic pollution in the oceans by developing a marine plastic waste collection system. The main goal is to clean up polluted marine areas and prevent new plastic waste from entering marine ecosystems.
|
|
||||||
|
|
||||||
_Innovation_: The project's innovation lies in the use of autonomous drones equipped with artificial intelligence (AI) technologies to locate and collect plastic waste at sea. The drones are capable of navigating autonomously, identifying plastic waste using sensors and image recognition algorithms, and collecting it for transport to a treatment point.
|
|
||||||
|
|
||||||
_Impact_: The OceanClean Tech project has several expected impacts:
|
|
||||||
1. _Environmental_: Significant reduction of plastic waste in the oceans, protecting marine biodiversity and ecosystems.
|
|
||||||
2. _Social_: Raising public awareness of marine pollution and involving local communities in clean-up actions.
|
|
||||||
3. _Economic_: Creating new economic opportunities related to sustainable marine waste management and the development of clean technologies.",BJ,2025-12-29,seniramane@gmail.com,"I heard about the Monaco Ocean Protection Challenge on LinkedIn, it immediately caught my attention!",Reduction of pollution (plastics chemicals noise light...),,,true,,,OceanClean Tech,"SENI Abd-Ramane, DJIBRIL Samir, SOULÉ SEIDOU Mansoura","Africa, Bénin",+2290161149564,
|
|
||||||
Omoding Olinga Simon,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Dagim Fisheries directly advances equitable access to safe, nutritious, affordable food while improving planetary health through zero-waste processing and sustainable fishing. Our multidisciplinary approach integrates nutrition science, food engineering, supply chain management, environmental conservation, and economics. We address malnutrition, reduce waste, empower fishing communities, and protect Lake Victoria's and Kyoga's ecosystem creating regenerative food systems scalable across East Africa toward the billion-lives impact goal.",UG,2024-01-05,simonomoding.ace@gmail.com,Through Linkedinn social media,Sustainable fishing and aquaculture & blue food,,,true,,,Dagim Fisheries (U) Ltd,"Omoding Simon, Ilukat Musa, Omiel Peter, Omongole Richard, Fellista Nakatabirwa","Africa, Ouganda",+256773351242,
|
|
||||||
Mutave Nelly,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,Hrkb,KE,1997-01-24,mutavenelly.mn@gmail.com,Friend shared link,Technology & innovations,,,true,,,Revamp Flips,Nthatisi Lesala,"Africa, Kenya",+254704458380,
|
|
||||||
Ketty Shamakamba,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Lake Farms is establishing an academy dedicated to preserving Lake Kariba and its communities. It is a center for training, innovation, and direct action.
|
|
||||||
|
|
||||||
Core Mission: Halt fish stock depletion and foster a sustainable blue economy.
|
|
||||||
|
|
||||||
Key Initiatives:
|
|
||||||
|
|
||||||
Training Hub: Equip local fishers with skills in sustainable aquaculture, ecosystem management, and cooperative business.
|
|
||||||
|
|
||||||
Innovation & Deployment: Design and deploy ethical, lake-friendly cage systems and restorative practices to rebuild wild stocks.
|
|
||||||
|
|
||||||
Community Enterprises: Launch community-owned ""Aqua-Hubs"" that provide food security, create livelihoods, and empower women.
|
|
||||||
|
|
||||||
This academy would create a lasting legacy of ecological restoration, poverty reduction, and resilience for Lake Kariba's people, directly honoring a commitment to ocean and freshwater preservation.",ZM,2021-11-30,ilovesolarfreezers@gmail.com,"We learned about the Monaco Ocean Protection Challenge through the communication channels of the Prince Albert II of Monaco Foundation and its associated networks, which highlight pioneering solutions for ocean and freshwater conservation.",Capacity building for coastal communities,,,true,,,LAKE FARMS AND FISHING LODGE LIMITED,"Board and Management Team Chisanga Mambwe – Board Chairperson (Strategic oversight) Provides governance leadership, investor relations support, and high-level oversight of the executive team. Ketty Shamakamba – Chief Executive Officer (CEO) Leads overall strategy, fundraising, partnerships, gender lens work, and company growth. Oversees business development, climate initiatives, and solar cold-chain expansion. Chiozya Mwanza – Chief Operations Officer (COO) Responsible for day-to-day operations, cage management, production planning, logistics coordination, and community engagement with fishers and women traders. Hamando Hamalabbi – Chief Financial Officer (CFO) (Accountant) Manages finance, accounting, compliance, investment reporting, budgeting, and financial controls. Muzalema Zimba – Chief Marketing Officer (CMO) (Sales & Marketing Manager) Oversees sales strategy, distribution channels, branding, customer acquisition, and premium market relationships (hotels, restaurants, wholesalers). Joshua Mwanza – Chief Operations Manager / Deputy COO (Operations Manager) Supports operations, distribution logistics, procurement, cold-chain coordination, and team supervision. Micheck Chulaula – Chief Farm Manager (CFM) (Farm Manager) Oversees cage management, feeding regimes, harvesting, processing coordination, and ensuring biosecurity and aquaculture standards. Mabel Kaunda – Chief Human Resources Officer (CHRO) (HR Manager/Secretary) Manages staff welfare, recruitment, training, compliance, and gender-inclusive workforce policies. Our operations team includes experts in farm management, logistics, and business development, ensuring efficient production, processing, and distribution. The finance and technology staff oversee solar freezer leasing, mobile payments, and digital monitoring systems, enabling scalable, sustainable impact. Many team members, including the founders, have personal connections to the communities we serve, which drives our ","Africa, Zambia",+260971094443,
|
|
||||||
Torrigiani Aurore,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Sea Blocks develops modular, low-tech artificial reefs designed to restore marine habitats in port environments.
|
|
||||||
|
|
||||||
Each reef is co-designed and assembled through participatory workshops involving companies, citizens and local stakeholders, then installed in partnership with ports. The modules are made from low-carbon materials and locally sourced shell waste, enhancing ecological functionality and accelerating colonisation by marine species.
|
|
||||||
|
|
||||||
The project combines ecological restoration, circular economy and awareness-raising, with scientific monitoring conducted by marine biology experts to assess biodiversity recovery and long-term impact.",FR,2021-02-03,seablocksrecif@gmail.com,Through professional networks and partners involved in ocean and coastal innovation.,Restoration of marine habitats & ecosystems,,,true,,,Sea Blocks,Olivier Meynard,"Europe, France",+33647780342,
|
|
||||||
Godfrey Noel,Received,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Project Title: Bamboo Stewardship for Mangrove Protection: Building Sustainable Livelihoods Along the East African Coast
|
|
||||||
|
|
||||||
The Problem
|
|
||||||
East African coastal mangrove ecosystems spanning from Somalia to Mozambique face catastrophic degradation, with communities harvesting mangroves for fuel and construction because alternative income sources remain unavailable. This extraction destroys critical carbon sinks, eliminates natural storm surge barriers, and collapses fish nursery habitats that sustain coastal food security. Traditional conservation approaches exclude communities from protected areas without providing viable economic alternatives, guaranteeing enforcement failure and continued ecosystem loss.
|
|
||||||
|
|
||||||
Our Solution
|
|
||||||
Kilimora, in strategic partnership with EarthLungs, is implementing a bamboo based mangrove protection system that transforms coastal communities from ecosystem exploiters into paid ecosystem stewards. We employ community members to cultivate and harvest fast growing bamboo (Bambusa species with 3 to 5 year harvest cycles and continuous regrowth capacity) in designated buffer zones adjacent to mangrove forests. This bamboo provides sustainable construction materials and biomass fuel alternatives that eliminate economic pressure on mangrove stands while generating verifiable income for participating households.
|
|
||||||
|
|
||||||
Technical Innovation
|
|
||||||
The initiative integrates drone based mangrove health monitoring with ground truth verification by community stewards, creating high resolution ecosystem data that supports both conservation management and carbon credit generation. Kilimora provides the artificial intelligence powered verification infrastructure and blockchain based transparent payment systems ensuring stewards receive direct compensation tied to measurable mangrove protection outcomes. EarthLungs contributes marine ecosystem expertise, coastal community organizing capacity, and connections to corporate blue carbon credit buyers.
|
|
||||||
|
|
||||||
Scale and Impact
|
|
||||||
The program cu",KE,2024-01-04,gnoel@kilimora.africa,LinkedIn network,Capacity building for coastal communities,,,,https://drive.google.com/drive/folders/1Ouz8-deBPfgUl7VYIwofxUwEYG4D9iMw?usp=drive_link,,Kilimora CLG,"Godfrey Noel, Zuhra Nagib, Matthew Muange, Hildah Gichuru, Ezra Maruti, Hildah Gichuru","Africa, Kenya",+254795647634,
|
|
||||||
Hellen flavine akinyi,Received,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,Plastic pollution from urban markets and streets flows into rivers ad ultimately into the ocean. single-use plastic paper bags are among the most common sources of marine debris.preventing plastic waste at the source is the most effective ad affordable solution than ocean multi-million clean up efforts,KE,2021-02-09,artworkspace1@gmail.com,thro. funds- for- Ngo newsletters,Consumer awareness and education,,,true,https://drive.google.com/drive/folders/1bFnRFeWaxyD2g52MG0l_Y6q_rQOCYbnc?usp=drive_link,,"STOPING OCEAN PLASTICS AT THE SOURCE;DIGITAL ECO-PACKAGING SOLUTION LED BY A YOUNG AFRICA WOMAN,KENYA 2026026","KIMBERLY ADHIAMBO CONIE, MAISON JOHN &PETER WAMBURA","Africa, Kenya",+27631484516,
|
|
||||||
Tochukwu Uwakeme,Received,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"Coastal Blue-Skills Hubs by Pikia is a scalable, community-led training and microenterprise program that equips coastal youth and women with practical skills, tools, and starter microgrants to reduce ocean pollution and strengthen climate-resilient livelihoods through waste-to-value (plastic collection/sorting), sustainable fishing practices, and mangrove/coastal restoration. The program will run through local “Hub” partners (NGOs/co-ops/schools), a lightweight mobile curriculum, and a train-the-trainer model, paired with verified community monitoring (simple metrics + photo evidence) to prove impact and unlock blue-economy buyers and sponsors.
|
|
||||||
Objectives:
|
|
||||||
• Cut land-to-ocean leakage by organizing community collection, sorting, and resale of plastics, with tracked volumes diverted.
|
|
||||||
• Increase resilient incomes by training and supporting community micro-enterprises (waste-to-value, eco-services, sustainable seafood handling) and link them to off takers.
|
|
||||||
• Restore natural coastal defenses through mangrove/coastal habitat restoration tied to local stewardship incentives and verified survival rates.
|
|
||||||
• Create a repeatable “Hub-in-a-box” model that can scale across coastal regions quickly with clear KPIs and partner networks delivering positive, measurable ocean impact in the short to medium term, consistent with MOPC’s focus on ocean-positive business concepts",US,,uwakemet@bu.edu,United Nations SDGs Newsletter.,Capacity building for coastal communities,,,true,https://drive.google.com/drive/folders/1ph6DBmqeSGvSSqxQkymPx9rlU-ZmGFnr?usp=drive_link,,Pikia Marine,"Tochukwu Uwakeme, Moses Imoleyo, Ihuoma Ohaegbulam",US,+12024255839,Boston University / United States
|
|
||||||
Veronica Nzuu,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"My project focuses on community-based climate and ocean education for children and youth, using storytelling, play, and interactive learning to build awareness around plastic pollution, waste segregation, and environmental responsibility. The objective is to transform how young people and families understand and relate to plastic consumption moving from awareness to everyday action. Through games, facilitated sessions, and community learning spaces, the project empowers children to become informed advocates within their households and neighborhoods, strengthening long-term behavior change and community ownership of sustainability solutions.",KE,2023-05-23,veramichael2000@gmail.com,Social media linked in,Consumer awareness and education,,,true,,,Furies,Angelo Mulu,"Africa, Kenya",+254748488312,
|
|
||||||
Cristiano da Silva Palma,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Project Idea & Scientific Context
|
|
||||||
|
|
||||||
The project develops a next-generation modular OTEC (Ocean Thermal Energy Conversion) system, combining innovative deep-ocean structures, ultra-optimized thermodynamic cycles, and AI-based monitoring to deliver continuous (24/7) clean energy, with initial pilot operation targeted from 2027. The system is designed for scalable deployment in tropical and island regions, validated through a pilot-scale OTEC unit operating with deep-water intake (~1000 m or more) and real-time intelligent control.
|
|
||||||
|
|
||||||
Sur le plan scientifique, la technologie OTEC repose sur l’exploitation de la différence de température entre les eaux de surface chaudes et les eaux profondes froides afin d’alimenter un cycle thermodynamique de production d’électricité, conformément aux analyses reconnues par la Convention-cadre des Nations Unies sur les changements climatiques (UNFCCC).
|
|
||||||
|
|
||||||
Dans les régions tropicales, où les eaux de surface peuvent dépasser 25 °C tandis que les eaux profondes se situent autour de 5 °C, le différentiel thermique (ΔT) peut excéder 20 °C, condition généralement considérée comme favorable à une application efficace de l’OTEC, comme le soulignent de nombreuses publications académiques, notamment celles de la MDPI.
|
|
||||||
|
|
||||||
En revanche, dans le bassin méditerranéen, y compris autour de la Principauté de Monaco, les données actuelles indiquent un ΔT généralement inférieur aux seuils classiques de viabilité de l’OTEC à grande échelle. Toutefois, à partir de 2027, evolving ocean temperature profiles, combined with AI-assisted thermodynamic optimization, high-efficiency working fluids, operation restricted to periods of maximum thermal contrast (summer), intake at greater depths, and high thermal-efficiency piping, may enable experimental and seasonal OTEC operation, positioning the Mediterranean as a future testbed for advanced ocean energy technologies.
|
|
||||||
|
|
||||||
By aligning scientific rigor with technological innovation, the project contributes to ocean protection",BR,2024-08-09,cristianospalma@yahoo.com.br,"I learned about the Monaco Ocean Protection Challenge through institutional email exchanges within the framework of the United Nations Framework Convention on Climate Change (UNFCCC), including communications with the UNFCCC Global Secretariat, notably Simon Stiell, Executive Secretary, as well as with UNFCCC National Focal Points in Monaco and France. These included Carl Dudek (Ministry of Foreign Affairs and Cooperation of the Principality of Monaco), Dietmar Petrausch and Wilfred Suddath-Deville (Ministry for Europe and Foreign Affairs of France), and Yue Dong and Bénédicte Jenot (French Ministry for the Ecological Transition).",Technology & innovations,,,true,,,Tabernacle Space Islands,Cristiano da Silva Palma,South America,+5511978020540,
|
|
||||||
Titus Nyandoro,Received,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"We are a Kenyan-based, ocean-minded for-profit fintech venture for fishing coastal communities that are dedicated to the sustainable blue economy",KE,2024-01-01,ktnyandoch@gmail.com,WhatsApp,Technology & innovations,,,true,https://drive.google.com/drive/folders/1twpoOtR1RIei27iSRXNquyV4iMBA9HdC?usp=drive_link,,VUA SOLUTIONS,"Matthew Egessa, Titus Nyandoro","Africa, Kenya",+254743378884,
|
|
||||||
Mzuvukile Benayo,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,Spatial Planning Collective more about engaging stakeholders and driving education.,ZA,2011-07-07,mzuvukilejames@gmail.com,FundsforNGOS email,Capacity building for coastal communities,,,true,,,Youth Innovation Programme,Zenande Mnethu,"Africa, South Africa",+27738223994,
|
|
||||||
Abdoulaye Sarr Ndour,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"AI-powered gamified learning platform for ocean conservation education (Duolingo-style for oceans). The platform features an AI tutor powered by ChatGPT-4/Claude, 4 educational modules covering Biodiversity, Climate, Threats, and Solutions, gamification elements including XP points, badges, and mini-games, plus professional certifications.
|
|
||||||
|
|
||||||
Target customers include B2C users (parents/students) paying 9.99 EUR/month subscriptions, schools paying 800-1,500 EUR/year for licenses, corporations paying 2K-50K EUR for CSR training programs, and professionals purchasing certifications for 49-299 EUR each.
|
|
||||||
|
|
||||||
Year 1 objectives: 10,000 users generating 207K EUR revenue. Year 3 objectives: 200,000 users generating 5.8M EUR revenue. Overall mission: 1 million ocean-literate people by 2030.
|
|
||||||
|
|
||||||
Tech stack: Next.js frontend, Supabase backend (PostgreSQL + Auth + Storage), OpenAI API for AI tutor functionality.
|
|
||||||
|
|
||||||
Timeline: 90 days to launch following MVP development, beta testing with 100 users, then public launch.
|
|
||||||
|
|
||||||
Initial budget required: 15-30K EUR covering development, educational content creation, and marketing expenses.",SN,,ndour.ecobox@gmail.com,Linkedin,Technology & innovations,,,true,,,OceanEdu AI,"Omar Cissé Faye, Fatou Cissé, Coumba Gueye","Africa, Senegal",+221775110218,"Saint Louis Gaston Berger University, Senegal"
|
|
||||||
Christopher Enriquez Urban,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"Project: AI-powered offshore infrastructure for Sargassum monitoring, harvesting, and conversion into industrial biomass.
|
|
||||||
|
|
||||||
Problem: Massive Sargassum blooms devastate Caribbean coasts but remain unused due to unpredictable availability and high logistics costs.
|
|
||||||
|
|
||||||
Solution: Neural-operator forecasting systems predict bloom movements with high accuracy, guiding automated offshore platforms that harvest and preprocess algae at sea—delivering consistent, industrial-grade feedstock.
|
|
||||||
|
|
||||||
Objectives: Create reliable supply chains for bio-based materials, reduce coastal environmental damage, generate jobs and enable circular economy applications in construction, energy, and agriculture.
|
|
||||||
|
|
||||||
Impact: Transforms environmental crisis into economic opportunity while addressing climate goals through fossil material substitution.",DE,,christopher@algrid.tech,LinkedIn,Other,,,true,,,Algrid,Valentina Iunosheva,"Europe, Germany",+4915679760251,"University of Leeds, Leeds, UK"
|
|
||||||
Sarfraaz Khan AYAZ KHAN,Received,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"Poseidon transforms ocean-recovered plastic into certified, customizable, high-end solid surface materials for architects, designers, artists, and sustainability-driven businesses.
|
|
||||||
Our advanced material development and manufacturing ensure durability, aesthetic quality, and long-term reuse as alternatives to conventional surfaces - integrating ocean plastic back into the economy. Each sheet removes approximately 15–30 kg of ocean plastic and includes a digital product passport that provides full traceability from collection to final use. Incubated at MonacoTech, Poseidon aligns with multiple UN Sustainable Development Goals and empowers creative professionals to lead eco-innovation, contributing to ocean cleanup, circularity, and measurable environmental impact.",MC,,info@poseidon-monaco.com,"Last year MOPC competition , JCI CCE event and news letter",Reduction of pollution (plastics chemicals noise light...),,,true,https://drive.google.com/drive/folders/11GEc6IYyLaZnQ_rtkgNHeafVPnuOZ2bz?usp=drive_link,,POSEIDON,Sarfraaz Khan AYAZ KHAN,"Europe, Monaco",+33745384992,SKEMA Business School and POLIMI Graduate School of Management
|
|
||||||
Francesca Rose Turner Prichard,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"Restore society’s relationship with the ocean by building connections between women and the ocean through sport, art and conservation activities.",ES,,francescaroseturner@gmail.com,Linkedin,Consumer awareness and education,,,true,,,Residensea,"Francesca Turner, Aoife Martin, Alberto Rangel","Europe, Spain",+34671298357,Southampton Solent University
|
|
||||||
Brian Ochieng Aliech,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"Our project seeks to address the pervasive challenge of plastic pollution that has afflicted urban centers and aquatic ecosystems across the globe.
|
|
||||||
By converting discarded plastic into durable construction materials, we aim to alleviate the strain on finite natural resources traditionally employed in the building industry, thereby reducing both costs and inefficiencies.
|
|
||||||
In addition, this initiative seeks to generate meaningful employment opportunities for young people in underserved communities, empowering them to achieve economic stability and dignity. Ultimately, our vision is nothing less than to contribute to the preservation and renewal of our planet.",KE,,ochiengaliech@gmail.com,Through a friend.,Reduction of pollution (plastics chemicals noise light...),,,true,,,NOLA AFRICA,"Brian Aliech, Charles Okutah, Hussein Hezekiah, Kevin Onsongo, Lidah Makena","Africa, Kenya",+254757008417,"University of Nairobi, Nairobi"
|
|
||||||
Adhithi Mugundha Kumar,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,We aim to provide a solution to invasive blue crabs in the Mediterranean by developing a bait-induced fishing method.,GB,2026-01-06,Adhithimukhundh@gmail.com,,Sustainable fishing and aquaculture & blue food,,,true,,,Blue crabs,Xenia Anagnostou,UK,+447512296331,
|
|
||||||
THIERRY BOUSSION,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Yuniboat develops an industrial model dedicated to the eco-reconditioning of leisure and professional boats, designed to significantly reduce the environmental impact of boating activities.
|
|
||||||
|
|
||||||
By extending the lifespan of existing boats rather than building new ones, Yuniboat directly contributes to the protection of oceans and marine ecosystems. Reconditioning avoids the extraction of new raw materials, limits fiberglass and plastic waste, and reduces emissions linked to manufacturing and end-of-life destruction.
|
|
||||||
|
|
||||||
Key Environmental Impacts
|
|
||||||
Reduction of marine pollution by preventing abandoned and end-of-life boats from becoming waste at sea or in ports.
|
|
||||||
Preservation of marine fauna and flora through lower emissions, reduced noise pollution, and cleaner propulsion systems (electric, biofuel, hybrid).
|
|
||||||
Lower pressure on natural resources, with up to 80% of boat components reused.
|
|
||||||
Decrease in carbon footprint, contributing to climate action and healthier marine ecosystems.
|
|
||||||
|
|
||||||
Project Objectives
|
|
||||||
Make boating more compatible with ocean preservation.
|
|
||||||
Support professionals (fishing, rental fleets) in meeting decarbonation goals by 2030.
|
|
||||||
Offer a sustainable, economically viable alternative to new boat construction.
|
|
||||||
Deploy a scalable industrial model capable of transforming the nautical and maritime sectors.
|
|
||||||
Yuniboat’s ambition is to position eco-reconditioning as a key lever for ocean protection, combining circular economy, innovation, and long-term impact on marine biodiversity.",FR,2022-06-01,t.boussion@yuniboat.com,we follow your activities on Linkedin and Instagram,Reduction of pollution (plastics chemicals noise light...),,,true,,,Yuniboat,Thierry Boussion,"Europe, France",+33621220023,
|
|
||||||
Daniele Tassara,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"MareNetto is a yacht-focused climate platform that automatically calculates and offsets superyacht CO2 emissions from AIS/MMSI data, then issues verifiable certificates that owners and charter managers use for marketing and ESG compliance",IT,,daniele.tassara@outlook.com,I lived in Monaco and i knew about this project,Technology & innovations,,,true,,,MareNetto,"Giambattista Figari, Giorgio Mussini","Europe, Italia",+393466376215,"Universita di Genova, Genova"
|
|
||||||
Gaia Minopoli,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Ogyre is a startup tackling marine plastic pollution through a Fishing for Litter model, working directly with fishing communities worldwide. Its mission is to clean the Ocean while turning plastic waste into a resource. By financially supporting fishers to recover marine litter during their daily activities, and by involving local partners for sorting and recycling, Ogyre delivers measurable environmental and social impact. The entire process is fully traceable through a blockchain-enabled platform, allowing companies to monitor progress and impact in real time. Active across Europe, South America, Africa, and Asia, Ogyre has already recovered over 800 tons of marine waste and proven a financially sustainable model—now scaling its impact globally to reach 30M kg of cumulated collection by 2030!",IT,2020-01-21,gaia.minopoli@ogyre.com,Scientific attaché of Italian Embassy in Paris,Reduction of pollution (plastics chemicals noise light...),,,true,,,Ogyre,Agnese Antoci Alessandro Serra Alice Casella Andrea Faldella Andrea Scatolero Antonio Augeri Chiara Maggiolini Davide Brugola Filippo Ferraris Gaia Minopoli Gian Piero Seregni Lorenzo Gastaldo Matteo Quaglio Mattia De Serio Michele Migliau Alessandro Sciarpelletti Francesco Carletto francesco notari Irene Eustazio Jurgen Ametaj Lorenzo Varas Marta Berardini Lucrezia Napoletano Gabriele Cusimano Enrica Sandigliano,"Europe, Italia",+393393499607,
|
|
||||||
Yajaira Cristina Alquinga Salazar,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"The general objective of this research plan is to study the dynamics of coastal dunes in the southwest of Buenos Aires Province, with special emphasis on the foredune, and its relationship with climatic, oceanographic, and anthropogenic factors. In particular, the study aims to determine the degree of influence of each of these factors, especially in areas where urban settlements have been established over the last 80 years, in comparison with adjacent sectors subjected to similar environmental conditions but without anthropogenic influence.",AR,,cristinalquinga@gmail.com,LinkedIn,Mitigation of climate change and sea-level rise,,,true,,,Dynamics of Coastal Dune Fields in the Southwest of Buenos Aires Province,"Bsc. Yajaira Cristina Alquinga Salazar, Dr. Gerardo M. E. Perillo and Dr Sibila A. Genchi",South America,+541136132787,Universidad Nacional del Sur
|
|
||||||
Jovana,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Project name: Symphony of the Blue
|
|
||||||
|
|
||||||
The Idea: Converting real-time oceanographic data (currents, temperature, pH levels) into immersive musical compositions using mathematical algorithms.
|
|
||||||
|
|
||||||
Objectives:
|
|
||||||
|
|
||||||
Emotional Data Visualization: Making the ""silent"" problems of the ocean audible to the public and investors through music.
|
|
||||||
|
|
||||||
Eco-Funding: Generating revenue for marine conservation through the sale of these unique, data-driven symphonies.
|
|
||||||
|
|
||||||
Ocean Literacy: Educating younger generations by integrating science, math, and art.",RS,2026-01-07,jovanaperisic059@gmail.com,,Technology & innovations,,,false,,,EcoMath,Jovana Perišić,"Europe, Serbia",+381645655226,
|
|
||||||
Amelia Martin,Received,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,Mud Rat is a biomaterials startup creating an eco friendly alternative to marine foams.,US,2023-06-14,amelia@mudratsurf.com,Google!,Consumer awareness and education,,,true,https://drive.google.com/drive/folders/1GzXe6ugfJQCFdqcZxj3lZrN4H7DSMAIE?usp=drive_link,,Mud Rat,"Jack Tarka, Patricio Acevedo, Brian Lassy",US,+18606824426,
|
|
||||||
Mulowoza Grace,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"We are tackling plastic pollution through innovative upcyling solutions, our work is centered around four main objectives;
|
|
||||||
1. Reduce plastic pollution through innovative upcyling, we are transforming plastic waste into valuable resources.
|
|
||||||
2. Promote waste separation and proper waste management practices.
|
|
||||||
3. Raise awareness about the importance of environmental conservation.
|
|
||||||
4. Empower youth to take action in environmental conservation.
|
|
||||||
Our project, combat plastic pollution through circular economy innovation aims to reduce plastic pollution which can end up into oceans by promoting circular approaches that emphasize reduction, reuse, recycling and sustainable alternatives.
|
|
||||||
Through transforming plastic waste into economic and social opportunities our team contribute to environmental protection, green job creation and sustainable development.",UG,2022-11-07,mulowozagrace@gmail.com,Facebook,Reduction of pollution (plastics chemicals noise light...),,,true,,,Divine youth environment initiative,"Mulowoza Grace, Nassaazi phiona, Sseruga ibraheem, Male simon , kirume Vivian Deborah","Africa, Ouganda",+256705620491,
|
|
||||||
Suraj Kumar Hota,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,Using Indian knowledge system to manage overfishing,IN,,surajkumarhota23@gmail.com,,Sustainable fishing and aquaculture & blue food,,,true,,,No project,SubhaKant Dalei,Asia,+919776476665,Berhampur University India
|
|
||||||
Shiva,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,IN,,shiv@gmail.com,Collage,Technology & innovations,,,true,,,NA,NA,Asia,+918529637418,
|
|
||||||
Sebastian Marzetti,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Underwater acoustics monitoring using easy to deploy systems
|
|
||||||
Our low power systems allow real-time alerts and data for immediate action",FR,2026-03-01,marzettisebastian@gmail.com,Linkedin,Technology & innovations,,,false,,,Intelligent Acoustics,Valentin Barchasz - Valentin Gies - Hervé Glotin,"Europe, France",+33766861456,
|
|
||||||
Emana Bilalović,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Ocean Asylum Certificates (OAC) establish legally protected micro-zones in the ocean by converting conservation into binding contractual commitments.
|
|
||||||
The project enables regulated no-exploitation areas that directly influence shipping and yachting behavior through enforceable restrictions, transparent monitoring, and long-term accountability.
|
|
||||||
Its objective is to embed ocean protection into maritime governance rather than rely on voluntary sustainability pledges.",XK,2025-08-18,emanabilalovic12@gmail.com,Instagram of University of Monaco,Sustainable shipping & yachting,,,true,,,Ocean Asylum Certificates,"Emana Bilalović, Alzana Bajrami","Europe, Kosovo",+381656075770,
|
|
||||||
Sabira Ayesha Bokhari,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,A gamified app to encourage sustainable coastal tourism,IN,,sabirabokhari@gmail.com,Ocean Oppurtunities,Other,,,true,,,Eco-Pirates,Aimen Akhtar,Asia,+33753635938,Universidad Catholica de Valencia
|
|
||||||
Vera Emma Porcher,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Idea:
|
|
||||||
Eco-engineered reef systems, built on circular-economy principles, repurposing surplus marine-grade concrete and recycled oyster shells into high-performance aquatic habitats that enhance and restore biodiversity and ecosystem services, support food security, and protect coastal communities and infrastructure at scale.
|
|
||||||
|
|
||||||
Objectives:
|
|
||||||
-Support long-term food security by restoring, conserving and enhancing productive marine habitats.
|
|
||||||
|
|
||||||
-Strengthen coastal protection by designing and deploying high-performance eco-structures that act as natural breakwaters, reducing wave energy and coastal erosion.
|
|
||||||
|
|
||||||
-Continuous tracking of ecosystem health in real time through automated ecological monitoring using AI-driven analysis to maximise reef performance.
|
|
||||||
|
|
||||||
- Scalable nature-inclusive designs and eco-structure integration for offshore oil & gas and offshore wind infrastructure to enhance ecological performance and biodiversity protection.
|
|
||||||
|
|
||||||
Other relevant details:
|
|
||||||
We are currently testing prototypes in Australia and developing an autonomous monitoring system, with early results showing very positive outcomes and remarkable improvements in biodiversity.",AU,2023-11-29,veraporcher20@gmail.com,Linkedin,Restoration of marine habitats & ecosystems,,,true,,,In-Depth Innovations,"Vera Porcher, Kane Dysart and Tynan Bartolo",Oceania,+61466053917,
|
|
||||||
Lee patrick EKOUAGUET,Received,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"OCEAN-PATCH is an intelligent, autonomous maritime safety patch designed to protect human lives at sea.
|
|
||||||
It detects critical situations (man overboard, distress, abnormal conditions) in real time and transmits alerts and data without batteries, using body or environmental energy.
|
|
||||||
|
|
||||||
The project aims to improve maritime safety while generating valuable ocean data to support prevention, monitoring, and smarter decision-making through AI.",FR,2023-10-23,ogoouecorpstechnologies@gmail.com,Through online research and innovation platforms focused on ocean protection and blue tech.,Technology & innovations,,,true,https://drive.google.com/drive/folders/1BEc9s5h5H41vf2bRxpqvz4AHBWMZS1Xm?usp=drive_link,,OGOOUE CORPS TECHNOLOGIES,"ANDRE BIAYOUMOU, NGABOU PASCAL XAVIER, LYNDA NGARBAHAM, DUPUIS NOUILE NICOLAS","Europe, France",+33778199372,
|
|
||||||
Tshephiso Kola,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"LumiNet is our solution to the fishing industry’s two biggest headaches: catching the wrong fish and losing expensive gear that pollutes the ocean forever. We are replacing standard nylon nets with a smart, dual-action material that actually works with nature. First, our nets glow with a specific light underwater that sharks and turtles instinctively avoid, which keeps them out of the net while the target fish swim right in. Second, we’ve solved the ghost gear problem with a built-in fail-safe: as long as the net is used in the sun, it stays strong, but if it gets lost and sinks into the dark ocean, it rapidly breaks down and turns into fish food. Our goal is simple: to stop plastic pollution at the source and make fishing more efficient, saving marine life and money at the same time.",ZA,,kolatshepisho@gmail.com,Social Media,Sustainable fishing and aquaculture & blue food,,,true,,,Luminet,Tshephiso Kola,Africa,+27671509841,"University of the Witwatersrand, Johannesburg"
|
|
||||||
Eric & Aurélie Viard,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,Use of organic edible seaweeds in daily food and gastronomy,FR,2007-03-11,eric@biovie.fr,We have been invited directly by Marine Jacq-Pietri to submit our project,Consumer awareness and education,,,true,,,Algues au quotidien,"Eric Viard, Aurélie Viard","Europe, France",+33695360436,
|
|
||||||
BARHOUMI Nawress,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"The project aims to develop an autonomous intelligent robot for cleaning marine environments, specifically targeting oil spills, human hair, and other pollutants. It focuses on sustainable technology, environmental protection, and smart control systems. The robot is built using recovered and recycled plastic materials, reinforcing the project’s commitment to circular economy principles and eco-friendly engineering.",TN,2024-05-05,nawressbarhoumigf@gmail.com,Newsletters,Technology & innovations,,,false,,,El Makina,"Mustapha Zoghlami, Nawress Barhoumi","Africa, Tunisia",+21621898617,
|
|
||||||
Yao Yinan,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"BluePulse is an innovative project that combines art, design, and technology to raise awareness about ocean pollution and marine conservation. Its main objective is to educate and inspire the public through creative visual campaigns, interactive installations, and sustainable product concepts that highlight the importance of protecting our oceans. The project also explores solutions to reduce plastic and chemical pollution, fostering a culture of environmental responsibility.",CN,,yyn982715367@outlook.com,I found out about the Monaco Ocean Protection Challenge through the organisers listed on the UArctic Congress 2026 website: https://www.uarcticcongress.fo/about,Consumer awareness and education,,,true,,,"BluePulse – Design, Protect, Inspire",Yinan Yao,Asia,+8615221826163,"Communication University of China, Nanjing(Location: Nanjing, China)"
|
|
||||||
Antalya Fadiyatullathifah,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,xxx,ID,2024-11-11,Antallathifah@gmail.com,xxxx,Blue Carbon,,,true,,,Environmental Consultant,xxx,Asia,+6281110115560,
|
|
||||||
Moramade Blanc,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"Le projet SIRECOP – Suivi Intelligent des Récifs Coralliens et de la Pêche à Belle-Anse vise à renforcer la résilience des récifs coralliens du Parc Naturel National Lagon des Huîtres (PNN-LdH) et à promouvoir une pêche durable dans le Sud-Est d’Haïti.
|
|
||||||
|
|
||||||
Face aux pressions climatiques et anthropiques, il combine des technologies innovantes (capteurs environnementaux, drones, caméras sous-marines et intelligence artificielle) et une approche participative impliquant les communautés de pêcheurs.
|
|
||||||
|
|
||||||
Le projet permettra de suivre la santé des récifs, de restaurer les zones dégradées et d’améliorer la gestion des ressources halieutiques, contribuant ainsi à la conservation des écosystèmes marins, à la sécurité alimentaire et au développement durable des communautés côtières de Belle-Anse.",HT,,blamo82@yahoo.fr,Through my university and professional networks and partnerships,Technology & innovations,,,true,,,« Suivi Intelligent des Récifs Coralliens et de la Pêche à Belle-Anse » « SIRECOP »,"Moramade Blanc, Wedeline Pierre, Chralens Calixte, Jacky Duvil,Ruth Catia Bernadin",Haïti,+50940809002,"Sorbonne Universite, France"
|
|
||||||
Samuel Nnaji,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"Project: Zero Ocean
|
|
||||||
|
|
||||||
Idea: Digital platform for transparent, efficient, and sustainable clean fuel supply chain in maritime
|
|
||||||
|
|
||||||
Objectives:
|
|
||||||
- Optimize clean fuel procurement and reduce emissions
|
|
||||||
- Ensure compliance with global regulations
|
|
||||||
- Enhance bunkering efficiency and audit trails
|
|
||||||
Key Features: eBDN, AI-driven analytics, real-time tracking, supplier integration",NG,,realstard247@gmail.com,WhatsApp,Sustainable shipping & yachting,,,true,,,Zero Ocean,Benjamin Odusanya,"Africa, Nigeria",+2348161502448,University of Nigeria
|
|
||||||
Hannah Gillespie,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"SeaBrew is an early-stage food and drink start-up developing a seaweed-reinforced coffee designed to improve micronutrient intake through an existing daily habit. Our product combines sustainably sourced seaweed with coffee to deliver nutrients such as magnesium, while maintaining taste and consumer acceptability. We have already conducted a blind taste test with positive consumer feedback and recently pitched SeaBrew to EIT Food, where we were awarded second place, which has encouraged us to progress towards more rigorous technical validation and compliance ahead of scaling.",GB,,hggillespie12@gmail.com,The Ocean Opportunity Lab (TOOL),Sustainable fishing and aquaculture & blue food,,,true,,,SeaBrew Coffee,"Anne Moullier, Joseph Flynn, Hannah Gillespie, Laura Coombs, Ronan Cooney",UK,+447887479247,"University of Cambridge, Cambridge, UK"
|
|
||||||
Rhea Thoppil,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"PhytOFlight
|
|
||||||
Plant-based mitigation of plastic pollution in Kerala’s backwaters
|
|
||||||
|
|
||||||
PhytOFlight is a nature-based initiative that uses phytoremediation and native aquatic vegetation to mitigate plastic and microplastic pollution in Kerala’s backwaters. Inspired by the “fight or flight” response, the project uses plants as active ecological defenders that intercept, trap, and reduce plastic waste while restoring ecosystem health.
|
|
||||||
|
|
||||||
Kerala’s backwaters are ecologically and economically vital, yet increasingly threatened by plastic pollution from domestic waste, and tourism. Conventional cleanup methods are costly and short-lived. PhytOFlight offers a low-cost, sustainable, and scalable alternative that works with natural processes rather than relying solely on mechanical removal.
|
|
||||||
|
|
||||||
Objectives
|
|
||||||
Reduce macroplastic and microplastic pollution in targeted backwater zones, improve water quality and support aquatic biodiversity and engage local communities in monitoring, maintenance and environmental awareness of such areas
|
|
||||||
|
|
||||||
PhytOFlight integrates ecological restoration with pollution control, offering a cost-effective, climate-resilient solution tailored to Kerala’s backwaters in India.",IN,,rmthoppil@gmail.com,Through my university,Reduction of pollution (plastics chemicals noise light...),,,true,,,phytoflight,Rhea Thoppil,Asia,+33745764372,"Sorbonne University, France"
|
|
||||||
Ethan Jezek,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"I have been developing an AI integrated app called OceanID that helps users identify marine species (vegetation, algae, and animals) by uploading photographs. By doing so, and by providing key and exciting information to users, I have ambitions of improving and better establishing community education and outreach, as well as marine networking in communities around the globe. Upon identifying an organism, users are presented with key ecological and economical information about the organism they captured on camera, recent publications, distribution, and if the species is currently a foodstuffs, will be presented with recipes, information on how to safely and sustainably harvest, and sustainable producers where a user could buy ingredients for said recipe . For higher level users, e.g. ocean users such as fishers, farmers, and researchers, information on permitting, local processors, producers, and developers is also provided (this information is provided for all users but intended to be helpful and beneficial for higher-level users).
|
|
||||||
|
|
||||||
Other functions on the app include; a database of all species the app has identified, a community tab that displays the discoveries of nearby and followed users, a map function where users can see community discoveries and the location of permit zones, and key economic players (see above) in relation to their location, and a cookbook that saves all of the recipes that a user has collected.",US,,ejezek12@gmail.com,I heard of the MOPC through colleagues I have on LinkedIN,Consumer awareness and education,,,true,,,OceanID,Ethan Jezek,US,+18178996766,"I have started this concept myself in Dallas, Texas but I am also a PhD candidate at the University of Waikato in New Zealand"
|
|
||||||
Nnaji Samuel Ebube,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"🌟 *Project: OceanFin - Boosting Nigeria's Blue Economy 🌊*
|
|
||||||
- *Idea*: Empower coastal communities with digital financial services for sustainable ocean-based livelihoods 🐟
|
|
||||||
- *Objectives*:
|
|
||||||
- Increase financial services 📈
|
|
||||||
- Improve financial inclusion for fishermen, traders 💸
|
|
||||||
- Promote sustainable ocean practices 🌿
|
|
||||||
- *Key features*: Digital payments, loans, insurance, FX services, international partnerships 🌍",NG,,nnajisamuel2448@gmail.com,Online,Capacity building for coastal communities,,,true,,,OceanFin,"Ifeoma Odusanya, Benjamin Odusanya","Africa, Nigeria",+2348161502448,University of Nigeria Nsukka
|
|
||||||
Sofie Boggio Sella,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"The project develops an AI-driven system to predict where coral reefs are most likely to survive under future climate conditions. By fusing seafloor structure, reef imagery, environmental data, and biodiversity indicators into a single probabilistic model, it moves beyond mapping what exists today to forecasting where restoration and protection will be most effective tomorrow. Its objective is to identify climate-resilient “safe havens” and restoration hotspots, providing actionable, uncertainty-aware maps for scientists and conservation practitioners. This enables smarter allocation of limited resources, transforming coral conservation from reactive damage control into a proactive strategy for long-term reef resilience.",IT,,boggiosellasofie@gmail.com,Linkedln,Restoration of marine habitats & ecosystems,,,true,,,PMRF: Probabilistic Multi Reef Fusion pipeline,"Sofie Boggio Sella, Lily Lewis, Mohammad Jahanbakht","Europe, Italia",+61448568796,James Cook University Australia
|
|
||||||
Christine Kurz,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,,,,christine.a.kurz@gmail.com,,,,,,,,Xy,Xy,,+4917622904612,
|
|
||||||
Antonella Bongiovanni,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"EVE Biofactory is a deep-biotech company leveraging microalgae to build the most scalable nano drug-delivery platform on the market.
|
|
||||||
Inefficient drug delivery causes treatment failure, patient harm, and up to $40B in annual losses from underperforming bioactives.
|
|
||||||
Inspired by the smallest ocean organisms, EVE develops Nanoalgosomes: naturally occurring exosomes produced from microalgae, the only delivery system that is scalable, circular, and fully biological.
|
|
||||||
Nanoalgosomes are cost-competitive, biologically active, and more efficient than synthetic nanoparticles, enabling lower drug doses and reducing the release of medicines and persistent nanomaterials into wastewater that today impact river and ocean ecosystems.",IT,2022-09-29,info@evebiofactory.com,Our mentor Alessandro ROmano pointed out the challenge and recommended our project would be a good fit.,Technology & innovations,,,true,,,EVE Biofactory,Antonella Bongiovanni - Natasa Zarovni - Mauro Manno - Paolo de Stefanis - Lorenzo Sbizzera - Gabriella Pocsfalvi - Paola Gargano,"Europe, Italia",+393286093034,
|
|
||||||
Justyna Grosjean,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"The Antifouling Coating of Tomorrow.
|
|
||||||
Lower Costs. Cleaner Oceans. Decarbonating Shipping.",DE,2021-05-11,justyna@cleanoceancoatings.com,Through the Fondation Prince Albert II de Monaco,Sustainable shipping & yachting,,,true,,,Clean Ocean Coatings GmbH,"Christina Linke, Jens Deppe, Friederike Bartels, Johana Chen, Sandra Lötsch, Patricia Greim","Europe, Germany",+33685638357,
|
|
||||||
Erick Patrick dos Anjos Vilhena,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Sustainable fish leather production is more than just an exotic alternative; it addresses critical issues within the fashion and food industries, as well as the environment.
|
|
||||||
|
|
||||||
Here are the main problems this solution solves:
|
|
||||||
|
|
||||||
1. Waste in the Fishing Industry (Circular Economy)
|
|
||||||
Currently, the vast majority of fish skins resulting from human consumption are discarded as organic waste.
|
|
||||||
|
|
||||||
The Problem: Thousands of tons of skins end up in landfills or are thrown back into rivers and oceans, causing pollution due to excess organic matter.
|
|
||||||
|
|
||||||
The Solution: It transforms a by-product (waste) into a high-value material, closing the loop of the circular economy.
|
|
||||||
|
|
||||||
2. Environmental Impact of Bovine Leather
|
|
||||||
Traditional (cow) leather carries a heavy ecological footprint that fish leather helps to mitigate.
|
|
||||||
|
|
||||||
Deforestation: Cattle ranching is a leading cause of deforestation. Fish production does not require new pastures.
|
|
||||||
|
|
||||||
Water Consumption: Raising cattle consumes massive volumes of water compared to existing aquaculture or artisanal fishing.
|
|
||||||
|
|
||||||
Carbon Emissions: Producing fish leather emits significantly fewer greenhouse gases than the beef industry supply chain.
|
|
||||||
|
|
||||||
3. Toxicity in Processing (Tanning)
|
|
||||||
Industrial tanning of common leathers often uses Chromium, a heavy metal that is highly polluting if disposed of incorrectly.
|
|
||||||
|
|
||||||
The Difference: Sustainable fish leather solutions focus on vegetable tanning (using tannins extracted from tree barks and plants). This eliminates toxic waste and results in a biodegradable product that is safe for both artisans and consumers.
|
|
||||||
|
|
||||||
4. Durability vs. Aesthetics
|
|
||||||
Many leather alternatives (such as ""synthetic leather"" made of plastic/PU) have low durability and pollute the environment with microplastics.
|
|
||||||
|
|
||||||
The Solution: Fish leather has a cross-fiber structure (unlike the parallel fibers in bovine leather), making it extremely strong and tear-resistant despite being thin. It solves the dilemma for those seeking a material that is delicate, durable, and eco-",BR,2023-01-01,e.vilhena@hotmail.com,Linkedln,Sustainable fishing and aquaculture & blue food,,,true,,,sustainable fish leather,Andria Carrilho,South America,+5596981337237,
|
|
||||||
Amaia Rodriguez,Received,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,Clean plastic from the sea with fishermen and transform the waste into materials for construction and architecture.,ES,2020-05-18,amaia@thegravitywave.com,A friend sent it to me,Reduction of pollution (plastics chemicals noise light...),,,true,https://drive.google.com/drive/folders/11McKvPyKzbUgYiFd2gfeWvLrlGGPhyP2?usp=sharing,,GRAVITY WAVE,"Amaia Rodriguez, Julen Rodriguez, Naiara Lopez, Alvaro Garcia, Camila Lago, Norberto De Rodrigo, Irene Hurtado","Europe, Spain",+34606655862,
|
|
||||||
Dr Mumthas Yahiya,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"“Nature-Based Solutions for Mitigating Ocean Acidification through Coastal Blue Carbon Ecosystems - Project Idea
|
|
||||||
|
|
||||||
This project focuses on mitigating ocean acidification by enhancing and restoring blue carbon ecosystems such as mangroves, seagrasses, and salt marshes. These ecosystems absorb atmospheric CO₂, increase local alkalinity, and act as natural buffers against pH reduction in coastal waters. The study will evaluate their potential as cost-effective, climate-resilient mitigation strategies.
|
|
||||||
|
|
||||||
Objectives
|
|
||||||
|
|
||||||
To assess the role of mangroves and seagrass meadows in reducing coastal seawater acidity.
|
|
||||||
|
|
||||||
To quantify carbon sequestration and alkalinity enhancement in selected coastal habitats.
|
|
||||||
|
|
||||||
To evaluate ecosystem-based management practices as mitigation tools for ocean acidification.
|
|
||||||
|
|
||||||
To provide policy-relevant recommendations for integrating blue carbon ecosystems into coastal climate action plans.
|
|
||||||
|
|
||||||
Relevance
|
|
||||||
|
|
||||||
The project supports climate change mitigation, marine biodiversity conservation, and sustainable coastal management while addressing the growing threat of ocean acidification.",,,mumthasy@gmail.com,IUCN,Mitigation of ocean acidification,,,true,,,Migratory birds,Thamanna K,US,+917012789400,Kerala
|
|
||||||
yvano voigt,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"Reducing plastic waste
|
|
||||||
|
|
||||||
Reducing skin cancer rates
|
|
||||||
|
|
||||||
Reducing coral reef destruction",FR,,yvano.voigt@gmail.com,thanks to the oceanography museum,Reduction of pollution (plastics chemicals noise light...),,,true,,,Totem by FrenchKiss suncare,"yvano voigt, Elsa Delpace","Europe, France",+33652294558,"Ipag business school, Nice France"
|
|
||||||
Lily Atussa Payton,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"Oyster Club NYC is a fledgling organization aimed at giving New Yorkers a hands-on connection to their maritime past, present, and future through the lens of ocean sustainability. Rending New Yorkers that NYC is truly their oyster, and that some of the strongest communities are built around the smallest of creatures. Specifically, we create bespoke events aimed at bringing together people to create community, discuss how making small choices can benefit our oceans, such as eating oysters, all while having fun in the process. This has manifested in a Learn-to-Shuck Holiday Party in December and a monthly oyster happy hour at various locations across the city. Our specific objectives are threefold:
|
|
||||||
- use oysters as a catalyst to expose New Yorkers to sustainable and regenerative food in a social environment,
|
|
||||||
- embed an oceans-focused mindset into an island city that often forgets its connection to the water, and
|
|
||||||
- build a climate-minded community across the five boroughs.",US,,lily.a.payton@gmail.com,Online research,Consumer awareness and education,,,true,,,Oyster Club NYC,"Lily Payton, Kelsey Burkin, Savannah Harker",US,+13015297789,N/A
|
|
||||||
Gary Molano,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"We breed better seaweed using genomic breeding techniques. We started with kelp, and have achieved 4fold harvestable yield gains in 5 years of breeding, a 10x speed advancement compared to breeding efforts in Asia. We are currently targeting traits that increase the value of seaweed, such as lower iodine and higher bioactive composition (fucoidan, alginate, laminarin, etc), to help make farmed kelp more competitive with wild harvests. We also have a breeding scheme that produces ""sterile"" kelp to protect local ecosystems from farmed kelp. This sterile kelp is produced using non-GMO techniques.",US,2023-07-19,gary@macrobreed.com,Through the ocean exchange newsletter,Sustainable fishing and aquaculture & blue food,,,true,,,MacroBreed,"Scott Lindell, Charles Yarish, Filipe Alberto",US,+12135198233,
|
|
||||||
Qendresa Krasniqi,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"We are developing a specialized ROV designed to restore marine ecosystems. Coastal ecosystems, such as the Oslofjord, are currently threatened by invasive species and marine debris. Specifically, the Pacific oyster is spreading rapidly, requiring efficient and noninvasive methods for removal to protect local biodiversity. Our current prototype is part of a joint venture with 'Matfat Oslofjorden,' where it will harvest invasive oysters from the Oslo Fjord to be repurposed as a sustainable food source. Our solution is efficient, non-invasive, and fully programmable for diverse oceanic habitats and tasks. Navier USN is not starting from scratch with a proven track record in developing autonomous surface vehicles (ASVs), our startup concept expands this expertise into the underwater domain. We are a seasoned technical and commercial team with a proven track record in autonomous maritime technology, including multiple world championship titles. Supported by prominent industry partners, we have the proven competence and scale to transform maritime environmental management",NO,2022-10-06,qendresa04@gmail.com,1000 Ocean StartUps,Restoration of marine habitats & ecosystems,,,true,,,Aegir by Navier USN,"Qendresa Krasniqi, Hedda Collin, Markus Marstad","Europe, Norway",+4798474602,
|
|
||||||
Dorra Fadhloun,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,PlastiTrack's goal is to turn citizen smartphone photos into citywide microplastic pollution heatmaps that municipalities use to prioritize cleanup investments.,TN,,dorra.fadhloun@msb.tn,LinkedIn,Technology & innovations,,,true,,,Oceani,Samar,"Africa, Tunisia",+21629508048,Mediterranean School of Business
|
|
||||||
Ahamed Adhnaf,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"AquaHorizon is a holistic ocean innovation hub that transforms ocean challenges into solutions while empowering coastal communities. Our objectives are to develop sustainable practices for marine conservation, reduce pollution, provide education and capacity building for coastal populations, and create a collaborative space where innovators can design and implement solutions for a healthier ocean and thriving communities.",LK,,anaadhnaf413@gmail.com,I learned about the Monaco Ocean Protection Challenge through a friend.,Capacity building for coastal communities,,,true,,,AquaHorizon,"Ahamed Adhnaf , Kaveesha Gunarathna, Mohammed Rifath","Africa, Sri Lanka",+94760270097,National Institute of Social Development - Sri Lanka
|
|
||||||
Robert Kunzmann,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"According to the UN, only 9% of 8.3 billion tons of plastic waste have been recycled over the past 65 years.
|
|
||||||
Most of the plastic waste ends up burned, burried or in the oceans. This profound impact on our environment is not fully understood yet, but micro plastics have been found in all the way from glaciers, to human placentas.
|
|
||||||
Today, recycling is not economically feasible in most situations. There are several reasons for this. Firstly, many recycling methods cannot handle mixed waste and therefore require waste to be sorted. Where chemical recycling can accept mixed plastic, the high temperature or pressure requirements lead to high costs. This is why recycling rates remain low. Plastalyst makes it possible to break down waste into core chemicals such as monomers or hydrogen and carbon monoxide (syngas for SAF and biodiesel).
|
|
||||||
Organic waste is decomposed into alcohols and syngas, whereas plastic is decomposed into methanol, alkanes or monomers. It uses only water, waste, and a reusable catalyst as input. The reaction occurred at a temperature of only 200°C. Compared to other methods, our method has significant advantages such as low energy use, which results in lower operational cost. Next, we use water as a solvent, drying of waste is not needed, therefore it will cut the cost of preparing the material. Lastly, no solvent is needed and no CO2 is emitted in the reaction. Unlike organic methods, such as biodigestion that require a lot of time and emit a lot of CO2, Plastalyst is a fast chemical method that emits no CO2.",LU,2019-04-01,robert.kunzmann@acbiode.com,Climate KIC,Reduction of pollution (plastics chemicals noise light...),,,true,,,AC Biode,Robert Kunzmann,"Europe, Luxembourg",+441751026862,
|
|
||||||
Mayoro MBAYE,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"The MER SEA GUEDJI center designated according to the "" Educational Archipelago"" concept ,composed of self- contained educational modules connected by landscaped pathways . This lightweight,reversible,and bioclimatic architecture respect the public martime domain integrates harmoniously into the natural and social environment .",SN,,dg@kma-international.com,Dr Manon Aminatou,Capacity building for coastal communities,,,true,,,MER SEA GUEDJI,Mayoro MBAYE +Mbacké SECK+Ali DOUCOURÈ,"Africa, Senegal",+221776441916,
|
|
||||||
Divin Arnaud KOUEBATOUKA,Received,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Our project transforms invasive water hyacinth, a major threat to rivers, coastal lagoons and marine ecosystems, into 100% organic absorbent solutions used to control oil and chemical pollution.
|
|
||||||
By harvesting water hyacinth before it reaches estuaries and coastal zones, we prevent ecosystem degradation while supplying industries and ports with sustainable spill-response materials.
|
|
||||||
Our flagship product, KUKIA®, absorbs hydrocarbons efficiently and is later recycled into alternative fuel for cement plants, creating a circular, zero-waste model.
|
|
||||||
The project combines ocean and freshwater protection, industrial pollution control, and community empowerment, generating income for women-led harvesting groups while reducing marine contamination risks.
|
|
||||||
This scalable solution contributes directly to ocean conservation, blue economy resilience, and sustainable industrial practices in Africa and beyond.",CG,2022-07-27,divinkoueba@gmail.com,"I learned about the Monaco Ocean Protection Challenge through professional networks and sustainability-focused opportunity monitoring platforms, including LinkedIn and grant-funding communities dedicated to ocean and climate innovation.",Restoration of marine habitats & ecosystems,,,false,https://drive.google.com/drive/folders/1uHEFuI-iosKap2OPSUdQsbXuih07g7n0?usp=drive_link,,Green Tech Africa,"Our lean startup is primarily run by a team of 3: Divin, Osvaldo, and Jessica, alongside a great supportive team of advisors. Using his skills in tech and as a civil & environmental engineer, Divin has designed and built several innovations geared towards sustainability, including the solar dryer now being used across Congo to revive the pyrethrum industry, smart roads, and smart pipes. The Central Africa Community recently awarded him the best innovator in Congo. He is the CEO. Working for L'Oréal, Osvaldo is well-versed in manufacturing and running supply chain processes. This experience, in addition to being an engineer, makes him an ideal CTO. Jessica has an extensive background in tech and finance. She has been a Microsoft Ambassador and Hult Prize coordinator. Her experience in handling projects and relationships with global agencies and customers is handy in her CFO role. She also hails from Homabay, Congo, where hyacinth surrounds the island for days and weeks at times. This blocks waterways and sometimes prevents children from going to school. The team has achieved several milestones, such as making scientific validation and proof of concept of the products, raising over CFA 3M in funding, and winning significant international awards, including the Central Africa Youth for Climate Action Award, Best Manufacturing Startup in Congo, Best Innovation in Congo by EAC, the World Engineering Day Hackathon by UNESCO, the TotalEnergies Startup of the Year, and Falling Walls Lab Brazzaville.","Africa, Congo",+242069323235,
|
|
||||||
Paul Schmitzberger,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"We decouple the production of marine protein from the ocean by replicating aquatic ecosystems in modular, automated, plug-and-play systems. We combine hardware, software and biology in products called LARA and Vortex. These modular units are controlled and operated by AI agents under the remote supervision of our biologists and system engineers. The agents optimize the growth of microalgae, zooplankton and fish or shrimps for human consumption. Our goal is to establish large-scale, environmentally friendly aquaculture operations in diverse environments, advancing global food security and sustainability.",AT,2019-11-15,laura@blue-planet-ecosystems.com,Online search,Sustainable fishing and aquaculture & blue food,,,false,,,Blue Planet Ecosystems,"Paul Schmitzberger, Cécile Deterre, Stephan Mayrhofer, Jens Cormier, Pierre De Villiers, Stephan Sergides, Jakob Weber, Romana Zabojnikova, Laura Belz","Austria, Europe",+436642347890,
|
|
||||||
Julia Denkmayr,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"NEREIA develops non-toxic antifouling technology using functional surfaces to prevent biofouling on ship hulls, reducing drag and improving fuel efficiency. Eliminating hull fouling could save the shipping industry up to $30bn in fuel costs and 200 Mt of CO2 annually.",AT,2026-04-30,julia@nereia-coatings.com,"Through a Carbon 13 domain expert, Thibaut Monfort Micheo, as well as LinkedIn and other social media.",Sustainable shipping & yachting,,,false,,,NEREIA,Rimah Darawish,"Austria, Europe",+393203476632,
|
|
||||||
Tara Lepine,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"OceanSeed deploys mobile hatchery units as emergency response infrastructure to restore marine ecosystems and fisheries after collapse, often caused by climate change. Its objectives are to rapidly produce native juvenile shellfish, rebuild ecosystems and keystone populations, protect biodiversity, and support local livelihoods. OceanSeed can be scaled globally through a network of mobile hatcheries, using aquaculture as a tool for conservation and climate adaptation.",CA,,tlep171@aucklanduni.ac.nz,Communication from our university department head.,Sustainable fishing and aquaculture & blue food,,,false,,,OceanSeed,Tara Lepine,Canada,+64273401929,"University of Auckland, Auckland, New Zealand"
|
|
||||||
Reid Barnett,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Our project is using our proprietary technology to grow plants directly on the surface of natural and manmade surface waters. This allows us to sequester carbon, nutirents, and other chemical pollutants at scale in both ocean systems and the freshwater systems upstream. After the plants are fully grown the entire system, material and biomass, can be pyrolyzed to generate carbon negative energy and lock carbon away in a stable form. This process is highly efficient and extremely inexpensive. When we pyrolyze the system we generate over three times more revenue than the total cost of the system and its operation, while opening up opportunites for blue carbon credits and creating bio-oil which can be refined for various uses.
|
|
||||||
This project is about creating an entirely new pathway for pollutants in the environment to redirect them from where they cause harm and towards where they can generate value and do good.",US,2024-05-01,reidbarnett@ceretunellc.com,We were connected through the team at Ocean Exchange.,Reduction of pollution (plastics chemicals noise light...),,,true,,,Ceretune LLC,Reid Barnett and Blake Parrish,US,+19198010336,
|
|
||||||
Ryan Borotra,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"Sentry Labs is developing graphene field-effect transistor (GFET)–based molecular sensors for sustainable fishing, aquaculture, and blue food systems. The project focuses on real-time, in-situ detection of biologically and chemically relevant signals in seawater to enable earlier identification of environmental and biological risks affecting farmed and wild stocks. Our objective is to provide robust, reproducible sensing systems that support healthier stocks, reduced losses, and more sustainable management of marine food production.",CA,2025-10-20,ryan@sentrylabs.cc,LinkedIn,Sustainable fishing and aquaculture & blue food,,,true,,,Sentry Labs,"Ryan Borotra, Martin Chaperot, Andrei Bogza",Canada,+16479659526,
|
|
||||||
Nadine Hakim,,the « Start-ups » category: Open to students fresh graduates & entrepreneurs with an existing comp,"SEAMOSS is a sustainable biodesign and coastal livelihood project focused on the cultivation and transformation of sea moss (marine macroalgae) as a nature-based solution to environmental and social challenges in coastal communities. The project combines regenerative aquaculture, biomaterial development, and community-led value chains to reduce pressure on marine ecosystems while creating local economic opportunities.
|
|
||||||
|
|
||||||
The core idea is to cultivate native sea moss species using low-impact, regenerative methods and transform the biomass into biodegradable materials and functional products that can replace plastic-based alternatives, particularly in packaging, design, and everyday consumer goods.",CO,2025-10-01,nadinehakimm@gmail.com,,Sustainable fishing and aquaculture & blue food,,,true,,,SEAMOSS COLOMBIA,Sandra Bessudo and Irene Arroyave,South America,+573205421979,
|
|
||||||
Maria Ester Faiella,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"ThermoShield is a modular underwater panel system that passively reduces local heat from coastal infrastructure. Its objective is to prevent thermal stress on sensitive marine ecosystems, protecting coral reefs and seagrass worldwide. The panels are easy to install, require no electricity and provide measurable local temperature reductions of 0.3–0.5°C, making the solution scalable and globally applicable.",IT,,maria.ester.faiella@gmail.com,LinkedIn,Restoration of marine habitats & ecosystems,,,true,,,ThermoShield,Maria Ester Faiella,"Europe, Italia",+393311538952,The American University of Rome
|
|
||||||
Kumari Anushka,,the « Business concepts » category: open to students (Bachelor Master MBA & PhD) & fresh graduates,"Coral reefs are collapsing - rising ocean temperatures have triggered mass bleaching, 84.6% of corals in Lakshadweep bleached recently. India has 1,439 km² of mapped coral reefs, coasts have 80 ± 33 microplastic particles per cubic meter, and ~30% of sampled market fish have microplastics. Odisha’s Bay of Bengal estuaries have elevated metal concentrations.
|
|
||||||
|
|
||||||
Each Reef Revival Pod is a solar-powered floating buoy deployed near degraded reefs with:
|
|
||||||
1. Underwater acoustics: healthy reefs produce sounds that can be played near dying reefs to attract marine life back to them. In trials, degraded patches with reef sounds saw fish population double.
|
|
||||||
2. Each pod pumps the surrounding seawater through fine filters to capture microplastic debris.
|
|
||||||
3. Water is also pumped through replaceable resin-based adsorption cartridges to bind with dissolved heavy metals in the water.
|
|
||||||
4. Onboard sensors log water quality (temperature, pH, turbidity, etc.) - collecting data for adaptive management.
|
|
||||||
|
|
||||||
After success in India’s waters, the project will be expanded to coral regions globally.
|
|
||||||
|
|
||||||
In India, the CRZ notification 2019 classifies coral reefs as ecologically sensitive (CRZ-I A) and regulates activities in coastal waters (CRZ-IV), so my revival pods should be permitted as non-invasive research/restoration infrastructure (no reef anchoring and removable).
|
|
||||||
|
|
||||||
The MoEFCC National Coastal Mission Scheme funds coral/mangrove conservation action plans, marine & coastal R&D - this would help with scaling the number of buoys deployed.
|
|
||||||
|
|
||||||
Also, the World Bank-supported Integrated Coastal Zone Management (ICZM) gives importance to science-based coastal planning; pods’ sensor data could be used for threat mapping and adaptive management in the deployed zones.
|
|
||||||
|
|
||||||
For global scaling: Australia’s Reef 2050 Plan, Indonesia’s COREMAP, and the US NOAA Coral Reef Conservation Program exist, so the project could plug into existing national funding priorities across eligible countries.",IN,,nasabutbetter@gmail.com,My university's professor,Restoration of marine habitats & ecosystems,,,true,,,accore,Kumari Anushka,Asia,+919798061093,Ashoka University
|
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
# Mixed Round Design Implementation Docs
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
This folder contains a single consolidated redesign program that intentionally blends:
|
||||||
|
|
||||||
|
- Delivery rigor and governance discipline from `codex-round-system-redesign`
|
||||||
|
- Target architecture depth and runtime detail from `claude-round-system-redesign`
|
||||||
|
- Award governance semantics from `glm-5-round-redesign` (especially `AWARD_MASTER` and explicit decision modes)
|
||||||
|
|
||||||
|
The goal is a complete, production-ready implementation plan for rebuilding round orchestration in MOPC with a full-cutover model.
|
||||||
|
|
||||||
|
## Foundation and Blend Strategy
|
||||||
|
|
||||||
|
### Foundation
|
||||||
|
The execution backbone is the `codex` style program model:
|
||||||
|
|
||||||
|
1. Contract freeze first
|
||||||
|
2. Schema/runtime implementation in explicit phases
|
||||||
|
3. Platform-wide dependency refit (not just feature slices)
|
||||||
|
4. Mandatory phase gates with hard release blockers
|
||||||
|
|
||||||
|
### Borrowed Enhancements
|
||||||
|
The plan imports high-value details from other proposals:
|
||||||
|
|
||||||
|
- `claude`: richer canonical model (`Pipeline -> Track -> Stage`), explicit transition engine, routing and live-control runtime detail
|
||||||
|
- `glm-5`: award decision governance (`JURY_VOTE`, `AWARD_MASTER`, `ADMIN`) and explicit award track behavior options
|
||||||
|
|
||||||
|
## Execution Model
|
||||||
|
|
||||||
|
- Single destructive cutover
|
||||||
|
- Full reseed
|
||||||
|
- No backward-compatibility adapter layer
|
||||||
|
- No dual-write period
|
||||||
|
- One atomic release commit once all gates are green
|
||||||
|
|
||||||
|
This model is intentionally selected because infrastructure reset/rebuild is allowed and preferred for architecture quality.
|
||||||
|
|
||||||
|
## Architecture Summary
|
||||||
|
|
||||||
|
- Competition lifecycle is stage-native, not round-pointer native.
|
||||||
|
- Projects progress through explicit `ProjectStageState` records.
|
||||||
|
- Special awards are first-class tracks, not bolt-on side tables.
|
||||||
|
- Routing is rule-driven with explainability payloads.
|
||||||
|
- Live finals are controlled by an admin cursor as the source of truth.
|
||||||
|
- Every override and decision is reasoned, immutable, and auditable.
|
||||||
|
|
||||||
|
## Folder Layout
|
||||||
|
|
||||||
|
- `master-implementation-plan.md`: end-to-end execution map
|
||||||
|
- `shared/`: cross-phase contracts, governance, test model, risks
|
||||||
|
- `phase-00-contract-freeze/` to `phase-07-validation-release/`: implementation phases
|
||||||
|
- `flowcharts/`: core control and routing diagrams
|
||||||
|
|
||||||
|
## How to Use This Plan
|
||||||
|
|
||||||
|
1. Start at `master-implementation-plan.md`.
|
||||||
|
2. Execute phases in order.
|
||||||
|
3. Do not start a phase unless all prior acceptance gates are complete.
|
||||||
|
4. Attach objective evidence for every gate.
|
||||||
|
5. Treat `phase-06-platform-dependency-refit` as mandatory release work, not cleanup.
|
||||||
|
|
||||||
|
## Non-Negotiable Rules
|
||||||
|
|
||||||
|
1. No hidden edit-only required settings.
|
||||||
|
2. Deterministic routing and ranking tie-break behavior.
|
||||||
|
3. Assignment coverage guarantees for eligible projects.
|
||||||
|
4. Explicit voting window control (schedules are advisory only).
|
||||||
|
5. No legacy orchestration contract references at release.
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Dependency Refit Map
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A[Schema Contracts] --> B[Router Refit]
|
||||||
|
A --> C[Service Refit]
|
||||||
|
B --> D[Admin UI Refit]
|
||||||
|
B --> E[Jury/Applicant/Public Refit]
|
||||||
|
C --> E
|
||||||
|
D --> F[Reporting/Exports]
|
||||||
|
E --> F
|
||||||
|
F --> G[Integration Consumer Validation]
|
||||||
|
G --> H[Legacy Symbol Sweep]
|
||||||
|
H --> I[Release Ready]
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
# End-to-End Pipeline Flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[Intake Stage] --> B[Filter Stage]
|
||||||
|
B -->|pass| C[Main Evaluation Stage]
|
||||||
|
B -->|reject| R[Rejected with Notification]
|
||||||
|
B -->|award rule: parallel| W1[Award Track Entry]
|
||||||
|
B -->|award rule: exclusive| W2[Award Track Entry + Main Routed Out]
|
||||||
|
|
||||||
|
C --> D[Selection Stage]
|
||||||
|
D --> E[Live Final Stage]
|
||||||
|
E --> F[Results Stage]
|
||||||
|
|
||||||
|
W1 --> W3[Award Evaluation]
|
||||||
|
W2 --> W3[Award Evaluation]
|
||||||
|
W3 --> W4[Award Winner Decision]
|
||||||
|
|
||||||
|
D -->|manual override| O[Override Action + Audit]
|
||||||
|
O --> D
|
||||||
|
|
||||||
|
E --> L[Live Cursor + Cohort Windows]
|
||||||
|
L --> V[Jury and Audience Voting]
|
||||||
|
V --> F
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Live Stage Controller
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[Admin Live Panel] --> B[Set Active Project Cursor]
|
||||||
|
B --> C[Persist Cursor Versioned Update]
|
||||||
|
C --> D[Broadcast Realtime Event]
|
||||||
|
D --> E[Jury Clients Sync]
|
||||||
|
D --> F[Audience Clients Sync]
|
||||||
|
|
||||||
|
A --> G[Open Cohort Window]
|
||||||
|
A --> H[Close Cohort Window]
|
||||||
|
G --> I[Vote Acceptance On]
|
||||||
|
H --> J[Vote Acceptance Off]
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Main vs Award Routing
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
P[Project in Filter Stage] --> Q{Routing Rule Match?}
|
||||||
|
Q -->|No| M[Remain in Main Track]
|
||||||
|
Q -->|Yes| Z{Routing Mode}
|
||||||
|
|
||||||
|
Z -->|PARALLEL| A[Create Award Stage State]
|
||||||
|
A --> B[Keep Main State Active]
|
||||||
|
|
||||||
|
Z -->|EXCLUSIVE| C[Create Award Stage State]
|
||||||
|
C --> D[Mark Main State Routed]
|
||||||
|
|
||||||
|
Z -->|POST_MAIN| E[Defer Route Until Gate Stage]
|
||||||
|
E --> F[Route After Main Gate Condition]
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Override Audit Flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[Override Request] --> B{Authz + Scope Check}
|
||||||
|
B -->|fail| X[FORBIDDEN]
|
||||||
|
B -->|pass| C{Reason Fields Present?}
|
||||||
|
C -->|no| Y[BAD_REQUEST]
|
||||||
|
C -->|yes| D[Fetch Current Value Snapshot]
|
||||||
|
D --> E[Apply Override Mutation]
|
||||||
|
E --> F[Persist Immutable OverrideAction]
|
||||||
|
F --> G[Append DecisionAuditLog]
|
||||||
|
G --> H[Return Updated Entity + Audit Ref]
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
# Master Implementation Plan
|
||||||
|
|
||||||
|
## Program Objective
|
||||||
|
Rebuild MOPC round orchestration from a round-centric model into a stage-native pipeline model that is easier to configure, more deterministic, and robust for main competition plus special awards and live finals.
|
||||||
|
|
||||||
|
## Program Constraints
|
||||||
|
|
||||||
|
- Preserve existing visual language and core UI component style.
|
||||||
|
- Complete architecture rebuild is allowed and encouraged.
|
||||||
|
- Delivery must be production-safe and verifiable.
|
||||||
|
- Release requires one atomic cutover commit after full validation.
|
||||||
|
|
||||||
|
## Hard Invariants
|
||||||
|
|
||||||
|
1. Every state transition is explicit, validated, and auditable.
|
||||||
|
2. Every override action captures `reasonCode` + `reasonText` + actor metadata.
|
||||||
|
3. No eligible project is left unassigned unless explicitly flagged as overflow with admin visibility.
|
||||||
|
4. Live active project state is admin-cursor driven.
|
||||||
|
5. Award routing behavior is explicit per award (`parallel`, `exclusive`, `post_main`).
|
||||||
|
6. Event contracts are deterministic and machine-readable.
|
||||||
|
7. At release, no runtime dependency on legacy `roundId` orchestration semantics remains.
|
||||||
|
|
||||||
|
## Phase Chain
|
||||||
|
|
||||||
|
1. Phase 00: Contract freeze
|
||||||
|
2. Phase 01: Schema and runtime foundation
|
||||||
|
3. Phase 02: Backend orchestration engine
|
||||||
|
4. Phase 03: Admin control-plane UX
|
||||||
|
5. Phase 04: Participant journeys
|
||||||
|
6. Phase 05: Special awards governance
|
||||||
|
7. Phase 06: Platform dependency refit
|
||||||
|
8. Phase 07: Validation and release
|
||||||
|
|
||||||
|
## Required Deliverables by Phase
|
||||||
|
|
||||||
|
- Phase 00: locked contracts, decision log, authz matrix, initial risk register
|
||||||
|
- Phase 01: canonical schema spec, migration/cutover scripts, reseed spec, integrity checks
|
||||||
|
- Phase 02: transition/routing/filtering/assignment/live runtime implementation specs
|
||||||
|
- Phase 03: wizard IA, advanced editor spec, form behavior and safety guardrails
|
||||||
|
- Phase 04: applicant/jury/audience runtime and UX contracts
|
||||||
|
- Phase 05: award governance modes and decision workflow implementation
|
||||||
|
- Phase 06: module-by-module refit completion + legacy symbol sweeps
|
||||||
|
- Phase 07: full test evidence, performance evidence, release runbook and sign-off
|
||||||
|
|
||||||
|
## Entry and Exit Criteria (Program Level)
|
||||||
|
|
||||||
|
### Entry
|
||||||
|
|
||||||
|
- Shared contracts and decisions are locked.
|
||||||
|
- Team alignment on cutover model and no-compatibility policy.
|
||||||
|
|
||||||
|
### Exit
|
||||||
|
|
||||||
|
- All phase acceptance gates complete.
|
||||||
|
- Test matrix green for U/I/E/P suites.
|
||||||
|
- Performance and resilience evidence approved.
|
||||||
|
- Legacy symbol sweeps are empty.
|
||||||
|
- Release evidence report signed by Engineering + Product + Operations.
|
||||||
|
|
||||||
|
## Release Blockers
|
||||||
|
|
||||||
|
1. Any failing acceptance gate.
|
||||||
|
2. Any unresolved CRITICAL or HIGH risk without approved mitigation.
|
||||||
|
3. Any missing test evidence for mandatory scenario IDs.
|
||||||
|
4. Any legacy orchestration symbol found in runtime code paths.
|
||||||
|
|
||||||
|
## Timeline Model
|
||||||
|
|
||||||
|
- Phase 00: 2-3 days
|
||||||
|
- Phase 01: 1-1.5 weeks
|
||||||
|
- Phase 02: 1.5-2.5 weeks
|
||||||
|
- Phase 03: 1-1.5 weeks
|
||||||
|
- Phase 04: 1-1.5 weeks
|
||||||
|
- Phase 05: 0.75-1.25 weeks
|
||||||
|
- Phase 06: 1-1.5 weeks
|
||||||
|
- Phase 07: 1 week
|
||||||
|
|
||||||
|
Total estimate: 8-11 weeks depending on test depth and refit complexity.
|
||||||
|
|
||||||
|
## Evidence Standards
|
||||||
|
|
||||||
|
Every acceptance gate requires at least one of:
|
||||||
|
|
||||||
|
- Unit/integration/E2E output
|
||||||
|
- API response captures
|
||||||
|
- deterministic symbol sweeps
|
||||||
|
- migration integrity query output
|
||||||
|
- performance benchmark output
|
||||||
|
- release runbook logs
|
||||||
|
|
||||||
|
## Enforcement Notes
|
||||||
|
|
||||||
|
- No phase skipping.
|
||||||
|
- No deferred blocker carry-forward.
|
||||||
|
- No "ship and patch later" for contract-level gaps.
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Phase 00 Acceptance Gates
|
||||||
|
|
||||||
|
- [ ] G-00-1 Decision log locked (`shared/decision-log.md` signed by Eng + Product)
|
||||||
|
- [ ] G-00-2 Domain and API contracts approved
|
||||||
|
- [ ] G-00-3 Authz matrix approved
|
||||||
|
- [ ] G-00-4 Test matrix approved and mapped to owners
|
||||||
|
- [ ] G-00-5 Risk register initialized with owners and mitigation targets
|
||||||
|
|
||||||
|
## Required Evidence
|
||||||
|
|
||||||
|
- contract review notes
|
||||||
|
- sign-off comments or approval records
|
||||||
|
- updated risk register with owners
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Phase 00 Overview: Contract Freeze
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
Lock all cross-phase contracts before implementation so the program executes with stable boundaries and no semantic drift.
|
||||||
|
|
||||||
|
## In Scope
|
||||||
|
|
||||||
|
- decision locking
|
||||||
|
- API/type contract baseline
|
||||||
|
- authorization baseline
|
||||||
|
- gate and evidence baseline
|
||||||
|
- initial risk baseline
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- schema implementation
|
||||||
|
- runtime implementation
|
||||||
|
- UI implementation
|
||||||
|
|
||||||
|
## Inputs
|
||||||
|
|
||||||
|
- `shared/program-charter.md`
|
||||||
|
- `shared/decision-log.md`
|
||||||
|
- `shared/domain-model.md`
|
||||||
|
- `shared/api-contracts.md`
|
||||||
|
- `shared/authz-matrix.md`
|
||||||
|
- `shared/test-matrix.md`
|
||||||
|
|
||||||
|
## Exit Criteria
|
||||||
|
|
||||||
|
1. Decision log marked locked with no unresolved critical decision.
|
||||||
|
2. API/type contracts accepted by backend and frontend owners.
|
||||||
|
3. Authz matrix accepted by security owner.
|
||||||
|
4. Risk register initialized with owners.
|
||||||
|
5. Phase 00 acceptance gates complete with evidence.
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Phase 00 Tasks
|
||||||
|
|
||||||
|
## Task Set A: Contract Alignment
|
||||||
|
|
||||||
|
- [ ] Validate `shared/domain-model.md` against current repository constraints.
|
||||||
|
- [ ] Validate `shared/api-contracts.md` names and payload conventions with router ownership.
|
||||||
|
- [ ] Validate event naming strategy with notification and webhook owners.
|
||||||
|
|
||||||
|
## Task Set B: Governance Lock
|
||||||
|
|
||||||
|
- [ ] Confirm `shared/decision-log.md` with Product + Engineering.
|
||||||
|
- [ ] Confirm cutover/no-compatibility policy in writing.
|
||||||
|
- [ ] Confirm override governance requirements and mandatory reason fields.
|
||||||
|
|
||||||
|
## Task Set C: Access and Security
|
||||||
|
|
||||||
|
- [ ] Validate `shared/authz-matrix.md` for each role.
|
||||||
|
- [ ] Define scope enforcement standard for program-scoped admin actions.
|
||||||
|
- [ ] Confirm audience vote abuse controls (token, rate-limit, dedupe key).
|
||||||
|
|
||||||
|
## Task Set D: Validation Baseline
|
||||||
|
|
||||||
|
- [ ] Validate `shared/test-matrix.md` coverage and practicality.
|
||||||
|
- [ ] Map each test ID to ownership.
|
||||||
|
- [ ] Confirm CI entry strategy for U/I/E/P layers.
|
||||||
|
|
||||||
|
## Task Set E: Risk Baseline
|
||||||
|
|
||||||
|
- [ ] Review `shared/risk-register.md` with owners.
|
||||||
|
- [ ] Add any repository-specific risks identified during contract review.
|
||||||
|
- [ ] Mark mitigation action owner and due phase per risk.
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Phase 01 Acceptance Gates
|
||||||
|
|
||||||
|
- [ ] G-01-1 `prisma generate` succeeds
|
||||||
|
- [ ] G-01-2 reset/reseed succeeds in local and staging
|
||||||
|
- [ ] G-01-3 integrity queries return expected zero-error results
|
||||||
|
- [ ] G-01-4 required indexes confirmed in DB metadata
|
||||||
|
- [ ] G-01-5 phase artifacts stored and linked
|
||||||
|
|
||||||
|
## Required Evidence
|
||||||
|
|
||||||
|
- migration command output
|
||||||
|
- reseed logs
|
||||||
|
- integrity query result captures
|
||||||
|
- schema diff summary
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
# Phase 01 Migration and Cutover Plan
|
||||||
|
|
||||||
|
## Strategy
|
||||||
|
Perform architecture rebuild with reset/reseed as the official path.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Finalize schema migration scripts.
|
||||||
|
2. Run local reset/reseed rehearsal.
|
||||||
|
3. Run staging reset/reseed rehearsal.
|
||||||
|
4. Execute integrity verification suite.
|
||||||
|
5. Lock schema contracts and produce baseline snapshot.
|
||||||
|
|
||||||
|
## Verification Script Requirements
|
||||||
|
|
||||||
|
- count checks for canonical entities
|
||||||
|
- FK integrity checks
|
||||||
|
- expected stage graph checks
|
||||||
|
- expected project intake state checks
|
||||||
|
|
||||||
|
## Example Verification Queries
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- orphan project stage states
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM "ProjectStageState" pss
|
||||||
|
LEFT JOIN "Project" p ON p.id = pss."projectId"
|
||||||
|
LEFT JOIN "Stage" s ON s.id = pss."stageId"
|
||||||
|
LEFT JOIN "Track" t ON t.id = pss."trackId"
|
||||||
|
WHERE p.id IS NULL OR s.id IS NULL OR t.id IS NULL;
|
||||||
|
|
||||||
|
-- project intake state coverage
|
||||||
|
SELECT COUNT(DISTINCT p.id) AS projects_without_intake
|
||||||
|
FROM "Project" p
|
||||||
|
LEFT JOIN "ProjectStageState" pss
|
||||||
|
ON pss."projectId" = p.id
|
||||||
|
LEFT JOIN "Stage" s
|
||||||
|
ON s.id = pss."stageId"
|
||||||
|
WHERE s."stageType" = 'INTAKE'
|
||||||
|
AND pss.id IS NULL;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cutover Readiness Artifacts Produced in Phase 01
|
||||||
|
|
||||||
|
- schema migration files
|
||||||
|
- seed scripts
|
||||||
|
- integrity query scripts
|
||||||
|
- reset/reseed execution logs
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Phase 01 Overview: Schema and Runtime Foundation
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
Implement the canonical schema and reset/reseed capability that supports stage-native orchestration with award and live runtime primitives.
|
||||||
|
|
||||||
|
## In Scope
|
||||||
|
|
||||||
|
- prisma schema rebuild for canonical entities
|
||||||
|
- indexes and constraints for hot paths
|
||||||
|
- reset/reseed strategy and scripts
|
||||||
|
- data integrity verification scripts
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- end-user UI behavior
|
||||||
|
- full router refit
|
||||||
|
|
||||||
|
## Key Design Choice
|
||||||
|
|
||||||
|
This phase uses full reset/reseed and does not attempt compatibility bridges.
|
||||||
|
|
||||||
|
## Exit Criteria
|
||||||
|
|
||||||
|
1. Schema compiles and generates client successfully.
|
||||||
|
2. Reset/reseed produces runnable dataset.
|
||||||
|
3. Integrity verification passes for FK/index and state initialization rules.
|
||||||
|
4. Phase 01 gates complete.
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
# Phase 01 Schema Specification
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
Introduce the canonical orchestration entities and remove legacy dependency assumptions around single `roundId` progression.
|
||||||
|
|
||||||
|
## New Canonical Tables
|
||||||
|
|
||||||
|
1. `Pipeline`
|
||||||
|
2. `Track`
|
||||||
|
3. `Stage`
|
||||||
|
4. `StageTransition`
|
||||||
|
5. `ProjectStageState`
|
||||||
|
6. `RoutingRule`
|
||||||
|
7. `Cohort`
|
||||||
|
8. `CohortProject`
|
||||||
|
9. `LiveProgressCursor`
|
||||||
|
10. `NotificationPolicy`
|
||||||
|
11. `OverrideAction`
|
||||||
|
12. `DecisionAuditLog`
|
||||||
|
|
||||||
|
## Award Governance Extensions
|
||||||
|
|
||||||
|
- Add `DecisionMode = JURY_VOTE | AWARD_MASTER | ADMIN`
|
||||||
|
- Add award-scoped governance metadata to award track configs
|
||||||
|
- Add award winner finalization audit event contracts
|
||||||
|
|
||||||
|
## Migration Model
|
||||||
|
|
||||||
|
- Build new schema directly as canonical target.
|
||||||
|
- Keep migration files deterministic and replay-safe.
|
||||||
|
- Do not implement dual-write or compatibility tables.
|
||||||
|
|
||||||
|
## Required Constraints
|
||||||
|
|
||||||
|
1. `trackId + sortOrder` unique in `Stage`
|
||||||
|
2. `projectId + trackId + stageId` unique in `ProjectStageState`
|
||||||
|
3. `fromStageId + toStageId` unique in `StageTransition`
|
||||||
|
4. `cohortId + projectId` unique in `CohortProject`
|
||||||
|
|
||||||
|
## Required Indexes
|
||||||
|
|
||||||
|
- `ProjectStageState(projectId, trackId, state)`
|
||||||
|
- `ProjectStageState(stageId, state)`
|
||||||
|
- `RoutingRule(pipelineId, isActive, priority)`
|
||||||
|
- `StageTransition(fromStageId, priority)`
|
||||||
|
- `DecisionAuditLog(entityType, entityId, createdAt)`
|
||||||
|
- `LiveProgressCursor(stageId, sessionId)`
|
||||||
|
|
||||||
|
## Data Initialization Rules
|
||||||
|
|
||||||
|
- Every seeded project must start with one intake-stage state.
|
||||||
|
- Seed must include main track plus at least two award tracks with different routing modes.
|
||||||
|
- Seed must include representative roles: admins, jury, applicants, observer, audience contexts.
|
||||||
|
|
||||||
|
## Integrity Checks
|
||||||
|
|
||||||
|
- No orphan states.
|
||||||
|
- No invalid transition targets across pipelines.
|
||||||
|
- No duplicate active state rows for same `(project, track, stage)`.
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Phase 01 Tasks
|
||||||
|
|
||||||
|
## Schema Build
|
||||||
|
|
||||||
|
- [ ] Implement canonical entities and enums in `prisma/schema.prisma`.
|
||||||
|
- [ ] Add required constraints and indexes.
|
||||||
|
- [ ] Remove or isolate legacy-only orchestration semantics from canonical paths.
|
||||||
|
|
||||||
|
## Seed and Fixtures
|
||||||
|
|
||||||
|
- [ ] Implement reseed script with realistic data volumes and edge cases.
|
||||||
|
- [ ] Include parallel, exclusive, and post-main award routing seed examples.
|
||||||
|
- [ ] Include live cohort seed data.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- [ ] Implement integrity SQL scripts.
|
||||||
|
- [ ] Implement automated verification command wrapper.
|
||||||
|
- [ ] Record baseline output and attach to gate evidence.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [ ] Update schema change notes.
|
||||||
|
- [ ] Document reset/reseed assumptions.
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Phase 02 Acceptance Gates
|
||||||
|
|
||||||
|
- [ ] G-02-1 transition engine tests pass
|
||||||
|
- [ ] G-02-2 routing determinism tests pass
|
||||||
|
- [ ] G-02-3 filtering policy tests pass
|
||||||
|
- [ ] G-02-4 assignment guarantee tests pass
|
||||||
|
- [ ] G-02-5 live cursor and cohort window tests pass
|
||||||
|
- [ ] G-02-6 override/audit tests pass
|
||||||
|
|
||||||
|
## Required Evidence
|
||||||
|
|
||||||
|
- U/I test output for all mapped IDs
|
||||||
|
- sample API responses for major mutation endpoints
|
||||||
|
- audit payload examples for transition and override flows
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
# Assignment Engine Specification
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
Generate high-quality, fair assignments while guaranteeing eligible project coverage.
|
||||||
|
|
||||||
|
## Inputs
|
||||||
|
|
||||||
|
- stage ID
|
||||||
|
- eligible project set
|
||||||
|
- assignee pool
|
||||||
|
- required reviews per project
|
||||||
|
- assignment strategy config
|
||||||
|
- availability and COI policies
|
||||||
|
|
||||||
|
## Hard Constraints
|
||||||
|
|
||||||
|
1. COI exclusion
|
||||||
|
2. role/status eligibility
|
||||||
|
3. explicit max-load cap
|
||||||
|
4. minimum review floor
|
||||||
|
|
||||||
|
## Soft Scoring Dimensions
|
||||||
|
|
||||||
|
- expertise overlap
|
||||||
|
- bio/project similarity
|
||||||
|
- availability weighting
|
||||||
|
- workload balancing
|
||||||
|
- optional geo diversity
|
||||||
|
- optional prior-familiarity weighting
|
||||||
|
|
||||||
|
## Guarantee Rules
|
||||||
|
|
||||||
|
1. No eligible project left uncovered.
|
||||||
|
2. If capacity insufficient, create overflow assignments with warning markers.
|
||||||
|
3. Preview and execution must match constraints and scoring semantics.
|
||||||
|
|
||||||
|
## Output Contract
|
||||||
|
|
||||||
|
- assigned count
|
||||||
|
- uncovered count (must be zero unless in explicit error mode)
|
||||||
|
- overflow assignment list
|
||||||
|
- conflict skips list
|
||||||
|
- fairness metrics (median load, max load)
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
# Filtering and Routing Specification
|
||||||
|
|
||||||
|
## Filtering Pipeline
|
||||||
|
|
||||||
|
1. deterministic gates
|
||||||
|
2. AI rubric evaluation
|
||||||
|
3. confidence band decisioning
|
||||||
|
4. manual queue resolution
|
||||||
|
|
||||||
|
## Deterministic Gates First Rule
|
||||||
|
|
||||||
|
AI execution is prohibited unless deterministic gates pass.
|
||||||
|
|
||||||
|
## AI Output Contract
|
||||||
|
|
||||||
|
- criteria scores
|
||||||
|
- overall recommendation
|
||||||
|
- confidence
|
||||||
|
- rationale
|
||||||
|
- risk flags
|
||||||
|
|
||||||
|
## Confidence Bands
|
||||||
|
|
||||||
|
- `high`: auto decision path
|
||||||
|
- `medium`: manual queue
|
||||||
|
- `low`: reject or manual based on stage policy
|
||||||
|
|
||||||
|
## Routing Rules
|
||||||
|
|
||||||
|
### Evaluation Order
|
||||||
|
|
||||||
|
1. stage-scoped rules
|
||||||
|
2. track-scoped rules
|
||||||
|
3. global rules
|
||||||
|
4. default fallback
|
||||||
|
|
||||||
|
### Deterministic Tie-Break
|
||||||
|
|
||||||
|
- highest priority wins
|
||||||
|
- if equal, lexical rule ID fallback
|
||||||
|
|
||||||
|
### Explainability Persisted
|
||||||
|
|
||||||
|
Each route persists:
|
||||||
|
|
||||||
|
- matched rule ID
|
||||||
|
- predicate snapshot
|
||||||
|
- mode (`AUTO|MANUAL`)
|
||||||
|
- destination track/stage
|
||||||
|
|
||||||
|
## Award Routing Modes
|
||||||
|
|
||||||
|
- `PARALLEL`: keep main progression and add award state
|
||||||
|
- `EXCLUSIVE`: route out of main progression into award track only
|
||||||
|
- `POST_MAIN`: route only after configured main gate stage
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Live Control Specification
|
||||||
|
|
||||||
|
## Source of Truth
|
||||||
|
Admin cursor state is the single source of truth for active project context during live stages.
|
||||||
|
|
||||||
|
## Core Controls
|
||||||
|
|
||||||
|
- start session
|
||||||
|
- next/previous
|
||||||
|
- jump to project
|
||||||
|
- reorder queue
|
||||||
|
- open/close cohort windows
|
||||||
|
- pause/resume session
|
||||||
|
|
||||||
|
## Runtime Requirements
|
||||||
|
|
||||||
|
1. cursor updates are versioned
|
||||||
|
2. race conditions return `CONFLICT` and require refresh/retry
|
||||||
|
3. real-time propagation to jury and audience clients
|
||||||
|
4. reconnect path converges to current cursor/window state
|
||||||
|
|
||||||
|
## Vote Acceptance Rules
|
||||||
|
|
||||||
|
- stage and cohort windows must be open
|
||||||
|
- dedupe key policy enforced (`session/cohort/project/voter/window`)
|
||||||
|
- closed windows reject submissions deterministically
|
||||||
|
|
||||||
|
## Event Contract
|
||||||
|
|
||||||
|
- `live.cursor.updated`
|
||||||
|
- `cohort.window.changed`
|
||||||
|
- `live.session.state.changed`
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Phase 02 Overview: Backend Orchestration Engine
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
Implement deterministic runtime behavior for stage transitions, routing, filtering, assignment, live cursor control, and notification/audit emission.
|
||||||
|
|
||||||
|
## In Scope
|
||||||
|
|
||||||
|
- transition engine
|
||||||
|
- routing engine
|
||||||
|
- filtering orchestration (gates + AI + manual queue)
|
||||||
|
- assignment orchestration with coverage guarantees
|
||||||
|
- live cursor and cohort window controls
|
||||||
|
- event and audit emission
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- full admin UI and participant UI implementation
|
||||||
|
|
||||||
|
## Exit Criteria
|
||||||
|
|
||||||
|
1. Runtime contracts implemented and integration-tested.
|
||||||
|
2. Determinism and idempotency guarantees proven for critical mutations.
|
||||||
|
3. Mandatory phase gates complete with test evidence.
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
# Stage Engine Specification
|
||||||
|
|
||||||
|
## Responsibilities
|
||||||
|
|
||||||
|
1. Validate transition legality.
|
||||||
|
2. Move project states transactionally.
|
||||||
|
3. Emit transition events and audit entries.
|
||||||
|
4. Enforce concurrency safety.
|
||||||
|
|
||||||
|
## State Machine
|
||||||
|
|
||||||
|
`PENDING -> IN_PROGRESS -> PASSED|REJECTED -> ROUTED -> COMPLETED`
|
||||||
|
|
||||||
|
`WITHDRAWN` is terminal for participant-triggered withdrawal paths.
|
||||||
|
|
||||||
|
## Transition Guards
|
||||||
|
|
||||||
|
- source state row exists and is active
|
||||||
|
- destination stage is active and in same pipeline (unless routing rule applies)
|
||||||
|
- stage window and guard conditions satisfied
|
||||||
|
- no concurrent conflicting transition
|
||||||
|
|
||||||
|
## Mutation Semantics
|
||||||
|
|
||||||
|
- transactional updates per batch slice
|
||||||
|
- optimistic locking/version checks
|
||||||
|
- per-project result collection for partial failure reporting
|
||||||
|
|
||||||
|
## Failure Codes
|
||||||
|
|
||||||
|
- `PRECONDITION_FAILED`: guard not satisfied
|
||||||
|
- `CONFLICT`: state moved after read
|
||||||
|
- `BAD_REQUEST`: invalid transition target
|
||||||
|
|
||||||
|
## Audit Contract
|
||||||
|
|
||||||
|
`eventType = stage.transitioned`
|
||||||
|
|
||||||
|
Payload includes:
|
||||||
|
|
||||||
|
- actor
|
||||||
|
- source stage
|
||||||
|
- destination stage
|
||||||
|
- old/new state
|
||||||
|
- reason/context
|
||||||
|
- timestamp
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Phase 02 Tasks
|
||||||
|
|
||||||
|
## Transition Engine
|
||||||
|
|
||||||
|
- [ ] Implement transition guard and mutation logic.
|
||||||
|
- [ ] Implement optimistic concurrency handling.
|
||||||
|
- [ ] Implement transition event and audit emission.
|
||||||
|
|
||||||
|
## Filtering and Routing
|
||||||
|
|
||||||
|
- [ ] Implement deterministic gate-first pipeline.
|
||||||
|
- [ ] Implement confidence band decision handling.
|
||||||
|
- [ ] Implement routing rule engine with explainability payloads.
|
||||||
|
|
||||||
|
## Assignment
|
||||||
|
|
||||||
|
- [ ] Implement assignment preview and execute parity.
|
||||||
|
- [ ] Implement coverage guarantee and overflow semantics.
|
||||||
|
- [ ] Implement assignment metrics output.
|
||||||
|
|
||||||
|
## Live Runtime
|
||||||
|
|
||||||
|
- [ ] Implement admin cursor operations and conflict-safe update model.
|
||||||
|
- [ ] Implement cohort window control.
|
||||||
|
- [ ] Implement real-time event propagation path.
|
||||||
|
|
||||||
|
## Notifications and Audit
|
||||||
|
|
||||||
|
- [ ] Implement default event producers for stage transitions and outcomes.
|
||||||
|
- [ ] Implement immutable audit payload structure.
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Phase 03 Acceptance Gates
|
||||||
|
|
||||||
|
- [ ] G-03-1 wizard can complete full required setup (E-001)
|
||||||
|
- [ ] G-03-2 no hidden edit-only required settings remain
|
||||||
|
- [ ] G-03-3 advanced editor enforces graph/config guardrails
|
||||||
|
- [ ] G-03-4 modal and form safety regressions pass
|
||||||
|
|
||||||
|
## Required Evidence
|
||||||
|
|
||||||
|
- E2E wizard completion evidence
|
||||||
|
- parity checklist artifacts
|
||||||
|
- targeted UI regression test output
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Advanced Editor Specification
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Provide direct manipulation of tracks, stages, transitions, and routing without polluting the default wizard path.
|
||||||
|
|
||||||
|
## Panels
|
||||||
|
|
||||||
|
1. Track/Stage List Panel
|
||||||
|
2. Stage Config Panel
|
||||||
|
3. Transition Graph Panel
|
||||||
|
4. Routing Rule Inspector
|
||||||
|
5. Simulation Panel
|
||||||
|
|
||||||
|
## Required Capabilities
|
||||||
|
|
||||||
|
- reorder stages within track
|
||||||
|
- move valid stages across tracks
|
||||||
|
- create/delete transitions
|
||||||
|
- edit rule predicates and priorities
|
||||||
|
- simulate outcomes for sample project IDs
|
||||||
|
|
||||||
|
## Guardrails
|
||||||
|
|
||||||
|
1. Block disconnected required paths.
|
||||||
|
2. Block orphan stage deletion.
|
||||||
|
3. Warn before destructive transition/rule removal.
|
||||||
|
4. Enforce schema validation for stage config payloads.
|
||||||
|
|
||||||
|
## Save Model
|
||||||
|
|
||||||
|
- draft buffer
|
||||||
|
- validation run
|
||||||
|
- transactional persist
|
||||||
|
- validation report artifact
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Form Behavior and Validation Rules
|
||||||
|
|
||||||
|
## Universal Rules
|
||||||
|
|
||||||
|
1. Every required field has inline validation.
|
||||||
|
2. Every select has deterministic default value.
|
||||||
|
3. Save actions are idempotent and disabled while pending.
|
||||||
|
4. Unsafe changes surface explicit impact warnings.
|
||||||
|
|
||||||
|
## Create/Edit Parity Requirements
|
||||||
|
|
||||||
|
- intake windows
|
||||||
|
- upload policy
|
||||||
|
- file requirements
|
||||||
|
- assignment policy
|
||||||
|
- filtering policy
|
||||||
|
- routing policy
|
||||||
|
- live policy
|
||||||
|
|
||||||
|
## Modal Safety Rules
|
||||||
|
|
||||||
|
1. Modal close must not mutate persisted state.
|
||||||
|
2. Non-submit buttons must explicitly set `type="button"`.
|
||||||
|
3. Escape/cancel should only dismiss local draft state.
|
||||||
|
|
||||||
|
## Payload Safety
|
||||||
|
|
||||||
|
- replace raw free-text config where structured selectors exist
|
||||||
|
- normalize serialization format for config payloads
|
||||||
|
- reject unknown keys in strict mode contracts
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Phase 03 Overview: Admin Control-Plane UX
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
Deliver a wizard-first admin control plane that exposes full required configuration in create-time flow, with a safe advanced editor for power users.
|
||||||
|
|
||||||
|
## In Scope
|
||||||
|
|
||||||
|
- setup wizard IA and behavior
|
||||||
|
- advanced stage and routing editor
|
||||||
|
- simulation and validation panel
|
||||||
|
- create/edit parity
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- visual redesign
|
||||||
|
- participant-facing workflows
|
||||||
|
|
||||||
|
## Exit Criteria
|
||||||
|
|
||||||
|
1. Full required setup possible from create flow.
|
||||||
|
2. No hidden edit-only required fields.
|
||||||
|
3. Validation and simulation guardrails implemented.
|
||||||
|
4. Phase gates complete.
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Phase 03 Tasks
|
||||||
|
|
||||||
|
## Wizard
|
||||||
|
|
||||||
|
- [ ] Implement 8-step setup flow.
|
||||||
|
- [ ] Implement step-level validation and progress state.
|
||||||
|
- [ ] Implement review/publish summary and blockers.
|
||||||
|
|
||||||
|
## Advanced Editor
|
||||||
|
|
||||||
|
- [ ] Implement stage/transition/routing editing surfaces.
|
||||||
|
- [ ] Implement simulation runner and result panel.
|
||||||
|
- [ ] Implement destructive action confirmations.
|
||||||
|
|
||||||
|
## Behavior and Safety
|
||||||
|
|
||||||
|
- [ ] Enforce create/edit parity checklist.
|
||||||
|
- [ ] Enforce modal safety rules.
|
||||||
|
- [ ] Enforce strict payload validation.
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
# Admin Wizard IA
|
||||||
|
|
||||||
|
## Step Sequence
|
||||||
|
|
||||||
|
1. Intake Setup
|
||||||
|
2. Main Track Stage Setup
|
||||||
|
3. Filtering Strategy
|
||||||
|
4. Assignment Strategy
|
||||||
|
5. Special Awards
|
||||||
|
6. Live Finals Configuration
|
||||||
|
7. Notifications and Overrides
|
||||||
|
8. Review + Publish
|
||||||
|
|
||||||
|
## Step Details
|
||||||
|
|
||||||
|
### 1) Intake Setup
|
||||||
|
|
||||||
|
- submission windows
|
||||||
|
- late policy
|
||||||
|
- file requirements
|
||||||
|
- MIME/size constraints
|
||||||
|
- applicant communication policy
|
||||||
|
|
||||||
|
### 2) Main Track Stage Setup
|
||||||
|
|
||||||
|
- stage list and ordering
|
||||||
|
- stage type assignment
|
||||||
|
- status defaults
|
||||||
|
- selection stage presets
|
||||||
|
|
||||||
|
### 3) Filtering Strategy
|
||||||
|
|
||||||
|
- deterministic gate definition
|
||||||
|
- AI rubric configuration
|
||||||
|
- confidence thresholds
|
||||||
|
- manual queue owners
|
||||||
|
|
||||||
|
### 4) Assignment Strategy
|
||||||
|
|
||||||
|
- required reviews
|
||||||
|
- max/min load settings
|
||||||
|
- availability weighting
|
||||||
|
- overflow handling policy
|
||||||
|
|
||||||
|
### 5) Special Awards
|
||||||
|
|
||||||
|
- award track enablement
|
||||||
|
- routing mode per award
|
||||||
|
- decision mode per award
|
||||||
|
- award jury restrictions
|
||||||
|
|
||||||
|
### 6) Live Finals
|
||||||
|
|
||||||
|
- cursor control mode
|
||||||
|
- jury vote config
|
||||||
|
- audience vote config
|
||||||
|
- cohort setup
|
||||||
|
- reveal policy
|
||||||
|
|
||||||
|
### 7) Notifications and Overrides
|
||||||
|
|
||||||
|
- default-on event toggles
|
||||||
|
- template overrides
|
||||||
|
- override governance policy
|
||||||
|
|
||||||
|
### 8) Review + Publish
|
||||||
|
|
||||||
|
- summary diff
|
||||||
|
- warnings/blockers
|
||||||
|
- simulation output
|
||||||
|
- publish action
|
||||||
|
|
||||||
|
## UX Requirements
|
||||||
|
|
||||||
|
- mobile-safe interaction and layout
|
||||||
|
- explicit required field indicators
|
||||||
|
- deterministic defaults for every select
|
||||||
|
- inline validation without hidden blockers
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Phase 04 Acceptance Gates
|
||||||
|
|
||||||
|
- [ ] G-04-1 applicant flow tests pass (E-002)
|
||||||
|
- [ ] G-04-2 jury flow tests pass (E-004)
|
||||||
|
- [ ] G-04-3 live audience tests pass (E-006/E-007)
|
||||||
|
- [ ] G-04-4 reconnect and realtime resilience evidence passes (P-004)
|
||||||
|
|
||||||
|
## Required Evidence
|
||||||
|
|
||||||
|
- E2E artifacts for applicant/jury/audience scenarios
|
||||||
|
- realtime and reconnect test captures
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Applicant Experience Specification
|
||||||
|
|
||||||
|
## Required Views
|
||||||
|
|
||||||
|
1. current stage and timeline
|
||||||
|
2. stage-specific requirements
|
||||||
|
3. deadlines and late policy status
|
||||||
|
4. team invite/account status
|
||||||
|
5. decision history (policy-scoped)
|
||||||
|
|
||||||
|
## Behavior Requirements
|
||||||
|
|
||||||
|
- requirement upload slots are stage-aware
|
||||||
|
- accepted MIME/size and deadline checks enforced at submit time
|
||||||
|
- timeline updates reflect transition and decision events quickly
|
||||||
|
- role-scoped team collaboration controls enforced
|
||||||
|
|
||||||
|
## Error States
|
||||||
|
|
||||||
|
- missing requirement definition
|
||||||
|
- expired upload window
|
||||||
|
- invalid MIME/size
|
||||||
|
- stale session/permission mismatch
|
||||||
|
|
||||||
|
## Notification Expectations
|
||||||
|
|
||||||
|
- intake submitted confirmation
|
||||||
|
- advanced/rejected updates
|
||||||
|
- additional requirement requests when policy allows
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Audience Live Vote Specification
|
||||||
|
|
||||||
|
## Core Rules
|
||||||
|
|
||||||
|
1. Audience sees only projects within active cohort/window policy.
|
||||||
|
2. Vote submission requires valid session eligibility and dedupe key check.
|
||||||
|
3. Closed windows reject submissions with typed error.
|
||||||
|
|
||||||
|
## Voting Modes
|
||||||
|
|
||||||
|
- per-project window
|
||||||
|
- per-cohort window
|
||||||
|
- optional criteria mode or simple score mode
|
||||||
|
|
||||||
|
## Safety and Abuse Controls
|
||||||
|
|
||||||
|
- tokenized access policy
|
||||||
|
- optional identity requirement
|
||||||
|
- rate-limit and dedupe enforcement
|
||||||
|
|
||||||
|
## Realtime Requirements
|
||||||
|
|
||||||
|
- active project state and window state sync in near real-time
|
||||||
|
- reconnect path restores current eligible ballot context
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Jury Experience Specification
|
||||||
|
|
||||||
|
## Assignment View
|
||||||
|
|
||||||
|
- grouped by stage
|
||||||
|
- explicit open/close window indicators
|
||||||
|
- progress and completion states
|
||||||
|
|
||||||
|
## Evaluation View
|
||||||
|
|
||||||
|
- criteria loaded from stage config
|
||||||
|
- required criteria enforcement
|
||||||
|
- draft autosave and submit lock behavior
|
||||||
|
- COI declaration flow integrated
|
||||||
|
|
||||||
|
## Access Rules
|
||||||
|
|
||||||
|
- only assigned projects visible
|
||||||
|
- voting restricted to open windows
|
||||||
|
- prior-stage material visibility policy respected
|
||||||
|
|
||||||
|
## Live Jury Behavior
|
||||||
|
|
||||||
|
- active project context sync via realtime updates
|
||||||
|
- vote actions gated by cursor and window state
|
||||||
|
- reconnect restores current live context
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Phase 04 Overview: Participant Journeys
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
Refit applicant, jury, observer, and audience experiences to stage-native contracts with correct realtime behavior.
|
||||||
|
|
||||||
|
## In Scope
|
||||||
|
|
||||||
|
- applicant intake/status flows
|
||||||
|
- jury assignment/evaluation/live flows
|
||||||
|
- audience voting and live score flows
|
||||||
|
- observer read-only reporting alignment
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- admin config internals
|
||||||
|
|
||||||
|
## Exit Criteria
|
||||||
|
|
||||||
|
1. End-to-end participant paths pass mandatory E2E tests.
|
||||||
|
2. Realtime behavior converges under reconnect and window changes.
|
||||||
|
3. Policy enforcement matches authz and stage contracts.
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Phase 04 Tasks
|
||||||
|
|
||||||
|
## Applicant
|
||||||
|
|
||||||
|
- [ ] Implement stage-native timeline and requirement resolver.
|
||||||
|
- [ ] Implement strict upload gating and policy enforcement.
|
||||||
|
|
||||||
|
## Jury
|
||||||
|
|
||||||
|
- [ ] Implement stage-scoped assignment and evaluation surfaces.
|
||||||
|
- [ ] Implement live jury context sync and voting constraints.
|
||||||
|
|
||||||
|
## Audience and Observer
|
||||||
|
|
||||||
|
- [ ] Implement cohort-scoped audience ballot visibility.
|
||||||
|
- [ ] Implement observer read-only stage/track reporting alignment.
|
||||||
|
|
||||||
|
## Realtime and Resilience
|
||||||
|
|
||||||
|
- [ ] Implement reconnect-state convergence behavior.
|
||||||
|
- [ ] Validate realtime event consistency under cursor updates.
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Phase 05 Acceptance Gates
|
||||||
|
|
||||||
|
- [ ] G-05-1 routing mode behavior validated (`parallel`, `exclusive`, `post_main`)
|
||||||
|
- [ ] G-05-2 governance auth tests pass (`JURY_VOTE`, `AWARD_MASTER`, `ADMIN`)
|
||||||
|
- [ ] G-05-3 winner decision timeline and audit output validated
|
||||||
|
|
||||||
|
## Required Evidence
|
||||||
|
|
||||||
|
- integration test outputs for routing and governance
|
||||||
|
- audit timeline payload captures
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
# Award Track and Governance Specification
|
||||||
|
|
||||||
|
## Award Track Principle
|
||||||
|
Awards share the same orchestration engine as the main competition; they are tracks, not detached side workflows.
|
||||||
|
|
||||||
|
## Routing Modes
|
||||||
|
|
||||||
|
- `PARALLEL`: award path runs while main path continues
|
||||||
|
- `EXCLUSIVE`: project exits main continuation path and runs award-only
|
||||||
|
- `POST_MAIN`: award route starts after configured main gate
|
||||||
|
|
||||||
|
## Governance Modes
|
||||||
|
|
||||||
|
- `JURY_VOTE`: assigned award jurors vote
|
||||||
|
- `AWARD_MASTER`: designated award owner decides within scope
|
||||||
|
- `ADMIN`: program/super admin decides
|
||||||
|
|
||||||
|
## Decision Requirements
|
||||||
|
|
||||||
|
- every winner/finalist decision emits audit entry
|
||||||
|
- manual overrides require reason code and text
|
||||||
|
- tie-break policy explicit and deterministic
|
||||||
|
|
||||||
|
## Permission Enforcement
|
||||||
|
|
||||||
|
- governance mode checked server-side on every decision mutation
|
||||||
|
- unauthorized attempts return `FORBIDDEN`
|
||||||
|
|
||||||
|
## Representative Decision Payload
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"awardId": "award_123",
|
||||||
|
"decisionMode": "AWARD_MASTER",
|
||||||
|
"winnerProjectId": "project_789",
|
||||||
|
"reasonCode": "SPONSOR_DECISION",
|
||||||
|
"reasonText": "Award sponsor selected based on category fit"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Phase 05 Overview: Special Awards and Governance
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
Implement special awards as first-class tracks with explicit routing and governance modes, including `AWARD_MASTER`.
|
||||||
|
|
||||||
|
## In Scope
|
||||||
|
|
||||||
|
- award track lifecycle
|
||||||
|
- routing semantics
|
||||||
|
- governance modes and permissions
|
||||||
|
- award decision and winner finalization workflows
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- sponsor legal/contract process documentation
|
||||||
|
|
||||||
|
## Exit Criteria
|
||||||
|
|
||||||
|
1. Mixed award modes run without collision in a single edition.
|
||||||
|
2. Governance modes enforce correct permissions server-side.
|
||||||
|
3. Winner decision audit trails are complete and immutable.
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Phase 05 Tasks
|
||||||
|
|
||||||
|
- [ ] Implement award track CRUD on canonical contracts.
|
||||||
|
- [ ] Implement award routing mode behaviors and edge-case handling.
|
||||||
|
- [ ] Implement governance mode permission checks.
|
||||||
|
- [ ] Implement winner finalization and audit timeline entries.
|
||||||
|
- [ ] Implement award-specific reporting outputs.
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Phase 06 Acceptance Gates
|
||||||
|
|
||||||
|
- [ ] G-06-1 dependency refit inventory fully signed off
|
||||||
|
- [ ] G-06-2 symbol sweeps clean (no runtime legacy hits)
|
||||||
|
- [ ] G-06-3 integration consumer payload checks pass
|
||||||
|
- [ ] G-06-4 cross-role smoke tests pass
|
||||||
|
|
||||||
|
## Required Evidence
|
||||||
|
|
||||||
|
- module sign-off checklist with owners
|
||||||
|
- sweep outputs
|
||||||
|
- webhook/export consumer validation logs
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Module Refit Map
|
||||||
|
|
||||||
|
## Router Layer Actions
|
||||||
|
|
||||||
|
- Rewrite orchestration endpoints to `pipeline/stage/routing` contracts.
|
||||||
|
- Refactor filtering, assignment, and live endpoints to stage-scoped semantics.
|
||||||
|
- Replace award detached flows with award-track-native contracts.
|
||||||
|
|
||||||
|
## Service Layer Actions
|
||||||
|
|
||||||
|
- Refactor AI filtering context to stage-native payloads.
|
||||||
|
- Refactor assignment engine to consume stage eligibility and availability.
|
||||||
|
- Refactor notification producers to new event taxonomy.
|
||||||
|
- Refactor reminders and summaries to stage references.
|
||||||
|
|
||||||
|
## UI Layer Actions
|
||||||
|
|
||||||
|
- Admin round pages become pipeline/stage control-plane pages.
|
||||||
|
- Jury and applicant pages consume stage timeline and stage requirement contracts.
|
||||||
|
- Public vote/live pages consume cohort and live cursor state.
|
||||||
|
|
||||||
|
## Reporting and Export Actions
|
||||||
|
|
||||||
|
- Replace round-grouped aggregations with stage/track aggregations.
|
||||||
|
- Update CSV/PDF payload field names to new contracts.
|
||||||
|
- Update observer dashboards and chart dimensions.
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Phase 06 Overview: Platform Dependency Refit
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
Refit every platform dependency from legacy round semantics to canonical stage contracts.
|
||||||
|
|
||||||
|
## In Scope
|
||||||
|
|
||||||
|
- module-by-module refit execution
|
||||||
|
- stale symbol removal
|
||||||
|
- integration payload consumer updates
|
||||||
|
- cross-role smoke validation
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- new feature expansion not required for contract migration
|
||||||
|
|
||||||
|
## Exit Criteria
|
||||||
|
|
||||||
|
1. dependency checklist complete
|
||||||
|
2. legacy symbol sweeps clean
|
||||||
|
3. external integration consumers validated
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Symbol Sweep Checklist
|
||||||
|
|
||||||
|
All commands must return zero actionable runtime hits.
|
||||||
|
|
||||||
|
- [ ] `rg "trpc\.round" src`
|
||||||
|
- [ ] `rg "\broundId\b" src/server src/components src/app`
|
||||||
|
- [ ] `rg "round\.settingsJson|roundType" src/server src/components src/app`
|
||||||
|
- [ ] `rg "model Round|enum RoundType" prisma/schema.prisma`
|
||||||
|
|
||||||
|
## Exceptions
|
||||||
|
|
||||||
|
- documentation-only references may be allowed with explicit annotation
|
||||||
|
- any code-path exception is release-blocking unless approved
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Phase 06 Tasks
|
||||||
|
|
||||||
|
- [ ] Execute router-layer refit checklist.
|
||||||
|
- [ ] Execute service-layer refit checklist.
|
||||||
|
- [ ] Execute UI-layer refit checklist.
|
||||||
|
- [ ] Execute reporting/export integration checklist.
|
||||||
|
- [ ] Run and document legacy symbol sweeps.
|
||||||
|
- [ ] Resolve all remaining contract drift findings.
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Phase 07 Acceptance Gates
|
||||||
|
|
||||||
|
- [ ] G-07-1 U/I/E/P matrix all green
|
||||||
|
- [ ] G-07-2 performance and resilience evidence accepted
|
||||||
|
- [ ] G-07-3 release evidence report complete and signed
|
||||||
|
- [ ] G-07-4 atomic cutover executed with successful post-checks
|
||||||
|
|
||||||
|
## Required Evidence
|
||||||
|
|
||||||
|
- consolidated test reports
|
||||||
|
- benchmark output captures
|
||||||
|
- signed release evidence report
|
||||||
|
- runbook execution logs
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Phase 07 Overview: Validation and Release
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
Execute complete validation suite, run final reset/reseed rehearsal, and perform atomic release cutover.
|
||||||
|
|
||||||
|
## In Scope
|
||||||
|
|
||||||
|
- full U/I/E/P test execution
|
||||||
|
- release evidence collation
|
||||||
|
- performance and resilience validation
|
||||||
|
- atomic release runbook execution
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- post-release enhancements
|
||||||
|
|
||||||
|
## Exit Criteria
|
||||||
|
|
||||||
|
1. full test matrix green
|
||||||
|
2. release evidence signed
|
||||||
|
3. atomic cutover and post-cutover smoke checks complete
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Performance and Resilience Plan
|
||||||
|
|
||||||
|
## Scenarios
|
||||||
|
|
||||||
|
### Assignment Throughput
|
||||||
|
|
||||||
|
- workload: 1000+ eligible projects
|
||||||
|
- metrics: runtime, coverage latency, overload count
|
||||||
|
|
||||||
|
### Filtering Throughput
|
||||||
|
|
||||||
|
- workload: high-volume gate + AI queue
|
||||||
|
- metrics: gate throughput, queue completion, retry/error rate
|
||||||
|
|
||||||
|
### Live Voting Burst
|
||||||
|
|
||||||
|
- workload: peak audience voting during active cursor changes
|
||||||
|
- metrics: vote latency p50/p95/p99, event drop count, cursor propagation delay
|
||||||
|
|
||||||
|
### Reconnect Recovery
|
||||||
|
|
||||||
|
- workload: intentional network interruptions
|
||||||
|
- metrics: time to state convergence, stale cursor mismatch rate
|
||||||
|
|
||||||
|
## Acceptance Policy
|
||||||
|
|
||||||
|
Thresholds set before run and documented in release evidence.
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
# Release Runbook (Atomic Cutover)
|
||||||
|
|
||||||
|
## Preconditions
|
||||||
|
|
||||||
|
- all prior phase gates complete
|
||||||
|
- signed release checklist
|
||||||
|
- rollback owner and communication owner assigned
|
||||||
|
|
||||||
|
## Cutover Sequence
|
||||||
|
|
||||||
|
1. freeze non-release writes and announce maintenance window
|
||||||
|
2. execute final backup snapshot
|
||||||
|
3. deploy release candidate build
|
||||||
|
4. run reset/reseed as planned for production state model
|
||||||
|
5. run post-deploy integrity and smoke checks
|
||||||
|
6. run mandatory critical-path E2E subset
|
||||||
|
7. publish completion and monitor
|
||||||
|
|
||||||
|
## Immediate Post-Cutover Checks
|
||||||
|
|
||||||
|
- auth and role gating paths
|
||||||
|
- transition mutation sanity
|
||||||
|
- assignment preview/execute path
|
||||||
|
- live cursor operations
|
||||||
|
- audience vote acceptance and dedupe
|
||||||
|
- reporting endpoint correctness
|
||||||
|
|
||||||
|
## Rollback Trigger Conditions
|
||||||
|
|
||||||
|
- integrity check failures
|
||||||
|
- critical mutation path failure
|
||||||
|
- unacceptable error-rate spike
|
||||||
|
|
||||||
|
## Rollback Plan (High Level)
|
||||||
|
|
||||||
|
- restore backup snapshot
|
||||||
|
- redeploy previous stable build
|
||||||
|
- validate critical-path smoke tests
|
||||||
|
- issue incident communication and postmortem schedule
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Phase 07 Tasks
|
||||||
|
|
||||||
|
- [ ] Execute full test matrix and store artifacts.
|
||||||
|
- [ ] Execute performance and resilience scenarios.
|
||||||
|
- [ ] Complete release evidence report.
|
||||||
|
- [ ] Run atomic cutover rehearsal and production runbook.
|
||||||
|
- [ ] Complete post-cutover smoke suite.
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
# API Contracts
|
||||||
|
|
||||||
|
## Contract Conventions
|
||||||
|
|
||||||
|
- All mutations return typed `errorCode` and machine-readable `details` on failure.
|
||||||
|
- All state-changing operations emit deterministic audit events.
|
||||||
|
- All response shapes include stable identifiers for client cache invalidation.
|
||||||
|
|
||||||
|
## Router Families
|
||||||
|
|
||||||
|
### `pipeline`
|
||||||
|
|
||||||
|
- `pipeline.create`
|
||||||
|
- `pipeline.update`
|
||||||
|
- `pipeline.simulate`
|
||||||
|
- `pipeline.publish`
|
||||||
|
- `pipeline.getSummary`
|
||||||
|
|
||||||
|
### `stage`
|
||||||
|
|
||||||
|
- `stage.create`
|
||||||
|
- `stage.updateConfig`
|
||||||
|
- `stage.list`
|
||||||
|
- `stage.transition`
|
||||||
|
- `stage.openWindow`
|
||||||
|
- `stage.closeWindow`
|
||||||
|
|
||||||
|
### `routing`
|
||||||
|
|
||||||
|
- `routing.preview`
|
||||||
|
- `routing.execute`
|
||||||
|
- `routing.listRules`
|
||||||
|
- `routing.upsertRule`
|
||||||
|
- `routing.toggleRule`
|
||||||
|
|
||||||
|
### `filtering`
|
||||||
|
|
||||||
|
- `filtering.previewBatch`
|
||||||
|
- `filtering.runStageFiltering`
|
||||||
|
- `filtering.getManualQueue`
|
||||||
|
- `filtering.resolveManualDecision`
|
||||||
|
|
||||||
|
### `assignment`
|
||||||
|
|
||||||
|
- `assignment.previewStageProjects`
|
||||||
|
- `assignment.assignStageProjects`
|
||||||
|
- `assignment.getCoverageReport`
|
||||||
|
- `assignment.rebalance`
|
||||||
|
|
||||||
|
### `cohort`
|
||||||
|
|
||||||
|
- `cohort.create`
|
||||||
|
- `cohort.assignProjects`
|
||||||
|
- `cohort.openVoting`
|
||||||
|
- `cohort.closeVoting`
|
||||||
|
|
||||||
|
### `live`
|
||||||
|
|
||||||
|
- `live.start`
|
||||||
|
- `live.setActiveProject`
|
||||||
|
- `live.jump`
|
||||||
|
- `live.reorder`
|
||||||
|
- `live.pause`
|
||||||
|
- `live.resume`
|
||||||
|
|
||||||
|
### `decision`
|
||||||
|
|
||||||
|
- `decision.override`
|
||||||
|
- `decision.auditTimeline`
|
||||||
|
|
||||||
|
### `award`
|
||||||
|
|
||||||
|
- `award.createTrack`
|
||||||
|
- `award.configureGovernance`
|
||||||
|
- `award.routeProjects`
|
||||||
|
- `award.finalizeWinners`
|
||||||
|
|
||||||
|
## Error Contract
|
||||||
|
|
||||||
|
- `BAD_REQUEST`
|
||||||
|
- `UNAUTHORIZED`
|
||||||
|
- `FORBIDDEN`
|
||||||
|
- `NOT_FOUND`
|
||||||
|
- `CONFLICT`
|
||||||
|
- `PRECONDITION_FAILED`
|
||||||
|
- `INTERNAL_SERVER_ERROR`
|
||||||
|
|
||||||
|
## Event Contract (Representative)
|
||||||
|
|
||||||
|
- `stage.transitioned`
|
||||||
|
- `routing.executed`
|
||||||
|
- `filtering.completed`
|
||||||
|
- `assignment.generated`
|
||||||
|
- `live.cursor.updated`
|
||||||
|
- `cohort.window.changed`
|
||||||
|
- `decision.overridden`
|
||||||
|
- `award.winner.finalized`
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Authorization Matrix
|
||||||
|
|
||||||
|
Roles:
|
||||||
|
|
||||||
|
- `SUPER_ADMIN`
|
||||||
|
- `PROGRAM_ADMIN`
|
||||||
|
- `AWARD_MASTER`
|
||||||
|
- `JURY_MEMBER`
|
||||||
|
- `APPLICANT`
|
||||||
|
- `OBSERVER`
|
||||||
|
- `AUDIENCE` (public voting context)
|
||||||
|
|
||||||
|
| Capability | Super Admin | Program Admin | Award Master | Jury | Applicant | Observer | Audience |
|
||||||
|
|---|---|---|---|---|---|---|---|
|
||||||
|
| Create/Edit Pipeline | Yes | Yes (scoped) | No | No | No | No | No |
|
||||||
|
| Publish Pipeline | Yes | Yes (scoped) | No | No | No | No | No |
|
||||||
|
| Configure Stage Rules | Yes | Yes (scoped) | No | No | No | No | No |
|
||||||
|
| Execute Manual Transition | Yes | Yes (scoped) | Limited (award scoped) | No | No | No | No |
|
||||||
|
| Override Decision | Yes | Yes (scoped) | Limited (award scoped) | No | No | No | No |
|
||||||
|
| View Audit Timeline | Yes | Yes (scoped) | Award scoped | Own actions | No | Read-only scoped | No |
|
||||||
|
| Assign Jurors | Yes | Yes (scoped) | Award scoped | No | No | No | No |
|
||||||
|
| Submit Evaluation | No | No | Optional (if configured) | Yes (assigned only) | No | No | No |
|
||||||
|
| Upload Intake Docs | No | No | No | No | Yes | No | No |
|
||||||
|
| Control Live Cursor | Yes | Yes (scoped) | No | No | No | No | No |
|
||||||
|
| Cast Audience Vote | No | No | No | No | Optional | No | Yes |
|
||||||
|
|
||||||
|
## Policy Notes
|
||||||
|
|
||||||
|
1. Program scoping applies to all admin operations.
|
||||||
|
2. `AWARD_MASTER` permissions are explicitly award-scoped and only active when governance mode allows it.
|
||||||
|
3. Jury endpoints always enforce assignment ownership and window constraints.
|
||||||
|
4. Audience endpoints enforce cohort membership + window state + dedupe key policy.
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Decision Log (Locked)
|
||||||
|
|
||||||
|
| ID | Decision | Status | Rationale | Impacted Phases |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| MX-001 | Canonical model is `Pipeline -> Track -> Stage` | Locked | Supports multi-track orchestration cleanly | 01-07 |
|
||||||
|
| MX-002 | Project progression stored in `ProjectStageState` records | Locked | Replaces brittle single-pointer round state | 01-07 |
|
||||||
|
| MX-003 | Intake is stage-native (`INTAKE`) rather than implicit pre-round behavior | Locked | Removes hidden workflow behavior | 01-04 |
|
||||||
|
| MX-004 | Full-cutover delivery with no compatibility bridge | Locked | Faster convergence to clean runtime | 00-07 |
|
||||||
|
| MX-005 | Special awards are first-class `Track` entities | Locked | Prevents duplicated orchestration logic | 01-06 |
|
||||||
|
| MX-006 | Award routing modes are `parallel`, `exclusive`, `post_main` | Locked | Supports real sponsor policy diversity | 02,05 |
|
||||||
|
| MX-007 | Award governance modes include `JURY_VOTE`, `AWARD_MASTER`, `ADMIN` | Locked | Explicit and policy-aligned control surfaces | 05 |
|
||||||
|
| MX-008 | Live progression source of truth is admin cursor | Locked | Needed for non-linear live event control | 02,04 |
|
||||||
|
| MX-009 | Voting windows are explicit open/close operations | Locked | Schedules alone are insufficient during live operations | 02,04 |
|
||||||
|
| MX-010 | Assignment engine guarantees eligible project coverage | Locked | Operational fairness and delivery reliability | 02,04 |
|
||||||
|
| MX-011 | Overrides require reason and immutable audit entries | Locked | Governance and explainability | 02,05,07 |
|
||||||
|
| MX-012 | Release is blocked by legacy symbol sweep failures | Locked | Prevents half-migrated runtime behavior | 06,07 |
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
# Dependency Refit Inventory
|
||||||
|
|
||||||
|
This inventory is release-blocking. Every listed module must be validated against the new contracts.
|
||||||
|
|
||||||
|
## Backend Routers
|
||||||
|
|
||||||
|
- `src/server/routers/_app.ts`
|
||||||
|
- `src/server/routers/round.ts`
|
||||||
|
- `src/server/routers/filtering.ts`
|
||||||
|
- `src/server/routers/live-voting.ts`
|
||||||
|
- `src/server/routers/specialAward.ts`
|
||||||
|
- `src/server/routers/assignment.ts`
|
||||||
|
- `src/server/routers/evaluation.ts`
|
||||||
|
- `src/server/routers/file.ts`
|
||||||
|
- `src/server/routers/project.ts`
|
||||||
|
- `src/server/routers/project-pool.ts`
|
||||||
|
- `src/server/routers/application.ts`
|
||||||
|
- `src/server/routers/applicant.ts`
|
||||||
|
- `src/server/routers/export.ts`
|
||||||
|
- `src/server/routers/analytics.ts`
|
||||||
|
- `src/server/routers/program.ts`
|
||||||
|
- `src/server/routers/roundTemplate.ts`
|
||||||
|
- `src/server/routers/gracePeriod.ts`
|
||||||
|
- `src/server/routers/webhook.ts`
|
||||||
|
|
||||||
|
## Backend Services
|
||||||
|
|
||||||
|
- `src/server/services/smart-assignment.ts`
|
||||||
|
- `src/server/services/ai-filtering.ts`
|
||||||
|
- `src/server/services/ai-evaluation-summary.ts`
|
||||||
|
- `src/server/services/evaluation-reminders.ts`
|
||||||
|
- `src/server/services/in-app-notification.ts`
|
||||||
|
- `src/server/services/award-eligibility-job.ts`
|
||||||
|
- `src/server/services/webhook-dispatcher.ts`
|
||||||
|
|
||||||
|
## Admin Surfaces
|
||||||
|
|
||||||
|
- `src/app/(admin)/admin/rounds/**`
|
||||||
|
- `src/app/(admin)/admin/awards/**`
|
||||||
|
- `src/app/(admin)/admin/reports/page.tsx`
|
||||||
|
- `src/components/admin/round-pipeline.tsx`
|
||||||
|
- `src/components/admin/assign-projects-dialog.tsx`
|
||||||
|
- `src/components/admin/advance-projects-dialog.tsx`
|
||||||
|
- `src/components/admin/remove-projects-dialog.tsx`
|
||||||
|
- `src/components/admin/file-requirements-editor.tsx`
|
||||||
|
- `src/components/forms/round-type-settings.tsx`
|
||||||
|
|
||||||
|
## Jury, Applicant, Public
|
||||||
|
|
||||||
|
- `src/app/(jury)/jury/**`
|
||||||
|
- `src/components/jury/**`
|
||||||
|
- `src/app/(applicant)/applicant/**`
|
||||||
|
- `src/app/(public)/apply/**`
|
||||||
|
- `src/app/(public)/my-submission/**`
|
||||||
|
- `src/app/(public)/vote/**`
|
||||||
|
- `src/app/(public)/live-scores/**`
|
||||||
|
|
||||||
|
## Reporting and Exports
|
||||||
|
|
||||||
|
- chart and observer modules under `src/components/charts/**` and `src/components/observer/**`
|
||||||
|
- export and PDF paths under `src/components/shared/export-pdf-button.tsx`, `src/components/admin/pdf-report.tsx`, `src/server/routers/export.ts`
|
||||||
|
|
||||||
|
## Schema and Seed Paths
|
||||||
|
|
||||||
|
- `prisma/schema.prisma`
|
||||||
|
- relevant migrations and seed scripts under `prisma/`
|
||||||
|
|
||||||
|
## Mandatory Legacy Sweep Queries (Release Blockers)
|
||||||
|
|
||||||
|
1. `rg "trpc\.round" src`
|
||||||
|
2. `rg "\broundId\b" src/server src/components src/app`
|
||||||
|
3. `rg "round\.settingsJson|roundType" src/server src/components src/app`
|
||||||
|
4. `rg "model Round|enum RoundType" prisma/schema.prisma`
|
||||||
|
|
||||||
|
Allowlist exceptions (if any) must be explicit and approved in Phase 06 gates.
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
# Domain Model and Contracts
|
||||||
|
|
||||||
|
## Canonical Enums
|
||||||
|
|
||||||
|
- `StageType = INTAKE | FILTER | EVALUATION | SELECTION | LIVE_FINAL | RESULTS`
|
||||||
|
- `TrackKind = MAIN | AWARD | SHOWCASE`
|
||||||
|
- `RoutingMode = PARALLEL | EXCLUSIVE | POST_MAIN`
|
||||||
|
- `StageStatus = DRAFT | ACTIVE | CLOSED | ARCHIVED`
|
||||||
|
- `ProjectStageStateValue = PENDING | IN_PROGRESS | PASSED | REJECTED | ROUTED | COMPLETED | WITHDRAWN`
|
||||||
|
- `DecisionMode = JURY_VOTE | AWARD_MASTER | ADMIN`
|
||||||
|
- `OverrideReasonCode = DATA_CORRECTION | POLICY_EXCEPTION | JURY_CONFLICT | SPONSOR_DECISION | ADMIN_DISCRETION`
|
||||||
|
|
||||||
|
## Core Entities
|
||||||
|
|
||||||
|
### Pipeline
|
||||||
|
|
||||||
|
- `id`
|
||||||
|
- `programId`
|
||||||
|
- `name`
|
||||||
|
- `slug`
|
||||||
|
- `status`
|
||||||
|
- `settingsJson`
|
||||||
|
- `createdAt`, `updatedAt`
|
||||||
|
|
||||||
|
### Track
|
||||||
|
|
||||||
|
- `id`
|
||||||
|
- `pipelineId`
|
||||||
|
- `kind`
|
||||||
|
- `specialAwardId?`
|
||||||
|
- `name`
|
||||||
|
- `slug`
|
||||||
|
- `sortOrder`
|
||||||
|
- `routingModeDefault?`
|
||||||
|
- `decisionMode?`
|
||||||
|
|
||||||
|
### Stage
|
||||||
|
|
||||||
|
- `id`
|
||||||
|
- `trackId`
|
||||||
|
- `stageType`
|
||||||
|
- `name`
|
||||||
|
- `slug`
|
||||||
|
- `sortOrder`
|
||||||
|
- `status`
|
||||||
|
- `configVersion`
|
||||||
|
- `configJson`
|
||||||
|
- `windowOpenAt?`, `windowCloseAt?`
|
||||||
|
|
||||||
|
### StageTransition
|
||||||
|
|
||||||
|
- `id`
|
||||||
|
- `fromStageId`
|
||||||
|
- `toStageId`
|
||||||
|
- `priority`
|
||||||
|
- `isDefault`
|
||||||
|
- `guardJson`
|
||||||
|
- `actionJson`
|
||||||
|
|
||||||
|
### ProjectStageState
|
||||||
|
|
||||||
|
- `id`
|
||||||
|
- `projectId`
|
||||||
|
- `trackId`
|
||||||
|
- `stageId`
|
||||||
|
- `state`
|
||||||
|
- `enteredAt`, `exitedAt`
|
||||||
|
- `decisionRef?`
|
||||||
|
- `outcomeJson`
|
||||||
|
|
||||||
|
### RoutingRule
|
||||||
|
|
||||||
|
- `id`
|
||||||
|
- `pipelineId`
|
||||||
|
- `scope` (`GLOBAL|TRACK|STAGE`)
|
||||||
|
- `predicateJson`
|
||||||
|
- `destinationTrackId`
|
||||||
|
- `destinationStageId?`
|
||||||
|
- `priority`
|
||||||
|
- `isActive`
|
||||||
|
|
||||||
|
### Cohort and Live Runtime
|
||||||
|
|
||||||
|
- `Cohort(id, stageId, name, votingMode, isOpen, windowOpenAt?, windowCloseAt?)`
|
||||||
|
- `CohortProject(cohortId, projectId, sortOrder)`
|
||||||
|
- `LiveProgressCursor(id, stageId, sessionId, activeProjectId?, activeOrderIndex?, updatedBy, updatedAt)`
|
||||||
|
|
||||||
|
### Governance Entities
|
||||||
|
|
||||||
|
- `OverrideAction(id, entityType, entityId, oldValueJson, newValueJson, reasonCode, reasonText, actedBy, actedAt)`
|
||||||
|
- `DecisionAuditLog(id, entityType, entityId, eventType, payloadJson, actorId?, createdAt)`
|
||||||
|
|
||||||
|
## Stage Config Union Contracts
|
||||||
|
|
||||||
|
### IntakeConfig
|
||||||
|
|
||||||
|
- file requirements
|
||||||
|
- accepted MIME and size constraints
|
||||||
|
- deadline and late policy
|
||||||
|
- team invite policy
|
||||||
|
|
||||||
|
### FilterConfig
|
||||||
|
|
||||||
|
- deterministic gates
|
||||||
|
- AI rubric
|
||||||
|
- confidence thresholds
|
||||||
|
- manual queue policy
|
||||||
|
- rejection notification policy
|
||||||
|
|
||||||
|
### EvaluationConfig
|
||||||
|
|
||||||
|
- criteria schema
|
||||||
|
- assignment strategy
|
||||||
|
- review thresholds
|
||||||
|
- COI policy
|
||||||
|
- visibility rules
|
||||||
|
|
||||||
|
### SelectionConfig
|
||||||
|
|
||||||
|
- ranking source
|
||||||
|
- finalist target
|
||||||
|
- override permissions
|
||||||
|
- promotion mode (`auto_top_n`, `hybrid`, `manual`)
|
||||||
|
|
||||||
|
### LiveFinalConfig
|
||||||
|
|
||||||
|
- session behavior
|
||||||
|
- jury voting config
|
||||||
|
- audience voting config
|
||||||
|
- cohort policy
|
||||||
|
- reveal policy
|
||||||
|
- schedule hints (advisory)
|
||||||
|
|
||||||
|
### ResultsConfig
|
||||||
|
|
||||||
|
- ranking weight rules
|
||||||
|
- publication policy
|
||||||
|
- winner override rules
|
||||||
|
|
||||||
|
## Constraint Rules
|
||||||
|
|
||||||
|
1. Stage ordering unique per track (`trackId + sortOrder`).
|
||||||
|
2. `ProjectStageState` unique on (`projectId`, `trackId`, `stageId`).
|
||||||
|
3. `StageTransition` unique on (`fromStageId`, `toStageId`).
|
||||||
|
4. Transition destination must remain in same pipeline unless explicit routing rule applies.
|
||||||
|
5. Override records immutable after insert.
|
||||||
|
6. Decision audit log append-only.
|
||||||
|
|
||||||
|
## Index Priorities
|
||||||
|
|
||||||
|
1. `ProjectStageState(projectId, trackId, state)`
|
||||||
|
2. `ProjectStageState(stageId, state)`
|
||||||
|
3. `RoutingRule(pipelineId, isActive, priority)`
|
||||||
|
4. `StageTransition(fromStageId, priority)`
|
||||||
|
5. `LiveProgressCursor(stageId, sessionId)`
|
||||||
|
6. `DecisionAuditLog(entityType, entityId, createdAt)`
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Phase Gate Traceability
|
||||||
|
|
||||||
|
| Phase | Gate ID | Evidence Required | Test IDs / Checks | Blocking |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 00 | G-00-1 | decision lock snapshot | decision-log review | Yes |
|
||||||
|
| 00 | G-00-2 | contract alignment review | API/type contract diff | Yes |
|
||||||
|
| 01 | G-01-1 | schema compile output | `prisma generate` | Yes |
|
||||||
|
| 01 | G-01-2 | reset/reseed output | seed logs + integrity queries | Yes |
|
||||||
|
| 01 | G-01-3 | index and FK evidence | SQL verification scripts | Yes |
|
||||||
|
| 02 | G-02-1 | transition runtime proof | U-001/U-002/I-001 | Yes |
|
||||||
|
| 02 | G-02-2 | routing determinism proof | U-003/I-003/I-004 | Yes |
|
||||||
|
| 02 | G-02-3 | filtering policy proof | U-004/U-005/E-003 | Yes |
|
||||||
|
| 02 | G-02-4 | assignment guarantees proof | U-006/U-007/I-005 | Yes |
|
||||||
|
| 02 | G-02-5 | audit/override proof | U-008/I-008 | Yes |
|
||||||
|
| 03 | G-03-1 | create/edit parity proof | parity checklist | Yes |
|
||||||
|
| 03 | G-03-2 | wizard completion proof | E-001 | Yes |
|
||||||
|
| 03 | G-03-3 | modal safety proof | targeted UI regressions | Yes |
|
||||||
|
| 04 | G-04-1 | applicant flow proof | E-002 | Yes |
|
||||||
|
| 04 | G-04-2 | jury flow proof | E-004 | Yes |
|
||||||
|
| 04 | G-04-3 | live audience proof | E-006/E-007/I-006/I-007 | Yes |
|
||||||
|
| 05 | G-05-1 | award routing proof | I-003/I-004 | Yes |
|
||||||
|
| 05 | G-05-2 | governance auth proof | U-010 + auth tests | Yes |
|
||||||
|
| 05 | G-05-3 | winner and audit proof | E-008 + I-008 | Yes |
|
||||||
|
| 06 | G-06-1 | dependency checklist complete | module sign-off evidence | Yes |
|
||||||
|
| 06 | G-06-2 | legacy sweeps clean | mandatory rg sweeps | Yes |
|
||||||
|
| 06 | G-06-3 | external consumer validation | webhook/export checks | Yes |
|
||||||
|
| 07 | G-07-1 | full test report | full matrix results | Yes |
|
||||||
|
| 07 | G-07-2 | performance report | P-001..P-004 evidence | Yes |
|
||||||
|
| 07 | G-07-3 | release evidence package | signed report template | Yes |
|
||||||
|
| 07 | G-07-4 | atomic cutover proof | release runbook logs | Yes |
|
||||||
|
|
||||||
|
Rule: no phase closes until all gates are complete with linked artifacts.
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
# Program Charter
|
||||||
|
|
||||||
|
## Mission
|
||||||
|
Deliver a complete, stage-native orchestration platform for MOPC that supports:
|
||||||
|
|
||||||
|
- edition-scoped intake and progression
|
||||||
|
- deterministic filtering and assignment
|
||||||
|
- parallel and exclusive award flows
|
||||||
|
- admin-driven live finals operations
|
||||||
|
- full auditability and release-grade validation
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
### In Scope
|
||||||
|
|
||||||
|
- Canonical data model rebuild around pipeline/track/stage.
|
||||||
|
- Backend orchestration engine (transition, routing, filtering, assignment, live, notifications, audit).
|
||||||
|
- Admin setup and control-plane UX refit.
|
||||||
|
- Applicant, jury, observer, and audience flow refit to new contracts.
|
||||||
|
- Special award governance modes including `AWARD_MASTER`.
|
||||||
|
- Platform-wide dependency refit of schema/runtime consumers.
|
||||||
|
- Full validation and atomic release process.
|
||||||
|
|
||||||
|
### Out of Scope
|
||||||
|
|
||||||
|
- Legacy contract compatibility bridges.
|
||||||
|
- Cosmetic redesign or major brand refresh.
|
||||||
|
- Non-orchestration feature expansion unrelated to competition lifecycle.
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
1. Admin setup can fully configure required competition behavior in create-time flow.
|
||||||
|
2. Stage progression and routing are deterministic and explainable.
|
||||||
|
3. Award tracks run without ad hoc side logic.
|
||||||
|
4. Live event operations are resilient under reconnect and burst traffic.
|
||||||
|
5. All platform dependencies are migrated and verified before release.
|
||||||
|
|
||||||
|
## Quality Bar
|
||||||
|
|
||||||
|
- Typed contracts at schema, API, and UI boundaries.
|
||||||
|
- Idempotent mutation semantics for high-risk operations.
|
||||||
|
- Strong audit trails for every governance-sensitive action.
|
||||||
|
- Mobile-safe interaction quality for live audience and jury experiences.
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
# Release Evidence Report Template
|
||||||
|
|
||||||
|
## Build Metadata
|
||||||
|
|
||||||
|
- Date:
|
||||||
|
- Commit SHA:
|
||||||
|
- Environment:
|
||||||
|
- Operator:
|
||||||
|
|
||||||
|
## Phase Completion Summary
|
||||||
|
|
||||||
|
- Phase 00:
|
||||||
|
- Phase 01:
|
||||||
|
- Phase 02:
|
||||||
|
- Phase 03:
|
||||||
|
- Phase 04:
|
||||||
|
- Phase 05:
|
||||||
|
- Phase 06:
|
||||||
|
- Phase 07:
|
||||||
|
|
||||||
|
## Test Summary
|
||||||
|
|
||||||
|
- Unit: pass/fail counts
|
||||||
|
- Integration: pass/fail counts
|
||||||
|
- E2E: pass/fail counts
|
||||||
|
- Performance: pass/fail counts
|
||||||
|
|
||||||
|
## Mandatory Scenario Results
|
||||||
|
|
||||||
|
| ID | Result | Evidence Link | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| E-001 | | | |
|
||||||
|
| E-002 | | | |
|
||||||
|
| E-003 | | | |
|
||||||
|
| E-004 | | | |
|
||||||
|
| E-005 | | | |
|
||||||
|
| E-006 | | | |
|
||||||
|
| E-007 | | | |
|
||||||
|
| E-008 | | | |
|
||||||
|
|
||||||
|
## Performance Results
|
||||||
|
|
||||||
|
| ID | Result | Evidence Link | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| P-001 | | | |
|
||||||
|
| P-002 | | | |
|
||||||
|
| P-003 | | | |
|
||||||
|
| P-004 | | | |
|
||||||
|
|
||||||
|
## Legacy Sweep Results
|
||||||
|
|
||||||
|
- `trpc.round` references:
|
||||||
|
- `roundId` orchestration references:
|
||||||
|
- `round.settingsJson` behavior references:
|
||||||
|
- schema `Round` references:
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
|
||||||
|
- None / list with severity and owner
|
||||||
|
|
||||||
|
## Sign-Off
|
||||||
|
|
||||||
|
- Engineering:
|
||||||
|
- Product:
|
||||||
|
- Operations:
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Risk Register
|
||||||
|
|
||||||
|
| ID | Risk | Probability | Impact | Mitigation | Owner | Status |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| R-001 | Hidden legacy coupling after schema rebuild | Medium | High | Mandatory symbol sweeps + module refit gates | Eng Lead | Open |
|
||||||
|
| R-002 | Assignment coverage edge-case failures at scale | Medium | High | Hard overflow policy + P-001 load tests | Backend Lead | Open |
|
||||||
|
| R-003 | Award governance permission drift | Low | High | explicit authz tests for each decision mode | Security Lead | Open |
|
||||||
|
| R-004 | Live cursor race conditions during events | Medium | High | optimistic lock + replay-safe event handling | Realtime Lead | Open |
|
||||||
|
| R-005 | Audience vote dedupe regressions | Medium | Medium | dedupe key contract + E-007 + I-007 tests | Backend Lead | Open |
|
||||||
|
| R-006 | Migration/reseed script incompleteness | Low | High | repeatable reset/reseed rehearsals | DB Owner | Open |
|
||||||
|
| R-007 | Reporting consumers break on contract shift | Medium | Medium | phase-06 consumer validation checklist | Data Lead | Open |
|
||||||
|
| R-008 | Scope creep during dependency refit | High | Medium | strict out-of-scope policy, defer noncritical features | PM | Open |
|
||||||
|
|
||||||
|
## Risk Handling Policy
|
||||||
|
|
||||||
|
- `High impact` items require explicit mitigation evidence before phase close.
|
||||||
|
- `Open` high/high risks block release in Phase 07.
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Test Matrix
|
||||||
|
|
||||||
|
All IDs are mandatory unless explicitly marked non-blocking with sign-off.
|
||||||
|
|
||||||
|
## Unit Tests
|
||||||
|
|
||||||
|
| ID | Area | Scenario | Expected |
|
||||||
|
|---|---|---|---|
|
||||||
|
| U-001 | Transition Engine | legal transition | persisted with audit event |
|
||||||
|
| U-002 | Transition Engine | illegal transition | typed validation error |
|
||||||
|
| U-003 | Routing | multiple rule match | deterministic priority winner |
|
||||||
|
| U-004 | Filtering Gates | missing required docs | blocked before AI pass |
|
||||||
|
| U-005 | AI Banding | uncertain confidence band | routed to manual queue |
|
||||||
|
| U-006 | Assignment | COI conflict | excluded from pool |
|
||||||
|
| U-007 | Assignment | insufficient capacity | overflow flagged + coverage preserved |
|
||||||
|
| U-008 | Override | missing reason fields | mutation rejected |
|
||||||
|
| U-009 | Live Cursor | concurrent cursor update | conflict handled and retried |
|
||||||
|
| U-010 | Award Governance | `AWARD_MASTER` on unauthorized award | forbidden |
|
||||||
|
|
||||||
|
## Integration Tests
|
||||||
|
|
||||||
|
| ID | Area | Scenario | Expected |
|
||||||
|
|---|---|---|---|
|
||||||
|
| I-001 | Pipeline CRUD | create/update/publish | graph integrity maintained |
|
||||||
|
| I-002 | Stage Config | invalid config schema | rejected |
|
||||||
|
| I-003 | Transition + Routing | filter pass to main + award parallel | dual states created |
|
||||||
|
| I-004 | Award Exclusive Routing | exclusive route | removed from main continuation |
|
||||||
|
| I-005 | Assignment API | preview vs execute parity | same constraints and outcomes |
|
||||||
|
| I-006 | Live Runtime | jump + reorder + open/close windows | consistent cursor state |
|
||||||
|
| I-007 | Cohort Voting | closed window submit | vote rejected |
|
||||||
|
| I-008 | Decision Audit | override applied | complete immutable timeline |
|
||||||
|
|
||||||
|
## End-to-End Tests
|
||||||
|
|
||||||
|
| ID | Persona | Scenario | Expected |
|
||||||
|
|---|---|---|---|
|
||||||
|
| E-001 | Admin | complete setup via wizard | no hidden edit-only blockers |
|
||||||
|
| E-002 | Applicant | upload intake requirements | status and deadlines enforced |
|
||||||
|
| E-003 | Admin | run filtering stage | gates + AI + manual queue behave |
|
||||||
|
| E-004 | Jury | complete evaluation workflow | criteria and lock policy enforced |
|
||||||
|
| E-005 | Admin | selection + override | finalists and audit aligned |
|
||||||
|
| E-006 | Live Admin | advance/back/jump + reorder | jury and audience sync realtime |
|
||||||
|
| E-007 | Audience | vote by cohort on mobile | visibility and dedupe enforced |
|
||||||
|
| E-008 | Admin | finalize results | ranking and publish outputs valid |
|
||||||
|
|
||||||
|
## Performance and Resilience
|
||||||
|
|
||||||
|
| ID | Area | Scenario | Threshold |
|
||||||
|
|---|---|---|---|
|
||||||
|
| P-001 | Assignment | 1000+ project batch | under agreed SLA |
|
||||||
|
| P-002 | Filtering | large AI queue | deterministic retry, no dropped jobs |
|
||||||
|
| P-003 | Live Voting | peak audience burst | acceptable p95 and no data loss |
|
||||||
|
| P-004 | Reconnect | disconnect/reconnect | state converges quickly |
|
||||||
|
|
||||||
|
## Release Block Rule
|
||||||
|
|
||||||
|
Any failing `U-*`, `I-*`, `E-*`, or `P-*` is release-blocking unless signed waiver exists.
|
||||||
|
|
@ -0,0 +1,661 @@
|
||||||
|
# Phase 0: Domain Model Validation
|
||||||
|
|
||||||
|
**Date**: 2026-02-12
|
||||||
|
**Status**: ✅ VALIDATED
|
||||||
|
**Reviewer**: Claude Sonnet 4.5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
This document validates the proposed canonical domain model from the redesign specification against the current MOPC Prisma schema. The validation confirms that all proposed entities, enums, and constraints are architecturally sound and can be implemented without conflicts.
|
||||||
|
|
||||||
|
**Result**: ✅ **APPROVED** - Domain model is complete, unambiguous, and ready for implementation in Phase 1.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Canonical Enums Validation
|
||||||
|
|
||||||
|
### 1.1 New Enums (To be Added)
|
||||||
|
|
||||||
|
| Enum Name | Values | Status | Notes |
|
||||||
|
|-----------|--------|--------|-------|
|
||||||
|
| **StageType** | `INTAKE`, `FILTER`, `EVALUATION`, `SELECTION`, `LIVE_FINAL`, `RESULTS` | ✅ Complete | Replaces implicit `RoundType` semantics |
|
||||||
|
| **TrackKind** | `MAIN`, `AWARD`, `SHOWCASE` | ✅ Complete | Enables first-class special awards |
|
||||||
|
| **RoutingMode** | `PARALLEL`, `EXCLUSIVE`, `POST_MAIN` | ✅ Complete | Controls award routing behavior |
|
||||||
|
| **StageStatus** | `DRAFT`, `ACTIVE`, `CLOSED`, `ARCHIVED` | ✅ Complete | Aligns with existing `RoundStatus` |
|
||||||
|
| **ProjectStageStateValue** | `PENDING`, `IN_PROGRESS`, `PASSED`, `REJECTED`, `ROUTED`, `COMPLETED`, `WITHDRAWN` | ✅ Complete | Explicit state machine for project progression |
|
||||||
|
| **DecisionMode** | `JURY_VOTE`, `AWARD_MASTER`, `ADMIN` | ✅ Complete | Award governance modes |
|
||||||
|
| **OverrideReasonCode** | `DATA_CORRECTION`, `POLICY_EXCEPTION`, `JURY_CONFLICT`, `SPONSOR_DECISION`, `ADMIN_DISCRETION` | ✅ Complete | Mandatory reason tracking for overrides |
|
||||||
|
|
||||||
|
**Validation Notes**:
|
||||||
|
- All enum values are mutually exclusive and unambiguous
|
||||||
|
- No conflicts with existing enums
|
||||||
|
- `StageStatus` deliberately mirrors `RoundStatus` for familiarity
|
||||||
|
- `ProjectStageStateValue` provides complete state coverage
|
||||||
|
|
||||||
|
### 1.2 Existing Enums (To be Extended or Deprecated)
|
||||||
|
|
||||||
|
| Current Enum | Action | Rationale |
|
||||||
|
|--------------|--------|-----------|
|
||||||
|
| **RoundType** | ⚠️ DEPRECATE in Phase 6 | Replaced by `StageType` + stage config |
|
||||||
|
| **RoundStatus** | ⚠️ DEPRECATE in Phase 6 | Replaced by `StageStatus` |
|
||||||
|
| **UserRole** | ✅ EXTEND | Add `AWARD_MASTER` and `AUDIENCE` values |
|
||||||
|
| **ProjectStatus** | ⚠️ DEPRECATE in Phase 6 | Replaced by `ProjectStageState` records |
|
||||||
|
|
||||||
|
**Action Items for Phase 1**:
|
||||||
|
- Add new enums to `prisma/schema.prisma`
|
||||||
|
- Extend `UserRole` with `AWARD_MASTER` and `AUDIENCE`
|
||||||
|
- Do NOT remove deprecated enums yet (Phase 6)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Core Entities Validation
|
||||||
|
|
||||||
|
### 2.1 New Core Models
|
||||||
|
|
||||||
|
#### Pipeline
|
||||||
|
```prisma
|
||||||
|
model Pipeline {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
programId String
|
||||||
|
name String
|
||||||
|
slug String @unique
|
||||||
|
status StageStatus @default(DRAFT)
|
||||||
|
settingsJson Json? @db.JsonB
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
program Program @relation(fields: [programId], references: [id])
|
||||||
|
tracks Track[]
|
||||||
|
routingRules RoutingRule[]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation**:
|
||||||
|
- ✅ All fields align with domain model spec
|
||||||
|
- ✅ `programId` FK ensures proper scoping
|
||||||
|
- ✅ `slug` unique constraint enables URL-friendly references
|
||||||
|
- ✅ `settingsJson` provides extensibility
|
||||||
|
- ✅ Relationships properly defined
|
||||||
|
|
||||||
|
#### Track
|
||||||
|
```prisma
|
||||||
|
model Track {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
pipelineId String
|
||||||
|
kind TrackKind @default(MAIN)
|
||||||
|
specialAwardId String? @unique
|
||||||
|
name String
|
||||||
|
slug String
|
||||||
|
sortOrder Int
|
||||||
|
routingModeDefault RoutingMode?
|
||||||
|
decisionMode DecisionMode?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
pipeline Pipeline @relation(fields: [pipelineId], references: [id], onDelete: Cascade)
|
||||||
|
specialAward SpecialAward? @relation(fields: [specialAwardId], references: [id])
|
||||||
|
stages Stage[]
|
||||||
|
projectStageStates ProjectStageState[]
|
||||||
|
routingRules RoutingRule[] @relation("DestinationTrack")
|
||||||
|
|
||||||
|
@@unique([pipelineId, slug])
|
||||||
|
@@index([pipelineId, sortOrder])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation**:
|
||||||
|
- ✅ `kind` determines track type (MAIN vs AWARD vs SHOWCASE)
|
||||||
|
- ✅ `specialAwardId` nullable for MAIN tracks, required for AWARD tracks
|
||||||
|
- ✅ `sortOrder` enables explicit ordering
|
||||||
|
- ✅ `routingModeDefault` and `decisionMode` provide award-specific config
|
||||||
|
- ✅ Unique constraint on `(pipelineId, slug)` prevents duplicates
|
||||||
|
- ✅ Index on `(pipelineId, sortOrder)` optimizes ordering queries
|
||||||
|
|
||||||
|
#### Stage
|
||||||
|
```prisma
|
||||||
|
model Stage {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
trackId String
|
||||||
|
stageType StageType
|
||||||
|
name String
|
||||||
|
slug String
|
||||||
|
sortOrder Int
|
||||||
|
status StageStatus @default(DRAFT)
|
||||||
|
configVersion Int @default(1)
|
||||||
|
configJson Json @db.JsonB
|
||||||
|
windowOpenAt DateTime?
|
||||||
|
windowCloseAt DateTime?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
track Track @relation(fields: [trackId], references: [id], onDelete: Cascade)
|
||||||
|
projectStageStates ProjectStageState[]
|
||||||
|
transitionsFrom StageTransition[] @relation("FromStage")
|
||||||
|
transitionsTo StageTransition[] @relation("ToStage")
|
||||||
|
cohorts Cohort[]
|
||||||
|
liveProgressCursor LiveProgressCursor?
|
||||||
|
|
||||||
|
@@unique([trackId, slug])
|
||||||
|
@@unique([trackId, sortOrder])
|
||||||
|
@@index([trackId, status])
|
||||||
|
@@index([status, windowOpenAt, windowCloseAt])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation**:
|
||||||
|
- ✅ `stageType` determines config schema (union type)
|
||||||
|
- ✅ `configVersion` enables config evolution
|
||||||
|
- ✅ `configJson` stores type-specific configuration
|
||||||
|
- ✅ `windowOpenAt`/`windowCloseAt` provide voting windows
|
||||||
|
- ✅ Unique constraints prevent duplicate `slug` or `sortOrder` per track
|
||||||
|
- ✅ Indexes optimize status and window queries
|
||||||
|
|
||||||
|
#### StageTransition
|
||||||
|
```prisma
|
||||||
|
model StageTransition {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
fromStageId String
|
||||||
|
toStageId String
|
||||||
|
priority Int @default(0)
|
||||||
|
isDefault Boolean @default(false)
|
||||||
|
guardJson Json? @db.JsonB
|
||||||
|
actionJson Json? @db.JsonB
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
fromStage Stage @relation("FromStage", fields: [fromStageId], references: [id], onDelete: Cascade)
|
||||||
|
toStage Stage @relation("ToStage", fields: [toStageId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@unique([fromStageId, toStageId])
|
||||||
|
@@index([fromStageId, priority])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation**:
|
||||||
|
- ✅ Explicit state machine definition
|
||||||
|
- ✅ `priority` enables deterministic tie-breaking
|
||||||
|
- ✅ `isDefault` marks default transition path
|
||||||
|
- ✅ `guardJson` and `actionJson` provide transition logic
|
||||||
|
- ✅ Unique constraint prevents duplicate transitions
|
||||||
|
- ✅ Index on `(fromStageId, priority)` optimizes transition lookup
|
||||||
|
|
||||||
|
#### ProjectStageState
|
||||||
|
```prisma
|
||||||
|
model ProjectStageState {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
projectId String
|
||||||
|
trackId String
|
||||||
|
stageId String
|
||||||
|
state ProjectStageStateValue
|
||||||
|
enteredAt DateTime @default(now())
|
||||||
|
exitedAt DateTime?
|
||||||
|
decisionRef String?
|
||||||
|
outcomeJson Json? @db.JsonB
|
||||||
|
|
||||||
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||||
|
track Track @relation(fields: [trackId], references: [id], onDelete: Cascade)
|
||||||
|
stage Stage @relation(fields: [stageId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@unique([projectId, trackId, stageId])
|
||||||
|
@@index([projectId, trackId, state])
|
||||||
|
@@index([stageId, state])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation**:
|
||||||
|
- ✅ Replaces single `roundId` pointer with explicit state records
|
||||||
|
- ✅ Unique constraint on `(projectId, trackId, stageId)` prevents duplicates
|
||||||
|
- ✅ `trackId` enables parallel track progression (main + awards)
|
||||||
|
- ✅ `state` provides current progression status
|
||||||
|
- ✅ `enteredAt`/`exitedAt` track state duration
|
||||||
|
- ✅ `decisionRef` links to decision audit
|
||||||
|
- ✅ `outcomeJson` stores stage-specific metadata
|
||||||
|
- ✅ Indexes optimize project queries and stage reporting
|
||||||
|
|
||||||
|
#### RoutingRule
|
||||||
|
```prisma
|
||||||
|
model RoutingRule {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
pipelineId String
|
||||||
|
scope String // 'GLOBAL' | 'TRACK' | 'STAGE'
|
||||||
|
predicateJson Json @db.JsonB
|
||||||
|
destinationTrackId String
|
||||||
|
destinationStageId String?
|
||||||
|
priority Int @default(0)
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
pipeline Pipeline @relation(fields: [pipelineId], references: [id], onDelete: Cascade)
|
||||||
|
destinationTrack Track @relation("DestinationTrack", fields: [destinationTrackId], references: [id])
|
||||||
|
|
||||||
|
@@index([pipelineId, isActive, priority])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation**:
|
||||||
|
- ✅ `scope` determines rule evaluation context
|
||||||
|
- ✅ `predicateJson` contains matching logic
|
||||||
|
- ✅ `destinationTrackId` required, `destinationStageId` optional
|
||||||
|
- ✅ `priority` enables deterministic rule ordering
|
||||||
|
- ✅ `isActive` allows toggling without deletion
|
||||||
|
- ✅ Index on `(pipelineId, isActive, priority)` optimizes rule lookup
|
||||||
|
|
||||||
|
### 2.2 Live Runtime Models
|
||||||
|
|
||||||
|
#### Cohort
|
||||||
|
```prisma
|
||||||
|
model Cohort {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
stageId String
|
||||||
|
name String
|
||||||
|
votingMode String // 'JURY' | 'AUDIENCE' | 'HYBRID'
|
||||||
|
isOpen Boolean @default(false)
|
||||||
|
windowOpenAt DateTime?
|
||||||
|
windowCloseAt DateTime?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
stage Stage @relation(fields: [stageId], references: [id], onDelete: Cascade)
|
||||||
|
cohortProjects CohortProject[]
|
||||||
|
|
||||||
|
@@index([stageId, isOpen])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation**:
|
||||||
|
- ✅ Groups projects for live voting
|
||||||
|
- ✅ `votingMode` determines who can vote
|
||||||
|
- ✅ `isOpen` controls voting acceptance
|
||||||
|
- ✅ `windowOpenAt`/`windowCloseAt` provide time bounds
|
||||||
|
- ✅ Index on `(stageId, isOpen)` optimizes active cohort queries
|
||||||
|
|
||||||
|
#### CohortProject
|
||||||
|
```prisma
|
||||||
|
model CohortProject {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
cohortId String
|
||||||
|
projectId String
|
||||||
|
sortOrder Int
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
cohort Cohort @relation(fields: [cohortId], references: [id], onDelete: Cascade)
|
||||||
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@unique([cohortId, projectId])
|
||||||
|
@@index([cohortId, sortOrder])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation**:
|
||||||
|
- ✅ Many-to-many join table for cohort membership
|
||||||
|
- ✅ `sortOrder` enables presentation ordering
|
||||||
|
- ✅ Unique constraint prevents duplicate membership
|
||||||
|
- ✅ Index on `(cohortId, sortOrder)` optimizes ordering queries
|
||||||
|
|
||||||
|
#### LiveProgressCursor
|
||||||
|
```prisma
|
||||||
|
model LiveProgressCursor {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
stageId String @unique
|
||||||
|
sessionId String
|
||||||
|
activeProjectId String?
|
||||||
|
activeOrderIndex Int?
|
||||||
|
updatedBy String
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
stage Stage @relation(fields: [stageId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@index([stageId, sessionId])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation**:
|
||||||
|
- ✅ Admin cursor as source of truth for live events
|
||||||
|
- ✅ `stageId` unique ensures one cursor per stage
|
||||||
|
- ✅ `sessionId` tracks live session
|
||||||
|
- ✅ `activeProjectId` and `activeOrderIndex` track current position
|
||||||
|
- ✅ `updatedBy` tracks admin actor
|
||||||
|
- ✅ Index on `(stageId, sessionId)` optimizes live queries
|
||||||
|
|
||||||
|
### 2.3 Governance Models
|
||||||
|
|
||||||
|
#### OverrideAction
|
||||||
|
```prisma
|
||||||
|
model OverrideAction {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
entityType String // 'PROJECT' | 'STAGE' | 'COHORT' | 'AWARD'
|
||||||
|
entityId String
|
||||||
|
oldValueJson Json? @db.JsonB
|
||||||
|
newValueJson Json @db.JsonB
|
||||||
|
reasonCode OverrideReasonCode
|
||||||
|
reasonText String
|
||||||
|
actedBy String
|
||||||
|
actedAt DateTime @default(now())
|
||||||
|
|
||||||
|
@@index([entityType, entityId, actedAt])
|
||||||
|
@@index([actedBy, actedAt])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation**:
|
||||||
|
- ✅ Immutable override audit trail
|
||||||
|
- ✅ `reasonCode` enum ensures valid reasons
|
||||||
|
- ✅ `reasonText` captures human explanation
|
||||||
|
- ✅ `actedBy` tracks actor
|
||||||
|
- ✅ Indexes optimize entity and actor queries
|
||||||
|
|
||||||
|
#### DecisionAuditLog
|
||||||
|
```prisma
|
||||||
|
model DecisionAuditLog {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
entityType String // 'STAGE' | 'ROUTING' | 'FILTERING' | 'ASSIGNMENT' | 'LIVE' | 'AWARD'
|
||||||
|
entityId String
|
||||||
|
eventType String // 'stage.transitioned' | 'routing.executed' | etc.
|
||||||
|
payloadJson Json @db.JsonB
|
||||||
|
actorId String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
@@index([entityType, entityId, createdAt])
|
||||||
|
@@index([eventType, createdAt])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation**:
|
||||||
|
- ✅ Append-only audit log for all decisions
|
||||||
|
- ✅ `eventType` aligns with event taxonomy
|
||||||
|
- ✅ `payloadJson` captures full event context
|
||||||
|
- ✅ `actorId` nullable for system events
|
||||||
|
- ✅ Indexes optimize entity timeline and event queries
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Constraint Rules Validation
|
||||||
|
|
||||||
|
### 3.1 Unique Constraints
|
||||||
|
|
||||||
|
| Model | Constraint | Status | Purpose |
|
||||||
|
|-------|-----------|--------|---------|
|
||||||
|
| Pipeline | `slug` | ✅ Valid | URL-friendly unique identifier |
|
||||||
|
| Track | `(pipelineId, slug)` | ✅ Valid | Prevent duplicate slugs per pipeline |
|
||||||
|
| Track | `(pipelineId, sortOrder)` | ❌ MISSING | **Correction needed**: Add unique constraint per domain model spec |
|
||||||
|
| Stage | `(trackId, slug)` | ✅ Valid | Prevent duplicate slugs per track |
|
||||||
|
| Stage | `(trackId, sortOrder)` | ✅ Valid | Prevent duplicate sort orders per track |
|
||||||
|
| StageTransition | `(fromStageId, toStageId)` | ✅ Valid | Prevent duplicate transitions |
|
||||||
|
| ProjectStageState | `(projectId, trackId, stageId)` | ✅ Valid | One state record per project/track/stage combo |
|
||||||
|
| CohortProject | `(cohortId, projectId)` | ✅ Valid | Prevent duplicate cohort membership |
|
||||||
|
|
||||||
|
**Action Items for Phase 1**:
|
||||||
|
- ✅ Most constraints align with spec
|
||||||
|
- ⚠️ **Add**: Unique constraint on `Track(pipelineId, sortOrder)` per domain model requirement
|
||||||
|
|
||||||
|
### 3.2 Foreign Key Constraints
|
||||||
|
|
||||||
|
All FK relationships validated:
|
||||||
|
- ✅ Cascade deletes properly configured
|
||||||
|
- ✅ Referential integrity preserved
|
||||||
|
- ✅ Nullable FKs appropriately marked
|
||||||
|
|
||||||
|
### 3.3 Index Priorities
|
||||||
|
|
||||||
|
All required indexes from domain model spec are present:
|
||||||
|
1. ✅ `ProjectStageState(projectId, trackId, state)`
|
||||||
|
2. ✅ `ProjectStageState(stageId, state)`
|
||||||
|
3. ✅ `RoutingRule(pipelineId, isActive, priority)`
|
||||||
|
4. ✅ `StageTransition(fromStageId, priority)`
|
||||||
|
5. ✅ `LiveProgressCursor(stageId, sessionId)`
|
||||||
|
6. ✅ `DecisionAuditLog(entityType, entityId, createdAt)`
|
||||||
|
|
||||||
|
**Additional indexes added**:
|
||||||
|
- `Track(pipelineId, sortOrder)` - optimizes track ordering
|
||||||
|
- `Stage(trackId, status)` - optimizes status filtering
|
||||||
|
- `Stage(status, windowOpenAt, windowCloseAt)` - optimizes window queries
|
||||||
|
- `Cohort(stageId, isOpen)` - optimizes active cohort queries
|
||||||
|
- `CohortProject(cohortId, sortOrder)` - optimizes presentation ordering
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. JSON Field Contracts Validation
|
||||||
|
|
||||||
|
### 4.1 Pipeline.settingsJson
|
||||||
|
**Purpose**: Pipeline-level configuration
|
||||||
|
**Expected Schema**:
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
notificationDefaults?: {
|
||||||
|
enabled: boolean;
|
||||||
|
channels: string[];
|
||||||
|
};
|
||||||
|
aiConfig?: {
|
||||||
|
filteringEnabled: boolean;
|
||||||
|
assignmentEnabled: boolean;
|
||||||
|
};
|
||||||
|
// ... extensible
|
||||||
|
}
|
||||||
|
```
|
||||||
|
**Status**: ✅ Flexible, extensible design
|
||||||
|
|
||||||
|
### 4.2 Stage.configJson
|
||||||
|
**Purpose**: Stage-type-specific configuration (union type)
|
||||||
|
**Expected Schemas** (by `stageType`):
|
||||||
|
|
||||||
|
**INTAKE**:
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
fileRequirements: FileRequirement[];
|
||||||
|
deadlinePolicy: 'strict' | 'flexible';
|
||||||
|
lateSubmissionAllowed: boolean;
|
||||||
|
teamInvitePolicy: {...};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**FILTER**:
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
deterministicGates: Gate[];
|
||||||
|
aiRubric: {...};
|
||||||
|
confidenceThresholds: {...};
|
||||||
|
manualQueuePolicy: {...};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**EVALUATION**:
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
criteria: Criterion[];
|
||||||
|
assignmentStrategy: {...};
|
||||||
|
reviewThresholds: {...};
|
||||||
|
coiPolicy: {...};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**SELECTION**:
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
rankingSource: 'scores' | 'votes' | 'hybrid';
|
||||||
|
finalistTarget: number;
|
||||||
|
promotionMode: 'auto_top_n' | 'hybrid' | 'manual';
|
||||||
|
overridePermissions: {...};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**LIVE_FINAL**:
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
sessionBehavior: {...};
|
||||||
|
juryVotingConfig: {...};
|
||||||
|
audienceVotingConfig: {...};
|
||||||
|
cohortPolicy: {...};
|
||||||
|
revealPolicy: {...};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**RESULTS**:
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
rankingWeightRules: {...};
|
||||||
|
publicationPolicy: {...};
|
||||||
|
winnerOverrideRules: {...};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Status**: ✅ Complete coverage, all stage types defined
|
||||||
|
|
||||||
|
### 4.3 ProjectStageState.outcomeJson
|
||||||
|
**Purpose**: Stage-specific outcome metadata
|
||||||
|
**Expected Schema**:
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
scores?: Record<string, number>;
|
||||||
|
decision?: string;
|
||||||
|
feedback?: string;
|
||||||
|
aiConfidence?: number;
|
||||||
|
manualReview?: boolean;
|
||||||
|
// ... extensible per stage type
|
||||||
|
}
|
||||||
|
```
|
||||||
|
**Status**: ✅ Flexible, extensible design
|
||||||
|
|
||||||
|
### 4.4 StageTransition.guardJson / actionJson
|
||||||
|
**Purpose**: Transition logic and side effects
|
||||||
|
**Expected Schemas**:
|
||||||
|
|
||||||
|
**guardJson**:
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
conditions: Array<{
|
||||||
|
field: string;
|
||||||
|
operator: 'eq' | 'gt' | 'lt' | 'in' | 'exists';
|
||||||
|
value: any;
|
||||||
|
}>;
|
||||||
|
requireAll: boolean;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**actionJson**:
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
actions: Array<{
|
||||||
|
type: 'notify' | 'update_field' | 'emit_event';
|
||||||
|
config: any;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
**Status**: ✅ Provides transition programmability
|
||||||
|
|
||||||
|
### 4.5 RoutingRule.predicateJson
|
||||||
|
**Purpose**: Rule matching logic
|
||||||
|
**Expected Schema**:
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
conditions: Array<{
|
||||||
|
field: string; // e.g., 'project.category', 'project.tags'
|
||||||
|
operator: 'eq' | 'in' | 'contains' | 'matches';
|
||||||
|
value: any;
|
||||||
|
}>;
|
||||||
|
matchAll: boolean;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
**Status**: ✅ Deterministic rule matching
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Data Initialization Rules Validation
|
||||||
|
|
||||||
|
### 5.1 Seed Requirements
|
||||||
|
|
||||||
|
**From Phase 1 spec**:
|
||||||
|
- ✅ Every seeded project must start with one intake-stage state
|
||||||
|
- ✅ Seed must include main track plus at least two award tracks with different routing modes
|
||||||
|
- ✅ Seed must include representative roles: admins, jury, applicants, observer, audience contexts
|
||||||
|
|
||||||
|
**Validation**:
|
||||||
|
- Seed requirements are clear and achievable
|
||||||
|
- Will be implemented in `prisma/seed.ts` during Phase 1
|
||||||
|
|
||||||
|
### 5.2 Integrity Checks
|
||||||
|
|
||||||
|
**Required checks** (from schema-spec.md):
|
||||||
|
- ✅ No orphan states
|
||||||
|
- ✅ No invalid transition targets across pipelines
|
||||||
|
- ✅ No duplicate active state rows for same `(project, track, stage)`
|
||||||
|
|
||||||
|
**Validation**:
|
||||||
|
- Integrity check SQL will be created in Phase 1
|
||||||
|
- Constraints and indexes prevent most integrity violations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Compatibility with Existing Models
|
||||||
|
|
||||||
|
### 6.1 Models That Remain Unchanged
|
||||||
|
- ✅ `User` - No changes needed
|
||||||
|
- ✅ `Program` - Gains `Pipeline` relation
|
||||||
|
- ✅ `Project` - Gains `ProjectStageState` relation, deprecates `roundId`
|
||||||
|
- ✅ `SpecialAward` - Gains `Track` relation
|
||||||
|
- ✅ `Evaluation` - No changes to core model
|
||||||
|
- ✅ `Assignment` - May need `stageId` addition (Phase 2)
|
||||||
|
|
||||||
|
### 6.2 Models to be Deprecated (Phase 6)
|
||||||
|
- ⚠️ `Round` - Replaced by `Pipeline` + `Track` + `Stage`
|
||||||
|
- ⚠️ Round-specific relations will be refactored
|
||||||
|
|
||||||
|
### 6.3 Integration Points
|
||||||
|
- ✅ `User.role` extends to include `AWARD_MASTER` and `AUDIENCE`
|
||||||
|
- ✅ `Program` gains `pipelines` relation
|
||||||
|
- ✅ `Project` gains `projectStageStates` relation
|
||||||
|
- ✅ `SpecialAward` gains `track` relation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Validation Summary
|
||||||
|
|
||||||
|
### ✅ Approved Elements
|
||||||
|
1. All 7 new canonical enums are complete and unambiguous
|
||||||
|
2. All 12 new core models align with domain model spec
|
||||||
|
3. All unique constraints match spec requirements (1 minor correction needed)
|
||||||
|
4. All foreign key relationships properly defined
|
||||||
|
5. All required indexes present (plus beneficial additions)
|
||||||
|
6. JSON field contracts provide flexibility and extensibility
|
||||||
|
7. Compatibility with existing models maintained
|
||||||
|
|
||||||
|
### ⚠️ Corrections Needed for Phase 1
|
||||||
|
1. **Track**: Add unique constraint on `(pipelineId, sortOrder)` to match spec
|
||||||
|
2. **UserRole**: Extend enum to add `AWARD_MASTER` and `AUDIENCE` values
|
||||||
|
|
||||||
|
### 📋 Action Items for Phase 1
|
||||||
|
- [ ] Implement all 7 new enums in `prisma/schema.prisma`
|
||||||
|
- [ ] Implement all 12 new models with proper constraints
|
||||||
|
- [ ] Extend `UserRole` enum with new values
|
||||||
|
- [ ] Add unique constraint on `Track(pipelineId, sortOrder)`
|
||||||
|
- [ ] Verify all indexes are created
|
||||||
|
- [ ] Create seed data with required representative examples
|
||||||
|
- [ ] Implement integrity check SQL queries
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Conclusion
|
||||||
|
|
||||||
|
**Status**: ✅ **DOMAIN MODEL VALIDATED AND APPROVED**
|
||||||
|
|
||||||
|
The proposed canonical domain model is architecturally sound, complete, and ready for implementation. All entities, enums, and constraints have been validated against both the design specification and the current MOPC codebase.
|
||||||
|
|
||||||
|
**Key Strengths**:
|
||||||
|
- Explicit state machine eliminates implicit round progression logic
|
||||||
|
- First-class award tracks enable flexible routing and governance
|
||||||
|
- JSON config fields provide extensibility without schema migrations
|
||||||
|
- Comprehensive audit trail ensures governance and explainability
|
||||||
|
- Index strategy optimizes common query patterns
|
||||||
|
|
||||||
|
**Minor Corrections**:
|
||||||
|
- 1 unique constraint addition needed (`Track.sortOrder`)
|
||||||
|
- 2 enum values to be added to existing `UserRole`
|
||||||
|
|
||||||
|
**Next Step**: Proceed to Phase 1 schema implementation with confidence that the domain model is solid.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Signed**: Claude Sonnet 4.5
|
||||||
|
**Date**: 2026-02-12
|
||||||
|
|
@ -47,6 +47,7 @@
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.0.4",
|
"cmdk": "^1.0.4",
|
||||||
|
"csv-parse": "^6.1.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"jspdf": "^4.1.0",
|
"jspdf": "^4.1.0",
|
||||||
|
|
@ -6085,6 +6086,12 @@
|
||||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/csv-parse": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-CEE+jwpgLn+MmtCpVcPtiCZpVtB6Z2OKPTr34pycYYoL7sxdOkXDdQ4lRiw6ioC0q6BLqhc6cKweCVvral8yhw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/d3-array": {
|
"node_modules/d3-array": {
|
||||||
"version": "3.2.4",
|
"version": "3.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.0.4",
|
"cmdk": "^1.0.4",
|
||||||
|
"csv-parse": "^6.1.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"jspdf": "^4.1.0",
|
"jspdf": "^4.1.0",
|
||||||
|
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
import { PrismaClient } from '@prisma/client'
|
|
||||||
|
|
||||||
const prisma = new PrismaClient()
|
|
||||||
|
|
||||||
async function check() {
|
|
||||||
const projectCount = await prisma.project.count()
|
|
||||||
console.log('Total projects:', projectCount)
|
|
||||||
|
|
||||||
const rounds = await prisma.round.findMany({
|
|
||||||
include: {
|
|
||||||
_count: { select: { projects: true } }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
for (const r of rounds) {
|
|
||||||
console.log(`Round: ${r.name} (id: ${r.id})`)
|
|
||||||
console.log(` Projects: ${r._count.projects}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check sample projects with their round
|
|
||||||
const sampleProjects = await prisma.project.findMany({
|
|
||||||
select: { id: true, title: true, roundId: true },
|
|
||||||
take: 5
|
|
||||||
})
|
|
||||||
console.log('\nSample projects:')
|
|
||||||
for (const p of sampleProjects) {
|
|
||||||
console.log(` ${p.title}: roundId=${p.roundId}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
check()
|
|
||||||
.then(() => prisma.$disconnect())
|
|
||||||
.catch(async (e) => {
|
|
||||||
console.error(e)
|
|
||||||
await prisma.$disconnect()
|
|
||||||
})
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
import { PrismaClient } from '@prisma/client'
|
|
||||||
|
|
||||||
const prisma = new PrismaClient()
|
|
||||||
|
|
||||||
async function cleanup() {
|
|
||||||
console.log('Checking all rounds...\n')
|
|
||||||
|
|
||||||
const rounds = await prisma.round.findMany({
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
slug: true,
|
|
||||||
projects: { select: { id: true, title: true } },
|
|
||||||
_count: { select: { projects: true } }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(`Found ${rounds.length} rounds:`)
|
|
||||||
for (const round of rounds) {
|
|
||||||
console.log(`- ${round.name} (slug: ${round.slug}): ${round._count.projects} projects`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find rounds with 9 or fewer projects (dummy data)
|
|
||||||
const dummyRounds = rounds.filter(r => r._count.projects <= 9)
|
|
||||||
|
|
||||||
if (dummyRounds.length > 0) {
|
|
||||||
console.log(`\nDeleting ${dummyRounds.length} dummy round(s)...`)
|
|
||||||
|
|
||||||
for (const round of dummyRounds) {
|
|
||||||
console.log(`\nProcessing: ${round.name}`)
|
|
||||||
|
|
||||||
const projectIds = round.projects.map(p => p.id)
|
|
||||||
|
|
||||||
if (projectIds.length > 0) {
|
|
||||||
// Delete team members
|
|
||||||
const teamDeleted = await prisma.teamMember.deleteMany({
|
|
||||||
where: { projectId: { in: projectIds } }
|
|
||||||
})
|
|
||||||
console.log(` Deleted ${teamDeleted.count} team members`)
|
|
||||||
|
|
||||||
// Delete projects
|
|
||||||
const projDeleted = await prisma.project.deleteMany({
|
|
||||||
where: { id: { in: projectIds } }
|
|
||||||
})
|
|
||||||
console.log(` Deleted ${projDeleted.count} projects`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the round
|
|
||||||
await prisma.round.delete({ where: { id: round.id } })
|
|
||||||
console.log(` Deleted round: ${round.name}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Summary
|
|
||||||
const remaining = await prisma.round.count()
|
|
||||||
const projects = await prisma.project.count()
|
|
||||||
console.log(`\n✅ Cleanup complete!`)
|
|
||||||
console.log(` Remaining rounds: ${remaining}`)
|
|
||||||
console.log(` Total projects: ${projects}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
.then(() => prisma.$disconnect())
|
|
||||||
.catch(async (e) => {
|
|
||||||
console.error(e)
|
|
||||||
await prisma.$disconnect()
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
import { PrismaClient } from '@prisma/client'
|
|
||||||
|
|
||||||
const prisma = new PrismaClient()
|
|
||||||
|
|
||||||
async function cleanup() {
|
|
||||||
console.log('Cleaning up dummy data...\n')
|
|
||||||
|
|
||||||
// Find and delete the dummy round
|
|
||||||
const dummyRound = await prisma.round.findFirst({
|
|
||||||
where: { slug: 'round-1-2026' },
|
|
||||||
include: { projects: true }
|
|
||||||
})
|
|
||||||
|
|
||||||
if (dummyRound) {
|
|
||||||
console.log(`Found dummy round: ${dummyRound.name}`)
|
|
||||||
console.log(`Projects in round: ${dummyRound.projects.length}`)
|
|
||||||
|
|
||||||
// Get project IDs to delete
|
|
||||||
const projectIds = dummyRound.projects.map(p => p.id)
|
|
||||||
|
|
||||||
// Delete team members for these projects
|
|
||||||
if (projectIds.length > 0) {
|
|
||||||
const teamDeleted = await prisma.teamMember.deleteMany({
|
|
||||||
where: { projectId: { in: projectIds } }
|
|
||||||
})
|
|
||||||
console.log(`Deleted ${teamDeleted.count} team members`)
|
|
||||||
|
|
||||||
// Delete the projects
|
|
||||||
const projDeleted = await prisma.project.deleteMany({
|
|
||||||
where: { id: { in: projectIds } }
|
|
||||||
})
|
|
||||||
console.log(`Deleted ${projDeleted.count} dummy projects`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the round
|
|
||||||
await prisma.round.delete({ where: { id: dummyRound.id } })
|
|
||||||
console.log('Deleted dummy round')
|
|
||||||
} else {
|
|
||||||
console.log('No dummy round found')
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\nCleanup complete!')
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
.then(() => prisma.$disconnect())
|
|
||||||
.catch(async (e) => {
|
|
||||||
console.error(e)
|
|
||||||
await prisma.$disconnect()
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
|
|
@ -0,0 +1,246 @@
|
||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
|
||||||
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
|
interface CheckResult {
|
||||||
|
name: string
|
||||||
|
passed: boolean
|
||||||
|
details: string
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runChecks(): Promise<CheckResult[]> {
|
||||||
|
const results: CheckResult[] = []
|
||||||
|
|
||||||
|
// 1. No orphan ProjectStageState (every PSS references valid project, track, stage)
|
||||||
|
const orphanStates = await prisma.$queryRaw<{ count: bigint }[]>`
|
||||||
|
SELECT COUNT(*) as count FROM "ProjectStageState" pss
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM "Project" p WHERE p.id = pss."projectId")
|
||||||
|
OR NOT EXISTS (SELECT 1 FROM "Track" t WHERE t.id = pss."trackId")
|
||||||
|
OR NOT EXISTS (SELECT 1 FROM "Stage" s WHERE s.id = pss."stageId")
|
||||||
|
`
|
||||||
|
const orphanCount = Number(orphanStates[0]?.count ?? 0)
|
||||||
|
results.push({
|
||||||
|
name: 'No orphan ProjectStageState',
|
||||||
|
passed: orphanCount === 0,
|
||||||
|
details: orphanCount === 0 ? 'All PSS records reference valid entities' : `Found ${orphanCount} orphan records`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 2. Every project has at least one stage state
|
||||||
|
const projectsWithoutState = await prisma.$queryRaw<{ count: bigint }[]>`
|
||||||
|
SELECT COUNT(*) as count FROM "Project" p
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM "ProjectStageState" pss WHERE pss."projectId" = p.id)
|
||||||
|
`
|
||||||
|
const noStateCount = Number(projectsWithoutState[0]?.count ?? 0)
|
||||||
|
const totalProjects = await prisma.project.count()
|
||||||
|
results.push({
|
||||||
|
name: 'Every project has at least one stage state',
|
||||||
|
passed: noStateCount === 0,
|
||||||
|
details: noStateCount === 0
|
||||||
|
? `All ${totalProjects} projects have stage states`
|
||||||
|
: `${noStateCount} projects missing stage states`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 3. No duplicate active states per (project, track, stage)
|
||||||
|
const duplicateStates = await prisma.$queryRaw<{ count: bigint }[]>`
|
||||||
|
SELECT COUNT(*) as count FROM (
|
||||||
|
SELECT "projectId", "trackId", "stageId", COUNT(*) as cnt
|
||||||
|
FROM "ProjectStageState"
|
||||||
|
WHERE "exitedAt" IS NULL
|
||||||
|
GROUP BY "projectId", "trackId", "stageId"
|
||||||
|
HAVING COUNT(*) > 1
|
||||||
|
) dupes
|
||||||
|
`
|
||||||
|
const dupeCount = Number(duplicateStates[0]?.count ?? 0)
|
||||||
|
results.push({
|
||||||
|
name: 'No duplicate active states per (project, track, stage)',
|
||||||
|
passed: dupeCount === 0,
|
||||||
|
details: dupeCount === 0 ? 'No duplicates found' : `Found ${dupeCount} duplicate active states`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 4. All transitions stay within same pipeline
|
||||||
|
const crossPipelineTransitions = await prisma.$queryRaw<{ count: bigint }[]>`
|
||||||
|
SELECT COUNT(*) as count FROM "StageTransition" st
|
||||||
|
JOIN "Stage" sf ON sf.id = st."fromStageId"
|
||||||
|
JOIN "Track" tf ON tf.id = sf."trackId"
|
||||||
|
JOIN "Stage" sto ON sto.id = st."toStageId"
|
||||||
|
JOIN "Track" tt ON tt.id = sto."trackId"
|
||||||
|
WHERE tf."pipelineId" != tt."pipelineId"
|
||||||
|
`
|
||||||
|
const crossCount = Number(crossPipelineTransitions[0]?.count ?? 0)
|
||||||
|
results.push({
|
||||||
|
name: 'All transitions stay within same pipeline',
|
||||||
|
passed: crossCount === 0,
|
||||||
|
details: crossCount === 0 ? 'All transitions are within pipeline' : `Found ${crossCount} cross-pipeline transitions`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 5. Stage sortOrder unique per track
|
||||||
|
const duplicateSortOrders = await prisma.$queryRaw<{ count: bigint }[]>`
|
||||||
|
SELECT COUNT(*) as count FROM (
|
||||||
|
SELECT "trackId", "sortOrder", COUNT(*) as cnt
|
||||||
|
FROM "Stage"
|
||||||
|
GROUP BY "trackId", "sortOrder"
|
||||||
|
HAVING COUNT(*) > 1
|
||||||
|
) dupes
|
||||||
|
`
|
||||||
|
const dupeSortCount = Number(duplicateSortOrders[0]?.count ?? 0)
|
||||||
|
results.push({
|
||||||
|
name: 'Stage sortOrder unique per track',
|
||||||
|
passed: dupeSortCount === 0,
|
||||||
|
details: dupeSortCount === 0 ? 'All sort orders unique' : `Found ${dupeSortCount} duplicate sort orders`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 6. Track sortOrder unique per pipeline
|
||||||
|
const duplicateTrackOrders = await prisma.$queryRaw<{ count: bigint }[]>`
|
||||||
|
SELECT COUNT(*) as count FROM (
|
||||||
|
SELECT "pipelineId", "sortOrder", COUNT(*) as cnt
|
||||||
|
FROM "Track"
|
||||||
|
GROUP BY "pipelineId", "sortOrder"
|
||||||
|
HAVING COUNT(*) > 1
|
||||||
|
) dupes
|
||||||
|
`
|
||||||
|
const dupeTrackCount = Number(duplicateTrackOrders[0]?.count ?? 0)
|
||||||
|
results.push({
|
||||||
|
name: 'Track sortOrder unique per pipeline',
|
||||||
|
passed: dupeTrackCount === 0,
|
||||||
|
details: dupeTrackCount === 0 ? 'All track orders unique' : `Found ${dupeTrackCount} duplicate track orders`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 7. Every Pipeline has at least one Track; every Track has at least one Stage
|
||||||
|
const emptyPipelines = await prisma.$queryRaw<{ count: bigint }[]>`
|
||||||
|
SELECT COUNT(*) as count FROM "Pipeline" p
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM "Track" t WHERE t."pipelineId" = p.id)
|
||||||
|
`
|
||||||
|
const emptyPipelineCount = Number(emptyPipelines[0]?.count ?? 0)
|
||||||
|
const emptyTracks = await prisma.$queryRaw<{ count: bigint }[]>`
|
||||||
|
SELECT COUNT(*) as count FROM "Track" t
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM "Stage" s WHERE s."trackId" = t.id)
|
||||||
|
`
|
||||||
|
const emptyTrackCount = Number(emptyTracks[0]?.count ?? 0)
|
||||||
|
results.push({
|
||||||
|
name: 'Every Pipeline has Tracks; every Track has Stages',
|
||||||
|
passed: emptyPipelineCount === 0 && emptyTrackCount === 0,
|
||||||
|
details: emptyPipelineCount === 0 && emptyTrackCount === 0
|
||||||
|
? 'All pipelines have tracks and all tracks have stages'
|
||||||
|
: `${emptyPipelineCount} empty pipelines, ${emptyTrackCount} empty tracks`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 8. RoutingRule destinations reference valid tracks in same pipeline
|
||||||
|
const badRoutingRules = await prisma.$queryRaw<{ count: bigint }[]>`
|
||||||
|
SELECT COUNT(*) as count FROM "RoutingRule" rr
|
||||||
|
WHERE rr."destinationTrackId" IS NOT NULL
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM "Track" t
|
||||||
|
WHERE t.id = rr."destinationTrackId"
|
||||||
|
AND t."pipelineId" = rr."pipelineId"
|
||||||
|
)
|
||||||
|
`
|
||||||
|
const badRouteCount = Number(badRoutingRules[0]?.count ?? 0)
|
||||||
|
results.push({
|
||||||
|
name: 'RoutingRule destinations reference valid tracks in same pipeline',
|
||||||
|
passed: badRouteCount === 0,
|
||||||
|
details: badRouteCount === 0
|
||||||
|
? 'All routing rules reference valid destination tracks'
|
||||||
|
: `Found ${badRouteCount} routing rules with invalid destinations`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 9. LiveProgressCursor references valid stage
|
||||||
|
const badCursors = await prisma.$queryRaw<{ count: bigint }[]>`
|
||||||
|
SELECT COUNT(*) as count FROM "LiveProgressCursor" lpc
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM "Stage" s WHERE s.id = lpc."stageId")
|
||||||
|
`
|
||||||
|
const badCursorCount = Number(badCursors[0]?.count ?? 0)
|
||||||
|
results.push({
|
||||||
|
name: 'LiveProgressCursor references valid stage',
|
||||||
|
passed: badCursorCount === 0,
|
||||||
|
details: badCursorCount === 0
|
||||||
|
? 'All cursors reference valid stages'
|
||||||
|
: `Found ${badCursorCount} cursors with invalid stage references`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 10. Cohort references valid stage
|
||||||
|
const badCohorts = await prisma.$queryRaw<{ count: bigint }[]>`
|
||||||
|
SELECT COUNT(*) as count FROM "Cohort" c
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM "Stage" s WHERE s.id = c."stageId")
|
||||||
|
`
|
||||||
|
const badCohortCount = Number(badCohorts[0]?.count ?? 0)
|
||||||
|
results.push({
|
||||||
|
name: 'Cohort references valid stage',
|
||||||
|
passed: badCohortCount === 0,
|
||||||
|
details: badCohortCount === 0
|
||||||
|
? 'All cohorts reference valid stages'
|
||||||
|
: `Found ${badCohortCount} cohorts with invalid stage references`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 11. Every EvaluationForm has a valid stageId
|
||||||
|
const badEvalForms = await prisma.$queryRaw<{ count: bigint }[]>`
|
||||||
|
SELECT COUNT(*) as count FROM "EvaluationForm" ef
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM "Stage" s WHERE s.id = ef."stageId")
|
||||||
|
`
|
||||||
|
const badFormCount = Number(badEvalForms[0]?.count ?? 0)
|
||||||
|
results.push({
|
||||||
|
name: 'Every EvaluationForm references valid stage',
|
||||||
|
passed: badFormCount === 0,
|
||||||
|
details: badFormCount === 0
|
||||||
|
? 'All evaluation forms reference valid stages'
|
||||||
|
: `Found ${badFormCount} forms with invalid stage references`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 12. Every FileRequirement has a valid stageId
|
||||||
|
const badFileReqs = await prisma.$queryRaw<{ count: bigint }[]>`
|
||||||
|
SELECT COUNT(*) as count FROM "FileRequirement" fr
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM "Stage" s WHERE s.id = fr."stageId")
|
||||||
|
`
|
||||||
|
const badFileReqCount = Number(badFileReqs[0]?.count ?? 0)
|
||||||
|
results.push({
|
||||||
|
name: 'Every FileRequirement references valid stage',
|
||||||
|
passed: badFileReqCount === 0,
|
||||||
|
details: badFileReqCount === 0
|
||||||
|
? 'All file requirements reference valid stages'
|
||||||
|
: `Found ${badFileReqCount} file requirements with invalid stage references`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 13. Count validation
|
||||||
|
const projectCountResult = await prisma.project.count()
|
||||||
|
const stageCount = await prisma.stage.count()
|
||||||
|
const trackCount = await prisma.track.count()
|
||||||
|
const pipelineCount = await prisma.pipeline.count()
|
||||||
|
const pssCount = await prisma.projectStageState.count()
|
||||||
|
results.push({
|
||||||
|
name: 'Count validation',
|
||||||
|
passed: projectCountResult > 0 && stageCount > 0 && trackCount > 0,
|
||||||
|
details: `Pipelines: ${pipelineCount}, Tracks: ${trackCount}, Stages: ${stageCount}, Projects: ${projectCountResult}, StageStates: ${pssCount}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('🔍 Running integrity checks...\n')
|
||||||
|
|
||||||
|
const results = await runChecks()
|
||||||
|
|
||||||
|
let allPassed = true
|
||||||
|
for (const result of results) {
|
||||||
|
const icon = result.passed ? '✅' : '❌'
|
||||||
|
console.log(`${icon} ${result.name}`)
|
||||||
|
console.log(` ${result.details}\n`)
|
||||||
|
if (!result.passed) allPassed = false
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('='.repeat(50))
|
||||||
|
if (allPassed) {
|
||||||
|
console.log('✅ All integrity checks passed!')
|
||||||
|
} else {
|
||||||
|
console.log('❌ Some integrity checks failed!')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.catch((e) => {
|
||||||
|
console.error('❌ Integrity check failed:', e)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
.finally(async () => {
|
||||||
|
await prisma.$disconnect()
|
||||||
|
})
|
||||||
1465
prisma/schema.prisma
1465
prisma/schema.prisma
File diff suppressed because it is too large
Load Diff
|
|
@ -1,510 +0,0 @@
|
||||||
import { PrismaClient, CompetitionCategory, OceanIssue, TeamMemberRole } from '@prisma/client'
|
|
||||||
import * as fs from 'fs'
|
|
||||||
import * as path from 'path'
|
|
||||||
import { fileURLToPath } from 'url'
|
|
||||||
import Papa from 'papaparse'
|
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url)
|
|
||||||
const __dirname = path.dirname(__filename)
|
|
||||||
|
|
||||||
const prisma = new PrismaClient()
|
|
||||||
|
|
||||||
// CSV Column Mapping
|
|
||||||
interface CandidatureRow {
|
|
||||||
'Full name': string
|
|
||||||
'Application status': string
|
|
||||||
'Category': string
|
|
||||||
'Comment ': string // Note the space after 'Comment'
|
|
||||||
'Country': string
|
|
||||||
'Date of creation': string
|
|
||||||
'E-mail': string
|
|
||||||
'How did you hear about MOPC?': string
|
|
||||||
'Issue': string
|
|
||||||
'Jury 1 attribués': string
|
|
||||||
'MOPC team comments': string
|
|
||||||
'Mentorship': string
|
|
||||||
'PHASE 1 - Submission': string
|
|
||||||
'PHASE 2 - Submission': string
|
|
||||||
"Project's name": string
|
|
||||||
'Team members': string
|
|
||||||
'Tri par zone': string
|
|
||||||
'Téléphone': string
|
|
||||||
'University': string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map CSV category strings to enum values
|
|
||||||
function mapCategory(category: string): CompetitionCategory | null {
|
|
||||||
if (!category) return null
|
|
||||||
const lower = category.toLowerCase()
|
|
||||||
if (lower.includes('start-up') || lower.includes('startup')) {
|
|
||||||
return 'STARTUP'
|
|
||||||
}
|
|
||||||
if (lower.includes('business concept')) {
|
|
||||||
return 'BUSINESS_CONCEPT'
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map CSV issue strings to enum values
|
|
||||||
function mapOceanIssue(issue: string): OceanIssue | null {
|
|
||||||
if (!issue) return null
|
|
||||||
const lower = issue.toLowerCase()
|
|
||||||
|
|
||||||
if (lower.includes('pollution')) return 'POLLUTION_REDUCTION'
|
|
||||||
if (lower.includes('climate') || lower.includes('sea-level')) return 'CLIMATE_MITIGATION'
|
|
||||||
if (lower.includes('technology') || lower.includes('innovation')) return 'TECHNOLOGY_INNOVATION'
|
|
||||||
if (lower.includes('shipping') || lower.includes('yachting')) return 'SUSTAINABLE_SHIPPING'
|
|
||||||
if (lower.includes('blue carbon')) return 'BLUE_CARBON'
|
|
||||||
if (lower.includes('habitat') || lower.includes('restoration') || lower.includes('ecosystem')) return 'HABITAT_RESTORATION'
|
|
||||||
if (lower.includes('community') || lower.includes('capacity') || lower.includes('coastal')) return 'COMMUNITY_CAPACITY'
|
|
||||||
if (lower.includes('fishing') || lower.includes('aquaculture') || lower.includes('blue food')) return 'SUSTAINABLE_FISHING'
|
|
||||||
if (lower.includes('awareness') || lower.includes('education') || lower.includes('consumer')) return 'CONSUMER_AWARENESS'
|
|
||||||
if (lower.includes('acidification')) return 'OCEAN_ACIDIFICATION'
|
|
||||||
|
|
||||||
return 'OTHER'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse team members string into array
|
|
||||||
function parseTeamMembers(teamMembersStr: string): { name: string; email?: string }[] {
|
|
||||||
if (!teamMembersStr) return []
|
|
||||||
|
|
||||||
// Split by comma or semicolon
|
|
||||||
const members = teamMembersStr.split(/[,;]/).map((m) => m.trim()).filter(Boolean)
|
|
||||||
|
|
||||||
return members.map((name) => ({
|
|
||||||
name: name.trim(),
|
|
||||||
// No emails in CSV, just names with titles
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract country code from location string or return ISO code directly
|
|
||||||
function extractCountry(location: string): string | null {
|
|
||||||
if (!location) return null
|
|
||||||
|
|
||||||
// If already a 2-letter ISO code, return it directly
|
|
||||||
const trimmed = location.trim()
|
|
||||||
if (/^[A-Z]{2}$/.test(trimmed)) return trimmed
|
|
||||||
|
|
||||||
// Common country mappings from the CSV data
|
|
||||||
const countryMappings: Record<string, string> = {
|
|
||||||
'tunisie': 'TN',
|
|
||||||
'tunisia': 'TN',
|
|
||||||
'royaume-uni': 'GB',
|
|
||||||
'uk': 'GB',
|
|
||||||
'united kingdom': 'GB',
|
|
||||||
'angleterre': 'GB',
|
|
||||||
'england': 'GB',
|
|
||||||
'espagne': 'ES',
|
|
||||||
'spain': 'ES',
|
|
||||||
'inde': 'IN',
|
|
||||||
'india': 'IN',
|
|
||||||
'france': 'FR',
|
|
||||||
'états-unis': 'US',
|
|
||||||
'usa': 'US',
|
|
||||||
'united states': 'US',
|
|
||||||
'allemagne': 'DE',
|
|
||||||
'germany': 'DE',
|
|
||||||
'italie': 'IT',
|
|
||||||
'italy': 'IT',
|
|
||||||
'portugal': 'PT',
|
|
||||||
'monaco': 'MC',
|
|
||||||
'suisse': 'CH',
|
|
||||||
'switzerland': 'CH',
|
|
||||||
'belgique': 'BE',
|
|
||||||
'belgium': 'BE',
|
|
||||||
'pays-bas': 'NL',
|
|
||||||
'netherlands': 'NL',
|
|
||||||
'australia': 'AU',
|
|
||||||
'australie': 'AU',
|
|
||||||
'japon': 'JP',
|
|
||||||
'japan': 'JP',
|
|
||||||
'chine': 'CN',
|
|
||||||
'china': 'CN',
|
|
||||||
'brésil': 'BR',
|
|
||||||
'brazil': 'BR',
|
|
||||||
'mexique': 'MX',
|
|
||||||
'mexico': 'MX',
|
|
||||||
'canada': 'CA',
|
|
||||||
'maroc': 'MA',
|
|
||||||
'morocco': 'MA',
|
|
||||||
'egypte': 'EG',
|
|
||||||
'egypt': 'EG',
|
|
||||||
'afrique du sud': 'ZA',
|
|
||||||
'south africa': 'ZA',
|
|
||||||
'nigeria': 'NG',
|
|
||||||
'kenya': 'KE',
|
|
||||||
'ghana': 'GH',
|
|
||||||
'senegal': 'SN',
|
|
||||||
'sénégal': 'SN',
|
|
||||||
'côte d\'ivoire': 'CI',
|
|
||||||
'ivory coast': 'CI',
|
|
||||||
'indonesia': 'ID',
|
|
||||||
'indonésie': 'ID',
|
|
||||||
'philippines': 'PH',
|
|
||||||
'vietnam': 'VN',
|
|
||||||
'thaïlande': 'TH',
|
|
||||||
'thailand': 'TH',
|
|
||||||
'malaisie': 'MY',
|
|
||||||
'malaysia': 'MY',
|
|
||||||
'singapour': 'SG',
|
|
||||||
'singapore': 'SG',
|
|
||||||
'grèce': 'GR',
|
|
||||||
'greece': 'GR',
|
|
||||||
'turquie': 'TR',
|
|
||||||
'turkey': 'TR',
|
|
||||||
'pologne': 'PL',
|
|
||||||
'poland': 'PL',
|
|
||||||
'norvège': 'NO',
|
|
||||||
'norway': 'NO',
|
|
||||||
'suède': 'SE',
|
|
||||||
'sweden': 'SE',
|
|
||||||
'danemark': 'DK',
|
|
||||||
'denmark': 'DK',
|
|
||||||
'finlande': 'FI',
|
|
||||||
'finland': 'FI',
|
|
||||||
'irlande': 'IE',
|
|
||||||
'ireland': 'IE',
|
|
||||||
'autriche': 'AT',
|
|
||||||
'austria': 'AT',
|
|
||||||
// Additional mappings from CSV data (French names, accented variants)
|
|
||||||
'nigéria': 'NG',
|
|
||||||
'tanzanie': 'TZ',
|
|
||||||
'tanzania': 'TZ',
|
|
||||||
'ouganda': 'UG',
|
|
||||||
'uganda': 'UG',
|
|
||||||
'zambie': 'ZM',
|
|
||||||
'zambia': 'ZM',
|
|
||||||
'somalie': 'SO',
|
|
||||||
'somalia': 'SO',
|
|
||||||
'jordanie': 'JO',
|
|
||||||
'jordan': 'JO',
|
|
||||||
'bulgarie': 'BG',
|
|
||||||
'bulgaria': 'BG',
|
|
||||||
'indonesie': 'ID',
|
|
||||||
'macédoine du nord': 'MK',
|
|
||||||
'north macedonia': 'MK',
|
|
||||||
'jersey': 'JE',
|
|
||||||
'kazakhstan': 'KZ',
|
|
||||||
'cameroun': 'CM',
|
|
||||||
'cameroon': 'CM',
|
|
||||||
'vanuatu': 'VU',
|
|
||||||
'bénin': 'BJ',
|
|
||||||
'benin': 'BJ',
|
|
||||||
'argentine': 'AR',
|
|
||||||
'argentina': 'AR',
|
|
||||||
'srbija': 'RS',
|
|
||||||
'serbia': 'RS',
|
|
||||||
'kraljevo': 'RS',
|
|
||||||
'kosovo': 'XK',
|
|
||||||
'pristina': 'XK',
|
|
||||||
'xinjiang': 'CN',
|
|
||||||
'haïti': 'HT',
|
|
||||||
'haiti': 'HT',
|
|
||||||
'sri lanka': 'LK',
|
|
||||||
'luxembourg': 'LU',
|
|
||||||
'congo': 'CG',
|
|
||||||
'brazzaville': 'CG',
|
|
||||||
'colombie': 'CO',
|
|
||||||
'colombia': 'CO',
|
|
||||||
'bogota': 'CO',
|
|
||||||
'ukraine': 'UA',
|
|
||||||
}
|
|
||||||
|
|
||||||
const lower = location.toLowerCase()
|
|
||||||
|
|
||||||
for (const [key, code] of Object.entries(countryMappings)) {
|
|
||||||
if (lower.includes(key)) {
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
console.log('Starting candidatures import...\n')
|
|
||||||
|
|
||||||
// Read the CSV file
|
|
||||||
const csvPath = path.join(__dirname, '../docs/candidatures_2026.csv')
|
|
||||||
|
|
||||||
if (!fs.existsSync(csvPath)) {
|
|
||||||
console.error(`CSV file not found at ${csvPath}`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const csvContent = fs.readFileSync(csvPath, 'utf-8')
|
|
||||||
|
|
||||||
// Parse CSV
|
|
||||||
const parseResult = Papa.parse<CandidatureRow>(csvContent, {
|
|
||||||
header: true,
|
|
||||||
skipEmptyLines: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (parseResult.errors.length > 0) {
|
|
||||||
console.warn('CSV parsing warnings:', parseResult.errors)
|
|
||||||
}
|
|
||||||
|
|
||||||
const rows = parseResult.data
|
|
||||||
console.log(`Found ${rows.length} candidatures in CSV\n`)
|
|
||||||
|
|
||||||
// Get or create program
|
|
||||||
let program = await prisma.program.findFirst({
|
|
||||||
where: {
|
|
||||||
name: 'Monaco Ocean Protection Challenge',
|
|
||||||
year: 2026,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!program) {
|
|
||||||
program = await prisma.program.create({
|
|
||||||
data: {
|
|
||||||
name: 'Monaco Ocean Protection Challenge',
|
|
||||||
year: 2026,
|
|
||||||
status: 'ACTIVE',
|
|
||||||
description: 'The Monaco Ocean Protection Challenge is a flagship program promoting innovative solutions for ocean conservation.',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
console.log('Created program:', program.name, program.year)
|
|
||||||
} else {
|
|
||||||
console.log('Using existing program:', program.name, program.year)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get or create Round 1
|
|
||||||
let round = await prisma.round.findFirst({
|
|
||||||
where: {
|
|
||||||
programId: program.id,
|
|
||||||
slug: 'mopc-2026-round-1',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!round) {
|
|
||||||
round = await prisma.round.create({
|
|
||||||
data: {
|
|
||||||
programId: program.id,
|
|
||||||
name: 'Round 1 - Semi-Finalists Selection',
|
|
||||||
slug: 'mopc-2026-round-1',
|
|
||||||
status: 'ACTIVE',
|
|
||||||
roundType: 'EVALUATION',
|
|
||||||
submissionStartDate: new Date('2025-09-01'),
|
|
||||||
submissionEndDate: new Date('2026-01-31'),
|
|
||||||
votingStartAt: new Date('2026-02-15'),
|
|
||||||
votingEndAt: new Date('2026-02-28'),
|
|
||||||
requiredReviews: 3,
|
|
||||||
settingsJson: {
|
|
||||||
gracePeriod: { hours: 24 },
|
|
||||||
allowLateSubmissions: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
console.log('Created round:', round.name)
|
|
||||||
} else {
|
|
||||||
console.log('Using existing round:', round.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\nImporting candidatures...\n')
|
|
||||||
|
|
||||||
let imported = 0
|
|
||||||
let skipped = 0
|
|
||||||
let errors = 0
|
|
||||||
|
|
||||||
for (const row of rows) {
|
|
||||||
try {
|
|
||||||
const projectName = row["Project's name"]?.trim()
|
|
||||||
const email = row['E-mail']?.trim()
|
|
||||||
|
|
||||||
if (!projectName || !email) {
|
|
||||||
console.log(`Skipping row: missing project name or email`)
|
|
||||||
skipped++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if project already exists
|
|
||||||
const existingProject = await prisma.project.findFirst({
|
|
||||||
where: {
|
|
||||||
roundId: round.id,
|
|
||||||
OR: [
|
|
||||||
{ title: projectName },
|
|
||||||
{ submittedByEmail: email },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (existingProject) {
|
|
||||||
console.log(`Skipping duplicate: ${projectName} (${email})`)
|
|
||||||
skipped++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get or create user
|
|
||||||
let user = await prisma.user.findUnique({
|
|
||||||
where: { email },
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
user = await prisma.user.create({
|
|
||||||
data: {
|
|
||||||
email,
|
|
||||||
name: row['Full name']?.trim() || 'Unknown',
|
|
||||||
role: 'APPLICANT',
|
|
||||||
status: 'NONE',
|
|
||||||
phoneNumber: row['Téléphone']?.trim() || null,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse date
|
|
||||||
let submittedAt: Date | null = null
|
|
||||||
if (row['Date of creation']) {
|
|
||||||
const dateStr = row['Date of creation'].trim()
|
|
||||||
const parsed = new Date(dateStr)
|
|
||||||
if (!isNaN(parsed.getTime())) {
|
|
||||||
submittedAt = parsed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create project
|
|
||||||
const project = await prisma.project.create({
|
|
||||||
data: {
|
|
||||||
programId: round.programId,
|
|
||||||
roundId: round.id,
|
|
||||||
title: projectName,
|
|
||||||
description: row['Comment ']?.trim() || null,
|
|
||||||
competitionCategory: mapCategory(row['Category']),
|
|
||||||
oceanIssue: mapOceanIssue(row['Issue']),
|
|
||||||
country: extractCountry(row['Country']),
|
|
||||||
geographicZone: row['Tri par zone']?.trim() || null,
|
|
||||||
institution: row['University']?.trim() || null,
|
|
||||||
wantsMentorship: row['Mentorship']?.toLowerCase() === 'true',
|
|
||||||
phase1SubmissionUrl: row['PHASE 1 - Submission']?.trim() || null,
|
|
||||||
phase2SubmissionUrl: row['PHASE 2 - Submission']?.trim() || null,
|
|
||||||
referralSource: row['How did you hear about MOPC?']?.trim() || null,
|
|
||||||
applicationStatus: row['Application status']?.trim() || 'Received',
|
|
||||||
internalComments: row['MOPC team comments']?.trim() || null,
|
|
||||||
submissionSource: 'CSV',
|
|
||||||
submittedByEmail: email,
|
|
||||||
submittedByUserId: user.id,
|
|
||||||
submittedAt: submittedAt || new Date(),
|
|
||||||
metadataJson: {
|
|
||||||
importedFrom: 'candidatures_2026.csv',
|
|
||||||
importedAt: new Date().toISOString(),
|
|
||||||
originalPhone: row['Téléphone']?.trim(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create team lead membership
|
|
||||||
await prisma.teamMember.create({
|
|
||||||
data: {
|
|
||||||
projectId: project.id,
|
|
||||||
userId: user.id,
|
|
||||||
role: 'LEAD',
|
|
||||||
title: 'Team Lead',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// Parse and create team members
|
|
||||||
const teamMembers = parseTeamMembers(row['Team members'])
|
|
||||||
const leadName = row['Full name']?.trim().toLowerCase()
|
|
||||||
|
|
||||||
for (const member of teamMembers) {
|
|
||||||
// Skip if it's the lead (already added)
|
|
||||||
if (member.name.toLowerCase() === leadName) continue
|
|
||||||
|
|
||||||
// Since we don't have emails for team members, we create placeholder accounts
|
|
||||||
// They can claim their accounts later
|
|
||||||
const memberEmail = `${member.name.toLowerCase().replace(/[^a-z0-9]/g, '.')}@pending.mopc.local`
|
|
||||||
|
|
||||||
let memberUser = await prisma.user.findUnique({
|
|
||||||
where: { email: memberEmail },
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!memberUser) {
|
|
||||||
memberUser = await prisma.user.create({
|
|
||||||
data: {
|
|
||||||
email: memberEmail,
|
|
||||||
name: member.name,
|
|
||||||
role: 'APPLICANT',
|
|
||||||
status: 'NONE',
|
|
||||||
metadataJson: {
|
|
||||||
isPendingEmailVerification: true,
|
|
||||||
originalName: member.name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if membership already exists
|
|
||||||
const existingMembership = await prisma.teamMember.findUnique({
|
|
||||||
where: {
|
|
||||||
projectId_userId: {
|
|
||||||
projectId: project.id,
|
|
||||||
userId: memberUser.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!existingMembership) {
|
|
||||||
await prisma.teamMember.create({
|
|
||||||
data: {
|
|
||||||
projectId: project.id,
|
|
||||||
userId: memberUser.id,
|
|
||||||
role: 'MEMBER',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Imported: ${projectName} (${email}) - ${teamMembers.length} team members`)
|
|
||||||
imported++
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`Error importing row:`, err)
|
|
||||||
errors++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Backfill: update any existing projects with null country
|
|
||||||
console.log('\nBackfilling missing country codes...\n')
|
|
||||||
let backfilled = 0
|
|
||||||
const nullCountryProjects = await prisma.project.findMany({
|
|
||||||
where: { roundId: round.id, country: null },
|
|
||||||
select: { id: true, submittedByEmail: true, title: true },
|
|
||||||
})
|
|
||||||
|
|
||||||
for (const project of nullCountryProjects) {
|
|
||||||
// Find the matching CSV row by email or title
|
|
||||||
const matchingRow = rows.find(
|
|
||||||
(r) =>
|
|
||||||
r['E-mail']?.trim() === project.submittedByEmail ||
|
|
||||||
r["Project's name"]?.trim() === project.title
|
|
||||||
)
|
|
||||||
if (matchingRow?.['Country']) {
|
|
||||||
const countryCode = extractCountry(matchingRow['Country'])
|
|
||||||
if (countryCode) {
|
|
||||||
await prisma.project.update({
|
|
||||||
where: { id: project.id },
|
|
||||||
data: { country: countryCode },
|
|
||||||
})
|
|
||||||
console.log(` Updated: ${project.title} → ${countryCode}`)
|
|
||||||
backfilled++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log(` Backfilled: ${backfilled} projects\n`)
|
|
||||||
|
|
||||||
console.log('\n========================================')
|
|
||||||
console.log(`Import complete!`)
|
|
||||||
console.log(` Imported: ${imported}`)
|
|
||||||
console.log(` Skipped: ${skipped}`)
|
|
||||||
console.log(` Errors: ${errors}`)
|
|
||||||
console.log(` Backfilled: ${backfilled}`)
|
|
||||||
console.log('========================================\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
main()
|
|
||||||
.catch((e) => {
|
|
||||||
console.error(e)
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
.finally(async () => {
|
|
||||||
await prisma.$disconnect()
|
|
||||||
})
|
|
||||||
|
|
@ -1,179 +0,0 @@
|
||||||
import { PrismaClient } from '@prisma/client'
|
|
||||||
import bcrypt from 'bcryptjs'
|
|
||||||
|
|
||||||
const prisma = new PrismaClient()
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
console.log('Setting up demo jury member...\n')
|
|
||||||
|
|
||||||
// Hash a password for the demo jury account
|
|
||||||
const password = 'JuryDemo2026!'
|
|
||||||
const passwordHash = await bcrypt.hash(password, 12)
|
|
||||||
|
|
||||||
// Create or update jury member
|
|
||||||
const juryUser = await prisma.user.upsert({
|
|
||||||
where: { email: 'jury.demo@monaco-opc.com' },
|
|
||||||
update: {
|
|
||||||
passwordHash,
|
|
||||||
mustSetPassword: false,
|
|
||||||
status: 'ACTIVE',
|
|
||||||
onboardingCompletedAt: new Date(),
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
email: 'jury.demo@monaco-opc.com',
|
|
||||||
name: 'Dr. Marie Laurent',
|
|
||||||
role: 'JURY_MEMBER',
|
|
||||||
status: 'ACTIVE',
|
|
||||||
passwordHash,
|
|
||||||
mustSetPassword: false,
|
|
||||||
passwordSetAt: new Date(),
|
|
||||||
onboardingCompletedAt: new Date(),
|
|
||||||
expertiseTags: ['Marine Biology', 'Ocean Conservation', 'Sustainable Innovation'],
|
|
||||||
notificationPreference: 'EMAIL',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(`Jury user: ${juryUser.email} (${juryUser.id})`)
|
|
||||||
console.log(`Password: ${password}\n`)
|
|
||||||
|
|
||||||
// Find the round
|
|
||||||
const round = await prisma.round.findFirst({
|
|
||||||
where: { slug: 'mopc-2026-round-1' },
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!round) {
|
|
||||||
console.error('Round not found! Run seed-candidatures first.')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Round: ${round.name} (${round.id})`)
|
|
||||||
|
|
||||||
// Ensure voting window is open
|
|
||||||
const now = new Date()
|
|
||||||
const votingStart = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000) // 7 days ago
|
|
||||||
const votingEnd = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000) // 30 days from now
|
|
||||||
|
|
||||||
await prisma.round.update({
|
|
||||||
where: { id: round.id },
|
|
||||||
data: {
|
|
||||||
status: 'ACTIVE',
|
|
||||||
votingStartAt: votingStart,
|
|
||||||
votingEndAt: votingEnd,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(`Voting window: ${votingStart.toISOString()} → ${votingEnd.toISOString()}\n`)
|
|
||||||
|
|
||||||
// Get some projects to assign
|
|
||||||
const projects = await prisma.project.findMany({
|
|
||||||
where: { roundId: round.id },
|
|
||||||
take: 8,
|
|
||||||
orderBy: { createdAt: 'desc' },
|
|
||||||
select: { id: true, title: true },
|
|
||||||
})
|
|
||||||
|
|
||||||
if (projects.length === 0) {
|
|
||||||
console.error('No projects found! Run seed-candidatures first.')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Found ${projects.length} projects to assign\n`)
|
|
||||||
|
|
||||||
// Create assignments
|
|
||||||
let created = 0
|
|
||||||
let skipped = 0
|
|
||||||
|
|
||||||
for (const project of projects) {
|
|
||||||
try {
|
|
||||||
await prisma.assignment.upsert({
|
|
||||||
where: {
|
|
||||||
userId_projectId_roundId: {
|
|
||||||
userId: juryUser.id,
|
|
||||||
projectId: project.id,
|
|
||||||
roundId: round.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
update: {},
|
|
||||||
create: {
|
|
||||||
userId: juryUser.id,
|
|
||||||
projectId: project.id,
|
|
||||||
roundId: round.id,
|
|
||||||
method: 'MANUAL',
|
|
||||||
isRequired: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
console.log(` Assigned: ${project.title}`)
|
|
||||||
created++
|
|
||||||
} catch {
|
|
||||||
skipped++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure evaluation criteria exist for this round
|
|
||||||
const existingForm = await prisma.evaluationForm.findFirst({
|
|
||||||
where: { roundId: round.id },
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!existingForm) {
|
|
||||||
await prisma.evaluationForm.create({
|
|
||||||
data: {
|
|
||||||
roundId: round.id,
|
|
||||||
isActive: true,
|
|
||||||
criteriaJson: [
|
|
||||||
{
|
|
||||||
id: 'innovation',
|
|
||||||
label: 'Innovation & Originality',
|
|
||||||
description: 'How innovative is the proposed solution? Does it bring a new approach to ocean conservation?',
|
|
||||||
scale: 10,
|
|
||||||
weight: 25,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'feasibility',
|
|
||||||
label: 'Technical Feasibility',
|
|
||||||
description: 'Is the solution technically viable? Can it be realistically implemented?',
|
|
||||||
scale: 10,
|
|
||||||
weight: 25,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'impact',
|
|
||||||
label: 'Environmental Impact',
|
|
||||||
description: 'What is the potential positive impact on ocean health and marine ecosystems?',
|
|
||||||
scale: 10,
|
|
||||||
weight: 30,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'team',
|
|
||||||
label: 'Team Capability',
|
|
||||||
description: 'Does the team have the skills, experience, and commitment to execute?',
|
|
||||||
scale: 10,
|
|
||||||
weight: 20,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
console.log('\nCreated evaluation form with 4 criteria')
|
|
||||||
} else {
|
|
||||||
console.log('\nEvaluation form already exists')
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n========================================')
|
|
||||||
console.log('Demo jury member setup complete!')
|
|
||||||
console.log(` Email: jury.demo@monaco-opc.com`)
|
|
||||||
console.log(` Password: ${password}`)
|
|
||||||
console.log(` Assignments: ${created} created, ${skipped} skipped`)
|
|
||||||
console.log(` Voting: OPEN (${round.name})`)
|
|
||||||
console.log('========================================\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
main()
|
|
||||||
.catch((e) => {
|
|
||||||
console.error(e)
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
.finally(async () => {
|
|
||||||
await prisma.$disconnect()
|
|
||||||
})
|
|
||||||
1104
prisma/seed.ts
1104
prisma/seed.ts
File diff suppressed because it is too large
Load Diff
|
|
@ -290,21 +290,21 @@ export function DashboardContent({ editionId, sessionName }: DashboardContentPro
|
||||||
|
|
||||||
const {
|
const {
|
||||||
edition,
|
edition,
|
||||||
activeRoundCount,
|
activeStageCount,
|
||||||
totalRoundCount,
|
totalStageCount,
|
||||||
projectCount,
|
projectCount,
|
||||||
newProjectsThisWeek,
|
newProjectsThisWeek,
|
||||||
totalJurors,
|
totalJurors,
|
||||||
activeJurors,
|
activeJurors,
|
||||||
evaluationStats,
|
evaluationStats,
|
||||||
totalAssignments,
|
totalAssignments,
|
||||||
recentRounds,
|
recentStages,
|
||||||
latestProjects,
|
latestProjects,
|
||||||
categoryBreakdown,
|
categoryBreakdown,
|
||||||
oceanIssueBreakdown,
|
oceanIssueBreakdown,
|
||||||
recentActivity,
|
recentActivity,
|
||||||
pendingCOIs,
|
pendingCOIs,
|
||||||
draftRounds,
|
draftStages,
|
||||||
unassignedProjects,
|
unassignedProjects,
|
||||||
} = data
|
} = data
|
||||||
|
|
||||||
|
|
@ -318,32 +318,25 @@ export function DashboardContent({ editionId, sessionName }: DashboardContentPro
|
||||||
|
|
||||||
const invitedJurors = totalJurors - activeJurors
|
const invitedJurors = totalJurors - activeJurors
|
||||||
|
|
||||||
// Compute per-round eval stats
|
// Compute per-stage eval stats
|
||||||
const roundsWithEvalStats = recentRounds.map((round) => {
|
const stagesWithEvalStats = recentStages.map((stage: typeof recentStages[number]) => {
|
||||||
const submitted = round.assignments.filter(
|
const submitted = stage.assignments.filter(
|
||||||
(a) => a.evaluation?.status === 'SUBMITTED'
|
(a: { evaluation: { status: string } | null }) => a.evaluation?.status === 'SUBMITTED'
|
||||||
).length
|
).length
|
||||||
const total = round._count.assignments
|
const total = stage._count.assignments
|
||||||
const percent = total > 0 ? Math.round((submitted / total) * 100) : 0
|
const percent = total > 0 ? Math.round((submitted / total) * 100) : 0
|
||||||
return { ...round, submittedEvals: submitted, totalEvals: total, evalPercent: percent }
|
return { ...stage, submittedEvals: submitted, totalEvals: total, evalPercent: percent }
|
||||||
})
|
})
|
||||||
|
|
||||||
// Upcoming deadlines from rounds
|
// Upcoming deadlines from stages
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const deadlines: { label: string; roundName: string; date: Date }[] = []
|
const deadlines: { label: string; stageName: string; date: Date }[] = []
|
||||||
for (const round of recentRounds) {
|
for (const stage of recentStages) {
|
||||||
if (round.votingEndAt && new Date(round.votingEndAt) > now) {
|
if (stage.windowCloseAt && new Date(stage.windowCloseAt) > now) {
|
||||||
deadlines.push({
|
deadlines.push({
|
||||||
label: 'Voting closes',
|
label: 'Window closes',
|
||||||
roundName: round.name,
|
stageName: stage.name,
|
||||||
date: new Date(round.votingEndAt),
|
date: new Date(stage.windowCloseAt),
|
||||||
})
|
|
||||||
}
|
|
||||||
if (round.submissionEndDate && new Date(round.submissionEndDate) > now) {
|
|
||||||
deadlines.push({
|
|
||||||
label: 'Submissions close',
|
|
||||||
roundName: round.name,
|
|
||||||
date: new Date(round.submissionEndDate),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -388,10 +381,10 @@ export function DashboardContent({ editionId, sessionName }: DashboardContentPro
|
||||||
<CardContent className="p-5">
|
<CardContent className="p-5">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-muted-foreground">Rounds</p>
|
<p className="text-sm font-medium text-muted-foreground">Stages</p>
|
||||||
<p className="text-2xl font-bold mt-1">{totalRoundCount}</p>
|
<p className="text-2xl font-bold mt-1">{totalStageCount}</p>
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
{activeRoundCount} active round{activeRoundCount !== 1 ? 's' : ''}
|
{activeStageCount} active stage{activeStageCount !== 1 ? 's' : ''}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-xl bg-blue-50 p-3">
|
<div className="rounded-xl bg-blue-50 p-3">
|
||||||
|
|
@ -474,13 +467,13 @@ export function DashboardContent({ editionId, sessionName }: DashboardContentPro
|
||||||
|
|
||||||
{/* Quick Actions */}
|
{/* Quick Actions */}
|
||||||
<div className="grid gap-3 sm:grid-cols-3">
|
<div className="grid gap-3 sm:grid-cols-3">
|
||||||
<Link href="/admin/rounds/new" className="group flex items-center gap-3 rounded-xl border border-border/60 p-4 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md hover:border-blue-500/30 hover:bg-blue-500/5">
|
<Link href="/admin/rounds/pipelines" className="group flex items-center gap-3 rounded-xl border border-border/60 p-4 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md hover:border-blue-500/30 hover:bg-blue-500/5">
|
||||||
<div className="rounded-xl bg-blue-50 p-2.5 transition-colors group-hover:bg-blue-100">
|
<div className="rounded-xl bg-blue-50 p-2.5 transition-colors group-hover:bg-blue-100">
|
||||||
<Plus className="h-4 w-4 text-blue-600" />
|
<Plus className="h-4 w-4 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium">New Round</p>
|
<p className="text-sm font-medium">Pipelines</p>
|
||||||
<p className="text-xs text-muted-foreground">Create a voting round</p>
|
<p className="text-xs text-muted-foreground">Manage stages & pipelines</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/admin/projects/new" className="group flex items-center gap-3 rounded-xl border border-border/60 p-4 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md hover:border-emerald-500/30 hover:bg-emerald-500/5">
|
<Link href="/admin/projects/new" className="group flex items-center gap-3 rounded-xl border border-border/60 p-4 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md hover:border-emerald-500/30 hover:bg-emerald-500/5">
|
||||||
|
|
@ -507,7 +500,7 @@ export function DashboardContent({ editionId, sessionName }: DashboardContentPro
|
||||||
<div className="grid gap-6 lg:grid-cols-12">
|
<div className="grid gap-6 lg:grid-cols-12">
|
||||||
{/* Left Column */}
|
{/* Left Column */}
|
||||||
<div className="space-y-6 lg:col-span-7">
|
<div className="space-y-6 lg:col-span-7">
|
||||||
{/* Rounds Card (enhanced) */}
|
{/* Stages Card (enhanced) */}
|
||||||
<AnimatedCard index={4}>
|
<AnimatedCard index={4}>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
|
@ -517,14 +510,14 @@ export function DashboardContent({ editionId, sessionName }: DashboardContentPro
|
||||||
<div className="rounded-lg bg-blue-500/10 p-1.5">
|
<div className="rounded-lg bg-blue-500/10 p-1.5">
|
||||||
<CircleDot className="h-4 w-4 text-blue-500" />
|
<CircleDot className="h-4 w-4 text-blue-500" />
|
||||||
</div>
|
</div>
|
||||||
Rounds
|
Stages
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Voting rounds in {edition.name}
|
Pipeline stages in {edition.name}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
href="/admin/rounds"
|
href="/admin/rounds/pipelines"
|
||||||
className="flex items-center gap-1 text-sm font-medium text-primary hover:underline"
|
className="flex items-center gap-1 text-sm font-medium text-primary hover:underline"
|
||||||
>
|
>
|
||||||
View all <ArrowRight className="h-3.5 w-3.5" />
|
View all <ArrowRight className="h-3.5 w-3.5" />
|
||||||
|
|
@ -532,52 +525,51 @@ export function DashboardContent({ editionId, sessionName }: DashboardContentPro
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{roundsWithEvalStats.length === 0 ? (
|
{stagesWithEvalStats.length === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center py-8 text-center">
|
<div className="flex flex-col items-center justify-center py-8 text-center">
|
||||||
<CircleDot className="h-12 w-12 text-muted-foreground/50" />
|
<CircleDot className="h-12 w-12 text-muted-foreground/50" />
|
||||||
<p className="mt-2 text-sm text-muted-foreground">
|
<p className="mt-2 text-sm text-muted-foreground">
|
||||||
No rounds created yet
|
No stages created yet
|
||||||
</p>
|
</p>
|
||||||
<Link
|
<Link
|
||||||
href="/admin/rounds/new"
|
href="/admin/rounds/pipelines"
|
||||||
className="mt-4 text-sm font-medium text-primary hover:underline"
|
className="mt-4 text-sm font-medium text-primary hover:underline"
|
||||||
>
|
>
|
||||||
Create your first round
|
Set up your pipeline
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{roundsWithEvalStats.map((round) => (
|
{stagesWithEvalStats.map((stage) => (
|
||||||
<Link
|
<div
|
||||||
key={round.id}
|
key={stage.id}
|
||||||
href={`/admin/rounds/${round.id}`}
|
|
||||||
className="block"
|
className="block"
|
||||||
>
|
>
|
||||||
<div className="rounded-lg border p-4 transition-all hover:bg-muted/50 hover:-translate-y-0.5 hover:shadow-md">
|
<div className="rounded-lg border p-4 transition-all hover:bg-muted/50 hover:-translate-y-0.5 hover:shadow-md">
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<div className="space-y-1.5 flex-1 min-w-0">
|
<div className="space-y-1.5 flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<p className="font-medium">{round.name}</p>
|
<p className="font-medium">{stage.name}</p>
|
||||||
<StatusBadge status={round.status} />
|
<StatusBadge status={stage.status} />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{round._count.projects} projects · {round._count.assignments} assignments
|
{stage._count.projectStageStates} projects · {stage._count.assignments} assignments
|
||||||
{round.totalEvals > 0 && (
|
{stage.totalEvals > 0 && (
|
||||||
<> · {round.evalPercent}% evaluated</>
|
<> · {stage.evalPercent}% evaluated</>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
{round.votingStartAt && round.votingEndAt && (
|
{stage.windowOpenAt && stage.windowCloseAt && (
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Voting: {formatDateOnly(round.votingStartAt)} – {formatDateOnly(round.votingEndAt)}
|
Window: {formatDateOnly(stage.windowOpenAt)} – {formatDateOnly(stage.windowCloseAt)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{round.totalEvals > 0 && (
|
{stage.totalEvals > 0 && (
|
||||||
<Progress value={round.evalPercent} className="mt-3 h-1.5" gradient />
|
<Progress value={stage.evalPercent} className="mt-3 h-1.5" gradient />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -690,7 +682,7 @@ export function DashboardContent({ editionId, sessionName }: DashboardContentPro
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{pendingCOIs > 0 && (
|
{pendingCOIs > 0 && (
|
||||||
<Link href="/admin/rounds" className="flex items-center justify-between rounded-lg border p-3 transition-colors hover:bg-muted/50">
|
<Link href="/admin/rounds/pipelines" className="flex items-center justify-between rounded-lg border p-3 transition-colors hover:bg-muted/50">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<ShieldAlert className="h-4 w-4 text-amber-500" />
|
<ShieldAlert className="h-4 w-4 text-amber-500" />
|
||||||
<span className="text-sm">COI declarations to review</span>
|
<span className="text-sm">COI declarations to review</span>
|
||||||
|
|
@ -707,16 +699,16 @@ export function DashboardContent({ editionId, sessionName }: DashboardContentPro
|
||||||
<Badge variant="warning">{unassignedProjects}</Badge>
|
<Badge variant="warning">{unassignedProjects}</Badge>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
{draftRounds > 0 && (
|
{draftStages > 0 && (
|
||||||
<Link href="/admin/rounds" className="flex items-center justify-between rounded-lg border p-3 transition-colors hover:bg-muted/50">
|
<Link href="/admin/rounds/pipelines" className="flex items-center justify-between rounded-lg border p-3 transition-colors hover:bg-muted/50">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<CircleDot className="h-4 w-4 text-blue-500" />
|
<CircleDot className="h-4 w-4 text-blue-500" />
|
||||||
<span className="text-sm">Draft rounds to activate</span>
|
<span className="text-sm">Draft stages to activate</span>
|
||||||
</div>
|
</div>
|
||||||
<Badge variant="secondary">{draftRounds}</Badge>
|
<Badge variant="secondary">{draftStages}</Badge>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
{pendingCOIs === 0 && unassignedProjects === 0 && draftRounds === 0 && (
|
{pendingCOIs === 0 && unassignedProjects === 0 && draftStages === 0 && (
|
||||||
<div className="flex flex-col items-center py-4 text-center">
|
<div className="flex flex-col items-center py-4 text-center">
|
||||||
<CheckCircle2 className="h-6 w-6 text-emerald-500" />
|
<CheckCircle2 className="h-6 w-6 text-emerald-500" />
|
||||||
<p className="mt-1.5 text-sm text-muted-foreground">All caught up!</p>
|
<p className="mt-1.5 text-sm text-muted-foreground">All caught up!</p>
|
||||||
|
|
@ -739,7 +731,7 @@ export function DashboardContent({ editionId, sessionName }: DashboardContentPro
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{roundsWithEvalStats.filter((r) => r.status !== 'DRAFT' && r.totalEvals > 0).length === 0 ? (
|
{stagesWithEvalStats.filter((s: typeof stagesWithEvalStats[number]) => s.status !== 'STAGE_DRAFT' && s.totalEvals > 0).length === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center py-6 text-center">
|
<div className="flex flex-col items-center justify-center py-6 text-center">
|
||||||
<TrendingUp className="h-8 w-8 text-muted-foreground/40" />
|
<TrendingUp className="h-8 w-8 text-muted-foreground/40" />
|
||||||
<p className="mt-2 text-sm text-muted-foreground">
|
<p className="mt-2 text-sm text-muted-foreground">
|
||||||
|
|
@ -748,19 +740,19 @@ export function DashboardContent({ editionId, sessionName }: DashboardContentPro
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
{roundsWithEvalStats
|
{stagesWithEvalStats
|
||||||
.filter((r) => r.status !== 'DRAFT' && r.totalEvals > 0)
|
.filter((s: typeof stagesWithEvalStats[number]) => s.status !== 'STAGE_DRAFT' && s.totalEvals > 0)
|
||||||
.map((round) => (
|
.map((stage: typeof stagesWithEvalStats[number]) => (
|
||||||
<div key={round.id} className="space-y-2">
|
<div key={stage.id} className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="text-sm font-medium truncate">{round.name}</p>
|
<p className="text-sm font-medium truncate">{stage.name}</p>
|
||||||
<span className="text-sm font-semibold tabular-nums">
|
<span className="text-sm font-semibold tabular-nums">
|
||||||
{round.evalPercent}%
|
{stage.evalPercent}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Progress value={round.evalPercent} className="h-2" gradient />
|
<Progress value={stage.evalPercent} className="h-2" gradient />
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{round.submittedEvals} of {round.totalEvals} evaluations submitted
|
{stage.submittedEvals} of {stage.totalEvals} evaluations submitted
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
@ -913,7 +905,7 @@ export function DashboardContent({ editionId, sessionName }: DashboardContentPro
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<p className="text-sm font-medium">
|
<p className="text-sm font-medium">
|
||||||
{deadline.label} — {deadline.roundName}
|
{deadline.label} — {deadline.stageName}
|
||||||
</p>
|
</p>
|
||||||
<p className={`text-xs ${isUrgent ? 'text-destructive' : 'text-muted-foreground'}`}>
|
<p className={`text-xs ${isUrgent ? 'text-destructive' : 'text-muted-foreground'}`}>
|
||||||
{formatDateOnly(deadline.date)} · in {days} day{days !== 1 ? 's' : ''}
|
{formatDateOnly(deadline.date)} · in {days} day{days !== 1 ? 's' : ''}
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ type Role = 'SUPER_ADMIN' | 'PROGRAM_ADMIN' | 'JURY_MEMBER' | 'MENTOR' | 'OBSERV
|
||||||
|
|
||||||
interface Assignment {
|
interface Assignment {
|
||||||
projectId: string
|
projectId: string
|
||||||
roundId: string
|
stageId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MemberRow {
|
interface MemberRow {
|
||||||
|
|
@ -269,7 +269,7 @@ export default function MemberInvitePage() {
|
||||||
} | null>(null)
|
} | null>(null)
|
||||||
|
|
||||||
// Pre-assignment state
|
// Pre-assignment state
|
||||||
const [selectedRoundId, setSelectedRoundId] = useState<string>('')
|
const [selectedStageId, setSelectedStageId] = useState<string>('')
|
||||||
|
|
||||||
const utils = trpc.useUtils()
|
const utils = trpc.useUtils()
|
||||||
|
|
||||||
|
|
@ -284,30 +284,27 @@ export default function MemberInvitePage() {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Fetch programs with rounds for pre-assignment
|
// Fetch programs with stages for pre-assignment
|
||||||
const { data: programsData } = trpc.program.list.useQuery({
|
const { data: programsData } = trpc.program.list.useQuery({
|
||||||
status: 'ACTIVE',
|
status: 'ACTIVE',
|
||||||
includeRounds: true,
|
includeStages: true,
|
||||||
})
|
})
|
||||||
// Flatten all rounds from all programs
|
// Flatten all stages from all programs
|
||||||
const rounds = useMemo(() => {
|
const stages = useMemo(() => {
|
||||||
if (!programsData) return []
|
if (!programsData) return []
|
||||||
type ProgramWithRounds = typeof programsData[number] & {
|
return programsData.flatMap((program) =>
|
||||||
rounds?: Array<{ id: string; name: string }>
|
((program.stages ?? []) as Array<{ id: string; name: string }>).map((stage: { id: string; name: string }) => ({
|
||||||
}
|
id: stage.id,
|
||||||
return (programsData as ProgramWithRounds[]).flatMap((program) =>
|
name: stage.name,
|
||||||
(program.rounds || []).map((round) => ({
|
|
||||||
id: round.id,
|
|
||||||
name: round.name,
|
|
||||||
programName: `${program.name} ${program.year}`,
|
programName: `${program.name} ${program.year}`,
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
}, [programsData])
|
}, [programsData])
|
||||||
|
|
||||||
// Fetch projects for selected round
|
// Fetch projects for selected stage
|
||||||
const { data: projectsData, isLoading: projectsLoading } = trpc.project.list.useQuery(
|
const { data: projectsData, isLoading: projectsLoading } = trpc.project.list.useQuery(
|
||||||
{ roundId: selectedRoundId, perPage: 200 },
|
{ stageId: selectedStageId, perPage: 200 },
|
||||||
{ enabled: !!selectedRoundId }
|
{ enabled: !!selectedStageId }
|
||||||
)
|
)
|
||||||
const projects = projectsData?.projects || []
|
const projects = projectsData?.projects || []
|
||||||
|
|
||||||
|
|
@ -355,7 +352,7 @@ export default function MemberInvitePage() {
|
||||||
|
|
||||||
// Per-row project assignment management
|
// Per-row project assignment management
|
||||||
const toggleProjectAssignment = (rowId: string, projectId: string) => {
|
const toggleProjectAssignment = (rowId: string, projectId: string) => {
|
||||||
if (!selectedRoundId) return
|
if (!selectedStageId) return
|
||||||
setRows((prev) =>
|
setRows((prev) =>
|
||||||
prev.map((r) => {
|
prev.map((r) => {
|
||||||
if (r.id !== rowId) return r
|
if (r.id !== rowId) return r
|
||||||
|
|
@ -363,7 +360,7 @@ export default function MemberInvitePage() {
|
||||||
if (existing) {
|
if (existing) {
|
||||||
return { ...r, assignments: r.assignments.filter((a) => a.projectId !== projectId) }
|
return { ...r, assignments: r.assignments.filter((a) => a.projectId !== projectId) }
|
||||||
} else {
|
} else {
|
||||||
return { ...r, assignments: [...r.assignments, { projectId, roundId: selectedRoundId }] }
|
return { ...r, assignments: [...r.assignments, { projectId, stageId: selectedStageId }] }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
@ -587,21 +584,21 @@ export default function MemberInvitePage() {
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<Label className="text-sm font-medium">Pre-assign Projects (Optional)</Label>
|
<Label className="text-sm font-medium">Pre-assign Projects (Optional)</Label>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Select a round to assign projects to jury members before they onboard
|
Select a stage to assign projects to jury members before they onboard
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Select
|
<Select
|
||||||
value={selectedRoundId || 'none'}
|
value={selectedStageId || 'none'}
|
||||||
onValueChange={(v) => setSelectedRoundId(v === 'none' ? '' : v)}
|
onValueChange={(v) => setSelectedStageId(v === 'none' ? '' : v)}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-[200px]">
|
<SelectTrigger className="w-[200px]">
|
||||||
<SelectValue placeholder="Select round" />
|
<SelectValue placeholder="Select stage" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="none">No pre-assignment</SelectItem>
|
<SelectItem value="none">No pre-assignment</SelectItem>
|
||||||
{rounds.map((round) => (
|
{stages.map((stage) => (
|
||||||
<SelectItem key={round.id} value={round.id}>
|
<SelectItem key={stage.id} value={stage.id}>
|
||||||
{round.programName} - {round.name}
|
{stage.programName} - {stage.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|
@ -683,7 +680,7 @@ export default function MemberInvitePage() {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Per-member project pre-assignment (only for jury members) */}
|
{/* Per-member project pre-assignment (only for jury members) */}
|
||||||
{row.role === 'JURY_MEMBER' && selectedRoundId && (
|
{row.role === 'JURY_MEMBER' && selectedStageId && (
|
||||||
<Collapsible className="space-y-2">
|
<Collapsible className="space-y-2">
|
||||||
<CollapsibleTrigger asChild>
|
<CollapsibleTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -64,12 +64,12 @@ import {
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { formatDate } from '@/lib/utils'
|
import { formatDate } from '@/lib/utils'
|
||||||
|
|
||||||
type RecipientType = 'ALL' | 'ROLE' | 'ROUND_JURY' | 'PROGRAM_TEAM' | 'USER'
|
type RecipientType = 'ALL' | 'ROLE' | 'STAGE_JURY' | 'PROGRAM_TEAM' | 'USER'
|
||||||
|
|
||||||
const RECIPIENT_TYPE_OPTIONS: { value: RecipientType; label: string }[] = [
|
const RECIPIENT_TYPE_OPTIONS: { value: RecipientType; label: string }[] = [
|
||||||
{ value: 'ALL', label: 'All Users' },
|
{ value: 'ALL', label: 'All Users' },
|
||||||
{ value: 'ROLE', label: 'By Role' },
|
{ value: 'ROLE', label: 'By Role' },
|
||||||
{ value: 'ROUND_JURY', label: 'Round Jury' },
|
{ value: 'STAGE_JURY', label: 'Stage Jury' },
|
||||||
{ value: 'PROGRAM_TEAM', label: 'Program Team' },
|
{ value: 'PROGRAM_TEAM', label: 'Program Team' },
|
||||||
{ value: 'USER', label: 'Specific User' },
|
{ value: 'USER', label: 'Specific User' },
|
||||||
]
|
]
|
||||||
|
|
@ -79,7 +79,7 @@ const ROLES = ['JURY_MEMBER', 'MENTOR', 'OBSERVER', 'APPLICANT', 'PROGRAM_ADMIN'
|
||||||
export default function MessagesPage() {
|
export default function MessagesPage() {
|
||||||
const [recipientType, setRecipientType] = useState<RecipientType>('ALL')
|
const [recipientType, setRecipientType] = useState<RecipientType>('ALL')
|
||||||
const [selectedRole, setSelectedRole] = useState('')
|
const [selectedRole, setSelectedRole] = useState('')
|
||||||
const [roundId, setRoundId] = useState('')
|
const [stageId, setStageId] = useState('')
|
||||||
const [selectedProgramId, setSelectedProgramId] = useState('')
|
const [selectedProgramId, setSelectedProgramId] = useState('')
|
||||||
const [selectedUserId, setSelectedUserId] = useState('')
|
const [selectedUserId, setSelectedUserId] = useState('')
|
||||||
const [subject, setSubject] = useState('')
|
const [subject, setSubject] = useState('')
|
||||||
|
|
@ -93,8 +93,11 @@ export default function MessagesPage() {
|
||||||
const utils = trpc.useUtils()
|
const utils = trpc.useUtils()
|
||||||
|
|
||||||
// Fetch supporting data
|
// Fetch supporting data
|
||||||
const { data: rounds } = trpc.round.listAll.useQuery()
|
// Get programs with stages
|
||||||
const { data: programs } = trpc.program.list.useQuery()
|
const { data: programs } = trpc.program.list.useQuery({ includeStages: true })
|
||||||
|
const rounds = programs?.flatMap((p) =>
|
||||||
|
((p.stages ?? []) as Array<{ id: string; name: string }>).map((s: { id: string; name: string }) => ({ ...s, program: { name: p.name } }))
|
||||||
|
) || []
|
||||||
const { data: templates } = trpc.message.listTemplates.useQuery()
|
const { data: templates } = trpc.message.listTemplates.useQuery()
|
||||||
const { data: users } = trpc.user.list.useQuery(
|
const { data: users } = trpc.user.list.useQuery(
|
||||||
{ page: 1, perPage: 100 },
|
{ page: 1, perPage: 100 },
|
||||||
|
|
@ -121,7 +124,7 @@ export default function MessagesPage() {
|
||||||
setBody('')
|
setBody('')
|
||||||
setSelectedTemplateId('')
|
setSelectedTemplateId('')
|
||||||
setSelectedRole('')
|
setSelectedRole('')
|
||||||
setRoundId('')
|
setStageId('')
|
||||||
setSelectedProgramId('')
|
setSelectedProgramId('')
|
||||||
setSelectedUserId('')
|
setSelectedUserId('')
|
||||||
setIsScheduled(false)
|
setIsScheduled(false)
|
||||||
|
|
@ -170,14 +173,14 @@ export default function MessagesPage() {
|
||||||
const roleLabel = selectedRole ? selectedRole.replace(/_/g, ' ') : ''
|
const roleLabel = selectedRole ? selectedRole.replace(/_/g, ' ') : ''
|
||||||
return roleLabel ? `All ${roleLabel}s` : 'By Role (none selected)'
|
return roleLabel ? `All ${roleLabel}s` : 'By Role (none selected)'
|
||||||
}
|
}
|
||||||
case 'ROUND_JURY': {
|
case 'STAGE_JURY': {
|
||||||
if (!roundId) return 'Round Jury (none selected)'
|
if (!stageId) return 'Stage Jury (none selected)'
|
||||||
const round = (rounds as Array<{ id: string; name: string; program?: { name: string } }> | undefined)?.find(
|
const stage = rounds?.find(
|
||||||
(r) => r.id === roundId
|
(r) => r.id === stageId
|
||||||
)
|
)
|
||||||
return round
|
return stage
|
||||||
? `Jury of ${round.program ? `${round.program.name} - ` : ''}${round.name}`
|
? `Jury of ${stage.program ? `${stage.program.name} - ` : ''}${stage.name}`
|
||||||
: 'Round Jury'
|
: 'Stage Jury'
|
||||||
}
|
}
|
||||||
case 'PROGRAM_TEAM': {
|
case 'PROGRAM_TEAM': {
|
||||||
if (!selectedProgramId) return 'Program Team (none selected)'
|
if (!selectedProgramId) return 'Program Team (none selected)'
|
||||||
|
|
@ -214,8 +217,8 @@ export default function MessagesPage() {
|
||||||
toast.error('Please select a role')
|
toast.error('Please select a role')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (recipientType === 'ROUND_JURY' && !roundId) {
|
if (recipientType === 'STAGE_JURY' && !stageId) {
|
||||||
toast.error('Please select a round')
|
toast.error('Please select a stage')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (recipientType === 'PROGRAM_TEAM' && !selectedProgramId) {
|
if (recipientType === 'PROGRAM_TEAM' && !selectedProgramId) {
|
||||||
|
|
@ -234,7 +237,7 @@ export default function MessagesPage() {
|
||||||
sendMutation.mutate({
|
sendMutation.mutate({
|
||||||
recipientType,
|
recipientType,
|
||||||
recipientFilter: buildRecipientFilter(),
|
recipientFilter: buildRecipientFilter(),
|
||||||
roundId: roundId || undefined,
|
stageId: stageId || undefined,
|
||||||
subject: subject.trim(),
|
subject: subject.trim(),
|
||||||
body: body.trim(),
|
body: body.trim(),
|
||||||
deliveryChannels,
|
deliveryChannels,
|
||||||
|
|
@ -292,7 +295,7 @@ export default function MessagesPage() {
|
||||||
onValueChange={(v) => {
|
onValueChange={(v) => {
|
||||||
setRecipientType(v as RecipientType)
|
setRecipientType(v as RecipientType)
|
||||||
setSelectedRole('')
|
setSelectedRole('')
|
||||||
setRoundId('')
|
setStageId('')
|
||||||
setSelectedProgramId('')
|
setSelectedProgramId('')
|
||||||
setSelectedUserId('')
|
setSelectedUserId('')
|
||||||
}}
|
}}
|
||||||
|
|
@ -329,15 +332,15 @@ export default function MessagesPage() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{recipientType === 'ROUND_JURY' && (
|
{recipientType === 'STAGE_JURY' && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Select Round</Label>
|
<Label>Select Stage</Label>
|
||||||
<Select value={roundId} onValueChange={setRoundId}>
|
<Select value={stageId} onValueChange={setStageId}>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Choose a round..." />
|
<SelectValue placeholder="Choose a stage..." />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{(rounds as Array<{ id: string; name: string; program?: { name: string } }> | undefined)?.map((round) => (
|
{rounds?.map((round) => (
|
||||||
<SelectItem key={round.id} value={round.id}>
|
<SelectItem key={round.id} value={round.id}>
|
||||||
{round.program ? `${round.program.name} - ${round.name}` : round.name}
|
{round.program ? `${round.program.name} - ${round.name}` : round.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
|
|
|
||||||
|
|
@ -86,53 +86,48 @@ export default async function ProgramDetailPage({ params }: ProgramDetailPagePro
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="flex flex-row items-center justify-between">
|
<CardHeader className="flex flex-row items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<CardTitle>Rounds</CardTitle>
|
<CardTitle>Stages</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Voting rounds for this program
|
Pipeline stages for this program
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<Button asChild>
|
<Button asChild>
|
||||||
<Link href={`/admin/rounds/new?programId=${id}`}>
|
<Link href={`/admin/rounds/pipelines?programId=${id}`}>
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
New Round
|
Manage Pipeline
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{program.rounds.length === 0 ? (
|
{(program.stages as Array<{ id: string; name: string; status: string; _count: { projects: number; assignments: number }; createdAt?: Date }>).length === 0 ? (
|
||||||
<div className="py-8 text-center text-muted-foreground">
|
<div className="py-8 text-center text-muted-foreground">
|
||||||
No rounds created yet. Create a round to start accepting projects.
|
No stages created yet. Set up a pipeline to get started.
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>Round</TableHead>
|
<TableHead>Stage</TableHead>
|
||||||
<TableHead>Status</TableHead>
|
<TableHead>Status</TableHead>
|
||||||
<TableHead>Projects</TableHead>
|
<TableHead>Projects</TableHead>
|
||||||
<TableHead>Assignments</TableHead>
|
<TableHead>Assignments</TableHead>
|
||||||
<TableHead>Created</TableHead>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{program.rounds.map((round) => (
|
{(program.stages as Array<{ id: string; name: string; status: string; _count: { projects: number; assignments: number } }>).map((stage) => (
|
||||||
<TableRow key={round.id}>
|
<TableRow key={stage.id}>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Link
|
<span className="font-medium">
|
||||||
href={`/admin/rounds/${round.id}`}
|
{stage.name}
|
||||||
className="font-medium hover:underline"
|
</span>
|
||||||
>
|
|
||||||
{round.name}
|
|
||||||
</Link>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Badge variant={statusColors[round.status] || 'secondary'}>
|
<Badge variant={statusColors[stage.status] || 'secondary'}>
|
||||||
{round.status}
|
{stage.status}
|
||||||
</Badge>
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{round._count.projects}</TableCell>
|
<TableCell>{stage._count.projects}</TableCell>
|
||||||
<TableCell>{round._count.assignments}</TableCell>
|
<TableCell>{stage._count.assignments}</TableCell>
|
||||||
<TableCell>{formatDateOnly(round.createdAt)}</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
|
|
||||||
|
|
@ -42,20 +42,31 @@ async function ProgramsContent() {
|
||||||
const programs = await prisma.program.findMany({
|
const programs = await prisma.program.findMany({
|
||||||
// Note: PROGRAM_ADMIN filtering should be handled via middleware or a separate relation
|
// Note: PROGRAM_ADMIN filtering should be handled via middleware or a separate relation
|
||||||
include: {
|
include: {
|
||||||
_count: {
|
pipelines: {
|
||||||
select: {
|
include: {
|
||||||
rounds: true,
|
tracks: {
|
||||||
|
include: {
|
||||||
|
stages: {
|
||||||
|
select: { id: true, status: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rounds: {
|
|
||||||
where: { status: 'ACTIVE' },
|
|
||||||
select: { id: true },
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
})
|
})
|
||||||
|
|
||||||
if (programs.length === 0) {
|
// Flatten stages per program for convenience
|
||||||
|
const programsWithStageCounts = programs.map((p) => {
|
||||||
|
const allStages = p.pipelines.flatMap((pl) =>
|
||||||
|
pl.tracks.flatMap((t) => t.stages)
|
||||||
|
)
|
||||||
|
const activeStages = allStages.filter((s) => s.status === 'STAGE_ACTIVE')
|
||||||
|
return { ...p, stageCount: allStages.length, activeStageCount: activeStages.length }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (programsWithStageCounts.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
|
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
|
||||||
|
|
@ -91,14 +102,14 @@ async function ProgramsContent() {
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>Program</TableHead>
|
<TableHead>Program</TableHead>
|
||||||
<TableHead>Year</TableHead>
|
<TableHead>Year</TableHead>
|
||||||
<TableHead>Rounds</TableHead>
|
<TableHead>Stages</TableHead>
|
||||||
<TableHead>Status</TableHead>
|
<TableHead>Status</TableHead>
|
||||||
<TableHead>Created</TableHead>
|
<TableHead>Created</TableHead>
|
||||||
<TableHead className="text-right">Actions</TableHead>
|
<TableHead className="text-right">Actions</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{programs.map((program) => (
|
{programsWithStageCounts.map((program) => (
|
||||||
<TableRow key={program.id}>
|
<TableRow key={program.id}>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -113,10 +124,10 @@ async function ProgramsContent() {
|
||||||
<TableCell>{program.year}</TableCell>
|
<TableCell>{program.year}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div>
|
<div>
|
||||||
<p>{program._count.rounds} total</p>
|
<p>{program.stageCount} total</p>
|
||||||
{program.rounds.length > 0 && (
|
{program.activeStageCount > 0 && (
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{program.rounds.length} active
|
{program.activeStageCount} active
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -165,7 +176,7 @@ async function ProgramsContent() {
|
||||||
|
|
||||||
{/* Mobile card view */}
|
{/* Mobile card view */}
|
||||||
<div className="space-y-4 md:hidden">
|
<div className="space-y-4 md:hidden">
|
||||||
{programs.map((program) => (
|
{programsWithStageCounts.map((program) => (
|
||||||
<Card key={program.id}>
|
<Card key={program.id}>
|
||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
|
|
@ -180,9 +191,9 @@ async function ProgramsContent() {
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
<div className="flex items-center justify-between text-sm">
|
<div className="flex items-center justify-between text-sm">
|
||||||
<span className="text-muted-foreground">Rounds</span>
|
<span className="text-muted-foreground">Stages</span>
|
||||||
<span>
|
<span>
|
||||||
{program._count.rounds} ({program.rounds.length} active)
|
{program.stageCount} ({program.activeStageCount} active)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between text-sm">
|
<div className="flex items-center justify-between text-sm">
|
||||||
|
|
|
||||||
|
|
@ -120,9 +120,7 @@ function EditProjectContent({ projectId }: { projectId: string }) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Fetch existing tags for suggestions
|
// Fetch existing tags for suggestions
|
||||||
const { data: existingTags } = trpc.project.getTags.useQuery({
|
const { data: existingTags } = trpc.project.getTags.useQuery({})
|
||||||
roundId: project?.roundId ?? undefined,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Mutations
|
// Mutations
|
||||||
const utils = trpc.useUtils()
|
const utils = trpc.useUtils()
|
||||||
|
|
@ -137,7 +135,7 @@ function EditProjectContent({ projectId }: { projectId: string }) {
|
||||||
const deleteProject = trpc.project.delete.useMutation({
|
const deleteProject = trpc.project.delete.useMutation({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
utils.project.list.invalidate()
|
utils.project.list.invalidate()
|
||||||
utils.round.get.invalidate()
|
utils.program.get.invalidate()
|
||||||
router.push('/admin/projects')
|
router.push('/admin/projects')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -202,7 +200,6 @@ function EditProjectContent({ projectId }: { projectId: string }) {
|
||||||
teamName: data.teamName || null,
|
teamName: data.teamName || null,
|
||||||
description: data.description || null,
|
description: data.description || null,
|
||||||
status: data.status,
|
status: data.status,
|
||||||
roundId: project?.roundId ?? undefined,
|
|
||||||
tags: data.tags,
|
tags: data.tags,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -86,17 +86,12 @@ function ProjectDetailContent({ projectId }: { projectId: string }) {
|
||||||
// Fetch files (flat list for backward compatibility)
|
// Fetch files (flat list for backward compatibility)
|
||||||
const { data: files } = trpc.file.listByProject.useQuery({ projectId })
|
const { data: files } = trpc.file.listByProject.useQuery({ projectId })
|
||||||
|
|
||||||
// Fetch grouped files by round (if project has a roundId)
|
// Fetch available stages for upload selector (if project has a programId)
|
||||||
const { data: groupedFiles } = trpc.file.listByProjectForRound.useQuery(
|
const { data: programData } = trpc.program.get.useQuery(
|
||||||
{ projectId, roundId: project?.roundId || '' },
|
{ id: project?.programId || '' },
|
||||||
{ enabled: !!project?.roundId }
|
|
||||||
)
|
|
||||||
|
|
||||||
// Fetch available rounds for upload selector (if project has a programId)
|
|
||||||
const { data: rounds } = trpc.round.listByProgram.useQuery(
|
|
||||||
{ programId: project?.programId || '' },
|
|
||||||
{ enabled: !!project?.programId }
|
{ enabled: !!project?.programId }
|
||||||
)
|
)
|
||||||
|
const availableStages = (programData?.stages as Array<{ id: string; name: string }>) || []
|
||||||
|
|
||||||
const utils = trpc.useUtils()
|
const utils = trpc.useUtils()
|
||||||
|
|
||||||
|
|
@ -148,15 +143,15 @@ function ProjectDetailContent({ projectId }: { projectId: string }) {
|
||||||
/>
|
/>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex flex-wrap items-center gap-1 text-sm text-muted-foreground">
|
<div className="flex flex-wrap items-center gap-1 text-sm text-muted-foreground">
|
||||||
{project.roundId ? (
|
{project.programId ? (
|
||||||
<Link
|
<Link
|
||||||
href={`/admin/rounds/${project.roundId}`}
|
href={`/admin/programs/${project.programId}`}
|
||||||
className="hover:underline"
|
className="hover:underline"
|
||||||
>
|
>
|
||||||
{project.round?.name ?? 'Round'}
|
{programData?.name ?? 'Program'}
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<span>No round</span>
|
<span>No program</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
|
|
@ -526,9 +521,7 @@ function ProjectDetailContent({ projectId }: { projectId: string }) {
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
{groupedFiles && groupedFiles.length > 0 ? (
|
{files && files.length > 0 ? (
|
||||||
<FileViewer groupedFiles={groupedFiles} />
|
|
||||||
) : files && files.length > 0 ? (
|
|
||||||
<FileViewer
|
<FileViewer
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
files={files.map((f) => ({
|
files={files.map((f) => ({
|
||||||
|
|
@ -551,13 +544,9 @@ function ProjectDetailContent({ projectId }: { projectId: string }) {
|
||||||
<p className="text-sm font-medium mb-3">Upload New Files</p>
|
<p className="text-sm font-medium mb-3">Upload New Files</p>
|
||||||
<FileUpload
|
<FileUpload
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
roundId={project.roundId || undefined}
|
availableStages={availableStages?.map((s: { id: string; name: string }) => ({ id: s.id, name: s.name }))}
|
||||||
availableRounds={rounds?.map((r: { id: string; name: string }) => ({ id: r.id, name: r.name }))}
|
|
||||||
onUploadComplete={() => {
|
onUploadComplete={() => {
|
||||||
utils.file.listByProject.invalidate({ projectId })
|
utils.file.listByProject.invalidate({ projectId })
|
||||||
if (project.roundId) {
|
|
||||||
utils.file.listByProjectForRound.invalidate({ projectId, roundId: project.roundId })
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -585,7 +574,7 @@ function ProjectDetailContent({ projectId }: { projectId: string }) {
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="outline" size="sm" asChild>
|
<Button variant="outline" size="sm" asChild>
|
||||||
<Link href={`/admin/rounds/${project.roundId}/assignments`}>
|
<Link href={`/admin/members`}>
|
||||||
Manage
|
Manage
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -688,10 +677,10 @@ function ProjectDetailContent({ projectId }: { projectId: string }) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* AI Evaluation Summary */}
|
{/* AI Evaluation Summary */}
|
||||||
{project.roundId && stats && stats.totalEvaluations > 0 && (
|
{assignments && assignments.length > 0 && stats && stats.totalEvaluations > 0 && (
|
||||||
<EvaluationSummaryCard
|
<EvaluationSummaryCard
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
roundId={project.roundId}
|
stageId={assignments[0].stageId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -30,26 +30,26 @@ function ImportPageContent() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const utils = trpc.useUtils()
|
const utils = trpc.useUtils()
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const roundIdParam = searchParams.get('round')
|
const stageIdParam = searchParams.get('stage')
|
||||||
|
|
||||||
const [selectedRoundId, setSelectedRoundId] = useState<string>(roundIdParam || '')
|
const [selectedStageId, setSelectedStageId] = useState<string>(stageIdParam || '')
|
||||||
|
|
||||||
// Fetch active programs with rounds
|
// Fetch active programs with stages
|
||||||
const { data: programs, isLoading: loadingPrograms } = trpc.program.list.useQuery({
|
const { data: programs, isLoading: loadingPrograms } = trpc.program.list.useQuery({
|
||||||
status: 'ACTIVE',
|
status: 'ACTIVE',
|
||||||
includeRounds: true,
|
includeStages: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Get all rounds from programs
|
// Get all stages from programs
|
||||||
const rounds = programs?.flatMap((p) =>
|
const stages = programs?.flatMap((p) =>
|
||||||
(p.rounds || []).map((r) => ({
|
((p.stages ?? []) as Array<{ id: string; name: string }>).map((s: { id: string; name: string }) => ({
|
||||||
...r,
|
...s,
|
||||||
programId: p.id,
|
programId: p.id,
|
||||||
programName: `${p.year} Edition`,
|
programName: `${p.year} Edition`,
|
||||||
}))
|
}))
|
||||||
) || []
|
) || []
|
||||||
|
|
||||||
const selectedRound = rounds.find((r) => r.id === selectedRoundId)
|
const selectedStage = stages.find((s: { id: string }) => s.id === selectedStageId)
|
||||||
|
|
||||||
if (loadingPrograms) {
|
if (loadingPrograms) {
|
||||||
return <ImportPageSkeleton />
|
return <ImportPageSkeleton />
|
||||||
|
|
@ -70,44 +70,44 @@ function ImportPageContent() {
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-semibold tracking-tight">Import Projects</h1>
|
<h1 className="text-2xl font-semibold tracking-tight">Import Projects</h1>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
Import projects from a CSV file into a round
|
Import projects from a CSV file into a stage
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Round selection */}
|
{/* Stage selection */}
|
||||||
{!selectedRoundId && (
|
{!selectedStageId && (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Select Round</CardTitle>
|
<CardTitle>Select Stage</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Choose the round you want to import projects into
|
Choose the stage you want to import projects into
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
{rounds.length === 0 ? (
|
{stages.length === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center py-8 text-center">
|
<div className="flex flex-col items-center justify-center py-8 text-center">
|
||||||
<AlertCircle className="h-12 w-12 text-muted-foreground/50" />
|
<AlertCircle className="h-12 w-12 text-muted-foreground/50" />
|
||||||
<p className="mt-2 font-medium">No Active Rounds</p>
|
<p className="mt-2 font-medium">No Active Stages</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Create a round first before importing projects
|
Create a stage first before importing projects
|
||||||
</p>
|
</p>
|
||||||
<Button asChild className="mt-4">
|
<Button asChild className="mt-4">
|
||||||
<Link href="/admin/rounds/new">Create Round</Link>
|
<Link href="/admin/rounds/new-pipeline">Create Pipeline</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Select value={selectedRoundId} onValueChange={setSelectedRoundId}>
|
<Select value={selectedStageId} onValueChange={setSelectedStageId}>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select a round" />
|
<SelectValue placeholder="Select a stage" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{rounds.map((round) => (
|
{stages.map((stage) => (
|
||||||
<SelectItem key={round.id} value={round.id}>
|
<SelectItem key={stage.id} value={stage.id}>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span>{round.name}</span>
|
<span>{stage.name}</span>
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
{round.programName}
|
{stage.programName}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
|
|
@ -117,11 +117,11 @@ function ImportPageContent() {
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (selectedRoundId) {
|
if (selectedStageId) {
|
||||||
router.push(`/admin/projects/import?round=${selectedRoundId}`)
|
router.push(`/admin/projects/import?stage=${selectedStageId}`)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={!selectedRoundId}
|
disabled={!selectedStageId}
|
||||||
>
|
>
|
||||||
Continue
|
Continue
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -132,14 +132,14 @@ function ImportPageContent() {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Import form */}
|
{/* Import form */}
|
||||||
{selectedRoundId && selectedRound && (
|
{selectedStageId && selectedStage && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<FileSpreadsheet className="h-8 w-8 text-muted-foreground" />
|
<FileSpreadsheet className="h-8 w-8 text-muted-foreground" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">Importing into: {selectedRound.name}</p>
|
<p className="font-medium">Importing into: {selectedStage.name}</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{selectedRound.programName}
|
{selectedStage.programName}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -147,11 +147,11 @@ function ImportPageContent() {
|
||||||
size="sm"
|
size="sm"
|
||||||
className="ml-auto"
|
className="ml-auto"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedRoundId('')
|
setSelectedStageId('')
|
||||||
router.push('/admin/projects/import')
|
router.push('/admin/projects/import')
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Change Round
|
Change Stage
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -172,32 +172,31 @@ function ImportPageContent() {
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value="csv" className="mt-4">
|
<TabsContent value="csv" className="mt-4">
|
||||||
<CSVImportForm
|
<CSVImportForm
|
||||||
programId={selectedRound.programId}
|
programId={selectedStage.programId}
|
||||||
roundId={selectedRoundId}
|
stageName={selectedStage.name}
|
||||||
roundName={selectedRound.name}
|
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
utils.project.list.invalidate()
|
utils.project.list.invalidate()
|
||||||
utils.round.get.invalidate()
|
utils.program.get.invalidate()
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="notion" className="mt-4">
|
<TabsContent value="notion" className="mt-4">
|
||||||
<NotionImportForm
|
<NotionImportForm
|
||||||
roundId={selectedRoundId}
|
programId={selectedStage.programId}
|
||||||
roundName={selectedRound.name}
|
stageName={selectedStage.name}
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
utils.project.list.invalidate()
|
utils.project.list.invalidate()
|
||||||
utils.round.get.invalidate()
|
utils.program.get.invalidate()
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="typeform" className="mt-4">
|
<TabsContent value="typeform" className="mt-4">
|
||||||
<TypeformImportForm
|
<TypeformImportForm
|
||||||
roundId={selectedRoundId}
|
programId={selectedStage.programId}
|
||||||
roundName={selectedRound.name}
|
stageName={selectedStage.name}
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
utils.project.list.invalidate()
|
utils.project.list.invalidate()
|
||||||
utils.round.get.invalidate()
|
utils.program.get.invalidate()
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
|
||||||
|
|
@ -85,11 +85,11 @@ const ROLE_SORT_ORDER: Record<string, number> = { LEAD: 0, MEMBER: 1, ADVISOR: 2
|
||||||
function NewProjectPageContent() {
|
function NewProjectPageContent() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const roundIdParam = searchParams.get('round')
|
const stageIdParam = searchParams.get('stage')
|
||||||
const programIdParam = searchParams.get('program')
|
const programIdParam = searchParams.get('program')
|
||||||
|
|
||||||
const [selectedProgramId, setSelectedProgramId] = useState<string>(programIdParam || '')
|
const [selectedProgramId, setSelectedProgramId] = useState<string>(programIdParam || '')
|
||||||
const [selectedRoundId, setSelectedRoundId] = useState<string>(roundIdParam || '')
|
const [selectedStageId, setSelectedStageId] = useState<string>(stageIdParam || '')
|
||||||
|
|
||||||
// Form state
|
// Form state
|
||||||
const [title, setTitle] = useState('')
|
const [title, setTitle] = useState('')
|
||||||
|
|
@ -113,7 +113,7 @@ function NewProjectPageContent() {
|
||||||
// Fetch programs
|
// Fetch programs
|
||||||
const { data: programs, isLoading: loadingPrograms } = trpc.program.list.useQuery({
|
const { data: programs, isLoading: loadingPrograms } = trpc.program.list.useQuery({
|
||||||
status: 'ACTIVE',
|
status: 'ACTIVE',
|
||||||
includeRounds: true,
|
includeStages: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Fetch wizard config for selected program (dropdown options)
|
// Fetch wizard config for selected program (dropdown options)
|
||||||
|
|
@ -128,7 +128,7 @@ function NewProjectPageContent() {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success('Project created successfully')
|
toast.success('Project created successfully')
|
||||||
utils.project.list.invalidate()
|
utils.project.list.invalidate()
|
||||||
utils.round.get.invalidate()
|
utils.program.get.invalidate()
|
||||||
router.push('/admin/projects')
|
router.push('/admin/projects')
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
|
|
@ -136,9 +136,9 @@ function NewProjectPageContent() {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Get rounds for selected program
|
// Get stages for selected program
|
||||||
const selectedProgram = programs?.find((p) => p.id === selectedProgramId)
|
const selectedProgram = programs?.find((p) => p.id === selectedProgramId)
|
||||||
const rounds = selectedProgram?.rounds || []
|
const stages = (selectedProgram?.stages || []) as Array<{ id: string; name: string }>
|
||||||
|
|
||||||
// Get dropdown options from wizard config
|
// Get dropdown options from wizard config
|
||||||
const categoryOptions = wizardConfig?.competitionCategories || []
|
const categoryOptions = wizardConfig?.competitionCategories || []
|
||||||
|
|
@ -216,7 +216,6 @@ function NewProjectPageContent() {
|
||||||
|
|
||||||
createProject.mutate({
|
createProject.mutate({
|
||||||
programId: selectedProgramId,
|
programId: selectedProgramId,
|
||||||
roundId: selectedRoundId || undefined,
|
|
||||||
title: title.trim(),
|
title: title.trim(),
|
||||||
teamName: teamName.trim() || undefined,
|
teamName: teamName.trim() || undefined,
|
||||||
description: description.trim() || undefined,
|
description: description.trim() || undefined,
|
||||||
|
|
@ -264,12 +263,12 @@ function NewProjectPageContent() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Program & Round selection */}
|
{/* Program & Stage selection */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Program & Round</CardTitle>
|
<CardTitle>Program & Stage</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Select the program for this project. Round assignment is optional.
|
Select the program for this project. Stage assignment is optional.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
|
|
@ -287,7 +286,7 @@ function NewProjectPageContent() {
|
||||||
<Label>Program *</Label>
|
<Label>Program *</Label>
|
||||||
<Select value={selectedProgramId} onValueChange={(v) => {
|
<Select value={selectedProgramId} onValueChange={(v) => {
|
||||||
setSelectedProgramId(v)
|
setSelectedProgramId(v)
|
||||||
setSelectedRoundId('') // Reset round on program change
|
setSelectedStageId('') // Reset stage on program change
|
||||||
}}>
|
}}>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select a program" />
|
<SelectValue placeholder="Select a program" />
|
||||||
|
|
@ -303,16 +302,16 @@ function NewProjectPageContent() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Round (optional)</Label>
|
<Label>Stage (optional)</Label>
|
||||||
<Select value={selectedRoundId || '__none__'} onValueChange={(v) => setSelectedRoundId(v === '__none__' ? '' : v)} disabled={!selectedProgramId}>
|
<Select value={selectedStageId || '__none__'} onValueChange={(v) => setSelectedStageId(v === '__none__' ? '' : v)} disabled={!selectedProgramId}>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="No round assigned" />
|
<SelectValue placeholder="No stage assigned" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="__none__">No round assigned</SelectItem>
|
<SelectItem value="__none__">No stage assigned</SelectItem>
|
||||||
{rounds.map((r: { id: string; name: string }) => (
|
{stages.map((s) => (
|
||||||
<SelectItem key={r.id} value={r.id}>
|
<SelectItem key={s.id} value={s.id}>
|
||||||
{r.name}
|
{s.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@ import { CountryFlagImg } from '@/components/ui/country-select'
|
||||||
import {
|
import {
|
||||||
ProjectFiltersBar,
|
ProjectFiltersBar,
|
||||||
type ProjectFilters,
|
type ProjectFilters,
|
||||||
|
type FilterOptions,
|
||||||
} from './project-filters'
|
} from './project-filters'
|
||||||
import { AnimatedCard } from '@/components/shared/animated-container'
|
import { AnimatedCard } from '@/components/shared/animated-container'
|
||||||
|
|
||||||
|
|
@ -121,7 +122,7 @@ function parseFiltersFromParams(
|
||||||
statuses: searchParams.get('status')
|
statuses: searchParams.get('status')
|
||||||
? searchParams.get('status')!.split(',')
|
? searchParams.get('status')!.split(',')
|
||||||
: [],
|
: [],
|
||||||
roundId: searchParams.get('round') || '',
|
stageId: searchParams.get('stage') || '',
|
||||||
competitionCategory: searchParams.get('category') || '',
|
competitionCategory: searchParams.get('category') || '',
|
||||||
oceanIssue: searchParams.get('issue') || '',
|
oceanIssue: searchParams.get('issue') || '',
|
||||||
country: searchParams.get('country') || '',
|
country: searchParams.get('country') || '',
|
||||||
|
|
@ -155,7 +156,7 @@ function filtersToParams(
|
||||||
if (filters.search) params.set('q', filters.search)
|
if (filters.search) params.set('q', filters.search)
|
||||||
if (filters.statuses.length > 0)
|
if (filters.statuses.length > 0)
|
||||||
params.set('status', filters.statuses.join(','))
|
params.set('status', filters.statuses.join(','))
|
||||||
if (filters.roundId) params.set('round', filters.roundId)
|
if (filters.stageId) params.set('stage', filters.stageId)
|
||||||
if (filters.competitionCategory)
|
if (filters.competitionCategory)
|
||||||
params.set('category', filters.competitionCategory)
|
params.set('category', filters.competitionCategory)
|
||||||
if (filters.oceanIssue) params.set('issue', filters.oceanIssue)
|
if (filters.oceanIssue) params.set('issue', filters.oceanIssue)
|
||||||
|
|
@ -180,7 +181,7 @@ export default function ProjectsPage() {
|
||||||
const [filters, setFilters] = useState<ProjectFilters>({
|
const [filters, setFilters] = useState<ProjectFilters>({
|
||||||
search: parsed.search,
|
search: parsed.search,
|
||||||
statuses: parsed.statuses,
|
statuses: parsed.statuses,
|
||||||
roundId: parsed.roundId,
|
stageId: parsed.stageId,
|
||||||
competitionCategory: parsed.competitionCategory,
|
competitionCategory: parsed.competitionCategory,
|
||||||
oceanIssue: parsed.oceanIssue,
|
oceanIssue: parsed.oceanIssue,
|
||||||
country: parsed.country,
|
country: parsed.country,
|
||||||
|
|
@ -251,7 +252,7 @@ export default function ProjectsPage() {
|
||||||
| 'REJECTED'
|
| 'REJECTED'
|
||||||
>)
|
>)
|
||||||
: undefined,
|
: undefined,
|
||||||
roundId: filters.roundId || undefined,
|
stageId: filters.stageId || undefined,
|
||||||
competitionCategory:
|
competitionCategory:
|
||||||
(filters.competitionCategory as 'STARTUP' | 'BUSINESS_CONCEPT') ||
|
(filters.competitionCategory as 'STARTUP' | 'BUSINESS_CONCEPT') ||
|
||||||
undefined,
|
undefined,
|
||||||
|
|
@ -283,14 +284,14 @@ export default function ProjectsPage() {
|
||||||
|
|
||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
|
||||||
const [projectToDelete, setProjectToDelete] = useState<{ id: string; title: string } | null>(null)
|
const [projectToDelete, setProjectToDelete] = useState<{ id: string; title: string } | null>(null)
|
||||||
// Assign to round dialog state
|
// Assign to stage dialog state
|
||||||
const [assignDialogOpen, setAssignDialogOpen] = useState(false)
|
const [assignDialogOpen, setAssignDialogOpen] = useState(false)
|
||||||
const [projectToAssign, setProjectToAssign] = useState<{ id: string; title: string } | null>(null)
|
const [projectToAssign, setProjectToAssign] = useState<{ id: string; title: string } | null>(null)
|
||||||
const [assignRoundId, setAssignRoundId] = useState('')
|
const [assignStageId, setAssignStageId] = useState('')
|
||||||
|
|
||||||
const [aiTagDialogOpen, setAiTagDialogOpen] = useState(false)
|
const [aiTagDialogOpen, setAiTagDialogOpen] = useState(false)
|
||||||
const [taggingScope, setTaggingScope] = useState<'round' | 'program'>('round')
|
const [taggingScope, setTaggingScope] = useState<'stage' | 'program'>('stage')
|
||||||
const [selectedRoundForTagging, setSelectedRoundForTagging] = useState<string>('')
|
const [selectedStageForTagging, setSelectedStageForTagging] = useState<string>('')
|
||||||
const [selectedProgramForTagging, setSelectedProgramForTagging] = useState<string>('')
|
const [selectedProgramForTagging, setSelectedProgramForTagging] = useState<string>('')
|
||||||
const [activeTaggingJobId, setActiveTaggingJobId] = useState<string | null>(null)
|
const [activeTaggingJobId, setActiveTaggingJobId] = useState<string | null>(null)
|
||||||
|
|
||||||
|
|
@ -350,8 +351,14 @@ export default function ProjectsPage() {
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const handleStartTagging = () => {
|
const handleStartTagging = () => {
|
||||||
if (taggingScope === 'round' && selectedRoundForTagging) {
|
if (taggingScope === 'stage' && selectedStageForTagging) {
|
||||||
startTaggingJob.mutate({ roundId: selectedRoundForTagging })
|
// Router only accepts programId; resolve from the selected stage's parent program
|
||||||
|
const parentProgram = programs?.find((p) =>
|
||||||
|
((p.stages ?? []) as Array<{ id: string }>)?.some((s: { id: string }) => s.id === selectedStageForTagging)
|
||||||
|
)
|
||||||
|
if (parentProgram) {
|
||||||
|
startTaggingJob.mutate({ programId: parentProgram.id })
|
||||||
|
}
|
||||||
} else if (taggingScope === 'program' && selectedProgramForTagging) {
|
} else if (taggingScope === 'program' && selectedProgramForTagging) {
|
||||||
startTaggingJob.mutate({ programId: selectedProgramForTagging })
|
startTaggingJob.mutate({ programId: selectedProgramForTagging })
|
||||||
}
|
}
|
||||||
|
|
@ -361,20 +368,19 @@ export default function ProjectsPage() {
|
||||||
if (!taggingInProgress) {
|
if (!taggingInProgress) {
|
||||||
setAiTagDialogOpen(false)
|
setAiTagDialogOpen(false)
|
||||||
setActiveTaggingJobId(null)
|
setActiveTaggingJobId(null)
|
||||||
setSelectedRoundForTagging('')
|
setSelectedStageForTagging('')
|
||||||
setSelectedProgramForTagging('')
|
setSelectedProgramForTagging('')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get selected program's rounds
|
// Get selected program's stages (flattened from pipelines -> tracks -> stages)
|
||||||
const selectedProgram = programs?.find(p => p.id === selectedProgramForTagging)
|
const selectedProgram = programs?.find(p => p.id === selectedProgramForTagging)
|
||||||
const programRounds = filterOptions?.rounds?.filter(r => r.program?.id === selectedProgramForTagging) ?? []
|
const programStages = selectedProgram?.stages ?? []
|
||||||
|
|
||||||
// Calculate stats for display
|
// Calculate stats for display
|
||||||
const selectedRound = filterOptions?.rounds?.find(r => r.id === selectedRoundForTagging)
|
|
||||||
const displayProgram = taggingScope === 'program'
|
const displayProgram = taggingScope === 'program'
|
||||||
? selectedProgram
|
? selectedProgram
|
||||||
: (selectedRound ? programs?.find(p => p.id === selectedRound.program?.id) : null)
|
: (selectedStageForTagging ? programs?.find(p => (p.stages as Array<{ id: string }>)?.some(s => s.id === selectedStageForTagging)) : null)
|
||||||
|
|
||||||
// Calculate progress percentage
|
// Calculate progress percentage
|
||||||
const taggingProgressPercent = jobStatus && jobStatus.totalProjects > 0
|
const taggingProgressPercent = jobStatus && jobStatus.totalProjects > 0
|
||||||
|
|
@ -387,7 +393,7 @@ export default function ProjectsPage() {
|
||||||
const [bulkStatus, setBulkStatus] = useState<string>('')
|
const [bulkStatus, setBulkStatus] = useState<string>('')
|
||||||
const [bulkConfirmOpen, setBulkConfirmOpen] = useState(false)
|
const [bulkConfirmOpen, setBulkConfirmOpen] = useState(false)
|
||||||
const [bulkAction, setBulkAction] = useState<'status' | 'assign' | 'delete'>('status')
|
const [bulkAction, setBulkAction] = useState<'status' | 'assign' | 'delete'>('status')
|
||||||
const [bulkAssignRoundId, setBulkAssignRoundId] = useState('')
|
const [bulkAssignStageId, setBulkAssignStageId] = useState('')
|
||||||
const [bulkAssignDialogOpen, setBulkAssignDialogOpen] = useState(false)
|
const [bulkAssignDialogOpen, setBulkAssignDialogOpen] = useState(false)
|
||||||
const [bulkDeleteConfirmOpen, setBulkDeleteConfirmOpen] = useState(false)
|
const [bulkDeleteConfirmOpen, setBulkDeleteConfirmOpen] = useState(false)
|
||||||
|
|
||||||
|
|
@ -406,7 +412,7 @@ export default function ProjectsPage() {
|
||||||
| 'REJECTED'
|
| 'REJECTED'
|
||||||
>)
|
>)
|
||||||
: undefined,
|
: undefined,
|
||||||
roundId: filters.roundId || undefined,
|
stageId: filters.stageId || undefined,
|
||||||
competitionCategory:
|
competitionCategory:
|
||||||
(filters.competitionCategory as 'STARTUP' | 'BUSINESS_CONCEPT') ||
|
(filters.competitionCategory as 'STARTUP' | 'BUSINESS_CONCEPT') ||
|
||||||
undefined,
|
undefined,
|
||||||
|
|
@ -446,12 +452,12 @@ export default function ProjectsPage() {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const bulkAssignToRound = trpc.projectPool.assignToRound.useMutation({
|
const bulkAssignToStage = trpc.projectPool.assignToStage.useMutation({
|
||||||
onSuccess: (result) => {
|
onSuccess: (result) => {
|
||||||
toast.success(`${result.assignedCount} project${result.assignedCount !== 1 ? 's' : ''} assigned to ${result.roundName}`)
|
toast.success(`${result.assignedCount} project${result.assignedCount !== 1 ? 's' : ''} assigned to stage`)
|
||||||
setSelectedIds(new Set())
|
setSelectedIds(new Set())
|
||||||
setAllMatchingSelected(false)
|
setAllMatchingSelected(false)
|
||||||
setBulkAssignRoundId('')
|
setBulkAssignStageId('')
|
||||||
setBulkAssignDialogOpen(false)
|
setBulkAssignDialogOpen(false)
|
||||||
utils.project.list.invalidate()
|
utils.project.list.invalidate()
|
||||||
},
|
},
|
||||||
|
|
@ -526,10 +532,9 @@ export default function ProjectsPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleBulkConfirm = () => {
|
const handleBulkConfirm = () => {
|
||||||
if (!bulkStatus || selectedIds.size === 0 || !filters.roundId) return
|
if (!bulkStatus || selectedIds.size === 0) return
|
||||||
bulkUpdateStatus.mutate({
|
bulkUpdateStatus.mutate({
|
||||||
ids: Array.from(selectedIds),
|
ids: Array.from(selectedIds),
|
||||||
roundId: filters.roundId,
|
|
||||||
status: bulkStatus as 'SUBMITTED' | 'ELIGIBLE' | 'ASSIGNED' | 'SEMIFINALIST' | 'FINALIST' | 'REJECTED',
|
status: bulkStatus as 'SUBMITTED' | 'ELIGIBLE' | 'ASSIGNED' | 'SEMIFINALIST' | 'FINALIST' | 'REJECTED',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -542,13 +547,13 @@ export default function ProjectsPage() {
|
||||||
? data.projects.some((p) => selectedIds.has(p.id)) && !allVisibleSelected
|
? data.projects.some((p) => selectedIds.has(p.id)) && !allVisibleSelected
|
||||||
: false
|
: false
|
||||||
|
|
||||||
const assignToRound = trpc.projectPool.assignToRound.useMutation({
|
const assignToStage = trpc.projectPool.assignToStage.useMutation({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success('Project assigned to round')
|
toast.success('Project assigned to stage')
|
||||||
utils.project.list.invalidate()
|
utils.project.list.invalidate()
|
||||||
setAssignDialogOpen(false)
|
setAssignDialogOpen(false)
|
||||||
setProjectToAssign(null)
|
setProjectToAssign(null)
|
||||||
setAssignRoundId('')
|
setAssignStageId('')
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error(error.message || 'Failed to assign project')
|
toast.error(error.message || 'Failed to assign project')
|
||||||
|
|
@ -579,7 +584,7 @@ export default function ProjectsPage() {
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-semibold tracking-tight">Projects</h1>
|
<h1 className="text-2xl font-semibold tracking-tight">Projects</h1>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
{data ? `${data.total} projects across all rounds` : 'Manage submitted projects across all rounds'}
|
{data ? `${data.total} projects across all stages` : 'Manage submitted projects across all stages'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
|
|
@ -632,7 +637,17 @@ export default function ProjectsPage() {
|
||||||
{/* Filters */}
|
{/* Filters */}
|
||||||
<ProjectFiltersBar
|
<ProjectFiltersBar
|
||||||
filters={filters}
|
filters={filters}
|
||||||
filterOptions={filterOptions}
|
filterOptions={filterOptions ? {
|
||||||
|
...filterOptions,
|
||||||
|
stages: programs?.flatMap(p =>
|
||||||
|
(p.stages as Array<{ id: string; name: string }>)?.map(s => ({
|
||||||
|
id: s.id,
|
||||||
|
name: s.name,
|
||||||
|
programName: p.name,
|
||||||
|
programYear: p.year,
|
||||||
|
})) ?? []
|
||||||
|
) ?? [],
|
||||||
|
} satisfies FilterOptions : undefined}
|
||||||
onChange={handleFiltersChange}
|
onChange={handleFiltersChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -755,7 +770,7 @@ export default function ProjectsPage() {
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{filters.search ||
|
{filters.search ||
|
||||||
filters.statuses.length > 0 ||
|
filters.statuses.length > 0 ||
|
||||||
filters.roundId ||
|
filters.stageId ||
|
||||||
filters.competitionCategory ||
|
filters.competitionCategory ||
|
||||||
filters.oceanIssue ||
|
filters.oceanIssue ||
|
||||||
filters.country
|
filters.country
|
||||||
|
|
@ -799,7 +814,7 @@ export default function ProjectsPage() {
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className="min-w-[280px]">Project</TableHead>
|
<TableHead className="min-w-[280px]">Project</TableHead>
|
||||||
<TableHead>Category</TableHead>
|
<TableHead>Category</TableHead>
|
||||||
<TableHead>Round</TableHead>
|
<TableHead>Stage</TableHead>
|
||||||
<TableHead>Tags</TableHead>
|
<TableHead>Tags</TableHead>
|
||||||
<TableHead>Assignments</TableHead>
|
<TableHead>Assignments</TableHead>
|
||||||
<TableHead>Status</TableHead>
|
<TableHead>Status</TableHead>
|
||||||
|
|
@ -871,8 +886,8 @@ export default function ProjectsPage() {
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{project.round ? (
|
{project.program ? (
|
||||||
<p>{project.round.name}</p>
|
<p>{project.program.name}</p>
|
||||||
) : (
|
) : (
|
||||||
<Badge variant="outline" className="text-xs text-amber-600 border-amber-300 bg-amber-50">
|
<Badge variant="outline" className="text-xs text-amber-600 border-amber-300 bg-amber-50">
|
||||||
Unassigned
|
Unassigned
|
||||||
|
|
@ -880,7 +895,7 @@ export default function ProjectsPage() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{project.round?.program?.name}
|
{project.program?.year}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
@ -939,7 +954,7 @@ export default function ProjectsPage() {
|
||||||
Edit
|
Edit
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
{!project.round && (
|
{project._count.assignments === 0 && (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
@ -948,7 +963,7 @@ export default function ProjectsPage() {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FolderOpen className="mr-2 h-4 w-4" />
|
<FolderOpen className="mr-2 h-4 w-4" />
|
||||||
Assign to Round
|
Assign to Stage
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
|
@ -1000,8 +1015,8 @@ export default function ProjectsPage() {
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
<div className="flex items-center justify-between text-sm">
|
<div className="flex items-center justify-between text-sm">
|
||||||
<span className="text-muted-foreground">Round</span>
|
<span className="text-muted-foreground">Stage</span>
|
||||||
<span>{project.round?.name ?? 'Unassigned'}</span>
|
<span>{project.program?.name ?? 'Unassigned'}</span>
|
||||||
</div>
|
</div>
|
||||||
{project.competitionCategory && (
|
{project.competitionCategory && (
|
||||||
<div className="flex items-center justify-between text-sm">
|
<div className="flex items-center justify-between text-sm">
|
||||||
|
|
@ -1079,7 +1094,7 @@ export default function ProjectsPage() {
|
||||||
Edit
|
Edit
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
{!project.round && (
|
{project._count.assignments === 0 && (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
@ -1088,7 +1103,7 @@ export default function ProjectsPage() {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FolderOpen className="mr-2 h-4 w-4" />
|
<FolderOpen className="mr-2 h-4 w-4" />
|
||||||
Assign to Round
|
Assign to Stage
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
|
@ -1138,10 +1153,10 @@ export default function ProjectsPage() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between text-sm">
|
<div className="flex items-center justify-between text-sm">
|
||||||
<span className="text-muted-foreground">Round</span>
|
<span className="text-muted-foreground">Program</span>
|
||||||
<span className="text-right">
|
<span className="text-right">
|
||||||
{project.round ? (
|
{project.program ? (
|
||||||
<>{project.round.name}</>
|
<>{project.program.name}</>
|
||||||
) : (
|
) : (
|
||||||
<Badge variant="outline" className="text-xs text-amber-600 border-amber-300 bg-amber-50">
|
<Badge variant="outline" className="text-xs text-amber-600 border-amber-300 bg-amber-50">
|
||||||
Unassigned
|
Unassigned
|
||||||
|
|
@ -1207,17 +1222,17 @@ export default function ProjectsPage() {
|
||||||
{selectedIds.size} selected
|
{selectedIds.size} selected
|
||||||
</Badge>
|
</Badge>
|
||||||
<div className="flex flex-wrap gap-2 flex-1">
|
<div className="flex flex-wrap gap-2 flex-1">
|
||||||
{/* Assign to Round */}
|
{/* Assign to Stage */}
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => setBulkAssignDialogOpen(true)}
|
onClick={() => setBulkAssignDialogOpen(true)}
|
||||||
>
|
>
|
||||||
<ArrowRightCircle className="mr-1.5 h-4 w-4" />
|
<ArrowRightCircle className="mr-1.5 h-4 w-4" />
|
||||||
Assign to Round
|
Assign to Stage
|
||||||
</Button>
|
</Button>
|
||||||
{/* Change Status (only when filtered by round) */}
|
{/* Change Status (only when filtered by stage) */}
|
||||||
{filters.roundId && (
|
{filters.stageId && (
|
||||||
<>
|
<>
|
||||||
<Select value={bulkStatus} onValueChange={setBulkStatus}>
|
<Select value={bulkStatus} onValueChange={setBulkStatus}>
|
||||||
<SelectTrigger className="w-[160px] h-9 text-sm">
|
<SelectTrigger className="w-[160px] h-9 text-sm">
|
||||||
|
|
@ -1332,30 +1347,30 @@ export default function ProjectsPage() {
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
|
|
||||||
{/* Assign to Round Dialog */}
|
{/* Assign to Stage Dialog */}
|
||||||
<Dialog open={assignDialogOpen} onOpenChange={(open) => {
|
<Dialog open={assignDialogOpen} onOpenChange={(open) => {
|
||||||
setAssignDialogOpen(open)
|
setAssignDialogOpen(open)
|
||||||
if (!open) { setProjectToAssign(null); setAssignRoundId('') }
|
if (!open) { setProjectToAssign(null); setAssignStageId('') }
|
||||||
}}>
|
}}>
|
||||||
<DialogContent className="sm:max-w-md">
|
<DialogContent className="sm:max-w-md">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Assign to Round</DialogTitle>
|
<DialogTitle>Assign to Stage</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Assign "{projectToAssign?.title}" to a round.
|
Assign "{projectToAssign?.title}" to a stage.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-4 py-4">
|
<div className="space-y-4 py-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Select Round</Label>
|
<Label>Select Stage</Label>
|
||||||
<Select value={assignRoundId} onValueChange={setAssignRoundId}>
|
<Select value={assignStageId} onValueChange={setAssignStageId}>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Choose a round..." />
|
<SelectValue placeholder="Choose a round..." />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{programs?.flatMap((p) =>
|
{programs?.flatMap((p) =>
|
||||||
(p.rounds || []).map((r: { id: string; name: string }) => (
|
((p.stages || []) as Array<{ id: string; name: string }>).map((s) => (
|
||||||
<SelectItem key={r.id} value={r.id}>
|
<SelectItem key={s.id} value={s.id}>
|
||||||
{p.name} {p.year} - {r.name}
|
{p.name} {p.year} - {s.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
|
@ -1369,46 +1384,46 @@ export default function ProjectsPage() {
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (projectToAssign && assignRoundId) {
|
if (projectToAssign && assignStageId) {
|
||||||
assignToRound.mutate({
|
assignToStage.mutate({
|
||||||
projectIds: [projectToAssign.id],
|
projectIds: [projectToAssign.id],
|
||||||
roundId: assignRoundId,
|
stageId: assignStageId,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={!assignRoundId || assignToRound.isPending}
|
disabled={!assignStageId || assignToStage.isPending}
|
||||||
>
|
>
|
||||||
{assignToRound.isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
{assignToStage.isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||||
Assign
|
Assign
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
{/* Bulk Assign to Round Dialog */}
|
{/* Bulk Assign to Stage Dialog */}
|
||||||
<Dialog open={bulkAssignDialogOpen} onOpenChange={(open) => {
|
<Dialog open={bulkAssignDialogOpen} onOpenChange={(open) => {
|
||||||
setBulkAssignDialogOpen(open)
|
setBulkAssignDialogOpen(open)
|
||||||
if (!open) setBulkAssignRoundId('')
|
if (!open) setBulkAssignStageId('')
|
||||||
}}>
|
}}>
|
||||||
<DialogContent className="sm:max-w-md">
|
<DialogContent className="sm:max-w-md">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Assign to Round</DialogTitle>
|
<DialogTitle>Assign to Stage</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Assign {selectedIds.size} selected project{selectedIds.size !== 1 ? 's' : ''} to a round. Projects will have their status set to "Assigned".
|
Assign {selectedIds.size} selected project{selectedIds.size !== 1 ? 's' : ''} to a stage. Projects will have their status set to "Assigned".
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-4 py-4">
|
<div className="space-y-4 py-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Select Round</Label>
|
<Label>Select Stage</Label>
|
||||||
<Select value={bulkAssignRoundId} onValueChange={setBulkAssignRoundId}>
|
<Select value={bulkAssignStageId} onValueChange={setBulkAssignStageId}>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Choose a round..." />
|
<SelectValue placeholder="Choose a stage..." />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{programs?.flatMap((p) =>
|
{programs?.flatMap((p) =>
|
||||||
(p.rounds || []).map((r: { id: string; name: string }) => (
|
((p.stages || []) as Array<{ id: string; name: string }>).map((s) => (
|
||||||
<SelectItem key={r.id} value={r.id}>
|
<SelectItem key={s.id} value={s.id}>
|
||||||
{p.name} {p.year} - {r.name}
|
{p.name} {p.year} - {s.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
|
@ -1422,16 +1437,16 @@ export default function ProjectsPage() {
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (bulkAssignRoundId && selectedIds.size > 0) {
|
if (bulkAssignStageId && selectedIds.size > 0) {
|
||||||
bulkAssignToRound.mutate({
|
bulkAssignToStage.mutate({
|
||||||
projectIds: Array.from(selectedIds),
|
projectIds: Array.from(selectedIds),
|
||||||
roundId: bulkAssignRoundId,
|
stageId: bulkAssignStageId,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={!bulkAssignRoundId || bulkAssignToRound.isPending}
|
disabled={!bulkAssignStageId || bulkAssignToStage.isPending}
|
||||||
>
|
>
|
||||||
{bulkAssignToRound.isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
{bulkAssignToStage.isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||||
Assign {selectedIds.size} Project{selectedIds.size !== 1 ? 's' : ''}
|
Assign {selectedIds.size} Project{selectedIds.size !== 1 ? 's' : ''}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1608,19 +1623,19 @@ export default function ProjectsPage() {
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setTaggingScope('round')}
|
onClick={() => setTaggingScope('stage')}
|
||||||
className={`flex flex-col items-center gap-2 p-4 rounded-lg border-2 transition-colors ${
|
className={`flex flex-col items-center gap-2 p-4 rounded-lg border-2 transition-colors ${
|
||||||
taggingScope === 'round'
|
taggingScope === 'stage'
|
||||||
? 'border-primary bg-primary/5'
|
? 'border-primary bg-primary/5'
|
||||||
: 'border-border hover:border-muted-foreground/30'
|
: 'border-border hover:border-muted-foreground/30'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<FolderOpen className={`h-6 w-6 ${taggingScope === 'round' ? 'text-primary' : 'text-muted-foreground'}`} />
|
<FolderOpen className={`h-6 w-6 ${taggingScope === 'stage' ? 'text-primary' : 'text-muted-foreground'}`} />
|
||||||
<span className={`text-sm font-medium ${taggingScope === 'round' ? 'text-primary' : ''}`}>
|
<span className={`text-sm font-medium ${taggingScope === 'stage' ? 'text-primary' : ''}`}>
|
||||||
Single Round
|
Single Stage
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-muted-foreground text-center">
|
<span className="text-xs text-muted-foreground text-center">
|
||||||
Tag projects in one specific round
|
Tag projects in one specific stage
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
|
@ -1637,7 +1652,7 @@ export default function ProjectsPage() {
|
||||||
Entire Edition
|
Entire Edition
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-muted-foreground text-center">
|
<span className="text-xs text-muted-foreground text-center">
|
||||||
Tag all projects across all rounds
|
Tag all projects across all stages
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1645,22 +1660,24 @@ export default function ProjectsPage() {
|
||||||
|
|
||||||
{/* Selection */}
|
{/* Selection */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{taggingScope === 'round' ? (
|
{taggingScope === 'stage' ? (
|
||||||
<>
|
<>
|
||||||
<Label htmlFor="round-select">Select Round</Label>
|
<Label htmlFor="stage-select">Select Stage</Label>
|
||||||
<Select
|
<Select
|
||||||
value={selectedRoundForTagging}
|
value={selectedStageForTagging}
|
||||||
onValueChange={setSelectedRoundForTagging}
|
onValueChange={setSelectedStageForTagging}
|
||||||
>
|
>
|
||||||
<SelectTrigger id="round-select">
|
<SelectTrigger id="stage-select">
|
||||||
<SelectValue placeholder="Choose a round..." />
|
<SelectValue placeholder="Choose a stage..." />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{filterOptions?.rounds?.map((round) => (
|
{programs?.flatMap(p =>
|
||||||
<SelectItem key={round.id} value={round.id}>
|
(p.stages as Array<{ id: string; name: string }>)?.map(s => (
|
||||||
{round.name} ({round.program?.name})
|
<SelectItem key={s.id} value={s.id}>
|
||||||
</SelectItem>
|
{s.name} ({p.name})
|
||||||
))}
|
</SelectItem>
|
||||||
|
)) ?? []
|
||||||
|
)}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</>
|
</>
|
||||||
|
|
@ -1716,7 +1733,7 @@ export default function ProjectsPage() {
|
||||||
onClick={handleStartTagging}
|
onClick={handleStartTagging}
|
||||||
disabled={
|
disabled={
|
||||||
taggingInProgress ||
|
taggingInProgress ||
|
||||||
(taggingScope === 'round' && !selectedRoundForTagging) ||
|
(taggingScope === 'stage' && !selectedStageForTagging) ||
|
||||||
(taggingScope === 'program' && !selectedProgramForTagging)
|
(taggingScope === 'program' && !selectedProgramForTagging)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ export default function ProjectPoolPage() {
|
||||||
const [selectedProgramId, setSelectedProgramId] = useState<string>('')
|
const [selectedProgramId, setSelectedProgramId] = useState<string>('')
|
||||||
const [selectedProjects, setSelectedProjects] = useState<string[]>([])
|
const [selectedProjects, setSelectedProjects] = useState<string[]>([])
|
||||||
const [assignDialogOpen, setAssignDialogOpen] = useState(false)
|
const [assignDialogOpen, setAssignDialogOpen] = useState(false)
|
||||||
const [targetRoundId, setTargetRoundId] = useState<string>('')
|
const [targetStageId, setTargetStageId] = useState<string>('')
|
||||||
const [searchQuery, setSearchQuery] = useState('')
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
const [categoryFilter, setCategoryFilter] = useState<'STARTUP' | 'BUSINESS_CONCEPT' | 'all'>('all')
|
const [categoryFilter, setCategoryFilter] = useState<'STARTUP' | 'BUSINESS_CONCEPT' | 'all'>('all')
|
||||||
const [currentPage, setCurrentPage] = useState(1)
|
const [currentPage, setCurrentPage] = useState(1)
|
||||||
|
|
@ -50,20 +50,22 @@ export default function ProjectPoolPage() {
|
||||||
{ enabled: !!selectedProgramId }
|
{ enabled: !!selectedProgramId }
|
||||||
)
|
)
|
||||||
|
|
||||||
const { data: rounds, isLoading: isLoadingRounds } = trpc.round.listByProgram.useQuery(
|
// Get stages from the selected program (program.list includes rounds/stages)
|
||||||
{ programId: selectedProgramId },
|
const { data: selectedProgramData, isLoading: isLoadingStages } = trpc.program.get.useQuery(
|
||||||
|
{ id: selectedProgramId },
|
||||||
{ enabled: !!selectedProgramId }
|
{ enabled: !!selectedProgramId }
|
||||||
)
|
)
|
||||||
|
const stages = (selectedProgramData?.stages || []) as Array<{ id: string; name: string }>
|
||||||
|
|
||||||
const utils = trpc.useUtils()
|
const utils = trpc.useUtils()
|
||||||
const assignMutation = trpc.projectPool.assignToRound.useMutation({
|
const assignMutation = trpc.projectPool.assignToStage.useMutation({
|
||||||
onSuccess: (result) => {
|
onSuccess: (result) => {
|
||||||
utils.project.list.invalidate()
|
utils.project.list.invalidate()
|
||||||
utils.round.get.invalidate()
|
utils.program.get.invalidate()
|
||||||
toast.success(`Assigned ${result.assignedCount} project${result.assignedCount !== 1 ? 's' : ''} to round`)
|
toast.success(`Assigned ${result.assignedCount} project${result.assignedCount !== 1 ? 's' : ''} to stage`)
|
||||||
setSelectedProjects([])
|
setSelectedProjects([])
|
||||||
setAssignDialogOpen(false)
|
setAssignDialogOpen(false)
|
||||||
setTargetRoundId('')
|
setTargetStageId('')
|
||||||
refetch()
|
refetch()
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
|
|
@ -72,17 +74,17 @@ export default function ProjectPoolPage() {
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleBulkAssign = () => {
|
const handleBulkAssign = () => {
|
||||||
if (selectedProjects.length === 0 || !targetRoundId) return
|
if (selectedProjects.length === 0 || !targetStageId) return
|
||||||
assignMutation.mutate({
|
assignMutation.mutate({
|
||||||
projectIds: selectedProjects,
|
projectIds: selectedProjects,
|
||||||
roundId: targetRoundId,
|
stageId: targetStageId,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleQuickAssign = (projectId: string, roundId: string) => {
|
const handleQuickAssign = (projectId: string, stageId: string) => {
|
||||||
assignMutation.mutate({
|
assignMutation.mutate({
|
||||||
projectIds: [projectId],
|
projectIds: [projectId],
|
||||||
roundId,
|
stageId,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,7 +111,7 @@ export default function ProjectPoolPage() {
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-semibold">Project Pool</h1>
|
<h1 className="text-2xl font-semibold">Project Pool</h1>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
Assign unassigned projects to evaluation rounds
|
Assign unassigned projects to evaluation stages
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -236,20 +238,20 @@ export default function ProjectPoolPage() {
|
||||||
: '-'}
|
: '-'}
|
||||||
</td>
|
</td>
|
||||||
<td className="p-3">
|
<td className="p-3">
|
||||||
{isLoadingRounds ? (
|
{isLoadingStages ? (
|
||||||
<Skeleton className="h-9 w-[200px]" />
|
<Skeleton className="h-9 w-[200px]" />
|
||||||
) : (
|
) : (
|
||||||
<Select
|
<Select
|
||||||
onValueChange={(roundId) => handleQuickAssign(project.id, roundId)}
|
onValueChange={(stageId) => handleQuickAssign(project.id, stageId)}
|
||||||
disabled={assignMutation.isPending}
|
disabled={assignMutation.isPending}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-[200px]">
|
<SelectTrigger className="w-[200px]">
|
||||||
<SelectValue placeholder="Assign to round..." />
|
<SelectValue placeholder="Assign to stage..." />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{rounds?.map((round: { id: string; name: string; sortOrder: number }) => (
|
{stages?.map((stage: { id: string; name: string }) => (
|
||||||
<SelectItem key={round.id} value={round.id}>
|
<SelectItem key={stage.id} value={stage.id}>
|
||||||
{round.name}
|
{stage.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|
@ -310,20 +312,20 @@ export default function ProjectPoolPage() {
|
||||||
<Dialog open={assignDialogOpen} onOpenChange={setAssignDialogOpen}>
|
<Dialog open={assignDialogOpen} onOpenChange={setAssignDialogOpen}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Assign Projects to Round</DialogTitle>
|
<DialogTitle>Assign Projects to Stage</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Assign {selectedProjects.length} selected project{selectedProjects.length > 1 ? 's' : ''} to:
|
Assign {selectedProjects.length} selected project{selectedProjects.length > 1 ? 's' : ''} to:
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-4 py-4">
|
<div className="space-y-4 py-4">
|
||||||
<Select value={targetRoundId} onValueChange={setTargetRoundId}>
|
<Select value={targetStageId} onValueChange={setTargetStageId}>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select round..." />
|
<SelectValue placeholder="Select stage..." />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{rounds?.map((round: { id: string; name: string; sortOrder: number }) => (
|
{stages?.map((stage: { id: string; name: string }) => (
|
||||||
<SelectItem key={round.id} value={round.id}>
|
<SelectItem key={stage.id} value={stage.id}>
|
||||||
{round.name}
|
{stage.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|
@ -335,7 +337,7 @@ export default function ProjectPoolPage() {
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleBulkAssign}
|
onClick={handleBulkAssign}
|
||||||
disabled={!targetRoundId || assignMutation.isPending}
|
disabled={!targetStageId || assignMutation.isPending}
|
||||||
>
|
>
|
||||||
{assignMutation.isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
{assignMutation.isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||||
Assign
|
Assign
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ const ISSUE_LABELS: Record<string, string> = {
|
||||||
export interface ProjectFilters {
|
export interface ProjectFilters {
|
||||||
search: string
|
search: string
|
||||||
statuses: string[]
|
statuses: string[]
|
||||||
roundId: string
|
stageId: string
|
||||||
competitionCategory: string
|
competitionCategory: string
|
||||||
oceanIssue: string
|
oceanIssue: string
|
||||||
country: string
|
country: string
|
||||||
|
|
@ -72,11 +72,11 @@ export interface ProjectFilters {
|
||||||
hasAssignments: boolean | undefined
|
hasAssignments: boolean | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FilterOptions {
|
export interface FilterOptions {
|
||||||
rounds: Array<{ id: string; name: string; program: { name: string; year: number } }>
|
|
||||||
countries: string[]
|
countries: string[]
|
||||||
categories: Array<{ value: string; count: number }>
|
categories: Array<{ value: string; count: number }>
|
||||||
issues: Array<{ value: string; count: number }>
|
issues: Array<{ value: string; count: number }>
|
||||||
|
stages?: Array<{ id: string; name: string; programName: string; programYear: number }>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ProjectFiltersBarProps {
|
interface ProjectFiltersBarProps {
|
||||||
|
|
@ -94,7 +94,7 @@ export function ProjectFiltersBar({
|
||||||
|
|
||||||
const activeFilterCount = [
|
const activeFilterCount = [
|
||||||
filters.statuses.length > 0,
|
filters.statuses.length > 0,
|
||||||
filters.roundId !== '',
|
filters.stageId !== '',
|
||||||
filters.competitionCategory !== '',
|
filters.competitionCategory !== '',
|
||||||
filters.oceanIssue !== '',
|
filters.oceanIssue !== '',
|
||||||
filters.country !== '',
|
filters.country !== '',
|
||||||
|
|
@ -114,7 +114,7 @@ export function ProjectFiltersBar({
|
||||||
onChange({
|
onChange({
|
||||||
search: filters.search,
|
search: filters.search,
|
||||||
statuses: [],
|
statuses: [],
|
||||||
roundId: '',
|
stageId: '',
|
||||||
competitionCategory: '',
|
competitionCategory: '',
|
||||||
oceanIssue: '',
|
oceanIssue: '',
|
||||||
country: '',
|
country: '',
|
||||||
|
|
@ -175,21 +175,21 @@ export function ProjectFiltersBar({
|
||||||
{/* Select filters grid */}
|
{/* Select filters grid */}
|
||||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-sm">Round / Edition</Label>
|
<Label className="text-sm">Stage / Edition</Label>
|
||||||
<Select
|
<Select
|
||||||
value={filters.roundId || '_all'}
|
value={filters.stageId || '_all'}
|
||||||
onValueChange={(v) =>
|
onValueChange={(v) =>
|
||||||
onChange({ ...filters, roundId: v === '_all' ? '' : v })
|
onChange({ ...filters, stageId: v === '_all' ? '' : v })
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="All rounds" />
|
<SelectValue placeholder="All stages" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="_all">All rounds</SelectItem>
|
<SelectItem value="_all">All stages</SelectItem>
|
||||||
{filterOptions?.rounds.map((r) => (
|
{filterOptions?.stages?.map((s) => (
|
||||||
<SelectItem key={r.id} value={r.id}>
|
<SelectItem key={s.id} value={s.id}>
|
||||||
{r.name} ({r.program.year} Edition)
|
{s.name} ({s.programYear} {s.programName})
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
import type { Route } from 'next'
|
||||||
import { trpc } from '@/lib/trpc/client'
|
import { trpc } from '@/lib/trpc/client'
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
|
|
@ -41,6 +42,7 @@ import {
|
||||||
GitCompare,
|
GitCompare,
|
||||||
UserCheck,
|
UserCheck,
|
||||||
Globe,
|
Globe,
|
||||||
|
Layers,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { formatDateOnly } from '@/lib/utils'
|
import { formatDateOnly } from '@/lib/utils'
|
||||||
import {
|
import {
|
||||||
|
|
@ -51,7 +53,7 @@ import {
|
||||||
ProjectRankingsChart,
|
ProjectRankingsChart,
|
||||||
CriteriaScoresChart,
|
CriteriaScoresChart,
|
||||||
GeographicDistribution,
|
GeographicDistribution,
|
||||||
CrossRoundComparisonChart,
|
CrossStageComparisonChart,
|
||||||
JurorConsistencyChart,
|
JurorConsistencyChart,
|
||||||
DiversityMetricsChart,
|
DiversityMetricsChart,
|
||||||
} from '@/components/charts'
|
} from '@/components/charts'
|
||||||
|
|
@ -59,11 +61,13 @@ import { ExportPdfButton } from '@/components/shared/export-pdf-button'
|
||||||
import { AnimatedCard } from '@/components/shared/animated-container'
|
import { AnimatedCard } from '@/components/shared/animated-container'
|
||||||
|
|
||||||
function ReportsOverview() {
|
function ReportsOverview() {
|
||||||
const { data: programs, isLoading } = trpc.program.list.useQuery({ includeRounds: true })
|
const { data: programs, isLoading } = trpc.program.list.useQuery({ includeStages: true })
|
||||||
const { data: dashStats, isLoading: statsLoading } = trpc.analytics.getDashboardStats.useQuery()
|
const { data: dashStats, isLoading: statsLoading } = trpc.analytics.getDashboardStats.useQuery()
|
||||||
|
|
||||||
// Flatten rounds from all programs
|
// Flatten stages from all programs
|
||||||
const rounds = programs?.flatMap(p => p.rounds.map(r => ({ ...r, programId: p.id, programName: `${p.year} Edition` }))) || []
|
const rounds = programs?.flatMap(p =>
|
||||||
|
((p.stages ?? []) as Array<{ id: string; name: string; status: string; votingEndAt?: string | Date | null }>).map((s: { id: string; name: string; status: string; votingEndAt?: string | Date | null }) => ({ ...s, programId: p.id, programName: `${p.year} Edition` }))
|
||||||
|
) || []
|
||||||
|
|
||||||
// Project reporting scope (default: latest program, all rounds)
|
// Project reporting scope (default: latest program, all rounds)
|
||||||
const [selectedValue, setSelectedValue] = useState<string | null>(null)
|
const [selectedValue, setSelectedValue] = useState<string | null>(null)
|
||||||
|
|
@ -73,7 +77,7 @@ function ReportsOverview() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const scopeInput = parseSelection(selectedValue)
|
const scopeInput = parseSelection(selectedValue)
|
||||||
const hasScope = !!scopeInput.roundId || !!scopeInput.programId
|
const hasScope = !!scopeInput.stageId || !!scopeInput.programId
|
||||||
|
|
||||||
const { data: projectRankings, isLoading: projectsLoading } =
|
const { data: projectRankings, isLoading: projectsLoading } =
|
||||||
trpc.analytics.getProjectRankings.useQuery(
|
trpc.analytics.getProjectRankings.useQuery(
|
||||||
|
|
@ -103,7 +107,7 @@ function ReportsOverview() {
|
||||||
|
|
||||||
const totalPrograms = dashStats?.programCount ?? programs?.length ?? 0
|
const totalPrograms = dashStats?.programCount ?? programs?.length ?? 0
|
||||||
const totalProjects = dashStats?.projectCount ?? 0
|
const totalProjects = dashStats?.projectCount ?? 0
|
||||||
const activeRounds = dashStats?.activeRoundCount ?? rounds.filter((r) => r.status === 'ACTIVE').length
|
const activeRounds = dashStats?.activeStageCount ?? rounds.filter((r: { status: string }) => r.status === 'STAGE_ACTIVE').length
|
||||||
const jurorCount = dashStats?.jurorCount ?? 0
|
const jurorCount = dashStats?.jurorCount ?? 0
|
||||||
const submittedEvaluations = dashStats?.submittedEvaluations ?? 0
|
const submittedEvaluations = dashStats?.submittedEvaluations ?? 0
|
||||||
const totalEvaluations = dashStats?.totalEvaluations ?? 0
|
const totalEvaluations = dashStats?.totalEvaluations ?? 0
|
||||||
|
|
@ -365,7 +369,7 @@ function ReportsOverview() {
|
||||||
<div className="flex justify-end gap-2 flex-wrap">
|
<div className="flex justify-end gap-2 flex-wrap">
|
||||||
<Button variant="outline" size="sm" asChild>
|
<Button variant="outline" size="sm" asChild>
|
||||||
<a
|
<a
|
||||||
href={`/api/export/evaluations?roundId=${round.id}`}
|
href={`/api/export/evaluations?stageId=${round.id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
|
|
@ -375,7 +379,7 @@ function ReportsOverview() {
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline" size="sm" asChild>
|
<Button variant="outline" size="sm" asChild>
|
||||||
<a
|
<a
|
||||||
href={`/api/export/results?roundId=${round.id}`}
|
href={`/api/export/results?stageId=${round.id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
|
|
@ -396,28 +400,30 @@ function ReportsOverview() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse selection value: "all:programId" for edition-wide, or roundId
|
// Parse selection value: "all:programId" for edition-wide, or stageId
|
||||||
function parseSelection(value: string | null): { roundId?: string; programId?: string } {
|
function parseSelection(value: string | null): { stageId?: string; programId?: string } {
|
||||||
if (!value) return {}
|
if (!value) return {}
|
||||||
if (value.startsWith('all:')) return { programId: value.slice(4) }
|
if (value.startsWith('all:')) return { programId: value.slice(4) }
|
||||||
return { roundId: value }
|
return { stageId: value }
|
||||||
}
|
}
|
||||||
|
|
||||||
function RoundAnalytics() {
|
function StageAnalytics() {
|
||||||
const [selectedValue, setSelectedValue] = useState<string | null>(null)
|
const [selectedValue, setSelectedValue] = useState<string | null>(null)
|
||||||
|
|
||||||
const { data: programs, isLoading: roundsLoading } = trpc.program.list.useQuery({ includeRounds: true })
|
const { data: programs, isLoading: roundsLoading } = trpc.program.list.useQuery({ includeStages: true })
|
||||||
|
|
||||||
// Flatten rounds from all programs with program name
|
// Flatten stages from all programs with program name
|
||||||
const rounds = programs?.flatMap(p => p.rounds.map(r => ({ ...r, programId: p.id, programName: `${p.year} Edition` }))) || []
|
const rounds = programs?.flatMap(p =>
|
||||||
|
((p.stages ?? []) as Array<{ id: string; name: string }>).map((s: { id: string; name: string }) => ({ ...s, programId: p.id, programName: `${p.year} Edition` }))
|
||||||
|
) || []
|
||||||
|
|
||||||
// Set default selected round
|
// Set default selected stage
|
||||||
if (rounds.length && !selectedValue) {
|
if (rounds.length && !selectedValue) {
|
||||||
setSelectedValue(rounds[0].id)
|
setSelectedValue(rounds[0].id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryInput = parseSelection(selectedValue)
|
const queryInput = parseSelection(selectedValue)
|
||||||
const hasSelection = !!queryInput.roundId || !!queryInput.programId
|
const hasSelection = !!queryInput.stageId || !!queryInput.programId
|
||||||
|
|
||||||
const { data: scoreDistribution, isLoading: scoreLoading } =
|
const { data: scoreDistribution, isLoading: scoreLoading } =
|
||||||
trpc.analytics.getScoreDistribution.useQuery(
|
trpc.analytics.getScoreDistribution.useQuery(
|
||||||
|
|
@ -458,11 +464,11 @@ function RoundAnalytics() {
|
||||||
const selectedRound = rounds.find((r) => r.id === selectedValue)
|
const selectedRound = rounds.find((r) => r.id === selectedValue)
|
||||||
const geoInput = queryInput.programId
|
const geoInput = queryInput.programId
|
||||||
? { programId: queryInput.programId }
|
? { programId: queryInput.programId }
|
||||||
: { programId: selectedRound?.programId || '', roundId: queryInput.roundId }
|
: { programId: selectedRound?.programId || '', stageId: queryInput.stageId }
|
||||||
const { data: geoData, isLoading: geoLoading } =
|
const { data: geoData, isLoading: geoLoading } =
|
||||||
trpc.analytics.getGeographicDistribution.useQuery(
|
trpc.analytics.getGeographicDistribution.useQuery(
|
||||||
geoInput,
|
geoInput,
|
||||||
{ enabled: hasSelection && !!(geoInput.programId || geoInput.roundId) }
|
{ enabled: hasSelection && !!(geoInput.programId || geoInput.stageId) }
|
||||||
)
|
)
|
||||||
|
|
||||||
if (roundsLoading) {
|
if (roundsLoading) {
|
||||||
|
|
@ -600,26 +606,26 @@ function RoundAnalytics() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function CrossRoundTab() {
|
function CrossStageTab() {
|
||||||
const { data: programs, isLoading: programsLoading } = trpc.program.list.useQuery({ includeRounds: true })
|
const { data: programs, isLoading: programsLoading } = trpc.program.list.useQuery({ includeStages: true })
|
||||||
|
|
||||||
const rounds = programs?.flatMap(p =>
|
const stages = programs?.flatMap((p) =>
|
||||||
p.rounds.map(r => ({ id: r.id, name: r.name, programName: `${p.year} Edition` }))
|
((p.stages ?? []) as Array<{ id: string; name: string }>).map((s: { id: string; name: string }) => ({ id: s.id, name: s.name, programName: `${p.year} Edition` }))
|
||||||
) || []
|
) || []
|
||||||
|
|
||||||
const [selectedRoundIds, setSelectedRoundIds] = useState<string[]>([])
|
const [selectedStageIds, setSelectedStageIds] = useState<string[]>([])
|
||||||
|
|
||||||
const { data: comparison, isLoading: comparisonLoading } =
|
const { data: comparison, isLoading: comparisonLoading } =
|
||||||
trpc.analytics.getCrossRoundComparison.useQuery(
|
trpc.analytics.getCrossStageComparison.useQuery(
|
||||||
{ roundIds: selectedRoundIds },
|
{ stageIds: selectedStageIds },
|
||||||
{ enabled: selectedRoundIds.length >= 2 }
|
{ enabled: selectedStageIds.length >= 2 }
|
||||||
)
|
)
|
||||||
|
|
||||||
const toggleRound = (roundId: string) => {
|
const toggleStage = (stageId: string) => {
|
||||||
setSelectedRoundIds((prev) =>
|
setSelectedStageIds((prev) =>
|
||||||
prev.includes(roundId)
|
prev.includes(stageId)
|
||||||
? prev.filter((id) => id !== roundId)
|
? prev.filter((id) => id !== stageId)
|
||||||
: [...prev, roundId]
|
: [...prev, stageId]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -629,40 +635,40 @@ function CrossRoundTab() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Round selector */}
|
{/* Stage selector */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Select Rounds to Compare</CardTitle>
|
<CardTitle>Select Stages to Compare</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Choose at least 2 rounds to compare metrics side by side
|
Choose at least 2 stages to compare metrics side by side
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{rounds.map((round) => {
|
{stages.map((stage) => {
|
||||||
const isSelected = selectedRoundIds.includes(round.id)
|
const isSelected = selectedStageIds.includes(stage.id)
|
||||||
return (
|
return (
|
||||||
<Badge
|
<Badge
|
||||||
key={round.id}
|
key={stage.id}
|
||||||
variant={isSelected ? 'default' : 'outline'}
|
variant={isSelected ? 'default' : 'outline'}
|
||||||
className="cursor-pointer text-sm py-1.5 px-3"
|
className="cursor-pointer text-sm py-1.5 px-3"
|
||||||
onClick={() => toggleRound(round.id)}
|
onClick={() => toggleStage(stage.id)}
|
||||||
>
|
>
|
||||||
{round.programName} - {round.name}
|
{stage.programName} - {stage.name}
|
||||||
</Badge>
|
</Badge>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
{selectedRoundIds.length < 2 && (
|
{selectedStageIds.length < 2 && (
|
||||||
<p className="text-sm text-muted-foreground mt-3">
|
<p className="text-sm text-muted-foreground mt-3">
|
||||||
Select at least 2 rounds to enable comparison
|
Select at least 2 stages to enable comparison
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Comparison charts */}
|
{/* Comparison charts */}
|
||||||
{comparisonLoading && selectedRoundIds.length >= 2 && (
|
{comparisonLoading && selectedStageIds.length >= 2 && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Skeleton className="h-[350px]" />
|
<Skeleton className="h-[350px]" />
|
||||||
<div className="grid gap-6 lg:grid-cols-2">
|
<div className="grid gap-6 lg:grid-cols-2">
|
||||||
|
|
@ -673,9 +679,9 @@ function CrossRoundTab() {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{comparison && (
|
{comparison && (
|
||||||
<CrossRoundComparisonChart data={comparison as Array<{
|
<CrossStageComparisonChart data={comparison as Array<{
|
||||||
roundId: string
|
stageId: string
|
||||||
roundName: string
|
stageName: string
|
||||||
projectCount: number
|
projectCount: number
|
||||||
evaluationCount: number
|
evaluationCount: number
|
||||||
completionRate: number
|
completionRate: number
|
||||||
|
|
@ -690,18 +696,18 @@ function CrossRoundTab() {
|
||||||
function JurorConsistencyTab() {
|
function JurorConsistencyTab() {
|
||||||
const [selectedValue, setSelectedValue] = useState<string | null>(null)
|
const [selectedValue, setSelectedValue] = useState<string | null>(null)
|
||||||
|
|
||||||
const { data: programs, isLoading: programsLoading } = trpc.program.list.useQuery({ includeRounds: true })
|
const { data: programs, isLoading: programsLoading } = trpc.program.list.useQuery({ includeStages: true })
|
||||||
|
|
||||||
const rounds = programs?.flatMap(p =>
|
const stages = programs?.flatMap((p) =>
|
||||||
p.rounds.map(r => ({ id: r.id, name: r.name, programId: p.id, programName: `${p.year} Edition` }))
|
((p.stages ?? []) as Array<{ id: string; name: string }>).map((s: { id: string; name: string }) => ({ id: s.id, name: s.name, programId: p.id, programName: `${p.year} Edition` }))
|
||||||
) || []
|
) || []
|
||||||
|
|
||||||
if (rounds.length && !selectedValue) {
|
if (stages.length && !selectedValue) {
|
||||||
setSelectedValue(rounds[0].id)
|
setSelectedValue(stages[0].id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryInput = parseSelection(selectedValue)
|
const queryInput = parseSelection(selectedValue)
|
||||||
const hasSelection = !!queryInput.roundId || !!queryInput.programId
|
const hasSelection = !!queryInput.stageId || !!queryInput.programId
|
||||||
|
|
||||||
const { data: consistency, isLoading: consistencyLoading } =
|
const { data: consistency, isLoading: consistencyLoading } =
|
||||||
trpc.analytics.getJurorConsistency.useQuery(
|
trpc.analytics.getJurorConsistency.useQuery(
|
||||||
|
|
@ -716,20 +722,20 @@ function JurorConsistencyTab() {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<label className="text-sm font-medium">Select Round:</label>
|
<label className="text-sm font-medium">Select Stage:</label>
|
||||||
<Select value={selectedValue || ''} onValueChange={setSelectedValue}>
|
<Select value={selectedValue || ''} onValueChange={setSelectedValue}>
|
||||||
<SelectTrigger className="w-[300px]">
|
<SelectTrigger className="w-[300px]">
|
||||||
<SelectValue placeholder="Select a round" />
|
<SelectValue placeholder="Select a stage" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{programs?.map((p) => (
|
{programs?.map((p) => (
|
||||||
<SelectItem key={`all:${p.id}`} value={`all:${p.id}`}>
|
<SelectItem key={`all:${p.id}`} value={`all:${p.id}`}>
|
||||||
{p.year} Edition — All Rounds
|
{p.year} Edition — All Stages
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
{rounds.map((round) => (
|
{stages.map((stage) => (
|
||||||
<SelectItem key={round.id} value={round.id}>
|
<SelectItem key={stage.id} value={stage.id}>
|
||||||
{round.programName} - {round.name}
|
{stage.programName} - {stage.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|
@ -762,18 +768,18 @@ function JurorConsistencyTab() {
|
||||||
function DiversityTab() {
|
function DiversityTab() {
|
||||||
const [selectedValue, setSelectedValue] = useState<string | null>(null)
|
const [selectedValue, setSelectedValue] = useState<string | null>(null)
|
||||||
|
|
||||||
const { data: programs, isLoading: programsLoading } = trpc.program.list.useQuery({ includeRounds: true })
|
const { data: programs, isLoading: programsLoading } = trpc.program.list.useQuery({ includeStages: true })
|
||||||
|
|
||||||
const rounds = programs?.flatMap(p =>
|
const stages = programs?.flatMap((p) =>
|
||||||
p.rounds.map(r => ({ id: r.id, name: r.name, programId: p.id, programName: `${p.year} Edition` }))
|
((p.stages ?? []) as Array<{ id: string; name: string }>).map((s: { id: string; name: string }) => ({ id: s.id, name: s.name, programId: p.id, programName: `${p.year} Edition` }))
|
||||||
) || []
|
) || []
|
||||||
|
|
||||||
if (rounds.length && !selectedValue) {
|
if (stages.length && !selectedValue) {
|
||||||
setSelectedValue(rounds[0].id)
|
setSelectedValue(stages[0].id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryInput = parseSelection(selectedValue)
|
const queryInput = parseSelection(selectedValue)
|
||||||
const hasSelection = !!queryInput.roundId || !!queryInput.programId
|
const hasSelection = !!queryInput.stageId || !!queryInput.programId
|
||||||
|
|
||||||
const { data: diversity, isLoading: diversityLoading } =
|
const { data: diversity, isLoading: diversityLoading } =
|
||||||
trpc.analytics.getDiversityMetrics.useQuery(
|
trpc.analytics.getDiversityMetrics.useQuery(
|
||||||
|
|
@ -788,20 +794,20 @@ function DiversityTab() {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<label className="text-sm font-medium">Select Round:</label>
|
<label className="text-sm font-medium">Select Stage:</label>
|
||||||
<Select value={selectedValue || ''} onValueChange={setSelectedValue}>
|
<Select value={selectedValue || ''} onValueChange={setSelectedValue}>
|
||||||
<SelectTrigger className="w-[300px]">
|
<SelectTrigger className="w-[300px]">
|
||||||
<SelectValue placeholder="Select a round" />
|
<SelectValue placeholder="Select a stage" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{programs?.map((p) => (
|
{programs?.map((p) => (
|
||||||
<SelectItem key={`all:${p.id}`} value={`all:${p.id}`}>
|
<SelectItem key={`all:${p.id}`} value={`all:${p.id}`}>
|
||||||
{p.year} Edition — All Rounds
|
{p.year} Edition — All Stages
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
{rounds.map((round) => (
|
{stages.map((stage) => (
|
||||||
<SelectItem key={round.id} value={round.id}>
|
<SelectItem key={stage.id} value={stage.id}>
|
||||||
{round.programName} - {round.name}
|
{stage.programName} - {stage.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|
@ -826,18 +832,18 @@ function DiversityTab() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ReportsPage() {
|
export default function ReportsPage() {
|
||||||
const [pdfRoundId, setPdfRoundId] = useState<string | null>(null)
|
const [pdfStageId, setPdfStageId] = useState<string | null>(null)
|
||||||
|
|
||||||
const { data: pdfPrograms } = trpc.program.list.useQuery({ includeRounds: true })
|
const { data: pdfPrograms } = trpc.program.list.useQuery({ includeStages: true })
|
||||||
const pdfRounds = pdfPrograms?.flatMap((p) =>
|
const pdfStages = pdfPrograms?.flatMap((p) =>
|
||||||
p.rounds.map((r) => ({ id: r.id, name: r.name, programName: `${p.year} Edition` }))
|
((p.stages ?? []) as Array<{ id: string; name: string }>).map((s: { id: string; name: string }) => ({ id: s.id, name: s.name, programName: `${p.year} Edition` }))
|
||||||
) || []
|
) || []
|
||||||
|
|
||||||
if (pdfRounds.length && !pdfRoundId) {
|
if (pdfStages.length && !pdfStageId) {
|
||||||
setPdfRoundId(pdfRounds[0].id)
|
setPdfStageId(pdfStages[0].id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedPdfRound = pdfRounds.find((r) => r.id === pdfRoundId)
|
const selectedPdfStage = pdfStages.find((r) => r.id === pdfStageId)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
|
@ -861,7 +867,7 @@ export default function ReportsPage() {
|
||||||
<TrendingUp className="h-4 w-4" />
|
<TrendingUp className="h-4 w-4" />
|
||||||
Analytics
|
Analytics
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="cross-round" className="gap-2">
|
<TabsTrigger value="cross-stage" className="gap-2">
|
||||||
<GitCompare className="h-4 w-4" />
|
<GitCompare className="h-4 w-4" />
|
||||||
Cross-Round
|
Cross-Round
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
|
|
@ -873,25 +879,31 @@ export default function ReportsPage() {
|
||||||
<Globe className="h-4 w-4" />
|
<Globe className="h-4 w-4" />
|
||||||
Diversity
|
Diversity
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="pipeline" className="gap-2" asChild>
|
||||||
|
<Link href={"/admin/reports/stages" as Route}>
|
||||||
|
<Layers className="h-4 w-4" />
|
||||||
|
By Pipeline
|
||||||
|
</Link>
|
||||||
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<div className="flex items-center gap-2 w-full sm:w-auto justify-between sm:justify-end">
|
<div className="flex items-center gap-2 w-full sm:w-auto justify-between sm:justify-end">
|
||||||
<Select value={pdfRoundId || ''} onValueChange={setPdfRoundId}>
|
<Select value={pdfStageId || ''} onValueChange={setPdfStageId}>
|
||||||
<SelectTrigger className="w-[220px]">
|
<SelectTrigger className="w-[220px]">
|
||||||
<SelectValue placeholder="Select round for PDF" />
|
<SelectValue placeholder="Select stage for PDF" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{pdfRounds.map((round) => (
|
{pdfStages.map((stage) => (
|
||||||
<SelectItem key={round.id} value={round.id}>
|
<SelectItem key={stage.id} value={stage.id}>
|
||||||
{round.programName} - {round.name}
|
{stage.programName} - {stage.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
{pdfRoundId && (
|
{pdfStageId && (
|
||||||
<ExportPdfButton
|
<ExportPdfButton
|
||||||
roundId={pdfRoundId}
|
stageId={pdfStageId}
|
||||||
roundName={selectedPdfRound?.name}
|
roundName={selectedPdfStage?.name}
|
||||||
programName={selectedPdfRound?.programName}
|
programName={selectedPdfStage?.programName}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -902,11 +914,11 @@ export default function ReportsPage() {
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="analytics">
|
<TabsContent value="analytics">
|
||||||
<RoundAnalytics />
|
<StageAnalytics />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="cross-round">
|
<TabsContent value="cross-stage">
|
||||||
<CrossRoundTab />
|
<CrossStageTab />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="consistency">
|
<TabsContent value="consistency">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,671 @@
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { trpc } from '@/lib/trpc/client'
|
||||||
|
import { useEdition } from '@/contexts/edition-context'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import type { Route } from 'next'
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
CardDescription,
|
||||||
|
} from '@/components/ui/card'
|
||||||
|
import { Badge } from '@/components/ui/badge'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton'
|
||||||
|
import { Progress } from '@/components/ui/progress'
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select'
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from '@/components/ui/table'
|
||||||
|
import {
|
||||||
|
ArrowLeft,
|
||||||
|
BarChart3,
|
||||||
|
Layers,
|
||||||
|
Users,
|
||||||
|
CheckCircle2,
|
||||||
|
Target,
|
||||||
|
Trophy,
|
||||||
|
} from 'lucide-react'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const stateColors: Record<string, string> = {
|
||||||
|
PENDING: 'bg-gray-100 text-gray-700',
|
||||||
|
IN_PROGRESS: 'bg-blue-100 text-blue-700',
|
||||||
|
PASSED: 'bg-emerald-100 text-emerald-700',
|
||||||
|
REJECTED: 'bg-red-100 text-red-700',
|
||||||
|
COMPLETED: 'bg-emerald-100 text-emerald-700',
|
||||||
|
ELIMINATED: 'bg-red-100 text-red-700',
|
||||||
|
WAITING: 'bg-amber-100 text-amber-700',
|
||||||
|
}
|
||||||
|
|
||||||
|
const awardStatusColors: Record<string, string> = {
|
||||||
|
DRAFT: 'bg-gray-100 text-gray-700',
|
||||||
|
NOMINATIONS_OPEN: 'bg-blue-100 text-blue-700',
|
||||||
|
VOTING_OPEN: 'bg-amber-100 text-amber-700',
|
||||||
|
CLOSED: 'bg-emerald-100 text-emerald-700',
|
||||||
|
ARCHIVED: 'bg-gray-100 text-gray-500',
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Stages Tab Content ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function StagesTabContent({
|
||||||
|
activePipelineId,
|
||||||
|
}: {
|
||||||
|
activePipelineId: string
|
||||||
|
}) {
|
||||||
|
const [selectedStageId, setSelectedStageId] = useState<string>('')
|
||||||
|
|
||||||
|
const { data: overview, isLoading: overviewLoading } =
|
||||||
|
trpc.analytics.getStageCompletionOverview.useQuery(
|
||||||
|
{ pipelineId: activePipelineId },
|
||||||
|
{ enabled: !!activePipelineId }
|
||||||
|
)
|
||||||
|
|
||||||
|
const { data: scoreDistribution, isLoading: scoresLoading } =
|
||||||
|
trpc.analytics.getStageScoreDistribution.useQuery(
|
||||||
|
{ stageId: selectedStageId },
|
||||||
|
{ enabled: !!selectedStageId }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (overviewLoading) {
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="grid gap-4 sm:grid-cols-4">
|
||||||
|
{[...Array(4)].map((_, i) => (
|
||||||
|
<Skeleton key={i} className="h-24" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<Skeleton className="h-64 w-full" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!overview) {
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
|
||||||
|
<BarChart3 className="h-12 w-12 text-muted-foreground/50 mb-3" />
|
||||||
|
<p className="font-medium">No pipeline data</p>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
Select a pipeline to view analytics.
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Summary cards */}
|
||||||
|
<div className="grid gap-4 sm:grid-cols-4">
|
||||||
|
<Card>
|
||||||
|
<CardContent className="py-5 text-center">
|
||||||
|
<Layers className="h-5 w-5 text-muted-foreground mx-auto mb-1" />
|
||||||
|
<p className="text-2xl font-bold">{overview.summary.totalStages}</p>
|
||||||
|
<p className="text-xs text-muted-foreground">Total Stages</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<CardContent className="py-5 text-center">
|
||||||
|
<Target className="h-5 w-5 text-muted-foreground mx-auto mb-1" />
|
||||||
|
<p className="text-2xl font-bold">{overview.summary.totalProjects}</p>
|
||||||
|
<p className="text-xs text-muted-foreground">Total Projects</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<CardContent className="py-5 text-center">
|
||||||
|
<Users className="h-5 w-5 text-muted-foreground mx-auto mb-1" />
|
||||||
|
<p className="text-2xl font-bold">{overview.summary.totalAssignments}</p>
|
||||||
|
<p className="text-xs text-muted-foreground">Total Assignments</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<CardContent className="py-5 text-center">
|
||||||
|
<CheckCircle2 className="h-5 w-5 text-emerald-600 mx-auto mb-1" />
|
||||||
|
<p className="text-2xl font-bold">{overview.summary.totalCompleted}</p>
|
||||||
|
<p className="text-xs text-muted-foreground">Evaluations Completed</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Stage overview table */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-lg">Stage Completion Overview</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Click a stage to see detailed score distribution
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-0">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Stage</TableHead>
|
||||||
|
<TableHead>Track</TableHead>
|
||||||
|
<TableHead>Type</TableHead>
|
||||||
|
<TableHead className="text-center">Projects</TableHead>
|
||||||
|
<TableHead className="text-center">Assignments</TableHead>
|
||||||
|
<TableHead className="text-center">Completed</TableHead>
|
||||||
|
<TableHead className="text-center">Jurors</TableHead>
|
||||||
|
<TableHead>Progress</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{overview.stages.map((stage) => (
|
||||||
|
<TableRow
|
||||||
|
key={stage.stageId}
|
||||||
|
className={cn(
|
||||||
|
'cursor-pointer transition-colors',
|
||||||
|
selectedStageId === stage.stageId && 'bg-brand-blue/5 dark:bg-brand-teal/5'
|
||||||
|
)}
|
||||||
|
onClick={() => setSelectedStageId(stage.stageId)}
|
||||||
|
>
|
||||||
|
<TableCell className="font-medium">{stage.stageName}</TableCell>
|
||||||
|
<TableCell className="text-muted-foreground text-sm">{stage.trackName}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Badge variant="outline" className="text-xs capitalize">
|
||||||
|
{stage.stageType.toLowerCase().replace(/_/g, ' ')}
|
||||||
|
</Badge>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-center tabular-nums">{stage.totalProjects}</TableCell>
|
||||||
|
<TableCell className="text-center tabular-nums">{stage.totalAssignments}</TableCell>
|
||||||
|
<TableCell className="text-center tabular-nums">{stage.completedEvaluations}</TableCell>
|
||||||
|
<TableCell className="text-center tabular-nums">{stage.jurorCount}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<div className="flex items-center gap-2 min-w-[120px]">
|
||||||
|
<Progress value={stage.completionRate} className="h-2 flex-1" />
|
||||||
|
<span className="text-xs tabular-nums font-medium w-8 text-right">
|
||||||
|
{stage.completionRate}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* State breakdown per stage */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-lg">Project State Breakdown</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{overview.stages.map((stage) => (
|
||||||
|
<div key={stage.stageId} className="space-y-2">
|
||||||
|
<p className="text-sm font-medium">{stage.stageName}</p>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{stage.stateBreakdown.map((sb) => (
|
||||||
|
<Badge
|
||||||
|
key={sb.state}
|
||||||
|
variant="outline"
|
||||||
|
className={cn('text-xs', stateColors[sb.state])}
|
||||||
|
>
|
||||||
|
{sb.state}: {sb.count}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
{stage.stateBreakdown.length === 0 && (
|
||||||
|
<span className="text-xs text-muted-foreground">No projects</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Score distribution for selected stage */}
|
||||||
|
{selectedStageId && (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-lg">
|
||||||
|
Score Distribution
|
||||||
|
{overview.stages.find((s) => s.stageId === selectedStageId) && (
|
||||||
|
<span className="text-sm font-normal text-muted-foreground ml-2">
|
||||||
|
— {overview.stages.find((s) => s.stageId === selectedStageId)?.stageName}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{scoresLoading ? (
|
||||||
|
<Skeleton className="h-48 w-full" />
|
||||||
|
) : scoreDistribution ? (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="grid gap-3 sm:grid-cols-3">
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="text-2xl font-bold tabular-nums">
|
||||||
|
{scoreDistribution.totalEvaluations}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-muted-foreground">Evaluations</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="text-2xl font-bold tabular-nums">
|
||||||
|
{scoreDistribution.averageGlobalScore.toFixed(1)}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-muted-foreground">Avg Score</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="text-2xl font-bold tabular-nums">
|
||||||
|
{scoreDistribution.criterionDistributions.length}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-muted-foreground">Criteria</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Score histogram (text-based) */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-sm font-medium">Global Score Distribution</p>
|
||||||
|
{scoreDistribution.globalDistribution.map((bucket) => {
|
||||||
|
const maxCount = Math.max(
|
||||||
|
...scoreDistribution.globalDistribution.map((b) => b.count),
|
||||||
|
1
|
||||||
|
)
|
||||||
|
const width = (bucket.count / maxCount) * 100
|
||||||
|
return (
|
||||||
|
<div key={bucket.score} className="flex items-center gap-2">
|
||||||
|
<span className="text-xs tabular-nums w-4 text-right">{bucket.score}</span>
|
||||||
|
<div className="flex-1 h-5 bg-muted/30 rounded overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="h-full bg-brand-blue/60 dark:bg-brand-teal/60 rounded transition-all"
|
||||||
|
style={{ width: `${width}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs tabular-nums w-6 text-right">{bucket.count}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-muted-foreground">No score data available for this stage.</p>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Awards Tab Content ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function AwardsTabContent({
|
||||||
|
activePipelineId,
|
||||||
|
}: {
|
||||||
|
activePipelineId: string
|
||||||
|
}) {
|
||||||
|
const [selectedAwardStageId, setSelectedAwardStageId] = useState<string>('')
|
||||||
|
|
||||||
|
const { data: awards, isLoading: awardsLoading } =
|
||||||
|
trpc.analytics.getAwardSummary.useQuery(
|
||||||
|
{ pipelineId: activePipelineId },
|
||||||
|
{ enabled: !!activePipelineId }
|
||||||
|
)
|
||||||
|
|
||||||
|
const { data: voteDistribution, isLoading: votesLoading } =
|
||||||
|
trpc.analytics.getAwardVoteDistribution.useQuery(
|
||||||
|
{ stageId: selectedAwardStageId },
|
||||||
|
{ enabled: !!selectedAwardStageId }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (awardsLoading) {
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Skeleton className="h-24" />
|
||||||
|
<Skeleton className="h-64 w-full" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!awards || awards.length === 0) {
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
|
||||||
|
<Trophy className="h-12 w-12 text-muted-foreground/50 mb-3" />
|
||||||
|
<p className="font-medium">No award tracks</p>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
This pipeline has no award tracks configured.
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Awards summary table */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-lg flex items-center gap-2">
|
||||||
|
<Trophy className="h-5 w-5" />
|
||||||
|
Award Tracks
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Click an award's stage to see vote/score distribution
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-0">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Award</TableHead>
|
||||||
|
<TableHead>Status</TableHead>
|
||||||
|
<TableHead>Scoring</TableHead>
|
||||||
|
<TableHead>Routing</TableHead>
|
||||||
|
<TableHead className="text-center">Projects</TableHead>
|
||||||
|
<TableHead className="text-center">Completion</TableHead>
|
||||||
|
<TableHead>Winner</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{awards.map((award) => (
|
||||||
|
<TableRow key={award.trackId}>
|
||||||
|
<TableCell className="font-medium">{award.awardName}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{award.awardStatus ? (
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className={cn('text-xs', awardStatusColors[award.awardStatus])}
|
||||||
|
>
|
||||||
|
{award.awardStatus.replace(/_/g, ' ')}
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
|
<span className="text-xs text-muted-foreground">—</span>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{award.scoringMode ? (
|
||||||
|
<Badge variant="outline" className="text-xs capitalize">
|
||||||
|
{award.scoringMode.toLowerCase().replace(/_/g, ' ')}
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
|
<span className="text-xs text-muted-foreground">—</span>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{award.routingMode ? (
|
||||||
|
<Badge variant="outline" className="text-xs">
|
||||||
|
{award.routingMode}
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
|
<span className="text-xs text-muted-foreground">—</span>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-center tabular-nums">{award.projectCount}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<div className="flex items-center gap-2 min-w-[100px]">
|
||||||
|
<Progress value={award.completionRate} className="h-2 flex-1" />
|
||||||
|
<span className="text-xs tabular-nums font-medium w-8 text-right">
|
||||||
|
{award.completionRate}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{award.winner ? (
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<Trophy className="h-3.5 w-3.5 text-amber-500 shrink-0" />
|
||||||
|
<span className="text-sm font-medium truncate max-w-[150px]">
|
||||||
|
{award.winner.title}
|
||||||
|
</span>
|
||||||
|
{award.winner.overridden && (
|
||||||
|
<Badge variant="outline" className="text-[10px] px-1 py-0">
|
||||||
|
overridden
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span className="text-xs text-muted-foreground">Not finalized</span>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Award stages clickable list */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-lg">Award Stage Details</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Select a stage to view vote and score distribution
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{awards.map((award) =>
|
||||||
|
award.stages.map((stage) => (
|
||||||
|
<button
|
||||||
|
key={stage.id}
|
||||||
|
onClick={() => setSelectedAwardStageId(stage.id)}
|
||||||
|
className={cn(
|
||||||
|
'w-full text-left rounded-lg border p-3 transition-colors',
|
||||||
|
selectedAwardStageId === stage.id
|
||||||
|
? 'border-brand-blue bg-brand-blue/5 dark:border-brand-teal dark:bg-brand-teal/5'
|
||||||
|
: 'hover:bg-muted/50'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-medium">{award.awardName} — {stage.name}</p>
|
||||||
|
<p className="text-xs text-muted-foreground mt-0.5">
|
||||||
|
Type: {stage.stageType.toLowerCase().replace(/_/g, ' ')} | Status: {stage.status}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Badge variant="outline" className="text-xs capitalize">
|
||||||
|
{stage.stageType.toLowerCase().replace(/_/g, ' ')}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Vote/score distribution for selected award stage */}
|
||||||
|
{selectedAwardStageId && (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-lg">
|
||||||
|
Vote / Score Distribution
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{votesLoading ? (
|
||||||
|
<Skeleton className="h-48 w-full" />
|
||||||
|
) : voteDistribution && voteDistribution.projects.length > 0 ? (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="grid gap-3 sm:grid-cols-2">
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="text-2xl font-bold tabular-nums">
|
||||||
|
{voteDistribution.totalEvaluations}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-muted-foreground">Evaluations</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="text-2xl font-bold tabular-nums">
|
||||||
|
{voteDistribution.totalVotes}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-muted-foreground">Award Votes</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Project</TableHead>
|
||||||
|
<TableHead>Team</TableHead>
|
||||||
|
<TableHead className="text-center">Evals</TableHead>
|
||||||
|
<TableHead className="text-center">Votes</TableHead>
|
||||||
|
<TableHead className="text-center">Avg Score</TableHead>
|
||||||
|
<TableHead className="text-center">Min</TableHead>
|
||||||
|
<TableHead className="text-center">Max</TableHead>
|
||||||
|
<TableHead className="text-center">Avg Rank</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{voteDistribution.projects.map((project) => (
|
||||||
|
<TableRow key={project.projectId}>
|
||||||
|
<TableCell className="font-medium truncate max-w-[200px]">
|
||||||
|
{project.title}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-sm text-muted-foreground truncate max-w-[120px]">
|
||||||
|
{project.teamName || '—'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-center tabular-nums">
|
||||||
|
{project.evaluationCount}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-center tabular-nums">
|
||||||
|
{project.voteCount}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-center tabular-nums font-medium">
|
||||||
|
{project.avgScore !== null ? project.avgScore.toFixed(1) : '—'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-center tabular-nums">
|
||||||
|
{project.minScore !== null ? project.minScore.toFixed(1) : '—'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-center tabular-nums">
|
||||||
|
{project.maxScore !== null ? project.maxScore.toFixed(1) : '—'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-center tabular-nums">
|
||||||
|
{project.avgRank !== null ? project.avgRank.toFixed(1) : '—'}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-muted-foreground">No vote/score data available for this stage.</p>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Main Page ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export default function StageAnalyticsReportsPage() {
|
||||||
|
const { currentEdition } = useEdition()
|
||||||
|
const programId = currentEdition?.id ?? ''
|
||||||
|
|
||||||
|
const [selectedPipelineId, setSelectedPipelineId] = useState<string>('')
|
||||||
|
|
||||||
|
// Fetch pipelines
|
||||||
|
const { data: pipelines, isLoading: pipelinesLoading } =
|
||||||
|
trpc.pipeline.list.useQuery(
|
||||||
|
{ programId },
|
||||||
|
{ enabled: !!programId }
|
||||||
|
)
|
||||||
|
|
||||||
|
// Auto-select first pipeline
|
||||||
|
const activePipelineId = selectedPipelineId || (pipelines?.[0]?.id ?? '')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Button variant="ghost" size="icon" asChild className="h-8 w-8">
|
||||||
|
<Link href={"/admin/reports" as Route}>
|
||||||
|
<ArrowLeft className="h-4 w-4" />
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl font-bold tracking-tight flex items-center gap-2">
|
||||||
|
<Layers className="h-6 w-6" />
|
||||||
|
Pipeline Analytics
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm text-muted-foreground mt-0.5">
|
||||||
|
Stage-level reporting for the pipeline system
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Pipeline selector */}
|
||||||
|
{pipelines && pipelines.length > 0 && (
|
||||||
|
<Select
|
||||||
|
value={activePipelineId}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
setSelectedPipelineId(v)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-[240px]">
|
||||||
|
<SelectValue placeholder="Select pipeline" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{pipelines.map((p) => (
|
||||||
|
<SelectItem key={p.id} value={p.id}>
|
||||||
|
{p.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{pipelinesLoading ? (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="grid gap-4 sm:grid-cols-4">
|
||||||
|
{[...Array(4)].map((_, i) => (
|
||||||
|
<Skeleton key={i} className="h-24" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<Skeleton className="h-64 w-full" />
|
||||||
|
</div>
|
||||||
|
) : !activePipelineId ? (
|
||||||
|
<Card>
|
||||||
|
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
|
||||||
|
<BarChart3 className="h-12 w-12 text-muted-foreground/50 mb-3" />
|
||||||
|
<p className="font-medium">No pipeline data</p>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
Select a pipeline to view analytics.
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
) : (
|
||||||
|
<Tabs defaultValue="stages" className="space-y-6">
|
||||||
|
<TabsList>
|
||||||
|
<TabsTrigger value="stages" className="gap-2">
|
||||||
|
<Layers className="h-4 w-4" />
|
||||||
|
Stages
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="awards" className="gap-2">
|
||||||
|
<Trophy className="h-4 w-4" />
|
||||||
|
Awards
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<TabsContent value="stages" className="space-y-6">
|
||||||
|
<StagesTabContent activePipelineId={activePipelineId} />
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="awards" className="space-y-6">
|
||||||
|
<AwardsTabContent activePipelineId={activePipelineId} />
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,443 +0,0 @@
|
||||||
'use client'
|
|
||||||
|
|
||||||
import { use, useState } from 'react'
|
|
||||||
import Link from 'next/link'
|
|
||||||
import { useRouter } from 'next/navigation'
|
|
||||||
import { trpc } from '@/lib/trpc/client'
|
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from '@/components/ui/card'
|
|
||||||
import { Badge } from '@/components/ui/badge'
|
|
||||||
import { Input } from '@/components/ui/input'
|
|
||||||
import { Textarea } from '@/components/ui/textarea'
|
|
||||||
import { Label } from '@/components/ui/label'
|
|
||||||
import { Skeleton } from '@/components/ui/skeleton'
|
|
||||||
import { Separator } from '@/components/ui/separator'
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from '@/components/ui/select'
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
} from '@/components/ui/dialog'
|
|
||||||
import {
|
|
||||||
ArrowLeft,
|
|
||||||
Loader2,
|
|
||||||
Save,
|
|
||||||
Trash2,
|
|
||||||
LayoutTemplate,
|
|
||||||
Plus,
|
|
||||||
X,
|
|
||||||
GripVertical,
|
|
||||||
} from 'lucide-react'
|
|
||||||
import { toast } from 'sonner'
|
|
||||||
|
|
||||||
const ROUND_TYPE_LABELS: Record<string, string> = {
|
|
||||||
FILTERING: 'Filtering',
|
|
||||||
EVALUATION: 'Evaluation',
|
|
||||||
LIVE_EVENT: 'Live Event',
|
|
||||||
}
|
|
||||||
|
|
||||||
const CRITERION_TYPES = [
|
|
||||||
{ value: 'numeric', label: 'Numeric (1-10)' },
|
|
||||||
{ value: 'text', label: 'Text' },
|
|
||||||
{ value: 'boolean', label: 'Yes/No' },
|
|
||||||
{ value: 'section_header', label: 'Section Header' },
|
|
||||||
]
|
|
||||||
|
|
||||||
type Criterion = {
|
|
||||||
id: string
|
|
||||||
label: string
|
|
||||||
type: string
|
|
||||||
description?: string
|
|
||||||
weight?: number
|
|
||||||
min?: number
|
|
||||||
max?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function RoundTemplateDetailPage({
|
|
||||||
params,
|
|
||||||
}: {
|
|
||||||
params: Promise<{ id: string }>
|
|
||||||
}) {
|
|
||||||
const { id } = use(params)
|
|
||||||
const router = useRouter()
|
|
||||||
const utils = trpc.useUtils()
|
|
||||||
|
|
||||||
const { data: template, isLoading } = trpc.roundTemplate.getById.useQuery({ id })
|
|
||||||
const [name, setName] = useState('')
|
|
||||||
const [description, setDescription] = useState('')
|
|
||||||
const [roundType, setRoundType] = useState('EVALUATION')
|
|
||||||
const [criteria, setCriteria] = useState<Criterion[]>([])
|
|
||||||
const [initialized, setInitialized] = useState(false)
|
|
||||||
const [deleteOpen, setDeleteOpen] = useState(false)
|
|
||||||
|
|
||||||
// Initialize form state from loaded data
|
|
||||||
if (template && !initialized) {
|
|
||||||
setName(template.name)
|
|
||||||
setDescription(template.description || '')
|
|
||||||
setRoundType(template.roundType)
|
|
||||||
setCriteria((template.criteriaJson as Criterion[]) || [])
|
|
||||||
setInitialized(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateTemplate = trpc.roundTemplate.update.useMutation({
|
|
||||||
onSuccess: () => {
|
|
||||||
utils.roundTemplate.getById.invalidate({ id })
|
|
||||||
utils.roundTemplate.list.invalidate()
|
|
||||||
toast.success('Template saved')
|
|
||||||
},
|
|
||||||
onError: (err) => {
|
|
||||||
toast.error(err.message)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const deleteTemplate = trpc.roundTemplate.delete.useMutation({
|
|
||||||
onSuccess: () => {
|
|
||||||
utils.roundTemplate.list.invalidate()
|
|
||||||
router.push('/admin/round-templates')
|
|
||||||
toast.success('Template deleted')
|
|
||||||
},
|
|
||||||
onError: (err) => {
|
|
||||||
toast.error(err.message)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleSave = () => {
|
|
||||||
updateTemplate.mutate({
|
|
||||||
id,
|
|
||||||
name: name.trim(),
|
|
||||||
description: description.trim() || undefined,
|
|
||||||
roundType: roundType as 'FILTERING' | 'EVALUATION' | 'LIVE_EVENT',
|
|
||||||
criteriaJson: criteria,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const addCriterion = () => {
|
|
||||||
setCriteria([
|
|
||||||
...criteria,
|
|
||||||
{
|
|
||||||
id: `criterion_${Date.now()}`,
|
|
||||||
label: '',
|
|
||||||
type: 'numeric',
|
|
||||||
weight: 1,
|
|
||||||
min: 1,
|
|
||||||
max: 10,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateCriterion = (index: number, updates: Partial<Criterion>) => {
|
|
||||||
setCriteria(criteria.map((c, i) => (i === index ? { ...c, ...updates } : c)))
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeCriterion = (index: number) => {
|
|
||||||
setCriteria(criteria.filter((_, i) => i !== index))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<Skeleton className="h-9 w-36" />
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Skeleton className="h-8 w-64" />
|
|
||||||
<Skeleton className="h-4 w-96" />
|
|
||||||
</div>
|
|
||||||
<Skeleton className="h-64 w-full" />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!template) {
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<Button variant="ghost" asChild className="-ml-4">
|
|
||||||
<Link href="/admin/round-templates">
|
|
||||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
||||||
Back to Templates
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
<Card>
|
|
||||||
<CardContent className="py-12 text-center">
|
|
||||||
<p className="font-medium">Template not found</p>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
{/* Header */}
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Button variant="ghost" asChild className="-ml-4">
|
|
||||||
<Link href="/admin/round-templates">
|
|
||||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
||||||
Back to Templates
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-start justify-between">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<LayoutTemplate className="h-7 w-7 text-primary" />
|
|
||||||
<div>
|
|
||||||
<h1 className="text-2xl font-semibold tracking-tight">
|
|
||||||
{template.name}
|
|
||||||
</h1>
|
|
||||||
<p className="text-muted-foreground">
|
|
||||||
Edit template configuration and criteria
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setDeleteOpen(true)}
|
|
||||||
className="text-destructive"
|
|
||||||
>
|
|
||||||
<Trash2 className="mr-2 h-4 w-4" />
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
<Button onClick={handleSave} disabled={updateTemplate.isPending}>
|
|
||||||
{updateTemplate.isPending ? (
|
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<Save className="mr-2 h-4 w-4" />
|
|
||||||
)}
|
|
||||||
Save Changes
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Basic Info */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-lg">Basic Information</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="name">Template Name</Label>
|
|
||||||
<Input
|
|
||||||
id="name"
|
|
||||||
value={name}
|
|
||||||
onChange={(e) => setName(e.target.value)}
|
|
||||||
placeholder="e.g., Standard Evaluation Round"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="description">Description</Label>
|
|
||||||
<Textarea
|
|
||||||
id="description"
|
|
||||||
value={description}
|
|
||||||
onChange={(e) => setDescription(e.target.value)}
|
|
||||||
placeholder="Describe what this template is for..."
|
|
||||||
rows={3}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Round Type</Label>
|
|
||||||
<Select value={roundType} onValueChange={setRoundType}>
|
|
||||||
<SelectTrigger className="w-[240px]">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{Object.entries(ROUND_TYPE_LABELS).map(([value, label]) => (
|
|
||||||
<SelectItem key={value} value={value}>
|
|
||||||
{label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className="pt-2 text-sm text-muted-foreground">
|
|
||||||
Created {new Date(template.createdAt).toLocaleDateString()} | Last updated{' '}
|
|
||||||
{new Date(template.updatedAt).toLocaleDateString()}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Criteria */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<CardTitle className="text-lg">Evaluation Criteria</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Define the criteria jurors will use to evaluate projects
|
|
||||||
</CardDescription>
|
|
||||||
</div>
|
|
||||||
<Button variant="outline" size="sm" onClick={addCriterion}>
|
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
|
||||||
Add Criterion
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
{criteria.length === 0 ? (
|
|
||||||
<div className="text-center py-8 text-muted-foreground">
|
|
||||||
<p>No criteria defined yet.</p>
|
|
||||||
<Button variant="outline" className="mt-3" onClick={addCriterion}>
|
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
|
||||||
Add First Criterion
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="space-y-4">
|
|
||||||
{criteria.map((criterion, index) => (
|
|
||||||
<div key={criterion.id}>
|
|
||||||
{index > 0 && <Separator className="mb-4" />}
|
|
||||||
<div className="flex items-start gap-3">
|
|
||||||
<div className="mt-2 text-muted-foreground">
|
|
||||||
<GripVertical className="h-5 w-5" />
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
|
||||||
<div className="sm:col-span-2 lg:col-span-2">
|
|
||||||
<Label className="text-xs text-muted-foreground">Label</Label>
|
|
||||||
<Input
|
|
||||||
value={criterion.label}
|
|
||||||
onChange={(e) =>
|
|
||||||
updateCriterion(index, { label: e.target.value })
|
|
||||||
}
|
|
||||||
placeholder="e.g., Innovation"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label className="text-xs text-muted-foreground">Type</Label>
|
|
||||||
<Select
|
|
||||||
value={criterion.type}
|
|
||||||
onValueChange={(val) =>
|
|
||||||
updateCriterion(index, { type: val })
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{CRITERION_TYPES.map((t) => (
|
|
||||||
<SelectItem key={t.value} value={t.value}>
|
|
||||||
{t.label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
{criterion.type === 'numeric' && (
|
|
||||||
<div>
|
|
||||||
<Label className="text-xs text-muted-foreground">Weight</Label>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
min={0}
|
|
||||||
max={10}
|
|
||||||
step={0.1}
|
|
||||||
value={criterion.weight ?? 1}
|
|
||||||
onChange={(e) =>
|
|
||||||
updateCriterion(index, {
|
|
||||||
weight: parseFloat(e.target.value) || 1,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="sm:col-span-2 lg:col-span-4">
|
|
||||||
<Label className="text-xs text-muted-foreground">
|
|
||||||
Description (optional)
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
value={criterion.description || ''}
|
|
||||||
onChange={(e) =>
|
|
||||||
updateCriterion(index, { description: e.target.value })
|
|
||||||
}
|
|
||||||
placeholder="Help text for jurors..."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="mt-5 h-8 w-8 text-muted-foreground hover:text-destructive"
|
|
||||||
onClick={() => removeCriterion(index)}
|
|
||||||
>
|
|
||||||
<X className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Template Metadata */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-lg">Template Info</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="grid gap-4 sm:grid-cols-3 text-sm">
|
|
||||||
<div>
|
|
||||||
<p className="text-muted-foreground">Type</p>
|
|
||||||
<Badge variant="secondary" className="mt-1">
|
|
||||||
{ROUND_TYPE_LABELS[template.roundType] || template.roundType}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-muted-foreground">Criteria Count</p>
|
|
||||||
<p className="font-medium mt-1">{criteria.length}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-muted-foreground">Has Custom Settings</p>
|
|
||||||
<p className="font-medium mt-1">
|
|
||||||
{template.settingsJson && Object.keys(template.settingsJson as object).length > 0
|
|
||||||
? 'Yes'
|
|
||||||
: 'No'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Delete Dialog */}
|
|
||||||
<Dialog open={deleteOpen} onOpenChange={setDeleteOpen}>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Delete Template</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Are you sure you want to delete "{template.name}"? This action
|
|
||||||
cannot be undone.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<DialogFooter>
|
|
||||||
<Button variant="outline" onClick={() => setDeleteOpen(false)}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="destructive"
|
|
||||||
onClick={() => deleteTemplate.mutate({ id })}
|
|
||||||
disabled={deleteTemplate.isPending}
|
|
||||||
>
|
|
||||||
{deleteTemplate.isPending && (
|
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
||||||
)}
|
|
||||||
Delete Template
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,302 +0,0 @@
|
||||||
'use client'
|
|
||||||
|
|
||||||
import { useState } from 'react'
|
|
||||||
import Link from 'next/link'
|
|
||||||
import { trpc } from '@/lib/trpc/client'
|
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from '@/components/ui/card'
|
|
||||||
import { Badge } from '@/components/ui/badge'
|
|
||||||
import { Skeleton } from '@/components/ui/skeleton'
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from '@/components/ui/dialog'
|
|
||||||
import { Input } from '@/components/ui/input'
|
|
||||||
import { Textarea } from '@/components/ui/textarea'
|
|
||||||
import { Label } from '@/components/ui/label'
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from '@/components/ui/select'
|
|
||||||
import {
|
|
||||||
LayoutTemplate,
|
|
||||||
Plus,
|
|
||||||
Calendar,
|
|
||||||
Settings2,
|
|
||||||
Trash2,
|
|
||||||
Loader2,
|
|
||||||
} from 'lucide-react'
|
|
||||||
import { toast } from 'sonner'
|
|
||||||
|
|
||||||
const ROUND_TYPE_LABELS: Record<string, string> = {
|
|
||||||
FILTERING: 'Filtering',
|
|
||||||
EVALUATION: 'Evaluation',
|
|
||||||
LIVE_EVENT: 'Live Event',
|
|
||||||
}
|
|
||||||
|
|
||||||
const ROUND_TYPE_COLORS: Record<string, 'default' | 'secondary' | 'outline'> = {
|
|
||||||
FILTERING: 'secondary',
|
|
||||||
EVALUATION: 'default',
|
|
||||||
LIVE_EVENT: 'outline',
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function RoundTemplatesPage() {
|
|
||||||
const utils = trpc.useUtils()
|
|
||||||
const { data: templates, isLoading } = trpc.roundTemplate.list.useQuery()
|
|
||||||
const [createOpen, setCreateOpen] = useState(false)
|
|
||||||
const [newName, setNewName] = useState('')
|
|
||||||
const [newDescription, setNewDescription] = useState('')
|
|
||||||
const [newRoundType, setNewRoundType] = useState('EVALUATION')
|
|
||||||
const [deleteId, setDeleteId] = useState<string | null>(null)
|
|
||||||
|
|
||||||
const createTemplate = trpc.roundTemplate.create.useMutation({
|
|
||||||
onSuccess: () => {
|
|
||||||
utils.roundTemplate.list.invalidate()
|
|
||||||
setCreateOpen(false)
|
|
||||||
setNewName('')
|
|
||||||
setNewDescription('')
|
|
||||||
setNewRoundType('EVALUATION')
|
|
||||||
toast.success('Template created')
|
|
||||||
},
|
|
||||||
onError: (err) => {
|
|
||||||
toast.error(err.message)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const deleteTemplate = trpc.roundTemplate.delete.useMutation({
|
|
||||||
onSuccess: () => {
|
|
||||||
utils.roundTemplate.list.invalidate()
|
|
||||||
setDeleteId(null)
|
|
||||||
toast.success('Template deleted')
|
|
||||||
},
|
|
||||||
onError: (err) => {
|
|
||||||
toast.error(err.message)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleCreate = () => {
|
|
||||||
if (!newName.trim()) return
|
|
||||||
createTemplate.mutate({
|
|
||||||
name: newName.trim(),
|
|
||||||
description: newDescription.trim() || undefined,
|
|
||||||
roundType: newRoundType as 'FILTERING' | 'EVALUATION' | 'LIVE_EVENT',
|
|
||||||
criteriaJson: [],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Skeleton className="h-9 w-48" />
|
|
||||||
<Skeleton className="h-9 w-40" />
|
|
||||||
</div>
|
|
||||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
||||||
{[...Array(3)].map((_, i) => (
|
|
||||||
<Skeleton key={i} className="h-40" />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
{/* Header */}
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<h1 className="text-2xl font-semibold tracking-tight">
|
|
||||||
Round Templates
|
|
||||||
</h1>
|
|
||||||
<p className="text-muted-foreground">
|
|
||||||
Save and reuse round configurations across editions
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Dialog open={createOpen} onOpenChange={setCreateOpen}>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<Button>
|
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
|
||||||
New Template
|
|
||||||
</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Create Template</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Create a blank template. You can also save an existing round as a template from the round detail page.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="space-y-4 py-2">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="template-name">Name</Label>
|
|
||||||
<Input
|
|
||||||
id="template-name"
|
|
||||||
placeholder="e.g., Standard Evaluation Round"
|
|
||||||
value={newName}
|
|
||||||
onChange={(e) => setNewName(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="template-description">Description</Label>
|
|
||||||
<Textarea
|
|
||||||
id="template-description"
|
|
||||||
placeholder="Describe what this template is for..."
|
|
||||||
value={newDescription}
|
|
||||||
onChange={(e) => setNewDescription(e.target.value)}
|
|
||||||
rows={3}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="template-type">Round Type</Label>
|
|
||||||
<Select value={newRoundType} onValueChange={setNewRoundType}>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{Object.entries(ROUND_TYPE_LABELS).map(([value, label]) => (
|
|
||||||
<SelectItem key={value} value={value}>
|
|
||||||
{label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<DialogFooter>
|
|
||||||
<Button variant="outline" onClick={() => setCreateOpen(false)}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={handleCreate}
|
|
||||||
disabled={!newName.trim() || createTemplate.isPending}
|
|
||||||
>
|
|
||||||
{createTemplate.isPending && (
|
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
||||||
)}
|
|
||||||
Create
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Templates Grid */}
|
|
||||||
{templates && templates.length > 0 ? (
|
|
||||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
||||||
{templates.map((template: typeof templates[number]) => {
|
|
||||||
const criteria = (template.criteriaJson as Array<unknown>) || []
|
|
||||||
const hasSettings = template.settingsJson && Object.keys(template.settingsJson as object).length > 0
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card key={template.id} className="group relative transition-colors hover:bg-muted/50">
|
|
||||||
<Link href={`/admin/round-templates/${template.id}`}>
|
|
||||||
<CardHeader className="pb-3">
|
|
||||||
<div className="flex items-start justify-between">
|
|
||||||
<CardTitle className="text-lg flex items-center gap-2">
|
|
||||||
<LayoutTemplate className="h-5 w-5 text-primary" />
|
|
||||||
{template.name}
|
|
||||||
</CardTitle>
|
|
||||||
<Badge variant={ROUND_TYPE_COLORS[template.roundType] || 'secondary'}>
|
|
||||||
{ROUND_TYPE_LABELS[template.roundType] || template.roundType}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
{template.description && (
|
|
||||||
<CardDescription className="line-clamp-2">
|
|
||||||
{template.description}
|
|
||||||
</CardDescription>
|
|
||||||
)}
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<Settings2 className="h-4 w-4" />
|
|
||||||
{criteria.length} criteria
|
|
||||||
</div>
|
|
||||||
{hasSettings && (
|
|
||||||
<Badge variant="outline" className="text-xs">
|
|
||||||
Custom settings
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
<div className="flex items-center gap-1 ml-auto">
|
|
||||||
<Calendar className="h-3.5 w-3.5" />
|
|
||||||
{new Date(template.createdAt).toLocaleDateString()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Link>
|
|
||||||
{/* Delete button */}
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="absolute top-3 right-3 opacity-0 group-hover:opacity-100 transition-opacity h-8 w-8"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
setDeleteId(template.id)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Trash2 className="h-4 w-4 text-muted-foreground hover:text-destructive" />
|
|
||||||
</Button>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<Card>
|
|
||||||
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
|
|
||||||
<LayoutTemplate className="h-12 w-12 text-muted-foreground/50" />
|
|
||||||
<p className="mt-2 font-medium">No templates yet</p>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Create a template or save an existing round configuration as a template
|
|
||||||
</p>
|
|
||||||
<Button className="mt-4" onClick={() => setCreateOpen(true)}>
|
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
|
||||||
Create Template
|
|
||||||
</Button>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Delete Confirmation Dialog */}
|
|
||||||
<Dialog open={!!deleteId} onOpenChange={() => setDeleteId(null)}>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Delete Template</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Are you sure you want to delete this template? This action cannot be undone.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<DialogFooter>
|
|
||||||
<Button variant="outline" onClick={() => setDeleteId(null)}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="destructive"
|
|
||||||
onClick={() => deleteId && deleteTemplate.mutate({ id: deleteId })}
|
|
||||||
disabled={deleteTemplate.isPending}
|
|
||||||
>
|
|
||||||
{deleteTemplate.isPending && (
|
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
||||||
)}
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,404 +0,0 @@
|
||||||
'use client'
|
|
||||||
|
|
||||||
import { Suspense, use, useState } from 'react'
|
|
||||||
import Link from 'next/link'
|
|
||||||
import { trpc } from '@/lib/trpc/client'
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from '@/components/ui/card'
|
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
import { Badge } from '@/components/ui/badge'
|
|
||||||
import { Skeleton } from '@/components/ui/skeleton'
|
|
||||||
import { Switch } from '@/components/ui/switch'
|
|
||||||
import { Label } from '@/components/ui/label'
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from '@/components/ui/table'
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from '@/components/ui/dropdown-menu'
|
|
||||||
import {
|
|
||||||
ArrowLeft,
|
|
||||||
ShieldAlert,
|
|
||||||
CheckCircle2,
|
|
||||||
AlertCircle,
|
|
||||||
MoreHorizontal,
|
|
||||||
ShieldCheck,
|
|
||||||
UserX,
|
|
||||||
StickyNote,
|
|
||||||
Loader2,
|
|
||||||
} from 'lucide-react'
|
|
||||||
import { toast } from 'sonner'
|
|
||||||
import { formatDistanceToNow } from 'date-fns'
|
|
||||||
|
|
||||||
interface PageProps {
|
|
||||||
params: Promise<{ id: string }>
|
|
||||||
}
|
|
||||||
|
|
||||||
function COIManagementContent({ roundId }: { roundId: string }) {
|
|
||||||
const [conflictsOnly, setConflictsOnly] = useState(false)
|
|
||||||
|
|
||||||
const { data: round, isLoading: loadingRound } = trpc.round.get.useQuery({ id: roundId })
|
|
||||||
const { data: coiList, isLoading: loadingCOI } = trpc.evaluation.listCOIByRound.useQuery({
|
|
||||||
roundId,
|
|
||||||
hasConflictOnly: conflictsOnly || undefined,
|
|
||||||
})
|
|
||||||
|
|
||||||
const utils = trpc.useUtils()
|
|
||||||
const reviewCOI = trpc.evaluation.reviewCOI.useMutation({
|
|
||||||
onSuccess: (data) => {
|
|
||||||
utils.evaluation.listCOIByRound.invalidate({ roundId })
|
|
||||||
toast.success(`COI marked as "${data.reviewAction}"`)
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
toast.error(error.message || 'Failed to review COI')
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (loadingRound || loadingCOI) {
|
|
||||||
return <COISkeleton />
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!round) {
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
|
|
||||||
<AlertCircle className="h-12 w-12 text-destructive/50" />
|
|
||||||
<p className="mt-2 font-medium">Round Not Found</p>
|
|
||||||
<Button asChild className="mt-4">
|
|
||||||
<Link href="/admin/rounds">Back to Rounds</Link>
|
|
||||||
</Button>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const conflictCount = coiList?.filter((c) => c.hasConflict).length ?? 0
|
|
||||||
const totalCount = coiList?.length ?? 0
|
|
||||||
const reviewedCount = coiList?.filter((c) => c.reviewAction).length ?? 0
|
|
||||||
|
|
||||||
const getReviewBadge = (reviewAction: string | null) => {
|
|
||||||
switch (reviewAction) {
|
|
||||||
case 'cleared':
|
|
||||||
return (
|
|
||||||
<Badge variant="default" className="bg-green-600">
|
|
||||||
<ShieldCheck className="mr-1 h-3 w-3" />
|
|
||||||
Cleared
|
|
||||||
</Badge>
|
|
||||||
)
|
|
||||||
case 'reassigned':
|
|
||||||
return (
|
|
||||||
<Badge variant="default" className="bg-blue-600">
|
|
||||||
<UserX className="mr-1 h-3 w-3" />
|
|
||||||
Reassigned
|
|
||||||
</Badge>
|
|
||||||
)
|
|
||||||
case 'noted':
|
|
||||||
return (
|
|
||||||
<Badge variant="secondary">
|
|
||||||
<StickyNote className="mr-1 h-3 w-3" />
|
|
||||||
Noted
|
|
||||||
</Badge>
|
|
||||||
)
|
|
||||||
default:
|
|
||||||
return (
|
|
||||||
<Badge variant="outline" className="text-amber-600 border-amber-300">
|
|
||||||
Pending Review
|
|
||||||
</Badge>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getConflictTypeBadge = (type: string | null) => {
|
|
||||||
switch (type) {
|
|
||||||
case 'financial':
|
|
||||||
return <Badge variant="destructive">Financial</Badge>
|
|
||||||
case 'personal':
|
|
||||||
return <Badge variant="secondary">Personal</Badge>
|
|
||||||
case 'organizational':
|
|
||||||
return <Badge variant="outline">Organizational</Badge>
|
|
||||||
case 'other':
|
|
||||||
return <Badge variant="outline">Other</Badge>
|
|
||||||
default:
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
{/* Header */}
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Button variant="ghost" asChild className="-ml-4">
|
|
||||||
<Link href={`/admin/rounds/${roundId}`}>
|
|
||||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
||||||
Back to Round
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
||||||
<Link href={`/admin/programs/${round.program.id}`} className="hover:underline">
|
|
||||||
{round.program.name}
|
|
||||||
</Link>
|
|
||||||
<span>/</span>
|
|
||||||
<Link href={`/admin/rounds/${roundId}`} className="hover:underline">
|
|
||||||
{round.name}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<h1 className="text-2xl font-semibold tracking-tight flex items-center gap-2">
|
|
||||||
<ShieldAlert className="h-6 w-6" />
|
|
||||||
Conflict of Interest Declarations
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Stats */}
|
|
||||||
<div className="grid gap-4 sm:grid-cols-3">
|
|
||||||
<Card>
|
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
||||||
<CardTitle className="text-sm font-medium">Total Declarations</CardTitle>
|
|
||||||
<ShieldAlert className="h-4 w-4 text-muted-foreground" />
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="text-2xl font-bold">{totalCount}</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
||||||
<CardTitle className="text-sm font-medium">Conflicts Declared</CardTitle>
|
|
||||||
<AlertCircle className="h-4 w-4 text-amber-500" />
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="text-2xl font-bold text-amber-600">{conflictCount}</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
||||||
<CardTitle className="text-sm font-medium">Reviewed</CardTitle>
|
|
||||||
<CheckCircle2 className="h-4 w-4 text-green-600" />
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="text-2xl font-bold">{reviewedCount}</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* COI Table */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<CardTitle className="text-lg">Declarations</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Review and manage conflict of interest declarations from jury members
|
|
||||||
</CardDescription>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Switch
|
|
||||||
id="conflicts-only"
|
|
||||||
checked={conflictsOnly}
|
|
||||||
onCheckedChange={setConflictsOnly}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="conflicts-only" className="text-sm">
|
|
||||||
Conflicts only
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
{coiList && coiList.length > 0 ? (
|
|
||||||
<div className="rounded-lg border">
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>Project</TableHead>
|
|
||||||
<TableHead>Juror</TableHead>
|
|
||||||
<TableHead>Conflict</TableHead>
|
|
||||||
<TableHead>Type</TableHead>
|
|
||||||
<TableHead>Description</TableHead>
|
|
||||||
<TableHead>Status</TableHead>
|
|
||||||
<TableHead className="w-12">Actions</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{coiList.map((coi) => (
|
|
||||||
<TableRow key={coi.id}>
|
|
||||||
<TableCell className="font-medium max-w-[200px] truncate">
|
|
||||||
{coi.assignment.project.title}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{coi.user.name || coi.user.email}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{coi.hasConflict ? (
|
|
||||||
<Badge variant="destructive">Yes</Badge>
|
|
||||||
) : (
|
|
||||||
<Badge variant="outline" className="text-green-600 border-green-300">
|
|
||||||
No
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{coi.hasConflict ? getConflictTypeBadge(coi.conflictType) : '-'}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="max-w-[200px]">
|
|
||||||
{coi.description ? (
|
|
||||||
<span className="text-sm text-muted-foreground truncate block">
|
|
||||||
{coi.description}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span className="text-sm text-muted-foreground">-</span>
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{coi.hasConflict ? (
|
|
||||||
<div className="space-y-1">
|
|
||||||
{getReviewBadge(coi.reviewAction)}
|
|
||||||
{coi.reviewedBy && (
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
by {coi.reviewedBy.name || coi.reviewedBy.email}
|
|
||||||
{coi.reviewedAt && (
|
|
||||||
<> {formatDistanceToNow(new Date(coi.reviewedAt), { addSuffix: true })}</>
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span className="text-sm text-muted-foreground">N/A</span>
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{coi.hasConflict && (
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
disabled={reviewCOI.isPending}
|
|
||||||
>
|
|
||||||
{reviewCOI.isPending ? (
|
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<MoreHorizontal className="h-4 w-4" />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end">
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() =>
|
|
||||||
reviewCOI.mutate({
|
|
||||||
id: coi.id,
|
|
||||||
reviewAction: 'cleared',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<ShieldCheck className="mr-2 h-4 w-4" />
|
|
||||||
Clear
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() =>
|
|
||||||
reviewCOI.mutate({
|
|
||||||
id: coi.id,
|
|
||||||
reviewAction: 'reassigned',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<UserX className="mr-2 h-4 w-4" />
|
|
||||||
Reassign
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() =>
|
|
||||||
reviewCOI.mutate({
|
|
||||||
id: coi.id,
|
|
||||||
reviewAction: 'noted',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<StickyNote className="mr-2 h-4 w-4" />
|
|
||||||
Note
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex flex-col items-center justify-center py-12 text-center">
|
|
||||||
<ShieldAlert className="h-12 w-12 text-muted-foreground/50" />
|
|
||||||
<p className="mt-2 font-medium">No Declarations Yet</p>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{conflictsOnly
|
|
||||||
? 'No conflicts of interest have been declared for this round'
|
|
||||||
: 'No jury members have submitted COI declarations for this round yet'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function COISkeleton() {
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<Skeleton className="h-9 w-36" />
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Skeleton className="h-4 w-48" />
|
|
||||||
<Skeleton className="h-8 w-80" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-4 sm:grid-cols-3">
|
|
||||||
{[1, 2, 3].map((i) => (
|
|
||||||
<Card key={i}>
|
|
||||||
<CardHeader className="pb-2">
|
|
||||||
<Skeleton className="h-4 w-32" />
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<Skeleton className="h-8 w-16" />
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<Skeleton className="h-5 w-48" />
|
|
||||||
<Skeleton className="h-4 w-64" />
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<Skeleton className="h-48 w-full" />
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function COIManagementPage({ params }: PageProps) {
|
|
||||||
const { id } = use(params)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Suspense fallback={<COISkeleton />}>
|
|
||||||
<COIManagementContent roundId={id} />
|
|
||||||
</Suspense>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,24 +0,0 @@
|
||||||
'use client'
|
|
||||||
|
|
||||||
import { use, useEffect } from 'react'
|
|
||||||
import { useRouter } from 'next/navigation'
|
|
||||||
|
|
||||||
// Redirect to round details page - filtering is now integrated there
|
|
||||||
export default function FilteringDashboardPage({
|
|
||||||
params,
|
|
||||||
}: {
|
|
||||||
params: Promise<{ id: string }>
|
|
||||||
}) {
|
|
||||||
const { id: roundId } = use(params)
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
router.replace(`/admin/rounds/${roundId}`)
|
|
||||||
}, [router, roundId])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex items-center justify-center py-12">
|
|
||||||
<p className="text-muted-foreground">Redirecting to round details...</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,599 +0,0 @@
|
||||||
'use client'
|
|
||||||
|
|
||||||
import { use, useState, useCallback } from 'react'
|
|
||||||
import Link from 'next/link'
|
|
||||||
import { trpc } from '@/lib/trpc/client'
|
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from '@/components/ui/card'
|
|
||||||
import { Badge } from '@/components/ui/badge'
|
|
||||||
import { Skeleton } from '@/components/ui/skeleton'
|
|
||||||
import { Input } from '@/components/ui/input'
|
|
||||||
import { Label } from '@/components/ui/label'
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from '@/components/ui/table'
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
} from '@/components/ui/dialog'
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from '@/components/ui/select'
|
|
||||||
import {
|
|
||||||
Collapsible,
|
|
||||||
CollapsibleContent,
|
|
||||||
CollapsibleTrigger,
|
|
||||||
} from '@/components/ui/collapsible'
|
|
||||||
import { Pagination } from '@/components/shared/pagination'
|
|
||||||
import { CsvExportDialog } from '@/components/shared/csv-export-dialog'
|
|
||||||
import { toast } from 'sonner'
|
|
||||||
import {
|
|
||||||
ArrowLeft,
|
|
||||||
CheckCircle2,
|
|
||||||
XCircle,
|
|
||||||
AlertTriangle,
|
|
||||||
ChevronDown,
|
|
||||||
RotateCcw,
|
|
||||||
Loader2,
|
|
||||||
ShieldCheck,
|
|
||||||
Download,
|
|
||||||
} from 'lucide-react'
|
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
|
|
||||||
const OUTCOME_BADGES: Record<
|
|
||||||
string,
|
|
||||||
{ variant: 'default' | 'destructive' | 'secondary' | 'outline'; icon: React.ReactNode; label: string }
|
|
||||||
> = {
|
|
||||||
PASSED: {
|
|
||||||
variant: 'default',
|
|
||||||
icon: <CheckCircle2 className="mr-1 h-3 w-3" />,
|
|
||||||
label: 'Passed',
|
|
||||||
},
|
|
||||||
FILTERED_OUT: {
|
|
||||||
variant: 'destructive',
|
|
||||||
icon: <XCircle className="mr-1 h-3 w-3" />,
|
|
||||||
label: 'Filtered Out',
|
|
||||||
},
|
|
||||||
FLAGGED: {
|
|
||||||
variant: 'secondary',
|
|
||||||
icon: <AlertTriangle className="mr-1 h-3 w-3" />,
|
|
||||||
label: 'Flagged',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function FilteringResultsPage({
|
|
||||||
params,
|
|
||||||
}: {
|
|
||||||
params: Promise<{ id: string }>
|
|
||||||
}) {
|
|
||||||
const { id: roundId } = use(params)
|
|
||||||
|
|
||||||
const [outcomeFilter, setOutcomeFilter] = useState<string>('')
|
|
||||||
const [page, setPage] = useState(1)
|
|
||||||
const [expandedRows, setExpandedRows] = useState<Set<string>>(new Set())
|
|
||||||
const [overrideDialog, setOverrideDialog] = useState<{
|
|
||||||
id: string
|
|
||||||
currentOutcome: string
|
|
||||||
} | null>(null)
|
|
||||||
const [overrideOutcome, setOverrideOutcome] = useState<string>('PASSED')
|
|
||||||
const [overrideReason, setOverrideReason] = useState('')
|
|
||||||
|
|
||||||
const perPage = 20
|
|
||||||
|
|
||||||
const { data, isLoading, refetch } = trpc.filtering.getResults.useQuery({
|
|
||||||
roundId,
|
|
||||||
outcome: outcomeFilter
|
|
||||||
? (outcomeFilter as 'PASSED' | 'FILTERED_OUT' | 'FLAGGED')
|
|
||||||
: undefined,
|
|
||||||
page,
|
|
||||||
perPage,
|
|
||||||
}, {
|
|
||||||
staleTime: 0, // Always refetch - results change after filtering runs
|
|
||||||
})
|
|
||||||
|
|
||||||
const utils = trpc.useUtils()
|
|
||||||
const overrideResult = trpc.filtering.overrideResult.useMutation()
|
|
||||||
const reinstateProject = trpc.filtering.reinstateProject.useMutation()
|
|
||||||
|
|
||||||
const exportResults = trpc.export.filteringResults.useQuery(
|
|
||||||
{ roundId },
|
|
||||||
{ enabled: false }
|
|
||||||
)
|
|
||||||
const [showExportDialog, setShowExportDialog] = useState(false)
|
|
||||||
|
|
||||||
const handleExport = () => {
|
|
||||||
setShowExportDialog(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleRequestExportData = useCallback(async () => {
|
|
||||||
const result = await exportResults.refetch()
|
|
||||||
return result.data ?? undefined
|
|
||||||
}, [exportResults])
|
|
||||||
|
|
||||||
const toggleRow = (id: string) => {
|
|
||||||
const next = new Set(expandedRows)
|
|
||||||
if (next.has(id)) next.delete(id)
|
|
||||||
else next.add(id)
|
|
||||||
setExpandedRows(next)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleOverride = async () => {
|
|
||||||
if (!overrideDialog || !overrideReason.trim()) return
|
|
||||||
try {
|
|
||||||
await overrideResult.mutateAsync({
|
|
||||||
id: overrideDialog.id,
|
|
||||||
finalOutcome: overrideOutcome as 'PASSED' | 'FILTERED_OUT' | 'FLAGGED',
|
|
||||||
reason: overrideReason.trim(),
|
|
||||||
})
|
|
||||||
toast.success('Result overridden')
|
|
||||||
setOverrideDialog(null)
|
|
||||||
setOverrideReason('')
|
|
||||||
refetch()
|
|
||||||
utils.project.list.invalidate()
|
|
||||||
} catch {
|
|
||||||
toast.error('Failed to override result')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleReinstate = async (projectId: string) => {
|
|
||||||
try {
|
|
||||||
await reinstateProject.mutateAsync({ roundId, projectId })
|
|
||||||
toast.success('Project reinstated')
|
|
||||||
refetch()
|
|
||||||
utils.project.list.invalidate()
|
|
||||||
} catch {
|
|
||||||
toast.error('Failed to reinstate project')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<Skeleton className="h-9 w-48" />
|
|
||||||
<Skeleton className="h-96 w-full" />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
{/* Header */}
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Button variant="ghost" asChild className="-ml-4">
|
|
||||||
<Link href={`/admin/rounds/${roundId}`}>
|
|
||||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
||||||
Back to Round
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<h1 className="text-2xl font-semibold tracking-tight">
|
|
||||||
Filtering Results
|
|
||||||
</h1>
|
|
||||||
<p className="text-muted-foreground">
|
|
||||||
Review and override filtering outcomes
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={handleExport}
|
|
||||||
disabled={exportResults.isFetching}
|
|
||||||
>
|
|
||||||
{exportResults.isFetching ? (
|
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<Download className="mr-2 h-4 w-4" />
|
|
||||||
)}
|
|
||||||
Export CSV
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Outcome Filter Tabs */}
|
|
||||||
<div className="flex gap-2">
|
|
||||||
{['', 'PASSED', 'FILTERED_OUT', 'FLAGGED'].map((outcome) => (
|
|
||||||
<Button
|
|
||||||
key={outcome || 'all'}
|
|
||||||
variant={outcomeFilter === outcome ? 'default' : 'outline'}
|
|
||||||
size="sm"
|
|
||||||
onClick={() => {
|
|
||||||
setOutcomeFilter(outcome)
|
|
||||||
setPage(1)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{outcome ? (
|
|
||||||
<>
|
|
||||||
{OUTCOME_BADGES[outcome].icon}
|
|
||||||
{OUTCOME_BADGES[outcome].label}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
'All'
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Results Table */}
|
|
||||||
{data && data.results.length > 0 ? (
|
|
||||||
<>
|
|
||||||
<Card>
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>Project</TableHead>
|
|
||||||
<TableHead>Category</TableHead>
|
|
||||||
<TableHead>Outcome</TableHead>
|
|
||||||
<TableHead className="w-[300px]">AI Reason</TableHead>
|
|
||||||
<TableHead className="text-right">Actions</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{data.results.map((result) => {
|
|
||||||
const isExpanded = expandedRows.has(result.id)
|
|
||||||
const effectiveOutcome =
|
|
||||||
result.finalOutcome || result.outcome
|
|
||||||
const badge = OUTCOME_BADGES[effectiveOutcome]
|
|
||||||
|
|
||||||
// Extract AI reasoning from aiScreeningJson
|
|
||||||
const aiScreening = result.aiScreeningJson as Record<string, {
|
|
||||||
meetsCriteria?: boolean
|
|
||||||
confidence?: number
|
|
||||||
reasoning?: string
|
|
||||||
qualityScore?: number
|
|
||||||
spamRisk?: boolean
|
|
||||||
}> | null
|
|
||||||
const firstAiResult = aiScreening ? Object.values(aiScreening)[0] : null
|
|
||||||
const aiReasoning = firstAiResult?.reasoning
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<TableRow
|
|
||||||
key={result.id}
|
|
||||||
className="cursor-pointer hover:bg-muted/50"
|
|
||||||
onClick={() => toggleRow(result.id)}
|
|
||||||
>
|
|
||||||
<TableCell>
|
|
||||||
<div>
|
|
||||||
<p className="font-medium">
|
|
||||||
{result.project.title}
|
|
||||||
</p>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{result.project.teamName}
|
|
||||||
{result.project.country && ` · ${result.project.country}`}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{result.project.competitionCategory ? (
|
|
||||||
<Badge variant="outline">
|
|
||||||
{result.project.competitionCategory.replace(
|
|
||||||
'_',
|
|
||||||
' '
|
|
||||||
)}
|
|
||||||
</Badge>
|
|
||||||
) : (
|
|
||||||
'-'
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Badge variant={badge?.variant || 'secondary'}>
|
|
||||||
{badge?.icon}
|
|
||||||
{badge?.label || effectiveOutcome}
|
|
||||||
</Badge>
|
|
||||||
{result.overriddenByUser && (
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
Overridden by {result.overriddenByUser.name || result.overriddenByUser.email}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{aiReasoning ? (
|
|
||||||
<div className="space-y-1">
|
|
||||||
<p className="text-sm line-clamp-2">
|
|
||||||
{aiReasoning}
|
|
||||||
</p>
|
|
||||||
{firstAiResult && (
|
|
||||||
<div className="flex gap-2 text-xs text-muted-foreground">
|
|
||||||
{firstAiResult.confidence !== undefined && (
|
|
||||||
<span>Confidence: {Math.round(firstAiResult.confidence * 100)}%</span>
|
|
||||||
)}
|
|
||||||
{firstAiResult.qualityScore !== undefined && (
|
|
||||||
<span>Quality: {firstAiResult.qualityScore}/10</span>
|
|
||||||
)}
|
|
||||||
{firstAiResult.spamRisk && (
|
|
||||||
<Badge variant="destructive" className="text-xs">Spam Risk</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span className="text-sm text-muted-foreground italic">
|
|
||||||
No AI screening
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="text-right">
|
|
||||||
<div
|
|
||||||
className="flex justify-end gap-1"
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => {
|
|
||||||
setOverrideOutcome('PASSED')
|
|
||||||
setOverrideDialog({
|
|
||||||
id: result.id,
|
|
||||||
currentOutcome: effectiveOutcome,
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ShieldCheck className="mr-1 h-3 w-3" />
|
|
||||||
Override
|
|
||||||
</Button>
|
|
||||||
{effectiveOutcome === 'FILTERED_OUT' && (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={() =>
|
|
||||||
handleReinstate(result.projectId)
|
|
||||||
}
|
|
||||||
disabled={reinstateProject.isPending}
|
|
||||||
>
|
|
||||||
<RotateCcw className="mr-1 h-3 w-3" />
|
|
||||||
Reinstate
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
{isExpanded && (
|
|
||||||
<TableRow key={`${result.id}-detail`}>
|
|
||||||
<TableCell colSpan={5} className="bg-muted/30">
|
|
||||||
<div className="p-4 space-y-4">
|
|
||||||
{/* Rule Results (non-AI rules only, AI shown separately) */}
|
|
||||||
<div>
|
|
||||||
<p className="text-sm font-medium mb-2">
|
|
||||||
Rule Results
|
|
||||||
</p>
|
|
||||||
{result.ruleResultsJson &&
|
|
||||||
Array.isArray(result.ruleResultsJson) ? (
|
|
||||||
<div className="space-y-2">
|
|
||||||
{(
|
|
||||||
result.ruleResultsJson as Array<{
|
|
||||||
ruleName: string
|
|
||||||
ruleType: string
|
|
||||||
passed: boolean
|
|
||||||
action: string
|
|
||||||
reasoning?: string
|
|
||||||
}>
|
|
||||||
).filter((rr) => rr.ruleType !== 'AI_SCREENING').map((rr, i) => (
|
|
||||||
<div
|
|
||||||
key={i}
|
|
||||||
className="flex items-start gap-2 text-sm"
|
|
||||||
>
|
|
||||||
{rr.passed ? (
|
|
||||||
<CheckCircle2 className="h-4 w-4 text-green-600 mt-0.5 flex-shrink-0" />
|
|
||||||
) : (
|
|
||||||
<XCircle className="h-4 w-4 text-red-600 mt-0.5 flex-shrink-0" />
|
|
||||||
)}
|
|
||||||
<div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="font-medium">
|
|
||||||
{rr.ruleName}
|
|
||||||
</span>
|
|
||||||
<Badge variant="outline" className="text-xs">
|
|
||||||
{rr.ruleType.replace('_', ' ')}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
{rr.reasoning && (
|
|
||||||
<p className="text-muted-foreground mt-0.5">
|
|
||||||
{rr.reasoning}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
No detailed rule results available
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* AI Screening Details */}
|
|
||||||
{aiScreening && Object.keys(aiScreening).length > 0 && (
|
|
||||||
<div>
|
|
||||||
<p className="text-sm font-medium mb-2">
|
|
||||||
AI Screening Analysis
|
|
||||||
</p>
|
|
||||||
<div className="space-y-3">
|
|
||||||
{Object.entries(aiScreening).map(([ruleId, screening]) => (
|
|
||||||
<div key={ruleId} className="p-3 bg-background rounded-lg border">
|
|
||||||
<div className="flex items-center gap-2 mb-2">
|
|
||||||
{screening.meetsCriteria ? (
|
|
||||||
<CheckCircle2 className="h-4 w-4 text-green-600" />
|
|
||||||
) : (
|
|
||||||
<XCircle className="h-4 w-4 text-red-600" />
|
|
||||||
)}
|
|
||||||
<span className="font-medium text-sm">
|
|
||||||
{screening.meetsCriteria ? 'Meets Criteria' : 'Does Not Meet Criteria'}
|
|
||||||
</span>
|
|
||||||
{screening.spamRisk && (
|
|
||||||
<Badge variant="destructive" className="text-xs">
|
|
||||||
<AlertTriangle className="h-3 w-3 mr-1" />
|
|
||||||
Spam Risk
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{screening.reasoning && (
|
|
||||||
<p className="text-sm text-muted-foreground mb-2">
|
|
||||||
{screening.reasoning}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<div className="flex gap-4 text-xs text-muted-foreground">
|
|
||||||
{screening.confidence !== undefined && (
|
|
||||||
<span>
|
|
||||||
Confidence: <strong>{Math.round(screening.confidence * 100)}%</strong>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{screening.qualityScore !== undefined && (
|
|
||||||
<span>
|
|
||||||
Quality Score: <strong>{screening.qualityScore}/10</strong>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Override Info */}
|
|
||||||
{result.overriddenByUser && (
|
|
||||||
<div className="pt-3 border-t">
|
|
||||||
<p className="text-sm font-medium mb-1">Manual Override</p>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Overridden to <strong>{result.finalOutcome}</strong> by{' '}
|
|
||||||
{result.overriddenByUser.name || result.overriddenByUser.email}
|
|
||||||
</p>
|
|
||||||
{result.overrideReason && (
|
|
||||||
<p className="text-sm text-muted-foreground mt-1">
|
|
||||||
Reason: {result.overrideReason}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Pagination
|
|
||||||
page={data.page}
|
|
||||||
totalPages={data.totalPages}
|
|
||||||
total={data.total}
|
|
||||||
perPage={perPage}
|
|
||||||
onPageChange={setPage}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Card>
|
|
||||||
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
|
|
||||||
<CheckCircle2 className="h-12 w-12 text-muted-foreground/50" />
|
|
||||||
<p className="mt-2 font-medium">No results found</p>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{outcomeFilter
|
|
||||||
? 'No results match this filter'
|
|
||||||
: 'Run filtering rules to generate results'}
|
|
||||||
</p>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Override Dialog */}
|
|
||||||
<Dialog
|
|
||||||
open={!!overrideDialog}
|
|
||||||
onOpenChange={(open) => {
|
|
||||||
if (!open) {
|
|
||||||
setOverrideDialog(null)
|
|
||||||
setOverrideReason('')
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Override Filtering Result</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Change the outcome for this project. This will be logged in the
|
|
||||||
audit trail.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>New Outcome</Label>
|
|
||||||
<Select
|
|
||||||
value={overrideOutcome}
|
|
||||||
onValueChange={setOverrideOutcome}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="PASSED">Passed</SelectItem>
|
|
||||||
<SelectItem value="FILTERED_OUT">Filtered Out</SelectItem>
|
|
||||||
<SelectItem value="FLAGGED">Flagged</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Reason</Label>
|
|
||||||
<Input
|
|
||||||
value={overrideReason}
|
|
||||||
onChange={(e) => setOverrideReason(e.target.value)}
|
|
||||||
placeholder="Explain why you're overriding..."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<DialogFooter>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => setOverrideDialog(null)}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={handleOverride}
|
|
||||||
disabled={
|
|
||||||
overrideResult.isPending || !overrideReason.trim()
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{overrideResult.isPending && (
|
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
||||||
)}
|
|
||||||
Override
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
|
|
||||||
{/* CSV Export Dialog with Column Selection */}
|
|
||||||
<CsvExportDialog
|
|
||||||
open={showExportDialog}
|
|
||||||
onOpenChange={setShowExportDialog}
|
|
||||||
exportData={exportResults.data ?? undefined}
|
|
||||||
isLoading={exportResults.isFetching}
|
|
||||||
filename="filtering-results"
|
|
||||||
onRequestData={handleRequestExportData}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,586 +0,0 @@
|
||||||
'use client'
|
|
||||||
|
|
||||||
import { use, useState } from 'react'
|
|
||||||
import Link from 'next/link'
|
|
||||||
import { trpc } from '@/lib/trpc/client'
|
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from '@/components/ui/card'
|
|
||||||
import { Input } from '@/components/ui/input'
|
|
||||||
import { Label } from '@/components/ui/label'
|
|
||||||
import { Badge } from '@/components/ui/badge'
|
|
||||||
import { Switch } from '@/components/ui/switch'
|
|
||||||
import { Skeleton } from '@/components/ui/skeleton'
|
|
||||||
import { Textarea } from '@/components/ui/textarea'
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from '@/components/ui/select'
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from '@/components/ui/dialog'
|
|
||||||
import { toast } from 'sonner'
|
|
||||||
import {
|
|
||||||
ArrowLeft,
|
|
||||||
Plus,
|
|
||||||
Trash2,
|
|
||||||
GripVertical,
|
|
||||||
Loader2,
|
|
||||||
FileCheck,
|
|
||||||
SlidersHorizontal,
|
|
||||||
Filter,
|
|
||||||
} from 'lucide-react'
|
|
||||||
|
|
||||||
type RuleType = 'FIELD_BASED' | 'DOCUMENT_CHECK' | 'AI_SCREENING'
|
|
||||||
|
|
||||||
const RULE_TYPE_LABELS: Record<RuleType, string> = {
|
|
||||||
FIELD_BASED: 'Field-Based',
|
|
||||||
DOCUMENT_CHECK: 'Document Check',
|
|
||||||
AI_SCREENING: 'AI Screening',
|
|
||||||
}
|
|
||||||
|
|
||||||
const RULE_TYPE_ICONS: Record<RuleType, React.ReactNode> = {
|
|
||||||
FIELD_BASED: <Filter className="h-4 w-4" />,
|
|
||||||
DOCUMENT_CHECK: <FileCheck className="h-4 w-4" />,
|
|
||||||
AI_SCREENING: <SlidersHorizontal className="h-4 w-4" />,
|
|
||||||
}
|
|
||||||
|
|
||||||
const FIELD_OPTIONS = [
|
|
||||||
{ value: 'competitionCategory', label: 'Competition Category' },
|
|
||||||
{ value: 'foundedAt', label: 'Founded Date' },
|
|
||||||
{ value: 'country', label: 'Country' },
|
|
||||||
{ value: 'geographicZone', label: 'Geographic Zone' },
|
|
||||||
{ value: 'tags', label: 'Tags' },
|
|
||||||
{ value: 'oceanIssue', label: 'Ocean Issue' },
|
|
||||||
]
|
|
||||||
|
|
||||||
const OPERATOR_OPTIONS = [
|
|
||||||
{ value: 'equals', label: 'Equals' },
|
|
||||||
{ value: 'not_equals', label: 'Not Equals' },
|
|
||||||
{ value: 'contains', label: 'Contains' },
|
|
||||||
{ value: 'in', label: 'In (list)' },
|
|
||||||
{ value: 'not_in', label: 'Not In (list)' },
|
|
||||||
{ value: 'is_empty', label: 'Is Empty' },
|
|
||||||
{ value: 'older_than_years', label: 'Older Than (years)' },
|
|
||||||
{ value: 'newer_than_years', label: 'Newer Than (years)' },
|
|
||||||
]
|
|
||||||
|
|
||||||
export default function FilteringRulesPage({
|
|
||||||
params,
|
|
||||||
}: {
|
|
||||||
params: Promise<{ id: string }>
|
|
||||||
}) {
|
|
||||||
const { id: roundId } = use(params)
|
|
||||||
|
|
||||||
const { data: rules, isLoading, refetch } =
|
|
||||||
trpc.filtering.getRules.useQuery({ roundId })
|
|
||||||
const createRule = trpc.filtering.createRule.useMutation()
|
|
||||||
const updateRule = trpc.filtering.updateRule.useMutation()
|
|
||||||
const deleteRule = trpc.filtering.deleteRule.useMutation()
|
|
||||||
|
|
||||||
const [showCreateDialog, setShowCreateDialog] = useState(false)
|
|
||||||
const [newRuleName, setNewRuleName] = useState('')
|
|
||||||
const [newRuleType, setNewRuleType] = useState<RuleType>('FIELD_BASED')
|
|
||||||
|
|
||||||
// Field-based config state
|
|
||||||
const [conditionField, setConditionField] = useState('competitionCategory')
|
|
||||||
const [conditionOperator, setConditionOperator] = useState('equals')
|
|
||||||
const [conditionValue, setConditionValue] = useState('')
|
|
||||||
const [conditionLogic, setConditionLogic] = useState<'AND' | 'OR'>('AND')
|
|
||||||
const [conditionAction, setConditionAction] = useState<'PASS' | 'REJECT' | 'FLAG'>('REJECT')
|
|
||||||
|
|
||||||
// Document check config state
|
|
||||||
const [minFileCount, setMinFileCount] = useState('1')
|
|
||||||
const [docAction, setDocAction] = useState<'PASS' | 'REJECT' | 'FLAG'>('REJECT')
|
|
||||||
|
|
||||||
// AI screening config state
|
|
||||||
const [criteriaText, setCriteriaText] = useState('')
|
|
||||||
const [aiAction, setAiAction] = useState<'REJECT' | 'FLAG'>('REJECT')
|
|
||||||
const [aiBatchSize, setAiBatchSize] = useState('20')
|
|
||||||
const [aiParallelBatches, setAiParallelBatches] = useState('1')
|
|
||||||
|
|
||||||
const handleCreateRule = async () => {
|
|
||||||
if (!newRuleName.trim()) return
|
|
||||||
|
|
||||||
let configJson: Record<string, unknown> = {}
|
|
||||||
|
|
||||||
if (newRuleType === 'FIELD_BASED') {
|
|
||||||
configJson = {
|
|
||||||
conditions: [
|
|
||||||
{
|
|
||||||
field: conditionField,
|
|
||||||
operator: conditionOperator,
|
|
||||||
value: conditionOperator === 'in' || conditionOperator === 'not_in'
|
|
||||||
? conditionValue.split(',').map((v) => v.trim())
|
|
||||||
: conditionOperator === 'older_than_years' ||
|
|
||||||
conditionOperator === 'newer_than_years' ||
|
|
||||||
conditionOperator === 'greater_than' ||
|
|
||||||
conditionOperator === 'less_than'
|
|
||||||
? Number(conditionValue)
|
|
||||||
: conditionValue,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
logic: conditionLogic,
|
|
||||||
action: conditionAction,
|
|
||||||
}
|
|
||||||
} else if (newRuleType === 'DOCUMENT_CHECK') {
|
|
||||||
configJson = {
|
|
||||||
minFileCount: parseInt(minFileCount) || 1,
|
|
||||||
action: docAction,
|
|
||||||
}
|
|
||||||
} else if (newRuleType === 'AI_SCREENING') {
|
|
||||||
configJson = {
|
|
||||||
criteriaText,
|
|
||||||
action: aiAction,
|
|
||||||
batchSize: parseInt(aiBatchSize) || 20,
|
|
||||||
parallelBatches: parseInt(aiParallelBatches) || 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await createRule.mutateAsync({
|
|
||||||
roundId,
|
|
||||||
name: newRuleName.trim(),
|
|
||||||
ruleType: newRuleType,
|
|
||||||
configJson,
|
|
||||||
priority: (rules?.length || 0) + 1,
|
|
||||||
})
|
|
||||||
toast.success('Rule created')
|
|
||||||
setShowCreateDialog(false)
|
|
||||||
resetForm()
|
|
||||||
refetch()
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(
|
|
||||||
error instanceof Error ? error.message : 'Failed to create rule'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleToggleActive = async (ruleId: string, isActive: boolean) => {
|
|
||||||
try {
|
|
||||||
await updateRule.mutateAsync({ id: ruleId, isActive })
|
|
||||||
refetch()
|
|
||||||
} catch {
|
|
||||||
toast.error('Failed to update rule')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDeleteRule = async (ruleId: string) => {
|
|
||||||
try {
|
|
||||||
await deleteRule.mutateAsync({ id: ruleId })
|
|
||||||
toast.success('Rule deleted')
|
|
||||||
refetch()
|
|
||||||
} catch {
|
|
||||||
toast.error('Failed to delete rule')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetForm = () => {
|
|
||||||
setNewRuleName('')
|
|
||||||
setNewRuleType('FIELD_BASED')
|
|
||||||
setConditionField('competitionCategory')
|
|
||||||
setConditionOperator('equals')
|
|
||||||
setConditionValue('')
|
|
||||||
setConditionLogic('AND')
|
|
||||||
setConditionAction('REJECT')
|
|
||||||
setMinFileCount('1')
|
|
||||||
setDocAction('REJECT')
|
|
||||||
setCriteriaText('')
|
|
||||||
setAiBatchSize('20')
|
|
||||||
setAiParallelBatches('1')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<Skeleton className="h-9 w-48" />
|
|
||||||
<div className="space-y-4">
|
|
||||||
{[...Array(3)].map((_, i) => (
|
|
||||||
<Skeleton key={i} className="h-24 w-full" />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
{/* Header */}
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Button variant="ghost" asChild className="-ml-4">
|
|
||||||
<Link href={`/admin/rounds/${roundId}/filtering`}>
|
|
||||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
||||||
Back to Filtering
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-start justify-between">
|
|
||||||
<div>
|
|
||||||
<h1 className="text-2xl font-semibold tracking-tight">
|
|
||||||
Filtering Rules
|
|
||||||
</h1>
|
|
||||||
<p className="text-muted-foreground">
|
|
||||||
Rules are evaluated in order of priority
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Dialog open={showCreateDialog} onOpenChange={setShowCreateDialog}>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<Button>
|
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
|
||||||
Add Rule
|
|
||||||
</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="max-w-lg">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Create Filtering Rule</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Define conditions that projects must meet
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Rule Name</Label>
|
|
||||||
<Input
|
|
||||||
value={newRuleName}
|
|
||||||
onChange={(e) => setNewRuleName(e.target.value)}
|
|
||||||
placeholder="e.g., Startup age check"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Rule Type</Label>
|
|
||||||
<Select
|
|
||||||
value={newRuleType}
|
|
||||||
onValueChange={(v) => setNewRuleType(v as RuleType)}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="FIELD_BASED">Field-Based</SelectItem>
|
|
||||||
<SelectItem value="DOCUMENT_CHECK">
|
|
||||||
Document Check
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="AI_SCREENING">AI Screening</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Field-Based Config */}
|
|
||||||
{newRuleType === 'FIELD_BASED' && (
|
|
||||||
<>
|
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Field</Label>
|
|
||||||
<Select
|
|
||||||
value={conditionField}
|
|
||||||
onValueChange={setConditionField}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{FIELD_OPTIONS.map((f) => (
|
|
||||||
<SelectItem key={f.value} value={f.value}>
|
|
||||||
{f.label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Operator</Label>
|
|
||||||
<Select
|
|
||||||
value={conditionOperator}
|
|
||||||
onValueChange={setConditionOperator}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{OPERATOR_OPTIONS.map((o) => (
|
|
||||||
<SelectItem key={o.value} value={o.value}>
|
|
||||||
{o.label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{conditionOperator !== 'is_empty' && (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Value</Label>
|
|
||||||
<Input
|
|
||||||
value={conditionValue}
|
|
||||||
onChange={(e) => setConditionValue(e.target.value)}
|
|
||||||
placeholder={
|
|
||||||
conditionOperator === 'in' ||
|
|
||||||
conditionOperator === 'not_in'
|
|
||||||
? 'Comma-separated values'
|
|
||||||
: 'Value'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Logic</Label>
|
|
||||||
<Select
|
|
||||||
value={conditionLogic}
|
|
||||||
onValueChange={(v) =>
|
|
||||||
setConditionLogic(v as 'AND' | 'OR')
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="AND">AND</SelectItem>
|
|
||||||
<SelectItem value="OR">OR</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Action</Label>
|
|
||||||
<Select
|
|
||||||
value={conditionAction}
|
|
||||||
onValueChange={(v) =>
|
|
||||||
setConditionAction(v as 'PASS' | 'REJECT' | 'FLAG')
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="PASS">Pass</SelectItem>
|
|
||||||
<SelectItem value="REJECT">Reject</SelectItem>
|
|
||||||
<SelectItem value="FLAG">Flag</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Document Check Config */}
|
|
||||||
{newRuleType === 'DOCUMENT_CHECK' && (
|
|
||||||
<>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Minimum File Count</Label>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
value={minFileCount}
|
|
||||||
onChange={(e) => setMinFileCount(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Action if not met</Label>
|
|
||||||
<Select
|
|
||||||
value={docAction}
|
|
||||||
onValueChange={(v) =>
|
|
||||||
setDocAction(v as 'PASS' | 'REJECT' | 'FLAG')
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="REJECT">Reject</SelectItem>
|
|
||||||
<SelectItem value="FLAG">Flag</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* AI Screening Config */}
|
|
||||||
{newRuleType === 'AI_SCREENING' && (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Screening Criteria</Label>
|
|
||||||
<Textarea
|
|
||||||
value={criteriaText}
|
|
||||||
onChange={(e) => setCriteriaText(e.target.value)}
|
|
||||||
placeholder="Describe the criteria for AI to evaluate projects against..."
|
|
||||||
rows={4}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Action for Non-Matching Projects</Label>
|
|
||||||
<Select value={aiAction} onValueChange={(v) => setAiAction(v as 'REJECT' | 'FLAG')}>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="REJECT">Auto Filter Out</SelectItem>
|
|
||||||
<SelectItem value="FLAG">Flag for Review</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
{aiAction === 'REJECT'
|
|
||||||
? 'Projects that don\'t meet criteria will be automatically filtered out.'
|
|
||||||
: 'Projects that don\'t meet criteria will be flagged for human review.'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="border-t pt-4">
|
|
||||||
<Label className="text-sm font-medium">Performance Settings</Label>
|
|
||||||
<p className="text-xs text-muted-foreground mb-3">
|
|
||||||
Adjust batch settings to balance speed vs. cost
|
|
||||||
</p>
|
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-xs">Batch Size</Label>
|
|
||||||
<Select value={aiBatchSize} onValueChange={setAiBatchSize}>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="1">1 (Individual)</SelectItem>
|
|
||||||
<SelectItem value="5">5 (Small)</SelectItem>
|
|
||||||
<SelectItem value="10">10 (Medium)</SelectItem>
|
|
||||||
<SelectItem value="20">20 (Default)</SelectItem>
|
|
||||||
<SelectItem value="50">50 (Large)</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<p className="text-[10px] text-muted-foreground">
|
|
||||||
Projects per API call. Smaller = more parallel potential
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-xs">Parallel Requests</Label>
|
|
||||||
<Select value={aiParallelBatches} onValueChange={setAiParallelBatches}>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="1">1 (Sequential)</SelectItem>
|
|
||||||
<SelectItem value="2">2</SelectItem>
|
|
||||||
<SelectItem value="3">3</SelectItem>
|
|
||||||
<SelectItem value="5">5 (Fast)</SelectItem>
|
|
||||||
<SelectItem value="10">10 (Maximum)</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<p className="text-[10px] text-muted-foreground">
|
|
||||||
Concurrent API calls. Higher = faster but more costly
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<DialogFooter>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => setShowCreateDialog(false)}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={handleCreateRule}
|
|
||||||
disabled={
|
|
||||||
createRule.isPending ||
|
|
||||||
!newRuleName.trim() ||
|
|
||||||
(newRuleType === 'AI_SCREENING' && !criteriaText.trim())
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{createRule.isPending && (
|
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
||||||
)}
|
|
||||||
Create Rule
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Rules List */}
|
|
||||||
{rules && rules.length > 0 ? (
|
|
||||||
<div className="space-y-3">
|
|
||||||
{rules.map((rule, index) => (
|
|
||||||
<Card key={rule.id}>
|
|
||||||
<CardContent className="flex items-center gap-4 py-4">
|
|
||||||
<div className="flex items-center gap-2 text-muted-foreground">
|
|
||||||
<GripVertical className="h-4 w-4" />
|
|
||||||
<span className="text-sm font-mono w-6 text-center">
|
|
||||||
{index + 1}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{RULE_TYPE_ICONS[rule.ruleType as RuleType]}
|
|
||||||
<p className="font-medium">{rule.name}</p>
|
|
||||||
<Badge variant="outline">
|
|
||||||
{RULE_TYPE_LABELS[rule.ruleType as RuleType]}
|
|
||||||
</Badge>
|
|
||||||
{!rule.isActive && (
|
|
||||||
<Badge variant="secondary">Disabled</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-muted-foreground mt-1">
|
|
||||||
{rule.ruleType === 'AI_SCREENING'
|
|
||||||
? (rule.configJson as Record<string, unknown>)
|
|
||||||
?.criteriaText
|
|
||||||
? String(
|
|
||||||
(rule.configJson as Record<string, unknown>)
|
|
||||||
.criteriaText
|
|
||||||
).slice(0, 80) + '...'
|
|
||||||
: 'AI screening rule'
|
|
||||||
: rule.ruleType === 'DOCUMENT_CHECK'
|
|
||||||
? `Min ${(rule.configJson as Record<string, unknown>)?.minFileCount || 1} file(s)`
|
|
||||||
: `${((rule.configJson as Record<string, unknown>)?.conditions as Array<Record<string, unknown>>)?.length || 0} condition(s)`}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Switch
|
|
||||||
checked={rule.isActive}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
handleToggleActive(rule.id, checked)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
onClick={() => handleDeleteRule(rule.id)}
|
|
||||||
disabled={deleteRule.isPending}
|
|
||||||
className="text-muted-foreground hover:text-destructive"
|
|
||||||
>
|
|
||||||
<Trash2 className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<Card>
|
|
||||||
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
|
|
||||||
<Filter className="h-12 w-12 text-muted-foreground/50" />
|
|
||||||
<p className="mt-2 font-medium">No rules configured</p>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Add filtering rules to screen projects automatically
|
|
||||||
</p>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,390 @@
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState, useCallback, useRef, useEffect } from 'react'
|
||||||
|
import { useRouter, useSearchParams } from 'next/navigation'
|
||||||
|
import type { Route } from 'next'
|
||||||
|
import { trpc } from '@/lib/trpc/client'
|
||||||
|
import { toast } from 'sonner'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { ArrowLeft, Loader2, Save, Rocket } from 'lucide-react'
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
import { WizardSection } from '@/components/admin/pipeline/wizard-section'
|
||||||
|
import { BasicsSection } from '@/components/admin/pipeline/sections/basics-section'
|
||||||
|
import { IntakeSection } from '@/components/admin/pipeline/sections/intake-section'
|
||||||
|
import { MainTrackSection } from '@/components/admin/pipeline/sections/main-track-section'
|
||||||
|
import { FilteringSection } from '@/components/admin/pipeline/sections/filtering-section'
|
||||||
|
import { AssignmentSection } from '@/components/admin/pipeline/sections/assignment-section'
|
||||||
|
import { AwardsSection } from '@/components/admin/pipeline/sections/awards-section'
|
||||||
|
import { LiveFinalsSection } from '@/components/admin/pipeline/sections/live-finals-section'
|
||||||
|
import { NotificationsSection } from '@/components/admin/pipeline/sections/notifications-section'
|
||||||
|
import { ReviewSection } from '@/components/admin/pipeline/sections/review-section'
|
||||||
|
|
||||||
|
import { defaultWizardState, defaultIntakeConfig, defaultFilterConfig, defaultEvaluationConfig, defaultLiveConfig } from '@/lib/pipeline-defaults'
|
||||||
|
import { validateAll, validateBasics, validateTracks } from '@/lib/pipeline-validation'
|
||||||
|
import type { WizardState, IntakeConfig, FilterConfig, EvaluationConfig, LiveFinalConfig } from '@/types/pipeline-wizard'
|
||||||
|
|
||||||
|
export default function NewPipelinePage() {
|
||||||
|
const router = useRouter()
|
||||||
|
const searchParams = useSearchParams()
|
||||||
|
const programId = searchParams.get('programId') ?? ''
|
||||||
|
|
||||||
|
const [state, setState] = useState<WizardState>(() => defaultWizardState(programId))
|
||||||
|
const [openSection, setOpenSection] = useState(0)
|
||||||
|
const initialStateRef = useRef(JSON.stringify(state))
|
||||||
|
|
||||||
|
// Dirty tracking — warn on navigate away
|
||||||
|
useEffect(() => {
|
||||||
|
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
||||||
|
if (JSON.stringify(state) !== initialStateRef.current) {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.addEventListener('beforeunload', handleBeforeUnload)
|
||||||
|
return () => window.removeEventListener('beforeunload', handleBeforeUnload)
|
||||||
|
}, [state])
|
||||||
|
|
||||||
|
const updateState = useCallback((updates: Partial<WizardState>) => {
|
||||||
|
setState((prev) => ({ ...prev, ...updates }))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Get stage configs from the main track
|
||||||
|
const mainTrack = state.tracks.find((t) => t.kind === 'MAIN')
|
||||||
|
const intakeStage = mainTrack?.stages.find((s) => s.stageType === 'INTAKE')
|
||||||
|
const filterStage = mainTrack?.stages.find((s) => s.stageType === 'FILTER')
|
||||||
|
const evalStage = mainTrack?.stages.find((s) => s.stageType === 'EVALUATION')
|
||||||
|
const liveStage = mainTrack?.stages.find((s) => s.stageType === 'LIVE_FINAL')
|
||||||
|
|
||||||
|
const intakeConfig = (intakeStage?.configJson ?? defaultIntakeConfig()) as unknown as IntakeConfig
|
||||||
|
const filterConfig = (filterStage?.configJson ?? defaultFilterConfig()) as unknown as FilterConfig
|
||||||
|
const evalConfig = (evalStage?.configJson ?? defaultEvaluationConfig()) as unknown as EvaluationConfig
|
||||||
|
const liveConfig = (liveStage?.configJson ?? defaultLiveConfig()) as unknown as LiveFinalConfig
|
||||||
|
|
||||||
|
const updateStageConfig = useCallback(
|
||||||
|
(stageType: string, configJson: Record<string, unknown>) => {
|
||||||
|
setState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
tracks: prev.tracks.map((track) => {
|
||||||
|
if (track.kind !== 'MAIN') return track
|
||||||
|
return {
|
||||||
|
...track,
|
||||||
|
stages: track.stages.map((stage) =>
|
||||||
|
stage.stageType === stageType ? { ...stage, configJson } : stage
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const updateMainTrackStages = useCallback(
|
||||||
|
(stages: WizardState['tracks'][0]['stages']) => {
|
||||||
|
setState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
tracks: prev.tracks.map((track) =>
|
||||||
|
track.kind === 'MAIN' ? { ...track, stages } : track
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
const basicsValid = validateBasics(state).valid
|
||||||
|
const tracksValid = validateTracks(state.tracks).valid
|
||||||
|
const allValid = validateAll(state).valid
|
||||||
|
|
||||||
|
// Mutations
|
||||||
|
const createMutation = trpc.pipeline.createStructure.useMutation({
|
||||||
|
onSuccess: (data) => {
|
||||||
|
initialStateRef.current = JSON.stringify(state) // prevent dirty warning
|
||||||
|
toast.success('Pipeline created successfully')
|
||||||
|
router.push(`/admin/rounds/pipeline/${data.pipeline.id}` as Route)
|
||||||
|
},
|
||||||
|
onError: (err) => {
|
||||||
|
toast.error(err.message)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const publishMutation = trpc.pipeline.publish.useMutation({
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success('Pipeline published successfully')
|
||||||
|
},
|
||||||
|
onError: (err) => {
|
||||||
|
toast.error(err.message)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSave = async (publish: boolean) => {
|
||||||
|
const validation = validateAll(state)
|
||||||
|
if (!validation.valid) {
|
||||||
|
toast.error('Please fix validation errors before saving')
|
||||||
|
// Open first section with errors
|
||||||
|
if (!validation.sections.basics.valid) setOpenSection(0)
|
||||||
|
else if (!validation.sections.tracks.valid) setOpenSection(2)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await createMutation.mutateAsync({
|
||||||
|
programId: state.programId,
|
||||||
|
name: state.name,
|
||||||
|
slug: state.slug,
|
||||||
|
settingsJson: {
|
||||||
|
...state.settingsJson,
|
||||||
|
notificationConfig: state.notificationConfig,
|
||||||
|
overridePolicy: state.overridePolicy,
|
||||||
|
},
|
||||||
|
tracks: state.tracks.map((t) => ({
|
||||||
|
name: t.name,
|
||||||
|
slug: t.slug,
|
||||||
|
kind: t.kind,
|
||||||
|
sortOrder: t.sortOrder,
|
||||||
|
routingModeDefault: t.routingModeDefault,
|
||||||
|
decisionMode: t.decisionMode,
|
||||||
|
stages: t.stages.map((s) => ({
|
||||||
|
name: s.name,
|
||||||
|
slug: s.slug,
|
||||||
|
stageType: s.stageType,
|
||||||
|
sortOrder: s.sortOrder,
|
||||||
|
configJson: s.configJson,
|
||||||
|
})),
|
||||||
|
awardConfig: t.awardConfig,
|
||||||
|
})),
|
||||||
|
autoTransitions: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (publish && result.pipeline.id) {
|
||||||
|
await publishMutation.mutateAsync({ id: result.pipeline.id })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSaving = createMutation.isPending || publishMutation.isPending
|
||||||
|
|
||||||
|
const sections = [
|
||||||
|
{
|
||||||
|
title: 'Basics',
|
||||||
|
description: 'Pipeline name, slug, and program',
|
||||||
|
isValid: basicsValid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Intake',
|
||||||
|
description: 'Submission windows and file requirements',
|
||||||
|
isValid: !!intakeStage,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Main Track Stages',
|
||||||
|
description: `${mainTrack?.stages.length ?? 0} stages configured`,
|
||||||
|
isValid: tracksValid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Filtering',
|
||||||
|
description: 'Gate rules and AI screening settings',
|
||||||
|
isValid: !!filterStage,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Assignment',
|
||||||
|
description: 'Jury evaluation assignment strategy',
|
||||||
|
isValid: !!evalStage,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Awards',
|
||||||
|
description: `${state.tracks.filter((t) => t.kind === 'AWARD').length} award tracks`,
|
||||||
|
isValid: true, // Awards are optional
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Live Finals',
|
||||||
|
description: 'Voting, cohorts, and reveal settings',
|
||||||
|
isValid: !!liveStage,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Notifications',
|
||||||
|
description: 'Event notifications and override governance',
|
||||||
|
isValid: true, // Always valid
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Review & Publish',
|
||||||
|
description: 'Validation summary and publish controls',
|
||||||
|
isValid: allValid,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Link href="/admin/rounds/pipelines">
|
||||||
|
<Button variant="ghost" size="icon">
|
||||||
|
<ArrowLeft className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-xl font-bold">Create Pipeline</h1>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Configure the full pipeline structure for project evaluation
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
disabled={isSaving || !allValid}
|
||||||
|
onClick={() => handleSave(false)}
|
||||||
|
>
|
||||||
|
{isSaving ? <Loader2 className="h-4 w-4 mr-2 animate-spin" /> : <Save className="h-4 w-4 mr-2" />}
|
||||||
|
Save Draft
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
disabled={isSaving || !allValid}
|
||||||
|
onClick={() => handleSave(true)}
|
||||||
|
>
|
||||||
|
{isSaving ? <Loader2 className="h-4 w-4 mr-2 animate-spin" /> : <Rocket className="h-4 w-4 mr-2" />}
|
||||||
|
Save & Publish
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Wizard Sections */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
{/* 0: Basics */}
|
||||||
|
<WizardSection
|
||||||
|
stepNumber={1}
|
||||||
|
title={sections[0].title}
|
||||||
|
description={sections[0].description}
|
||||||
|
isOpen={openSection === 0}
|
||||||
|
onToggle={() => setOpenSection(openSection === 0 ? -1 : 0)}
|
||||||
|
isValid={sections[0].isValid}
|
||||||
|
>
|
||||||
|
<BasicsSection state={state} onChange={updateState} />
|
||||||
|
</WizardSection>
|
||||||
|
|
||||||
|
{/* 1: Intake */}
|
||||||
|
<WizardSection
|
||||||
|
stepNumber={2}
|
||||||
|
title={sections[1].title}
|
||||||
|
description={sections[1].description}
|
||||||
|
isOpen={openSection === 1}
|
||||||
|
onToggle={() => setOpenSection(openSection === 1 ? -1 : 1)}
|
||||||
|
isValid={sections[1].isValid}
|
||||||
|
>
|
||||||
|
<IntakeSection
|
||||||
|
config={intakeConfig}
|
||||||
|
onChange={(c) =>
|
||||||
|
updateStageConfig('INTAKE', c as unknown as Record<string, unknown>)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</WizardSection>
|
||||||
|
|
||||||
|
{/* 2: Main Track Stages */}
|
||||||
|
<WizardSection
|
||||||
|
stepNumber={3}
|
||||||
|
title={sections[2].title}
|
||||||
|
description={sections[2].description}
|
||||||
|
isOpen={openSection === 2}
|
||||||
|
onToggle={() => setOpenSection(openSection === 2 ? -1 : 2)}
|
||||||
|
isValid={sections[2].isValid}
|
||||||
|
>
|
||||||
|
<MainTrackSection
|
||||||
|
stages={mainTrack?.stages ?? []}
|
||||||
|
onChange={updateMainTrackStages}
|
||||||
|
/>
|
||||||
|
</WizardSection>
|
||||||
|
|
||||||
|
{/* 3: Filtering */}
|
||||||
|
<WizardSection
|
||||||
|
stepNumber={4}
|
||||||
|
title={sections[3].title}
|
||||||
|
description={sections[3].description}
|
||||||
|
isOpen={openSection === 3}
|
||||||
|
onToggle={() => setOpenSection(openSection === 3 ? -1 : 3)}
|
||||||
|
isValid={sections[3].isValid}
|
||||||
|
>
|
||||||
|
<FilteringSection
|
||||||
|
config={filterConfig}
|
||||||
|
onChange={(c) =>
|
||||||
|
updateStageConfig('FILTER', c as unknown as Record<string, unknown>)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</WizardSection>
|
||||||
|
|
||||||
|
{/* 4: Assignment */}
|
||||||
|
<WizardSection
|
||||||
|
stepNumber={5}
|
||||||
|
title={sections[4].title}
|
||||||
|
description={sections[4].description}
|
||||||
|
isOpen={openSection === 4}
|
||||||
|
onToggle={() => setOpenSection(openSection === 4 ? -1 : 4)}
|
||||||
|
isValid={sections[4].isValid}
|
||||||
|
>
|
||||||
|
<AssignmentSection
|
||||||
|
config={evalConfig}
|
||||||
|
onChange={(c) =>
|
||||||
|
updateStageConfig('EVALUATION', c as unknown as Record<string, unknown>)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</WizardSection>
|
||||||
|
|
||||||
|
{/* 5: Awards */}
|
||||||
|
<WizardSection
|
||||||
|
stepNumber={6}
|
||||||
|
title={sections[5].title}
|
||||||
|
description={sections[5].description}
|
||||||
|
isOpen={openSection === 5}
|
||||||
|
onToggle={() => setOpenSection(openSection === 5 ? -1 : 5)}
|
||||||
|
isValid={sections[5].isValid}
|
||||||
|
>
|
||||||
|
<AwardsSection tracks={state.tracks} onChange={(tracks) => updateState({ tracks })} />
|
||||||
|
</WizardSection>
|
||||||
|
|
||||||
|
{/* 6: Live Finals */}
|
||||||
|
<WizardSection
|
||||||
|
stepNumber={7}
|
||||||
|
title={sections[6].title}
|
||||||
|
description={sections[6].description}
|
||||||
|
isOpen={openSection === 6}
|
||||||
|
onToggle={() => setOpenSection(openSection === 6 ? -1 : 6)}
|
||||||
|
isValid={sections[6].isValid}
|
||||||
|
>
|
||||||
|
<LiveFinalsSection
|
||||||
|
config={liveConfig}
|
||||||
|
onChange={(c) =>
|
||||||
|
updateStageConfig('LIVE_FINAL', c as unknown as Record<string, unknown>)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</WizardSection>
|
||||||
|
|
||||||
|
{/* 7: Notifications */}
|
||||||
|
<WizardSection
|
||||||
|
stepNumber={8}
|
||||||
|
title={sections[7].title}
|
||||||
|
description={sections[7].description}
|
||||||
|
isOpen={openSection === 7}
|
||||||
|
onToggle={() => setOpenSection(openSection === 7 ? -1 : 7)}
|
||||||
|
isValid={sections[7].isValid}
|
||||||
|
>
|
||||||
|
<NotificationsSection
|
||||||
|
config={state.notificationConfig}
|
||||||
|
onChange={(notificationConfig) => updateState({ notificationConfig })}
|
||||||
|
overridePolicy={state.overridePolicy}
|
||||||
|
onOverridePolicyChange={(overridePolicy) => updateState({ overridePolicy })}
|
||||||
|
/>
|
||||||
|
</WizardSection>
|
||||||
|
|
||||||
|
{/* 8: Review */}
|
||||||
|
<WizardSection
|
||||||
|
stepNumber={9}
|
||||||
|
title={sections[8].title}
|
||||||
|
description={sections[8].description}
|
||||||
|
isOpen={openSection === 8}
|
||||||
|
onToggle={() => setOpenSection(openSection === 8 ? -1 : 8)}
|
||||||
|
isValid={sections[8].isValid}
|
||||||
|
>
|
||||||
|
<ReviewSection state={state} />
|
||||||
|
</WizardSection>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue