{
  "markdown": "# ai-cv-evaluator\n\nBackend service that ingests a candidate CV + project report, evaluates against a job description + study case brief, and returns structured results. Built with Go and Clean Architecture.\n\n## Admin UI & Dashboards\n\n### Production Site & Admin Login\n\n- **Production site**: https://ai-cv-evaluator.web.id\n- **Admin Dashboard**: https://ai-cv-evaluator.web.id/app/dashboard\n- **SSO Login** (Authelia):\n  - Username: `admin`\n  - Password: `admin123`\n\n### Observability Stack — not deployed\n\nGrafana, Prometheus, Loki, Jaeger, OTEL, Promtail, cAdvisor and Mailpit sit behind a compose\n`profiles: [\"observability\"]` gate and are **not started by a production deploy**. They do not fit\nthe host's memory budget. The production URLs these dashboards would live at return nothing today.\nSee [Observability was deprecated in production](knowledge/decisions/observability-was-deprecated-in-production.md).\n\nThey do run locally (see Local Development below) and in production only when started explicitly\nwith `--profile observability`. Dashboards available: HTTP Metrics (RED), Job Queue Metrics, AI\nProvider Metrics (p50/p95/p99/max latency, token usage, request rates), Request Drilldown.\n\n### AI Metrics Features\n\n- **Token Counting**: Accurate LLM token counting using `tiktoken-go` (Go port of OpenAI's\n  tiktoken)\n- **Latency Percentiles**: p50, p95, p99, and max latency tracking for AI provider calls\n- **Provider Metrics**: Request rates and response times by provider (Groq, OpenRouter, OpenAI)\n- **Prometheus Metrics**: `ai_tokens_total`, `ai_requests_total`, `ai_request_duration_seconds`\n\n### Local Development\n\n- **Frontend**: http://localhost:3001 (Vue 3 + Vite with HMR)\n- **Backend API**: http://localhost:8080\n- **Grafana**: http://localhost:3000\n- **Prometheus**: http://localhost:9090\n- **Jaeger**: http://localhost:16686\n- **Redpanda Console**: http://localhost:8090\n\n## Status\n\n- **CI (unit tests, 80% coverage gate, dev Playwright)**\n  [![CI](https://github.com/fairyhunter13/ai-cv-evaluator/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/fairyhunter13/ai-cv-evaluator/actions/workflows/ci.yml)\n- **Coverage (Codecov)**\n  [![codecov](https://codecov.io/gh/fairyhunter13/ai-cv-evaluator/branch/main/graph/badge.svg)](https://codecov.io/gh/fairyhunter13/ai-cv-evaluator)\n- **Security Scans** [![Security\n  Scans](https://github.com/fairyhunter13/ai-cv-evaluator/actions/workflows/security.yml/badge.svg?branch=main)](https://github.com/fairyhunter13/ai-cv-evaluator/actions/workflows/security.yml)\n- **Docker Publish** [![Docker\n  Publish](https://github.com/fairyhunter13/ai-cv-evaluator/actions/workflows/docker-publish.yml/badge.svg)](https://github.com/fairyhunter13/ai-cv-evaluator/actions/workflows/docker-publish.yml)\n- **Secrets Healthcheck** [![Secrets\n  Healthcheck](https://github.com/fairyhunter13/ai-cv-evaluator/actions/workflows/secrets-healthcheck.yml/badge.svg)](https://github.com/fairyhunter13/ai-cv-evaluator/actions/workflows/secrets-healthcheck.yml)\n- **Deploy (strict semantic versioning required)**\n  [![Deploy](https://github.com/fairyhunter13/ai-cv-evaluator/actions/workflows/deploy.yml/badge.svg)](https://github.com/fairyhunter13/ai-cv-evaluator/actions/workflows/deploy.yml)\n\n## 📚 Documentation\n\n| Document | What it covers |\n|---|---|\n| [Architecture](docs/architecture/ARCHITECTURE.md) | System design, layering, the evaluation pipeline |\n| [Frontend development](docs/development/FRONTEND_DEVELOPMENT.md) | Admin frontend setup and workflow |\n| [Operations](docs/operations.md) | Running it in production |\n| [Observability](docs/observability.md) | Metrics, traces, dashboards (opt-in profile, not deployed) |\n| [Data retention](docs/data-retention.md) | What is stored, for how long |\n| [SSO rate limiting](docs/security/SSO_RATE_LIMITING.md) | Auth gate behaviour |\n\n## Quick Start\n\n- Prereqs: Docker, Docker Compose, Go 1.24+, Node.js 18+\n- Set up env for dev (one of):\n  ```bash\n  cp .env.sample .env\n  # or, if available\n  make decrypt-env   # uses secrets/env.sops.yaml -> .env\n  ```\n\n### Development Environment\n```bash\n# Start complete development environment\nmake dev-full\n```\nThis starts:\n- Backend services (PostgreSQL, Redpanda, Qdrant, Tika)\n- **Database migrations run automatically** via Docker Compose dependencies\n- Backend API server (port 8080)\n- Frontend development server with HMR (port 3001)\n- Redpanda Console (port 8090)\n\n### Production Deployment\n```bash\n# Start production environment\ndocker compose -f docker-compose.prod.yml up -d\n```\n- **Database migrations run automatically** before app services start\n- Backend API server\n- Frontend (served by Nginx)\n- Worker containers\n- All supporting services\n\n### API Endpoints\n- `POST /v1/upload` (multipart: `cv`, `project`)\n- `POST /v1/evaluate` (JSON)\n- `GET /v1/result/{id}`\n- `GET /healthz`, `GET /readyz`, `GET /metrics`\n- `GET /openapi.yaml`\n- Admin API: `POST /admin/token`, `GET /admin/api/status`\n\n## API (Contract-first)\nSee `api/openapi.yaml` for the complete schema. Examples:\n\n- POST `/v1/evaluate` request\n  ```json\n  {\n    \"cv_id\": \"...\",\n    \"project_id\": \"...\",\n    \"job_description\": \"...\",\n    \"study_case_brief\": \"...\"\n  }\n  ```\n- Queued response\n  ```json\n  { \"id\": \"456\", \"status\": \"queued\" }\n  ```\n- Completed response\n  ```json\n  {\n    \"id\": \"456\",\n    \"status\": \"completed\",\n    \"result\": {\n      \"cv_match_rate\": 0.82,\n      \"cv_feedback\": \"...\",\n      \"project_score\": 7.5,\n      \"project_feedback\": \"...\",\n      \"overall_summary\": \"...\"\n    }\n  }\n  ```\n\n## Architecture\n- **Split Architecture**: Separate server, worker, and frontend containers for optimal scalability\n- **Server Container**: Handles HTTP requests, file uploads, and job creation (API-only)\n- **Worker Container**: Single optimized worker tuned for free-tier AI providers\n\t- **1 concurrent worker by default** (`CONSUMER_MAX_CONCURRENCY=1`) for Groq/OpenRouter free tiers\n\t- Safe to increase `CONSUMER_MAX_CONCURRENCY` in higher-capacity environments when needed\n\t- Handles all 8 Kafka partitions with dynamic internal scaling when concurrency > 1\n\t- Exactly-once processing with auto-commit offsets\n\t- Push-based delivery for immediate job processing\n\t- Simplified deployment (single worker vs previous 4-worker setup)\n- **Frontend Container**: Vue 3 + Vite admin dashboard with Hot Module Replacement\n- **Queue System**: Redpanda (Kafka-compatible) for reliable message delivery\n  - 8 partitions for parallel processing within single worker\n  - Redpanda Console for monitoring topics, consumer groups, and messages\n  - Modern SPA with Tailwind CSS styling\n  - API communication with backend via HTTP\n  - Development: HMR-enabled dev server (port 3001)\n  - Production: Static files served by Nginx\n- **Clean Architecture** in `internal/` with ports and adapters:\n  - `domain/` entities, errors, ports (Queue, AIClient, TextExtractor)\n  - `usecase/` orchestration services\n  - `adapter/` http, repo (pgx), queue (redpanda), textextractor (Tika), observability, vector (Qdrant)\n- **Async Processing**: Redpanda/Kafka queue with worker processes\n- **Text Extraction**: Out-of-process using Apache Tika container\n- **Observability**: OpenTelemetry traces + Prometheus metrics\n\nSee `docs/README.md` for complete documentation index and `docs/architecture/ARCHITECTURE.md` for detailed diagrams.\n\n## Secrets and SOPS\n\nThis repository uses SOPS (with age) to encrypt sensitive files so they can be committed safely.\n\n- Encrypted artifacts (all under `secrets/`):\n  - `secrets/env.sops.yaml` – encrypted development environment configuration\n  - `secrets/env.production.sops.yaml` – encrypted production environment configuration\n  - `secrets/project.md.sops` and `secrets/project.md.enc` – encrypted study case project brief\n  - `secrets/rfc/**.sops` – encrypted RFC submission markdowns\n  - `secrets/cv/**.sops` – encrypted CV files (optimized + original)\n- Plaintext counterparts such as `.env`, `.env.production`, and `submissions/**` (CVs, RFCs,\n  project.md) are **gitignored** and should not be committed.\n\n### Local prerequisites\n- Install `sops` and `age`.\n- Ensure your age private key exists at `~/.config/sops/age/keys.txt`.\n- Your public recipient is printed by:\n  ```bash\n  age-keygen -y ~/.config/sops/age/keys.txt\n  ```\n\n### Decrypt\n- Dev/prod env (recommended):\n  ```bash\n  # From secrets/env.sops.yaml -> .env\n  make decrypt-env\n\n  # From secrets/env.production.sops.yaml -> .env.production\n  make decrypt-env-production\n  ```\n- Project brief (for local inspection):\n  ```bash\n  # From secrets/project.md.enc -> submissions/project.md\n  make decrypt-project\n  ```\n\n### Edit and re-encrypt\nFor `secrets/project.md.sops` (binary), decrypt to plaintext, edit, then re-encrypt:\n```bash\n# decrypt to plaintext, edit it\nSOPS_AGE_KEY_FILE=\"$HOME/.config/sops/age/keys.txt\" \\\n  sops -d --input-type binary --output-type binary secrets/project.md.sops > submissions/project.md\n\n# re-encrypt to .sops using your age key (or use Makefile target for .enc)\nSOPS_AGE_KEY_FILE=\"$HOME/.config/sops/age/keys.txt\" \\\n  sops --encrypt --input-type binary --output-type binary submissions/project.md > secrets/project.md.sops\n```\n\nFor env files, you can edit the encrypted YAML in place and SOPS will re-encrypt on save:\n```bash\nsops secrets/env.sops.yaml\n# or\nsops secrets/env.production.sops.yaml\n```\n\n### CI/CD\n- Store the age private key in a secret (e.g., `SOPS_AGE_KEY`).\n- In CI, write the key to `~/.config/sops/age/keys.txt`, then use Make targets to decrypt:\n  ```bash\n  make decrypt-env           # for dev/test\n  make decrypt-env-production  # for production deploy\n  ```\n- See the CI rules for full examples in `.github/workflows/ci.yml` and `.github/workflows/deploy.yml`.\n\n## Observability\n- Metrics:\n  - HTTP: `http_requests_total`, `http_request_duration_seconds`\n  - Queue: `jobs_enqueued_total`, `jobs_processing`, `jobs_completed_total`, `jobs_failed_total`\n- Evaluation distributions: `evaluation_cv_match_rate` [0..1], `evaluation_project_score` [1..10]\n- Traces:\n  - HTTP, DB, queue worker spans; export via OTLP (`OTEL_EXPORTER_OTLP_ENDPOINT`).\n\n## RAG Seeding\n- Seed files live under `configs/rag/`.\n- Supported YAML shapes:\n  - `items: [\"...\", \"...\"]` (list of strings)\n  - `texts: [\"...\", \"...\"]` (list of strings)\n  - `data: [{text: \"...\", type: rubric|job|..., section: \"...\", weight: 0.30}]`\n- Metadata is carried to Qdrant payload as `source`, `type`, `section`, `weight` and used for simple re-ranking (by `weight` desc).\n- Seed both corpora with:\n  ```bash\n  make seed-rag  # requires QDRANT_URL (defaults http://localhost:6333); uses configured embeddings (e.g., OPENAI_API_KEY)\n  ```\n\n## Testing\n- Unit tests:\n  ```bash\n  make test\n  ```\n- E2E (tagged):\n  ```bash\n  make test-e2e  # assumes running app stack\n  ```\n\n## Deployment (Overview)\n- CI validates code, tests, and builds the container.\n- Deploy via GitHub Actions to a Docker-capable VPS using SSH keys.\n- **Database migrations run automatically** via dedicated migration container.\n- App runs behind Docker Compose, with Postgres, Redpanda, Qdrant, and Tika on internal network.\n\n## Configuration\nEnvironment variables (see `.env.sample`):\n- Core: `APP_ENV`, `PORT`, `DB_URL`, `KAFKA_BROKERS`\n- AI: `OPENROUTER_API_KEY`, `OPENROUTER_API_KEY_2`, `OPENAI_API_KEY`, etc.\n- Vector DB: `QDRANT_URL`, `QDRANT_API_KEY`\n- Extractor: `TIKA_URL`\n- Observability: `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_SERVICE_NAME`\n- Limits & CORS: `MAX_UPLOAD_MB`, `RATE_LIMIT_PER_MIN`, `CORS_ALLOW_ORIGINS`\n\t- Queue / AI safety: `CONSUMER_MAX_CONCURRENCY` (defaults to 1), `OPENROUTER_MIN_INTERVAL` (defaults to 5s) for free-tier-friendly throughput\n- Frontend: `FRONTEND_SEPARATED` (enables API-only mode)\n\n- Notes:\n- Groq chat uses an internal curated list of models (for example, `llama-3.1-8b-instant`,\n  `llama-3.3-70b-versatile`). Groq model selection and fallback are automatic and not configurable\n  via environment variables.\n- OpenRouter chat uses free models discovered from the OpenRouter API; there is no fixed chat\n  model environment variable.\n- Embeddings are performed via OpenAI; set `OPENAI_API_KEY` and `EMBEDDINGS_MODEL` (default\n  `text-embedding-3-small`). If `OPENAI_API_KEY` is not set, embeddings and RAG are skipped.\n- E2E tests run against live providers (no stub/mock). Ensure `OPENROUTER_API_KEY` (and\n  `OPENAI_API_KEY` for RAG) are present before running E2E.\n- Frontend separation: Set `FRONTEND_SEPARATED=true` to enable API-only backend mode.\n\n",
  "bytes": 12492,
  "sha": "b6e01f0cbc0e442da20c5e46f294b570ee6b2f62c81e53a930634a1b6eb49a23",
  "repo_slug": "fairyhunter13/ai-cv-evaluator",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/okf_fairyhunter13_ai_cv_evaluator_knowledge__b033c53b/readme"
}