[{'Text': '# GBS Tools Documentation\n\nGBS tools provide MCP access to the PT Research App MongoDB cluster for managing Global Broadband Statistics (GBS) data. Human-supervised AI data entry for Jolanta.\n\n## Setup\n\nRequires:\n- `mongosh` installed on the system (uses subprocess for robust MongoDB Atlas connectivity)\n- `PT_RESEARCH_DATABASE_URI` environment variable set (MongoDB Atlas cluster URI)\n\n## Tools\n\n### `list_operators`\n\nDiscover operators in the GBS database. Use before `get_gbs_status`, `add_statistic`, `create_source`.\n\n**Parameters:**\n- `country` (str, optional): Filter by country name (exact match, e.g. "United Kingdom"). Leave empty for all.\n- `limit` (int, optional): Cap the number of returned operators. 0 = no limit. Useful to prevent context overflow in agents.\n\n**Returns:**\n- Operators sorted alphabetically by name, with country, operator_id, and technologies.\n- If no country filter and >400 operators, warns to use a country filter or limit.\n- If limit is set, shows "N of M" at the end.\n\n**Example:**\n```\nlist_operators(country="United Kingdom", limit=10)\n```\n\nOutput (excerpt):\n```\n(country filter: \'United Kingdom\', 214 match(es))\nOperators:\n  186K | United Kingdom | id=662ff7a3f9d5751ffbf3024d | techs=[\'ADSL\']\n  AAISP | United Kingdom | id=662ff7a3f9d5751ffbf301bd | techs=[\'ADSL\', \'FTTH\', \'FTTP\', \'VDSL\', \'G.fast cabinet\', \'3G\', \'4G\']\n  ...\n... showing 10 of 214 operators\n```\n\n---\n\n### `get_gbs_status`\n\nCheck what GBS data exists for an operator/period, identify gaps, and check staleness vs. current period.\n\n**Parameters:**\n- `country` (str): Country name (exact match, e.g. "United Kingdom")\n- `operator` (str): Operator name (exact match, case-sensitive)\n- `period` (str): Quarter string, e.g. "2025Q1"\n\n**Returns:**\n- operator_id and technologies list\n- Existing record counts broken down by type (broadband/mobile/iptv) and state (pending/approved/published)\n- Staleness: shows quarter distance from current period (e.g. "2 quarters behind current (2025Q3)")\n- Gaps: missing combinations grouped by type for readability\n\n**Example:**\n```\nget_gbs_status(country="United Kingdom", operator="BT Group", period="2025Q3")\n```\n\nOutput (excerpt):\n```\nGBS status: BT Group (United Kingdom) — 2025Q3\noperator_id: 662ff7a3f9d5751ffbf300f6\ntechnologies: ADSL, ADSL2+, DSL, Fibre, FTTP, FTTx, ...\n\nExisting: 9 records\n  by type: broadband: 8, iptv: 1\n  by state: published: 9\n\nStaleness: current\n\nGaps (369 missing combinations):\n  broadband (118):\n    ADSL / Infrastructure / Residential\n    ADSL / Infrastructure / Business\n    ...\n  mobile (126):\n    ...\n  iptv (125):\n    ...\n```\n\n---\n\n### `add_statistic`\n\nInsert a GBS Statistic (subscriber count) for an operator/period/technology.\n\nOperator name and country are auto-filled from the operator record — no need to pass them.\nRejects duplicates (same operator/period/type/tech/channel/domain).\n\n**Parameters:**\n- `type` (str): broadband | mobile | iptv\n- `operator_id` (str): Operator ObjectId (from list_operators or get_gbs_status)\n- `period` (str): Quarter (e.g. "2025Q1") — validated format\n- `tech` (str): Technology (must match operator\'s available technologies)\n- `channel` (str): Infrastructure | Retail\n- `domain` (str): Residential | Business | Total\n- `subscribers` (int): Subscriber count (must be >= 0)\n- `is_estimate` (bool, optional): Whether value is estimated. Default: false.\n- `restated` (bool, optional): Whether value was restated. Default: false.\n- `notes` (str, optional): Notes about the statistic.\n- `created_by` (str, optional): Creator identifier. Default: "gbs-agent".\n\n**Returns:**\n- Inserted record ID, state, and echo of what was inserted — or rejection reason (duplicate, invalid input).\n\n**Example:**\n```\nadd_statistic(\n  type="broadband",\n  operator_id="662ff7a3f9d5751ffbf2fffe",\n  period="2025Q1",\n  tech="FTTP",\n  channel="Infrastructure",\n  domain="Residential",\n  subscribers=50000,\n  notes="Q1 2025 FTTP residential subscribers"\n)\n```\n\nOutput:\n```\nCreated statistic 69ccd63b054a1c59505a74f0 (state: pending)\n  seethelight (United Kingdom) | 2025Q1 | broadband | FTTP | Infrastructure | Residential | subscribers=50000\n```\n\n---\n\n### `create_source`\n\nInsert a Source (document/report reference) for an operator and period.\n\nRejects duplicates (same operator/year/quarter/type/url/fileUrl). Requires at least one of `url` or `file_url`.\n\n**Parameters:**\n- `operator_id` (str): Operator ObjectId\n- `year` (int): Year (e.g. 2025) — validated range 1990-2100\n- `quarter` (int): Quarter 1-4\n- `type` (str): Source type (e.g. "report", "earnings_call", "tariff", "regulatory_filing") — must not be empty\n- `url` (str, optional): Web link — stored as-is\n- `file_url` (str, optional): File to archive into `pt-research-sources`. Stored `fileUrl` is always the permanent HTTPS URL on that bucket.\n  - `s3://bucket/key` — server-side copy (same AWS account)\n  - `http(s)://...` — download then upload (e.g. presigned URL or public file)\n\n**Returns:**\n- Inserted record ID with operator name and period — or rejection reason (duplicate, missing url).\n\n**Example:**\n```\ncreate_source(\n  operator_id="662ff7a3f9d5751ffbf2fffe",\n  year=2025,\n  quarter=1,\n  type="earnings_call",\n  url="https://investor.example.com/q1-2025-earnings-call"\n)\n```\n\nOutput:\n```\nCreated source 69ccd63e391c82b96ea4b085\n  seethelight | 2025Q1 | type=earnings_call | url=https://investor.example.com/q1-2025-earnings-call\n```\n\n---\n\n## BSON Data Format\n\nAll inserts use BSON Date fields to align with Prisma web app expectations:\n\n- **Statistics**: `date`, `createdAt`, `updatedAt` all set to current BSON Date\n- **Sources**: `createdAt`, `updatedAt` set to current BSON Date\n- **Statistics**: `is_archived` always set to `false` on insert\n\nWeb app reads these fields as-is; if missing or wrong type, the app may crash or show incomplete data.\n\n---\n\n## Workflow Example\n\n1. **List UK operators** to find the one you want:\n   ```\n   list_operators(country="United Kingdom", limit=20)\n   ```\n\n2. **Check gaps** for a specific operator/period:\n   ```\n   get_gbs_status(country="United Kingdom", operator="seethelight", period="2025Q1")\n   ```\n\n3. **Add statistics** for each gap:\n   ```\n   add_statistic(type="broadband", operator="seethelight", operator_id="...", ...)\n   ```\n\n4. **Create sources** to reference the data:\n   ```\n   create_source(operator_id="...", year=2025, quarter=1, type="report", url="...")\n   ```\n\n---\n\n## Errors & Troubleshooting\n\n### "PT_RESEARCH_DATABASE_URI is not set"\n- Export the URI before running: `export PT_RESEARCH_DATABASE_URI=$(tr -d \'\\n\' < .mongodb_uri)`\n\n### "mongosh not found"\n- Install mongosh:\n  - macOS: `brew install mongosh`\n  - Ubuntu/Debian: `sudo apt install mongodb-mongosh`\n  - Amazon Linux/RHEL: `sudo yum install mongodb-mongosh`\n\n### "Operator not found"\n- Check spelling and country exactly; both are case-sensitive.\n- Use `list_operators()` to find the exact name.\n\n### "Tech \'<tech>\' not valid for operator"\n- The technology must match one of operator\'s registered technologies.\n- Use `list_operators()` to see technologies for an operator.\n\n### Document inserts succeed but web app shows incomplete data\n- Check that `createdAt`, `updatedAt` are present and are BSON Date type (not null or string).\n- Run verification script: `PT_RESEARCH_DATABASE_URI=$(tr -d \'\\n\' < .mongodb_uri) uv run python scripts/verify_gbs_bson_shapes.py`\n\n---\n\n## Testing\n\nUnit tests (no real DB needed):\n```bash\nuv run pytest tests/test_gbs_tools.py -v\n```\n\nIntegration tests (requires PT_RESEARCH_DATABASE_URI):\n```bash\nPT_RESEARCH_DATABASE_URI=$(tr -d \'\\n\' < .mongodb_uri) uv run python -m point_topic_mcp.tools.gbs_tools\n```\n\nBSON shape verification:\n```bash\nPT_RESEARCH_DATABASE_URI=$(tr -d \'\\n\' < .mongodb_uri) uv run python scripts/verify_gbs_bson_shapes.py\n```\n'}]