{"record":{"id":"f17cb713a07c6239","repo":"nexu-io/open-design","slug":"unsupported-source-source","errorCode":null,"errorMessage":"Unsupported source: {source}","messagePattern":"Unsupported source: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"design-templates/last30days/scripts/lib/normalize.py","lineNumber":59,"sourceCode":"        \"x\": _normalize_x,\n        \"youtube\": _normalize_youtube,\n        \"tiktok\": lambda s, i, idx, fd, td: _normalize_shortform_video(s, i, idx, fd, td, \"TK\", \"TikTok post\"),\n        \"instagram\": lambda s, i, idx, fd, td: _normalize_shortform_video(s, i, idx, fd, td, \"IG\", \"Instagram reel\"),\n        \"hackernews\": _normalize_hackernews,\n        \"bluesky\": lambda s, i, idx, fd, td: _normalize_microblog(s, i, idx, fd, td, \"BS\", \"Bluesky post\"),\n        \"truthsocial\": lambda s, i, idx, fd, td: _normalize_microblog(s, i, idx, fd, td, \"TS\", \"Truth Social post\"),\n        \"threads\": lambda s, i, idx, fd, td: _normalize_microblog(s, i, idx, fd, td, \"TH\", \"Threads post\"),\n        \"xquik\": _normalize_x,\n        \"pinterest\": _normalize_pinterest,\n        \"polymarket\": _normalize_polymarket,\n        \"grounding\": _normalize_grounding,\n        \"xiaohongshu\": _normalize_grounding,\n        \"github\": _normalize_github,\n        \"perplexity\": _normalize_grounding,\n    }\n    normalizer = normalizers.get(source)\n    if normalizer is None:\n        raise ValueError(f\"Unsupported source: {source}\")\n    normalized = [normalizer(source, item, index, from_date, to_date) for index, item in enumerate(items)]\n    require_date = source == \"grounding\"\n    filtered = filter_by_date_range(normalized, from_date, to_date, require_date=require_date)\n    if filtered:\n        return filtered\n    if freshness_mode == \"evergreen_ok\" and source == \"youtube\":\n        if require_date:\n            return [item for item in normalized if item.published_at]\n        return normalized\n    return filtered\n\n\ndef _remap_comments(\n    raw: list[Any],\n    score_keys: tuple[str, ...],\n    excerpt_keys: tuple[str, ...],\n) -> list[dict[str, Any]]:\n    \"\"\"Normalize comments from any source into the shared Reddit-compatible shape.","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/design-templates/last30days/scripts/lib/normalize.py#L41-L77","documentation":"normalize.py builds a `normalizers` dict mapping known source names (youtube, reddit, x, truthsocial, threads, xquik, pinterest, polymarket, grounding, xiaohongshu, github, perplexity, and others in the table) to per-source normalizer callables. If `source` is absent from the dict, `normalizer is None` and the function raises ValueError(f\"Unsupported source: {source}\"). This is a programmer/contract error, not a user-config one — the source list should already have been validated upstream (e.g. by parse_search_flag and pipeline.available_sources).","triggerScenarios":"Calling normalize.normalize_items(items, source=\"googlenews\", ...) where 'googlenews' is not a key in the normalizers table; a planner/external plan emits a subquery source name that has no normalizer; a typo introduced when adding a new source (registered in available_sources but not in normalizers).","commonSituations":"Adding a new source and forgetting the normalize.py entry; an external --plan that names a source the local build does not recognize; mismatch between two builds where one added a source.","solutions":["If you are a user: pass only sources returned by `--search` validation (those are guaranteed to have normalizers).","If you are adding a source: register a normalizer entry in the dict at the top of normalize.py.","If the bad source came from an external plan, edit the plan to use a supported source name.","Grep `normalizers = {` in normalize.py to list all currently supported source keys."],"exampleFix":"# before (external plan emits a source the build lacks)\nplan = {\"acme\": [{\"source\": \"googlenews\", ...}]}\n# after\nplan = {\"acme\": [{\"source\": \"grounding\", ...}]}","handlingStrategy":"type-guard","validationCode":"from lib.normalize import _NORMALIZERS  # or re-derive the dict\nSUPPORTED = set(_NORMALIZERS.keys())\nif source not in SUPPORTED:\n    raise ValueError(f'Unsupported source: {source!r}; known: {sorted(SUPPORTED)}')","typeGuard":"def is_supported_source(name: object, known: set[str]) -> bool:\n    return isinstance(name, str) and name in known","tryCatchPattern":null,"preventionTips":["When adding a source, register it in normalize.py, available_sources, and SEARCH_ALIAS together.","Validate external plan sources against the normalizers dict before running the pipeline."],"tags":["python","argument-validation","normalization","contract"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}