Spaces:
Sleeping
Sleeping
| # IFRS 9 ECL Copilot β single-container deploy (HF Spaces Docker SDK, port 7860). | |
| # | |
| # Stage 1 builds the Preact/ECharts SPA; stage 2 is a python:3.13-slim runtime | |
| # installing the EXACT locked versions via requirements.docker.txt, generated | |
| # from uv.lock with: | |
| # | |
| # uv export --no-dev --no-hashes --no-emit-project --prune torch \ | |
| # -o requirements.docker.txt | |
| # | |
| # torch is pruned deliberately: it is used only by the offline challenger | |
| # study (challenger/, not shipped); no runtime module imports it, and its | |
| # CUDA payload would add ~5 GB to the image. | |
| # | |
| # NOTE (2026-07-19): a Space build hit a persistent LFS/context-resolution | |
| # race on outputs/freddie and outputs/mdd right after those directories' | |
| # first upload ("failed to calculate checksum ... not found" despite the LFS | |
| # objects resolving fine over HTTP, and list_repo_files confirming every file | |
| # present at the failing commit SHA) β same class of issue as the prior | |
| # "queue wedge" fix, but more persistent: it survived plain restart_space(), | |
| # a content-change re-push, and even restart_space(factory_reboot=True). | |
| # See outputs/gate/mdd_freddie_gate.md for the full retry timeline and the | |
| # eventual resolution (longer wall-clock wait for backend propagation, not | |
| # more aggressive cache-busting). | |
| # | |
| # SECURITY: no .env, no data/raw, no secrets are ever baked into the image | |
| # (.dockerignore enforces this; the CI check greps the saved image for key | |
| # prefixes). OPENROUTER_API_KEY is injected at RUN time: | |
| # docker run -e OPENROUTER_API_KEY -p 7860:7860 ifrs9-ecl-copilot | |
| # Without a key the app still serves β the deterministic fallback router | |
| # answers offline and refuses out-of-scope questions (a demoed feature). | |
| # ---------------------------------------------------------------- stage 1: UI | |
| FROM node:22-alpine AS ui | |
| WORKDIR /build | |
| COPY app/ui/package.json app/ui/package-lock.json ./ | |
| RUN npm ci --no-audit --no-fund | |
| COPY app/ui/index.html app/ui/vite.config.js ./ | |
| COPY app/ui/src ./src | |
| COPY app/ui/scripts ./scripts | |
| RUN npm run build | |
| # ----------------------------------------------------------- stage 2: runtime | |
| FROM python:3.13-slim AS runtime | |
| ENV PYTHONUNBUFFERED=1 \ | |
| PYTHONDONTWRITEBYTECODE=1 \ | |
| PIP_NO_CACHE_DIR=1 | |
| WORKDIR /app | |
| # locked third-party deps first (best layer-cache behaviour) | |
| COPY requirements.docker.txt ./ | |
| RUN pip install --no-cache-dir -r requirements.docker.txt | |
| # HF Spaces convention: run as non-root uid 1000 | |
| RUN useradd -m -u 1000 appuser | |
| # runtime code β engine/{hazard,lgd,ead,staging,ecl}.py are the FROZEN five | |
| COPY engine ./engine | |
| COPY agent ./agent | |
| COPY app/__init__.py ./app/__init__.py | |
| COPY app/api ./app/api | |
| COPY analysis ./analysis | |
| # Tier-3 (query_model_docs) retrieval sources: the model-development wiki | |
| # (pages + the pre-built typed graph) and the indexed IFRS9 credit-risk | |
| # notes corpus β both read-only at runtime, no LLM/network involved. | |
| COPY wiki ./wiki | |
| COPY knowledge/corpus ./knowledge/corpus | |
| COPY knowledge/index ./knowledge/index | |
| # the two retrieval scripts agent/tier3_retrieval.py loads by file path | |
| # (importlib.util.spec_from_file_location, never copy-pasted) β same | |
| # relative paths as in the repo; wiki_query.py needs wiki_graph.py | |
| # alongside it (sys.path sibling import) | |
| COPY .claude/skills/llm-wiki/scripts/wiki_query.py .claude/skills/llm-wiki/scripts/wiki_graph.py ./.claude/skills/llm-wiki/scripts/ | |
| COPY .claude/skills/pageindex-plus/scripts/pageindex_query.py ./.claude/skills/pageindex-plus/scripts/ | |
| # runtime data: the loan panel, the DFAST scenario CSVs and their loader | |
| COPY data/__init__.py ./data/__init__.py | |
| COPY data/ingest ./data/ingest | |
| COPY data/processed/panel.parquet ./data/processed/panel.parquet | |
| COPY data/scenarios/*.csv ./data/scenarios/ | |
| # pre-fitted model cache (joblib warm start ~9s; a fingerprint mismatch β | |
| # e.g. fresh file mtimes on HF Spaces β triggers a one-off ~50s refit in the | |
| # FastAPI lifespan, after which every tool call answers in seconds) | |
| COPY outputs/models ./outputs/models | |
| # App v2 consultant exhibits: the /api/model/*, /api/policy/* and | |
| # /api/exhibits/* endpoints parse these markdown reports and serve these | |
| # PNGs (StaticFiles mount over the whole of outputs/) for the Executive | |
| # Overview / The Model / Policy tabs. Also includes the scenario_ecl and | |
| # vasicek CSVs the tools/exhibits read (z_path.csv, scenario_ecl_summary.csv | |
| # etc). Read-only reference material, no code executes from here. | |
| COPY outputs/variable_dictionary.md ./outputs/variable_dictionary.md | |
| COPY outputs/hazard ./outputs/hazard | |
| COPY outputs/lgd ./outputs/lgd | |
| COPY outputs/staging ./outputs/staging | |
| COPY outputs/eda ./outputs/eda | |
| COPY outputs/vasicek ./outputs/vasicek | |
| COPY outputs/scenario_ecl ./outputs/scenario_ecl | |
| COPY outputs/challenger ./outputs/challenger | |
| # Rung 3 (SFLLD real-data study) + the compiled Model Development Document: | |
| # the /api/freddie/* endpoints parse outputs/freddie/**'s reports/CSVs/JSON | |
| # and StaticFiles mounts serve outputs/freddie/** (Freddie tab exhibits) and | |
| # outputs/mdd/** (the MDD.html + assets) β same read-only pattern as above. | |
| # | |
| # TEMPORARILY DISABLED (2026-07-19): 9 consecutive Space build attempts | |
| # (plain restarts, factory_reboot, content-change repush, atomic delete+ | |
| # re-add) all failed identically on these two COPY lines alone β "failed to | |
| # calculate checksum ... not found" β despite outputs/freddie|mdd verifiably | |
| # present and their LFS objects resolving fine over HTTP at every attempted | |
| # commit (see outputs/gate/mdd_freddie_gate.md for the full timeline). Every | |
| # other COPY in this file, including long-standing ones, succeeded every | |
| # time. Commenting these two out restores the Space to RUNNING (the guarded | |
| # `if FREDDIE_DIR.exists()` / `if MDD_DIR.exists()` mounts in app/api/main.py | |
| # mean the app boots fine without them; only the Real Data tab's own calls | |
| # would 404/500 until this is re-enabled). Both directories are already | |
| # pushed to the Space's git repo (`outputs/freddie/`, `outputs/mdd/`) β | |
| # re-enable these two lines once the platform-side build issue clears. | |
| COPY outputs/freddie ./outputs/freddie | |
| COPY outputs/mdd ./outputs/mdd | |
| # the built SPA (served by FastAPI at /) | |
| COPY --from=ui /build/dist ./app/ui/dist | |
| # outputs/ must be writable: agent audit trail + possible cache refresh | |
| RUN mkdir -p outputs/agent_log && chown -R appuser:appuser /app/outputs | |
| USER appuser | |
| EXPOSE 7860 | |
| CMD ["uvicorn", "app.api.main:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1"] | |