{"record":{"id":"9963f468a1420d08","repo":"mvanhorn/last30days-skill","slug":"stocktwits-item-has-no-symbol","errorCode":null,"errorMessage":"StockTwits item has no symbol","messagePattern":"StockTwits item has no symbol","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/last30days/scripts/lib/stocktwits.py","lineNumber":310,"sourceCode":"    tagged = bull + bear\n    return {\n        \"bullish\": bull,\n        \"bearish\": bear,\n        \"untagged\": len(messages) - tagged,\n        \"pct_bullish\": round(100 * bull / tagged) if tagged else None,\n        \"sample\": len(messages),\n    }\n\n\ndef refetch_datum(item: Any, datum_key: str) -> dict[str, Any]:\n    \"\"\"Re-fetch the same paginated, date-filtered symbol-stream population.\"\"\"\n    from . import http\n\n    if datum_key != \"pct_bullish\":\n        raise KeyError(f\"Unsupported StockTwits datum: {datum_key}\")\n    symbol = str(item.metadata.get(\"symbol\") or item.container or \"\").strip().upper()\n    if not symbol:\n        raise ValueError(\"StockTwits item has no symbol\")\n    url = _STREAM_URL.format(symbol=urllib.parse.quote(symbol))\n    window = item.metadata.get(\"freshness_window\") or {}\n    depth = str(window.get(\"depth\") or \"default\")\n    target = _DEPTH.get(depth, _DEPTH[\"default\"])\n    messages: list[dict[str, Any]] = []\n    cursor_max = None\n    while len(messages) < target:\n        request_kwargs: dict[str, Any] = {\"timeout\": 10, \"retries\": 2}\n        if cursor_max:\n            request_kwargs[\"params\"] = {\"max\": cursor_max}\n        data = http.request(\"GET\", url, **request_kwargs)\n        if not isinstance(data, dict) or not isinstance(data.get(\"messages\"), list):\n            raise KeyError(\"StockTwits symbol stream was not returned\")\n        batch = data[\"messages\"]\n        if not batch:\n            break\n        messages.extend(batch)\n        cursor = data.get(\"cursor\") or {}","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/mvanhorn/last30days-skill/blob/c7460f6114449ddfe6ea3fc2f23c3d910c0e740c/skills/last30days/scripts/lib/stocktwits.py#L292-L328","documentation":"ValueError raised in StockTwits refetch_datum when the symbol needed to rebuild the stream URL cannot be found - both item.metadata['symbol'] and item.container are empty after strip/upper. Without a symbol there is no /streams/symbol/<S>.json endpoint to query, so re-fetch is impossible.","triggerScenarios":"refetch_datum(item, 'pct_bullish') on an item whose metadata dict lacks 'symbol' and whose container attribute is empty/None - typically items constructed manually or deserialized from a schema that dropped metadata.","commonSituations":"Persisted items losing metadata across schema migrations; test fixtures built without metadata; items created via parse paths that only set container sometimes; refetch attempted on non-symbol (watchlist) items.","solutions":["Ensure ingest stores the resolved symbol in item.metadata['symbol']","Guard before calling: skip refetch when neither metadata symbol nor container is non-empty","Re-run search_stocktwits for the topic to rebuild a fully-populated item"],"exampleFix":"# before\ndatum = refetch_datum(item, \"pct_bullish\")  # no symbol anywhere\n\n# after\nsymbol = (item.metadata.get(\"symbol\") or item.container or \"\").strip().upper()\nif not symbol:\n    skip(item)\nelse:\n    datum = refetch_datum(item, \"pct_bullish\")","handlingStrategy":"validation","validationCode":"symbol = str(item.metadata.get(\"symbol\") or getattr(item, \"container\", \"\") or \"\").strip().upper()\nif not symbol:\n    skip_refetch(item)  # cannot rebuild the stream URL without a symbol","typeGuard":"def item_has_stocktwits_symbol(item) -> bool:\n    return bool(\n        str(item.metadata.get(\"symbol\") or getattr(item, \"container\", \"\") or \"\").strip()\n    )","tryCatchPattern":"try:\n    datum = refetch_datum(item, \"pct_bullish\")\nexcept ValueError as e:\n    if \"no symbol\" in str(e):\n        skip_refetch(item)  # data-quality gap; re-ingest, don't retry","preventionTips":["Write the resolved symbol into item.metadata['symbol'] at ingest","Guard refetch scheduling on symbol presence","Rebuild items via search_stocktwits rather than manual construction"],"tags":["stocktwits","data-quality","valueerror","refetch"],"backgroundTag":null,"analyzedSha":"c7460f6114449ddfe6ea3fc2f23c3d910c0e740c","analyzedAt":"2026-08-15T03:34:49.540Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}