Consolidated round management, AI filtering enhancements, MinIO storage restructure
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m45s

- Fix STAGE_ACTIVE bug in assignment router (now ROUND_ACTIVE)
- Add evaluation form CRUD (getForm + upsertForm endpoints)
- Add advanceProjects mutation for manual project advancement
- Rewrite round detail page: 7-tab consolidated interface
- Add filtering rules UI with full CRUD (field-based, document check, AI screening)
- Add pageCount field to ProjectFile for document page limit filtering
- Enhance AI filtering: per-file page limits, category/region-aware guidelines
- Restructure MinIO paths: {ProjectName}/{RoundName}/{timestamp}-{file}
- Update dashboard and pool page links from /admin/competitions to /admin/rounds

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-16 09:20:02 +01:00
parent 845554fdb8
commit 8e5fc18da6
14 changed files with 2606 additions and 303 deletions

View File

@@ -149,20 +149,27 @@ export const fileRouter = router({
})
}
let isLate = false
if (input.roundId) {
const stage = await ctx.prisma.round.findUnique({
where: { id: input.roundId },
select: { windowCloseAt: true },
})
// Fetch project title and optional round name for storage path
const [project, roundInfo] = await Promise.all([
ctx.prisma.project.findUniqueOrThrow({
where: { id: input.projectId },
select: { title: true },
}),
input.roundId
? ctx.prisma.round.findUnique({
where: { id: input.roundId },
select: { name: true, windowCloseAt: true },
})
: null,
])
if (stage?.windowCloseAt) {
isLate = new Date() > stage.windowCloseAt
}
let isLate = false
if (roundInfo?.windowCloseAt) {
isLate = new Date() > roundInfo.windowCloseAt
}
const bucket = BUCKET_NAME
const objectKey = generateObjectKey(input.projectId, input.fileName)
const objectKey = generateObjectKey(project.title, input.fileName, roundInfo?.name)
const uploadUrl = await getPresignedUrl(bucket, objectKey, 'PUT', 3600) // 1 hour
@@ -1122,8 +1129,20 @@ export const fileRouter = router({
else if (input.mimeType.includes('presentation') || input.mimeType.includes('powerpoint'))
fileType = 'PRESENTATION'
// Fetch project title and window name for storage path
const [project, submissionWindow] = await Promise.all([
ctx.prisma.project.findUniqueOrThrow({
where: { id: input.projectId },
select: { title: true },
}),
ctx.prisma.submissionWindow.findUniqueOrThrow({
where: { id: input.submissionWindowId },
select: { name: true },
}),
])
const bucket = BUCKET_NAME
const objectKey = generateObjectKey(input.projectId, input.fileName)
const objectKey = generateObjectKey(project.title, input.fileName, submissionWindow.name)
const uploadUrl = await getPresignedUrl(bucket, objectKey, 'PUT', 3600)
// Remove any existing file for this project+requirement combo (replace)