| """Financing Starts Now β runtime client. |
| |
| Talks to the Modal backend (modal.App("financing-starts-now")) over HTTP, OR |
| serves canned data when FINANCING_MOCK is set β same signatures either way, so |
| the UI never branches on mode. The HF Space deploy injects |
| FINANCING_STARTS_NOW_API_URL. |
| """ |
| from __future__ import annotations |
|
|
| import os |
|
|
| import httpx |
|
|
| API_URL = os.environ.get( |
| "FINANCING_STARTS_NOW_API_URL", |
| "https://rafalbogusdxc--financing-starts-now-api.modal.run", |
| ).rstrip("/") |
|
|
| MOCK = os.environ.get("FINANCING_MOCK", "").strip().lower() in ("1", "true", "yes", "on") |
|
|
| TIMEOUT_S = 30 |
|
|
|
|
| class BackendError(RuntimeError): |
| """Inference backend unreachable or returned an error.""" |
|
|
|
|
| def health() -> dict: |
| if MOCK: |
| return {"status": "mock"} |
| try: |
| resp = httpx.get(f"{API_URL}/health", timeout=10, follow_redirects=True) |
| resp.raise_for_status() |
| return resp.json() |
| except Exception: |
| return {"status": "unreachable"} |
|
|
|
|
| def genie_summary(path_key: str, profile_sentence: str, matches: list[dict]) -> tuple[str, bool]: |
| """Personalized recommendation text for this answer path. |
| |
| Returns (markdown_text, was_cached). Raises BackendError on failure. |
| """ |
| if MOCK: |
| top = matches[0] |
| runner = matches[1] if len(matches) > 1 else None |
| text = ( |
| f"*Poof!* The scrolls are clear. As {profile_sentence.split(';')[0].strip()}, " |
| f"your strongest path is **{top['name']}** β {top['amount_label']}. " |
| f"It scores **{top['score']}%** against your answers: your stage and funding " |
| f"needs sit squarely inside this programme's range, and the door is open " |
| f"({top['deadline_label']}). Start with: {top['apply'][0].lower()}." |
| ) |
| if runner: |
| text += ( |
| f"\n\nKeep **{runner['name']}** ({runner['score']}%) as your second wish β " |
| f"{runner['amount_label']}. Choose it if the first path stalls." |
| ) |
| text += "\n\n*(mock genie β set FINANCING_MOCK=0 and deploy Modal for the real one)*" |
| return text, True |
|
|
| try: |
| resp = httpx.post( |
| f"{API_URL}/genie", |
| json={ |
| "path_key": path_key, |
| "profile_sentence": profile_sentence, |
| "matches": matches, |
| }, |
| timeout=TIMEOUT_S, |
| follow_redirects=True, |
| ) |
| resp.raise_for_status() |
| data = resp.json() |
| return data["summary"], bool(data.get("cached")) |
| except httpx.HTTPStatusError as e: |
| raise BackendError(f"Backend error {e.response.status_code}: " |
| f"{e.response.text[:300]}") from e |
| except httpx.HTTPError as e: |
| raise BackendError(f"Cannot reach the Genie backend at {API_URL} ({e})") from e |
|
|