Replace manual roll calls with a 10-second video. VisioMark identifies every face, marks attendance, and notifies the teacher — all before the class settles down.
Features · Architecture · Tech Stack · Getting Started · Configuration · Contributing · License
VisioMark is a production-grade EdTech attendance platform. A class representative takes a picture or records a 10-second video of the room; the system extracts faces, generates 512-dimensional embeddings via ArcFace, matches them against enrolled students in a Qdrant vector database, and pushes live results back to the teacher's app via SSE — all asynchronously, without blocking the API.
It is designed for institutions that need a tamper-resistant, mobile-first attendance workflow at scale.
- Asynchronous AI pipeline — Large media (up to 50 MB) is uploaded directly to cloud storage via SAS/presigned URLs, keeping FastAPI stateless throughout.
- ONNX inference — InsightFace models (RetinaFace detector + ArcFace ResNet50 embedder) run via ONNX Runtime for stable, dependency-light 512D embedding generation.
- Vector identity matching — Qdrant cosine similarity search at threshold
0.65resolves identities against pre-enrolled student embeddings in milliseconds. - Real-time updates — SSE pushes session completion events to the Flutter app; optimistic UI keeps interactions instant without polling.
- Dispute engine — 8-hour dispute SLA with extended media retention, in-app evidence review, and teacher-side override workflows.
Click to Expand
Class Rep records 10s video (back camera enforced)
│
▼
VisioMark Mobile App
│ presigned URL upload
▼
Cloud Storage (Azure Blob / S3-compatible / MinIO)
│ storage event
▼
FastAPI ──► Redis Queue
│
▼
Celery GPU Worker
├── Extract frames (video_utils)
├── RetinaFace (face detection)
├── ArcFace ResNet50 ONNX (512D embeddings)
└── Qdrant cosine match (threshold 0.45)
│
▼
PostgreSQL (bulk insert attendance records)
│
▼
SSE webhook ──► Teacher App UI
| Layer | Technology |
|---|---|
| Mobile app | Flutter, Riverpod, fl_chart, GoRouter |
| API gateway | FastAPI, Pydantic v2, SQLAlchemy 2.0 |
| Async workers | Celery, Redis |
| Transactional DB | PostgreSQL |
| Vector search | Qdrant Cloud |
| Object storage | MinIO (local), Azure Blob / S3-compatible (cloud) |
| ML inference | ONNX Runtime — RetinaFace + ArcFace ResNet50 |
| Auth | Bcrypt + pepper hashing, OTP (email & SMS) |
VisioMark/
├── backend_services/
│ ├── api/
│ │ ├── main.py # FastAPI app entry point
│ │ ├── dependencies.py # Shared DI (DB sessions, auth)
│ │ ├── routers/ # auth, attendance, classroom,
│ │ │ # enrollment, analytics, webhooks
│ │ └── schemas/ # Pydantic request/response models
│ ├── core/
│ │ ├── config.py # Settings (pydantic-settings)
│ │ ├── azure_blob_handler.py # SAS URL generation
│ │ └── logger.py
│ ├── db/
│ │ ├── models.py # SQLAlchemy ORM models
│ │ ├── postgres_client.py # Async engine + session factory
│ │ └── qdrant_client.py # Qdrant collection helpers
│ ├── worker/
│ │ ├── celery_app.py # Celery broker/backend config
│ │ ├── tasks.py # process_session task
│ │ ├── video_utils.py # Frame extraction (OpenCV)
│ │ ├── ai_pipeline/
│ │ │ ├── insight_engine.py # RetinaFace + ArcFace wrapper
│ │ │ └── tracker.py # Multi-face dedup across frames
│ │ └── weights/
│ │ └── check_weights.py # Auto-download ONNX artifacts
│ ├── requirements.txt # API dependencies
│ ├── requirements-worker.txt # Worker dependencies (includes ONNX RT)
│ ├── docker-compose.yml
│ ├── Dockerfile.api
│ ├── Dockerfile.worker
│ └── .env.example
└── mobile_app/
├── lib/
│ ├── core/ # API client, router, theme
│ ├── providers/ # Riverpod state providers
│ └── screens/ # auth, attendance, classroom, main
├── pubspec.yaml
└── test/
| Requirement | Minimum version | Notes |
|---|---|---|
| Docker | 24.0 | With Docker Compose v2 (docker compose) |
| Docker BuildKit | enabled | Set DOCKER_BUILDKIT=1 |
| Flutter SDK | 3.19 | For mobile app only |
| GPU (optional) | CUDA 11.8 | CPU inference works; GPU recommended for production |
No local Python install required. All backend services run inside Docker containers.
git clone https://github.com/<your-org>/visiomark.git
cd visiomark/backend_services
cp .env.example .env
# Edit .env — see Configuration section belowdocker compose up -d --buildThis starts: api, worker, postgres, redis, qdrant, and minio.
First boot takes ~3 minutes as the worker downloads ONNX model weights (~350 MB).
# Follow logs
docker compose logs -f api worker
# Check all containers are healthy
docker compose ps| Service | URL |
|---|---|
| API (Swagger UI) | http://localhost:8000/docs |
| MinIO Console | http://localhost:9001 |
| Qdrant Dashboard | http://localhost:6333/dashboard |
cd ../mobile_app
flutter pub get
# Update lib/core/config.dart with your local API IP
# e.g. const apiBase = 'http://192.168.1.x:8000';
flutter runCopy .env.example to .env and fill in each variable. All required variables must be set before docker compose up.
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
✅ | PostgreSQL connection string (Use asyncpg format) |
REDIS_URL |
✅ | Redis broker URL |
QDRANT_URL |
✅ | Qdrant Cloud HTTPS endpoint |
QDRANT_API_KEY |
✅ | Secret API Key for Qdrant Cloud |
QDRANT_COLLECTION |
— | Collection name for vector embeddings |
AZURE_STORAGE_CONNECTION_STRING |
✅ | Connection string for Azure Blob Storage |
AZURE_CONTAINER_NAME |
— | Container name inside Azure Blob |
JWT_SECRET |
✅ | Secret key for JWT signing (min 32 chars) |
JWT_ALGORITHM |
— | Algorithm used for JWT encoding |
JWT_EXPIRE_MINUTES |
— | Lifespan of an access token |
PASSWORD_PEPPER |
✅ | Global bcrypt pepper for password hashing |
WORKER_CALLBACK_SECRET |
✅ | Secret to authenticate internal Celery webhooks |
API_INTERNAL_URL |
— | Internal URL for Celery to reach FastAPI |
- Passwords hashed with bcrypt (per-user salt) plus a global pepper stored outside the database.
- All endpoints are JWT-protected; tokens are short-lived to minimize exposure windows.
- Media upload uses presigned/SAS URLs — the API never proxies binary data.
Contributions are welcome! Please read the guidelines before opening a PR.
- Fork the repository and create a feature branch:
git checkout -b feat/your-feature - Follow the existing code style (ruff for Python,
flutter formatfor Dart). - Write or update tests for any changed behaviour.
- Open a pull request against
mainwith a clear description of the change.
For large changes, open an issue first to discuss the approach.
Reporting bugs: Use the GitHub Issues tracker. Include your OS, Docker version, and the full stack trace.
This project is licensed under the MIT License.