"""Agent response parsers and input normalizers. Pure module: no FastAPI imports, no HTTPException, and no module-level state. Mirrors job/candidate/decorators.py — helpers that clean/shape data before or after the graph nodes run. """ from __future__ import annotations import uuid def normalize_job_posts(job_posts) -> list[dict]: if not job_posts: return [] normalized=[] for item in job_posts: if not isinstance(item,dict): continue job_id=item.get("id") if job_id is None: continue normalized.append({ "id":str(job_id), "title":item.get("title") or "", "description":item.get("description") or "", "post_text":item.get("post_text") or "", "requirements":item.get("requirements") or [], "optional_skills":item.get("optional_skills") or [], "location":item.get("location") or "", "employment_type":item.get("employment_type") or "", }) return normalized def parse_match_response(data,allowed_ids) -> tuple[list[str],str,str,str]: if not isinstance(data,dict): raise RuntimeError(f"model did not return a JSON object: {data!r}") allowed=set(allowed_ids or []) raw_ids=data.get("suggested_job_post_ids") or [] if not isinstance(raw_ids,list): raw_ids=[] suggested=[] seen=set() for raw_id in raw_ids: job_id=str(raw_id).strip() if not job_id or job_id not in allowed or job_id in seen: continue try: uuid.UUID(job_id) except ValueError: continue seen.add(job_id) suggested.append(job_id) summary=data.get("summary") if not isinstance(summary,str): summary="" reasoning=data.get("reasoning") if isinstance(reasoning,list): reasoning="\n".join(str(item) for item in reasoning) if not isinstance(reasoning,str): reasoning="" experience=data.get("experience") if not isinstance(experience,str): experience="" return suggested,summary.strip(),reasoning.strip(),experience.strip()