{
  "name": "11 - Social Source Connector Foundation (Telegram Public Channel Preview)",
  "nodes": [
    {
      "id": "bb110000-0000-0000-0000-0000000000n1",
      "name": "Overview Note RU",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -460,
        -340
      ],
      "parameters": {
        "content": "## 11 \u2014 Social Source Connector Foundation v0.1 \u2014 Telegram PUBLIC channel preview (DEC-109/110)\n\n\u041f\u0435\u0440\u0432\u044b\u0439 non-Avito \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440 \u043f\u043e Stage 3.4 (DEC-096: \u043e\u0434\u0438\u043d \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u0437\u0430 \u0440\u0430\u0437). \u0412\u044b\u0431\u0440\u0430\u043d **Telegram public-channel preview** (`t.me/s/<channel>`): \u043c\u0430\u043a\u0441\u0438\u043c\u0443\u043c \u043a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0442\u043d\u043e\u0439 \u0440\u0435\u043a\u043b\u0430\u043c\u043d\u043e\u0439 \u0446\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u043d\u0430 \u0435\u0434\u0438\u043d\u0438\u0446\u0443 \u0440\u0438\u0441\u043a\u0430 \u2014 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u0431\u0435\u0437 \u043b\u043e\u0433\u0438\u043d\u0430/\u0441\u0435\u0441\u0441\u0438\u0439, \u0431\u0435\u0437 member-\u0434\u0430\u043d\u043d\u044b\u0445.\n\n\u0427\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442 (\u0414\u0415\u0422\u0415\u0420\u041c\u0418\u041d\u0418\u0420\u041e\u0412\u0410\u041d\u041d\u041e, $0, fixture-first):\n1. \u0420\u0443\u0447\u043d\u043e\u0439 \u0441\u0442\u0430\u0440\u0442 \u2192 Set Connector Config (`fixture_mode=true`, `live_mode=false`, allowlist \u043a\u0430\u043d\u0430\u043b\u043e\u0432).\n2. fixture_mode=true \u2192 Build Fixture Telegram Channel Posts (6 \u043f\u043e\u0441\u0442\u043e\u0432: 3 \u043a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0442\u043d\u044b\u0435 \u0440\u0435\u043a\u043b\u0430\u043c\u044b + 1 \u0441\u043b\u0430\u0431\u044b\u0439 \u0444\u0438\u043d\u0430\u043d\u0441\u043e\u0432\u044b\u0439 \u0441\u0438\u0433\u043d\u0430\u043b + 1 hard-negative \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c + 1 \u0434\u0443\u0431\u043b\u044c \u0432 \u0431\u0430\u0442\u0447\u0435). \u041a\u0430\u043d\u0430\u043b\u044b \u043f\u043e\u043c\u0435\u0447\u0435\u043d\u044b `_fixture` \u2014 \u041d\u0415 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0435.\n3. Normalize: \u0432\u0430\u043b\u0438\u0434\u043d\u043e\u0441\u0442\u044c \u2192 business relevance (strong/weak/hard-negative, \u0441\u043b\u043e\u0432\u0430\u0440\u044c v0.1 \u043a\u0430\u043a \u0432 WF09) \u2192 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0435 \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u044b \u0422\u041e\u041b\u042c\u041a\u041e \u0434\u043e\u0441\u043b\u043e\u0432\u043d\u043e \u0438\u0437 \u0442\u0435\u043a\u0441\u0442\u0430 \u043f\u043e\u0441\u0442\u0430 (evidence = post_url, \u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0430 DEC-097/098; contact_channel = \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f: telegram/phone, \u0444\u043e\u0440\u043c\u0430\u0442 \u2014 \u0432 notes, DEC-114) \u2192 dedup_key=`telegram::social_channel::<canonical post_url>`.\n4. \u0414\u0435\u0434\u0443\u043f \u043f\u0440\u043e\u0442\u0438\u0432 market_record_registry + \u0432\u043d\u0443\u0442\u0440\u0438 \u0431\u0430\u0442\u0447\u0430 (\u043f\u0430\u0442\u0442\u0435\u0440\u043d WF09).\n5. \u041f\u0438\u0448\u0435\u0442 \u0422\u041e\u041b\u042c\u041a\u041e: raw_market_records (40) \u2014 unique + duplicate-audit, market_record_registry (15) \u2014 \u0442\u043e\u043b\u044c\u043a\u043e unique, agent_requests (21) \u2014 1 \u0441\u0442\u0440\u043e\u043a\u0430 completed.\n\n\u0427\u0415\u0413\u041e \u041d\u0415 \u0414\u0415\u041b\u0410\u0415\u0422: \u041d\u0415 \u0445\u043e\u0434\u0438\u0442 \u0432 \u0441\u0435\u0442\u044c (HTTP-\u043d\u043e\u0434\u044b \u043d\u0435\u0442 \u0432\u043e\u043e\u0431\u0449\u0435; live-\u0432\u0435\u0442\u043a\u0430 = guard \u0441 \u043e\u0448\u0438\u0431\u043a\u043e\u0439); \u041d\u0415 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 Claude/Apify/Firecrawl; \u041d\u0415 \u0447\u0438\u0442\u0430\u0435\u0442 \u0433\u0440\u0443\u043f\u043f\u044b/\u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0435 \u0447\u0430\u0442\u044b/MTProto; \u041d\u0415 \u0432\u044b\u0434\u0443\u043c\u044b\u0432\u0430\u0435\u0442 \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u044b; \u041d\u0415 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442 outreach; \u041d\u0415\u0422 \u0430\u0432\u0442\u043e-handoff \u0432 WF08; \u041d\u0415 \u043f\u0438\u0448\u0435\u0442 \u0431\u0438\u0437\u043d\u0435\u0441-\u0432\u043a\u043b\u0430\u0434\u043a\u0438. active=false.\n\n**v0.2 (DEC-120):** \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d \u041e\u0425\u0420\u0410\u041d\u042f\u0415\u041c\u042b\u0419 live-\u043f\u0443\u0442\u044c: \u0433\u0435\u0439\u0442 \u043e\u0434\u043e\u0431\u0440\u0435\u043d\u0438\u044f (token) -> \u041e\u0422\u041a\u041b\u042e\u0427\u0401\u041d\u041d\u042b\u0419 HTTP-\u043f\u043b\u0435\u0439\u0441\u0445\u043e\u043b\u0434\u0435\u0440 (t.me/s/<channel> public preview only) -> \u043f\u0430\u0440\u0441\u0435\u0440 (\u0431\u0440\u043e\u0441\u0430\u0435\u0442 \u043e\u0448\u0438\u0431\u043a\u0443 \u0431\u0435\u0437 HTML). \u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0432\u0441\u0451 \u0438\u043d\u0435\u0440\u0442\u043d\u043e: live_mode=false, token \u043f\u0443\u0441\u0442, allowlist \u043f\u0443\u0441\u0442, HTTP disabled. \u041d\u0438\u043a\u0430\u043a\u0438\u0445 \u0433\u0440\u0443\u043f\u043f/\u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0445 \u0447\u0430\u0442\u043e\u0432/MTProto/\u0441\u043a\u0440\u044b\u0442\u044b\u0445 \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u043e\u0432/auto-outreach.\n\n**v0.3 (DEC-124/126):** \u043a\u0430\u0436\u0434\u044b\u0439 \u043f\u0440\u043e\u0433\u043e\u043d \u043f\u0438\u0448\u0435\u0442 1 \u0441\u0442\u0440\u043e\u043a\u0443 \u0432 live_source_runs (23 \u043a\u043e\u043b\u043e\u043d\u043a\u0438: \u0440\u0435\u0436\u0438\u043c, allowlist, \u0441\u0447\u0451\u0442\u0447\u0438\u043a\u0438, external_calls, \u0441\u0442\u043e\u0438\u043c\u043e\u0441\u0442\u044c, next action). contact_public \u043f\u0438\u0448\u0435\u0442\u0441\u044f Sheets-safe (\u0430\u043f\u043e\u0441\u0442\u0440\u043e\u0444 \u043f\u0435\u0440\u0435\u0434 +/= \u2014 \u0444\u0438\u043a\u0441 #ERROR! \u0434\u043b\u044f \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u043e\u0432). Live-\u043f\u0443\u0442\u044c (DEC-120) \u0431\u0435\u0437 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439: token=I_APPROVE_LIVE_TELEGRAM_PREVIEW + \u043d\u0435\u043f\u0443\u0441\u0442\u043e\u0439 allowlist + \u0412\u041a\u041b\u042e\u0427\u0415\u041d\u0418\u0415 disabled HTTP-\u043d\u043e\u0434\u044b; max_posts<=10; \u0442\u043e\u043b\u044c\u043a\u043e t.me/s/<channel>.\n\n**v0.4 (DEC-132):** \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0439, \u043d\u043e gated live-\u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u043e\u0433\u043e preview. \u0422\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442 \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f `live_transport`: **firecrawl** (\u043f\u0440\u0435\u0434\u043f\u043e\u0447\u0442\u0438\u0442\u0435\u043b\u044c\u043d\u043e, \u0435\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u043a\u0440\u0435\u0434\u0435\u043d\u0448\u043b Firecrawl) \u0418\u041b\u0418 **http_get** (fallback, \u0431\u0435\u0437 \u043f\u043e\u0448\u0442\u0443\u0447\u043d\u043e\u0439 \u043f\u043b\u0430\u0442\u044b). \u041e\u0431\u0430 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442-\u0443\u0437\u043b\u0430 \u041e\u0422\u041a\u041b\u042e\u0427\u0415\u041d\u042b \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e; \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u2014 \u043d\u043e\u0434\u0430 `Route Live Transport`. \u0413\u0435\u0439\u0442 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 username \u0418\u041b\u0418 `https://t.me/s/<channel>` / `https://t.me/<channel>`, \u043d\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u0443\u0435\u0442 \u0432 username \u0438 \u041e\u0422\u041a\u041b\u041e\u041d\u042f\u0415\u0422 `t.me/+`/`joinchat`/`t.me/c`/\u0433\u0440\u0443\u043f\u043f\u044b/\u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0435/\u0447\u0438\u0441\u043b\u043e\u0432\u044b\u0435 id; `max_channels<=2` (\u043f\u0435\u0440\u0432\u044b\u0439 smoke), `max_posts<=10`. \u0421\u0442\u043e\u0438\u043c\u043e\u0441\u0442\u044c \u043f\u0438\u0448\u0435\u0442\u0441\u044f \u0432 agent_requests + live_source_runs: http_get = $0 (\u0431\u0435\u0437 \u043f\u043e\u0448\u0442\u0443\u0447\u043d\u043e\u0439 \u043f\u043b\u0430\u0442\u044b); firecrawl = `cost_not_recovered` (\u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e != \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u043e). \u041f\u0430\u0440\u0441\u0435\u0440 \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 channel/post_url/\u0434\u0430\u0442\u0443/\u0442\u0435\u043a\u0441\u0442/view_count(\u043e\u043f\u0446.)/\u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0430\u043a\u0442 \u0434\u043e\u0441\u043b\u043e\u0432\u043d\u043e. Fixture-\u043f\u0443\u0442\u044c \u0431\u0435\u0437 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439.",
        "height": 880,
        "width": 640,
        "color": 4
      }
    },
    {
      "id": "bb110000-0000-0000-0000-0000000000n2",
      "name": "Setup and Test Note RU",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        220,
        -340
      ],
      "parameters": {
        "content": "## \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0438 \u0442\u0435\u0441\u0442\n\n1. \u041d\u0415 \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c (active=false). \u0412\u043a\u043b\u0430\u0434\u043a\u0438 \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0442 (agent_requests 21 / raw_market_records 40 / market_record_registry 15 \u2014 \u043a\u0430\u043a \u0443 WF07/WF09).\n2. \u041f\u043e\u0441\u043b\u0435 \u0438\u043c\u043f\u043e\u0440\u0442\u0430 \u043f\u0435\u0440\u0435\u043f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c \u043a\u0440\u0435\u0434\u0435\u043d\u0448\u043b Google Sheets \u043d\u0430 4 sheet-\u043d\u043e\u0434\u0430\u0445 (1 read + 3 append) \u0438 \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c PASTE_SPREADSHEET_ID_HERE \u043d\u0430 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0439 ID.\n3. \u0422\u0435\u0441\u0442 1 (fixture, \u043f\u0435\u0440\u0432\u044b\u0439 \u043f\u0440\u043e\u0433\u043e\u043d, $0): Execute once \u2192 \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u043e: posts_received=6, structurally_valid=6, hard_skipped=1 (\u044e\u0440. \u0430\u0434\u0440\u0435\u0441 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c), business_relevant=5, unique=4, duplicates=1 (\u0434\u0443\u0431\u043b\u044c \u043f\u043e\u0441\u0442\u0430 101 \u0432 \u0431\u0430\u0442\u0447\u0435), raw_market_records +5 (4 unique + 1 duplicate audit), registry +4, agent_requests +1 (completed). \u041a\u043e\u043d\u0442\u0430\u043a\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0443 \u043f\u043e\u0441\u0442\u0430 101 (@kredit_broker_msk_fixture, \u0434\u043e\u0441\u043b\u043e\u0432\u043d\u043e \u0438\u0437 \u0442\u0435\u043a\u0441\u0442\u0430; `contact_channel=telegram` \u2014 \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f \u043a\u0430\u043d\u0430\u043b\u0430, \u041d\u0415 \u0444\u043e\u0440\u043c\u0430\u0442 (DEC-114); `contact_format=handle` + `contact_source_url` + `contact_use_policy=manual_review` \u0432 notes).\n4. \u0422\u0435\u0441\u0442 2 (fixture, \u043f\u043e\u0432\u0442\u043e\u0440): \u0432\u0441\u0435 4 \u043a\u043b\u044e\u0447\u0430 \u0443\u0436\u0435 \u0432 registry \u2192 unique=0, duplicates=5, registry +0, raw +5 (duplicate audit), summary next_action: \u041d\u0415 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c WF08.\n5. \u0422\u0435\u0441\u0442 3 (live guard): fixture_mode=false \u2192 \u0432\u043e\u0440\u043a\u0444\u043b\u043e\u0443 \u043f\u0430\u0434\u0430\u0435\u0442 \u043d\u0430 \u043d\u043e\u0434\u0435 LIVE Mode Guard \u0441 \u043f\u043e\u043d\u044f\u0442\u043d\u043e\u0439 \u043e\u0448\u0438\u0431\u043a\u043e\u0439 (live \u043d\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d; \u043d\u0443\u0436\u043d\u044b \u043e\u0434\u043e\u0431\u0440\u0435\u043d\u0438\u0435 + \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442 + \u043a\u0440\u0435\u0434\u0435\u043d\u0448\u043b). \u0412\u0435\u0440\u043d\u0443\u0442\u044c fixture_mode=true.\n6. WF08 handoff (\u0432\u0440\u0443\u0447\u043d\u0443\u044e, \u043f\u043e\u0441\u043b\u0435 \u043e\u0434\u043e\u0431\u0440\u0435\u043d\u0438\u044f): agent_request_id_filter=<wf11_req_\u2026>, deterministic_first. \u041e\u0436\u0438\u0434\u0430\u0435\u043c\u043e: 3 \u043a\u043e\u043c\u043f\u0435\u0442\u0438\u0442\u043e\u0440\u043d\u044b\u0445 \u043f\u043e\u0441\u0442\u0430 \u2192 monitor_queue, 1 market_signal \u2192 content/review.\n\nLive-\u0440\u0435\u0436\u0438\u043c (\u0431\u0443\u0434\u0443\u0449\u0435\u0435, \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0435 \u043e\u0434\u043e\u0431\u0440\u0435\u043d\u0438\u0435): \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442 Firecrawl/HTTP GET \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b t.me/s/<channel>; \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u0430\u043d\u0430\u043b\u044b \u0438\u0437 allowlist; \u043d\u0438\u043a\u0430\u043a\u043e\u0433\u043e MTProto. \u0421\u0442\u043e\u0438\u043c\u043e\u0441\u0442\u044c fixture-\u043f\u0440\u043e\u0433\u043e\u043d\u0430: $0 \u0432\u0441\u0435\u0433\u0434\u0430.\n7. v0.3: live_source_runs +1 \u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u043f\u0440\u043e\u0433\u043e\u043d (mode=fixture, external_calls=0, cost=0). \u041f\u0435\u0440\u0435\u0434 \u043f\u0435\u0440\u0432\u044b\u043c \u043f\u0440\u043e\u0433\u043e\u043d\u043e\u043c \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0432\u043a\u043b\u0430\u0434\u043a\u0443 live_source_runs (23 \u043a\u043e\u043b\u043e\u043d\u043a\u0438 \u0438\u0437 docs/TABLE_SCHEMA.md).\n\n**v0.4 live (\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0435 \u043e\u0434\u043e\u0431\u0440\u0435\u043d\u0438\u0435):** 1) `live_mode=true`; 2) `live_approval_token=I_APPROVE_LIVE_TELEGRAM_PREVIEW`; 3) \u043d\u0435\u043f\u0443\u0441\u0442\u043e\u0439 `live_channel_allowlist` (\u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0435 \u043a\u0430\u043d\u0430\u043b\u044b/`t.me/s` URL; \u0411\u0415\u0417 `+`/joinchat/\u0433\u0440\u0443\u043f\u043f/`t.me/c`); 4) `live_transport`= firecrawl (\u043d\u0443\u0436\u0435\u043d Firecrawl \u043a\u0440\u0435\u0434\u0435\u043d\u0448\u043b) \u0438\u043b\u0438 http_get; 5) \u0412\u041a\u041b\u042e\u0427\u0418\u0422\u042c \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u0443\u044e \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442-\u043d\u043e\u0434\u0443 (Firecrawl/HTTP) \u2014 \u043e\u0431\u0435 disabled \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e; 6) `max_channels<=2`, `max_posts<=10` \u043d\u0430 \u043f\u0435\u0440\u0432\u043e\u043c smoke. \u041f\u043e\u0441\u043b\u0435 \u043f\u0440\u043e\u0433\u043e\u043d\u0430 \u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0440\u0435\u0430\u043b\u044c\u043d\u0443\u044e \u0441\u0442\u043e\u0438\u043c\u043e\u0441\u0442\u044c Firecrawl \u0432 COSTS_AND_LIMITS + live_source_runs (\u0435\u0441\u043b\u0438 \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430 \u2014 `cost_not_recovered`). \u041f\u0430\u0440\u0441\u0435\u0440-\u0442\u0435\u0441\u0442 \u0431\u0435\u0437 \u0441\u0435\u0442\u0438: `n8n/fixtures/wf11_tme_s_preview_sample.html`.",
        "height": 880,
        "width": 600,
        "color": 5
      }
    },
    {
      "id": "bb110000-0000-0000-0000-000000000001",
      "name": "Manual Start",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -420,
        560
      ],
      "parameters": {}
    },
    {
      "id": "bb110000-0000-0000-0000-0000000000cf",
      "name": "Set Connector Config",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -200,
        560
      ],
      "parameters": {
        "jsCode": "// WF11 Social Source Connector Foundation config \u2014 Telegram PUBLIC channel preview (t.me/s/<channel>).\n// Deterministic ids; MSK timestamps; NO external calls by default (fixture-first, DEC-110).\nfunction moscowIsoNow(){var m=new Date(Date.now()+10800000);return m.toISOString().replace('Z','+03:00');}\nfunction moscowStamp(){var z=function(n){return String(n).padStart(2,'0');};var m=new Date(Date.now()+10800000);return m.getUTCFullYear()+z(m.getUTCMonth()+1)+z(m.getUTCDate())+'_'+z(m.getUTCHours())+z(m.getUTCMinutes())+z(m.getUTCSeconds());}\nconst now=moscowIsoNow();\nconst stamp=moscowStamp();\nvar base = ({\n  fixture_mode:true,\n  live_mode:false,\n  // --- live v0.4.2 GATE-PROTECTED PATH (DEC-116/120/132/135): real Telegram public preview fetch, inert by default.\n  // live_mode=false, approval token empty, tracked-channel list empty -> the LIVE Preview Approval Gate blocks\n  // every external call. Transport nodes stay ENABLED but are UNREACHABLE unless the gate passes. Arming live\n  // requires ALL of: live_mode=true, exact token, a non-empty validated tracked-channel list, a selected transport.\n  // Scope is hard-limited: PUBLIC https://t.me/s/<channel> preview pages only \u2014 no groups, no private chats,\n  // no t.me/+ invite links, no joinchat, no t.me/c private channels, no MTProto/sessions, no login,\n  // no member extraction, no hidden contacts, no auto-outreach.\n  live_transport:'firecrawl',          // PREFERRED transport (Firecrawl scrape) if a Firecrawl credential exists\n  live_transport_fallback:'http_get',  // fallback: plain HTTP GET of the public preview page (no per-call fee)\n  live_channel_allowlist:[],           // usernames OR https://t.me/s/<channel> / https://t.me/<channel>\n  live_max_channels:2,                 // first-smoke cap (<=2)\n  live_max_posts_per_channel:10,       // <=10\n  live_approval_token:'',\n  live_requires_operator_approval:true,\n  source_type:'social_channel',\n  platform:'telegram',\n  requested_by:'operator',\n  request_type:'social_source_discovery',\n  region:'\u041c\u043e\u0441\u043a\u0432\u0430/\u041c\u041e',\n  service_focus:'credit_broker',\n  channel_allowlist:[\n    'kredit_broker_msk_fixture',\n    'ipoteka_refinance_fixture',\n    'finance_news_fixture',\n    'biz_uslugi_fixture'\n  ],\n  preview_url_template:'https://t.me/s/<channel>',\n  pipeline_limit:10,\n  include_irrelevant_control_fixture:true,\n  approval_status_for_unique:'new',\n  write_duplicate_audit:true,\n  hard_skip_debug_audit:false,\n  live_debug_audit:false,\n  duplicate_next_action:'monitor_duplicate',\n  spreadsheet_id_placeholder:'PASTE_SPREADSHEET_ID',\n  agent_request_id:'wf11_req_'+stamp,\n  run_id:'wf11_'+stamp,\n  created_at:now\n});\n\n// --- Stage 5 agent-called mode: WF20 passes the APPROVED plan bounds. Live runs still pass ONLY through the\n// --- same LIVE Preview Approval Gate; public t.me/s previews only (scope policy unchanged).\nvar __ci=null;try{var __c=$('When Called by Agent').first().json;if(__c&&String(__c.agent_request_id||'').length)__ci=__c;}catch(e){__ci=null;}\nif(__ci){\n  var __ch=String(__ci.channels||'').split(/[\\s,;]+/).map(function(s){return s.replace(/^https?:\\/\\/t\\.me\\/(s\\/)?/i,'').replace(/^@/,'').trim();}).filter(Boolean).slice(0,3);\n  var __pp=Math.min(Math.max(1,Number(__ci.max_posts)||30),30);\n  var __live=String(__ci.data_mode||'fixture').toLowerCase()==='live';\n  base.agent_called=true;\n  base.requested_by='agent';\n  base.agent_request_id=String(__ci.agent_request_id);\n  base.run_id=String(__ci.source_run_id||base.run_id);\n  base.workflow_run_id=String(__ci.workflow_run_id||'');\n  base.data_mode=__live?'live':'fixture';\n  base.fixture_mode=!__live;\n  base.live_mode=__live;\n  base.live_channel_allowlist=__ch;\n  base.live_max_channels=Math.min(Math.max(1,__ch.length||1),3);\n  base.live_max_posts_per_channel=__pp;\n  base.pipeline_limit=Math.min(__pp*(__ch.length||1),90); // TELEGRAM-CAP-001: approved total ceiling (<=90), per-channel cap stays live_max_posts_per_channel\n  base.live_transport=String(__ci.transport||'http_get');\n  base.live_approval_token=String(__ci.approval_token||'');\n}\nreturn [{ json: base }];\n"
      }
    },
    {
      "id": "bb110000-0000-0000-0000-0000000000if",
      "name": "IF fixture_mode?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        20,
        560
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "cond-fixture-mode",
              "leftValue": "={{ $json.fixture_mode }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "bb110000-0000-0000-0000-0000000000fx",
      "name": "Build Fixture Telegram Channel Posts",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        440
      ],
      "parameters": {
        "jsCode": "// Fixture Telegram PUBLIC channel preview posts (what a t.me/s/<channel> parse would yield). $0, no network.\n// Channel names carry the _fixture suffix on purpose \u2014 they are NOT real channels.\n// Posts model: competitor ad copy (the main value of Telegram channels per Stage 3.4), one weak finance\n// longread (market_signal), one hard-negative control (legal-address ad), one in-batch duplicate.\nconst posts=[\n { channel:'kredit_broker_msk_fixture', channel_title:'\u041a\u0440\u0435\u0434\u0438\u0442\u043d\u044b\u0439 \u0431\u0440\u043e\u043a\u0435\u0440 \u041c\u043e\u0441\u043a\u0432\u0430 (fixture)', post_id:'101',\n   post_url:'https://t.me/kredit_broker_msk_fixture/101',\n   published_at:'2026-06-11T14:05:00+03:00',\n   text:'\u041a\u0440\u0435\u0434\u0438\u0442\u043d\u044b\u0439 \u0431\u0440\u043e\u043a\u0435\u0440. \u041f\u043e\u043c\u043e\u0449\u044c \u0432 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0438 \u043a\u0440\u0435\u0434\u0438\u0442\u0430 \u043f\u043e\u0441\u043b\u0435 \u043e\u0442\u043a\u0430\u0437\u043e\u0432 \u0438 \u0441 \u043f\u043b\u043e\u0445\u043e\u0439 \u043a\u0440\u0435\u0434\u0438\u0442\u043d\u043e\u0439 \u0438\u0441\u0442\u043e\u0440\u0438\u0435\u0439. \u0411\u0435\u0437 \u043f\u0440\u0435\u0434\u043e\u043f\u043b\u0430\u0442\u044b, \u043e\u043f\u043b\u0430\u0442\u0430 \u0437\u0430 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442. \u041a\u043e\u043c\u0438\u0441\u0441\u0438\u044f \u043e\u0442 3%. \u041c\u043e\u0441\u043a\u0432\u0430 \u0438 \u041c\u041e. \u0421\u0432\u044f\u0437\u044c: @kredit_broker_msk_fixture' },\n { channel:'kredit_broker_msk_fixture', channel_title:'\u041a\u0440\u0435\u0434\u0438\u0442\u043d\u044b\u0439 \u0431\u0440\u043e\u043a\u0435\u0440 \u041c\u043e\u0441\u043a\u0432\u0430 (fixture)', post_id:'102',\n   post_url:'https://t.me/kredit_broker_msk_fixture/102',\n   published_at:'2026-06-11T18:40:00+03:00',\n   text:'\u041a\u0440\u0435\u0434\u0438\u0442 \u0434\u043b\u044f \u0431\u0438\u0437\u043d\u0435\u0441\u0430 \u0418\u041f \u0438 \u041e\u041e\u041e: \u043e\u0431\u043e\u0440\u043e\u0442\u043d\u044b\u0435 \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0430, \u0442\u0435\u043d\u0434\u0435\u0440\u043d\u044b\u0435 \u0437\u0430\u0439\u043c\u044b, \u0431\u0430\u043d\u043a\u043e\u0432\u0441\u043a\u0438\u0435 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0438. \u0420\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u043f\u043e \u0434\u043e\u0433\u043e\u0432\u043e\u0440\u0443. \u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u0437\u0430 1 \u0434\u0435\u043d\u044c.' },\n { channel:'ipoteka_refinance_fixture', channel_title:'\u0418\u043f\u043e\u0442\u0435\u043a\u0430 \u0438 \u0440\u0435\u0444\u0438\u043d\u0430\u043d\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 (fixture)', post_id:'55',\n   post_url:'https://t.me/ipoteka_refinance_fixture/55',\n   published_at:'2026-06-10T11:20:00+03:00',\n   text:'\u0420\u0435\u0444\u0438\u043d\u0430\u043d\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438\u043f\u043e\u0442\u0435\u043a\u0438: \u0441\u043d\u0438\u0436\u0435\u043d\u0438\u0435 \u0441\u0442\u0430\u0432\u043a\u0438, \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u043a\u0440\u0435\u0434\u0438\u0442\u043e\u0432 \u0432 \u043e\u0434\u0438\u043d \u043f\u043b\u0430\u0442\u0451\u0436. \u0420\u0430\u0441\u0447\u0451\u0442 \u0432\u044b\u0433\u043e\u0434\u044b \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u043e. \u0420\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u043f\u043e \u0434\u043e\u0433\u043e\u0432\u043e\u0440\u0443, \u041c\u043e\u0441\u043a\u0432\u0430.' },\n { channel:'finance_news_fixture', channel_title:'\u0424\u0438\u043d\u0430\u043d\u0441\u043e\u0432\u044b\u0435 \u043d\u043e\u0432\u043e\u0441\u0442\u0438 (fixture)', post_id:'9001',\n   post_url:'https://t.me/finance_news_fixture/9001',\n   published_at:'2026-06-09T09:00:00+03:00',\n   text:'\u0411\u0430\u043d\u043a\u0438 \u0443\u0436\u0435\u0441\u0442\u043e\u0447\u0438\u043b\u0438 \u0441\u043a\u043e\u0440\u0438\u043d\u0433 \u0432 2026 \u0433\u043e\u0434\u0443: \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043e\u0442\u043a\u0430\u0437\u043e\u0432 \u043f\u043e \u043f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u043c \u043a\u0440\u0435\u0434\u0438\u0442\u0430\u043c \u0432\u044b\u0440\u043e\u0441\u043b\u043e. \u0420\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u043c, \u0447\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u0437\u0430\u0451\u043c\u0449\u0438\u043a\u0443 \u043f\u043e\u0441\u043b\u0435 \u043e\u0442\u043a\u0430\u0437\u0430 \u0431\u0430\u043d\u043a\u0430 \u0438 \u043a\u0430\u043a \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b.' },\n { channel:'biz_uslugi_fixture', channel_title:'\u0411\u0438\u0437\u043d\u0435\u0441-\u0443\u0441\u043b\u0443\u0433\u0438 (fixture)', post_id:'77',\n   post_url:'https://t.me/biz_uslugi_fixture/77',\n   published_at:'2026-06-11T10:00:00+03:00',\n   text:'\u042e\u0440\u0438\u0434\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0430\u0434\u0440\u0435\u0441 \u0434\u043b\u044f \u041e\u041e\u041e \u043e\u0442 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u0438\u043a\u0430. \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u041e\u041e\u041e \u0438 \u0418\u041f \u043f\u043e\u0434 \u043a\u043b\u044e\u0447, \u0431\u0443\u0445\u0433\u0430\u043b\u0442\u0435\u0440\u0441\u043a\u043e\u0435 \u0441\u043e\u043f\u0440\u043e\u0432\u043e\u0436\u0434\u0435\u043d\u0438\u0435. \u041c\u0435\u0442\u0440\u043e \u0422\u0430\u0433\u0430\u043d\u0441\u043a\u0430\u044f.' },\n { channel:'kredit_broker_msk_fixture', channel_title:'\u041a\u0440\u0435\u0434\u0438\u0442\u043d\u044b\u0439 \u0431\u0440\u043e\u043a\u0435\u0440 \u041c\u043e\u0441\u043a\u0432\u0430 (fixture)', post_id:'101',\n   post_url:'https://t.me/kredit_broker_msk_fixture/101?single',\n   published_at:'2026-06-11T14:05:00+03:00',\n   text:'\u041a\u0440\u0435\u0434\u0438\u0442\u043d\u044b\u0439 \u0431\u0440\u043e\u043a\u0435\u0440. \u041f\u043e\u043c\u043e\u0449\u044c \u0432 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0438 \u043a\u0440\u0435\u0434\u0438\u0442\u0430 \u043f\u043e\u0441\u043b\u0435 \u043e\u0442\u043a\u0430\u0437\u043e\u0432 \u0438 \u0441 \u043f\u043b\u043e\u0445\u043e\u0439 \u043a\u0440\u0435\u0434\u0438\u0442\u043d\u043e\u0439 \u0438\u0441\u0442\u043e\u0440\u0438\u0435\u0439. \u0411\u0435\u0437 \u043f\u0440\u0435\u0434\u043e\u043f\u043b\u0430\u0442\u044b, \u043e\u043f\u043b\u0430\u0442\u0430 \u0437\u0430 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442. \u041a\u043e\u043c\u0438\u0441\u0441\u0438\u044f \u043e\u0442 3%. \u041c\u043e\u0441\u043a\u0432\u0430 \u0438 \u041c\u041e. \u0421\u0432\u044f\u0437\u044c: @kredit_broker_msk_fixture' }\n];\nreturn posts.map(p=>({json:p}));"
      }
    },
    {
      "id": "bb110000-0000-0000-0000-0000000000lg",
      "name": "LIVE Preview Approval Gate",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        700
      ],
      "parameters": {
        "jsCode": "// LIVE PREVIEW APPROVAL GATE v0.4.2 (DEC-116/120/132/135) \u2014 fires on live_mode=true. INERT BY DEFAULT: throws\n// unless the operator armed the run. This gate is the SINGLE safety boundary for external calls: the transport\n// nodes downstream stay enabled but are unreachable unless this gate passes (exact token + non-empty validated\n// tracked-channel list + selected transport). Same pattern as the other live-capable workflows.\n// Hard scope (CONTACT_AND_OUTREACH_POLICY): PUBLIC https://t.me/s/<channel> preview pages ONLY \u2014 no groups,\n// no private chats, no t.me/+ invites, no joinchat, no t.me/c private channels, no MTProto/sessions,\n// no login scraping, no member extraction, no hidden contacts, no outreach.\nfunction str(v){return v==null?'':String(v).trim();}\nconst cfg=$('Set Connector Config').first().json;\nconst token=str(cfg.live_approval_token);\nconst transport=str(cfg.live_transport)||'firecrawl';\nconst maxPosts=Math.min(Number(cfg.live_max_posts_per_channel)||10,30);\nconst maxChannels=Math.min(Number(cfg.live_max_channels)||2,3);\nif(token!=='I_APPROVE_LIVE_TELEGRAM_PREVIEW'){\n  throw new Error('WF11 live gate: operator approval token missing. Live Telegram public-channel preview requires explicit operator approval: set live_approval_token=I_APPROVE_LIVE_TELEGRAM_PREVIEW, fill the tracked-channel list (live_channel_allowlist), choose live_transport (firecrawl|http_get). Transport nodes are enabled but only reachable through this gate. Default run stays fixture-only ($0). See docs/N8N_WORKFLOW_11_SOCIAL_SOURCE_CONNECTOR_FOUNDATION_RU.md and STAGE_3_4 \u00a75.7.');\n}\nif(transport!=='firecrawl'&&transport!=='http_get'){\n  throw new Error('WF11 live gate: live_transport must be \"firecrawl\" (preferred) or \"http_get\" (fallback).');\n}\nconst raw=Array.isArray(cfg.live_channel_allowlist)?cfg.live_channel_allowlist.map(c=>str(c)).filter(Boolean):[];\nif(!raw.length){ throw new Error('WF11 live gate: the tracked-channel list (live_channel_allowlist) is empty \u2014 tracked-channels-only operation, no discovery crawling.'); }\n// non-public / non-channel paths that must never be fetched\nconst BLOCK=['joinchat','c','s','+','addstickers','proxy','socks','iv','share','login'];\nfunction normChannel(entry){\n  entry=str(entry);\n  if(!entry)return null;\n  // private invite links / groups: t.me/+<hash>, joinchat, leading +\n  if(/joinchat/i.test(entry)||/t\\.me\\/\\+/i.test(entry)||entry.charAt(0)==='+'||entry.indexOf('/+')>=0)return null;\n  let c=entry;\n  let m=entry.match(/t\\.me\\/s\\/([A-Za-z0-9_]+)/i)||entry.match(/t\\.me\\/([A-Za-z0-9_]+)/i);\n  if(m)c=m[1];\n  c=c.replace(/^@/,'');\n  if(BLOCK.indexOf(c.toLowerCase())>=0)return null;      // private/system paths (t.me/c, joinchat, ...)\n  if(!/^[A-Za-z0-9_]{4,32}$/.test(c))return null;        // public channel username charset only\n  if(!/[A-Za-z]/.test(c))return null;                    // must contain a letter (reject numeric ids)\n  return c;\n}\nconst good=[];const bad=[];\nfor(const e of raw){const c=normChannel(e);if(c){if(good.indexOf(c)<0)good.push(c);}else bad.push(e);}\nif(bad.length){ throw new Error('WF11 live gate: rejected non-public-channel entries (groups/invite links/private/joinchat/t.me-c are forbidden): '+bad.join(', ')); }\nconst channels=good.slice(0,maxChannels); // MVP cap (<=3 channels, <=30 posts each)\n// one item per tracked channel; the gate-protected transport node fetches the PUBLIC preview page only.\nreturn channels.map(c=>({ json: { channel:c, fetch_url:'https://t.me/s/'+c, max_posts:maxPosts, live_transport:transport } }));"
      }
    },
    {
      "id": "bb110000-0000-0000-0000-0000000000nm",
      "name": "Normalize Telegram Posts",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        440
      ],
      "parameters": {
        "jsCode": "// Normalize Telegram public-channel preview posts into the WF09-compatible record shape. Deterministic, $0.\n// v0.4.1 (DEC-133): relevance is decided by POST-LEVEL evidence ONLY. Channel title/username/allowlist context\n// may RAISE confidence/metadata but can NEVER by itself make a post business-relevant. Posts with no post-level\n// finance/broker evidence are classed irrelevant_live_false_positive and skipped (NOT written to\n// raw_market_records / market_record_registry by default). Pure market news/digest -> market_signal;\n// competitor_activity REQUIRES post-level offering/competitor evidence (pitch/case/price/CTA/commission/...).\n// Contact policy (DEC-097/098): contact_public copied ONLY when verbatim in the public post text.\nfunction str(v){return v==null?'':String(v).trim();}\nfunction low(v){return str(v).toLowerCase();}\nfunction cut(s,n){s=str(s);return s.length>n?s.slice(0,n):s;}\nfunction hashText(s){s=str(s);let h=5381;for(let i=0;i<s.length;i++){h=((h<<5)+h+s.charCodeAt(i))>>>0;}return ('00000000'+h.toString(16)).slice(-8);}\nfunction canonPostUrl(u){u=str(u);return u.replace(/[?#].*$/,'').replace(/\\/+$/,'');}\nfunction detectSysEvent(t){t=str(t);return /channel name was changed to|\u043a\u0430\u043d\u0430\u043b \u043f\u0435\u0440\u0435\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d|(\u0441\u043c\u0435\u043d\u0438\u043b|\u0438\u0437\u043c\u0435\u043d\u0438\u043b|\u043f\u043e\u043c\u0435\u043d\u044f\u043b)\\s+\u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\\s+\u043a\u0430\u043d\u0430\u043b\u0430|(\u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435|\u0438\u043c\u044f)\\s+\u043a\u0430\u043d\u0430\u043b\u0430\\s+(\u0438\u0437\u043c\u0435\u043d\u0435\u043d\u043e|\u0441\u043c\u0435\u043d\u0435\u043d\u043e)|(\u0441\u043c\u0435\u043d\u0438\u043b|\u0438\u0437\u043c\u0435\u043d\u0438\u043b|\u043e\u0431\u043d\u043e\u0432\u0438\u043b|\u0443\u0434\u0430\u043b\u0438\u043b)\\s+\u0444\u043e\u0442\u043e\\s+\u043a\u0430\u043d\u0430\u043b\u0430|(\u0444\u043e\u0442\u043e|\u0430\u0432\u0430\u0442\u0430\u0440)\\s+\u043a\u0430\u043d\u0430\u043b\u0430\\s+(\u0438\u0437\u043c\u0435\u043d\u0435\u043d\u043e|\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u043e)|channel photo (was )?(changed|updated)|channel (was )?created|\u043a\u0430\u043d\u0430\u043b \u0441\u043e\u0437\u0434\u0430\u043d|(\u0437\u0430\u043a\u0440\u0435\u043f\u0438\u043b|\u043e\u0442\u043a\u0440\u0435\u043f\u0438\u043b)\\s+\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438/i.test(t);}\nfunction freshnessOf(pub){var p=Date.parse(str(pub));var now=Date.now();if(isNaN(p))return {age_days:null,freshness_bucket:'unknown',within_default_window:false};var age=Math.floor((now-p)/86400000);var b=age>30?'stale':(age>15?'aging':'fresh');return {age_days:age,freshness_bucket:b,within_default_window:age<=30};}\nfunction detectNegQuote(t){t=str(t);var tl=low(t);var scam=/\u043c\u043e\u0448\u0435\u043d\u043d\u0438\u0447\u0435\u0441\u0442\u0432|\u043e\u0431\u043c\u0430\u043d|\u0440\u0430\u0437\u0432\u043e\u0434|\u043e\u0441\u0442\u043e\u0440\u043e\u0436\u043d|\u043d\u0435 \u0432\u0435\u0440\u044c\u0442\u0435|\u043d\u0435 \u0432\u0435\u0434\u0438\u0442\u0435\u0441\u044c|\u043e\u0441\u0442\u0435\u0440\u0435\u0433\u0430\u0439\u0442\u0435\u0441\u044c/.test(tl);var claims=[];var pats=[/\u0443\u0434\u0430\u043b[\u0430-\u044f\u0451]* \u043f\u0440\u043e\u0441\u0440\u043e\u0447\u043a/i,/\u043e\u0447\u0438\u0441\u0442[\u0430-\u044f\u0451]* \u043a\u0440\u0435\u0434\u0438\u0442\u043d/i,/\u0437\u0430 \u043e\u0434\u0438\u043d \u0434\u0435\u043d\u044c/i,/\u0437\u0430 1 \u0434\u0435\u043d\u044c/i,/\u0441\u043e\u0442\u0440[\u0435\u0451]\u043c \u043a\u0440\u0435\u0434\u0438\u0442\u043d/i];for(var i=0;i<pats.length;i++){var m=pats[i].exec(t);if(m)claims.push(m[0]);}var has=scam&&claims.length>0;return {has_quoted_scam:has,quoted_claims:has?claims:[]};}\nvar AFFIL=['\u043f\u0430\u0440\u0442\u043d\u0451\u0440\u0441\u043a','\u043f\u0430\u0440\u0442\u043d\u0435\u0440\u0441\u043a','\u0440\u0435\u0444\u0435\u0440\u0430\u043b\u044c\u043d','\u0440\u0435\u0444\u0435\u0440\u0430\u043b','\u0437\u0430 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0437\u0430\u0451\u043c\u0449\u0438\u043a','\u0437\u0430 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0437\u0430\u0435\u043c\u0449\u0438\u043a','\u0432\u043e\u0437\u043d\u0430\u0433\u0440\u0430\u0436\u0434\u0435\u043d\u0438\u0435 \u0437\u0430','\u0430\u0433\u0435\u043d\u0442\u0441\u043a\u043e\u0435 \u0432\u043e\u0437\u043d\u0430\u0433\u0440\u0430\u0436\u0434\u0435\u043d\u0438\u0435','\u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u0430\u044f \u0441\u0441\u044b\u043b\u043a\u0430','\u043b\u0438\u0447\u043d\u0430\u044f \u0441\u0441\u044b\u043b\u043a\u0430','\u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0439\u0442\u0435\u0441\u044c \u0438 \u043f\u043e\u043b\u0443\u0447'];\n// term matcher: short tokens (<=3 chars, e.g. \u0438\u043f/\u043a\u0438/\u0446\u0431) require word boundaries (Cyrillic+Latin aware);\n// longer tokens use substring. Applied to POST TEXT ONLY for relevance.\nfunction hasWord(blob,term){return new RegExp('(^|[^a-z\u0430-\u044f\u04510-9])'+term+'([^a-z\u0430-\u044f\u04510-9]|$)','i').test(blob);}\nfunction matchTerms(blob,list){const h=[];for(let i=0;i<list.length;i++){const t=list[i];const ok=(t.length<=3)?hasWord(blob,t):(blob.indexOf(t)>=0);if(ok&&h.indexOf(t)<0)h.push(t);}return h;}\nconst cfg=$('Set Connector Config').first().json;\nconst fixtureMode=(cfg.fixture_mode===true||low(cfg.fixture_mode)==='true');\nconst items=$input.all().map(i=>i.json);\n// POST-LEVEL finance/broker evidence (credit_brokerage niche v0.1; migrates to niche packs, DEC-100).\nconst POSITIVE=['\u043a\u0440\u0435\u0434\u0438\u0442','\u043a\u0440\u0435\u0434\u0438\u0442\u043d','\u043a\u0440\u0435\u0434\u0438\u0442\u043d\u0430\u044f \u0438\u0441\u0442\u043e\u0440\u0438','\u043a\u0440\u0435\u0434\u0438\u0442\u043d\u043e\u0439 \u0438\u0441\u0442\u043e\u0440\u0438','\u043e\u0442\u043a\u0430\u0437','\u043e\u0434\u043e\u0431\u0440\u0435\u043d\u0438','\u0431\u0430\u043d\u043a','\u0438\u043f\u043e\u0442\u0435\u043a','\u0440\u0435\u0444\u0438\u043d\u0430\u043d\u0441','\u0441\u0442\u0430\u0432\u043a','\u043f\u043b\u0430\u0442\u0451\u0436','\u043f\u043b\u0430\u0442\u0435\u0436','\u043f\u0435\u0440\u0435\u043f\u043b\u0430\u0442','\u0437\u0430\u043b\u043e\u0433','\u0434\u043e\u0445\u043e\u0434','\u0431\u0438\u0437\u043d\u0435\u0441','\u043a\u043e\u043c\u0438\u0441\u0441\u0438','\u0431\u0440\u043e\u043a\u0435\u0440','\u043a\u043e\u043d\u0441\u0443\u043b\u044c\u0442\u0430\u0446\u0438','\u0434\u043e\u043b\u0433\u043e\u0432','\u0441\u043a\u043e\u0440\u0438\u043d\u0433','\u0441\u0442\u0440\u0430\u0445\u043e\u0432','\u0441\u0435\u043c\u0435\u0439\u043d\u0430\u044f \u0438\u043f\u043e\u0442\u0435\u043a','\u043b\u044c\u0433\u043e\u0442\u043d','\u0437\u0430\u0439\u043c','\u0437\u0430\u0451\u043c\u0449\u0438\u043a','\u043f\u0440\u043e\u0441\u0440\u043e\u0447\u043a','\u0431\u0430\u043d\u043a\u0440\u043e\u0442','\u043f\u043e\u0434 \u0437\u0430\u043b\u043e\u0433','\u0434\u043b\u044f \u0431\u0438\u0437\u043d\u0435\u0441\u0430'];\nconst SHORT_POSITIVE=['\u0438\u043f','\u043a\u0438','\u0446\u0431','\u043e\u043e\u043e','\u0431\u043a\u0438'];\n// Competitor/offering POST-LEVEL evidence -> competitor_activity (service pitch/case/price/CTA/commission/...).\nconst OFFER=['\u043a\u0440\u0435\u0434\u0438\u0442\u043d\u044b\u0439 \u0431\u0440\u043e\u043a\u0435\u0440','\u0438\u043f\u043e\u0442\u0435\u0447\u043d\u044b\u0439 \u0431\u0440\u043e\u043a\u0435\u0440','\u043f\u043e\u043c\u043e\u0449\u044c \u0432 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0438 \u043a\u0440\u0435\u0434\u0438\u0442','\u043f\u043e\u043c\u043e\u0436\u0435\u043c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043a\u0440\u0435\u0434\u0438\u0442','\u043f\u043e\u043c\u043e\u0436\u0435\u043c \u0432\u0437\u044f\u0442\u044c \u043a\u0440\u0435\u0434\u0438\u0442','\u043f\u043e\u043c\u043e\u0436\u0435\u043c \u0441 \u043a\u0440\u0435\u0434\u0438\u0442','\u043f\u043e\u043c\u043e\u0436\u0435\u043c \u0441 \u0438\u043f\u043e\u0442\u0435\u043a','\u043f\u043e\u0434\u0431\u043e\u0440 \u0431\u0430\u043d\u043a\u0430','\u043f\u043e\u0434\u0431\u043e\u0440 \u043a\u0440\u0435\u0434\u0438\u0442','\u043f\u043e\u0434\u0431\u0435\u0440\u0451\u043c \u0431\u0430\u043d\u043a','\u043f\u043e\u0434\u0431\u0435\u0440\u0435\u043c \u0431\u0430\u043d\u043a','\u043f\u043e\u0434\u0431\u0435\u0440\u0451\u043c \u043a\u0440\u0435\u0434\u0438\u0442','\u043f\u043e\u0434\u0431\u0435\u0440\u0435\u043c \u043a\u0440\u0435\u0434\u0438\u0442','\u043f\u043e\u0434\u0431\u0435\u0440\u0451\u043c \u0438\u043f\u043e\u0442\u0435\u043a','\u043f\u043e\u0434\u0431\u0435\u0440\u0435\u043c \u0438\u043f\u043e\u0442\u0435\u043a','\u0431\u0435\u0437 \u043f\u0440\u0435\u0434\u043e\u043f\u043b\u0430\u0442\u044b','\u043e\u043f\u043b\u0430\u0442\u0430 \u0437\u0430 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442','\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u043f\u043e \u0434\u043e\u0433\u043e\u0432\u043e\u0440\u0443','\u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u0437\u0430\u044f\u0432\u043a','\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0439\u0442\u0435 \u0437\u0430\u044f\u0432\u043a','\u043d\u0430\u043f\u0438\u0448\u0438\u0442\u0435 \u0432 \u043b\u0438\u0447\u043a','\u043f\u0438\u0448\u0438\u0442\u0435 \u0432 \u043b\u0438\u0447\u043a','\u0432 \u043b\u0438\u0447\u043a\u0443','\u0441\u0432\u044f\u0436\u0438\u0442\u0435\u0441\u044c \u0441 \u043d\u0430\u043c\u0438','\u0437\u0430\u043f\u0438\u0448\u0438\u0442\u0435\u0441\u044c \u043d\u0430 \u043a\u043e\u043d\u0441\u0443\u043b\u044c\u0442\u0430\u0446\u0438','\u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u0430\u044f \u043a\u043e\u043d\u0441\u0443\u043b\u044c\u0442\u0430\u0446\u0438','\u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u044b\u0439 \u0440\u0430\u0441\u0447\u0451\u0442','\u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u044b\u0439 \u0440\u0430\u0441\u0447\u0435\u0442','\u0440\u0430\u0441\u0447\u0451\u0442 \u0432\u044b\u0433\u043e\u0434\u044b','\u0440\u0430\u0441\u0447\u0451\u0442 \u043f\u043b\u0430\u0442\u0435\u0436\u0430','\u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u0435\u043c \u043f\u043b\u0430\u0442\u0451\u0436','\u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u0435\u043c \u043f\u043b\u0430\u0442\u0435\u0436','\u0441\u043d\u0438\u0436\u0435\u043d\u0438\u0435 \u0441\u0442\u0430\u0432\u043a\u0438','\u0441\u043d\u0438\u0437\u0438\u043c \u0441\u0442\u0430\u0432\u043a\u0443','\u0441\u043d\u0438\u0437\u0438\u0442\u044c \u0441\u0442\u0430\u0432\u043a\u0443','\u043e\u0434\u043e\u0431\u0440\u0435\u043d\u0438\u0435 \u043a\u0440\u0435\u0434\u0438\u0442','\u043e\u0434\u043e\u0431\u0440\u0438\u043c \u043a\u0440\u0435\u0434\u0438\u0442','\u043f\u043e\u043c\u043e\u0436\u0435\u043c \u0441 \u043e\u0434\u043e\u0431\u0440\u0435\u043d\u0438','\u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b','\u0438\u0441\u043f\u0440\u0430\u0432\u0438\u043c \u043a\u0440\u0435\u0434\u0438\u0442\u043d','\u0438\u0441\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043a\u0440\u0435\u0434\u0438\u0442\u043d\u043e\u0439 \u0438\u0441\u0442\u043e\u0440\u0438','\u043a\u0440\u0435\u0434\u0438\u0442 \u043f\u043e\u0434 \u0437\u0430\u043b\u043e\u0433','\u0437\u0430\u0439\u043c \u043f\u043e\u0434 \u0437\u0430\u043b\u043e\u0433','\u043a\u0440\u0435\u0434\u0438\u0442 \u0434\u043b\u044f \u0431\u0438\u0437\u043d\u0435\u0441\u0430','\u043e\u0431\u043e\u0440\u043e\u0442\u043d\u044b\u0435 \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0430','\u0442\u0435\u043d\u0434\u0435\u0440\u043d\u044b\u0435 \u0437\u0430\u0439\u043c\u044b','\u0431\u0430\u043d\u043a\u043e\u0432\u0441\u043a\u0438\u0435 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0438','\u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0437\u0430 1 \u0434\u0435\u043d\u044c','\u043d\u0430\u0448 \u043a\u043b\u0438\u0435\u043d\u0442','\u043f\u043e\u043c\u043e\u0433\u043b\u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c','\u043f\u043e\u043c\u043e\u0433\u043b\u0438 \u0441\u043d\u0438\u0437\u0438\u0442\u044c','\u043e\u0434\u043e\u0431\u0440\u0438\u043b\u0438 \u043a\u0440\u0435\u0434\u0438\u0442','\u043e\u0434\u043e\u0431\u0440\u0438\u043b\u0438 \u0438\u043f\u043e\u0442\u0435\u043a','\u043a\u0435\u0439\u0441 \u043a\u043b\u0438\u0435\u043d\u0442\u0430','\u043a\u043e\u043c\u0438\u0441\u0441\u0438\u044f \u043e\u0442','\u0440\u0435\u0444\u0438\u043d\u0430\u043d\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043a\u0440\u0435\u0434\u0438\u0442','\u0440\u0435\u0444\u0438\u043d\u0430\u043d\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438\u043f\u043e\u0442\u0435\u043a','\u0440\u0435\u0444\u0438\u043d\u0430\u043d\u0441\u0438\u0440 \u0438\u043f\u043e\u0442\u0435\u043a'];\n// STRONG, UNAMBIGUOUS broker/credit-service evidence \u2014 overrides adjacent/hard-negative (a real broker post may also mention property/recruitment).\nconst STRONG_SERVICE=['\u043a\u0440\u0435\u0434\u0438\u0442\u043d\u044b\u0439 \u0431\u0440\u043e\u043a\u0435\u0440','\u0438\u043f\u043e\u0442\u0435\u0447\u043d\u044b\u0439 \u0431\u0440\u043e\u043a\u0435\u0440','\u043f\u043e\u043c\u043e\u0449\u044c \u0432 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0438 \u043a\u0440\u0435\u0434\u0438\u0442','\u043f\u043e\u043c\u043e\u0436\u0435\u043c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043a\u0440\u0435\u0434\u0438\u0442','\u043f\u043e\u043c\u043e\u0436\u0435\u043c \u0432\u0437\u044f\u0442\u044c \u043a\u0440\u0435\u0434\u0438\u0442','\u043f\u043e\u0434\u0431\u0435\u0440\u0451\u043c \u0431\u0430\u043d\u043a','\u043f\u043e\u0434\u0431\u0435\u0440\u0435\u043c \u0431\u0430\u043d\u043a','\u043f\u043e\u0434\u0431\u043e\u0440 \u0431\u0430\u043d\u043a\u0430','\u043f\u043e\u0434\u0431\u043e\u0440 \u043a\u0440\u0435\u0434\u0438\u0442','\u043a\u0440\u0435\u0434\u0438\u0442 \u043f\u043e\u0434 \u0437\u0430\u043b\u043e\u0433','\u0437\u0430\u0439\u043c \u043f\u043e\u0434 \u0437\u0430\u043b\u043e\u0433','\u0440\u0435\u0444\u0438\u043d\u0430\u043d\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043a\u0440\u0435\u0434\u0438\u0442','\u0440\u0435\u0444\u0438\u043d\u0430\u043d\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438\u043f\u043e\u0442\u0435\u043a','\u043e\u0434\u043e\u0431\u0440\u0435\u043d\u0438\u0435 \u043a\u0440\u0435\u0434\u0438\u0442','\u043e\u0434\u043e\u0431\u0440\u0438\u043c \u043a\u0440\u0435\u0434\u0438\u0442','\u0438\u0441\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043a\u0440\u0435\u0434\u0438\u0442\u043d\u043e\u0439 \u0438\u0441\u0442\u043e\u0440\u0438','\u043a\u0440\u0435\u0434\u0438\u0442 \u0434\u043b\u044f \u0431\u0438\u0437\u043d\u0435\u0441\u0430','\u043e\u0431\u043e\u0440\u043e\u0442\u043d\u044b\u0435 \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0430','\u0442\u0435\u043d\u0434\u0435\u0440\u043d\u044b\u0435 \u0437\u0430\u0439\u043c\u044b','\u0431\u0430\u043d\u043a\u043e\u0432\u0441\u043a\u0438\u0435 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0438','\u043e\u043f\u043b\u0430\u0442\u0430 \u0437\u0430 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442','\u0431\u0435\u0437 \u043f\u0440\u0435\u0434\u043e\u043f\u043b\u0430\u0442\u044b'];\n// ADJACENT real-estate object/lot promotion (developer/property marketing) \u2014 credit_brokerage MVP skips by default.\nconst RE_OBJECT=['\u043b\u043e\u0442 \u043d\u0435\u0434\u0435\u043b\u0438','\u043e\u0431\u044a\u0435\u043a\u0442 \u043d\u0435\u0434\u0435\u043b\u0438','\u0436\u043a ','\u043d\u043e\u0432\u043e\u0441\u0442\u0440\u043e\u0439\u043a','\u0437\u0430\u0441\u0442\u0440\u043e\u0439\u0449\u0438\u043a','\u043a\u0432\u0430\u0440\u0442\u0438\u0440','\u0430\u043f\u0430\u0440\u0442\u0430\u043c\u0435\u043d\u0442','\u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u043a','\u0435\u0432\u0440\u043e\u0434\u0432\u0443\u0448\u043a','\u0435\u0432\u0440\u043e\u0442\u0440\u0435\u0448\u043a','\u0435\u0432\u0440\u043e\u0442\u0440\u0451\u0448\u043a','\u0442\u0430\u0443\u043d\u0445\u0430\u0443\u0441','\u043a\u043e\u0442\u0442\u0435\u0434\u0436','\u0443\u0447\u0430\u0441\u0442\u043e\u043a','\u0441\u0434\u0430\u0447\u0430 \u0434\u043e\u043c\u0430','\u0434\u043e\u043c \u0441\u0434\u0430\u043d','\u043f\u0440\u043e\u0434\u0430\u0436\u0430 \u043a\u0432\u0430\u0440\u0442\u0438\u0440','\u043f\u0440\u043e\u0434\u0430\u044e\u0442\u0441\u044f \u043a\u0432\u0430\u0440\u0442\u0438\u0440','\u0440\u0430\u0441\u0441\u0440\u043e\u0447\u043a\u0430 \u043e\u0442 \u0437\u0430\u0441\u0442\u0440\u043e\u0439\u0449\u0438\u043a','\u0438\u043f\u043e\u0442\u0435\u043a\u0430 \u043e\u0442 \u0437\u0430\u0441\u0442\u0440\u043e\u0439\u0449\u0438\u043a','\u0446\u0435\u043d\u0430 \u0437\u0430 \u043c\u0435\u0442\u0440','\u0441\u0432\u043e\u0431\u043e\u0434\u043d\u044b\u0435 \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u043a'];\n// ADJACENT real-estate agent recruitment / hiring \u2014 credit_brokerage MVP skips by default.\nconst RE_RECRUIT=['\u0432 \u043f\u043e\u0438\u0441\u043a\u0430\u0445 \u0430\u0433\u0435\u043d\u0442\u043e\u0432','\u0438\u0449\u0435\u043c \u0430\u0433\u0435\u043d\u0442\u043e\u0432','\u0442\u0440\u0435\u0431\u0443\u044e\u0442\u0441\u044f \u0430\u0433\u0435\u043d\u0442\u044b','\u0442\u0440\u0435\u0431\u0443\u044e\u0442\u0441\u044f \u0440\u0438\u0435\u043b\u0442\u043e\u0440','\u043d\u0443\u0436\u043d\u044b \u0430\u0433\u0435\u043d\u0442\u044b','\u0430\u0433\u0435\u043d\u0442 \u043f\u043e \u043d\u0435\u0434\u0432\u0438\u0436\u0438\u043c\u043e\u0441\u0442\u0438','\u0440\u0438\u0435\u043b\u0442\u043e\u0440','\u0440\u0438\u044d\u043b\u0442\u043e\u0440','\u0432\u0430\u043a\u0430\u043d\u0441\u0438\u044f','\u0432\u0430\u043a\u0430\u043d\u0441\u0438\u0438','\u0432 \u043d\u0430\u0448\u0443 \u043a\u043e\u043c\u0430\u043d\u0434\u0443','\u043f\u0440\u0438\u0441\u043e\u0435\u0434\u0438\u043d\u044f\u0439\u0441\u044f \u043a \u043a\u043e\u043c\u0430\u043d\u0434\u0435','\u043f\u0440\u0438\u0441\u043e\u0435\u0434\u0438\u043d\u044f\u0439\u0442\u0435\u0441\u044c \u043a \u043a\u043e\u043c\u0430\u043d\u0434\u0435','% \u043e\u0442 \u0441\u0434\u0435\u043b\u043a\u0438','% \u043e\u0442 \u043a\u043e\u043c\u0438\u0441\u0441\u0438\u0438','\u043d\u0430\u0431\u0438\u0440\u0430\u0435\u043c \u0430\u0433\u0435\u043d\u0442\u043e\u0432','\u0438\u0449\u0435\u043c \u0441\u043e\u0442\u0440\u0443\u0434\u043d\u0438\u043a','\u043e\u0431\u0443\u0447\u0435\u043d\u0438\u0435 \u0441 \u043d\u0443\u043b\u044f','\u0440\u0430\u0431\u043e\u0442\u0430 \u0432 \u043d\u0435\u0434\u0432\u0438\u0436\u0438\u043c\u043e\u0441\u0442\u0438','\u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440 \u043f\u043e \u043f\u0440\u043e\u0434\u0430\u0436\u0430\u043c \u043d\u0435\u0434\u0432\u0438\u0436\u0438\u043c\u043e\u0441\u0442\u0438'];\n// GREETING / holiday / personal / lifestyle \u2014 irrelevant_live_false_positive unless real finance evidence.\nconst GREETING=['\u0441 \u0434\u043d\u0451\u043c','\u0441 \u0434\u043d\u0435\u043c','\u0441 \u043f\u0440\u0430\u0437\u0434\u043d\u0438\u043a','\u043f\u043e\u0437\u0434\u0440\u0430\u0432\u043b\u044f','\u0441 \u043d\u043e\u0432\u044b\u043c \u0433\u043e\u0434\u043e\u043c','\u0441 8 \u043c\u0430\u0440\u0442\u0430','\u0441 23 \u0444\u0435\u0432\u0440\u0430\u043b\u044f','\u0434\u0435\u043d\u044c \u0440\u043e\u0441\u0441\u0438\u0438','\u0434\u043d\u0451\u043c \u0440\u043e\u0441\u0441\u0438\u0438','\u0434\u043d\u0435\u043c \u0440\u043e\u0441\u0441\u0438\u0438','\u043f\u0440\u0430\u0437\u0434\u043d\u0438\u0447\u043d','\u0445\u043e\u0440\u043e\u0448\u0438\u0445 \u0432\u044b\u0445\u043e\u0434\u043d','\u0445\u043e\u0440\u043e\u0448\u0435\u0439 \u043d\u0435\u0434\u0435\u043b\u0438','\u043e\u0442\u043b\u0438\u0447\u043d\u043e\u0439 \u043d\u0435\u0434\u0435\u043b\u0438','\u0445\u043e\u0440\u043e\u0448\u0435\u0433\u043e \u0434\u043d\u044f','\u0434\u043e\u0431\u0440\u043e\u0435 \u0443\u0442\u0440\u043e','\u0434\u043e\u0431\u0440\u044b\u0439 \u0432\u0435\u0447\u0435\u0440','\u0434\u043e\u0431\u0440\u043e\u0439 \u043d\u043e\u0447\u0438','\u043f\u0440\u0438\u0432\u0435\u0442 \u043a\u043e\u0442\u044f\u0442\u043a','\u043a\u043e\u0442\u044f\u0442\u043a','\u043f\u043e\u044f\u0432\u0438\u043b\u043e\u0441\u044c \u0432\u0434\u043e\u0445\u043d\u043e\u0432\u0435\u043d\u0438','\u0432\u0434\u043e\u0445\u043d\u043e\u0432\u0435\u043d\u0438','\u0432\u044b\u0437\u0434\u043e\u0440\u043e\u0432\u043b\u0435\u043d\u0438','\u0432\u044b\u0437\u0434\u043e\u0440\u0430\u0432\u043b\u0438\u0432\u0430\u0439','\u0431\u0435\u0440\u0435\u0433\u0438\u0442\u0435 \u0441\u0435\u0431\u044f','\u0431\u0435\u0440\u0435\u0433\u0438 \u0441\u0435\u0431\u044f','\u0434\u043b\u044f \u0434\u0443\u0448\u0438','\u043a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u044f \u0434\u0432\u0443\u0445 \u0447\u0430\u0441\u043e\u0432','\u0447\u0430\u0441\u0430 \u0434\u043b\u044f \u0441\u0435\u0431\u044f','\u0447\u0430\u0441 \u0434\u043b\u044f \u0441\u0435\u0431\u044f','\u043c\u0435\u0434\u0438\u0442\u0430\u0446\u0438','\u0445\u043e\u0440\u043e\u0448\u0435\u0433\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0438','\u043e\u0442\u043b\u0438\u0447\u043d\u043e\u0433\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0438','\u0441 \u043f\u044f\u0442\u043d\u0438\u0446\u0435\u0439','\u0441 \u043f\u043e\u043d\u0435\u0434\u0435\u043b\u044c\u043d\u0438\u043a'];\n// Market-signal POST-LEVEL evidence (news/digest/rates/programs) -> market_signal (no direct offer required).\nconst MARKET=['\u0446\u0435\u043d\u0442\u0440\u043e\u0431\u0430\u043d\u043a','\u0446\u0431 \u0440\u0444','\u043a\u043b\u044e\u0447\u0435\u0432\u0430\u044f \u0441\u0442\u0430\u0432\u043a\u0430','\u0440\u0435\u0433\u0443\u043b\u044f\u0442\u043e\u0440','\u0433\u043e\u0441\u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a','\u0433\u043e\u0441\u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c','\u043b\u044c\u0433\u043e\u0442\u043d\u0430\u044f \u0438\u043f\u043e\u0442\u0435\u043a\u0430','\u0441\u0435\u043c\u0435\u0439\u043d\u0430\u044f \u0438\u043f\u043e\u0442\u0435\u043a\u0430','\u0443\u0436\u0435\u0441\u0442\u043e\u0447','\u0441\u043c\u044f\u0433\u0447','\u0432\u044b\u0440\u043e\u0441\u043b','\u0432\u044b\u0440\u043e\u0441','\u0441\u043d\u0438\u0437\u0438\u043b','\u043f\u0430\u0434\u0435\u043d\u0438','\u0441\u043f\u0440\u043e\u0441','\u0440\u044b\u043d\u043e\u043a','\u0440\u044b\u043d\u043a\u0435','\u0437\u0430\u043a\u043e\u043d','\u043d\u043e\u0432\u043e\u0441\u0442','\u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438','\u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0430','\u0431\u0430\u043d\u043a\u0438 ','\u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a','\u0442\u0440\u0435\u043d\u0434','\u043f\u0440\u043e\u0433\u043d\u043e\u0437','\u0438\u043d\u0444\u043b\u044f\u0446','\u0432\u044b\u0434\u0430\u0447','\u043e\u0434\u043e\u0431\u0440\u044f\u0435\u043c\u043e\u0441\u0442'];\nconst HARD_NEG=['\u044e\u0440\u0438\u0434\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0430\u0434\u0440\u0435\u0441','\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043e\u043e\u043e','\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u0438\u043f','\u0431\u0443\u0445\u0433\u0430\u043b\u0442\u0435\u0440','\u044d\u043a\u0432\u0430\u0439\u0440\u0438\u043d\u0433','pos-\u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b','\u043a\u0430\u0441\u0441\u0430','\u043f\u0435\u0447\u0430\u0442\u044c \u0438 \u0448\u0442\u0430\u043c\u043f','\u044d\u0446\u043f','\u0430\u0440\u0435\u043d\u0434\u0430 \u043e\u0444\u0438\u0441\u0430','\u043a\u043e\u0432\u043e\u0440\u043a\u0438\u043d\u0433'];\nconst KW=[['\u043a\u0440\u0435\u0434\u0438\u0442\u043d\u044b\u0439 \u0431\u0440\u043e\u043a\u0435\u0440','\u043a\u0440\u0435\u0434\u0438\u0442\u043d\u044b\u0439 \u0431\u0440\u043e\u043a\u0435\u0440'],['\u043f\u043e\u0441\u043b\u0435 \u043e\u0442\u043a\u0430\u0437\u043e\u0432','\u043f\u043e\u0441\u043b\u0435 \u043e\u0442\u043a\u0430\u0437\u043e\u0432'],['\u043f\u043b\u043e\u0445','\u043f\u043b\u043e\u0445\u0430\u044f \u043a\u0440\u0435\u0434\u0438\u0442\u043d\u0430\u044f \u0438\u0441\u0442\u043e\u0440\u0438\u044f'],['\u0431\u0435\u0437 \u043f\u0440\u0435\u0434\u043e\u043f\u043b\u0430\u0442\u044b','\u0431\u0435\u0437 \u043f\u0440\u0435\u0434\u043e\u043f\u043b\u0430\u0442\u044b'],['\u043e\u043f\u043b\u0430\u0442\u0430 \u0437\u0430 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442','\u043e\u043f\u043b\u0430\u0442\u0430 \u0437\u0430 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442'],['\u043f\u043e \u0434\u043e\u0433\u043e\u0432\u043e\u0440\u0443','\u0440\u0430\u0431\u043e\u0442\u0430 \u043f\u043e \u0434\u043e\u0433\u043e\u0432\u043e\u0440\u0443'],['\u0434\u043b\u044f \u0431\u0438\u0437\u043d\u0435\u0441\u0430','\u0431\u0438\u0437\u043d\u0435\u0441-\u043a\u0440\u0435\u0434\u0438\u0442'],['\u043e\u0431\u043e\u0440\u043e\u0442\u043d\u044b\u0435','\u043e\u0431\u043e\u0440\u043e\u0442\u043d\u044b\u0435 \u043a\u0440\u0435\u0434\u0438\u0442\u044b'],['\u0442\u0435\u043d\u0434\u0435\u0440\u043d','\u0442\u0435\u043d\u0434\u0435\u0440\u043d\u044b\u0435 \u0437\u0430\u0439\u043c\u044b'],['\u0431\u0430\u043d\u043a\u043e\u0432\u0441\u043a\u0438\u0435 \u0433\u0430\u0440\u0430\u043d\u0442','\u0431\u0430\u043d\u043a\u043e\u0432\u0441\u043a\u0438\u0435 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0438'],['\u0440\u0435\u0444\u0438\u043d\u0430\u043d\u0441\u0438\u0440','\u0440\u0435\u0444\u0438\u043d\u0430\u043d\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435'],['\u0441\u043d\u0438\u0436\u0435\u043d\u0438\u0435 \u0441\u0442\u0430\u0432\u043a','\u0441\u043d\u0438\u0436\u0435\u043d\u0438\u0435 \u0441\u0442\u0430\u0432\u043a\u0438'],['\u0437\u0430 1 \u0434\u0435\u043d\u044c','\u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c'],['\u043a\u043e\u043c\u0438\u0441\u0441\u0438\u044f \u043e\u0442','\u0446\u0435\u043d\u043e\u0432\u043e\u0439 \u044f\u043a\u043e\u0440\u044c']];\nconst out=[];\nfor(const p of items){\n  const channel=str(p.channel);const postId=str(p.post_id);const text=str(p.text);\n  const textBlob=low(text);                                 // RELEVANCE source: POST TEXT ONLY\n  const channelBlob=low(p.channel_title)+' '+low(channel);  // metadata/confidence ONLY (never relevance)\n  const valid=!!(channel&&postId&&text.length>=30);\n  const posHit=matchTerms(textBlob,POSITIVE).concat(SHORT_POSITIVE.filter(t=>hasWord(textBlob,t)));\n  const offerHit=matchTerms(textBlob,OFFER);\n  const marketHit=matchTerms(textBlob,MARKET);\n  const negHit=matchTerms(textBlob,HARD_NEG);\n  const objectHit=matchTerms(textBlob,RE_OBJECT);            // real-estate object/lot promo (POST TEXT ONLY)\n  const recruitHit=matchTerms(textBlob,RE_RECRUIT);          // real-estate agent recruitment (POST TEXT ONLY)\n  const greetHit=matchTerms(textBlob,GREETING);             // greeting/holiday/personal (POST TEXT ONLY)\n  const strongHit=matchTerms(textBlob,STRONG_SERVICE);      // unambiguous broker/credit-service evidence\n  const affHit=matchTerms(textBlob,AFFIL);                  // \u00a79.4 partner/referral program\n  const hasFinanceEvidence=(posHit.length>0||offerHit.length>0||marketHit.length>0||strongHit.length>0);\n  const hasServiceOffer=(offerHit.length>0||strongHit.length>0||affHit.length>0);\n  let relevance_class='';let record_type_hint='irrelevant';let predicted_entity_type='irrelevant';\n  let predicted_route='skipped_log';let confidence=10;let hard_skip=false;let relevance_reason='';\n  if(!valid){ relevance_class='invalid';relevance_reason='invalid: missing channel/post_id or text<30'; }\n  else if(detectSysEvent(text)){ relevance_class='system_event';record_type_hint='system_event';predicted_entity_type='system_event';predicted_route='skipped_log';hard_skip=true;confidence=1;relevance_reason='telegram_service_message (channel rename/photo/pin) \u2014 service NOT derived from new title'; }\n  else if(negHit.length&&!strongHit.length){ relevance_class='hard_negative';hard_skip=true;relevance_reason='hard negative (unrelated B2B): '+negHit.slice(0,3).join(', '); }\n  else if((recruitHit.length||objectHit.length)&&!strongHit.length){ relevance_class='adjacent_real_estate_signal';hard_skip=true;relevance_reason='adjacent_real_estate_signal (skipped by default for credit_brokerage MVP; no strong broker/credit-service evidence): '+(recruitHit.concat(objectHit)).slice(0,4).join(', '); }\n  else if(greetHit.length&&!hasServiceOffer&&marketHit.length<2){ relevance_class='irrelevant_false_positive';hard_skip=true;relevance_reason='irrelevant_live_false_positive: greeting/holiday/personal/lifestyle without post-level finance evidence ('+greetHit.slice(0,3).join(', ')+')'; }\n  else if(!hasFinanceEvidence){ relevance_class='irrelevant_false_positive';hard_skip=true;relevance_reason='irrelevant_live_false_positive: no post-level finance/broker evidence (channel context alone is not sufficient)'; }\n  else if(hasServiceOffer){ relevance_class='competitor_activity';record_type_hint='competitor_activity';predicted_entity_type='competitor';predicted_route='monitor_queue';confidence=70;relevance_reason='competitor/offering evidence (post-level service/case/CTA): '+(strongHit.concat(offerHit)).slice(0,5).join(', '); }\n  else { relevance_class='market_signal';record_type_hint='market_signal';predicted_entity_type='market_signal';predicted_route='content_queue';confidence=45;relevance_reason=(marketHit.length?('market signal (news/program/rate, no direct offer): '+marketHit.slice(0,5).join(', ')):('finance context (no service offer): '+posHit.slice(0,5).join(', '))); }\n  // channel context may only RAISE confidence on an ALREADY-relevant post \u2014 never create relevance.\n  if(record_type_hint!=='irrelevant'&&/\u043a\u0440\u0435\u0434\u0438\u0442|\u0438\u043f\u043e\u0442\u0435\u043a|\u0431\u0440\u043e\u043a\u0435\u0440|\u0444\u0438\u043d\u0430\u043d\u0441|\u0437\u0430\u0439\u043c/.test(channelBlob)){confidence=Math.min(confidence+10,95);}\n  // public contact: verbatim @handle or +7 phone in POST TEXT only \u2014 never reconstructed.\n  let contact='';let contactChannel='';let contactFormat='';\n  const h=text.match(/@[a-zA-Z0-9_]{5,32}/);\n  const ph=text.match(/(?:\\+7|8)[\\s\\-(]*\\d{3}[\\s\\-)]*\\d{3}[\\s\\-]*\\d{2}[\\s\\-]*\\d{2}/);\n  if(ph){contact=ph[0];contactChannel='phone';contactFormat='phone';}\n  else if(h){contact=h[0];contactChannel='telegram';contactFormat='handle';}\n  const kws=[];const seen={};for(const k of KW){if(textBlob.indexOf(k[0])>=0&&!seen[k[1]]){seen[k[1]]=1;kws.push(k[1]);}}\n  let service='unknown';\n  if(textBlob.indexOf('\u0438\u043f\u043e\u0442\u0435\u043a')>=0||textBlob.indexOf('\u0440\u0435\u0444\u0438\u043d\u0430\u043d\u0441\u0438\u0440')>=0)service='mortgage_refinance';\n  else if(textBlob.indexOf('\u0434\u043b\u044f \u0431\u0438\u0437\u043d\u0435\u0441\u0430')>=0||textBlob.indexOf('\u043e\u0431\u043e\u0440\u043e\u0442\u043d\u044b\u0435')>=0||textBlob.indexOf('\u0442\u0435\u043d\u0434\u0435\u0440\u043d')>=0)service='business_credit';\n  else if(textBlob.indexOf('\u043f\u043e\u0441\u043b\u0435 \u043e\u0442\u043a\u0430\u0437\u043e\u0432')>=0||textBlob.indexOf('\u043e\u0442\u043a\u0430\u0437')>=0)service='credit_after_refusals';\n  if(textBlob.indexOf('\u043a\u0440\u0435\u0434\u0438\u0442\u043d\u044b\u0439 \u0431\u0440\u043e\u043a\u0435\u0440')>=0)service='credit_broker';\n  if(record_type_hint==='system_event')service='unknown';\n  const canon=canonPostUrl(p.post_url)||('https://t.me/'+channel+'/'+postId);\n  // \u00a79.4 affiliate subtype, \u00a79.6 negation/quotation, \u00a79.7 freshness, \u00a713 data_mode + eligibility\n  let activity_subtype='';\n  if(record_type_hint==='competitor_activity'){ activity_subtype = (affHit.length?'affiliate_program':((strongHit.length&&/\u0440\u0430\u0437\u0431\u0438\u0440\u0430|\u043e\u0431\u044a\u044f\u0441\u043d\u044f|\u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430|\u043a\u0435\u0439\u0441|\u043a\u0430\u043a (\u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432|\u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432|\u043f\u043e\u043b\u0443\u0447)/i.test(textBlob))?'educational_offer':'direct_offer')); }\n  const nq=detectNegQuote(text);\n  const fr=freshnessOf(p.published_at);\n  const dataMode=(fixtureMode?'fixture':'live');\n  const skip_reason=(record_type_hint==='system_event')?'telegram_service_message':(hard_skip?relevance_class:'');\n  const report_eligible=(dataMode==='live')&&!hard_skip&&record_type_hint!=='system_event'&&record_type_hint!=='irrelevant';\n  const llm_eligible=!hard_skip&&record_type_hint!=='system_event'&&valid;\n  out.push({ json: {\n    is_valid_listing:valid,\n    hard_skip:hard_skip,\n    relevance_class:relevance_class,\n    relevance_reason:relevance_reason,\n    source_type:str(cfg.source_type)||'social_channel',\n    platform:str(cfg.platform)||'telegram',\n    source_url:'https://t.me/s/'+channel,\n    post_url:canon,\n    profile_url:'https://t.me/'+channel,\n    profile_name:str(p.channel_title)||channel,\n    author_handle:'@'+channel,\n    published_at:str(p.published_at),\n    region_hint:(textBlob.indexOf('\u043c\u043e\u0441\u043a\u0432')>=0?'\u041c\u043e\u0441\u043a\u0432\u0430/\u041c\u041e':''),\n    service_hint:service,\n    query:'allowlist:'+channel,\n    text_context:cut(text,3500),\n    comment_text:'',\n    contact_public:contact,\n    contact_channel:contactChannel,\n    contact_format:contactFormat,\n    dedup_key:'telegram::social_channel::'+canon,\n    text_hash:hashText(low(text)),\n    record_type_hint:record_type_hint,\n    touchpoint_type:(record_type_hint==='competitor_activity')?'public_channel_post':(record_type_hint==='market_signal'?'public_channel_post':'irrelevant_source'),\n    lead_intent_hint:'none',\n    urgency_hint:'none',\n    interest_topic:(record_type_hint==='competitor_activity')?'competitor channel ad copy':(record_type_hint==='market_signal'?'market context':''),\n    probable_need:'',\n    competitor_related:(record_type_hint==='competitor_activity'),\n    competitor_name:(record_type_hint==='competitor_activity')?(str(p.channel_title)||channel):'',\n    semantic_keywords:kws.join(', '),\n    ad_channel_hint:'telegram',\n    confidence_score:confidence,\n    lead_temperature:'none',\n    next_action:'monitor',\n    responsible:'operator',\n    estimated_analysis_cost_usd:0,\n    manager_note:(str(p.view_count)?('views='+str(p.view_count)):''),\n    predicted_route:predicted_route,\n    predicted_entity_type:predicted_entity_type,\n    contact_evidence_url:contact?canon:'',\n    activity_subtype:activity_subtype,\n    skip_reason:skip_reason,\n    data_mode:dataMode,\n    age_days:fr.age_days,\n    freshness_bucket:fr.freshness_bucket,\n    within_default_window:fr.within_default_window,\n    exact_evidence_url:canon,\n    quoted_claims:nq.quoted_claims.join(' | '),\n    report_eligible:report_eligible,\n    llm_eligible:llm_eligible,\n    taxonomy_version:'semantic-v2.0',\n    notes:''\n  }});\n}\nreturn out;"
      }
    },
    {
      "id": "bb110000-0000-0000-0000-0000000000r1",
      "name": "Read market_record_registry",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "position": [
        680,
        440
      ],
      "parameters": {
        "authentication": "serviceAccount",
        "operation": "read",
        "documentId": {
          "__rl": true,
          "value": "={{ $env.MS_SPREADSHEET_ID || \"PASTE_SPREADSHEET_ID\" }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "market_record_registry",
          "mode": "name"
        },
        "options": {}
      },
      "credentials": {
        "googleApi": {
          "name": "<your credential>"
        }
      },
      "alwaysOutputData": true,
      "onError": "continueRegularOutput",
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "id": "bb110000-0000-0000-0000-0000000000dd",
      "name": "Deduplicate Posts",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        900,
        440
      ],
      "parameters": {
        "jsCode": "// Dedup normalized posts vs market_record_registry (by dedup_key) + within batch. No external calls.\n// TELEGRAM-CAP-001: fair bounded persistence \u2014 a per-channel cap AND a global total ceiling, selected by\n// ROUND-ROBIN over the configured channels so an earlier channel can never starve later ones. Only UNIQUE\n// accepted rows consume capacity; invalid, hard-skipped and duplicate rows are recorded but never counted.\n// Global dedup (registry + in-batch) is unchanged; per-channel/per-post provenance is preserved.\nfunction str(v){return v==null?'':String(v).trim();}\nfunction chanKey(r){var c=str(r.author_handle).replace(/^@/,'');if(!c)c=str(r.profile_url).replace(/^https?:\\/\\/t\\.me\\/(s\\/)?/i,'').replace(/\\/.*$/,'');if(!c)c=str(r.post_url).replace(/^https?:\\/\\/t\\.me\\/(s\\/)?/i,'').replace(/\\/.*$/,'');return c.toLowerCase();}\nconst cfg=$('Set Connector Config').first().json;\nconst approvalUnique=str(cfg.approval_status_for_unique)||'new';\nconst dupNext=str(cfg.duplicate_next_action)||'monitor_duplicate';\nconst totalCap=Math.max(1,Math.min(Number(cfg.pipeline_limit)||10,90));\nconst perChannelCap=Math.max(1,Math.min(Number(cfg.live_max_posts_per_channel)||totalCap,30));\nconst recs=$('Normalize Telegram Posts').all().map(i=>i.json);\nlet reg=[];\ntry{reg=$('Read market_record_registry').all().map(i=>i.json).filter(Boolean);}catch(e){reg=[];}\nconst regSet=new Set(reg.map(r=>str(r&&r.dedup_key)).filter(Boolean));\nconst seen=new Set();\nconst results=new Array(recs.length);\nconst candByCh={};const chanOrder=[];\nfor(let i=0;i<recs.length;i++){\n  const r=recs[i];\n  if(r.is_valid_listing===false){results[i]={dedup_status:'invalid',approval_status:'invalid',next_action:'ignore'};continue;}\n  if(r.hard_skip===true){results[i]={dedup_status:'hard_skipped',approval_status:'skipped_irrelevant',next_action:'ignore'};continue;}\n  const key=str(r.dedup_key);\n  if(key&&regSet.has(key)){results[i]={dedup_status:'duplicate_in_registry',approval_status:'duplicate',next_action:dupNext};continue;}\n  if(key&&seen.has(key)){results[i]={dedup_status:'duplicate_in_batch',approval_status:'duplicate',next_action:dupNext};continue;}\n  if(key)seen.add(key);\n  const ch=chanKey(r);\n  if(!candByCh[ch]){candByCh[ch]=[];chanOrder.push(ch);}\n  candByCh[ch].push(i);\n}\nconst accepted=new Set();let globalAccepted=0;const perCh={};let progress=true;\nwhile(globalAccepted<totalCap&&progress){\n  progress=false;\n  for(let c=0;c<chanOrder.length;c++){\n    if(globalAccepted>=totalCap)break;\n    const ch=chanOrder[c];const q=candByCh[ch];const taken=perCh[ch]||0;\n    if(taken<perChannelCap&&taken<q.length){accepted.add(q[taken]);perCh[ch]=taken+1;globalAccepted++;progress=true;}\n  }\n}\nfor(let c=0;c<chanOrder.length;c++){\n  const q=candByCh[chanOrder[c]];\n  for(let k=0;k<q.length;k++){const i=q[k];\n    if(accepted.has(i))results[i]={dedup_status:'unique',approval_status:approvalUnique,next_action:str(recs[i].next_action)};\n    else results[i]={dedup_status:'over_pipeline_limit',approval_status:'skipped',next_action:'ignore'};\n  }\n}\nreturn recs.map((r,i)=>({json:Object.assign({},r,results[i])}));"
      }
    },
    {
      "id": "bb110000-0000-0000-0000-0000000000b1",
      "name": "Build raw_market_records Rows",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1120,
        300
      ],
      "parameters": {
        "jsCode": "// Build raw_market_records rows (exactly 40 columns, matching WF07/WF09 schema). No external calls.\nfunction str(v){return v==null?'':String(v).trim();}\n// Sheets formula-injection / #ERROR! fix (DEC-124): leading + or = is parsed as a formula by Google\n// Sheets (USER_ENTERED) \u2014 '+7 ...' phones become #ERROR!. A leading apostrophe forces text.\nfunction sheetsSafeText(v){v=str(v);return /^[+=]/.test(v)?(\"'\"+v):v;}\nfunction moscowStamp(){var z=function(n){return String(n).padStart(2,'0');};var m=new Date(Date.now()+10800000);return m.getUTCFullYear()+z(m.getUTCMonth()+1)+z(m.getUTCDate())+'_'+z(m.getUTCHours())+z(m.getUTCMinutes())+z(m.getUTCSeconds());}\nconst cfg=$('Set Connector Config').first().json;\nconst reqId=str(cfg.agent_request_id);\nconst created=str(cfg.created_at);\nconst stamp=str(cfg.run_id).replace(/^wf11_/,'')||moscowStamp();\nconst writeDupAudit=(cfg.write_duplicate_audit===true||str(cfg.write_duplicate_audit).toLowerCase()==='true');\nconst hardDebug=(cfg.hard_skip_debug_audit===true||str(cfg.hard_skip_debug_audit).toLowerCase()==='true')||(cfg.live_debug_audit===true||str(cfg.live_debug_audit).toLowerCase()==='true');\nconst recs=$('Deduplicate Posts').all().map(i=>i.json);\nconst out=[];let idx=0;\nfor(const r of recs){\n  idx++;\n  const ds0=str(r.dedup_status);\n  if(ds0==='invalid'||ds0==='over_pipeline_limit')continue;\n  if(ds0==='hard_skipped'&&!hardDebug)continue;\n  if(!writeDupAudit && ds0!=='unique')continue;\n  // contact policy: contact format + evidence URL are recorded in notes (no dedicated columns in the 40-col schema yet)\n  let notes='stage_3_4_telegram_public_channel_preview_connector_foundation';\n  if(str(r.contact_public)){ notes+='; contact_format='+(str(r.contact_format)||'unknown')+'; contact_source_url='+str(r.contact_evidence_url)+'; contact_use_policy=manual_review (CONTACT_AND_OUTREACH_POLICY)'; }\n  out.push({ json: {\n    record_id:'wf11_rec_'+stamp+'_'+idx,\n    agent_request_id:reqId,\n    source_run_id:str(cfg.run_id)||reqId,\n    data_mode:str(cfg.data_mode)||'live',\n    created_at:created,\n    source_type:str(r.source_type),\n    platform:str(r.platform),\n    source_url:str(r.source_url),\n    post_url:str(r.post_url),\n    profile_url:str(r.profile_url),\n    profile_name:str(r.profile_name),\n    author_handle:str(r.author_handle),\n    published_at:str(r.published_at),\n    region_hint:str(r.region_hint),\n    service_hint:str(r.service_hint),\n    query:str(r.query),\n    text_context:str(r.text_context),\n    comment_text:str(r.comment_text),\n    contact_public:sheetsSafeText(r.contact_public),\n    contact_channel:str(r.contact_channel),\n    dedup_key:str(r.dedup_key),\n    record_type_hint:str(r.record_type_hint),\n    touchpoint_type:str(r.touchpoint_type),\n    lead_intent_hint:str(r.lead_intent_hint),\n    urgency_hint:str(r.urgency_hint),\n    interest_topic:str(r.interest_topic),\n    probable_need:str(r.probable_need),\n    competitor_related:(r.competitor_related===true||str(r.competitor_related).toLowerCase()==='true'),\n    competitor_name:str(r.competitor_name),\n    semantic_keywords:str(r.semantic_keywords),\n    ad_channel_hint:str(r.ad_channel_hint),\n    confidence_score:Number(r.confidence_score)||0,\n    lead_temperature:str(r.lead_temperature),\n    next_action:str(r.next_action),\n    responsible:str(r.responsible),\n    dedup_status:str(r.dedup_status),\n    approval_status:str(r.approval_status),\n    approved_by:'',\n    approved_at:'',\n    estimated_analysis_cost_usd:Number(r.estimated_analysis_cost_usd)||0,\n    manager_note:str(r.manager_note),\n    notes:notes\n  }});\n}\n// AGENT-SUMMARY-001: 0 collected rows must NOT kill the summary/ledger chain \u2014 emit a marker\n// item so the run still writes its live_source_runs ledger row (WF16/08/10/12 trust the ledger).\nif(out.length===0){return [{json:{ms_no_rows:true}}];}\nreturn out;"
      }
    },
    {
      "id": "bb110000-0000-0000-0000-0000000000a1",
      "name": "Append raw_market_records",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "position": [
        1340,
        300
      ],
      "parameters": {
        "authentication": "serviceAccount",
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $env.MS_SPREADSHEET_ID || \"PASTE_SPREADSHEET_ID\" }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "raw_market_records",
          "mode": "name"
        },
        "options": {},
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": []
        }
      },
      "credentials": {
        "googleApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "id": "bb110000-0000-0000-0000-0000000000b2",
      "name": "Build market_record_registry Rows",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1560,
        200
      ],
      "parameters": {
        "jsCode": "// Build market_record_registry rows for UNIQUE records only (exactly 15 columns). No external calls.\nfunction str(v){return v==null?'':String(v).trim();}\nfunction moscowIsoNow(){var m=new Date(Date.now()+10800000);return m.toISOString().replace('Z','+03:00');}\nconst now=moscowIsoNow();\nconst recs=$('Deduplicate Posts').all().map(i=>i.json);\nconst out=[];\nfor(const r of recs){\n  if(str(r.dedup_status)!=='unique')continue;\n  out.push({ json: {\n    dedup_key:str(r.dedup_key),\n    source_type:str(r.source_type),\n    platform:str(r.platform),\n    source_url:str(r.source_url),\n    post_url:str(r.post_url),\n    profile_url:str(r.profile_url),\n    author_handle:str(r.author_handle),\n    text_hash:str(r.text_hash),\n    first_seen_at:now,\n    last_seen_at:now,\n    last_route:str(r.predicted_route),\n    last_processing_status:'raw_collected',\n    last_entity_type:str(r.predicted_entity_type),\n    agent_request_id:str($('Set Connector Config').first().json.agent_request_id),\n    note:'telegram_public_channel_preview_connector_foundation_stage_3_4'\n  }});\n}\nreturn out;"
      }
    },
    {
      "id": "bb110000-0000-0000-0000-0000000000a2",
      "name": "Append market_record_registry",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "position": [
        1780,
        200
      ],
      "parameters": {
        "authentication": "serviceAccount",
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $env.MS_SPREADSHEET_ID || \"PASTE_SPREADSHEET_ID\" }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "market_record_registry",
          "mode": "name"
        },
        "options": {},
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": []
        }
      },
      "credentials": {
        "googleApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "id": "bb110000-0000-0000-0000-0000000000b3",
      "name": "Build agent_requests Row",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1560,
        440
      ],
      "parameters": {
        "jsCode": "// One agent_requests row (exactly 21 columns). status=completed. No external calls.\n// v0.4.1 (DEC-133): in LIVE mode the request title/details/notes use the ACTUAL live allowlist + transport\n// (was logging the fixture allowlist). Relevance counters now subtract post-level false positives.\nfunction str(v){return v==null?'':String(v).trim();}\nfunction cut(s,n){s=str(s);return s.length>n?s.slice(0,n):s;}\nconst cfg=$('Set Connector Config').first().json;\n// agent-called mode: the canonical agent_requests row was already written by WF18 \u2014 never a second (legacy-shaped) one.\nif(cfg.agent_called===true){return [{json:{ms_agent_mode:true}}];} // AGENT-SUMMARY-001: marker keeps the ledger chain alive; the IF below bypasses the legacy append\nconst recs=$('Deduplicate Posts').all().map(i=>i.json);\nconst posts_received=recs.length;\nconst invalid_items=recs.filter(r=>str(r.dedup_status)==='invalid').length;\nconst structurally_valid_items=posts_received-invalid_items;\nconst hard_skipped_items=recs.filter(r=>str(r.dedup_status)==='hard_skipped').length;\nconst business_relevant_items=structurally_valid_items-hard_skipped_items;\nconst over_pipeline_limit=recs.filter(r=>str(r.dedup_status)==='over_pipeline_limit').length;\nconst unique=recs.filter(r=>str(r.dedup_status)==='unique').length;\nconst duplicate=recs.filter(r=>str(r.dedup_status).indexOf('duplicate')===0).length;\nconst false_positives=recs.filter(r=>str(r.relevance_class)==='irrelevant_false_positive').length;\nconst hard_negatives=recs.filter(r=>str(r.relevance_class)==='hard_negative').length;\nconst adjacent_skips=recs.filter(r=>str(r.relevance_class)==='adjacent_real_estate_signal').length;\nconst fixtureMode=(cfg.fixture_mode===true||str(cfg.fixture_mode).toLowerCase()==='true');\n// LIVE FIX: pick the allowlist that actually drove this run.\nconst fixAllow=Array.isArray(cfg.channel_allowlist)?cfg.channel_allowlist:[];\nconst liveAllow=Array.isArray(cfg.live_channel_allowlist)?cfg.live_channel_allowlist.map(c=>str(c)).filter(Boolean):[];\nconst allowArr=fixtureMode?fixAllow:liveAllow;\nconst channels=allowArr.join(', ');\nconst transport=str(cfg.live_transport)||'firecrawl';\nfunction nextAction(){\n  if(unique>0&&business_relevant_items>0){\n    var s='Inspect raw_market_records, then run Workflow 08 manually with agent_request_id_filter. No auto-handoff, no outreach (DEC-098).';\n    if(false_positives>0)s+=' '+false_positives+' irrelevant_live_false_positive post(s) skipped (channel context is not post-level evidence).';\n    if(false_positives>=business_relevant_items)s+=' Skipped false positives are high \u2014 consider tightening the list of tracked channels / relevance config.';\n    return s;\n  }\n  if(false_positives>0)return 'No unique business-relevant posts ('+false_positives+' false positives skipped): adjust the list of tracked channels / relevance config; do NOT run Workflow 08.';\n  return 'No unique business-relevant posts: review fixtures / tracked channels; do NOT run Workflow 08.';\n}\nlet notes='WF11 foundation (DEC-110): Telegram PUBLIC channel preview connector, fixture-first, no external calls, no MTProto/groups/private chats. No auto-handoff to WF08. Source='+(fixtureMode?'fixture':'live_preview')+'. Contact policy: public contacts verbatim from post text only, evidence URL in raw notes, default manual_review.';\nif(false_positives>0||hard_negatives>0||adjacent_skips>0){ notes+=' Relevance gate (DEC-133/135, POST-LEVEL evidence only): irrelevant_false_positives='+false_positives+', adjacent_real_estate_signals='+adjacent_skips+', hard_negatives='+hard_negatives+' (all skipped, not written to raw/registry by default for the credit_brokerage MVP).'; }\nif(!fixtureMode){ notes+=' transport='+transport+'; source_cost: '+(transport==='firecrawl'?'firecrawl credits \u2014 cost_not_recovered (unknown != free)':'http_get \u2014 no per-call fee ($0)')+'.'; }\nreturn [{ json: {\n  agent_request_id:str(cfg.agent_request_id),\n  created_at:str(cfg.created_at),\n  requested_by:str(cfg.requested_by)||'operator',\n  request_text:'Telegram public channel preview discovery ('+(fixtureMode?'fixture':'live_preview')+' tracked channels): '+channels,\n  request_type:str(cfg.request_type)||'social_source_discovery',\n  source_scope:'social_public',\n  platforms:'telegram',\n  query:'tracked channels: '+channels,\n  region:str(cfg.region)||'\u041c\u043e\u0441\u043a\u0432\u0430/\u041c\u041e',\n  service_focus:str(cfg.service_focus)||'credit_broker',\n  requested_limit:Number(cfg.pipeline_limit)||10,\n  status:'completed',\n  plan_summary:'WF11 Telegram public-channel preview connector ('+(fixtureMode?'fixture mode':'live preview mode, transport='+transport)+') -> raw_market_records + market_record_registry for the Touchpoint Analyzer (Workflow 08). One source at a time (DEC-096).',\n  estimated_source_cost_usd:0,\n  estimated_analysis_cost_usd:0,\n  approval_required:false,\n  approved_by:'',\n  approved_at:'',\n  result_summary:'posts_received='+posts_received+'; structurally_valid_items='+structurally_valid_items+'; invalid_items='+invalid_items+'; business_relevant_items='+business_relevant_items+'; hard_skipped_items='+hard_skipped_items+' (irrelevant_false_positives='+false_positives+', adjacent_real_estate_signals='+adjacent_skips+', hard_negatives='+hard_negatives+'); unique='+unique+'; duplicates='+duplicate+'; over_pipeline_limit='+over_pipeline_limit+'.',\n  next_action:nextAction(),\n  notes:notes\n}}];"
      }
    },
    {
      "id": "bb110000-0000-0000-0000-0000000000a3",
      "name": "Append agent_requests",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "position": [
        1780,
        440
      ],
      "parameters": {
        "authentication": "serviceAccount",
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $env.MS_SPREADSHEET_ID || \"PASTE_SPREADSHEET_ID\" }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "agent_requests",
          "mode": "name"
        },
        "options": {},
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": []
        }
      },
      "credentials": {
        "googleApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "id": "bb110000-0000-0000-0000-0000000000fs",
      "name": "Final Summary Output",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2000,
        440
      ],
      "parameters": {
        "jsCode": "// Final run summary. No external calls.\nfunction str(v){return v==null?'':String(v).trim();}\nconst cfg=$('Set Connector Config').first().json;\nconst recs=$('Deduplicate Posts').all().map(i=>i.json);\nconst posts_received=recs.length;\nconst invalid_items=recs.filter(r=>str(r.dedup_status)==='invalid').length;\nconst structurally_valid_items=posts_received-invalid_items;\nconst hard_skipped_items=recs.filter(r=>str(r.dedup_status)==='hard_skipped').length;\nconst business_relevant_items=structurally_valid_items-hard_skipped_items;\nconst over_pipeline_limit=recs.filter(r=>str(r.dedup_status)==='over_pipeline_limit').length;\nconst unique=recs.filter(r=>str(r.dedup_status)==='unique').length;\nconst duplicate=recs.filter(r=>str(r.dedup_status).indexOf('duplicate')===0).length;\nconst false_positives=recs.filter(r=>str(r.relevance_class)==='irrelevant_false_positive').length;\nconst hard_negatives=recs.filter(r=>str(r.relevance_class)==='hard_negative').length;\nconst adjacent_skips=recs.filter(r=>str(r.relevance_class)==='adjacent_real_estate_signal').length;\nconst writeDupAudit=(cfg.write_duplicate_audit===true||str(cfg.write_duplicate_audit).toLowerCase()==='true');\nconst rawWritten=writeDupAudit?(unique+duplicate):unique;\nconst record_type_counts={};\nfor(const r of recs){\n  if(['invalid','over_pipeline_limit','hard_skipped'].indexOf(str(r.dedup_status))>=0)continue;\n  const t=str(r.record_type_hint)||'unknown';record_type_counts[t]=(record_type_counts[t]||0)+1;\n}\nfunction nextAction(){\n  if(unique>0&&business_relevant_items>0){\n    var s='Inspect raw_market_records, then run Workflow 08 (Touchpoint Analyzer) manually with agent_request_id_filter. No auto-handoff, no outreach.';\n    if(false_positives>0)s+=' ('+false_positives+' irrelevant_live_false_positive post(s) skipped \u2014 channel context is not post-level evidence.)';\n    if(false_positives>=business_relevant_items)s+=' Skipped false positives are high \u2014 consider tightening the list of tracked channels / relevance config.';\n    return s;\n  }\n  if(false_positives>0)return 'No unique business-relevant posts ('+false_positives+' false positives skipped): adjust the list of tracked channels / relevance config; do NOT run Workflow 08.';\n  return 'No unique business-relevant posts: review fixtures / tracked channels; do NOT run Workflow 08.';\n}\nreturn [{ json: {\n  workflow:'11 - Social Source Connector Foundation (Telegram Public Channel Preview)',\n  agent_request_id:str(cfg.agent_request_id),\n  run_id:str(cfg.run_id),\n  items_received:posts_received,\n  items_written:rawWritten,\n  items_relevant:business_relevant_items, // adapter contract aliases (AGENT-SUMMARY-001)\n  fixture_mode:(cfg.fixture_mode===true||str(cfg.fixture_mode).toLowerCase()==='true'),\n  live_mode:(cfg.live_mode===true||str(cfg.live_mode).toLowerCase()==='true'),\n  posts_received:posts_received,\n  structurally_valid_items:structurally_valid_items,\n  invalid_items:invalid_items,\n  business_relevant_items:business_relevant_items,\n  hard_skipped_items:hard_skipped_items,\n  irrelevant_false_positives:false_positives,\n  adjacent_real_estate_skips:adjacent_skips,\n  hard_negative_skips:hard_negatives,\n  over_pipeline_limit:over_pipeline_limit,\n  unique_count:unique,\n  duplicate_count:duplicate,\n  raw_market_records_written:rawWritten,\n  registry_rows_written:unique,\n  record_type_counts:record_type_counts,\n  source_cost_usd:0,\n  next_action:nextAction()\n}}];"
      }
    },
    {
      "parameters": {
        "url": "={{ $json.fetch_url }}",
        "options": {
          "timeout": 15000,
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        }
      },
      "id": "wf11-live-http-placeholder",
      "name": "Fetch t.me Preview Page (HTTP fallback, LIVE gated)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        660,
        820
      ],
      "notes": "LIVE gated transport (DEC-135): unreachable unless the LIVE Preview Approval Gate passes (live_mode=true + exact approval token + non-empty validated tracked-channel list + live_transport='http_get'). Public t.me/s/<channel> preview GET only \u2014 no credentials, no login, no MTProto, no member extraction. Safety is the approval gate + tracked-channel validation + caps, NOT manual node disabling."
    },
    {
      "parameters": {
        "jsCode": "// PARSE LIVE PREVIEW POSTS v0.4.2 (DEC-135). Reachable ONLY after the LIVE Preview Approval Gate passes\n// (live_mode + exact token + validated tracked channels + selected transport) and the selected transport node\n// runs. Accepts EITHER a Firecrawl scrape response (j.data.html / j.data.rawHtml / j.data.markdown) OR a plain\n// HTTP GET text body (j.data / j.body). If no html arrives (e.g. an empty/blocked response), this node THROWS\n// \u2014 guaranteeing no fabricated posts enter the pipeline. Public t.me/s preview only; no MTProto, no sessions,\n// no login, no member extraction.\nfunction str(v){return v==null?'':String(v).trim();}\nconst out=[];\nfor(const it of $input.all()){\n  const j=it.json||{};\n  let html='';\n  if(j.data&&typeof j.data==='object'){ html=str(j.data.html||j.data.rawHtml||j.data.markdown); }\n  else { html=str(j.data||j.body||j.html||j.markdown); }\n  if(!html){\n    throw new Error('WF11 live parse: no HTML received from the selected transport (Firecrawl/HTTP). The transport is gate-protected (DEC-135): it only runs after live_mode + exact approval token + validated tracked channels + selected transport. Check the transport response/credential; default runs use fixture mode ($0).');\n  }\n  const maxPosts=Math.min(Number(j.max_posts)||10,10);\n  // t.me/s preview page: each post is a tgme_widget_message block with data-post=\"<channel>/<id>\".\n  const blocks=html.split('tgme_widget_message_wrap').slice(1);\n  let count=0;\n  for(const b of blocks){\n    if(count>=maxPosts)break;\n    const idm=b.match(/data-post=\"([^\"\\/]+)\\/(\\d+)\"/);\n    const txtm=b.match(/tgme_widget_message_text[^>]*>([\\s\\S]*?)<\\/div>/);\n    const timem=b.match(/datetime=\"([^\"]+)\"/);\n    const titlem=b.match(/tgme_widget_message_owner_name[^>]*><span[^>]*>([\\s\\S]*?)<\\/span>/);\n    const viewm=b.match(/tgme_widget_message_views[^>]*>([^<]+)</);\n    if(!idm||!txtm)continue;\n    const text=txtm[1].replace(/<br\\s*\\/?>/gi,' ').replace(/<[^>]+>/g,'').replace(/&amp;/g,'&').replace(/&quot;/g,'\"').replace(/&#39;/g,\"'\").replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/\\s+/g,' ').trim();\n    if(!text)continue;\n    count++;\n    out.push({ json: { channel:idm[1], channel_title:titlem?titlem[1].replace(/<[^>]+>/g,'').trim():idm[1], post_id:idm[2], post_url:'https://t.me/'+idm[1]+'/'+idm[2], published_at:timem?timem[1]:'', view_count:viewm?str(viewm[1]):'', text:text } });\n  }\n}\nif(!out.length){ throw new Error('WF11 live parse: 0 posts parsed from preview HTML \u2014 check channel allowlist / page structure before retrying.'); }\nreturn out;"
      },
      "id": "wf11-live-parse-placeholder",
      "name": "Parse Live Preview Posts (inert)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        900,
        720
      ]
    },
    {
      "parameters": {
        "jsCode": "// Build ONE live_source_runs row (23 columns, docs/TABLE_SCHEMA.md) \u2014 run observability (DEC-126/132).\n// Logs what ran, which source, counters, external_calls, cost and what to do next. Never logs the approval\n// token value itself \u2014 only whether one was used. Live source cost is unknown for Firecrawl -> cost_not_recovered.\nfunction str(v){return v==null?'':String(v).trim();}\nconst cfg=$('Set Connector Config').first().json;\nconst recs=$('Deduplicate Posts').all().map(i=>i.json);\nconst fixtureMode=(cfg.fixture_mode===true||str(cfg.fixture_mode).toLowerCase()==='true');\nconst invalid=recs.filter(r=>str(r.dedup_status)==='invalid').length;\nconst hard=recs.filter(r=>str(r.dedup_status)==='hard_skipped').length;\nconst unique=recs.filter(r=>str(r.dedup_status)==='unique').length;\nconst dup=recs.filter(r=>str(r.dedup_status).indexOf('duplicate')===0).length;\nconst writeDupAudit=(cfg.write_duplicate_audit===true||str(cfg.write_duplicate_audit).toLowerCase()==='true');\nconst allow=fixtureMode?(Array.isArray(cfg.channel_allowlist)?cfg.channel_allowlist:[]):(Array.isArray(cfg.live_channel_allowlist)?cfg.live_channel_allowlist:[]);\nconst cap=Math.min(Number(cfg.live_max_channels)||2,2);\nconst transport=str(cfg.live_transport)||'firecrawl';\nconst liveChannels=fixtureMode?0:Math.min(allow.length,cap);\nconst costNote=fixtureMode?'fixture: $0':(transport==='firecrawl'?'firecrawl credits \u2014 cost_not_recovered (record Firecrawl usage after the run; unknown != free)':'http_get public preview \u2014 no per-call fee ($0), bandwidth only');\nreturn [{ json: {\n  run_id:str(cfg.run_id),\n  created_at:str(cfg.created_at),\n  workflow:'WF11 - Social Source Connector (Telegram Preview)',\n  source_family:'social_channel',\n  platform:'telegram',\n  mode:fixtureMode?'fixture':'live',\n  approval_token_used:fixtureMode?'no':(str(cfg.live_approval_token)?'yes':'no'),\n  source_allowlist:allow.join(', '),\n  max_items:Number(fixtureMode?cfg.pipeline_limit:cfg.live_max_posts_per_channel)||10,\n  items_received:recs.length,\n  items_relevant:recs.length-invalid-hard,\n  items_written_raw:writeDupAudit?(unique+dup):unique,\n  items_written:writeDupAudit?(unique+dup):unique,\n  items_unique:unique,\n  items_duplicate:dup,\n  hard_skipped:hard,\n  external_calls:fixtureMode?0:liveChannels,\n  estimated_source_cost_usd:0,\n  llm_calls:0,\n  estimated_llm_cost_usd:0,\n  status:'completed',\n  error_summary:'',\n  operator_next_action:(unique>0?'Inspect raw_market_records, then run WF08 manually with agent_request_id_filter ('+str(cfg.agent_request_id)+'). Skipped false positives (no post-level finance evidence) are not handed off.':'No unique posts; review the list of tracked channels / relevance config; do NOT run WF08.'),\n  notes:'stage_3_source_foundation_telegram_public_preview; transport='+(fixtureMode?'fixture':transport)+'; source_cost: '+costNote+'; t.me/s public preview pages only; no MTProto/groups/private chats; no member extraction; no outreach.'\n}}];"
      },
      "id": "wf11-runlog-build",
      "name": "Build live_source_runs Row",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2220,
        640
      ]
    },
    {
      "parameters": {
        "authentication": "serviceAccount",
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $env.MS_SPREADSHEET_ID || \"PASTE_SPREADSHEET_ID\" }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "live_source_runs",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": []
        },
        "options": {}
      },
      "credentials": {
        "googleApi": {
          "name": "<your credential>"
        }
      },
      "id": "wf11-app-runlog",
      "name": "Append live_source_runs",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "position": [
        2440,
        640
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "cond-transport-firecrawl",
              "leftValue": "={{ $json.live_transport }}",
              "rightValue": "firecrawl",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "wf11-live-route-transport",
      "name": "Route Live Transport",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        440,
        700
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.firecrawl.dev/v2/scrape",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"url\": \"{{ $json.fetch_url }}\",\n  \"formats\": [\"html\"],\n  \"onlyMainContent\": false\n}",
        "options": {
          "timeout": 30000
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "id": "wf11-live-firecrawl-placeholder",
      "name": "Firecrawl Scrape t.me Preview (LIVE gated transport)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        660,
        620
      ],
      "notes": "LIVE gated transport (DEC-135): unreachable unless the LIVE Preview Approval Gate passes (live_mode=true + exact approval token + non-empty validated tracked-channel list + live_transport='firecrawl') and a Firecrawl credential is bound. POST public t.me/s/<channel> preview to Firecrawl scrape (html). No login, no MTProto, public preview only. Safety is the approval gate + tracked-channel validation + caps, NOT manual node disabling."
    },
    {
      "parameters": {
        "inputSource": "workflowInputs",
        "workflowInputs": {
          "values": [
            {
              "name": "agent_request_id",
              "type": "string"
            },
            {
              "name": "source_run_id",
              "type": "string"
            },
            {
              "name": "workflow_run_id",
              "type": "string"
            },
            {
              "name": "data_mode",
              "type": "string"
            },
            {
              "name": "channels",
              "type": "string"
            },
            {
              "name": "max_posts",
              "type": "string"
            },
            {
              "name": "approval_token",
              "type": "string"
            },
            {
              "name": "transport",
              "type": "string"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.1,
      "position": [
        -1160,
        480
      ],
      "id": "wf11-subtrig",
      "name": "When Called by Agent"
    },
    {
      "id": "b1000000-0000-0000-0000-000000anyrow",
      "name": "Any Rows To Write?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1210,
        160
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "cond-anyrow",
              "leftValue": "={{ $json.ms_no_rows === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "false",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "b1000000-0000-0000-0000-000000legacy",
      "name": "Write Legacy Request Row?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1650,
        300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "cond-legacy",
              "leftValue": "={{ $json.ms_agent_mode === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "false",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    }
  ],
  "connections": {
    "Manual Start": {
      "main": [
        [
          {
            "node": "Set Connector Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Connector Config": {
      "main": [
        [
          {
            "node": "IF fixture_mode?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF fixture_mode?": {
      "main": [
        [
          {
            "node": "Build Fixture Telegram Channel Posts",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "LIVE Preview Approval Gate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Fixture Telegram Channel Posts": {
      "main": [
        [
          {
            "node": "Normalize Telegram Posts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Telegram Posts": {
      "main": [
        [
          {
            "node": "Read market_record_registry",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read market_record_registry": {
      "main": [
        [
          {
            "node": "Deduplicate Posts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Deduplicate Posts": {
      "main": [
        [
          {
            "node": "Build raw_market_records Rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build raw_market_records Rows": {
      "main": [
        [
          {
            "node": "Any Rows To Write?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append raw_market_records": {
      "main": [
        [
          {
            "node": "Build market_record_registry Rows",
            "type": "main",
            "index": 0
          },
          {
            "node": "Build agent_requests Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build market_record_registry Rows": {
      "main": [
        [
          {
            "node": "Append market_record_registry",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build agent_requests Row": {
      "main": [
        [
          {
            "node": "Write Legacy Request Row?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append agent_requests": {
      "main": [
        [
          {
            "node": "Build live_source_runs Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LIVE Preview Approval Gate": {
      "main": [
        [
          {
            "node": "Route Live Transport",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch t.me Preview Page (HTTP fallback, LIVE gated)": {
      "main": [
        [
          {
            "node": "Parse Live Preview Posts (inert)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Live Preview Posts (inert)": {
      "main": [
        [
          {
            "node": "Normalize Telegram Posts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build live_source_runs Row": {
      "main": [
        [
          {
            "node": "Append live_source_runs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append live_source_runs": {
      "main": [
        [
          {
            "node": "Final Summary Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route Live Transport": {
      "main": [
        [
          {
            "node": "Firecrawl Scrape t.me Preview (LIVE gated transport)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Fetch t.me Preview Page (HTTP fallback, LIVE gated)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Firecrawl Scrape t.me Preview (LIVE gated transport)": {
      "main": [
        [
          {
            "node": "Parse Live Preview Posts (inert)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Called by Agent": {
      "main": [
        [
          {
            "node": "Set Connector Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Any Rows To Write?": {
      "main": [
        [
          {
            "node": "Append raw_market_records",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build agent_requests Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write Legacy Request Row?": {
      "main": [
        [
          {
            "node": "Append agent_requests",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build live_source_runs Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "bb110000-social-source-connector-v042-quality-gate-adjacent-gated-transport-20260617"
}