Files

1 line
85 KiB
JSON

{"repo": "annkoem/lawkhback", "files": {"README.md": {"0196b0d81ea6f31708e815cf33d2cba98e41a3b0": {"t": 1785746657.241407, "n": 13650, "h": "2a5d6f3aa15589c1", "c": "# LawKhBack\n\nLocal-first backend for a Cambodian law assistant app.\n\nThis repository runs the backend API used by the Expo mobile app and the Telegram bot interface. It uses local ChromaDB retrieval, local sentence-transformer embeddings, and a configurable LLM provider for answer generation.\n\n## Overview\n\nLawKhBack is designed for a local demo or a normal server host where the Chroma database can live beside the backend. The API accepts legal questions, retrieves relevant Cambodian law chunks from ChromaDB, sends the retrieved context to the configured model provider, and returns an answer with structured citations.\n\nSubmission deliverables:\n\n- [1-Page RAG Tech Report](docs/rag-tech-report.md)\n- [System Diagram](docs/system-diagram.md)\n\nSupported clients:\n\n- Expo React Native APK / Android emulator\n- Telegram bot\n- Direct HTTP requests to the backend API\n\nDefault model provider:\n\n```txt\nLLM_PROVIDER=openrouter\nOPENROUTER_MODEL=openrouter/free\n```\n\nSupported generation providers:\n\n```txt\nopenrouter\ndeepseek\nollama\n```\n\n## Architecture\n\n```txt\nExpo app / Telegram bot\n -> FastAPI backend\n -> local ChromaDB\n -> local sentence-transformers embedding model\n -> OpenRouter / DeepSeek / Ollama chat completion API\n -> answer + citations\n```\n\n## Repository Layout\n\n```txt\nLawKhBack/\n README.md\n .env.example\n requirements.txt\n run_rag_api.ps1\n rag/\n server.py\n query.py\n chroma_db/\n auth_store.py\n law_assets.py\n scripts/\n prepare_law_assets.py\n law_assets/\n library/ (gitignored, generated locally)\n index/ (gitignored, generated locally)\n telegram_bot/\n bot.py\n api_client.py\n config.py\n .runtime/ (gitignored, created at startup)\n```\n\nImportant paths:\n\n- `rag/server.py` contains the FastAPI endpoints.\n- `rag/query.py` contains retrieval, prompt building, OpenRouter/DeepSeek/Ollama provider switching, and self-check logic.\n- `rag/chroma_db/` is where the local Chroma database should be placed.\n- `scripts/prepare_law_assets.py` prepares source law ZIP/material folders for the law library API.\n- `telegram_bot/` contains the Telegram polling bot that calls the same `/chat` backend endpoint.\n\n## Requirements\n\n- Python 3.10 or newer\n- Git LFS, used for the included ChromaDB files\n- OpenRouter API key or DeepSeek API key, depending on provider\n- MongoDB Atlas URI for signup, login, password reset, and saved chat history\n- Telegram bot token if using the Telegram interface\n\n## Setup\n\nClone the repository and enter the project folder:\n\n```powershell\ngit lfs install\ngit clone <your-repo-url>\ncd LawKhBack\ngit lfs pull\n```\n\nCreate a virtual environment:\n\n```powershell\npython -m venv .venv\n.\\.venv\\Scripts\\Activate.ps1\n```\n\nInstall dependencies:\n\n```powershell\npip install -r requirements.txt\n```\n\nCreate your local environment file:\n\n```powershell\nCopy-Item .env.example .env\n```\n\nEdit `.env` and set at least one provider key:\n\n```txt\nOPENROUTER_API_KEY=your_openrouter_api_key\n```\n\nor:\n\n```txt\nDEEPSEEK_API_KEY=your_deepseek_api_key\n```\n\nIf using Telegram, also set:\n\n```txt\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\n```\n\nFor auth and saved chat history, set:\n\n```txt\nMONGODB_URI=your_mongodb_connection_string\nMONGODB_DB=lawkh\nJWT_SECRET=replace_this_with_a_long_random_secret\n```\n\nIf Atlas rejects local connections, check the Atlas Network Access IP allowlist first. The backend uses MongoDB for auth and chat history; without a reachable Mongo URI, `/chat` still works but auth/history endpoints cannot persist data.\n\n## Law Documents\n\nThe law document ZIPs/source folders are intentionally not committed. Keep them outside git, then prepare them into the local gitignored library.\n\nSet this in `.env`:\n\n```txt\nLAW_ASSETS_SOURCE_DIR=D:\\Users\\aykay\\SProjects\\KhmerOCR\\Original Materials\nLAW_ASSETS_DIR=law_assets/library\nLAW_ASSETS_INDEX=law_assets/index/documents.json\n```\n\nPrepare the law library:\n\n```powershell\npython -B scripts\\prepare_law_assets.py\n```\n\nExpected output includes:\n\n```txt\nIndexed 1189 documents\n```\n\nThe preparation step copies/extracts documents into short local filenames so Windows long paths do not break testing. Generated files stay ignored by git.\n\n## ChromaDB\n\nThis repo includes the packaged Chroma database under:\n\n```txt\nrag/chroma_db/\n```\n\nThe folder contains `chroma.sqlite3` and the related Chroma index files for the `cambodian_laws` collection. These large files are stored with Git LFS.\n\nAt runtime, the backend copies the packaged database into:\n\n```txt\n.runtime/chroma_db/\n```\n\nThe runtime copy is ignored by git. This keeps the committed database clean while allowing Chroma/SQLite to create temporary journal files locally.\n\nDefault collection name:\n\n```txt\ncambodian_laws\n```\n\nIf you want to use a different packaged database, set `CHROMA_SOURCE_DIR` in `.env`:\n\n```txt\nCHROMA_SOURCE_DIR=D:\\path\\to\\your\\chroma_db\n```\n\nIf you want to bypass the runtime copy and point Chroma directly at a database, set `CHROMA_DIR`:\n\n```txt\nCHROMA_DIR=D:\\path\\to\\your\\runtime_chroma_db\n```\n\n## Run Locally\n\nStart the backend:\n\n```powershell\n.\\run_rag_api.ps1\n```\n\nBy default this starts:\n\n- FastAPI backend on `http://localhost:8000`\n- Telegram bot in the background when `RUN_TELEGRAM_BOT=true` and `TELEGRAM_BOT_TOKEN` is set\n\nLinux/cloud hosts can start only the web API with:\n\n```bash\npython start_backend.py\n```\n\nThe default port is `8000`. If a host sets `PORT`, `start_backend.py` uses that value automatically.\n\nHealth check:\n\n```txt\nGET http://localhost:8000/health\n```\n\nExpected response:\n\n```json\n{\n \"status\": \"ok\",\n \"service\": \"cambodian-legal-rag\",\n \"provider\": \"openrouter\"\n}\n```\n\nThe `provider` value follows your `LLM_PROVIDER` setting.\n\n## Environment Variables\n\nDefault `.env` shape:\n\n```txt\nLLM_PROVIDER=openrouter\nOPENROUTER_API_KEY=your_openrouter_api_key\nOPENROUTER_MODEL=openrouter/free\nOPENROUTER_BASE_URL=https://openrouter.ai/api/v1\nOPENROUTER_SITE_URL=http://localhost:3000\nOPENROUTER_APP_NAME=Feasible\nDEEPSEEK_API_KEY=your_deepseek_api_key\nDEEPSEEK_MODEL=deepseek-v4-flash\nDEEPSEEK_BASE_URL=https://api.deepseek.com\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\nRUN_TELEGRAM_BOT=true\nRAG_API_BASE_URL=http://localhost:8000\nRAG_REQUEST_TIMEOUT_SECONDS=60\n\nMONGODB_URI=mongodb+srv://username:password@cluster.example.mongodb.net/\nMONGODB_DB=lawkh\nMONGODB_TIMEOUT_MS=5000\nJWT_SECRET=change_this_for_local_dev\nJWT_ACCESS_TOKEN_MINUTES=1440\nPASSWORD_RESET_TOKEN_MINUTES=30\n\nLAW_ASSETS_DIR=law_assets/library\nLAW_ASSETS_INDEX=law_assets/index/documents.json\nLAW_ASSETS_SOURCE_DIR=D:\\path\\to\\Original Materials\n\nCHROMA_SOURCE_DIR=rag/chroma_db\nCHROMA_DIR=\nCHROMA_COLLECTION_NAME=cambodian_laws\nEMBEDDING_MODEL=intfloat/multilingual-e5-large\nRAG_OFFLINE_MODE=false\n\nOLLAMA_URL=http://localhost:11434\nOLLAMA_MODEL=gpt-oss:20b\n```\n\nTo run only the backend without Telegram:\n\n```txt\nRUN_TELEGRAM_BOT=false\n```\n\nTo switch generation to Ollama:\n\n```txt\nLLM_PROVIDER=ollama\nOLLAMA_URL=http://localhost:11434\nOLLAMA_MODEL=gpt-oss:20b\n```\n\nTo switch generation to DeepSeek:\n\n```txt\nLLM_PROVIDER=deepseek\nDEEPSEEK_API_KEY=your_deepseek_api_key\nDEEPSEEK_MODEL=deepseek-v4-flash\nDEEPSEEK_BASE_URL=https://api.deepseek.com\n```\n\nDeepSeek currently exposes these model IDs through `/models`:\n\n```txt\ndeepseek-v4-flash\ndeepseek-v4-pro\n```\n\n## Hosting\n\nThis backend is better suited to Render, Railway, Fly.io, or a VPS than Vercel because it uses local ChromaDB files, a larger embedding model, and longer-running AI requests.\n\nRecommended simple deployment:\n\n```txt\nRender web service\n -> start command: python start_backend.py\n -> port: 8000 or host-provided PORT\n -> MongoDB Atlas\n -> DeepSeek or OpenRouter\n -> packaged ChromaDB from Git LFS\n```\n\nThe repo includes:\n\n- `Procfile` for platforms that detect Python web services.\n- `render.yaml` for Render blueprint-style setup.\n- `start_backend.py` for Linux/cloud startup without PowerShell or reload mode.\n\nRequired hosting environment variables:\n\n```txt\nLLM_PROVIDER=deepseek\nDEEPSEEK_API_KEY=your_deepseek_key\nDEEPSEEK_MODEL=deepseek-v4-flash\nMONGODB_URI=your_mongodb_uri\nMONGODB_DB=lawkh\nJWT_SECRET=long_random_secret\nCHROMA_SOURCE_DIR=rag/chroma_db\nCHROMA_COLLECTION_NAME=cambodian_laws\nEMBEDDING_MODEL=intfloat/multilingual-e5-large\nRAG_OFFLINE_MODE=false\nRUN_TELEGRAM_BOT=false\n```\n\nFor the hosted API, keep Telegram as a separate worker/service if needed. Do not run the Telegram polling bot inside the web process on most hosts.\n\n## API\n\n### `GET /health`\n\nReturns service status and active provider.\n\n### `POST /chat`\n\nMain endpoint for the Expo app and Telegram bot.\n\nThis endpoint is public. If the client sends `Authorization: Bearer <token>`, the backend attempts to save the turn under that user. If no token is sent, RAG answer generation still works.\n\nRequest:\n\n```json\n{\n \"question\": \"What are the tax registration requirements for a new company in Cambodia?\",\n \"chatId\": \"chat_optional\",\n \"history\": [\n {\n \"role\": \"user\",\n \"content\": \"I am setting up a company.\"\n }\n ],\n \"filters\": {\n \"categoryIds\": [\"tax\", \"business-registration\"],\n \"documentIds\": []\n }\n}\n```\n\nResponse:\n\n```json\n{\n \"chatId\": \"chat_abc123\",\n \"answer\": \"Generated legal answer...\",\n \"citations\": [\n {\n \"id\": \"cite_001\",\n \"title\": \"Law on Taxation\",\n \"fullCitation\": \"Law on Taxation (Tax)\",\n \"documentId\": \"tax/law-on-taxation-2023-pdf\",\n \"categoryId\": \"tax\",\n \"page\": null,\n \"excerpt\": \"Relevant source excerpt...\",\n \"score\": 0.82,\n \"pdfUrl\": \"http://localhost:8000/law/documents/tax%2Flaw-on-taxation-2023-pdf/download\",\n \"downloadUrl\": \"http://localhost:8000/law/documents/tax%2Flaw-on-taxation-2023-pdf/download\",\n \"chunkId\": \"123\",\n \"locationLabel\": \"OCR chunk 123\"\n }\n ]\n}\n```\n\nCitation metadata is intentionally source-driven. The backend does not ask the LLM to invent PDF pages. If a real PDF page is unavailable, `page`, `pageStart`, and `pageEnd` are `null`, and `locationLabel` describes the OCR chunk/source location instead.\n\nCitation `documentId` values match the law-library document IDs accepted by:\n\n```txt\nGET /law/documents/{documentId}\nGET /law/documents/{documentId}/download\n```\n\nDocument IDs may contain `/`, so frontend clients should URL-encode them before calling document endpoints, for example `tax%2Flaw-on-taxation-2023-pdf`.\n\nOther available endpoints:\n\n- `POST /auth/signup`\n- `POST /auth/login`\n- `POST /auth/password/forgot`\n- `POST /auth/password/reset`\n- `GET /auth/me`\n- `PATCH /auth/me`\n- `GET /me`\n- `PATCH /me`\n- `GET /chats`\n- `GET /chats/{chatId}`\n- `GET /law/categories`\n- `GET /law/categories/{categoryId}/documents`\n- `GET /law/documents/{documentId}`\n- `GET /law/documents/{documentId}/download`\n- `GET /citations/{citationId}`\n- `POST /api/query`\n- `POST /api/query/stream`\n\n### Auth Flow\n\nSignup request:\n\n```json\n{\n \"name\": \"User Name\",\n \"email\": \"user@example.com\",\n \"password\": \"Password123!\"\n}\n```\n\nSignup/login response:\n\n```json\n{\n \"accessToken\": \"jwt_token\",\n \"user\": {\n \"id\": \"mongo_user_id\",\n \"name\": \"User Name\",\n \"email\": \"user@example.com\",\n \"preferences\": {\n \"darkMode\": true\n }\n }\n}\n```\n\nForgot password is intentionally simple for the MVP. `POST /auth/password/forgot` returns a reset token directly when the email exists, then `POST /auth/password/reset` changes the password with that token.\n\n### Law Document Viewer\n\n`GET /law/documents/{documentId}` returns document metadata plus direct file URLs:\n\n```json\n{\n \"id\": \"labour/example-pdf\",\n \"categoryId\": \"labour\",\n \"title\": \"Example Labour Document\",\n \"subtitle\": \"Employment and worker protections\",\n \"year\": \"2024\",\n \"pages\": null,\n \"size\": \"1.2 MB\",\n \"pdfUrl\": \"http://localhost:8000/law/documents/labour%2Fexample-pdf/download\",\n \"fileUrl\": \"http://localhost:8000/law/documents/labour%2Fexample-pdf/download\",\n \"downloadUrl\": \"http://localhost:8000/law/documents/labour%2Fexample-pdf/download\",\n \"content\": \"This document is available for download: example.pdf\"\n}\n```\n\n`GET /law/documents/{documentId}/download` supports URL-encoded IDs and returns PDFs with inline browser-friendly headers:\n\n```txt\nContent-Type: application/pdf\nContent-Disposition: inline\nAccept-Ranges: bytes\n```\n\n## Expo App Usage\n\nUse these backend URLs from the Expo app:\n\n- Web on same machine: `http://localhost:8000`\n- Android emulator: `http://10.0.2.2:8000`\n- Physical device: `http://<your-lan-ip>:8000`\n\nThe Expo app should call:\n\n```txt\nPOST /chat\n```\n\nThe OpenRouter/DeepSeek key and Telegram token stay in this backend repo's `.env`, not inside the APK.\n\n## Telegram Bot Usage\n\nThe Telegram bot is started by `run_rag_api.ps1` when:\n\n```txt\nRUN_TELEGRAM_BOT=true\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\n```\n\nThe bot uses polling and sends user questions to:\n\n```txt\nhttp://localhost:8000/chat\n```\n\nOnly one polling instance can use the same Telegram bot token at a time. The startup script skips creating a duplicate local `bot.py` process, but Telegram will still return a `409 Conflict` if the same token is already running from another machine or hosting service.\n\nBot logs are written to:\n\n```txt\ntelegram_bot_stdout.log\ntelegram_bot_stderr.log\n```\n\nThese log files are ignored by git.\n\n## Git Notes\n\nDo not commit `.env`.\n\nThe Chroma DB files are intentionally committed through Git LFS so a clone can run local RAG without receiving the database through a separate channel.\n\nDo not commit law document ZIPs or generated law library files. The source ZIP/material folder should be prepared locally with `python -B scripts\\prepare_law_assets.py`; generated `law_assets/library/` and `law_assets/index/` are gitignored.\n"}, "3acf90ca3b318ec8117139feef8a9ba70907d78f": {"t": 1785766639.8142147, "n": 12278, "h": "188155cb385725ad", "c": "# LawKhBack\n\nLocal-first backend for a Cambodian law assistant app.\n\nThis repository runs the backend API used by the Expo mobile app and the Telegram bot interface. It uses local ChromaDB retrieval, local sentence-transformer embeddings, and a configurable LLM provider for answer generation.\n\n## Overview\n\nLawKhBack is designed for a local demo or a normal server host where the Chroma database can live beside the backend. The API accepts legal questions, retrieves relevant Cambodian law chunks from ChromaDB, sends the retrieved context to the configured model provider, and returns an answer with structured citations.\n\nSubmission deliverables:\n\n- [1-Page RAG Tech Report](docs/rag-tech-report.md)\n- [System Diagram](docs/system-diagram.md)\n\nSupported clients:\n\n- Expo React Native APK / Android emulator\n- Telegram bot\n- Direct HTTP requests to the backend API\n\nDefault model provider:\n\n```txt\nLLM_PROVIDER=openrouter\nOPENROUTER_MODEL=openrouter/free\n```\n\nSupported generation providers:\n\n```txt\nopenrouter\ndeepseek\nollama\n```\n\n## Architecture\n\n```txt\nExpo app / Telegram bot\n -> FastAPI backend\n -> local ChromaDB\n -> local sentence-transformers embedding model\n -> OpenRouter / DeepSeek / Ollama chat completion API\n -> answer + citations\n```\n\n## Repository Layout\n\n```txt\nLawKhBack/\n README.md\n .env.example\n requirements.txt\n run_rag_api.ps1\n rag/\n server.py\n query.py\n chroma_db/\n auth_store.py\n law_assets.py\n scripts/\n prepare_law_assets.py\n law_assets/\n library/ (gitignored, generated locally)\n index/ (gitignored, generated locally)\n telegram_bot/\n bot.py\n api_client.py\n config.py\n .runtime/ (gitignored, created at startup)\n```\n\nImportant paths:\n\n- `rag/server.py` contains the FastAPI endpoints.\n- `rag/query.py` contains retrieval, prompt building, OpenRouter/DeepSeek/Ollama provider switching, and self-check logic.\n- `rag/chroma_db/` is where the local Chroma database should be placed.\n- `scripts/prepare_law_assets.py` prepares source law ZIP/material folders for the law library API.\n- `telegram_bot/` contains the Telegram polling bot that calls the same `/chat` backend endpoint.\n\n## Requirements\n\n- Python 3.10 or newer\n- Git LFS, used for the included ChromaDB files\n- OpenRouter API key or DeepSeek API key, depending on provider\n- MongoDB Atlas URI for signup, login, password reset, and saved chat history\n- Telegram bot token if using the Telegram interface\n\n## Setup\n\nClone the repository and enter the project folder:\n\n```powershell\ngit lfs install\ngit clone <your-repo-url>\ncd LawKhBack\ngit lfs pull\n```\n\nCreate a virtual environment:\n\n```powershell\npython -m venv .venv\n.\\.venv\\Scripts\\Activate.ps1\n```\n\nInstall dependencies:\n\n```powershell\npip install -r requirements.txt\n```\n\nCreate your local environment file:\n\n```powershell\nCopy-Item .env.example .env\n```\n\nEdit `.env` and set at least one provider key:\n\n```txt\nOPENROUTER_API_KEY=your_openrouter_api_key\n```\n\nor:\n\n```txt\nDEEPSEEK_API_KEY=your_deepseek_api_key\n```\n\nIf using Telegram, also set:\n\n```txt\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\n```\n\nFor auth and saved chat history, set:\n\n```txt\nMONGODB_URI=your_mongodb_connection_string\nMONGODB_DB=lawkh\nJWT_SECRET=replace_this_with_a_long_random_secret\n```\n\nIf Atlas rejects local connections, check the Atlas Network Access IP allowlist first. The backend uses MongoDB for auth and chat history; without a reachable Mongo URI, `/chat` still works but auth/history endpoints cannot persist data.\n\n## Law Documents\n\nThe law document ZIPs/source folders are intentionally not committed. Keep them outside git, then prepare them into the local gitignored library.\n\nSet this in `.env`:\n\n```txt\nLAW_ASSETS_SOURCE_DIR=D:\\Users\\aykay\\SProjects\\KhmerOCR\\Original Materials\nLAW_ASSETS_DIR=law_assets/library\nLAW_ASSETS_INDEX=law_assets/index/documents.json\n```\n\nPrepare the law library:\n\n```powershell\npython -B scripts\\prepare_law_assets.py\n```\n\nExpected output includes:\n\n```txt\nIndexed 1189 documents\n```\n\nThe preparation step copies/extracts documents into short local filenames so Windows long paths do not break testing. Generated files stay ignored by git.\n\n## ChromaDB\n\nThis repo includes the packaged Chroma database under:\n\n```txt\nrag/chroma_db/\n```\n\nThe folder contains `chroma.sqlite3` and the related Chroma index files for the `cambodian_laws` collection. These large files are stored with Git LFS.\n\nAt runtime, the backend copies the packaged database into:\n\n```txt\n.runtime/chroma_db/\n```\n\nThe runtime copy is ignored by git. This keeps the committed database clean while allowing Chroma/SQLite to create temporary journal files locally.\n\nDefault collection name:\n\n```txt\ncambodian_laws\n```\n\nIf you want to use a different packaged database, set `CHROMA_SOURCE_DIR` in `.env`:\n\n```txt\nCHROMA_SOURCE_DIR=D:\\path\\to\\your\\chroma_db\n```\n\nIf you want to bypass the runtime copy and point Chroma directly at a database, set `CHROMA_DIR`:\n\n```txt\nCHROMA_DIR=D:\\path\\to\\your\\runtime_chroma_db\n```\n\n## Run Locally\n\nStart the backend:\n\n```powershell\n.\\run_rag_api.ps1\n```\n\nBy default this starts:\n\n- FastAPI backend on `http://localhost:8000`\n- Telegram bot in the background when `RUN_TELEGRAM_BOT=true` and `TELEGRAM_BOT_TOKEN` is set\n\nHealth check:\n\n```txt\nGET http://localhost:8000/health\n```\n\nExpected response:\n\n```json\n{\n \"status\": \"ok\",\n \"service\": \"cambodian-legal-rag\",\n \"provider\": \"openrouter\"\n}\n```\n\nThe `provider` value follows your `LLM_PROVIDER` setting.\n\n## Environment Variables\n\nDefault `.env` shape:\n\n```txt\nLLM_PROVIDER=openrouter\nOPENROUTER_API_KEY=your_openrouter_api_key\nOPENROUTER_MODEL=openrouter/free\nOPENROUTER_BASE_URL=https://openrouter.ai/api/v1\nOPENROUTER_SITE_URL=http://localhost:3000\nOPENROUTER_APP_NAME=Feasible\nDEEPSEEK_API_KEY=your_deepseek_api_key\nDEEPSEEK_MODEL=deepseek-v4-flash\nDEEPSEEK_BASE_URL=https://api.deepseek.com\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\nRUN_TELEGRAM_BOT=true\nRAG_API_BASE_URL=http://localhost:8000\nRAG_REQUEST_TIMEOUT_SECONDS=60\n\nMONGODB_URI=mongodb+srv://username:password@cluster.example.mongodb.net/\nMONGODB_DB=lawkh\nMONGODB_TIMEOUT_MS=5000\nJWT_SECRET=change_this_for_local_dev\nJWT_ACCESS_TOKEN_MINUTES=1440\nPASSWORD_RESET_TOKEN_MINUTES=30\n\nLAW_ASSETS_DIR=law_assets/library\nLAW_ASSETS_INDEX=law_assets/index/documents.json\nLAW_ASSETS_SOURCE_DIR=D:\\path\\to\\Original Materials\n\nCHROMA_SOURCE_DIR=rag/chroma_db\nCHROMA_DIR=\nCHROMA_COLLECTION_NAME=cambodian_laws\nEMBEDDING_MODEL=intfloat/multilingual-e5-large\n\nOLLAMA_URL=http://localhost:11434\nOLLAMA_MODEL=gpt-oss:20b\n```\n\nTo run only the backend without Telegram:\n\n```txt\nRUN_TELEGRAM_BOT=false\n```\n\nTo switch generation to Ollama:\n\n```txt\nLLM_PROVIDER=ollama\nOLLAMA_URL=http://localhost:11434\nOLLAMA_MODEL=gpt-oss:20b\n```\n\nTo switch generation to DeepSeek:\n\n```txt\nLLM_PROVIDER=deepseek\nDEEPSEEK_API_KEY=your_deepseek_api_key\nDEEPSEEK_MODEL=deepseek-v4-flash\nDEEPSEEK_BASE_URL=https://api.deepseek.com\n```\n\nDeepSeek currently exposes these model IDs through `/models`:\n\n```txt\ndeepseek-v4-flash\ndeepseek-v4-pro\n```\n\n## API\n\n### `GET /health`\n\nReturns service status and active provider.\n\n### `POST /chat`\n\nMain endpoint for the Expo app and Telegram bot.\n\nThis endpoint is public. If the client sends `Authorization: Bearer <token>`, the backend attempts to save the turn under that user. If no token is sent, RAG answer generation still works.\n\nRequest:\n\n```json\n{\n \"question\": \"What are the tax registration requirements for a new company in Cambodia?\",\n \"chatId\": \"chat_optional\",\n \"history\": [\n {\n \"role\": \"user\",\n \"content\": \"I am setting up a company.\"\n }\n ],\n \"filters\": {\n \"categoryIds\": [\"tax\", \"business-registration\"],\n \"documentIds\": []\n }\n}\n```\n\nResponse:\n\n```json\n{\n \"chatId\": \"chat_abc123\",\n \"answer\": \"Generated legal answer...\",\n \"citations\": [\n {\n \"id\": \"cite_001\",\n \"title\": \"Law on Taxation\",\n \"fullCitation\": \"Law on Taxation (Tax)\",\n \"documentId\": \"tax/law-on-taxation-2023-pdf\",\n \"categoryId\": \"tax\",\n \"page\": null,\n \"excerpt\": \"Relevant source excerpt...\",\n \"score\": 0.82,\n \"pdfUrl\": \"http://localhost:8000/law/documents/tax%2Flaw-on-taxation-2023-pdf/download\",\n \"downloadUrl\": \"http://localhost:8000/law/documents/tax%2Flaw-on-taxation-2023-pdf/download\",\n \"chunkId\": \"123\",\n \"locationLabel\": \"OCR chunk 123\"\n }\n ]\n}\n```\n\nCitation metadata is intentionally source-driven. The backend does not ask the LLM to invent PDF pages. If a real PDF page is unavailable, `page`, `pageStart`, and `pageEnd` are `null`, and `locationLabel` describes the OCR chunk/source location instead.\n\nCitation `documentId` values match the law-library document IDs accepted by:\n\n```txt\nGET /law/documents/{documentId}\nGET /law/documents/{documentId}/download\n```\n\nDocument IDs may contain `/`, so frontend clients should URL-encode them before calling document endpoints, for example `tax%2Flaw-on-taxation-2023-pdf`.\n\nOther available endpoints:\n\n- `POST /auth/signup`\n- `POST /auth/login`\n- `POST /auth/password/forgot`\n- `POST /auth/password/reset`\n- `GET /auth/me`\n- `PATCH /auth/me`\n- `GET /me`\n- `PATCH /me`\n- `GET /chats`\n- `GET /chats/{chatId}`\n- `GET /law/categories`\n- `GET /law/categories/{categoryId}/documents`\n- `GET /law/documents/{documentId}`\n- `GET /law/documents/{documentId}/download`\n- `GET /citations/{citationId}`\n- `POST /api/query`\n- `POST /api/query/stream`\n\n### Auth Flow\n\nSignup request:\n\n```json\n{\n \"name\": \"User Name\",\n \"email\": \"user@example.com\",\n \"password\": \"Password123!\"\n}\n```\n\nSignup/login response:\n\n```json\n{\n \"accessToken\": \"jwt_token\",\n \"user\": {\n \"id\": \"mongo_user_id\",\n \"name\": \"User Name\",\n \"email\": \"user@example.com\",\n \"preferences\": {\n \"darkMode\": true\n }\n }\n}\n```\n\nForgot password is intentionally simple for the MVP. `POST /auth/password/forgot` returns a reset token directly when the email exists, then `POST /auth/password/reset` changes the password with that token.\n\n### Law Document Viewer\n\n`GET /law/documents/{documentId}` returns document metadata plus direct file URLs:\n\n```json\n{\n \"id\": \"labour/example-pdf\",\n \"categoryId\": \"labour\",\n \"title\": \"Example Labour Document\",\n \"subtitle\": \"Employment and worker protections\",\n \"year\": \"2024\",\n \"pages\": null,\n \"size\": \"1.2 MB\",\n \"pdfUrl\": \"http://localhost:8000/law/documents/labour%2Fexample-pdf/download\",\n \"fileUrl\": \"http://localhost:8000/law/documents/labour%2Fexample-pdf/download\",\n \"downloadUrl\": \"http://localhost:8000/law/documents/labour%2Fexample-pdf/download\",\n \"content\": \"This document is available for download: example.pdf\"\n}\n```\n\n`GET /law/documents/{documentId}/download` supports URL-encoded IDs and returns PDFs with inline browser-friendly headers:\n\n```txt\nContent-Type: application/pdf\nContent-Disposition: inline\nAccept-Ranges: bytes\n```\n\n## Expo App Usage\n\nUse these backend URLs from the Expo app:\n\n- Web on same machine: `http://localhost:8000`\n- Android emulator: `http://10.0.2.2:8000`\n- Physical device: `http://<your-lan-ip>:8000`\n\nThe Expo app should call:\n\n```txt\nPOST /chat\n```\n\nThe OpenRouter/DeepSeek key and Telegram token stay in this backend repo's `.env`, not inside the APK.\n\n## Telegram Bot Usage\n\nThe Telegram bot is started by `run_rag_api.ps1` when:\n\n```txt\nRUN_TELEGRAM_BOT=true\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\n```\n\nThe bot uses polling and sends user questions to:\n\n```txt\nhttp://localhost:8000/chat\n```\n\nOnly one polling instance can use the same Telegram bot token at a time. The startup script skips creating a duplicate local `bot.py` process, but Telegram will still return a `409 Conflict` if the same token is already running from another machine or hosting service.\n\nBot logs are written to:\n\n```txt\ntelegram_bot_stdout.log\ntelegram_bot_stderr.log\n```\n\nThese log files are ignored by git.\n\n## Git Notes\n\nDo not commit `.env`.\n\nThe Chroma DB files are intentionally committed through Git LFS so a clone can run local RAG without receiving the database through a separate channel.\n\nDo not commit law document ZIPs or generated law library files. The source ZIP/material folder should be prepared locally with `python -B scripts\\prepare_law_assets.py`; generated `law_assets/library/` and `law_assets/index/` are gitignored.\n"}, "9fdc970549565e99be21bf9e3f61a55ad740f0b7": {"t": 1785766641.087733, "n": 12156, "h": "1e01c21e457a7971", "c": "# LawKhBack\n\nLocal-first backend for a Cambodian law assistant app.\n\nThis repository runs the backend API used by the Expo mobile app and the Telegram bot interface. It uses local ChromaDB retrieval, local sentence-transformer embeddings, and a configurable LLM provider for answer generation.\n\n## Overview\n\nLawKhBack is designed for a local demo or a normal server host where the Chroma database can live beside the backend. The API accepts legal questions, retrieves relevant Cambodian law chunks from ChromaDB, sends the retrieved context to the configured model provider, and returns an answer with structured citations.\n\nSupported clients:\n\n- Expo React Native APK / Android emulator\n- Telegram bot\n- Direct HTTP requests to the backend API\n\nDefault model provider:\n\n```txt\nLLM_PROVIDER=openrouter\nOPENROUTER_MODEL=openrouter/free\n```\n\nSupported generation providers:\n\n```txt\nopenrouter\ndeepseek\nollama\n```\n\n## Architecture\n\n```txt\nExpo app / Telegram bot\n -> FastAPI backend\n -> local ChromaDB\n -> local sentence-transformers embedding model\n -> OpenRouter / DeepSeek / Ollama chat completion API\n -> answer + citations\n```\n\n## Repository Layout\n\n```txt\nLawKhBack/\n README.md\n .env.example\n requirements.txt\n run_rag_api.ps1\n rag/\n server.py\n query.py\n chroma_db/\n auth_store.py\n law_assets.py\n scripts/\n prepare_law_assets.py\n law_assets/\n library/ (gitignored, generated locally)\n index/ (gitignored, generated locally)\n telegram_bot/\n bot.py\n api_client.py\n config.py\n .runtime/ (gitignored, created at startup)\n```\n\nImportant paths:\n\n- `rag/server.py` contains the FastAPI endpoints.\n- `rag/query.py` contains retrieval, prompt building, OpenRouter/DeepSeek/Ollama provider switching, and self-check logic.\n- `rag/chroma_db/` is where the local Chroma database should be placed.\n- `scripts/prepare_law_assets.py` prepares source law ZIP/material folders for the law library API.\n- `telegram_bot/` contains the Telegram polling bot that calls the same `/chat` backend endpoint.\n\n## Requirements\n\n- Python 3.10 or newer\n- Git LFS, used for the included ChromaDB files\n- OpenRouter API key or DeepSeek API key, depending on provider\n- MongoDB Atlas URI for signup, login, password reset, and saved chat history\n- Telegram bot token if using the Telegram interface\n\n## Setup\n\nClone the repository and enter the project folder:\n\n```powershell\ngit lfs install\ngit clone <your-repo-url>\ncd LawKhBack\ngit lfs pull\n```\n\nCreate a virtual environment:\n\n```powershell\npython -m venv .venv\n.\\.venv\\Scripts\\Activate.ps1\n```\n\nInstall dependencies:\n\n```powershell\npip install -r requirements.txt\n```\n\nCreate your local environment file:\n\n```powershell\nCopy-Item .env.example .env\n```\n\nEdit `.env` and set at least one provider key:\n\n```txt\nOPENROUTER_API_KEY=your_openrouter_api_key\n```\n\nor:\n\n```txt\nDEEPSEEK_API_KEY=your_deepseek_api_key\n```\n\nIf using Telegram, also set:\n\n```txt\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\n```\n\nFor auth and saved chat history, set:\n\n```txt\nMONGODB_URI=your_mongodb_connection_string\nMONGODB_DB=lawkh\nJWT_SECRET=replace_this_with_a_long_random_secret\n```\n\nIf Atlas rejects local connections, check the Atlas Network Access IP allowlist first. The backend uses MongoDB for auth and chat history; without a reachable Mongo URI, `/chat` still works but auth/history endpoints cannot persist data.\n\n## Law Documents\n\nThe law document ZIPs/source folders are intentionally not committed. Keep them outside git, then prepare them into the local gitignored library.\n\nSet this in `.env`:\n\n```txt\nLAW_ASSETS_SOURCE_DIR=D:\\Users\\aykay\\SProjects\\KhmerOCR\\Original Materials\nLAW_ASSETS_DIR=law_assets/library\nLAW_ASSETS_INDEX=law_assets/index/documents.json\n```\n\nPrepare the law library:\n\n```powershell\npython -B scripts\\prepare_law_assets.py\n```\n\nExpected output includes:\n\n```txt\nIndexed 1189 documents\n```\n\nThe preparation step copies/extracts documents into short local filenames so Windows long paths do not break testing. Generated files stay ignored by git.\n\n## ChromaDB\n\nThis repo includes the packaged Chroma database under:\n\n```txt\nrag/chroma_db/\n```\n\nThe folder contains `chroma.sqlite3` and the related Chroma index files for the `cambodian_laws` collection. These large files are stored with Git LFS.\n\nAt runtime, the backend copies the packaged database into:\n\n```txt\n.runtime/chroma_db/\n```\n\nThe runtime copy is ignored by git. This keeps the committed database clean while allowing Chroma/SQLite to create temporary journal files locally.\n\nDefault collection name:\n\n```txt\ncambodian_laws\n```\n\nIf you want to use a different packaged database, set `CHROMA_SOURCE_DIR` in `.env`:\n\n```txt\nCHROMA_SOURCE_DIR=D:\\path\\to\\your\\chroma_db\n```\n\nIf you want to bypass the runtime copy and point Chroma directly at a database, set `CHROMA_DIR`:\n\n```txt\nCHROMA_DIR=D:\\path\\to\\your\\runtime_chroma_db\n```\n\n## Run Locally\n\nStart the backend:\n\n```powershell\n.\\run_rag_api.ps1\n```\n\nBy default this starts:\n\n- FastAPI backend on `http://localhost:8000`\n- Telegram bot in the background when `RUN_TELEGRAM_BOT=true` and `TELEGRAM_BOT_TOKEN` is set\n\nHealth check:\n\n```txt\nGET http://localhost:8000/health\n```\n\nExpected response:\n\n```json\n{\n \"status\": \"ok\",\n \"service\": \"cambodian-legal-rag\",\n \"provider\": \"openrouter\"\n}\n```\n\nThe `provider` value follows your `LLM_PROVIDER` setting.\n\n## Environment Variables\n\nDefault `.env` shape:\n\n```txt\nLLM_PROVIDER=openrouter\nOPENROUTER_API_KEY=your_openrouter_api_key\nOPENROUTER_MODEL=openrouter/free\nOPENROUTER_BASE_URL=https://openrouter.ai/api/v1\nOPENROUTER_SITE_URL=http://localhost:3000\nOPENROUTER_APP_NAME=Feasible\nDEEPSEEK_API_KEY=your_deepseek_api_key\nDEEPSEEK_MODEL=deepseek-v4-flash\nDEEPSEEK_BASE_URL=https://api.deepseek.com\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\nRUN_TELEGRAM_BOT=true\nRAG_API_BASE_URL=http://localhost:8000\nRAG_REQUEST_TIMEOUT_SECONDS=60\n\nMONGODB_URI=mongodb+srv://username:password@cluster.example.mongodb.net/\nMONGODB_DB=lawkh\nMONGODB_TIMEOUT_MS=5000\nJWT_SECRET=change_this_for_local_dev\nJWT_ACCESS_TOKEN_MINUTES=1440\nPASSWORD_RESET_TOKEN_MINUTES=30\n\nLAW_ASSETS_DIR=law_assets/library\nLAW_ASSETS_INDEX=law_assets/index/documents.json\nLAW_ASSETS_SOURCE_DIR=D:\\path\\to\\Original Materials\n\nCHROMA_SOURCE_DIR=rag/chroma_db\nCHROMA_DIR=\nCHROMA_COLLECTION_NAME=cambodian_laws\nEMBEDDING_MODEL=intfloat/multilingual-e5-large\n\nOLLAMA_URL=http://localhost:11434\nOLLAMA_MODEL=gpt-oss:20b\n```\n\nTo run only the backend without Telegram:\n\n```txt\nRUN_TELEGRAM_BOT=false\n```\n\nTo switch generation to Ollama:\n\n```txt\nLLM_PROVIDER=ollama\nOLLAMA_URL=http://localhost:11434\nOLLAMA_MODEL=gpt-oss:20b\n```\n\nTo switch generation to DeepSeek:\n\n```txt\nLLM_PROVIDER=deepseek\nDEEPSEEK_API_KEY=your_deepseek_api_key\nDEEPSEEK_MODEL=deepseek-v4-flash\nDEEPSEEK_BASE_URL=https://api.deepseek.com\n```\n\nDeepSeek currently exposes these model IDs through `/models`:\n\n```txt\ndeepseek-v4-flash\ndeepseek-v4-pro\n```\n\n## API\n\n### `GET /health`\n\nReturns service status and active provider.\n\n### `POST /chat`\n\nMain endpoint for the Expo app and Telegram bot.\n\nThis endpoint is public. If the client sends `Authorization: Bearer <token>`, the backend attempts to save the turn under that user. If no token is sent, RAG answer generation still works.\n\nRequest:\n\n```json\n{\n \"question\": \"What are the tax registration requirements for a new company in Cambodia?\",\n \"chatId\": \"chat_optional\",\n \"history\": [\n {\n \"role\": \"user\",\n \"content\": \"I am setting up a company.\"\n }\n ],\n \"filters\": {\n \"categoryIds\": [\"tax\", \"business-registration\"],\n \"documentIds\": []\n }\n}\n```\n\nResponse:\n\n```json\n{\n \"chatId\": \"chat_abc123\",\n \"answer\": \"Generated legal answer...\",\n \"citations\": [\n {\n \"id\": \"cite_001\",\n \"title\": \"Law on Taxation\",\n \"fullCitation\": \"Law on Taxation (Tax)\",\n \"documentId\": \"tax/law-on-taxation-2023-pdf\",\n \"categoryId\": \"tax\",\n \"page\": null,\n \"excerpt\": \"Relevant source excerpt...\",\n \"score\": 0.82,\n \"pdfUrl\": \"http://localhost:8000/law/documents/tax%2Flaw-on-taxation-2023-pdf/download\",\n \"downloadUrl\": \"http://localhost:8000/law/documents/tax%2Flaw-on-taxation-2023-pdf/download\",\n \"chunkId\": \"123\",\n \"locationLabel\": \"OCR chunk 123\"\n }\n ]\n}\n```\n\nCitation metadata is intentionally source-driven. The backend does not ask the LLM to invent PDF pages. If a real PDF page is unavailable, `page`, `pageStart`, and `pageEnd` are `null`, and `locationLabel` describes the OCR chunk/source location instead.\n\nCitation `documentId` values match the law-library document IDs accepted by:\n\n```txt\nGET /law/documents/{documentId}\nGET /law/documents/{documentId}/download\n```\n\nDocument IDs may contain `/`, so frontend clients should URL-encode them before calling document endpoints, for example `tax%2Flaw-on-taxation-2023-pdf`.\n\nOther available endpoints:\n\n- `POST /auth/signup`\n- `POST /auth/login`\n- `POST /auth/password/forgot`\n- `POST /auth/password/reset`\n- `GET /auth/me`\n- `PATCH /auth/me`\n- `GET /me`\n- `PATCH /me`\n- `GET /chats`\n- `GET /chats/{chatId}`\n- `GET /law/categories`\n- `GET /law/categories/{categoryId}/documents`\n- `GET /law/documents/{documentId}`\n- `GET /law/documents/{documentId}/download`\n- `GET /citations/{citationId}`\n- `POST /api/query`\n- `POST /api/query/stream`\n\n### Auth Flow\n\nSignup request:\n\n```json\n{\n \"name\": \"User Name\",\n \"email\": \"user@example.com\",\n \"password\": \"Password123!\"\n}\n```\n\nSignup/login response:\n\n```json\n{\n \"accessToken\": \"jwt_token\",\n \"user\": {\n \"id\": \"mongo_user_id\",\n \"name\": \"User Name\",\n \"email\": \"user@example.com\",\n \"preferences\": {\n \"darkMode\": true\n }\n }\n}\n```\n\nForgot password is intentionally simple for the MVP. `POST /auth/password/forgot` returns a reset token directly when the email exists, then `POST /auth/password/reset` changes the password with that token.\n\n### Law Document Viewer\n\n`GET /law/documents/{documentId}` returns document metadata plus direct file URLs:\n\n```json\n{\n \"id\": \"labour/example-pdf\",\n \"categoryId\": \"labour\",\n \"title\": \"Example Labour Document\",\n \"subtitle\": \"Employment and worker protections\",\n \"year\": \"2024\",\n \"pages\": null,\n \"size\": \"1.2 MB\",\n \"pdfUrl\": \"http://localhost:8000/law/documents/labour%2Fexample-pdf/download\",\n \"fileUrl\": \"http://localhost:8000/law/documents/labour%2Fexample-pdf/download\",\n \"downloadUrl\": \"http://localhost:8000/law/documents/labour%2Fexample-pdf/download\",\n \"content\": \"This document is available for download: example.pdf\"\n}\n```\n\n`GET /law/documents/{documentId}/download` supports URL-encoded IDs and returns PDFs with inline browser-friendly headers:\n\n```txt\nContent-Type: application/pdf\nContent-Disposition: inline\nAccept-Ranges: bytes\n```\n\n## Expo App Usage\n\nUse these backend URLs from the Expo app:\n\n- Web on same machine: `http://localhost:8000`\n- Android emulator: `http://10.0.2.2:8000`\n- Physical device: `http://<your-lan-ip>:8000`\n\nThe Expo app should call:\n\n```txt\nPOST /chat\n```\n\nThe OpenRouter/DeepSeek key and Telegram token stay in this backend repo's `.env`, not inside the APK.\n\n## Telegram Bot Usage\n\nThe Telegram bot is started by `run_rag_api.ps1` when:\n\n```txt\nRUN_TELEGRAM_BOT=true\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\n```\n\nThe bot uses polling and sends user questions to:\n\n```txt\nhttp://localhost:8000/chat\n```\n\nOnly one polling instance can use the same Telegram bot token at a time. The startup script skips creating a duplicate local `bot.py` process, but Telegram will still return a `409 Conflict` if the same token is already running from another machine or hosting service.\n\nBot logs are written to:\n\n```txt\ntelegram_bot_stdout.log\ntelegram_bot_stderr.log\n```\n\nThese log files are ignored by git.\n\n## Git Notes\n\nDo not commit `.env`.\n\nThe Chroma DB files are intentionally committed through Git LFS so a clone can run local RAG without receiving the database through a separate channel.\n\nDo not commit law document ZIPs or generated law library files. The source ZIP/material folder should be prepared locally with `python -B scripts\\prepare_law_assets.py`; generated `law_assets/library/` and `law_assets/index/` are gitignored.\n"}, "b1f189c4703ffe95ffc147b0190b1bd257bac2cb": {"t": 1785766642.6538088, "n": 10436, "h": "93a7062884674a7c", "c": "# LawKhBack\n\nLocal-first backend for a Cambodian law assistant app.\n\nThis repository runs the backend API used by the Expo mobile app and the Telegram bot interface. It uses local ChromaDB retrieval, local sentence-transformer embeddings, and a configurable LLM provider for answer generation.\n\n## Overview\n\nLawKhBack is designed for a local demo or a normal server host where the Chroma database can live beside the backend. The API accepts legal questions, retrieves relevant Cambodian law chunks from ChromaDB, sends the retrieved context to the configured model provider, and returns an answer with structured citations.\n\nSupported clients:\n\n- Expo React Native APK / Android emulator\n- Telegram bot\n- Direct HTTP requests to the backend API\n\nDefault model provider:\n\n```txt\nLLM_PROVIDER=openrouter\nOPENROUTER_MODEL=openrouter/free\n```\n\nSupported generation providers:\n\n```txt\nopenrouter\ndeepseek\nollama\n```\n\n## Architecture\n\n```txt\nExpo app / Telegram bot\n -> FastAPI backend\n -> local ChromaDB\n -> local sentence-transformers embedding model\n -> OpenRouter / DeepSeek / Ollama chat completion API\n -> answer + citations\n```\n\n## Repository Layout\n\n```txt\nLawKhBack/\n README.md\n .env.example\n requirements.txt\n run_rag_api.ps1\n rag/\n server.py\n query.py\n chroma_db/\n auth_store.py\n law_assets.py\n scripts/\n prepare_law_assets.py\n law_assets/\n library/ (gitignored, generated locally)\n index/ (gitignored, generated locally)\n telegram_bot/\n bot.py\n api_client.py\n config.py\n .runtime/ (gitignored, created at startup)\n```\n\nImportant paths:\n\n- `rag/server.py` contains the FastAPI endpoints.\n- `rag/query.py` contains retrieval, prompt building, OpenRouter/DeepSeek/Ollama provider switching, and self-check logic.\n- `rag/chroma_db/` is where the local Chroma database should be placed.\n- `scripts/prepare_law_assets.py` prepares source law ZIP/material folders for the law library API.\n- `telegram_bot/` contains the Telegram polling bot that calls the same `/chat` backend endpoint.\n\n## Requirements\n\n- Python 3.10 or newer\n- Git LFS, used for the included ChromaDB files\n- OpenRouter API key or DeepSeek API key, depending on provider\n- MongoDB Atlas URI for signup, login, password reset, and saved chat history\n- Telegram bot token if using the Telegram interface\n\n## Setup\n\nClone the repository and enter the project folder:\n\n```powershell\ngit lfs install\ngit clone <your-repo-url>\ncd LawKhBack\ngit lfs pull\n```\n\nCreate a virtual environment:\n\n```powershell\npython -m venv .venv\n.\\.venv\\Scripts\\Activate.ps1\n```\n\nInstall dependencies:\n\n```powershell\npip install -r requirements.txt\n```\n\nCreate your local environment file:\n\n```powershell\nCopy-Item .env.example .env\n```\n\nEdit `.env` and set at least one provider key:\n\n```txt\nOPENROUTER_API_KEY=your_openrouter_api_key\n```\n\nor:\n\n```txt\nDEEPSEEK_API_KEY=your_deepseek_api_key\n```\n\nIf using Telegram, also set:\n\n```txt\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\n```\n\nFor auth and saved chat history, set:\n\n```txt\nMONGODB_URI=your_mongodb_connection_string\nMONGODB_DB=lawkh\nJWT_SECRET=replace_this_with_a_long_random_secret\n```\n\nIf Atlas rejects local connections, check the Atlas Network Access IP allowlist first. The backend uses MongoDB for auth and chat history; without a reachable Mongo URI, `/chat` still works but auth/history endpoints cannot persist data.\n\n## Law Documents\n\nThe law document ZIPs/source folders are intentionally not committed. Keep them outside git, then prepare them into the local gitignored library.\n\nSet this in `.env`:\n\n```txt\nLAW_ASSETS_SOURCE_DIR=D:\\Users\\aykay\\SProjects\\KhmerOCR\\Original Materials\nLAW_ASSETS_DIR=law_assets/library\nLAW_ASSETS_INDEX=law_assets/index/documents.json\n```\n\nPrepare the law library:\n\n```powershell\npython -B scripts\\prepare_law_assets.py\n```\n\nExpected output includes:\n\n```txt\nIndexed 1189 documents\n```\n\nThe preparation step copies/extracts documents into short local filenames so Windows long paths do not break testing. Generated files stay ignored by git.\n\n## ChromaDB\n\nThis repo includes the packaged Chroma database under:\n\n```txt\nrag/chroma_db/\n```\n\nThe folder contains `chroma.sqlite3` and the related Chroma index files for the `cambodian_laws` collection. These large files are stored with Git LFS.\n\nAt runtime, the backend copies the packaged database into:\n\n```txt\n.runtime/chroma_db/\n```\n\nThe runtime copy is ignored by git. This keeps the committed database clean while allowing Chroma/SQLite to create temporary journal files locally.\n\nDefault collection name:\n\n```txt\ncambodian_laws\n```\n\nIf you want to use a different packaged database, set `CHROMA_SOURCE_DIR` in `.env`:\n\n```txt\nCHROMA_SOURCE_DIR=D:\\path\\to\\your\\chroma_db\n```\n\nIf you want to bypass the runtime copy and point Chroma directly at a database, set `CHROMA_DIR`:\n\n```txt\nCHROMA_DIR=D:\\path\\to\\your\\runtime_chroma_db\n```\n\n## Run Locally\n\nStart the backend:\n\n```powershell\n.\\run_rag_api.ps1\n```\n\nBy default this starts:\n\n- FastAPI backend on `http://localhost:8000`\n- Telegram bot in the background when `RUN_TELEGRAM_BOT=true` and `TELEGRAM_BOT_TOKEN` is set\n\nHealth check:\n\n```txt\nGET http://localhost:8000/health\n```\n\nExpected response:\n\n```json\n{\n \"status\": \"ok\",\n \"service\": \"cambodian-legal-rag\",\n \"provider\": \"openrouter\"\n}\n```\n\nThe `provider` value follows your `LLM_PROVIDER` setting.\n\n## Environment Variables\n\nDefault `.env` shape:\n\n```txt\nLLM_PROVIDER=openrouter\nOPENROUTER_API_KEY=your_openrouter_api_key\nOPENROUTER_MODEL=openrouter/free\nOPENROUTER_BASE_URL=https://openrouter.ai/api/v1\nOPENROUTER_SITE_URL=http://localhost:3000\nOPENROUTER_APP_NAME=Feasible\nDEEPSEEK_API_KEY=your_deepseek_api_key\nDEEPSEEK_MODEL=deepseek-v4-flash\nDEEPSEEK_BASE_URL=https://api.deepseek.com\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\nRUN_TELEGRAM_BOT=true\nRAG_API_BASE_URL=http://localhost:8000\nRAG_REQUEST_TIMEOUT_SECONDS=60\n\nMONGODB_URI=mongodb+srv://username:password@cluster.example.mongodb.net/\nMONGODB_DB=lawkh\nMONGODB_TIMEOUT_MS=5000\nJWT_SECRET=change_this_for_local_dev\nJWT_ACCESS_TOKEN_MINUTES=1440\nPASSWORD_RESET_TOKEN_MINUTES=30\n\nLAW_ASSETS_DIR=law_assets/library\nLAW_ASSETS_INDEX=law_assets/index/documents.json\nLAW_ASSETS_SOURCE_DIR=D:\\path\\to\\Original Materials\n\nCHROMA_SOURCE_DIR=rag/chroma_db\nCHROMA_DIR=\nCHROMA_COLLECTION_NAME=cambodian_laws\nEMBEDDING_MODEL=intfloat/multilingual-e5-large\n\nOLLAMA_URL=http://localhost:11434\nOLLAMA_MODEL=gpt-oss:20b\n```\n\nTo run only the backend without Telegram:\n\n```txt\nRUN_TELEGRAM_BOT=false\n```\n\nTo switch generation to Ollama:\n\n```txt\nLLM_PROVIDER=ollama\nOLLAMA_URL=http://localhost:11434\nOLLAMA_MODEL=gpt-oss:20b\n```\n\nTo switch generation to DeepSeek:\n\n```txt\nLLM_PROVIDER=deepseek\nDEEPSEEK_API_KEY=your_deepseek_api_key\nDEEPSEEK_MODEL=deepseek-v4-flash\nDEEPSEEK_BASE_URL=https://api.deepseek.com\n```\n\nDeepSeek currently exposes these model IDs through `/models`:\n\n```txt\ndeepseek-v4-flash\ndeepseek-v4-pro\n```\n\n## API\n\n### `GET /health`\n\nReturns service status and active provider.\n\n### `POST /chat`\n\nMain endpoint for the Expo app and Telegram bot.\n\nThis endpoint is public. If the client sends `Authorization: Bearer <token>`, the backend attempts to save the turn under that user. If no token is sent, RAG answer generation still works.\n\nRequest:\n\n```json\n{\n \"question\": \"What are the tax registration requirements for a new company in Cambodia?\",\n \"chatId\": \"chat_optional\",\n \"history\": [\n {\n \"role\": \"user\",\n \"content\": \"I am setting up a company.\"\n }\n ],\n \"filters\": {\n \"categoryIds\": [\"tax\", \"business-registration\"],\n \"documentIds\": []\n }\n}\n```\n\nResponse:\n\n```json\n{\n \"chatId\": \"chat_abc123\",\n \"answer\": \"Generated legal answer...\",\n \"citations\": [\n {\n \"id\": \"cite_001\",\n \"title\": \"Law on Taxation\",\n \"fullCitation\": \"Law on Taxation (Tax)\",\n \"documentId\": \"tax_ocr/example.txt\",\n \"categoryId\": \"tax\",\n \"excerpt\": \"Relevant source excerpt...\",\n \"score\": 0.82\n }\n ]\n}\n```\n\nOther available endpoints:\n\n- `POST /auth/signup`\n- `POST /auth/login`\n- `POST /auth/password/forgot`\n- `POST /auth/password/reset`\n- `GET /auth/me`\n- `PATCH /auth/me`\n- `GET /me`\n- `PATCH /me`\n- `GET /chats`\n- `GET /chats/{chatId}`\n- `GET /law/categories`\n- `GET /law/categories/{categoryId}/documents`\n- `GET /law/documents/{documentId}`\n- `GET /law/documents/{documentId}/download`\n- `GET /citations/{citationId}`\n- `POST /api/query`\n- `POST /api/query/stream`\n\n### Auth Flow\n\nSignup request:\n\n```json\n{\n \"name\": \"User Name\",\n \"email\": \"user@example.com\",\n \"password\": \"Password123!\"\n}\n```\n\nSignup/login response:\n\n```json\n{\n \"accessToken\": \"jwt_token\",\n \"user\": {\n \"id\": \"mongo_user_id\",\n \"name\": \"User Name\",\n \"email\": \"user@example.com\",\n \"preferences\": {\n \"darkMode\": true\n }\n }\n}\n```\n\nForgot password is intentionally simple for the MVP. `POST /auth/password/forgot` returns a reset token directly when the email exists, then `POST /auth/password/reset` changes the password with that token.\n\n## Expo App Usage\n\nUse these backend URLs from the Expo app:\n\n- Web on same machine: `http://localhost:8000`\n- Android emulator: `http://10.0.2.2:8000`\n- Physical device: `http://<your-lan-ip>:8000`\n\nThe Expo app should call:\n\n```txt\nPOST /chat\n```\n\nThe OpenRouter/DeepSeek key and Telegram token stay in this backend repo's `.env`, not inside the APK.\n\n## Telegram Bot Usage\n\nThe Telegram bot is started by `run_rag_api.ps1` when:\n\n```txt\nRUN_TELEGRAM_BOT=true\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\n```\n\nThe bot uses polling and sends user questions to:\n\n```txt\nhttp://localhost:8000/chat\n```\n\nOnly one polling instance can use the same Telegram bot token at a time. The startup script skips creating a duplicate local `bot.py` process, but Telegram will still return a `409 Conflict` if the same token is already running from another machine or hosting service.\n\nBot logs are written to:\n\n```txt\ntelegram_bot_stdout.log\ntelegram_bot_stderr.log\n```\n\nThese log files are ignored by git.\n\n## Git Notes\n\nDo not commit `.env`.\n\nThe Chroma DB files are intentionally committed through Git LFS so a clone can run local RAG without receiving the database through a separate channel.\n\nDo not commit law document ZIPs or generated law library files. The source ZIP/material folder should be prepared locally with `python -B scripts\\prepare_law_assets.py`; generated `law_assets/library/` and `law_assets/index/` are gitignored.\n"}, "cdc16c8401a6abd01631be34ee431f8b015223e9": {"t": 1785766644.9409027, "n": 7160, "h": "0282cde0b77c8220", "c": "# LawKhBack\n\nLocal-first backend for a Cambodian law assistant app.\n\nThis repository runs the backend API used by the Expo mobile app and the Telegram bot interface. It uses local ChromaDB retrieval, local sentence-transformer embeddings, and a configurable LLM provider for answer generation.\n\n## Overview\n\nLawKhBack is designed for a local demo or a normal server host where the Chroma database can live beside the backend. The API accepts legal questions, retrieves relevant Cambodian law chunks from ChromaDB, sends the retrieved context to the configured model provider, and returns an answer with structured citations.\n\nSupported clients:\n\n- Expo React Native APK / Android emulator\n- Telegram bot\n- Direct HTTP requests to the backend API\n\nDefault model provider:\n\n```txt\nLLM_PROVIDER=openrouter\nOPENROUTER_MODEL=openrouter/free\n```\n\nSupported generation providers:\n\n```txt\nopenrouter\ndeepseek\nollama\n```\n\n## Architecture\n\n```txt\nExpo app / Telegram bot\n -> FastAPI backend\n -> local ChromaDB\n -> local sentence-transformers embedding model\n -> OpenRouter / DeepSeek / Ollama chat completion API\n -> answer + citations\n```\n\n## Repository Layout\n\n```txt\nLawKhBack/\n README.md\n .env.example\n requirements.txt\n run_rag_api.ps1\n rag/\n server.py\n query.py\n chroma_db/\n telegram_bot/\n bot.py\n api_client.py\n config.py\n .runtime/ (gitignored, created at startup)\n```\n\nImportant paths:\n\n- `rag/server.py` contains the FastAPI endpoints.\n- `rag/query.py` contains retrieval, prompt building, OpenRouter/DeepSeek/Ollama provider switching, and self-check logic.\n- `rag/chroma_db/` is where the local Chroma database should be placed.\n- `telegram_bot/` contains the Telegram polling bot that calls the same `/chat` backend endpoint.\n\n## Requirements\n\n- Python 3.10 or newer\n- Git LFS, used for the included ChromaDB files\n- OpenRouter API key or DeepSeek API key, depending on provider\n- Telegram bot token if using the Telegram interface\n\n## Setup\n\nClone the repository and enter the project folder:\n\n```powershell\ngit lfs install\ngit clone <your-repo-url>\ncd LawKhBack\ngit lfs pull\n```\n\nCreate a virtual environment:\n\n```powershell\npython -m venv .venv\n.\\.venv\\Scripts\\Activate.ps1\n```\n\nInstall dependencies:\n\n```powershell\npip install -r requirements.txt\n```\n\nCreate your local environment file:\n\n```powershell\nCopy-Item .env.example .env\n```\n\nEdit `.env` and set at least one provider key:\n\n```txt\nOPENROUTER_API_KEY=your_openrouter_api_key\n```\n\nor:\n\n```txt\nDEEPSEEK_API_KEY=your_deepseek_api_key\n```\n\nIf using Telegram, also set:\n\n```txt\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\n```\n\n## ChromaDB\n\nThis repo includes the packaged Chroma database under:\n\n```txt\nrag/chroma_db/\n```\n\nThe folder contains `chroma.sqlite3` and the related Chroma index files for the `cambodian_laws` collection. These large files are stored with Git LFS.\n\nAt runtime, the backend copies the packaged database into:\n\n```txt\n.runtime/chroma_db/\n```\n\nThe runtime copy is ignored by git. This keeps the committed database clean while allowing Chroma/SQLite to create temporary journal files locally.\n\nDefault collection name:\n\n```txt\ncambodian_laws\n```\n\nIf you want to use a different packaged database, set `CHROMA_SOURCE_DIR` in `.env`:\n\n```txt\nCHROMA_SOURCE_DIR=D:\\path\\to\\your\\chroma_db\n```\n\nIf you want to bypass the runtime copy and point Chroma directly at a database, set `CHROMA_DIR`:\n\n```txt\nCHROMA_DIR=D:\\path\\to\\your\\runtime_chroma_db\n```\n\n## Run Locally\n\nStart the backend:\n\n```powershell\n.\\run_rag_api.ps1\n```\n\nBy default this starts:\n\n- FastAPI backend on `http://localhost:8000`\n- Telegram bot in the background when `RUN_TELEGRAM_BOT=true` and `TELEGRAM_BOT_TOKEN` is set\n\nHealth check:\n\n```txt\nGET http://localhost:8000/health\n```\n\nExpected response:\n\n```json\n{\n \"status\": \"ok\",\n \"service\": \"cambodian-legal-rag\",\n \"provider\": \"openrouter\"\n}\n```\n\nThe `provider` value follows your `LLM_PROVIDER` setting.\n\n## Environment Variables\n\nDefault `.env` shape:\n\n```txt\nLLM_PROVIDER=openrouter\nOPENROUTER_API_KEY=your_openrouter_api_key\nOPENROUTER_MODEL=openrouter/free\nOPENROUTER_BASE_URL=https://openrouter.ai/api/v1\nOPENROUTER_SITE_URL=http://localhost:3000\nOPENROUTER_APP_NAME=Feasible\nDEEPSEEK_API_KEY=your_deepseek_api_key\nDEEPSEEK_MODEL=deepseek-v4-flash\nDEEPSEEK_BASE_URL=https://api.deepseek.com\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\nRUN_TELEGRAM_BOT=true\nRAG_API_BASE_URL=http://localhost:8000\nRAG_REQUEST_TIMEOUT_SECONDS=60\n\nCHROMA_SOURCE_DIR=rag/chroma_db\nCHROMA_DIR=\nCHROMA_COLLECTION_NAME=cambodian_laws\nEMBEDDING_MODEL=intfloat/multilingual-e5-large\n\nOLLAMA_URL=http://localhost:11434\nOLLAMA_MODEL=gpt-oss:20b\n```\n\nTo run only the backend without Telegram:\n\n```txt\nRUN_TELEGRAM_BOT=false\n```\n\nTo switch generation to Ollama:\n\n```txt\nLLM_PROVIDER=ollama\nOLLAMA_URL=http://localhost:11434\nOLLAMA_MODEL=gpt-oss:20b\n```\n\nTo switch generation to DeepSeek:\n\n```txt\nLLM_PROVIDER=deepseek\nDEEPSEEK_API_KEY=your_deepseek_api_key\nDEEPSEEK_MODEL=deepseek-v4-flash\nDEEPSEEK_BASE_URL=https://api.deepseek.com\n```\n\nDeepSeek currently exposes these model IDs through `/models`:\n\n```txt\ndeepseek-v4-flash\ndeepseek-v4-pro\n```\n\n## API\n\n### `GET /health`\n\nReturns service status and active provider.\n\n### `POST /chat`\n\nMain endpoint for the Expo app and Telegram bot.\n\nRequest:\n\n```json\n{\n \"question\": \"What are the tax registration requirements for a new company in Cambodia?\",\n \"chatId\": \"chat_optional\",\n \"history\": [\n {\n \"role\": \"user\",\n \"content\": \"I am setting up a company.\"\n }\n ],\n \"filters\": {\n \"categoryIds\": [\"tax\", \"business-registration\"],\n \"documentIds\": []\n }\n}\n```\n\nResponse:\n\n```json\n{\n \"chatId\": \"chat_abc123\",\n \"answer\": \"Generated legal answer...\",\n \"citations\": [\n {\n \"id\": \"cite_001\",\n \"title\": \"Law on Taxation\",\n \"fullCitation\": \"Law on Taxation (Tax)\",\n \"documentId\": \"tax_ocr/example.txt\",\n \"categoryId\": \"tax\",\n \"excerpt\": \"Relevant source excerpt...\",\n \"score\": 0.82\n }\n ]\n}\n```\n\nOther available endpoints:\n\n- `GET /chats`\n- `GET /law/categories`\n- `GET /law/categories/{categoryId}/documents`\n- `GET /law/documents/{documentId}`\n- `GET /citations/{citationId}`\n- `POST /api/query`\n- `POST /api/query/stream`\n\n## Expo App Usage\n\nUse these backend URLs from the Expo app:\n\n- Web on same machine: `http://localhost:8000`\n- Android emulator: `http://10.0.2.2:8000`\n- Physical device: `http://<your-lan-ip>:8000`\n\nThe Expo app should call:\n\n```txt\nPOST /chat\n```\n\nThe OpenRouter/DeepSeek key and Telegram token stay in this backend repo's `.env`, not inside the APK.\n\n## Telegram Bot Usage\n\nThe Telegram bot is started by `run_rag_api.ps1` when:\n\n```txt\nRUN_TELEGRAM_BOT=true\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\n```\n\nThe bot uses polling and sends user questions to:\n\n```txt\nhttp://localhost:8000/chat\n```\n\nBot logs are written to:\n\n```txt\ntelegram_bot_stdout.log\ntelegram_bot_stderr.log\n```\n\nThese log files are ignored by git.\n\n## Git Notes\n\nDo not commit `.env`.\n\nThe Chroma DB files are intentionally committed through Git LFS so a clone can run local RAG without receiving the database through a separate channel.\n"}, "c0367e54f57ca9412b08e5d5574229a4cabe43b9": {"t": 1785766647.0141058, "n": 6451, "h": "c87a98d7f00c1ca2", "c": "# LawKhBack\n\nLocal-first backend for a Cambodian law assistant app.\n\nThis repository runs the backend API used by the Expo mobile app and the Telegram bot interface. It uses local ChromaDB retrieval, local sentence-transformer embeddings, and OpenRouter for answer generation by default.\n\n## Overview\n\nLawKhBack is designed for a local demo or a normal server host where the Chroma database can live beside the backend. The API accepts legal questions, retrieves relevant Cambodian law chunks from ChromaDB, sends the retrieved context to an OpenRouter model, and returns an answer with structured citations.\n\nSupported clients:\n\n- Expo React Native APK / Android emulator\n- Telegram bot\n- Direct HTTP requests to the backend API\n\nDefault model provider:\n\n```txt\nLLM_PROVIDER=openrouter\nOPENROUTER_MODEL=openrouter/free\n```\n\n## Architecture\n\n```txt\nExpo app / Telegram bot\n -> FastAPI backend\n -> local ChromaDB\n -> local sentence-transformers embedding model\n -> OpenRouter chat completion API\n -> answer + citations\n```\n\n## Repository Layout\n\n```txt\nLawKhBack/\n README.md\n .env.example\n requirements.txt\n run_rag_api.ps1\n rag/\n server.py\n query.py\n chroma_db/\n telegram_bot/\n bot.py\n api_client.py\n config.py\n .runtime/ (gitignored, created at startup)\n```\n\nImportant paths:\n\n- `rag/server.py` contains the FastAPI endpoints.\n- `rag/query.py` contains retrieval, prompt building, OpenRouter/Ollama provider switching, and self-check logic.\n- `rag/chroma_db/` is where the local Chroma database should be placed.\n- `telegram_bot/` contains the Telegram polling bot that calls the same `/chat` backend endpoint.\n\n## Requirements\n\n- Python 3.10 or newer\n- Git LFS, used for the included ChromaDB files\n- OpenRouter API key\n- Telegram bot token if using the Telegram interface\n\n## Setup\n\nClone the repository and enter the project folder:\n\n```powershell\ngit lfs install\ngit clone <your-repo-url>\ncd LawKhBack\ngit lfs pull\n```\n\nCreate a virtual environment:\n\n```powershell\npython -m venv .venv\n.\\.venv\\Scripts\\Activate.ps1\n```\n\nInstall dependencies:\n\n```powershell\npip install -r requirements.txt\n```\n\nCreate your local environment file:\n\n```powershell\nCopy-Item .env.example .env\n```\n\nEdit `.env` and set at least:\n\n```txt\nOPENROUTER_API_KEY=your_openrouter_api_key\n```\n\nIf using Telegram, also set:\n\n```txt\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\n```\n\n## ChromaDB\n\nThis repo includes the packaged Chroma database under:\n\n```txt\nrag/chroma_db/\n```\n\nThe folder contains `chroma.sqlite3` and the related Chroma index files for the `cambodian_laws` collection. These large files are stored with Git LFS.\n\nAt runtime, the backend copies the packaged database into:\n\n```txt\n.runtime/chroma_db/\n```\n\nThe runtime copy is ignored by git. This keeps the committed database clean while allowing Chroma/SQLite to create temporary journal files locally.\n\nDefault collection name:\n\n```txt\ncambodian_laws\n```\n\nIf you want to use a different packaged database, set `CHROMA_SOURCE_DIR` in `.env`:\n\n```txt\nCHROMA_SOURCE_DIR=D:\\path\\to\\your\\chroma_db\n```\n\nIf you want to bypass the runtime copy and point Chroma directly at a database, set `CHROMA_DIR`:\n\n```txt\nCHROMA_DIR=D:\\path\\to\\your\\runtime_chroma_db\n```\n\n## Run Locally\n\nStart the backend:\n\n```powershell\n.\\run_rag_api.ps1\n```\n\nBy default this starts:\n\n- FastAPI backend on `http://localhost:8000`\n- Telegram bot in the background when `RUN_TELEGRAM_BOT=true` and `TELEGRAM_BOT_TOKEN` is set\n\nHealth check:\n\n```txt\nGET http://localhost:8000/health\n```\n\nExpected response:\n\n```json\n{\n \"status\": \"ok\",\n \"service\": \"cambodian-legal-rag\",\n \"provider\": \"openrouter\"\n}\n```\n\n## Environment Variables\n\nDefault `.env` shape:\n\n```txt\nLLM_PROVIDER=openrouter\nOPENROUTER_API_KEY=your_openrouter_api_key\nOPENROUTER_MODEL=openrouter/free\nOPENROUTER_BASE_URL=https://openrouter.ai/api/v1\nOPENROUTER_SITE_URL=http://localhost:3000\nOPENROUTER_APP_NAME=Feasible\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\nRUN_TELEGRAM_BOT=true\nRAG_API_BASE_URL=http://localhost:8000\nRAG_REQUEST_TIMEOUT_SECONDS=60\n\nCHROMA_SOURCE_DIR=rag/chroma_db\nCHROMA_DIR=\nCHROMA_COLLECTION_NAME=cambodian_laws\nEMBEDDING_MODEL=intfloat/multilingual-e5-large\n\nOLLAMA_URL=http://localhost:11434\nOLLAMA_MODEL=gpt-oss:20b\n```\n\nTo run only the backend without Telegram:\n\n```txt\nRUN_TELEGRAM_BOT=false\n```\n\nTo switch generation to Ollama:\n\n```txt\nLLM_PROVIDER=ollama\nOLLAMA_URL=http://localhost:11434\nOLLAMA_MODEL=gpt-oss:20b\n```\n\n## API\n\n### `GET /health`\n\nReturns service status and active provider.\n\n### `POST /chat`\n\nMain endpoint for the Expo app and Telegram bot.\n\nRequest:\n\n```json\n{\n \"question\": \"What are the tax registration requirements for a new company in Cambodia?\",\n \"chatId\": \"chat_optional\",\n \"history\": [\n {\n \"role\": \"user\",\n \"content\": \"I am setting up a company.\"\n }\n ],\n \"filters\": {\n \"categoryIds\": [\"tax\", \"business-registration\"],\n \"documentIds\": []\n }\n}\n```\n\nResponse:\n\n```json\n{\n \"chatId\": \"chat_abc123\",\n \"answer\": \"Generated legal answer...\",\n \"citations\": [\n {\n \"id\": \"cite_001\",\n \"title\": \"Law on Taxation\",\n \"fullCitation\": \"Law on Taxation (Tax)\",\n \"documentId\": \"tax_ocr/example.txt\",\n \"categoryId\": \"tax\",\n \"excerpt\": \"Relevant source excerpt...\",\n \"score\": 0.82\n }\n ]\n}\n```\n\nOther available endpoints:\n\n- `GET /chats`\n- `GET /law/categories`\n- `GET /law/categories/{categoryId}/documents`\n- `GET /law/documents/{documentId}`\n- `GET /citations/{citationId}`\n- `POST /api/query`\n- `POST /api/query/stream`\n\n## Expo App Usage\n\nUse these backend URLs from the Expo app:\n\n- Web on same machine: `http://localhost:8000`\n- Android emulator: `http://10.0.2.2:8000`\n- Physical device: `http://<your-lan-ip>:8000`\n\nThe Expo app should call:\n\n```txt\nPOST /chat\n```\n\nThe OpenRouter key and Telegram token stay in this backend repo's `.env`, not inside the APK.\n\n## Telegram Bot Usage\n\nThe Telegram bot is started by `run_rag_api.ps1` when:\n\n```txt\nRUN_TELEGRAM_BOT=true\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\n```\n\nThe bot uses polling and sends user questions to:\n\n```txt\nhttp://localhost:8000/chat\n```\n\nBot logs are written to:\n\n```txt\ntelegram_bot_stdout.log\ntelegram_bot_stderr.log\n```\n\nThese log files are ignored by git.\n\n## Git Notes\n\nDo not commit `.env`.\n\nThe Chroma DB files are intentionally committed through Git LFS so a clone can run local RAG without receiving the database through a separate channel.\n"}, "d8a18718fb34c051056860ed09b281bda4621ede": {"t": 1785766649.9078782, "n": 6787, "h": "7f1f7f376c60d84d", "c": "# LawKhBack\n\nLocal-first backend for a Cambodian law assistant app.\n\nThis repository runs the backend API used by the Expo mobile app and the Telegram bot interface. It uses local ChromaDB retrieval, local sentence-transformer embeddings, and OpenRouter for answer generation by default.\n\n## Overview\n\nLawKhBack is designed for a local demo or a normal server host where the Chroma database can live beside the backend. The API accepts legal questions, retrieves relevant Cambodian law chunks from ChromaDB, sends the retrieved context to an OpenRouter model, and returns an answer with structured citations.\n\nSupported clients:\n\n- Expo React Native APK / Android emulator\n- Telegram bot\n- Direct HTTP requests to the backend API\n\nDefault model provider:\n\n```txt\nLLM_PROVIDER=openrouter\nOPENROUTER_MODEL=openrouter/free\n```\n\n## Architecture\n\n```txt\nExpo app / Telegram bot\n -> FastAPI backend\n -> local ChromaDB\n -> local sentence-transformers embedding model\n -> OpenRouter chat completion API\n -> answer + citations\n```\n\n## Repository Layout\n\n```txt\nLawKhBack/\n README.md\n .env.example\n requirements.txt\n run_rag_api.ps1\n rag/\n server.py\n query.py\n chroma_db/\n telegram_bot/\n bot.py\n api_client.py\n config.py\n```\n\nImportant paths:\n\n- `rag/server.py` contains the FastAPI endpoints.\n- `rag/query.py` contains retrieval, prompt building, OpenRouter/Ollama provider switching, and self-check logic.\n- `rag/chroma_db/` is where the local Chroma database should be placed.\n- `telegram_bot/` contains the Telegram polling bot that calls the same `/chat` backend endpoint.\n\n## Requirements\n\n- Python 3.10 or newer\n- Git LFS, used for the included ChromaDB files\n- OpenRouter API key\n- Telegram bot token if using the Telegram interface\n\n## Setup\n\nClone the repository and enter the project folder:\n\n```powershell\ngit lfs install\ngit clone <your-repo-url>\ncd LawKhBack\ngit lfs pull\n```\n\nCreate a virtual environment:\n\n```powershell\npython -m venv .venv\n.\\.venv\\Scripts\\Activate.ps1\n```\n\nInstall dependencies:\n\n```powershell\npip install -r requirements.txt\n```\n\nCreate your local environment file:\n\n```powershell\nCopy-Item .env.example .env\n```\n\nEdit `.env` and set at least:\n\n```txt\nOPENROUTER_API_KEY=your_openrouter_api_key\n```\n\nIf using Telegram, also set:\n\n```txt\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\n```\n\n## ChromaDB\n\nThis repo includes the packaged Chroma database under:\n\n```txt\nrag/chroma_db/\n```\n\nThe folder contains `chroma.sqlite3` and the related Chroma index files for the `cambodian_laws` collection. These large files are stored with Git LFS.\n\nAt runtime, the backend copies the packaged database into:\n\n```txt\n.runtime/chroma_db/\n```\n\nThe runtime copy is ignored by git. This keeps the committed database clean while allowing Chroma/SQLite to create temporary journal files locally.\n\nDefault collection name:\n\n```txt\ncambodian_laws\n```\n\nIf you want to use a different packaged database, set `CHROMA_SOURCE_DIR` in `.env`:\n\n```txt\nCHROMA_SOURCE_DIR=D:\\path\\to\\your\\chroma_db\n```\n\nIf you want to bypass the runtime copy and point Chroma directly at a database, set `CHROMA_DIR`:\n\n```txt\nCHROMA_DIR=D:\\path\\to\\your\\runtime_chroma_db\n```\n\n## Run Locally\n\nStart the backend:\n\n```powershell\n.\\run_rag_api.ps1\n```\n\nBy default this starts:\n\n- FastAPI backend on `http://localhost:8000`\n- Telegram bot in the background when `RUN_TELEGRAM_BOT=true` and `TELEGRAM_BOT_TOKEN` is set\n\nHealth check:\n\n```txt\nGET http://localhost:8000/health\n```\n\nExpected response:\n\n```json\n{\n \"status\": \"ok\",\n \"service\": \"cambodian-legal-rag\",\n \"provider\": \"openrouter\"\n}\n```\n\n## Environment Variables\n\nDefault `.env` shape:\n\n```txt\nLLM_PROVIDER=openrouter\nOPENROUTER_API_KEY=your_openrouter_api_key\nOPENROUTER_MODEL=openrouter/free\nOPENROUTER_BASE_URL=https://openrouter.ai/api/v1\nOPENROUTER_SITE_URL=http://localhost:3000\nOPENROUTER_APP_NAME=Feasible\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\nRUN_TELEGRAM_BOT=true\nRAG_API_BASE_URL=http://localhost:8000\nRAG_REQUEST_TIMEOUT_SECONDS=60\n\nCHROMA_SOURCE_DIR=rag/chroma_db\nCHROMA_DIR=\nCHROMA_COLLECTION_NAME=cambodian_laws\nEMBEDDING_MODEL=intfloat/multilingual-e5-large\n\nOLLAMA_URL=http://localhost:11434\nOLLAMA_MODEL=gpt-oss:20b\n```\n\nTo run only the backend without Telegram:\n\n```txt\nRUN_TELEGRAM_BOT=false\n```\n\nTo switch generation to Ollama:\n\n```txt\nLLM_PROVIDER=ollama\nOLLAMA_URL=http://localhost:11434\nOLLAMA_MODEL=gpt-oss:20b\n```\n\n## API\n\n### `GET /health`\n\nReturns service status and active provider.\n\n### `POST /chat`\n\nMain endpoint for the Expo app and Telegram bot.\n\nRequest:\n\n```json\n{\n \"question\": \"What are the tax registration requirements for a new company in Cambodia?\",\n \"chatId\": \"chat_optional\",\n \"history\": [\n {\n \"role\": \"user\",\n \"content\": \"I am setting up a company.\"\n }\n ],\n \"filters\": {\n \"categoryIds\": [\"tax\", \"business-registration\"],\n \"documentIds\": []\n }\n}\n```\n\nResponse:\n\n```json\n{\n \"chatId\": \"chat_abc123\",\n \"answer\": \"Generated legal answer...\",\n \"citations\": [\n {\n \"id\": \"cite_001\",\n \"title\": \"Law on Taxation\",\n \"fullCitation\": \"Law on Taxation (Tax)\",\n \"documentId\": \"tax_ocr/example.txt\",\n \"categoryId\": \"tax\",\n \"excerpt\": \"Relevant source excerpt...\",\n \"score\": 0.82\n }\n ]\n}\n```\n\nOther available endpoints:\n\n- `GET /chats`\n- `GET /law/categories`\n- `GET /law/categories/{categoryId}/documents`\n- `GET /law/documents/{documentId}`\n- `GET /citations/{citationId}`\n- `POST /api/query`\n- `POST /api/query/stream`\n\n## Expo App Usage\n\nUse these backend URLs from the Expo app:\n\n- Web on same machine: `http://localhost:8000`\n- Android emulator: `http://10.0.2.2:8000`\n- Physical device: `http://<your-lan-ip>:8000`\n\nThe Expo app should call:\n\n```txt\nPOST /chat\n```\n\nThe OpenRouter key and Telegram token stay in this backend repo's `.env`, not inside the APK.\n\n## Telegram Bot Usage\n\nThe Telegram bot is started by `run_rag_api.ps1` when:\n\n```txt\nRUN_TELEGRAM_BOT=true\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\n```\n\nThe bot uses polling and sends user questions to:\n\n```txt\nhttp://localhost:8000/chat\n```\n\nBot logs are written to:\n\n```txt\ntelegram_bot_stdout.log\ntelegram_bot_stderr.log\n```\n\nThese log files are ignored by git.\n\n## Local Hosting Notes\n\nThis repo is intended for local hosting or a normal server host where local files can persist.\n\nIt is not currently a clean Vercel deployment target because it depends on:\n\n- local ChromaDB files\n- local embedding model loading\n- local persistent vector storage\n\nFor Vercel, the retrieval layer would need to move to hosted embeddings and a hosted vector database.\n\n## Git Notes\n\nDo not commit `.env`.\n\nThe Chroma DB files are intentionally committed through Git LFS so a clone can run local RAG without receiving the database through a separate channel.\n"}, "e67a3aebb5d702c58044ce24121e49ae7e2cf33c": {"t": 1785766651.428083, "n": 6339, "h": "ac227f593448a579", "c": "# LawKhBack\n\nLocal-first backend for a Cambodian law assistant app.\n\nThis repository runs the backend API used by the Expo mobile app and the Telegram bot interface. It uses local ChromaDB retrieval, local sentence-transformer embeddings, and OpenRouter for answer generation by default.\n\n## Overview\n\nLawKhBack is designed for a local demo or a normal server host where the Chroma database can live beside the backend. The API accepts legal questions, retrieves relevant Cambodian law chunks from ChromaDB, sends the retrieved context to an OpenRouter model, and returns an answer with structured citations.\n\nSupported clients:\n\n- Expo React Native APK / Android emulator\n- Telegram bot\n- Direct HTTP requests to the backend API\n\nDefault model provider:\n\n```txt\nLLM_PROVIDER=openrouter\nOPENROUTER_MODEL=openrouter/free\n```\n\n## Architecture\n\n```txt\nExpo app / Telegram bot\n -> FastAPI backend\n -> local ChromaDB\n -> local sentence-transformers embedding model\n -> OpenRouter chat completion API\n -> answer + citations\n```\n\n## Repository Layout\n\n```txt\nLawKhBack/\n README.md\n .env.example\n requirements.txt\n run_rag_api.ps1\n rag/\n server.py\n query.py\n chroma_db/\n telegram_bot/\n bot.py\n api_client.py\n config.py\n```\n\nImportant paths:\n\n- `rag/server.py` contains the FastAPI endpoints.\n- `rag/query.py` contains retrieval, prompt building, OpenRouter/Ollama provider switching, and self-check logic.\n- `rag/chroma_db/` is where the local Chroma database should be placed.\n- `telegram_bot/` contains the Telegram polling bot that calls the same `/chat` backend endpoint.\n\n## Requirements\n\n- Python 3.10 or newer\n- Git LFS, used for the included ChromaDB files\n- OpenRouter API key\n- Telegram bot token if using the Telegram interface\n\n## Setup\n\nClone the repository and enter the project folder:\n\n```powershell\ngit lfs install\ngit clone <your-repo-url>\ncd LawKhBack\ngit lfs pull\n```\n\nCreate a virtual environment:\n\n```powershell\npython -m venv .venv\n.\\.venv\\Scripts\\Activate.ps1\n```\n\nInstall dependencies:\n\n```powershell\npip install -r requirements.txt\n```\n\nCreate your local environment file:\n\n```powershell\nCopy-Item .env.example .env\n```\n\nEdit `.env` and set at least:\n\n```txt\nOPENROUTER_API_KEY=your_openrouter_api_key\n```\n\nIf using Telegram, also set:\n\n```txt\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\n```\n\n## ChromaDB\n\nThis repo includes the Chroma database under:\n\n```txt\nrag/chroma_db/\n```\n\nThe folder contains `chroma.sqlite3` and the related Chroma index files for the `cambodian_laws` collection. These large files are stored with Git LFS.\n\nDefault collection name:\n\n```txt\ncambodian_laws\n```\n\nIf you want to use a different database, set `CHROMA_DIR` in `.env`:\n\n```txt\nCHROMA_DIR=D:\\path\\to\\your\\chroma_db\n```\n\n## Run Locally\n\nStart the backend:\n\n```powershell\n.\\run_rag_api.ps1\n```\n\nBy default this starts:\n\n- FastAPI backend on `http://localhost:8000`\n- Telegram bot in the background when `RUN_TELEGRAM_BOT=true` and `TELEGRAM_BOT_TOKEN` is set\n\nHealth check:\n\n```txt\nGET http://localhost:8000/health\n```\n\nExpected response:\n\n```json\n{\n \"status\": \"ok\",\n \"service\": \"cambodian-legal-rag\",\n \"provider\": \"openrouter\"\n}\n```\n\n## Environment Variables\n\nDefault `.env` shape:\n\n```txt\nLLM_PROVIDER=openrouter\nOPENROUTER_API_KEY=your_openrouter_api_key\nOPENROUTER_MODEL=openrouter/free\nOPENROUTER_BASE_URL=https://openrouter.ai/api/v1\nOPENROUTER_SITE_URL=http://localhost:3000\nOPENROUTER_APP_NAME=Feasible\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\nRUN_TELEGRAM_BOT=true\nRAG_API_BASE_URL=http://localhost:8000\nRAG_REQUEST_TIMEOUT_SECONDS=60\n\nCHROMA_DIR=rag/chroma_db\nCHROMA_COLLECTION_NAME=cambodian_laws\nEMBEDDING_MODEL=intfloat/multilingual-e5-large\n\nOLLAMA_URL=http://localhost:11434\nOLLAMA_MODEL=gpt-oss:20b\n```\n\nTo run only the backend without Telegram:\n\n```txt\nRUN_TELEGRAM_BOT=false\n```\n\nTo switch generation to Ollama:\n\n```txt\nLLM_PROVIDER=ollama\nOLLAMA_URL=http://localhost:11434\nOLLAMA_MODEL=gpt-oss:20b\n```\n\n## API\n\n### `GET /health`\n\nReturns service status and active provider.\n\n### `POST /chat`\n\nMain endpoint for the Expo app and Telegram bot.\n\nRequest:\n\n```json\n{\n \"question\": \"What are the tax registration requirements for a new company in Cambodia?\",\n \"chatId\": \"chat_optional\",\n \"history\": [\n {\n \"role\": \"user\",\n \"content\": \"I am setting up a company.\"\n }\n ],\n \"filters\": {\n \"categoryIds\": [\"tax\", \"business-registration\"],\n \"documentIds\": []\n }\n}\n```\n\nResponse:\n\n```json\n{\n \"chatId\": \"chat_abc123\",\n \"answer\": \"Generated legal answer...\",\n \"citations\": [\n {\n \"id\": \"cite_001\",\n \"title\": \"Law on Taxation\",\n \"fullCitation\": \"Law on Taxation (Tax)\",\n \"documentId\": \"tax_ocr/example.txt\",\n \"categoryId\": \"tax\",\n \"excerpt\": \"Relevant source excerpt...\",\n \"score\": 0.82\n }\n ]\n}\n```\n\nOther available endpoints:\n\n- `GET /chats`\n- `GET /law/categories`\n- `GET /law/categories/{categoryId}/documents`\n- `GET /law/documents/{documentId}`\n- `GET /citations/{citationId}`\n- `POST /api/query`\n- `POST /api/query/stream`\n\n## Expo App Usage\n\nUse these backend URLs from the Expo app:\n\n- Web on same machine: `http://localhost:8000`\n- Android emulator: `http://10.0.2.2:8000`\n- Physical device: `http://<your-lan-ip>:8000`\n\nThe Expo app should call:\n\n```txt\nPOST /chat\n```\n\nThe OpenRouter key and Telegram token stay in this backend repo's `.env`, not inside the APK.\n\n## Telegram Bot Usage\n\nThe Telegram bot is started by `run_rag_api.ps1` when:\n\n```txt\nRUN_TELEGRAM_BOT=true\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\n```\n\nThe bot uses polling and sends user questions to:\n\n```txt\nhttp://localhost:8000/chat\n```\n\nBot logs are written to:\n\n```txt\ntelegram_bot_stdout.log\ntelegram_bot_stderr.log\n```\n\nThese log files are ignored by git.\n\n## Local Hosting Notes\n\nThis repo is intended for local hosting or a normal server host where local files can persist.\n\nIt is not currently a clean Vercel deployment target because it depends on:\n\n- local ChromaDB files\n- local embedding model loading\n- local persistent vector storage\n\nFor Vercel, the retrieval layer would need to move to hosted embeddings and a hosted vector database.\n\n## Git Notes\n\nDo not commit `.env`.\n\nThe Chroma DB files are intentionally committed through Git LFS so a clone can run local RAG without receiving the database through a separate channel.\n"}, "bee0890d21ce14c1cd3c61c4baf5076deed69fcf": {"t": 1785766652.7210138, "n": 6307, "h": "5e25612612e2a7c1", "c": "# LawKhBack\n\nLocal-first backend for a Cambodian law assistant app.\n\nThis repository runs the backend API used by the Expo mobile app and the Telegram bot interface. It uses local ChromaDB retrieval, local sentence-transformer embeddings, and OpenRouter for answer generation by default.\n\n## Overview\n\nLawKhBack is designed for a local demo or a normal server host where the Chroma database can live beside the backend. The API accepts legal questions, retrieves relevant Cambodian law chunks from ChromaDB, sends the retrieved context to an OpenRouter model, and returns an answer with structured citations.\n\nSupported clients:\n\n- Expo React Native APK / Android emulator\n- Telegram bot\n- Direct HTTP requests to the backend API\n\nDefault model provider:\n\n```txt\nLLM_PROVIDER=openrouter\nOPENROUTER_MODEL=openrouter/free\n```\n\n## Architecture\n\n```txt\nExpo app / Telegram bot\n -> FastAPI backend\n -> local ChromaDB\n -> local sentence-transformers embedding model\n -> OpenRouter chat completion API\n -> answer + citations\n```\n\n## Repository Layout\n\n```txt\nLawKhBack/\n README.md\n .env.example\n requirements.txt\n run_rag_api.ps1\n rag/\n server.py\n query.py\n chroma_db/\n telegram_bot/\n bot.py\n api_client.py\n config.py\n```\n\nImportant paths:\n\n- `rag/server.py` contains the FastAPI endpoints.\n- `rag/query.py` contains retrieval, prompt building, OpenRouter/Ollama provider switching, and self-check logic.\n- `rag/chroma_db/` is where the local Chroma database should be placed.\n- `telegram_bot/` contains the Telegram polling bot that calls the same `/chat` backend endpoint.\n\n## Requirements\n\n- Python 3.10 or newer\n- A local ChromaDB folder for the `cambodian_laws` collection\n- OpenRouter API key\n- Telegram bot token if using the Telegram interface\n\n## Setup\n\nClone the repository and enter the project folder:\n\n```powershell\ngit clone <your-repo-url>\ncd LawKhBack\n```\n\nCreate a virtual environment:\n\n```powershell\npython -m venv .venv\n.\\.venv\\Scripts\\Activate.ps1\n```\n\nInstall dependencies:\n\n```powershell\npip install -r requirements.txt\n```\n\nCreate your local environment file:\n\n```powershell\nCopy-Item .env.example .env\n```\n\nEdit `.env` and set at least:\n\n```txt\nOPENROUTER_API_KEY=your_openrouter_api_key\n```\n\nIf using Telegram, also set:\n\n```txt\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\n```\n\n## Attach ChromaDB\n\nPlace the Chroma database files inside:\n\n```txt\nrag/chroma_db/\n```\n\nThe folder should contain `chroma.sqlite3` and the related Chroma index files.\n\nDefault collection name:\n\n```txt\ncambodian_laws\n```\n\nIf your database lives somewhere else, set `CHROMA_DIR` in `.env`:\n\n```txt\nCHROMA_DIR=D:\\path\\to\\your\\chroma_db\n```\n\n## Run Locally\n\nStart the backend:\n\n```powershell\n.\\run_rag_api.ps1\n```\n\nBy default this starts:\n\n- FastAPI backend on `http://localhost:8000`\n- Telegram bot in the background when `RUN_TELEGRAM_BOT=true` and `TELEGRAM_BOT_TOKEN` is set\n\nHealth check:\n\n```txt\nGET http://localhost:8000/health\n```\n\nExpected response:\n\n```json\n{\n \"status\": \"ok\",\n \"service\": \"cambodian-legal-rag\",\n \"provider\": \"openrouter\"\n}\n```\n\n## Environment Variables\n\nDefault `.env` shape:\n\n```txt\nLLM_PROVIDER=openrouter\nOPENROUTER_API_KEY=your_openrouter_api_key\nOPENROUTER_MODEL=openrouter/free\nOPENROUTER_BASE_URL=https://openrouter.ai/api/v1\nOPENROUTER_SITE_URL=http://localhost:3000\nOPENROUTER_APP_NAME=Feasible\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\nRUN_TELEGRAM_BOT=true\nRAG_API_BASE_URL=http://localhost:8000\nRAG_REQUEST_TIMEOUT_SECONDS=60\n\nCHROMA_DIR=rag/chroma_db\nCHROMA_COLLECTION_NAME=cambodian_laws\nEMBEDDING_MODEL=intfloat/multilingual-e5-large\n\nOLLAMA_URL=http://localhost:11434\nOLLAMA_MODEL=gpt-oss:20b\n```\n\nTo run only the backend without Telegram:\n\n```txt\nRUN_TELEGRAM_BOT=false\n```\n\nTo switch generation to Ollama:\n\n```txt\nLLM_PROVIDER=ollama\nOLLAMA_URL=http://localhost:11434\nOLLAMA_MODEL=gpt-oss:20b\n```\n\n## API\n\n### `GET /health`\n\nReturns service status and active provider.\n\n### `POST /chat`\n\nMain endpoint for the Expo app and Telegram bot.\n\nRequest:\n\n```json\n{\n \"question\": \"What are the tax registration requirements for a new company in Cambodia?\",\n \"chatId\": \"chat_optional\",\n \"history\": [\n {\n \"role\": \"user\",\n \"content\": \"I am setting up a company.\"\n }\n ],\n \"filters\": {\n \"categoryIds\": [\"tax\", \"business-registration\"],\n \"documentIds\": []\n }\n}\n```\n\nResponse:\n\n```json\n{\n \"chatId\": \"chat_abc123\",\n \"answer\": \"Generated legal answer...\",\n \"citations\": [\n {\n \"id\": \"cite_001\",\n \"title\": \"Law on Taxation\",\n \"fullCitation\": \"Law on Taxation (Tax)\",\n \"documentId\": \"tax_ocr/example.txt\",\n \"categoryId\": \"tax\",\n \"excerpt\": \"Relevant source excerpt...\",\n \"score\": 0.82\n }\n ]\n}\n```\n\nOther available endpoints:\n\n- `GET /chats`\n- `GET /law/categories`\n- `GET /law/categories/{categoryId}/documents`\n- `GET /law/documents/{documentId}`\n- `GET /citations/{citationId}`\n- `POST /api/query`\n- `POST /api/query/stream`\n\n## Expo App Usage\n\nUse these backend URLs from the Expo app:\n\n- Web on same machine: `http://localhost:8000`\n- Android emulator: `http://10.0.2.2:8000`\n- Physical device: `http://<your-lan-ip>:8000`\n\nThe Expo app should call:\n\n```txt\nPOST /chat\n```\n\nThe OpenRouter key and Telegram token stay in this backend repo's `.env`, not inside the APK.\n\n## Telegram Bot Usage\n\nThe Telegram bot is started by `run_rag_api.ps1` when:\n\n```txt\nRUN_TELEGRAM_BOT=true\nTELEGRAM_BOT_TOKEN=your_telegram_bot_token\n```\n\nThe bot uses polling and sends user questions to:\n\n```txt\nhttp://localhost:8000/chat\n```\n\nBot logs are written to:\n\n```txt\ntelegram_bot_stdout.log\ntelegram_bot_stderr.log\n```\n\nThese log files are ignored by git.\n\n## Local Hosting Notes\n\nThis repo is intended for local hosting or a normal server host where local files can persist.\n\nIt is not currently a clean Vercel deployment target because it depends on:\n\n- local ChromaDB files\n- local embedding model loading\n- local persistent vector storage\n\nFor Vercel, the retrieval layer would need to move to hosted embeddings and a hosted vector database.\n\n## Git Notes\n\nDo not commit `.env`.\n\nThe repository includes `rag/chroma_db/.gitkeep` so the Chroma folder exists in fresh clones. If you want the Chroma database to travel with the repo, commit the actual Chroma DB files under `rag/chroma_db/`.\n"}}}}