Spaces:
Sleeping
Sleeping
Commit History
-
[docs] Neutral comments: drop self-promotion framing; describe behaviour, not the model
Hélder Monteiro +1: CodexComment-only cleanup. The intake attribution comments no longer read like the app's purpose is marketing the author (removed 'carries the other follow links…' etc.) — they now just say what the element is. The _is_weak_reasoning docstring describes the unusable-response condition rather than labelling 'the model'. No behaviour change. Co-authored-by: Codex <noreply@openai.com>407af0d -
[ux] Simplify intake credit to 'Made by @helmo for the … Hackathon'
Hélder Monteiro +1: CodexDrop the built-by name + LinkedIn/HF icons; the HF profile (@helmo) already carries the follow links and the Space lists the author. One quiet line on the first screen only, with @helmo linking to the HF profile. Co-authored-by: Codex <noreply@openai.com>644e47e -
[ux] Move credit to the intake screen only; LinkedIn/HF as logo-icons
Hélder Monteiro +1: CodexPer preference: show the author credit only on the first (intake) screen, not across the flow. LinkedIn and Hugging Face are now logo-icons (monochrome, inherit currentColor to stay on-brand) rather than text links, and the 'Made for the Hugging Face Build Small Hackathon' line sits on its own row below: · Built by Hélder Monteiro · [in] · 🤗 · Made for the Hugging Face Build Small Hackathon Lives inside the intake column so it hides with the screen; removed the previous global footer from app.py. Co-authored-by: Codex <noreply@openai.com>ebc2550 -
[ux] Add discreet credit footer (LinkedIn, Hugging Face, hackathon tag)
Hélder Monteiro +1: CodexA small bottom-of-page credit so visitors/judges can find and follow the author, without intruding on the calm, private flow shown to a vulnerable person — kept out of the site bar and the interview/assessment screens. Links open in a new tab; styled from DESIGN.md tokens (muted text, teal links). Rendered as a <div> since GLOBAL_CSS hides Gradio's own <footer>. Co-authored-by: Codex <noreply@openai.com>7005424 -
[ux] Hide stray 'processing' overlay on recommendation card actions
Hélder Monteiro +1: CodexSelecting a country (and revealing the cards from the assessment) only re-renders HTML — pure Python, instant — but Gradio's generic per-component progress overlay still painted a 'processing…' spinner over the card being updated. Set show_progress='hidden' on the card-select clicks and the assessment->recommendations reveal, matching the interview fix. Co-authored-by: Codex <noreply@openai.com>c2877c3 -
[assess] Agentic guard against junk/jailbreak input; tidy economic UX
Hélder Monteiro +1: CodexPer feedback that a smart agent should refuse to assess garbage rather than fabricate a result — and that this must be MODEL REASONING, not hardcoded prose matching: - Prompt: a precedence GATE checked before any category. If the answers are nonsensical / test strings / code / contradictory / devoid of real info, the agent sets case_type=unclear, leaves grounds+countries empty, and asks for a genuine account. 'No protection ground' now requires a genuine account — junk is unclear, never economic (the bucket the model was wrongly falling through to). - assessment.py: an unclear case yields NO recommendations (don't invent destinations from junk); _synth_reasoning returns a clean 'I couldn't determine your situation' message instead of a protection narrative. - recommendations.py: a 'needs more info' panel (no cards, no roadmap, proceed hidden) when there are no recs; the economic intro is trimmed to one line since the full honest explanation already lives in the assessm…9c6656b -
[fix] Give Ollama a real context window (root cause); deterministic weak-check
Hélder Monteiro +1: CodexRoot cause of the 'assessment acts like a chatbot / refuses' behavior: Ollama loaded the model with its default ~4096-token context, which silently TRUNCATED the assessment prompt (system + facts + injected guideline/country tool results) and dropped the instructions — so the model drifted to a greeting. Fix the cause: - agent/loop.py passes options.num_ctx (env NUM_CTX, default 16384) on every chat. - deploy/modal_app.py loads the model with OLLAMA_CONTEXT_LENGTH=NUM_CTX so the server matches the client (no reload on first request). With 16k context lfm2.5:8b now produces a correct, structured assessment on its own (verified over Modal: refugee · sexual-orientation PSG · high · 3 cards). Per feedback, replace the brittle prose phrase-matching weak-detector with a DETERMINISTIC signal: the answer is weak only when the model produced no structured output (case_type/grounds/countries) or almost no text — no guessing at non-deterministic wording. The interview-grounded fallback then r…cd3c555 -
[fix] Treat chatbot-greeting assessments as weak (use grounded fallback)
Hélder Monteiro +1: Codexlfm2.5:8b sometimes answered the assessment with a conversational greeting that asked the person for info it already had ('Could you tell me your preferred language?… please share … when you're ready'). That isn't a refusal, so it slipped past _is_weak_reasoning and leaked to the screen. Add greeting / request-for-info markers so these are treated as weak and replaced by the deterministic interview-grounded analysis + recommendations. Co-authored-by: Codex <noreply@openai.com>e88a54f -
[fix] Robust assessment + 2-3 recs + documents progress
Hélder Monteiro +1: CodexThree issues found in the live test (small-model quality + UX), not deployment: 1. Assessment could show a refusal ('I cannot determine a case type…'). The rich deterministic fallback only triggered when the narration was <120 chars, so a long non-answer leaked. Add _is_weak_reasoning() (refusal markers + length); when weak, take the interview-derived case_type/grounds/risk and the structured _synth_reasoning instead of the model's non-answer. 2. Recommendations always showed ONE country (we only topped up when the list was empty). Now top up protection cases to 2-3 using the person's own stated destination_preferences (looked up, signatory-only), then strong curated systems; cap at 3. Algeria case now -> Sweden/France/Italy. 3. 'Prepare my documents' looked frozen during the LLM draft. show_documents is now an async generator: reveal the docs screen with a 'Preparing your document package…' status immediately, then render the finished Word+PDF package. C…6614702
-
[assess] Remove web search entirely (prompt, code, module, test, env)
Hélder Monteiro +1: CodexThe model was narrating web searches and 'current information' it could not actually do, because the assessment PROMPT still told it to use web_search even though the tool was unwired (not in ASSESSMENT_TOOLS). That both misled the user ('feels like web search is enabled') and risked fabricated 'current' claims. - Prompt: drop all web_search references; state plainly there is no web access and to never claim current/latest info it cannot retrieve. - assessment.py: remove the dead web_search status chip and the now-moot outbound-query privacy guard; update the docstring. - Delete the dormant agent/tools/web_search.py module (imported nowhere active). - Tests: drop test_web_search_real and the Tavily gating (assessment uses only local tools now). - .env.example: remove the Tavily key + stale per-phase model vars; note there is no web search. Co-authored-by: Codex <noreply@openai.com>79d3fac -
[fix] Economic recommendations follow the profile, not a hardcoded Gulf list
Hélder Monteiro +1: CodexThe cards contradicted the assessment: the reasoning sensibly suggested Germany/Norway/Canada for a skilled EU resident, but the cards always showed UAE/Qatar/Saudi because economic cases were hardcoded to work_route_countries(). Economic destinations are WORK routes, not asylum, so the model's profile-aware picks are appropriate (and a skilled person needs Western/EU routes, a low-skilled worker needs Gulf/regional). Now use the model's recommended countries (any country), fall back to its looked-up ones, then the curated Gulf shortlist only as a last resort. Cards/roadmap render gracefully for non-Gulf destinations: a 'Regular / skilled-worker visa' label, region, a generic 'check official immigration site' caveat, and the kafala note only where kafala applies. Co-authored-by: Codex <noreply@openai.com>2349f80 -
[fix] Hide doubled 'processing' spinner; never leak @@END / metadata
Hélder Monteiro +1: Codex1) Two 'processing…' overlays appeared in the interview responder because the choice controls are now always mounted-visible, so Gradio painted its generic per-component progress spinner on each. Set show_progress='hidden' on the interview events (Begin + Continue + reveal); the conversation itself signals progress and the assessment has its own bar. 2) The assessment leaked '@@END' and a raw 'Summary of my assessment' list when the small model wrote the metadata as prose instead of the @@ASSESSMENT block. parse_assessment now recognises the metadata in three forms (the @@ASSESSMENT block, a 'Summary of my assessment' section, or a bare run of field: lines), strips all of it plus any stray @@ASSESSMENT/@@END markers from the visible reasoning, and parses fields tolerant of bullets and parenthetical glosses. Co-authored-by: Codex <noreply@openai.com>3c64ee8 -
[fix] Choice controls never toggle visibility; non-signatories not asylum dests
Hélder Monteiro +1: Codex1) The recurring 'press Continue to show options' bug. Real root cause: toggling a gr.CheckboxGroup from hidden->visible does NOT build its option DOM, so it paints empty until the next interaction — no in-generator/round-trip trick fixed it. Permanent fix: mount the choice controls (radio + both checkbox groups) VISIBLE and drive them by their CHOICES alone (empty = collapsed via a CSS :has rule). Setting choices on an already-mounted-visible group always renders (the one behaviour we'd confirmed works). country/text still toggle visible (those components handle it fine). This also kills the 'all controls visible / stale English labels' leak. 2) Pakistan (a non-signatory with no UNHCR) was shown as an asylum destination with a UNHCR/RSD roadmap. Protection-case recommendations now require the country to be a Convention signatory; non-signatories are dropped. And the roadmap's first step adapts: 'Register with UNHCR' only where UNHCR runs registrati…d22000a -
[ux] Permanent fix: reveal controls in a separate round-trip; localise chrome
Hélder Monteiro +1: CodexRoot cause of the recurring 'options need an extra Continue' (and the Portuguese screen showing ALL controls at once with stale English labels): the control reveal/hide rode in the SAME update as the chat re-render, and the browser drops a CheckboxGroup's paint when it competes with a large chat-HTML change. No in-generator trick (two-pass, sleep, single-frame) fixed it because it's one render cycle. Fix: split each interview turn into two round-trips — the click handler only advances the conversation and hides controls; a chained .then(reveal_controls) then reveals the active control on its own, with nothing else changing, so it always paints. Applied to both the Begin flow and Continue. Also localise the interview chrome (rail steps, 'Your answer', 'Continue') into all 10 languages so the UI around the conversation matches its language — the question text and options were already translated; only this chrome was English. Co-authored-by: Codex <noreply@openai.com>a1f40f1 -
[ux] Reveal choices in one frame; make assessment always substantive
Hélder Monteiro +1: CodexTwo fixes: 1. Choice controls (esp. the documents CheckboxGroup) rendered empty until an extra Continue. Root cause: the reveal frame sent visible:true ALONE, but a CheckboxGroup whose choices were set while hidden builds no option DOM — the choices must ride along on the reveal. Emit a single frame carrying both visibility and choices (exactly what the working Continue path does); drop the two-pass/sleep entirely. 2. Assessment could come out 'dry' — when the small model returned little parseable text, the fallback was a bare one-liner with no recommendations. Now derive case_type/grounds/risk from the interview as a safety net, write a structured multi-paragraph analysis (classification, ground, risk, why protection must be sought abroad, ranked destinations with real data), and guarantee recommendations via a curated strong-asylum shortlist so the next screen is never empty. The model's own richer narration is still preferred when present. Co-a…ec936b9 -
[ux] Reliably reveal every choice control (fix docs options)
Hélder Monteiro +1: CodexThe hidden-then-reveal frames streamed over one response, so Gradio could coalesce the choices-update and the visibility toggle into a single paint — the second checkbox group (documents) then rendered empty until an extra Continue. Emit the two passes from an async generator with a real beat between them so the client paints them as separate cycles. Co-authored-by: Codex <noreply@openai.com>297e933 -
[assess] Honest handling of economic (non-protection) cases
Hélder Monteiro +1: CodexEconomic migration has no right to asylum under the 1951/1969 Conventions, yet the app showed 'Strong match' asylum cards and a UNHCR/RSD roadmap to everyone regardless of case_type — fabricated legal guidance for an economic migrant (CLAUDE.md: wrong output is worse than no output). - Prompt: explicit rule that economic hardship ('no jobs', 'better life') is NOT a protection ground — classify as economic_or_other and point to regular work/study routes, not asylum. - country_lookup: expose non-signatory economic fields (work visa, job market, kafala, honest strategic_guidance caveats) + a curated work_route_countries() shortlist (Gulf labour markets with employer-sponsored work visas — the historically accessible routes, unlike strict Western/EU criteria). - assessment: for economic_or_other, use the deterministic labour shortlist — never the small model's possibly-Western asylum picks. - recommendations: render economic cases as honest 'Work route' cards (work visa, job…180ae9b -
[ux] Show multi-select options instantly (no extra Continue)
Hélder Monteiro +1: CodexThe single shared CheckboxGroup swapped its choices at reveal time, which Gradio renders a beat late — so the persecution-grounds / documents options only appeared after an extra Continue press. Give each multi-select question its own pre-mounted group whose (language-correct) choices are set once and never swapped; only visibility toggles thereafter, so options paint at once. Co-authored-by: Codex <noreply@openai.com>597cc8f -
[ux] Keep the top nav fixed in place across screen transitions
Hélder Monteiro +1: CodexThe site bar spans .gradio-container and centers its content, so it only moves if the container itself changes width. Two causes fixed: - the shell had max-width but no width, so it shrink-wrapped to each phase's content and re-centered between screens -> pin it with width:100%. - a vertical scrollbar appearing on tall screens (and vanishing on short ones) shifted the viewport width -> reserve the gutter always so the page never re-centers. Co-authored-by: Codex <noreply@openai.com>71ed9f5 -
[ux] Make all phase screens fill the shell at the same width
Hélder Monteiro +1: CodexThe card on each phase was shrink-wrapping to its content because the Gradio wrapper column sized to content, so width:100% on the card resolved to the content width — intake ~680px, interview situation ~560px, interview chat ~648px. Tag each outer wrapper with .screen-wrap and force it (and its card) to fill the shell, so all five screens now render at one consistent width. Co-authored-by: Codex <noreply@openai.com>c7aa25a -
[ux] Consistent screen width — all phases use 1180px
Hélder Monteiro +1: CodexEvery screen now shares one width (the largest, 1180px): intake and interview were narrower (1000 / 780); now they match assessment, recommendations and documents. No more per-screen sizing. Co-authored-by: Codex <noreply@openai.com>8d1c642 -
[ux] Widen the intake screen (was too narrow / mobile-looking)
Hélder Monteiro +1: Codexintake-screen max-width 780 -> 1000, inner 560 -> 760, language grid 480 -> 680, so the welcome no longer looks like a narrow mobile card on desktop. Co-authored-by: Codex <noreply@openai.com>52844d8 -
[ux] Trim site-bar nav + redundant intake tagline
Hélder Monteiro +1: Codex- Site bar: removed the non-clickable Intake/Interview/Assessment/… labels; now just "Fugee | Safe guidance for people on the move". - Intake: removed the duplicate "Safe guidance for people on the move" tagline above "Choose your language" (it's already in the site bar). Co-authored-by: Codex <noreply@openai.com>350aa07 -
[ux] Trust note: drop the unenforced consent clause
Hélder Monteiro +1: CodexThere is no consent control, so the note now reads simply "This conversation is private." (was "… Nothing is stored without your consent."). Co-authored-by: Codex <noreply@openai.com>a97b739 -
[brand+ux] Rebrand to Fugee, render markdown, branded detailed documents
Hélder Monteiro +1: Codex- Rebrand Refuge -> Fugee across all user-facing text (wordmark, site bar, page title, agent persona, document headers). Word-boundary replace so the legal term "Refugee" is untouched. Repo/package name stays "refuge". - app/mdlite.py: tiny Markdown->HTML (bold/italic/code, #headings, -/1. lists, paragraphs). Assessment reasoning + chat bubbles now render markdown instead of showing literal ** and #. - Documents: branded masthead (Fugee mark + wordmark), Fraunces/Inter web fonts, page footer + page numbers, nicer headings/steps/contacts styling — for both the PDF and (wordmark header) the editable Word files. 58 fast tests pass; verified PDF carries the brand + footer and markdown renders. Co-authored-by: Codex <noreply@openai.com>aefd221
-
[assessment] Disable web search; guarantee visible grounded reasoning
Hélder Monteiro +1: Codex- Web search removed from the assessment tools: it's grounded only in sources we control — curated country data + the UNHCR guidelines (RAG). No unvetted web claims. - Fix "no summary shown": the small model sometimes returned only the structured @@ASSESSMENT block (no narration), leaving the reasoning panel empty. The prompt now requires readable narration before the block, and a deterministic fallback synthesises a readable summary (case type, grounds, risk, destinations) if the model's narration is too short — so the reasoning panel is never empty. Verified live: the Afghan case now shows ~2.2k chars of grounded reasoning. Co-authored-by: Codex <noreply@openai.com>465999b -
[agentic] Ground assessment in UNHCR guidelines via a RAG tool
Hélder Monteiro +1: CodexIntegrate specs/data/guidelines (UNHCR Handbook + Guidelines No. 1-14) so the agent cites real guidance instead of inventing law — a local, pure-Python retrieval tool (not an external wiki compiler). - data/scripts/build_guidelines_index.py: extract PDF text, chunk, embed with a local Ollama embedding model (nomic-embed-text), write a JSON index (2023 chunks; gitignored/regenerable). - agent/tools/guideline_search.py: semantic top-k search (cosine over cached vectors) returning real guideline excerpts; AgentTool guideline_search. - assessment: guideline_search registered first in ASSESSMENT_TOOLS; prompt now instructs the agent to look up the relevant ground in the Handbook/Guidelines and cite it. Tool-status chip "Consulting UNHCR guidelines: …". Verified live: the Afghan gay man's case is assessed as refugee / "particular social group", grounded in Guideline No. 9 + the Handbook. Deps: pypdf already present; uses ollama embeddings (retrieval, not generation). Co-authore…f963859 -
[fix] CheckboxGroup reveal lag, proceed-button label, LGBTQ legal correctness
Hélder Monteiro +1: Codex- Choice controls now render immediately: control_frames sets the choices while the control is still hidden (pass 1), then reveals it (pass 2). Defeats Gradio's lag of populating choices on a just-revealed component. - "See your recommendations" button: set its label on reveal (it was an empty orange bar). - Assessment legal knowledge: encode that persecution for sexual orientation / gender identity IS a 1951 Convention ground (membership of a particular social group, UNHCR Guidelines No. 9) — applies regardless of origin. Verified live: an Afghan gay man's case is now correctly assessed as a refugee claim on that ground (was wrongly "not covered by the Convention"). 64 fast tests pass. Co-authored-by: Codex <noreply@openai.com>9092864 -
[assessment] Don't auto-jump to recommendations — let the person read first
Hélder Monteiro +1: CodexThe assessment used to hand off to the recommendations screen the instant the reasoning stream finished, so the evaluation couldn't be read. Now the assessment stays on screen and reveals a "See your recommendations →" button when it's done; only clicking it advances. Removed the automatic .then() jump. Co-authored-by: Codex <noreply@openai.com>fdf77b8 -
[interview] Restore persecution categories + fix CheckboxGroup reveal lag
Hélder Monteiro +1: Codex- Re-added the persecution-grounds question (Political / Ethnic / Religious / Gender-based / Sexual orientation / Climate displacement / Other) as a multi-select, in addition to the free-text "what happened" narrative; options translated into all 10 languages. Shown in the assessment facts + review. - Fixed "options only appear after pressing Continue": each transition now renders in two passes (question with controls hidden, then the control), which makes the CheckboxGroup appear immediately. 64 fast tests pass. Co-authored-by: Codex <noreply@openai.com>a7e2b39 -
[interview] Conditional questions, agentic correction loop, bold review labels
Hélder Monteiro +1: CodexFrom live testing: - If current country == origin (person still in their country / in hiding), use "what is happening … that makes you fear for your safety" wording and SKIP the "how long ago did you leave" question (conditional script in interview_script). - "Something needs changing" no longer resets the whole interview: the agent now asks what to correct (free text) and an LLM maps the correction to the right field(s), then re-shows the summary (agentic correction loop). - Review summary labels are bold (markdown ** -> <strong> in the chat renderer). - Assessment: thinking off so the reasoning narration streams visibly (was stuck on "Beginning your assessment…") and runs faster. 64 fast tests pass. Co-authored-by: Codex <noreply@openai.com>bfcae03 -
[agentic] LLM labeled review summary, eligibility-aware assessment, control fix
Hélder Monteiro +1: Codex- Interview review step is now an LLM step: a warm, labelled summary in the person's language (country of origin: …, current country: …, what happened: …, etc.), not plain bullets; deterministic labelled fallback if the model is down. - Assessment reasons honestly about case type (refugee / broader protection / statelessness / economic_or_other / unclear) instead of assuming everyone is a Convention refugee — e.g. it won't push UNHCR asylum at a clearly economic or stateless case. case_type parsed + stored on session.assessment. - Fixed the "control only appears after pressing Continue" lag by pre-mounting the choice controls with choices (Gradio lags updating choices on a freshly revealed component). 64 fast tests pass. Co-authored-by: Codex <noreply@openai.com>dc49265 -
[agentic] Port pi loop hooks + LLM-drafted editable (Word) documents
Hélder Monteiro +1: CodexAudited /tmp/pi and ported its agentic control architecture into Refuge's Python loop, then used it to make the assessment and documents genuinely agentic (the hackathon point: small-LLM agentic AI). Agent framework (agent/loop.py) — Python port of pi's AgentLoopConfig hooks: - LoopHooks: transform_context, before_tool_call, after_tool_call, should_stop_after_turn, prepare_next_turn; plus tool `terminate` handling. Assessment now uses the hooks: - should_stop_after_turn ends the run once the @@ASSESSMENT block is produced (no rambling); before_tool_call blocks web_search queries containing the person's private narrative (privacy, ARCHITECTURE §Security). Documents are now agentic + editable: - agent/drafting.py: the LLM drafts an in-depth, first-person personal statement from the person's own answers, in their language, with [placeholders] to complete (deterministic fallback if the model is down; nothing fabricated). - doc_generator: exports every document as editable Word…b93c45d -
[fix] Fully-scripted multilingual interview (no LLM) + remove walkthrough chrome
Hélder Monteiro +1: CodexPer testing feedback: letting the LLM phrase interview questions caused wrong questions, mismatched controls (Yes/No on a free-text question), repeats, and drift. Replaced it with a deterministic, fully-scripted interview. - app/interview_script.py: fixed question bank, each with a fixed control, pre-translated into all 10 languages, templated with collected values (e.g. "what happened in {origin}"). No model is used in the interview at all. - app/phases/interview.py rewritten: pure lookup/render; controls are correct by construction; one question at a time; guaranteed progression to Review; on confirm it advances state -> ASSESSMENT (language-independent trigger). - reason-for-leaving is now a free-text narrative (templated with origin), not reductive pills, per feedback. - removed the meta "Phase N — …" walkthrough descriptions from every screen (they were design-walkthrough scaffolding, not product UI). - app.py: assessment trigger keys off state==ASSESSMENT (was a frag…e6d3ad1 -
[ui] Mockup chrome: site-bar, per-phase headers, wider layout
Hélder Monteiro +1: Codex- widen the shell to 1180px (was 780) so the assessment/recommendations/document screens breathe like the mockup; intake/interview still cap themselves at 780 - add the sticky site-bar (brand + phase nav) from the mockup - add the "Phase N — Title + description" header above each phase screen (each phase wrapped: outer visibility column > phase header > screen card) Co-authored-by: Codex <noreply@openai.com>6c43929 -
[fix] Deterministic slot-filling interview (agentic flow, consistent UI)
Hélder Monteiro +1: CodexThe interview previously depended on the small model to emit a @@RESPONDER directive and drive phase progression — which it did inconsistently (controls flip-flopped, intro varied, questions repeated, phases never advanced so the flow never proceeded to assessment). Re-architected the pi way: the APP deterministically drives the loop, the LLM only phrases. - app/phases/interview.py: a fixed, ordered SLOTS plan; each slot has a fixed control (country picker / choice pills / text) and target session.interview field. The app shows the right control every time, advances one step per turn, drives the phase rail, and always reaches Review -> Assessment. The LLM only phrases each question warmly in the chosen language and acknowledges the prior answer (thinking off). Answers are captured straight into session.interview (real data for the assessment facts + documents). - removed the brittle directive parsing from the interview path. - tests updated to the deterministic contract; r…aa796c4 -
[T049-T055 + UX] Phase 5 documents + single-model + responder/UX fixes
Hélder Monteiro +1: CodexPhase 5 — Document package: - agent/tools/doc_generator.py + 4 WeasyPrint templates: real PDFs from session data, amber-highlighted pre-fills, blanks (never [NAME]/PLACEHOLDER), logged field sources; zip for "Download all"; head-less preview snippet - app/phases/documents.py: preview + evidence checklist (pre-checked from documents_available) + file list + Download all + Start over - nav: Recommendations "Prepare my documents" -> Documents; Start over -> intake - launch(allowed_paths=[tempdir]) so Gradio can serve the generated PDFs - tests: 8 document tests (real PDF text via pypdf 6.12.2, pinned one-before) Single LLM + UX fixes (from live testing): - one model end-to-end: MODEL_ID=lfm2.5:8b (fast ~2s, tool-calling + good multilingual); removed per-phase model split (no fallback, hackathon rule) - languages limited to those lfm2.5:8b handles well (probe-verified): English, French, Spanish, Portuguese, Arabic, Hindi, Chinese, Japanese, Korean, Russian - responder: free-text…f05e15f -
[T044-T048 + data] Phase 4 recommendations + real UNHCR stats integration
Hélder Monteiro +1: CodexPhase 4 — Recommendations + roadmap: - app/phases/recommendations.py: 2-3 country cards (flag, Fraunces name, match badge from real fields, real facts block, prep details, select button), auto- select top country, vertical roadmap (>=5 steps, country-adapted, contact/time chips, header names country). Cards/facts come from country_lookup data. - app/app.py: assessment -> recommendations handoff via .then() - tests/integration/test_recommendations.py (offline, real country data) Data layer — downloaded, integrated, tested (curated stays authoritative): - country_lookup: curated specs/data/countries.json is ALWAYS the structural base (orgs, signatories, asylum systems); countries_enriched.json overlays ONLY numeric stats.* (never replaces curated fields). Added resolve_iso3. - fix enrich_downloader: ISO3 is "iso" not "iso3" (was enriching 0/152 -> 151); complementary protection is "dec_other" not "dec_complementary" - fix query_data: filter on coo_iso/coa_iso (was coo_code/c…7a05b1b -
[T035-T043] Phase 3 — situation assessment (reasoning stream + real tools)
Hélder Monteiro +1: Codex- agent/tools/country_lookup.py: offline lookup from countries.json (enriched if present), flat schema (unhcrPresence, processingTimeMonths, acceptanceRate, primaryLanguage, requiredDocuments, legalAidOrgs, unhcrOffice, tier, warning); not_found instead of fabrication; PENDING stats surfaced as None (T036/SC-026) - agent/tools/web_search.py: real Tavily search via httpx; fails loudly w/o key or on API error (no fake results) (T035/SC-025) - app/prompts/assessment.md: 5-step scaffold (1951 + AU 1969 grounds, transit safety, find programmes, rank 2-3) + @@ASSESSMENT structured block (T038) - app/assessment_parse.py: parse grounds/risk/countries from the block - app/phases/assessment.py: facts sidebar (8 fields), live reasoning stream, progress bar, "Searching: …" tool chips; stream_assessment core runs the model with both tools, then attaches authoritative country_lookup data to ranked recommendations (origin excluded) into session.assessment (T037/T039/T040) - app/app.py…4270181 -
[T030-T034] Phase 2 — intake screen + language selection + theming
Hélder Monteiro +1: Codex- app/phases/intake.py: mockup-faithful intake (#phase-1) — topo backdrop, logo+wordmark+tagline, 8 language pills in native script, amber "Begin in <language>" primary, privacy trust chip. Single-select w/ click-to-deselect. - pure logic: select_language, begin_interview (ValueError w/o language), toggle_selection, begin_label — all unit-tested (T034) - intake -> interview navigation: Begin hides intake, reveals + streams the interview in the chosen language (language-aware bootstrap) - app/app.py: global theme/chrome overrides (hide Gradio footer, body bg, fonts, flatten default component boxes, focus rings); mount intake + hidden interview; removed standalone hero (intake owns the wordmark now) - interview.build(visible=...) so it can start hidden - scripts/validate_design_tokens.py: also scan intake/interview CSS modules Tests: 40 pass (incl real-model integration). Token validator PASS. Co-authored-by: Codex <noreply@openai.com>fa21080 -
[T017-T029] Phase 1 — agent loop + interview core
Hélder Monteiro +1: Codex- agent/loop.py: full turn cycle (already streamed); separate system prompt from persisted history (no accumulation); create_loop() session factory (T019) - app/prompts/interview.md: 4 sub-phase question bank + @@RESPONDER directive (T020) - app/prompt_loader.py: compose system + interview prompts (T021) - app/responder.py: parse @@RESPONDER -> ResponderSpec, lenient fallback - app/phases/interview.py: mockup-faithful progress rail, chat bubbles, thinking indicator, structured responder (choice/country/text), ghost "Save and continue later"; real per-delta streaming; turn_end sets responder mode; agent_end advances state (T022-T025) - app/countries.py: country selector choices from countries.json - app/design_tokens.py: align CSS var names to mockup (--primary/--bg/--r-md/ --s-md) + shadows/maxw from DESIGN §4/§5; app.py hero updated - app/app.py: mount interview, auto-start first question via demo.load Tests (33 pass): responder parse, event contracts (T028), loop isolation…e16f16c -
[T011+] Env config: uv venv, .env loader, remote Ollama host + model
Hélder Monteiro +1: Codex- app/config.py: dependency-free .env loader (OLLAMA_HOST/MODEL_ID/MODEL_PROVIDER) - .env.example committed; .env gitignored (points at remote 192.168.0.233:11434, MODEL_ID=qwen3.5:4b — qwen2.5:7b absent on host) - app.py loads .env at startup; tests/conftest.py loads it for test runs - venv recreated with uv Co-authored-by: Codex <noreply@openai.com>2af1cec -
[T001-T016] Phase 0 — project setup: agent loop, state machine, Gradio skeleton
Hélder Monteiro +1: Codex- requirements.txt + pyproject.toml pinned one release before latest (gradio 6.15.2, weasyprint 68.1, httpx 0.28.0, ollama 0.6.1, litellm 1.87.1, pytest 9.0.2, pytest-asyncio 1.3.0) - agent/events.py: 8 typed AgentEvent dataclasses - agent/loop.py: pure-Python async AgentLoop (stream, tools, steer, abort, error-as-event); agent/tools/base.py AgentTool contract - app/state/session.py: forward-only interview state machine - app/app.py + app/design_tokens.py: Gradio skeleton, DESIGN.md :root tokens - app/prompts/system.md: Refuge case-worker persona - scripts/validate_design_tokens.py: T010 token validator - tests/unit: state machine (7) + loop ping (1) — all green Co-authored-by: Codex <noreply@openai.com>944f1ac