[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-79474":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":15,"stars7d":17,"stars30d":18,"stars90d":16,"forks30d":16,"starsTrendScore":19,"compositeScore":20,"rankGlobal":10,"rankLanguage":10,"license":21,"archived":22,"fork":22,"defaultBranch":23,"hasWiki":22,"hasPages":22,"topics":24,"createdAt":10,"pushedAt":10,"updatedAt":29,"readmeContent":30,"aiSummary":31,"trendingCount":16,"starSnapshotCount":16,"syncStatus":32,"lastSyncTime":33,"discoverSource":34},79474,"zepiris","zepto-labs\u002Fzepiris","zepto-labs","No OTPs. No registers. No buddy punching. Just a selfie. ZepIris is Zepto's purpose-built face authentication platform - engineered for scale, tuned for budget phones.","https:\u002F\u002Fblog.zeptonow.com\u002Fzepiris-reimagining-scalable-face-authentication-for-attendance-at-zepto-040da77d8231",null,"Python",333,82,3,4,0,16,277,12,5.76,"Other",false,"main",[25,26,27,28],"computer-vision","data-science","open-source","vector-search","2026-06-12 02:03:54","# ZepIris\n\n[![Python 3.10+](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fpython-3.10%2B-blue)](https:\u002F\u002Fwww.python.org\u002F)\n[![FastAPI](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FFastAPI-0.127%2B-green)](https:\u002F\u002Ffastapi.tiangolo.com\u002F)\n[![Poetry](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FPoetry-2.x-blueviolet)](https:\u002F\u002Fpython-poetry.org\u002F)\n[![License: MIT](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FLicense-MIT-yellow.svg)](LICENSE)\n\n## 👁️ ZepIris — Scalable Face Authentication\n\n**No OTPs. No registers. No buddy punching. Just a selfie.**\n\nZepIris is Zepto's purpose-built face authentication platform — open-sourced for teams running identity verification at operational scale.\n\nIt handles the full pipeline: face detection, embedding generation, vector search, and spoof\u002Fblur\u002Fnsfw flagging. Designed to work on budget smartphones, in low light, under high concurrency.\n\nIf you're running attendance or identity workflows at scale and don't want to stitch together multiple vendors, this is it.\n\n**Current version:** v1.0.0. URL paths use `\u002Fv1\u002F...` for the HTTP API; OpenAPI `info.version` and the Python package follow semver (`pyproject.toml` \u002F `zepiris.version`).\n\n---\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Features](#features)\n- [Architecture](#architecture)\n- [Quick Start](#quick-start)\n- [API Endpoints](#api-endpoints)\n- [Database Schema](#database-schema)\n- [Configuration](#configuration)\n- [Development](#development)\n- [Testing](#testing)\n- [Troubleshooting](#troubleshooting)\n- [Documentation](#documentation)\n- [License](#license)\n- [Citation](#citation)\n- [Acknowledgments](#acknowledgments)\n- [Support & Community](#support--community)\n\n---\n\n## Overview\n\nZepIris simplifies face recognition and verification workflows by providing:\n\n- **Pre-integrated face embeddings** using AuraFace-v1 OR InsightFace's buffalo_l model, both are 512-dimensional representations, based on the flag ML_SERVICE_FACE_MODEL set in .env\n- **1-to-N face vector search** via Milvus for fast COSINE similarity matching\n- **Automated content safety checks**: nsfw detection, anti-spoofing, blur detection — all via a dedicated ML inference service\n- **Multi-tenant support** — per-tenant face enrollment and search with isolated namespaces\n- **Full CRUD operations** — insert, upsert, delete, and get for face records\n- **Production-ready microservice architecture** with independent scaling for ML inference\n- **S3-compatible image storage** via MinIO\n- **REST API** with OpenAPI\u002FSwagger auto-documentation and `requestId` traceability\n\n### Use Cases\n\n- **Attendance Tracking** — Enroll employee faces, query against live camera feeds\n- **Onboarding Workflows** — Identity verification with liveness detection\n- **Face-Based Access Control** — 1-to-N face matching with content safety validation\n- **Quality Assurance** — Automatic detection of low-quality, spoofed, or unsafe images\n\n---\n\n## Features\n\n\n| Capability                    | Description                                                                              |\n| ----------------------------- | ---------------------------------------------------------------------------------------- |\n| **Face Embedding**            | Extract 512-dimensional Normalized embeddings using AuraFace-v1 OR InsightFace's buffalo_l             |\n| **1-to-N Face Search**        | Query vectors against Milvus for fast COSINE similarity matching                         |\n| **Full CRUD API**             | Insert, upsert, delete, and retrieve face records with multi-tenant isolation            |\n| **Content Safety (ML)**       | NSFW, spoof\u002Fdeepfake, and blur detection via dedicated ML inference microservice       |\n| **Multi-Tenant**              | Per-tenant face enrollment and search with isolated namespaces                           |\n| **Microservice Architecture** | Separate ML inference service (port 8001) scales independently from main API (port 8000) |\n| **Image Storage**             | S3-compatible MinIO integration for persistent image archival                            |\n| **REST API**                  | FastAPI with OpenAPI\u002FSwagger docs, `requestId` on every response                         |\n| **Docker Ready**              | Multi-stage Dockerfiles for both services + Docker Compose with all dependencies         |\n| **Configurable Thresholds**   | Fine-tune quality checks (blur sensitivity, spoof threshold, NSFW confidence)          |\n\n\n---\n\n## Architecture\n\nZepIris consists of **two independent FastAPI microservices** that communicate via HTTP:\n\n```\n┌─────────────────────────────────────────────────┐\n│  Client \u002F Application                           │\n└──────────────┬──────────────────────────────────┘\n               │ (REST API)\n      ┌────────▼──────────────────────────────┐\n      │  Main API (port 8000)                 │\n      │  ├─ POST \u002Fv1\u002Ffaces\u002Fsearch             │\n      │  ├─ POST \u002Fv1\u002Ffaces\u002Finsert             │\n      │  ├─ POST \u002Fv1\u002Ffaces\u002Fupsert             │\n      │  ├─ DELETE \u002Fv1\u002Ffaces\u002Fdelete            │\n      │  ├─ GET  \u002Fv1\u002Ffaces\u002Fget\u002F{face_id}      │\n      │  ├─ GET  \u002Fhealthz                     │\n      │  └─ GET  \u002Freadyz                      │\n      └───┬────────────────┬──────────────────┘\n          │                │\n       ┌──▼──┐          ┌──▼─────┐\n       │MinIO│          │ Milvus │\n       │ S3  │          │ Vector │\n       │Store│          │ Store  │\n       └─────┘          └────────┘\n                           │\n      ┌────────────────────▼───────────────────┐\n      │ ML Inference (port 8001)                │\n      │ ├─ POST \u002Fv1\u002Fembed   (Face Embedding)    │\n      │ ├─ POST \u002Fv1\u002FNSFW  (NSFW Detection)  │\n      │ ├─ POST \u002Fv1\u002Fspoof   (Spoof Detection)   │\n      │ ├─ POST \u002Fv1\u002Fblur    (Blur Detection)     │\n      │ └─ POST \u002Fv1\u002Fassess  (Combined IQA)      │\n      └─────────────────────────────────────────┘\n```\n\n### Main API Service (Port 8000)\n\n**Responsibilities:**\n\n- Handle user-facing CRUD and search endpoints under `\u002Fv1\u002Ffaces\u002F`\n- Manage image uploads and storage via MinIO\n- Coordinate with ML inference service for IQA and embedding extraction\n- Index face embeddings in Milvus with multi-tenant support\n- Return structured responses with `requestId` for traceability\n\n**Dependencies:**\n\n- FastAPI, Uvicorn, Pydantic\n- Milvus vector database (with Etcd for metadata)\n- MinIO (S3-compatible object storage)\n- HTTPx (for ML service communication)\n\n### ML Inference Service (Port 8001)\n\n**Responsibilities:**\n\n- Run independent, parallelizable ML workloads\n- Maintain 4 PyTorch models in memory:\n  - **Face Embedding** — AuraFace-v1 OR InsightFace's buffalo_l (640×640 input → 512-d output) based on the flag ML_SERVICE_FACE_MODEL set in .env\n  - **NSFW Detection** — MobileNetV2 (2-class classifier)\n  - **Spoof Detection** — MobileNetV3-Large (liveness detection)\n  - **Blur Detection** — ResNet18 (image quality assessment)\n- Expose HTTP endpoints for individual or combined inference\n- Combined IQA runs all 3 quality checks in parallel via `ThreadPoolExecutor`\n\n**Benefits:**\n\n- Scale independently — run on GPU hardware if needed\n- Reuse models across requests — no repeated loading\n- Parallel execution — run all 3 quality checks simultaneously\n\n---\n\n## Quick Start\n\n### Prerequisites\n\n- **Python 3.10–3.14** (tested on 3.10–3.14)\n- **Poetry 2.x** for dependency management ([install here](https:\u002F\u002Fpython-poetry.org\u002Fdocs\u002F#installation))\n- **Docker & Docker Compose** (v1.29+; recommended for all-in-one setup)\n- **4GB+ RAM** for Milvus, **10GB+ free disk space**\n\nCheck your versions:\n\n```bash\npython3 --version\npoetry --version\n```\n\n### Docker Compose (One Command)\n\nFor a fully containerized local setup:\n\n```bash\n# Clone and navigate\ngit clone \u003Crepository-url>\ncd zepiris\n\n# Start all services (Milvus, MinIO, Etcd, API, ML inference)\ndocker-compose up -d\n\n# Verify health\ncurl http:\u002F\u002Flocalhost:8000\u002Fhealthz\n\n# Open API documentation\n# Visit: http:\u002F\u002Flocalhost:8000\u002Fdocs\n\n# Stop services\ndocker-compose down\n```\n\nThe Compose file sets `name: zepiris`, so images are tagged `zepiris-api` and `zepiris-ml-inference` regardless of clone directory name.\n\nThis starts:\n\n- Main API (port 8000)\n- ML inference (port 8001)\n- Milvus (port 19530)\n- MinIO (host port 9002 → container 9000, console on 9001)\n- Etcd (metadata store for Milvus)\n\n---\n\n## API Endpoints\n\n### Interactive Documentation\n\nOnce running, visit these URLs in your browser:\n\n- **Main API**: [http:\u002F\u002Flocalhost:8000\u002Fdocs](http:\u002F\u002Flocalhost:8000\u002Fdocs) (Swagger UI)\n- **ML Inference**: [http:\u002F\u002Flocalhost:8001\u002Fdocs](http:\u002F\u002Flocalhost:8001\u002Fdocs) (Swagger UI)\n\n### Main API (`\u002Fv1\u002Ffaces\u002F`)\n\n#### Health & Readiness\n\n```bash\ncurl http:\u002F\u002Flocalhost:8000\u002Fhealthz\n# {\"status\": \"ok\"}\n\ncurl http:\u002F\u002Flocalhost:8000\u002Freadyz\n# {\"status\": \"ok\"}\n```\n\n#### Insert a Face\n\nRegister a new face with an ID and tenant:\n\n```bash\ncurl -X POST http:\u002F\u002Flocalhost:8000\u002Fv1\u002Ffaces\u002Finsert \\\n  -F \"id=employee_001\" \\\n  -F \"tenant=acme_corp\" \\\n  -F \"file=@face.jpg\"\n```\n\n**Response:**\n\n```json\n{\n  \"requestId\": \"a1b2c3d4-e5f6-...\",\n  \"imageQualityAssessment\": {\n    \"passed\": true,\n    \"nsfw\": {\"is_safe\": true, \"probability\": 0.02},\n    \"spoof\": {\"is_spoof\": false, \"probability\": 0.05},\n    \"blur\": {\"is_sharp\": true, \"probability\": 0.10}\n  },\n  \"userOperationResult\": {\n    \"operation\": \"INSERT\",\n    \"status\": \"success\"\n  }\n}\n```\n\n**Parameters:**\n\n- `id` (required) — unique face identifier\n- `tenant` (required) — tenant namespace for isolation\n- `file` (required) — JPEG\u002FPNG image file (max 5 MB)\n- Returns `409 Conflict` if a face with the same `id` already exists\n- Returns `422 Unprocessable Entity` if IQA fails or no face detected\n\n#### Search Similar Faces\n\nUpload an image and find matching faces in the database:\n\n```bash\ncurl -X POST \"http:\u002F\u002Flocalhost:8000\u002Fv1\u002Ffaces\u002Fsearch?top_k=5\" \\\n  -F \"id=query_001\" \\\n  -F \"tenant=acme_corp\" \\\n  -F \"file=@query_face.jpg\"\n```\n\n**Response:**\n\n```json\n{\n  \"requestId\": \"d4e5f6a7-b8c9-...\",\n  \"imageQualityAssessment\": {\n    \"passed\": true,\n    \"nsfw\": {\"is_safe\": true, \"probability\": 0.01},\n    \"spoof\": {\"is_spoof\": false, \"probability\": 0.03},\n    \"blur\": {\"is_sharp\": true, \"probability\": 0.08}\n  },\n  \"searchResult\": {\n    \"matches\": [\n      {\"id\": \"employee_001\", \"score\": 0.92}\n    ]\n  }\n}\n```\n\n**Parameters:**\n\n- `id` (required, form) — identifier for this query\n- `tenant` (required, form) — tenant namespace\n- `file` (required, form) — JPEG\u002FPNG image file\n- `top_k` (optional, query, default: 5) — number of matches to return\n- `threshold` (optional, query) — minimum similarity score; defaults to `ZEPIRIS_MILVUS_SEARCH_THRESHOLD`\n- Returns `200 OK` with empty `matches` array if IQA fails or no face detected\n\n#### Upsert a Face\n\nInsert or update a face record:\n\n```bash\ncurl -X POST http:\u002F\u002Flocalhost:8000\u002Fv1\u002Ffaces\u002Fupsert \\\n  -F \"id=employee_001\" \\\n  -F \"tenant=acme_corp\" \\\n  -F \"file=@updated_face.jpg\"\n```\n\n**Response:** Same structure as Insert, with `\"operation\": \"UPSERT\"`.\n\n#### Delete a Face\n\nRemove a face record by ID:\n\n```bash\ncurl -X DELETE \"http:\u002F\u002Flocalhost:8000\u002Fv1\u002Ffaces\u002Fdelete?id=employee_001\"\n```\n\n**Response:**\n\n```json\n{\n  \"requestId\": \"f6a7b8c9-d0e1-...\",\n  \"userOperationResult\": {\n    \"operation\": \"DELETE\",\n    \"status\": \"success\"\n  }\n}\n```\n\n#### Get Face Metadata\n\nRetrieve a face record by ID:\n\n```bash\ncurl http:\u002F\u002Flocalhost:8000\u002Fv1\u002Ffaces\u002Fget\u002Femployee_001\n```\n\n**Response:**\n\n```json\n{\n  \"face_id\": \"employee_001\",\n  \"tenant\": \"acme_corp\",\n  \"object_key\": \"faces\u002Femployee_001\"\n}\n```\n\n### ML Inference API (`\u002Fv1\u002F`)\n\nAll POST endpoints accept JSON bodies with base64-encoded images.\n\n#### Base Payload Format\n\n```json\n{\n  \"image_b64\": \"\u003Cbase64-encoded image bytes>\"\n}\n```\n\n**Example:** Encode an image to base64:\n\n```bash\nbase64 -i face.jpg | pbcopy   # macOS\ncat face.jpg | base64         # Linux\n```\n\n#### Health Check\n\n```bash\ncurl http:\u002F\u002Flocalhost:8001\u002Fhealthz\n# {\"status\": \"ok\"}\n```\n\n#### NSFW Detection\n\nCheck if image contains NSFW content:\n\n```bash\ncurl -X POST http:\u002F\u002Flocalhost:8001\u002Fv1\u002Fnsfw \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"image_b64\": \"...\"}'\n```\n\n**Response:**\n\n```json\n{\n  \"is_safe\": true,\n  \"probability\": 0.02\n}\n```\n\n#### Spoof Detection\n\nCheck if face is real or spoofed\u002Fdeepfake:\n\n```bash\ncurl -X POST http:\u002F\u002Flocalhost:8001\u002Fv1\u002Fspoof \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"image_b64\": \"...\"}'\n```\n\n**Response:**\n\n```json\n{\n  \"is_spoof\": false,\n  \"probability\": 0.05\n}\n```\n\n#### Blur Detection\n\nCheck if face image is sharp enough:\n\n```bash\ncurl -X POST http:\u002F\u002Flocalhost:8001\u002Fv1\u002Fblur \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"image_b64\": \"...\"}'\n```\n\n**Response:**\n\n```json\n{\n  \"is_sharp\": true,\n  \"probability\": 0.10\n}\n```\n\n#### Face Embedding\n\nGenerate a 512-dimensional face embedding:\n\n```bash\ncurl -X POST http:\u002F\u002Flocalhost:8001\u002Fv1\u002Fembed \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"image_b64\": \"...\"}'\n```\n\n**Response:**\n\n```json\n{\n  \"face_detected\": true,\n  \"embedding\": [0.123, -0.456, 0.789, \"...\"],\n  \"embedding_dim\": 512\n}\n```\n\n#### Combined Assessment (IQA)\n\nRun all 3 quality checks in parallel:\n\n```bash\ncurl -X POST http:\u002F\u002Flocalhost:8001\u002Fv1\u002Fassess \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"image_b64\": \"...\"}'\n```\n\n**Response:**\n\n```json\n{\n  \"passed\": true,\n  \"nsfw\": {\"is_safe\": true, \"probability\": 0.02},\n  \"spoof\": {\"is_spoof\": false, \"probability\": 0.05},\n  \"blur\": {\"is_sharp\": true, \"probability\": 0.10}\n}\n```\n\n`passed` is `true` when: `nsfw.is_safe AND (NOT spoof.is_spoof) AND blur.is_sharp`.\n\n---\n\n## Database Schema\n\n**Collection:** `zepiris_faces` (Milvus)\n\n\n| Field        | Type              | Purpose               |\n| ------------ | ----------------- | --------------------- |\n| `face_id`    | VARCHAR(128)      | Primary key           |\n| `tenant`     | VARCHAR(256)      | Multi-tenancy support |\n| `object_key` | VARCHAR(512)      | MinIO image path      |\n| `embedding`  | FLOAT_VECTOR(512) | Face embedding vector |\n\n\n**Index:** FLAT with COSINE similarity metric.\n\n---\n\n## Configuration\n\n### Environment Variables\n\nCopy `.env.example` to `.env` and customize. All settings use environment variable prefixes.\n\n#### Main API Service (`ZEPIRIS_`*)\n\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `ZEPIRIS_API_TITLE` | `ZepIris` | API title (shown in docs) |\n| `ZEPIRIS_API_VERSION` | `1.0.0` | API version (OpenAPI `info.version`) |\n| `ZEPIRIS_API_HOST` | `0.0.0.0` | Bind host |\n| `ZEPIRIS_API_PORT` | `8000` | Bind port |\n| `ZEPIRIS_MINIO_ENDPOINT` | `localhost:9002` | MinIO S3 host:port |\n| `ZEPIRIS_MINIO_ACCESS_KEY` | `minioadmin` | MinIO access key |\n| `ZEPIRIS_MINIO_SECRET_KEY` | `minioadmin` | MinIO secret key |\n| `ZEPIRIS_MINIO_BUCKET` | `zepiris` | S3 bucket name |\n| `ZEPIRIS_MINIO_SECURE` | `false` | Use TLS for MinIO |\n| `ZEPIRIS_MILVUS_HOST` | `localhost` | Milvus vector database host |\n| `ZEPIRIS_MILVUS_PORT` | `19530` | Milvus port |\n| `ZEPIRIS_MILVUS_COLLECTION` | `zepiris_faces` | Milvus collection name |\n| `ZEPIRIS_MILVUS_EMBEDDING_DIM` | `512` | Face embedding dimension |\n| `ZEPIRIS_MILVUS_SEARCH_THRESHOLD` | `0.5` | Default COSINE similarity threshold for search |\n| `ZEPIRIS_ML_INFERENCE_SERVICE_URL` | *(required)* | URL of the ML inference service (e.g. `http:\u002F\u002Flocalhost:8001`) |\n\n> **Note:** `ZEPIRIS_ML_INFERENCE_SERVICE_URL` is **required**. The main API will not start without it. Set it to `http:\u002F\u002Fml-inference:8001` in Docker Compose or `http:\u002F\u002Flocalhost:8001` when running locally.\n\n#### ML Inference Service (`ML_SERVICE_`*)\n\n\n| Variable                             | Default                        | Description                                  |\n| ------------------------------------ | ------------------------------ | -------------------------------------------- |\n| `ML_SERVICE_HOST`                    | `0.0.0.0`                      | Bind host                                    |\n| `ML_SERVICE_PORT`                    | `8001`                         | Bind port                                    |\n| `ML_SERVICE_ML_DEVICE`               | `cpu`                          | Inference device: `cpu`, `cuda:0`, `mps`     |\n| `ML_SERVICE_NSFW_LOCAL_MODEL_PATH` | `\u002Fapp\u002Fmodels\u002Fnsfw_model.pth` | NSFW model file                            |\n| `ML_SERVICE_NSFW_HF_REPO_ID`       | ``                             | HuggingFace repo for NSFW model (optional) |\n| `ML_SERVICE_NSFW_THRESHOLD`        | `0.5`                          | NSFW classification threshold (0–1)        |\n| `ML_SERVICE_SPOOF_LOCAL_MODEL_PATH`  | `\u002Fapp\u002Fmodels\u002Fspoof_model.pth`  | Spoof model file                             |\n| `ML_SERVICE_SPOOF_HF_REPO_ID`        | ``                             | HuggingFace repo for spoof model (optional)  |\n| `ML_SERVICE_SPOOF_THRESHOLD`         | `0.5`                          | Spoof classification threshold (0–1)         |\n| `ML_SERVICE_BLUR_LOCAL_MODEL_PATH`   | `\u002Fapp\u002Fmodels\u002Fblur_model.pth`   | Blur model file                              |\n| `ML_SERVICE_BLUR_HF_REPO_ID`         | ``                             | HuggingFace repo for blur model (optional)   |\n| `ML_SERVICE_BLUR_THRESHOLD`          | `0.5`                          | Blur classification threshold (0–1)          |\n| `ML_SERVICE_FACE_EMBEDDING_DIM`      | `512`                          | Face embedding dimension                     |\n| `ML_SERVICE_FACE_DETECTION_WIDTH`    | `640`                          | Face detection input width                   |\n| `ML_SERVICE_FACE_DETECTION_HEIGHT`   | `640`                          | Face detection input height                  |\n| `ML_SERVICE_FACE_AREA_THRESHOLD`     | `0.01`                         | Minimum face area (fraction of image)        |\n\n\n### Example: GPU Inference\n\nTo enable GPU inference, set:\n\n```bash\nexport ML_SERVICE_ML_DEVICE=cuda:0\npoetry run zepiris-ml-inference-api\n```\n\nOr in `.env`:\n\n```bash\nML_SERVICE_ML_DEVICE=cuda:0\n```\n\nEnsure PyTorch CUDA version matches your GPU driver.\n\n---\n\n## Development\n\n### Project Structure\n\n```\nzepiris\u002F\n├── pyproject.toml              # Project metadata, dependencies\n├── poetry.lock                 # Locked dependency versions\n├── poetry.toml                 # Poetry config (in-project .venv)\n├── .env.example                # Environment variable template\n├── Dockerfile                  # Main service container\n├── ml_inference.Dockerfile     # ML service container\n├── docker-compose.yml          # All services (MinIO, Etcd, Milvus, ML, API)\n│\n├── zepiris\u002F\n│   ├── main.py                 # Main FastAPI app factory + lifespan\n│   ├── config.py               # Pydantic settings (ZEPIRIS_* prefix)\n│   ├── deps.py                 # FastAPI dependency injection\n│   ├── exceptions.py           # Domain exceptions (DuplicateFaceIdError, etc.)\n│   ├── exception_handlers.py   # Error response formatting\n│   │\n│   ├── api\u002Froutes\u002F\n│   │   ├── __init__.py         # build_api_router()\n│   │   ├── face.py             # \u002Fv1\u002Ffaces\u002F search, insert, upsert, delete, get\n│   │   └── health.py           # GET \u002Fhealthz, \u002Freadyz\n│   │\n│   ├── ml_inference\u002F\n│   │   ├── app.py              # ML service FastAPI app + MLServiceSettings\n│   │   ├── routes.py           # ML endpoints (\u002Fv1\u002Fnsfw, \u002Fspoof, \u002Fblur, \u002Fembed, \u002Fassess)\n│   │   ├── deps.py             # ML service dependency injection (503 if model missing)\n│   │   ├── base.py             # Base ModelService + ModelServiceConfig\n│   │   ├── face_embedding.py   # FaceEmbeddingService (AuraFace OR Buffalo_l)\n│   │   ├── nsfw_detection.py   # NSFWDetectionService (MobileNetV2)\n│   │   ├── spoof_detection.py  # SpoofDetectionService (MobileNetV3)\n│   │   ├── blur_detection.py   # BlurDetectionService (ResNet18)\n│   │   ├── image_quality_assessment.py # Combined IQA (ThreadPoolExecutor)\n│   │   └── models\u002F             # PyTorch model definitions\n│   │\n│   ├── services\u002F\n│   │   ├── embedding.py        # FaceEmbeddingProvider (ABC) + MLInferenceEmbeddingService\n│   │   ├── iqa.py              # MLInferenceIQAService → HTTP \u002Fv1\u002Fassess\n│   │   ├── milvus_store.py     # MilvusFaceStore (vector CRUD + search)\n│   │   ├── minio_storage.py    # MinioStorageService (S3 storage)\n│   │   └── ml_client.py        # MLInferenceClient (httpx, sync)\n│   │\n│   └── schemas\u002F\n│       ├── face.py             # SearchResponse, UpsertResponse, DeleteResponse\n│       └── ml_inference.py     # FaceEmbeddingResult, IQA result schemas\n│\n└── models\u002F                     # Pre-trained model weights\n    ├── nsfw_model.pth\n    ├── spoof_model.pth\n    └── blur_model.pth\n```\n\n### Development Setup\n\n```bash\n# Activate virtual environment\nsource .venv\u002Fbin\u002Factivate\n# or use Poetry shell\npoetry shell\n```\n\n### Running with Auto-Reload\n\nFor local development with hot-reloading:\n\n```bash\n# Terminal 1 — ML service with reload\nuvicorn zepiris.ml_inference.app:app --reload --host 0.0.0.0 --port 8001\n\n# Terminal 2 — Main API with reload\nZEPIRIS_ML_INFERENCE_SERVICE_URL=http:\u002F\u002Flocalhost:8001 \\\n  uvicorn zepiris.main:app --reload --host 0.0.0.0 --port 8000\n```\n\n### Adding Dependencies\n\n```bash\n# Add a runtime dependency\npoetry add httpx-oauth\n\n# Add a development-only tool\npoetry add --group dev black ruff pytest\n\n# After changing dependencies, commit both files\ngit add pyproject.toml poetry.lock\ngit commit -m \"chore: add new dependencies\"\n```\n\n---\n\n## Testing\n\nZepIris includes test scripts for both unit and integration testing.\n\n### End-to-End ML Service Test\n\nTests all ML endpoints with real model inference:\n\n```bash\npython scripts\u002Ftest_ml_service.py \\\n  --test-image path\u002Fto\u002Fimage.jpg \\\n  --nsfw-model .\u002Fmodels\u002Fnsfw_model.pth \\\n  --spoof-model .\u002Fmodels\u002Fspoof_model.pth \\\n  --blur-model .\u002Fmodels\u002Fblur_model.pth\n```\n\nOr test against a running service:\n\n```bash\npython scripts\u002Ftest_ml_service.py \\\n  --test-image path\u002Fto\u002Fimage.jpg \\\n  --no-server --port 8001\n```\n\n### Direct Model Testing\n\nLoad and test models in-process without HTTP:\n\n```bash\npython scripts\u002Ftest_models.py \\\n  --test-image path\u002Fto\u002Fimage.jpg \\\n  --nsfw-model .\u002Fmodels\u002Fnsfw_model.pth \\\n  --spoof-model .\u002Fmodels\u002Fspoof_model.pth \\\n  --blur-model .\u002Fmodels\u002Fblur_model.pth\n```\n\n### Manual API Testing\n\nUse `curl`, `httpie`, or Postman to test endpoints. Interactive Swagger docs available at `\u002Fdocs` on both services.\n\n---\n\n## Troubleshooting\n\n### Poetry Installation Issues\n\n**Problem:** `poetry install` fails with \"No file\u002Ffolder found\"\n\n**Solution:** This is expected on first run. Poetry will resolve dependencies. Try again.\n\n**Problem:** Wrong Python version\n\n**Solution:** Point Poetry to the correct interpreter:\n\n```bash\npoetry env use \u002Fpath\u002Fto\u002Fpython3.10\npoetry install\n```\n\n### Service Connection Issues\n\n**Problem:** Main API can't connect to Milvus or MinIO\n\n**Solution:** Verify external services are running:\n\n```bash\n# Check all services\ndocker compose ps\n\n# Or check individually\ncurl http:\u002F\u002Flocalhost:9002\u002Fminio\u002Fhealth\u002Flive  # MinIO (host port 9002)\ncurl http:\u002F\u002Flocalhost:8001\u002Fhealthz             # ML inference\n```\n\n**Problem:** Main API fails to start with \"ML_INFERENCE_SERVICE_URL required\"\n\n**Solution:** Set the required environment variable:\n\n```bash\nexport ZEPIRIS_ML_INFERENCE_SERVICE_URL=http:\u002F\u002Flocalhost:8001\n```\n\n**Problem:** ML service doesn't load models on startup\n\n**Solution:** Check logs for model loading errors:\n\n```bash\ndocker compose logs ml-inference  # if using Docker Compose\n# or\npoetry run zepiris-ml-inference-api       # if running locally\n```\n\nEnsure model files exist at configured paths. Default: `\u002Fapp\u002Fmodels\u002F{nsfw,spoof,blur}_model.pth`. If a model fails to load, that endpoint returns **503 Service Unavailable**.\n\n### GPU Not Detected\n\n**Problem:** ML service ignores `ML_SERVICE_ML_DEVICE=cuda`\n\n**Solution:**\n\n1. Verify PyTorch CUDA version matches your GPU driver:\n\n```bash\npython -c \"import torch; print(torch.cuda.is_available())\"\n```\n\n1. If unavailable, reinstall PyTorch with correct CUDA version:\n\n```bash\npoetry remove torch torchvision\npoetry add torch torchvision --platform linux --python \"^3.10\"\n```\n\n### API Timeouts\n\n**Problem:** Requests hang or timeout to ML service\n\n**Solution:**\n\n- Ensure ML inference service is running and healthy (`GET \u002Fhealthz`)\n- Check if models are still loading (can take 30-60s on first startup)\n- Check network connectivity between services\n- Monitor resource usage (disk space for model downloads, RAM for inference)\n\n---\n\n## Documentation\n\n- **[LOCAL_SETUP_AND_TEST.md](docs\u002FLOCAL_SETUP_AND_TEST.md)** — Step-by-step local testing guide (20-30 min)\n- **[docs\u002FAPI_REFERENCE.md](docs\u002FAPI_REFERENCE.md)** — Complete API endpoint reference\n- **[docs\u002FCONFIGURATION.md](docs\u002FCONFIGURATION.md)** — Environment variables & tuning\n- **[CONTRIBUTING.md](CONTRIBUTING.md)** — Code guidelines & contribution workflow\n- **[.env.example](.env.example)** — Complete environment variable template\n\n### Additional Resources\n\n- [AuraFace Documentation](https:\u002F\u002Fhuggingface.co\u002Ffal\u002FAuraFace-v1) — Face embedding & detection model\n- [InsightFace Documentation](https:\u002F\u002Fgithub.com\u002Fdeepinsight\u002Finsightface) — Wrapper over the face embedding & detection model\n- [Milvus Vector Database](https:\u002F\u002Fmilvus.io\u002Fdocs) — Vector storage & search\n- [MinIO S3 SDK](https:\u002F\u002Fmin.io\u002Fdocs\u002Fminio\u002Fkubernetes\u002Fupstream\u002F) — Object storage\n- [FastAPI Best Practices](https:\u002F\u002Ffastapi.tiangolo.com\u002Fdeployment\u002Fconcepts\u002F) — Web framework\n\n---\n\n## License\n\nThis project is licensed under the **MIT License** — see [LICENSE](LICENSE) file for details.\n\n> The project is released under the [LICENSE](LICENSE) with contribution expectations described in [CONTRIBUTING.md](CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).\n\n---\n\n## Citation\n\nIf you use ZepIris in research or production, please cite:\n\n```bibtex\n@software{zepiris2026,\n  title={ZepIris: Scalable Face Authentication},\n  author={Zepto Data Science Team},\n  year={2026},\n  url={https:\u002F\u002Fgithub.com\u002Fzepto-labs\u002Fzepiris}\n}\n```\n\n---\n\n## Acknowledgments\n\nZepIris stands on the shoulders of excellent open-source projects:\n\n- **[AuraFace](https:\u002F\u002Fhuggingface.co\u002Ffal\u002FAuraFace-v1)** & **[InsightFace](https:\u002F\u002Fgithub.com\u002Fdeepinsight\u002Finsightface)** — State-of-the-art face embedding models\n- **[Milvus](https:\u002F\u002Fmilvus.io\u002F)** — High-performance vector database\n- **[FastAPI](https:\u002F\u002Ffastapi.tiangolo.com\u002F)** — Modern async Python web framework\n- **[PyTorch](https:\u002F\u002Fpytorch.org\u002F)** — Deep learning framework\n- **[MinIO](https:\u002F\u002Fmin.io\u002F)** — S3-compatible object storage\n\n---\n\n## Support & Community\n\n- **Issues:** [GitHub Issues](https:\u002F\u002Fgithub.com\u002Fzepto-labs\u002Fzepiris\u002Fissues)\n- **Discussions:** [GitHub Discussions](https:\u002F\u002Fgithub.com\u002Fzepto-labs\u002Fzepiris\u002Fdiscussions)\n- **Email:** [opensource@zepto.com](mailto:opensource@zepto.com)\n\n---\n\n**Last Updated:** April 2026\n**Status:** v1.0.0\n","ZepIris 是一个专为大规模面部认证设计的平台，旨在通过自拍实现无OTP、无注册、无代打卡的面部识别。其核心功能包括面部检测、嵌入生成、向量搜索以及欺诈\u002F模糊\u002F不适当内容标记，并且针对预算手机进行了优化，在低光照和高并发环境下也能良好运行。技术上，ZepIris 使用Python 3.10+开发，集成了FastAPI框架和Poetry包管理器，支持多租户环境下的全CRUD操作，采用Milvus进行高效的余弦相似度匹配，还提供了自动化的安全检查服务。该平台适用于需要在运营规模下进行身份验证或考勤跟踪的企业，特别是那些希望避免整合多个供应商解决方案的场景。",2,"2026-06-11 03:58:05","CREATED_QUERY"]