[{'Text': '"""GBS (Global Broadband Statistics) tools for PT Research App.\n\nMongoDB utils for Statistics and Sources. Used by PT Systems Agent for\nhuman-supervised GBS data entry (see upc_query_agent issue #37).\n\nUses mongosh (shell) instead of PyMongo - avoids Python SSL issues in\ncloud/EC2 environments. Requires mongosh installed on the host.\n\nRef: pt-research-app schema (Statistics, Sources)\n"""\n\nfrom typing import Optional\n\nfrom point_topic_mcp.core.utils import check_env_vars\nfrom point_topic_mcp.core import mongodb_utils as mongo\nfrom dotenv import load_dotenv\n\nload_dotenv()\n\n# GBS tools require PT Research App MongoDB connection\nif check_env_vars("gbs_tools", ["PT_RESEARCH_DATABASE_URI"]):\n\n    # Valid enums from pt-research-app\n    VALID_TYPES = ["broadband", "mobile", "iptv"]\n    VALID_CHANNELS = ["Infrastructure", "Retail"]\n    VALID_DOMAINS = ["Residential", "Business", "Total"]\n\n    def _valid_object_id(oid: str) -> bool:\n        """Check if string is valid 24-char hex ObjectId."""\n        if not oid or len(oid) != 24:\n            return False\n        return all(c in "0123456789abcdefABCDEF" for c in oid)\n\n    def list_operators(country: str = "", ctx=None) -> str:\n        """\n        List ALL operators in the GBS database. Use before get_gbs_status to discover\n        valid operator names and IDs.\n\n        Always returns the complete, unfiltered list for the given country.\n        Use the country parameter to narrow results and prevent context overflow.\n\n        Args:\n            country: Optional filter by country (exact match, e.g. "United Kingdom").\n                     Leave empty for all operators.\n\n        Returns:\n            Complete list of operators sorted by name, with name, country,\n            operator_id (use in add_statistic/create_source).\n        """\n        ops = mongo.find_operators(country)\n\n        if not ops:\n            return "No operators found" + (f" for country \'{country}\'" if country else "")\n\n        # Sort by name (case-insensitive) for stable, predictable order\n        ops_sorted = sorted(ops, key=lambda o: o.get("name", "").lower())\n\n        rows = []\n        for op in ops_sorted:\n            oid = mongo._oid_from_doc(op)\n            name = op.get("name", "?")\n            c = op.get("country", "?")\n            techs = op.get("technologies", [])\n            \n            rows.append(f"  {name} | {c} | id={oid} | techs={techs}")\n\n        filter_header = ""\n        if country:\n            filter_header = f"(country filter: \'{country}\', {len(ops_sorted)} match(es))\\n"\n\n        return filter_header + "Operators:\\n" + "\\n".join(rows)\n\n    def _quarter_distance(a: str, b: str) -> int | None:\n        """Number of quarters between two period strings (e.g. \'2025Q1\' vs \'2025Q3\' → 2)."""\n        try:\n            ya, qa = int(a[:4]), int(a[-1])\n            yb, qb = int(b[:4]), int(b[-1])\n            return abs((ya * 4 + qa) - (yb * 4 + qb))\n        except (ValueError, IndexError):\n            return None\n\n    def get_gbs_status(\n        country: str,\n        operator: str,\n        period: str,\n        ctx=None,\n    ) -> str:\n        """\n        Get GBS status for a country/operator/period: what exists, gaps, staleness.\n\n        Args:\n            country: Country name (exact match, e.g. "United Kingdom" not "UK")\n            operator: Operator name (exact match, case-sensitive)\n            period: Quarter string (e.g. "2025Q1", "2024Q4")\n\n        Returns:\n            Summary of existing statistics, missing combinations (gaps), and\n            staleness vs current stats period.\n        """\n        op = mongo.find_one_operator(operator, country)\n'}]