❯ AI · Platform · 2025
UINLP
A full-stack platform for collecting, distributing, and managing multi-modal annotation tasks on AWS.
The problem
Annotators work in one tool, dataset owners in another, and the artifacts move between them by hand. Text, image, audio, and video tasks each want different fields, so a fixed form cannot work. UINLP exists to make collection, distribution, and verification one flow instead of a chain of handoffs.
The approach
A serverless AWS stack: FastAPI on Lambda via Mangum behind API Gateway, with Cognito OAuth (authorization code plus PKCE) validated against the JWKS endpoint. Datasets land in S3 through presigned URLs, metadata lives in DynamoDB, and a batch Lambda unzips uploads and splits files into per-modality chunks before flagging the dataset complete.
Architecture
- Next.js Admin Dashboard
- Flutter Annotator App
- FastAPI on Lambda
- Cognito OAuth
- DynamoDB
- Batch Lambda + S3
FLOWS
- Next.js Admin DashboardRESTFastAPI on Lambda
- Flutter Annotator Apppresigned URLsFastAPI on Lambda
- FastAPI on LambdaJWKS validationCognito OAuth
- FastAPI on LambdaCRUDDynamoDB
- FastAPI on Lambdadataset ingestBatch Lambda + S3
Three DynamoDB tables and three S3 buckets keep the model small: tables for datasets, assets, and publications; buckets for raw uploads, chunked assets, and results. The Flutter annotator is offline-first, renders fields dynamically per task type, and uploads ZIP-packed results back through presigned URLs. Annotators acknowledge work and admins verify it, so nothing reaches a dataset silently. The Next.js admin dashboard manages datasets and publication with soft-delete and verification. Shared Pydantic models and repositories live in an internal Python library so the API and the batch processor never drift.
Key decisions
Presigned URLs over proxying files
S3 moves the bytes; Lambda only ever handles metadata.
Offline-first annotator
Field work does not stall on connectivity, and results still land through the same URL path.
One internal library for models
The API and the batch processor parse the same shapes, which removes a whole class of integration bugs.
Terraform for all of it
Three tables, three buckets, Cognito, gateway, ECR, and Route 53 in one plan.
What I'd do differently
The batch processor grew from a single extraction script. I would design the chunk-splitting pipeline as a resumable job from the start, because a half-extracted upload is worse than a failed one when it gets retried.