[{'Text': '    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        if not op:\n            return f"Operator not found: \'{operator}\' in \'{country}\'. Check name and country match exactly."\n\n        operator_id = mongo._oid_from_doc(op)\n        technologies = op.get("technologies", [])\n\n        gv = mongo.find_global_variables_current()\n        current_period = gv.get("currentStatsPeriod", "unknown") if gv else "unknown"\n\n        existing = mongo.find_statistics(operator_id, period)\n\n        existing_combos = {\n            (s["type"], s["tech"], s["channel"], s["domain"]) for s in existing\n        }\n\n        # Group gaps by type for readability\n        gaps_by_type: dict[str, list[str]] = {}\n        total_gaps = 0\n        for t in VALID_TYPES:\n            type_gaps = []\n            for tech in technologies:\n                for ch in VALID_CHANNELS:\n                    for dom in VALID_DOMAINS:\n                        if (t, tech, ch, dom) not in existing_combos:\n                            type_gaps.append(f"    {tech} / {ch} / {dom}")\n            if type_gaps:\n                gaps_by_type[t] = type_gaps\n                total_gaps += len(type_gaps)\n\n        # State breakdown\n        state_names = {1: "pending", 2: "approved", 3: "published"}\n        by_state: dict[int, int] = {}\n        by_type: dict[str, int] = {}\n        for s in existing:\n            st = s.get("state", 1)\n            by_state[st] = by_state.get(st, 0) + 1\n            tp = s.get("type", "unknown")\n            by_type[tp] = by_type.get(tp, 0) + 1\n\n        # Staleness\n        dist = _quarter_distance(period, current_period)\n        if period == current_period:\n            staleness = "current"\n        elif dist is not None:\n            staleness = f"{dist} quarter{\'s\' if dist != 1 else \'\'} behind current ({current_period})"\n        else:\n            staleness = f"older than current ({current_period})"\n\n        # Build output\n        lines = [\n            f"GBS status: {operator} ({country}) — {period}",\n            f"operator_id: {operator_id}",\n            f"technologies: {\', \'.join(technologies)}",\n            "",\n            f"Existing: {len(existing)} records",\n        ]\n\n        if by_type:\n            lines.append(f"  by type: {\', \'.join(f\'{k}: {v}\' for k, v in sorted(by_type.items()))}")\n        if by_state:\n            lines.append(f"  by state: {\', \'.join(f\'{state_names.get(k, k)}: {v}\' for k, v in sorted(by_state.items()))}")\n\n        lines.extend([\n            "",\n            f"Staleness: {staleness}",\n            "",\n            f"Gaps ({total_gaps} missing combinations):",\n        ])\n\n        for gtype, glist in gaps_by_type.items():\n            lines.append(f"  {gtype} ({len(glist)}):")\n            lines.extend(glist)\n\n        if total_gaps == 0:\n            lines.append("  None — all combinations covered")\n\n        return "\\n".join(lines)\n'}]