"""Build a minimal unified project operating context packet.""" from __future__ import annotations from pathlib import Path from typing import Any, Optional from app.schemas.project_operating_context import ProjectOperatingContext def _safe_int(value: Any) -> Optional[int]: try: if value is None: return None return int(value) except Exception: return None class ProjectOperatingContextBuilder: def __init__(self, repo_scope: str | None = None) -> None: self.repo_scope = str(repo_scope or "").strip() or self._default_repo_scope() @staticmethod def _default_repo_scope() -> str: return Path(__file__).resolve().parents[4].name @staticmethod def _normalize_anchor(value: Any) -> dict[str, Any]: if not isinstance(value, dict): return {} normalized: dict[str, Any] = {} for key, raw in value.items(): if not str(key or "").strip(): continue if raw in (None, "", [], {}): continue normalized[str(key).strip()] = raw return normalized def build( self, *, current_project_id: int | None = None, page_anchor: dict[str, Any] | None = None, thread_anchor: dict[str, Any] | None = None, ) -> ProjectOperatingContext: return ProjectOperatingContext( current_project_id=_safe_int(current_project_id), current_repo_scope=self.repo_scope, page_anchor=self._normalize_anchor(page_anchor), thread_anchor=self._normalize_anchor(thread_anchor), ) def build_from_ingress( self, *, project_hint_id: int | None = None, rag_request_context: dict[str, Any] | None = None, thread_context: dict[str, Any] | None = None, world_model: dict[str, Any] | None = None, session_project_id: int | None = None, existing_packet: dict[str, Any] | None = None, ) -> ProjectOperatingContext: context = rag_request_context if isinstance(rag_request_context, dict) else {} world = world_model if isinstance(world_model, dict) else {} existing = existing_packet if isinstance(existing_packet, dict) else {} page_anchor = self._normalize_anchor(existing.get("page_anchor")) if not page_anchor: requested_project_id = _safe_int(context.get("project_id")) or _safe_int(project_hint_id) source_chat_id = str(context.get("source_chat_id") or "").strip() if requested_project_id is not None: page_anchor["project_id"] = requested_project_id if source_chat_id: page_anchor["source_chat_id"] = source_chat_id thread_anchor = self._normalize_anchor(existing.get("thread_anchor")) if not thread_anchor: raw_thread = thread_context if isinstance(thread_context, dict) else {} reply_to_message_id = str(raw_thread.get("reply_to_message_id") or "").strip() root_message_id = str(raw_thread.get("root_message_id") or "").strip() if reply_to_message_id: thread_anchor["reply_to_message_id"] = reply_to_message_id if root_message_id: thread_anchor["root_message_id"] = root_message_id current_project_id = ( _safe_int(world.get("resolved_project_id")) or _safe_int(existing.get("current_project_id")) or _safe_int(session_project_id) or _safe_int(context.get("project_id")) or _safe_int(project_hint_id) ) repo_scope = str( world.get("repo_scope") or existing.get("current_repo_scope") or self.repo_scope or "" ).strip() or self.repo_scope return ProjectOperatingContextBuilder(repo_scope=repo_scope).build( current_project_id=current_project_id, page_anchor=page_anchor, thread_anchor=thread_anchor, )