# AI Platform AI Platform provides Lead Scoring with a Python backend and internal SvelteKit Admin Web. Local app-auth verification and integration limitations are recorded in `docs/contracts/app-auth-provider-handoff.md`; local checks do not establish production readiness. ## Runtime layout - Python 3.13 backend package: `src/ai_platform`, served as `ai_platform.main:app`. - PostgreSQL 16 with a new, project-specific Compose volume named `ai_platform_pgdata`. - Alembic schema changes are applied as a one-shot Compose service before the backend starts. - SvelteKit is a separate web image under `apps/web`. - Historical implementation is retained in Git history, not as a separate source tree. The local Compose stack runs one backend process. It is for development only; default credentials and loopback port mappings are not deployment settings. Keep production secrets outside `.env` and inject them through the deployment environment or a secrets manager. ## Local setup Copy the backend/Compose template and the separate SvelteKit server-only template only when setting up a new workspace. Both `.env` files are ignored by Git; preserve any existing `.env` and `apps/web/.env` instead of overwriting them. ```sh cp .env.example .env cp apps/web/.env.example apps/web/.env make install ``` For a new database, prepare its schema once before starting the application: ```sh make db-up make migrate ``` Then start PostgreSQL and both native servers from one terminal: ```sh make dev ``` Set `BACKEND_PORT` once in the root `.env` (default `8000`; use `8002` to avoid a local conflict). `make backend-dev` uses that port, and `make web-dev` passes the matching server-only `BACKEND_INTERNAL_URL` to Web, overriding any old URL in `apps/web/.env`. An exported `BACKEND_PORT` or `make dev BACKEND_PORT=8002` can override the root value. Other root secrets are not passed to Web. The web development server normally listens at `127.0.0.1:5173` (see its terminal output). `Ctrl+C` stops the native servers; PostgreSQL stays running. `make dev` does not run migrations or start Omni/AIHub. To run the servers in separate terminals instead, use `make backend-dev` and `make web-dev`. The local web server should call the backend through server-only configuration. Do not expose `BACKEND_INTERNAL_URL`, database credentials, or provider keys as browser-visible `PUBLIC_*` variables. Compose can instead build and start all services, including the one-shot migration step: ```sh docker compose up --build ``` The database is reachable from the host at `127.0.0.1:55432`, backend at `127.0.0.1:8000`, and web at `127.0.0.1:3000` by default. To discard this Compose stack's database and its data, run `docker compose down -v`; unrelated project volumes are not targeted. The local database starts with only the development database. To run tests safely, create a separate database in that same new PostgreSQL instance, then pass its URL explicitly: ```sh docker compose exec postgres createdb -U ai_platform ai_platform_test AI_PLATFORM_DATABASE_URL=postgresql+asyncpg://ai_platform:local-change-this-password@127.0.0.1:55432/ai_platform_test make test ``` `make test` rejects a database URL that does not contain `ai_platform_test`. CI provisions its own isolated PostgreSQL 16 test database. ## Chấm điểm AI qua CLIProxy khi phát triển local Không cần chạy AIHub để thử adapter OpenAI-compatible. Cấu hình URL trên backend (trong môi trường hoặc `.env` local đã có): ```sh AI_PLATFORM_ENVIRONMENT=development AI_PLATFORM_OPENAI_BASE_URL=http://127.0.0.1:8317 # AI_PLATFORM_OPENAI_API_KEY=... # chỉ cần nếu CLIProxy yêu cầu xác thực ``` Chạy `make backend-dev` trên máy host: `127.0.0.1:8317` bên trong Docker không trỏ tới CLIProxy trên host. Trong Admin Web, tạo và kích hoạt **một phiên bản Cấu hình AI mới**, chọn `OpenAI-compatible`, nhập URL `http://127.0.0.1:8317`, model `gemini-3.5-flash-lite`, prompt và timeout. `AI_PLATFORM_OPENAI_BASE_URL` là URL mặc định cho profile cũ và là địa chỉ duy nhất nhận `AI_PLATFORM_OPENAI_API_KEY`; URL khác nhập trong Web không được gửi key đó. Profile cũ mặc định AIHub; URL AIHub vẫn cấu hình bằng `AI_PLATFORM_AIHUB_BASE_URL`. Lựa chọn này áp dụng cho các run mới của Lead Scoring, không đổi các run đã nhận trước đó. Gửi `POST /v1/lead-scoring/runs` với `ai_profile=medium`, `ai_weight>0` và app key; OpenAI-compatible không dùng `X-Tenant-VKey`. Trang “Thử chấm điểm” trên Web cho xem trước hội thoại và quy tắc mẫu. Mặc định chỉ chấm quy tắc (`ai_weight=0`); bật “Chấm thêm bằng AI” để gửi `ai_profile=medium` với trọng số quy tắc/AI 0.5/0.5. Chế độ này dùng provider đang active và có thể phát sinh chi phí. Mẫu không gửi V-Key nên với AIHub sẽ dự phòng bằng điểm quy tắc; xem trang chi tiết run để kiểm tra kết quả thực tế. Với endpoint OpenAI-compatible chạy ở production, URL phải dùng HTTPS. Key được cấu hình trên server chỉ gửi tới URL được gắn với key đó; thay URL trên Web không tự chuyển key. Không đặt key vào Web hoặc file versioned. CLIProxy local chỉ xác minh được đường gọi thử; chưa chứng minh các điều kiện phát hành production. ## App credentials Mỗi installation/môi trường Omni dùng một `app_id` ổn định và một backend app key chung cho các account. Vận hành cấp/rotate/revoke qua CLI; raw key chỉ được xuất khi cấp/rotate, chuyển an toàn sang backend Omni, không qua browser/log/Git: ```sh uv run --no-sync ai-platform create-app omni-local uv run --no-sync ai-platform rotate-app-key omni-local uv run --no-sync ai-platform revoke-app-key omni-local ``` Admin Web `/platform/apps` chỉ xem trạng thái và thu hồi, không hiển thị hay cấp/rotate key. POST `/v1/lead-scoring/score` dùng `Authorization: Bearer `; body có `account_id` từ Omni. Omni dùng `LEAD_SCORING_API_URL`, `AI_PLATFORM_APP_ID`, `AI_PLATFORM_APP_API_KEY` ở backend. Đã bỏ tenant provisioning API/secret; không đăng ký account ở AI Platform. Generated OpenAPI và các fixture tại `contracts/lead-scoring/v2/`; chạy `make contract` để tái sinh và `uv run --no-sync python scripts/export_contract_fixtures.py --check` để kiểm tra drift fixture mà không sửa file. Omni cần đối chiếu response/source request trước writeback; provider checks chưa chứng minh tích hợp Omni/EMS. Schema chưa phát hành hiện có một migration `0001_initial`, tạo thẳng app credentials, admin, AI profiles, runs. Database mới chạy `make migrate`. Database local còn revision cũ không tự upgrade sang baseline: dùng database mới, hoặc xác nhận phạm vi dữ liệu cần giữ/bỏ trước khi dựng lại hay stamp sau kiểm tra schema; không chạy `alembic stamp head` để che schema khác biệt. Startup vẫn kiểm tra revision và không tự migrate. Lịch sử migrations sau lần phát hành đầu tiên phải được giữ. Fixture runner nội bộ dùng `LEAD_SCORING_FIXTURE_APP_API_KEY` server-only. Backend dọn run quá 90 ngày (gồm snapshot, fingerprint và thông báo liên quan) khi khởi động và mỗi ngày. Backup và app/account offboarding cần chính sách riêng trước production. ## Quality checks The opt-in pre-commit runner is installed by `make install` as a project-local development dependency. Run: ```sh make check-fast make check ``` `make check-fast` runs the pinned Ruff and pre-commit-hooks rules. `make check` additionally runs Python type/import checks and tests; tests must use a separate test database, never a developer or production database. CI uses the same fast hooks and the Makefile test/check commands with an isolated PostgreSQL 16 service. ## Operations notes - Apply migrations as a release step; application startup must not migrate the database. - `/health/live` is liveness; `/health/ready` checks readiness and PostgreSQL. The Compose backend health check waits for readiness. - Avoid logging credentials, request payloads containing personal data, or provider responses with sensitive content. - The Compose PostgreSQL volume is disposable development data. Backup, restore, retention, secrets, TLS, and alerting must be defined for the eventual deployment target. The web container currently assumes `package.json`, `bun.lock`, a production SvelteKit build at `build/index.js`, and Bun compatibility with the selected SvelteKit adapter. Confirm those assumptions with the web implementation before treating the image as verified.