borderless / ui /globe_details.py
spagestic's picture
Todo schema updated, Carousel UI scroll fix
450f57c
Raw
History Blame Contribute Delete
8.96 kB
# ui/globe_details.py
from __future__ import annotations
import re
import time
from typing import Any
from apis.rest_countries import lookup_country
from ui.agent.synthesis import build_country_report_sections
_COUNTRY_NAME_TO_ISO2 = {
"canada": "CA",
"germany": "DE",
"australia": "AU",
"ireland": "IE",
"new zealand": "NZ",
"united kingdom": "GB",
"uk": "GB",
"portugal": "PT",
"netherlands": "NL",
"singapore": "SG",
"united states": "US",
"usa": "US",
}
_INCOMPLETE_SUMMARIES = {
"no findings could be produced for this to-do.",
"no detailed findings were captured for this country yet.",
}
_TOOL_ACTIVITY_LABELS = {
"search_immigration_info": "Searching immigration sources",
"get_country_profile": "Loading country profile",
"scrape_web_page": "Reading official page",
"crawl_web_site": "Crawling official site",
}
_URL_PATTERN = re.compile(r"https?://[^\s\)\]>\"']+")
def split_country_title(title: str) -> tuple[str, str]:
if "—" in title:
country, pathway = title.split("—", 1)
return country.strip(), pathway.strip()
if " - " in title:
country, pathway = title.split(" - ", 1)
return country.strip(), pathway.strip()
return title.strip(), "Skilled migration pathway"
def is_incomplete_summary(summary: str) -> bool:
text = summary.strip().lower()
if not text:
return True
return text in _INCOMPLETE_SUMMARIES
def _extract_urls(text: str) -> list[str]:
seen: set[str] = set()
urls: list[str] = []
for url in _URL_PATTERN.findall(text):
cleaned = url.rstrip(".,;")
if cleaned not in seen:
seen.add(cleaned)
urls.append(cleaned)
return urls
def iso2_from_country_name(country: str, marker_isos: set[str] | None = None) -> str | None:
lowered = country.strip().lower()
if lowered in _COUNTRY_NAME_TO_ISO2:
code = _COUNTRY_NAME_TO_ISO2[lowered]
if marker_isos is None or code in marker_isos:
return code
candidates = marker_isos or set(_COUNTRY_NAME_TO_ISO2.values())
for iso2 in candidates:
info = lookup_country(iso2)
if info and str(info.get("name", "")).lower() == lowered:
return iso2.upper()
return None
def iso2_from_todo_title(title: str, marker_isos: set[str] | None = None) -> str | None:
country, _ = split_country_title(title)
return iso2_from_country_name(country, marker_isos)
def _preview_text(text: str, limit: int = 220) -> str:
cleaned = re.sub(r"[#>*`_\-[\]()]", " ", text)
cleaned = re.sub(r"\s+", " ", cleaned).strip()
if len(cleaned) <= limit:
return cleaned
return f"{cleaned[: limit - 1]}…"
def _activity_from_tool(tool_name: str, args: dict[str, Any], message: str) -> str:
label = _TOOL_ACTIVITY_LABELS.get(tool_name, f"Running {tool_name}")
if tool_name == "search_immigration_info":
query = str(args.get("query") or "").strip()
if query:
return f"{label}: {query[:160]}"
if tool_name == "scrape_web_page":
url = str(args.get("url") or "").strip()
if url:
return f"{label}: {url[:120]}"
if tool_name == "crawl_web_site":
url = str(args.get("url") or "").strip()
if url:
return f"{label}: {url[:120]}"
if message.strip():
return message.strip()[:220]
return label
def _base_detail(country: str, methods: str) -> dict[str, Any]:
label = f"{country}{methods}"
return {
"country": country,
"title": label,
"pathway": methods,
"summary": "",
"preview": "",
"sources": [],
"activities": [],
"current_activity": "",
"sections": {},
"updated_at": int(time.time()),
}
def _enrich_detail_with_iso2(
detail: dict[str, Any],
country: str,
marker_isos: set[str],
) -> tuple[str | None, dict[str, Any]]:
iso2 = iso2_from_country_name(country, marker_isos or None)
if iso2:
detail["iso2"] = iso2
info = lookup_country(iso2)
if info:
detail["country"] = str(info.get("name") or detail["country"])
key = iso2 or str(detail["country"])
return iso2, detail if iso2 else detail | {"iso2": None, "key": key}
def _merge_detail_into_state(
state: dict[str, Any],
*,
iso2: str | None,
country_key: str,
detail: dict[str, Any],
) -> dict[str, Any]:
country_details = dict(state.get("country_details") or {})
country_details[country_key] = detail
markers = []
for marker in state.get("markers", []):
marker_copy = dict(marker)
if iso2 and marker_copy.get("iso2") == iso2:
marker_copy["detail"] = detail
markers.append(marker_copy)
return {
**state,
"markers": markers,
"country_details": country_details,
"details_version": int(state.get("details_version", 0)) + 1,
}
def build_research_progress_detail(
country: str,
methods: str,
*,
tool_name: str,
args: dict[str, Any],
message: str,
existing: dict[str, Any] | None = None,
) -> dict[str, Any]:
detail = dict(existing or _base_detail(country, methods))
activity = _activity_from_tool(tool_name, args, message)
activities = list(detail.get("activities") or [])
if activity and (not activities or activities[-1] != activity):
activities.append(activity)
activities = activities[-6:]
detail.update(
{
"status": "researching",
"current_activity": activity,
"activities": activities,
"preview": activity,
"updated_at": int(time.time()),
}
)
return detail
def build_country_detail(country: str, methods: str, summary: str) -> dict[str, Any]:
detail = _base_detail(country, methods)
if is_incomplete_summary(summary):
detail.update(
{
"status": "incomplete",
"preview": (
"Research could not produce verified findings for this country. "
"Check the parallel research panel for tool activity."
),
"sections": build_country_report_sections("", country=country, pathway=methods),
}
)
return detail
sections = build_country_report_sections(summary, country=country, pathway=methods)
detail.update(
{
"status": "complete",
"summary": summary.strip(),
"preview": _preview_text(summary),
"sources": _extract_urls(summary),
"sections": sections,
"current_activity": "Research complete",
}
)
return detail
def attach_research_progress_to_globe(
state: dict[str, Any],
*,
country: str,
methods: str,
tool_name: str,
args: dict[str, Any],
message: str,
todo_title: str | None = None,
) -> dict[str, Any]:
if todo_title and not country:
country, methods = split_country_title(todo_title)
marker_isos = {str(marker.get("iso2", "")).upper() for marker in state.get("markers", [])}
marker_isos.discard("")
country_details = dict(state.get("country_details") or {})
iso2 = iso2_from_country_name(country, marker_isos or None)
key = iso2 or country
existing = country_details.get(key)
if existing and existing.get("status") == "complete":
return state
detail = build_research_progress_detail(
country,
methods,
tool_name=tool_name,
args=args,
message=message,
existing=existing,
)
iso2, detail = _enrich_detail_with_iso2(detail, country, marker_isos)
return _merge_detail_into_state(
state,
iso2=iso2,
country_key=iso2 or detail["country"],
detail=detail,
)
def attach_finding_to_globe(
state: dict[str, Any],
*,
country: str,
methods: str,
summary: str,
todo_title: str | None = None,
) -> dict[str, Any]:
"""Merge researcher findings into globe_state for marker popups."""
if todo_title and not country:
country, methods = split_country_title(todo_title)
marker_isos = {str(marker.get("iso2", "")).upper() for marker in state.get("markers", [])}
marker_isos.discard("")
detail = build_country_detail(country, methods, summary)
iso2, detail = _enrich_detail_with_iso2(detail, country, marker_isos)
return _merge_detail_into_state(
state,
iso2=iso2,
country_key=iso2 or detail["country"],
detail=detail,
)