{
  "name": "02 - Claude API Single Record Analysis v2 RESILIENT ROUTER TEST DYNAMIC SHEET",
  "nodes": [
    {
      "id": "rr000000-0000-0000-0000-000000000001",
      "name": "\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 Resilient Router",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -240,
        -320
      ],
      "parameters": {
        "content": "RESILIENT ROUTER TEST \u2014 \u0422\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u0442 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0443 C+E: JSON Repair Formatter + Multi-Tab Router.\n\n\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430:\n1. Primary Claude Analysis (baseline raw JSON)\n2. Parse Primary JSON \u2192 \u0435\u0441\u043b\u0438 \u043d\u0435 \u043f\u0430\u0440\u0441\u0438\u0442\u0441\u044f \u2192 Repair Formatter (\u0432\u0442\u043e\u0440\u043e\u0439 \u0432\u044b\u0437\u043e\u0432 Claude)\n3. Router \u2192 6 \u0432\u043a\u043b\u0430\u0434\u043e\u043a Google Sheets: results / review_queue / monitor_queue / content_queue / skipped_log / technical_errors\n\n\u0422\u0440\u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0432\u044b\u0432\u043e\u0434\u0430:\n\u2022 parsed_success \u2014 \u043f\u0435\u0440\u0432\u0438\u0447\u043d\u044b\u0439 \u0438\u043b\u0438 \u043e\u0442\u0440\u0435\u043c\u043e\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 JSON \u0432\u0430\u043b\u0438\u0434\u0435\u043d\n\u2022 business_skip \u2014 Claude \u0440\u0435\u0448\u0438\u043b, \u0447\u0442\u043e \u0437\u0430\u043f\u0438\u0441\u044c \u043d\u0435\u0440\u0435\u043b\u0435\u0432\u0430\u043d\u0442\u043d\u0430\n\u2022 technical_error \u2014 JSON \u043d\u0435 \u043f\u0430\u0440\u0441\u0438\u0442\u0441\u044f \u043d\u0438 primary, \u043d\u0438 repair\n\n\u0422\u0435\u0441\u0442\u044b A-E:\n\u2022 A \u2014 \u0433\u043e\u0440\u044f\u0447\u0438\u0439 \u043b\u0438\u0434 \u2192 results\n\u2022 B \u2014 \u0441\u043b\u0430\u0431\u044b\u0439 \u043b\u0438\u0434 \u2192 review_queue\n\u2022 C \u2014 \u043a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0442 \u2192 monitor_queue\n\u2022 D \u2014 \u043f\u0440\u0438\u043d\u0443\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 Markdown \u2192 repair \u2192 results\n\u2022 E \u2014 \u043d\u0435\u0447\u0438\u0442\u0430\u0435\u043c\u044b\u0439 \u043e\u0442\u0432\u0435\u0442 \u2192 technical_errors\n\n\u041c\u0435\u043d\u044f\u0439 test_id \u0432 Set Test Selector (A/B/C/D/E). active=false \u2014 \u043d\u0435 \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c.",
        "height": 280,
        "width": 1100,
        "color": 5
      }
    },
    {
      "id": "rr000000-0000-0000-0000-000000000021",
      "name": "\u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438 \u043f\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044e",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        2900,
        -320
      ],
      "parameters": {
        "content": "\u041f\u041e\u0420\u042f\u0414\u041e\u041a \u0422\u0415\u0421\u0422\u0418\u0420\u041e\u0412\u0410\u041d\u0418\u042f:\n1. Set test_id = A \u2192 Run \u2192 \u043f\u0440\u043e\u0432\u0435\u0440\u044c route=results, parse_method=primary_json\n2. Set test_id = B \u2192 Run \u2192 \u043f\u0440\u043e\u0432\u0435\u0440\u044c route=review_queue\n3. Set test_id = C \u2192 Run \u2192 \u043f\u0440\u043e\u0432\u0435\u0440\u044c route=monitor_queue\n4. Set test_id = D \u2192 Run \u2192 \u043f\u0440\u043e\u0432\u0435\u0440\u044c route=results, repair_used=true, parse_method=repaired_json\n5. Set test_id = E \u2192 Run \u2192 \u043f\u0440\u043e\u0432\u0435\u0440\u044c route=technical_errors, repair_status=failed\n\n\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0439 \u0432 \u043a\u0430\u0436\u0434\u043e\u043c \u0442\u0435\u0441\u0442\u0435:\n\u2022 test_pass_basic = true\n\u2022 \u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430\u044f \u0432\u043a\u043b\u0430\u0434\u043a\u0430 Google Sheets \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0430\n\u2022 processing_status, parse_method, route\n\n\u0417\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0439 \u0431\u0430\u043b\u0430\u043d\u0441 API \u0414\u041e \u0438 \u041f\u041e\u0421\u041b\u0415.\n\u0422\u0435\u0441\u0442\u044b D \u0438 E \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0442 Repair API \u2014 \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0439 \u0434\u043e\u043f. \u0441\u0442\u043e\u0438\u043c\u043e\u0441\u0442\u044c.\n\u041d\u0415 \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0443\u0439 workflow \u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u0440\u0443\u0447\u043d\u043e\u0439 \u0437\u0430\u043f\u0443\u0441\u043a.",
        "height": 260,
        "width": 520,
        "color": 3
      }
    },
    {
      "id": "rr000000-0000-0000-0000-000000000002",
      "name": "Manual Start",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        100,
        200
      ],
      "parameters": {}
    },
    {
      "id": "rr000000-0000-0000-0000-000000000003",
      "name": "Set Test Selector",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        380,
        200
      ],
      "parameters": {
        "mode": "manual",
        "assignments": {
          "assignments": [
            {
              "id": "rr000000-0000-0000-0000-000000000031",
              "name": "test_id",
              "value": "A",
              "type": "string"
            }
          ]
        }
      }
    },
    {
      "id": "rr000000-0000-0000-0000-000000000004",
      "name": "Select Test Record",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        200
      ],
      "parameters": {
        "jsCode": "const testId = String($json.test_id || 'A').toUpperCase();\nconst records = {\n  A: {\n    test_id: 'A', expected_route: 'results',\n    expected_entity_type: 'lead_signal', expected_recommended_action: 'contact',\n    expected_quality_range: '80-100', mock_mode: 'none',\n    source_type: 'social', platform: 'telegram',\n    source_url: 'https://t.me/test_record_A', profile_url: 'https://t.me/user_test_A',\n    published_at: '2026-06-05', parsed_at: '2026-06-06',\n    text_context: '\u0421\u0440\u043e\u0447\u043d\u043e \u043d\u0443\u0436\u0435\u043d \u0437\u0430\u0439\u043c \u043f\u043e\u0434 \u0437\u0430\u043b\u043e\u0433 \u041f\u0422\u0421! Toyota Camry 2019, \u043e\u0442\u043b\u0438\u0447\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435. \u041d\u0443\u0436\u043d\u043e 250 000 \u0440\u0443\u0431\u043b\u0435\u0439, \u0431\u0430\u043d\u043a\u0438 \u0432\u0435\u0437\u0434\u0435 \u043e\u0442\u043a\u0430\u0437\u0430\u043b\u0438 \u0438\u0437-\u0437\u0430 \u0438\u0441\u043f\u043e\u0440\u0447\u0435\u043d\u043d\u043e\u0439 \u043a\u0440\u0435\u0434\u0438\u0442\u043d\u043e\u0439 \u0438\u0441\u0442\u043e\u0440\u0438\u0438. \u0414\u0435\u043d\u044c\u0433\u0438 \u043d\u0443\u0436\u043d\u044b \u0441\u0435\u0433\u043e\u0434\u043d\u044f \u2014 \u0437\u0430\u0432\u0442\u0440\u0430 \u0443\u0436\u0435 \u043f\u043e\u0437\u0434\u043d\u043e. \u041c\u043e\u0441\u043a\u0432\u0430, \u0440\u0430\u0439\u043e\u043d \u0412\u044b\u0445\u0438\u043d\u043e. \u041d\u0430\u043f\u0438\u0448\u0438\u0442\u0435 \u0432 \u043b\u0438\u0447\u043a\u0443.'\n  },\n  B: {\n    test_id: 'B', expected_route: 'review_queue',\n    expected_entity_type: 'lead_signal', expected_recommended_action: 'investigate',\n    expected_quality_range: '35-69', mock_mode: 'none',\n    source_type: 'social', platform: 'vk',\n    source_url: 'https://vk.com/wall-test_B', profile_url: 'https://vk.com/user_test_B',\n    published_at: '2026-06-01', parsed_at: '2026-06-06',\n    text_context: '\u0418\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u044e\u0441\u044c \u0437\u0430\u0439\u043c\u0430\u043c\u0438 \u043f\u043e\u0434 \u0437\u0430\u043b\u043e\u0433 \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0438\u043b\u044f. \u0425\u043e\u0447\u0443 \u043f\u043e\u043d\u044f\u0442\u044c \u0443\u0441\u043b\u043e\u0432\u0438\u044f, \u0441\u0440\u0430\u0432\u043d\u0438\u0442\u044c \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u044b. \u0415\u0441\u0442\u044c \u043b\u0438 \u0443 \u043a\u043e\u0433\u043e-\u0442\u043e \u043e\u043f\u044b\u0442? \u041f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u043d\u0443\u0436\u043d\u043e \u043e\u043a\u043e\u043b\u043e 100 \u0442\u044b\u0441\u044f\u0447, \u043d\u0435 \u0441\u0440\u043e\u0447\u043d\u043e, \u043f\u0440\u043e\u0441\u0442\u043e \u0438\u0437\u0443\u0447\u0430\u044e.'\n  },\n  C: {\n    test_id: 'C', expected_route: 'monitor_queue',\n    expected_entity_type: 'competitor', expected_recommended_action: 'monitor',\n    expected_quality_range: '65-90', mock_mode: 'none',\n    source_type: 'scraped_web', platform: 'website',\n    source_url: 'https://example-competitor-mfo.ru/pts-zalog', profile_url: '',\n    published_at: '2026-06-03', parsed_at: '2026-06-06',\n    text_context: '\u0417\u0430\u0439\u043c\u044b \u043f\u043e\u0434 \u0437\u0430\u043b\u043e\u0433 \u041f\u0422\u0421 \u0438 \u0430\u0432\u0442\u043e\u043c\u043e\u0431\u0438\u043b\u044f. \u0421\u0442\u0430\u0432\u043a\u0430 \u043e\u0442 2,5% \u0432 \u043c\u0435\u0441\u044f\u0446. \u041e\u0434\u043e\u0431\u0440\u0435\u043d\u0438\u0435 \u0437\u0430 1 \u0447\u0430\u0441. \u0420\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u0441 \u043b\u044e\u0431\u043e\u0439 \u043a\u0440\u0435\u0434\u0438\u0442\u043d\u043e\u0439 \u0438\u0441\u0442\u043e\u0440\u0438\u0435\u0439. \u041c\u043e\u0441\u043a\u0432\u0430 \u0438 \u041c\u041e. \u0421\u0443\u043c\u043c\u0430 \u043e\u0442 50 000 \u0434\u043e 1 000 000 \u0440\u0443\u0431. \u0411\u0435\u0437 \u0441\u043a\u0440\u044b\u0442\u044b\u0445 \u043a\u043e\u043c\u0438\u0441\u0441\u0438\u0439. \u0422\u0435\u043b\u0435\u0444\u043e\u043d: +7(499)555-00-11.'\n  },\n  D: {\n    test_id: 'D', expected_route: 'results',\n    expected_entity_type: 'lead_signal', expected_recommended_action: 'contact',\n    expected_quality_range: '75-95', mock_mode: 'mock_markdown',\n    raw_response_preview: '**\u0410\u043d\u0430\u043b\u0438\u0437 \u0437\u0430\u043f\u0438\u0441\u0438:** \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0438\u0449\u0435\u0442 \u0437\u0430\u0439\u043c \u043f\u043e\u0434 \u0437\u0430\u043b\u043e\u0433 \u041f\u0422\u0421 \u0432 \u041c\u043e\u0441\u043a\u0432\u0435, \u0441\u0443\u043c\u043c\u0430 200000 \u0440\u0443\u0431., \u0431\u0430\u043d\u043a\u0438 \u043e\u0442\u043a\u0430\u0437\u0430\u043b\u0438, \u0434\u0435\u043d\u044c\u0433\u0438 \u043d\u0443\u0436\u043d\u044b \u0441\u0435\u0433\u043e\u0434\u043d\u044f. \u042d\u0442\u043e lead_signal, recommended_action contact, lead_signal_score 88, quality_score 82, entity_type lead_signal, region \u041c\u043e\u0441\u043a\u0432\u0430, platform telegram, source_type social.',\n    source_type: 'social', platform: 'telegram',\n    source_url: 'https://t.me/test_record_D', profile_url: 'https://t.me/user_test_D',\n    published_at: '2026-06-05', parsed_at: '2026-06-06',\n    text_context: '(mock \u2014 primary API bypassed, simulated Markdown response)'\n  },\n  E: {\n    test_id: 'E', expected_route: 'technical_errors',\n    expected_entity_type: 'any', expected_recommended_action: 'any',\n    expected_quality_range: 'any', mock_mode: 'mock_unrepairable',\n    raw_response_preview: '%%%% unreadable broken response no useful market facts %%%%',\n    source_type: 'social', platform: 'telegram',\n    source_url: 'https://t.me/test_record_E', profile_url: '',\n    published_at: '2026-06-05', parsed_at: '2026-06-06',\n    text_context: '(mock \u2014 primary API bypassed, simulated unrepairable response)'\n  }\n};\nconst rec = records[testId] || records['A'];\nreturn [{ json: rec }];"
      }
    },
    {
      "id": "rr000000-0000-0000-0000-000000000005",
      "name": "Build Primary Claude Request",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        940,
        200
      ],
      "parameters": {
        "jsCode": "const record = $json;\nconst mockMode = record.mock_mode || 'none';\n\nif (mockMode !== 'none') {\n  return [{ json: {\n    skip_primary_api: true,\n    mock_mode: mockMode,\n    raw_response_preview: record.raw_response_preview || '',\n    original_record: record\n  }}];\n}\n\nconst systemPrompt = `You are Marketing Scout Agent v2 -- a market intelligence analyst for a secured lending business in Moscow and Moscow Oblast, Russia.\n\nFor every record ask: What does this mean for the operator's business and what should they do? Reason like a business owner.\n\nANALYSIS PRIORITY ORDER:\n1. LEAD SIGNAL first -- potential client needing secured loan? Moscow/MO? PTS/auto/real estate collateral? Urgency? Contactable?\n2. COMPETITOR second -- active secured lending business Moscow/MO? Threat level?\n3. CONTENT IDEA third -- client fear/objection/knowledge gap for secured lending?\n4. IRRELEVANT if none apply.\n\nIDEAL CLIENT: Car owner (PTS clean title), Moscow/MO, needs cash urgently, bank-rejected, 50k-500k RUB.\n\nHigh-urgency signals (raise lead_signal_score): \u0441\u0440\u043e\u0447\u043d\u043e, \u0441\u0435\u0433\u043e\u0434\u043d\u044f, \u0431\u0430\u043d\u043a\u0438 \u043e\u0442\u043a\u0430\u0437\u0430\u043b\u0438, \u043d\u0435 \u0434\u0430\u044e\u0442 \u043a\u0440\u0435\u0434\u0438\u0442, \u0438\u0441\u043f\u043e\u0440\u0447\u0435\u043d\u0430 \u043a\u0440\u0435\u0434\u0438\u0442\u043d\u0430\u044f \u0438\u0441\u0442\u043e\u0440\u0438\u044f + specific amount + collateral type.\n\nREGION RULES:\n- Moscow/MO explicit: lead_signal_score 60-100\n- Region ambiguous: eligible up to 55\n- Another city/region: lead_signal_score capped at 40\nCompetitors: Moscow/MO or national coverage -> score normally; other region only -> cap competitor_strength at 50.\n\nlead_signal_score calibration:\n- 85-100: fit + urgency + readiness + Moscow/MO confirmed\n- 70-84: strong fit+urgency, region confirmed, readiness partial\n- 55-69: clear product fit, one signal confirmed, region present\n- 35-54: intent apparent, fit unclear or region outside MO\n- 1-34: no real lead signal\n\nrecommended_action=contact requires lead_signal_score>=70.\n\ncompetitor_strength calibration:\n- 85-100: fresh (<=30d), Moscow/MO, stated rate, same-day, bad-credit accepted, contactable\n- 65-84: active professional, Moscow/MO confirmed, rate absent or one signal missing\n- 45-64: present but older content or uncertain coverage\n- 25-44: weak - stale or different region\n- 1: not a competitor\n\nSKIP RULES -- return status=skipped, quality_score=1, all scores=1 when:\n- Fewer than 40 meaningful chars\n- Pure navigation boilerplate\n- No connection to financial services\n- published_at >180 days before parsed_at with no fresh signals\n\nREASON FIELD (3 sentences required):\n1. WHAT: what is this record, key evidence from text\n2. WHY: why scores are what they are, cite specific signals\n3. NEXT: what operator should do and why\n\nOUTPUT FORMAT -- CRITICAL:\nRespond with ONLY a valid JSON object.\nNo markdown, no code fences, no preamble.\nFirst character must be {. Last must be }.\nAll 25 fields required. Empty string for unknown strings. 1 for unknown integers.\n\nREQUIRED JSON SCHEMA:\n{\n  \"created_at\": \"<parsed_at value ISO 8601>\",\n  \"source_type\": \"<from input>\",\n  \"platform\": \"<from input>\",\n  \"source_url\": \"<from input>\",\n  \"parsed_at\": \"<from input>\",\n  \"published_at\": \"<from input or empty>\",\n  \"freshness_status\": \"<fresh|recent|old|unknown>\",\n  \"entity_type\": \"<competitor|lead_signal|market_signal|content_idea|irrelevant>\",\n  \"company_name\": \"<explicitly in text only or empty>\",\n  \"profile_name\": \"<explicitly in text only or empty>\",\n  \"profile_url\": \"<from input or empty>\",\n  \"region\": \"<explicitly mentioned or empty>\",\n  \"service_type\": \"<secured_auto_loan|secured_real_estate_loan|pts_loan|refinancing|mortgage_adjacent|generic_lending|unknown>\",\n  \"offer_text\": \"<1 sentence: what offered/sought or content angle title>\",\n  \"terms\": \"<explicit rate/conditions only or empty>\",\n  \"contact_public\": \"<phone/email/Telegram from text only or empty>\",\n  \"text_context\": \"<cleaned summary max 300 chars>\",\n  \"detected_need\": \"<lead_signal only: need+amount+urgency+bank rejection+region or empty>\",\n  \"competitor_strength\": <integer 1-100; 1 if not competitor>,\n  \"lead_signal_score\": <integer 1-100>,\n  \"content_idea_score\": <integer 1-100>,\n  \"quality_score\": <integer 1-100>,\n  \"reason\": \"<3 sentences: what+evidence; why scores; next action>\",\n  \"recommended_action\": \"<monitor|contact|create_content|ignore|investigate>\",\n  \"status\": \"<analyzed|skipped>\"\n}`;\n\nreturn [{ json: {\n  skip_primary_api: false,\n  model: 'claude-sonnet-4-6',\n  max_tokens: 1400,\n  temperature: 0.2,\n  system: systemPrompt,\n  messages: [{\n    role: 'user',\n    content: JSON.stringify({\n      source_type: record.source_type || '',\n      platform: record.platform || '',\n      source_url: record.source_url || '',\n      profile_url: record.profile_url || '',\n      parsed_at: record.parsed_at || '',\n      published_at: record.published_at || '',\n      text_context: record.text_context || ''\n    })\n  }]\n}}];"
      }
    },
    {
      "id": "rr000000-0000-0000-0000-000000000006",
      "name": "IF Skip Primary API?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1220,
        200
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "rr000000-0000-0000-0000-000000000061",
              "leftValue": "={{ $json.skip_primary_api }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true"
              }
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "rr000000-0000-0000-0000-000000000007",
      "name": "Claude Primary API Request",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1500,
        60
      ],
      "onError": "continueRegularOutput",
      "parameters": {
        "method": "POST",
        "url": "https://aiprimetech.io/v1/messages",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ model: $json.model, max_tokens: $json.max_tokens, temperature: $json.temperature, system: $json.system, messages: $json.messages }) }}",
        "options": {}
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "rr000000-0000-0000-0000-000000000008",
      "name": "Parse Primary JSON",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1780,
        60
      ],
      "parameters": {
        "jsCode": "const response = $json;\nconst testRecord = $('Select Test Record').first().json;\n\n// Handle HTTP error responses\nif (response.error || response.status >= 400) {\n  return [{ json: {\n    parse_ok: false,\n    parse_method: 'primary_json',\n    repair_used: false,\n    processing_status: 'technical_error_candidate',\n    parse_error: 'HTTP error: ' + JSON.stringify(response.error || response.status),\n    raw_response_preview: JSON.stringify(response).substring(0, 800),\n    original_record: testRecord\n  }}];\n}\n\n// Find text item\nlet textItem;\ntry {\n  if (!response.content || !Array.isArray(response.content)) throw new Error('No content array');\n  textItem = response.content.find(c => c.type === 'text');\n} catch(e) {\n  return [{ json: {\n    parse_ok: false, parse_method: 'primary_json', repair_used: false,\n    processing_status: 'technical_error_candidate',\n    parse_error: 'No content array: ' + e.message,\n    raw_response_preview: JSON.stringify(response).substring(0, 800),\n    original_record: testRecord\n  }}];\n}\n\nif (!textItem || !textItem.text) {\n  return [{ json: {\n    parse_ok: false, parse_method: 'primary_json', repair_used: false,\n    processing_status: 'technical_error_candidate',\n    parse_error: 'No text item in content array',\n    raw_response_preview: JSON.stringify(response.content || response).substring(0, 800),\n    original_record: testRecord\n  }}];\n}\n\nconst rawPreview = textItem.text.substring(0, 1200);\n\n// Parse with defensive cleanup\nlet parsed;\ntry {\n  let txt = textItem.text.trim();\n  txt = txt.replace(/^```json\\s*/i, '').replace(/\\s*```$/,'').trim();\n  txt = txt.replace(/^```\\s*/,'').replace(/\\s*```$/,'').trim();\n  const bStart = txt.indexOf('{');\n  const bEnd = txt.lastIndexOf('}');\n  if (bStart !== -1 && bEnd !== -1) txt = txt.substring(bStart, bEnd + 1);\n  txt = txt.replace(/[\\u2018\\u2019]/g, \"'\").replace(/[\\u201C\\u201D\\u00AB\\u00BB]/g, '\"');\n  parsed = JSON.parse(txt);\n} catch(e) {\n  return [{ json: {\n    parse_ok: false, parse_method: 'primary_json', repair_used: false,\n    processing_status: 'technical_error_candidate',\n    parse_error: 'JSON.parse failed: ' + e.message,\n    raw_response_preview: rawPreview,\n    original_record: testRecord\n  }}];\n}\n\nreturn [{ json: {\n  parse_ok: true,\n  parse_method: 'primary_json',\n  repair_used: false,\n  repair_status: '',\n  parse_error: '',\n  raw_response_preview: rawPreview,\n  ...parsed\n}}];"
      }
    },
    {
      "id": "rr000000-0000-0000-0000-000000000009",
      "name": "IF Primary Parse OK?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        2060,
        60
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "rr000000-0000-0000-0000-000000000091",
              "leftValue": "={{ $json.parse_ok }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true"
              }
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "rr000000-0000-0000-0000-000000000010",
      "name": "Build Repair Request",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1500,
        380
      ],
      "parameters": {
        "jsCode": "const input = $json;\nconst testRecord = $('Select Test Record').first().json;\n\nconst rawPreview = input.raw_response_preview || input.original_record?.raw_response_preview || 'empty';\nconst parseErr = input.parse_error || 'Primary API bypassed (mock mode)';\n\nconst repairSystem = `You are a JSON repair formatter. Convert the provided raw model response into one strict JSON object matching the schema below. Rules:\n- Do not add new facts. Do not invent contacts, regions, scores, or terms.\n- Extract only what is explicitly stated in the raw response.\n- If a value is unknown use empty string for text fields and 1 for score fields.\n- If the raw response contains no useful market facts at all: return status=skipped, entity_type=irrelevant, recommended_action=ignore, all scores=1.\n- Return JSON only. No markdown. No explanation. First char must be {, last must be }.\nSchema fields required: created_at, source_type, platform, source_url, parsed_at, published_at, freshness_status, entity_type, company_name, profile_name, profile_url, region, service_type, offer_text, terms, contact_public, text_context, detected_need, competitor_strength, lead_signal_score, content_idea_score, quality_score, reason, recommended_action, status.\nEnum values: entity_type=[competitor,lead_signal,market_signal,content_idea,irrelevant]; recommended_action=[monitor,contact,create_content,ignore,investigate]; status=[analyzed,skipped]; freshness_status=[fresh,recent,old,unknown].`;\n\nconst userMsg = JSON.stringify({\n  original_record: {\n    source_type: testRecord.source_type || '',\n    platform: testRecord.platform || '',\n    source_url: testRecord.source_url || '',\n    published_at: testRecord.published_at || '',\n    parsed_at: testRecord.parsed_at || ''\n  },\n  raw_primary_response: rawPreview,\n  parse_error: parseErr,\n  instruction: 'Convert raw_primary_response into the required JSON schema. Return JSON only.'\n});\n\nreturn [{ json: {\n  model: 'claude-sonnet-4-6',\n  max_tokens: 900,\n  temperature: 0.0,\n  system: repairSystem,\n  messages: [{ role: 'user', content: userMsg }]\n}}];"
      }
    },
    {
      "id": "rr000000-0000-0000-0000-000000000011",
      "name": "Claude Repair API Request",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1780,
        380
      ],
      "onError": "continueRegularOutput",
      "parameters": {
        "method": "POST",
        "url": "https://aiprimetech.io/v1/messages",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ model: $json.model, max_tokens: $json.max_tokens, temperature: $json.temperature, system: $json.system, messages: $json.messages }) }}",
        "options": {}
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "rr000000-0000-0000-0000-000000000012",
      "name": "Parse Repaired JSON",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2060,
        380
      ],
      "parameters": {
        "jsCode": "const response = $json;\nconst testRecord = $('Select Test Record').first().json;\nconst mockMode = testRecord.mock_mode || 'none';\n\n// Test E: force technical_error for mock_unrepairable\nif (mockMode === 'mock_unrepairable') {\n  return [{ json: {\n    parse_ok: false,\n    parse_method: 'repaired_json',\n    repair_used: true,\n    repair_status: 'failed',\n    processing_status: 'technical_error',\n    route: 'technical_errors',\n    needs_manual_review: true,\n    parse_error: 'Simulated unrepairable response \u2014 forced technical_error for Test E',\n    raw_response_preview: testRecord.raw_response_preview || '%%%% unrepairable %%%%',\n    test_id: testRecord.test_id,\n    expected_route: testRecord.expected_route,\n    expected_entity_type: testRecord.expected_entity_type,\n    expected_recommended_action: testRecord.expected_recommended_action,\n    expected_quality_range: testRecord.expected_quality_range,\n    test_pass_basic: true,\n    test_notes: 'mock_unrepairable forced technical_error as expected'\n  }}];\n}\n\n// Handle HTTP error from repair call\nif (response.error || response.status >= 400) {\n  return [{ json: {\n    parse_ok: false, parse_method: 'repaired_json', repair_used: true,\n    repair_status: 'failed', processing_status: 'technical_error', route: 'technical_errors',\n    needs_manual_review: true,\n    parse_error: 'Repair HTTP error: ' + JSON.stringify(response.error || response.status),\n    raw_response_preview: JSON.stringify(response).substring(0, 500)\n  }}];\n}\n\n// Find text item\nlet textItem;\ntry {\n  textItem = (response.content || []).find(c => c.type === 'text');\n} catch(e) { textItem = null; }\n\nif (!textItem || !textItem.text) {\n  return [{ json: {\n    parse_ok: false, parse_method: 'repaired_json', repair_used: true,\n    repair_status: 'failed', processing_status: 'technical_error', route: 'technical_errors',\n    needs_manual_review: true,\n    parse_error: 'No text item in repair response',\n    raw_response_preview: JSON.stringify(response.content || response).substring(0, 500)\n  }}];\n}\n\nconst rawPreview = textItem.text.substring(0, 1200);\n\nlet parsed;\ntry {\n  let txt = textItem.text.trim();\n  txt = txt.replace(/^```json\\s*/i,'').replace(/\\s*```$/,'').trim();\n  txt = txt.replace(/^```\\s*/,'').replace(/\\s*```$/,'').trim();\n  const bStart = txt.indexOf('{'); const bEnd = txt.lastIndexOf('}');\n  if (bStart !== -1 && bEnd !== -1) txt = txt.substring(bStart, bEnd + 1);\n  txt = txt.replace(/[\\u2018\\u2019]/g,\"'\").replace(/[\\u201C\\u201D\\u00AB\\u00BB]/g,'\"');\n  parsed = JSON.parse(txt);\n} catch(e) {\n  return [{ json: {\n    parse_ok: false, parse_method: 'repaired_json', repair_used: true,\n    repair_status: 'failed', processing_status: 'technical_error', route: 'technical_errors',\n    needs_manual_review: true,\n    parse_error: 'Repair JSON.parse failed: ' + e.message,\n    raw_response_preview: rawPreview\n  }}];\n}\n\nconst parseMethod = (mockMode === 'mock_markdown') ? 'mock_markdown_repair' : 'repaired_json';\n\nreturn [{ json: {\n  parse_ok: true,\n  parse_method: parseMethod,\n  repair_used: true,\n  repair_status: 'success',\n  parse_error: '',\n  raw_response_preview: rawPreview,\n  ...parsed\n}}];"
      }
    },
    {
      "id": "rr000000-0000-0000-0000-000000000013",
      "name": "Normalize + Route",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2340,
        200
      ],
      "parameters": {
        "jsCode": "const data = $json;\nconst testRecord = $('Select Test Record').first().json;\n\nfunction clamp(v) { const n = parseInt(v); return isNaN(n) ? 1 : Math.min(100, Math.max(1, n)); }\n\n// Normalize free-text service_type into allowed enum values\nfunction normalizeServiceType(raw, hay) {\n  const allowed = ['secured_auto_loan','secured_real_estate_loan','pts_loan','refinancing','mortgage_adjacent','generic_lending'];\n  const r = (raw || '').toLowerCase().trim();\n  if (allowed.includes(r)) return r;\n  const s = r + ' ' + (hay || '');\n  if (s.includes('\u043f\u0442\u0441') || s.includes('pts')) return 'pts_loan';\n  if ((s.includes('\u0430\u0432\u0442\u043e') || s.includes('\u043c\u0430\u0448\u0438\u043d')) && (s.includes('\u0437\u0430\u043b\u043e\u0433') || s.includes('collateral') || s.includes('\u043b\u043e\u043c\u0431\u0430\u0440\u0434'))) return 'secured_auto_loan';\n  if (s.includes('\u043d\u0435\u0434\u0432\u0438\u0436') || s.includes('\u043a\u0432\u0430\u0440\u0442\u0438\u0440') || s.includes('\u0434\u043e\u043c') || s.includes('\u0437\u0435\u043c\u043b')) return 'secured_real_estate_loan';\n  if (s.includes('\u0440\u0435\u0444\u0438\u043d\u0430\u043d\u0441') || s.includes('refinanc')) return 'refinancing';\n  if (s.includes('\u0438\u043f\u043e\u0442\u0435\u043a') || s.includes('mortgage')) return 'mortgage_adjacent';\n  if (s.includes('\u0431\u0438\u0437\u043d\u0435\u0441') || s.includes('business')) {\n    if (s.includes('\u043d\u0435\u0434\u0432\u0438\u0436') || s.includes('\u043a\u0432\u0430\u0440\u0442\u0438\u0440')) return 'secured_real_estate_loan';\n    return 'generic_lending';\n  }\n  return 'unknown';\n}\n\n// Descriptive company_name fallback (never invent a brand)\nfunction companyNameFallback(existing, entity, hay) {\n  if (existing && String(existing).trim() !== '') return existing;\n  if (entity === 'competitor' && (hay.includes('\u043c\u0444\u043e') || hay.includes('\u043c\u0438\u043a\u0440\u043e\u0444\u0438\u043d\u0430\u043d\u0441') || hay.includes('mfo'))) return '\u041c\u0424\u041e / \u0447\u0430\u0441\u0442\u043d\u044b\u0439 \u043a\u0440\u0435\u0434\u0438\u0442\u043e\u0440';\n  if (hay.includes('\u0447\u0430\u0441\u0442\u043d\u044b\u0439 \u0438\u043d\u0432\u0435\u0441\u0442\u043e\u0440')) return '\u0427\u0430\u0441\u0442\u043d\u044b\u0439 \u0438\u043d\u0432\u0435\u0441\u0442\u043e\u0440';\n  if (hay.includes('\u0430\u0432\u0442\u043e\u043b\u043e\u043c\u0431\u0430\u0440\u0434')) return '\u0410\u0432\u0442\u043e\u043b\u043e\u043c\u0431\u0430\u0440\u0434';\n  if (hay.includes('\u0431\u0440\u043e\u043a\u0435\u0440')) return '\u0411\u0440\u043e\u043a\u0435\u0440';\n  if (entity === 'competitor') return '\u041a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0442 \u0431\u0435\u0437 \u0431\u0440\u0435\u043d\u0434\u0430';\n  return '';\n}\n\n// Pass-through for confirmed technical_error (from Parse Repaired JSON)\nif (data.processing_status === 'technical_error' && data.route === 'technical_errors') {\n  const expRoute = testRecord.expected_route || '';\n  return [{ json: {\n    ...data,\n    created_at: data.created_at || testRecord.parsed_at || '',\n    source_type: data.source_type || testRecord.source_type || '',\n    platform: data.platform || testRecord.platform || '',\n    source_url: data.source_url || testRecord.source_url || '',\n    parsed_at: data.parsed_at || testRecord.parsed_at || '',\n    published_at: data.published_at || testRecord.published_at || '',\n    test_id: testRecord.test_id,\n    expected_route: testRecord.expected_route,\n    expected_entity_type: testRecord.expected_entity_type,\n    expected_recommended_action: testRecord.expected_recommended_action,\n    expected_quality_range: testRecord.expected_quality_range,\n    actual_entity_type: data.entity_type || '',\n    actual_recommended_action: data.recommended_action || '',\n    actual_quality_score: data.quality_score || 1,\n    actual_lead_signal_score: data.lead_signal_score || 1,\n    actual_content_idea_score: data.content_idea_score || 1,\n    actual_competitor_strength: data.competitor_strength || 1,\n    test_pass_basic: data.test_pass_basic !== undefined ? data.test_pass_basic : (data.route === expRoute),\n    test_notes: data.test_notes || ('route=' + data.route + '; expected=' + expRoute)\n  }}];\n}\n\n// Normalize schema fields\nconst entity = data.entity_type || 'irrelevant';\nconst status = data.status || 'analyzed';\nconst recAction = data.recommended_action || 'ignore';\nconst leadScore = clamp(data.lead_signal_score);\nconst compScore = clamp(data.competitor_strength);\nconst contentScore = clamp(data.content_idea_score);\nconst qualScore = clamp(data.quality_score);\n\nconst validEntities = ['competitor','lead_signal','market_signal','content_idea','irrelevant'];\nconst validActions = ['monitor','contact','create_content','ignore','investigate'];\nconst safeEntity = validEntities.includes(entity) ? entity : 'irrelevant';\nconst safeAction = validActions.includes(recAction) ? recAction : 'ignore';\nconst safeStatus = (status === 'skipped') ? 'skipped' : 'analyzed';\n\n// Derived text haystacks for normalization and routing keyword checks\nconst hay = ((data.text_context||'') + ' ' + (data.offer_text||'') + ' ' + (data.detected_need||'') + ' ' + (data.reason||'') + ' ' + (data.service_type||'') + ' ' + (testRecord.text_context||'')).toLowerCase();\nconst companyHay = ((data.text_context||'') + ' ' + (data.reason||'') + ' ' + (data.offer_text||'') + ' ' + (data.source_url||testRecord.source_url||'') + ' ' + (data.detected_need||'')).toLowerCase();\n\nconst safeServiceType = normalizeServiceType(data.service_type, hay);\nconst safeCompanyName = companyNameFallback(data.company_name, safeEntity, companyHay);\n\n// Determine processing_status\nlet procStatus = 'parsed_success';\nif (safeStatus === 'skipped' || safeEntity === 'irrelevant') procStatus = 'business_skip';\n\n// Weak / potential lead detection (must run before content_queue routing)\nconst srcType = (data.source_type || testRecord.source_type || '').toLowerCase();\nconst isSocialOrClassified = (srcType === 'social' || srcType === 'classified');\nconst productTerms = ['loan','collateral','pts','auto','real estate','refinanc','\u0437\u0430\u0439\u043c','\u043a\u0440\u0435\u0434\u0438\u0442','\u0437\u0430\u043b\u043e\u0433','\u043f\u0442\u0441','\u0430\u0432\u0442\u043e','\u043c\u0430\u0448\u0438\u043d','\u043d\u0435\u0434\u0432\u0438\u0436','\u043a\u0432\u0430\u0440\u0442\u0438\u0440','\u0437\u0435\u043c\u043b','\u0440\u0435\u0444\u0438\u043d\u0430\u043d\u0441','\u0438\u043f\u043e\u0442\u0435\u043a'];\nconst mentionsProduct = productTerms.some(t => hay.includes(t));\n\nconst weakLead = (\n  (safeEntity === 'lead_signal' && leadScore >= 30 && leadScore <= 69) ||\n  (safeAction === 'investigate') ||\n  (leadScore >= 30 && isSocialOrClassified && mentionsProduct) ||\n  (safeEntity === 'content_idea' && leadScore >= 30 && isSocialOrClassified && safeServiceType !== 'unknown')\n);\n\n// Routing priority:\n// 1 technical_errors (handled above) 2 skipped/irrelevant 3 hot lead 4 weak lead\n// 5 competitor 6 pure content idea 7 fallback review_queue\nlet route;\nlet needsReview;\n\nif (safeStatus === 'skipped' || safeEntity === 'irrelevant') {\n  route = 'skipped_log'; needsReview = false;\n} else if (safeEntity === 'lead_signal' && leadScore >= 70 && safeAction === 'contact') {\n  route = 'results'; needsReview = false;\n} else if (weakLead) {\n  route = 'review_queue'; needsReview = true;\n} else if (safeEntity === 'competitor' && compScore >= 45) {\n  route = 'monitor_queue'; needsReview = false;\n} else if (safeEntity === 'content_idea' && contentScore >= 50) {\n  route = 'content_queue'; needsReview = true;\n} else {\n  route = 'review_queue'; needsReview = true;\n}\n\n// Safety: enforce route is exactly one of the six valid sheet tabs\nconst validRoutes = ['results','review_queue','monitor_queue','content_queue','skipped_log','technical_errors'];\nlet parseError = data.parse_error || '';\nif (!validRoutes.includes(route)) {\n  route = 'technical_errors';\n  procStatus = 'technical_error';\n  needsReview = true;\n  parseError = (parseError ? parseError + '; ' : '') + 'invalid_route';\n}\n\nconst expRoute = testRecord.expected_route || '';\nconst expEntity = testRecord.expected_entity_type || 'any';\n\n// Test pass logic: route-focused for review_queue (entity may legitimately\n// differ after repair); strict entity match for other routes\nlet testPass;\nif (expRoute === 'review_queue') {\n  testPass = (route === 'review_queue') && (needsReview === true) && (leadScore >= 30);\n} else {\n  testPass = (route === expRoute) && (expEntity === 'any' || safeEntity === expEntity);\n}\n\nreturn [{ json: {\n  created_at: data.created_at || testRecord.parsed_at || '',\n  source_type: data.source_type || testRecord.source_type || '',\n  platform: data.platform || testRecord.platform || '',\n  source_url: data.source_url || testRecord.source_url || '',\n  parsed_at: data.parsed_at || testRecord.parsed_at || '',\n  published_at: data.published_at || testRecord.published_at || '',\n  freshness_status: data.freshness_status || 'unknown',\n  entity_type: safeEntity,\n  company_name: safeCompanyName,\n  profile_name: data.profile_name || '',\n  profile_url: data.profile_url || testRecord.profile_url || '',\n  region: data.region || '',\n  service_type: safeServiceType,\n  offer_text: data.offer_text || '',\n  terms: data.terms || '',\n  contact_public: data.contact_public || '',\n  text_context: data.text_context || '',\n  detected_need: data.detected_need || '',\n  competitor_strength: compScore,\n  lead_signal_score: leadScore,\n  content_idea_score: contentScore,\n  quality_score: qualScore,\n  reason: data.reason || '',\n  recommended_action: safeAction,\n  status: safeStatus,\n  processing_status: procStatus,\n  parse_method: data.parse_method || 'primary_json',\n  parse_error: parseError,\n  raw_response_preview: data.raw_response_preview || '',\n  route: route,\n  needs_manual_review: needsReview,\n  source_record_type: testRecord.source_type || '',\n  repair_used: data.repair_used || false,\n  repair_status: data.repair_status || '',\n  test_id: testRecord.test_id,\n  expected_route: testRecord.expected_route,\n  expected_entity_type: testRecord.expected_entity_type,\n  expected_recommended_action: testRecord.expected_recommended_action,\n  expected_quality_range: testRecord.expected_quality_range,\n  actual_entity_type: safeEntity,\n  actual_recommended_action: safeAction,\n  actual_quality_score: qualScore,\n  actual_lead_signal_score: leadScore,\n  actual_content_idea_score: contentScore,\n  actual_competitor_strength: compScore,\n  test_pass_basic: testPass,\n  test_notes: 'route=' + route + '; entity=' + safeEntity + '; lead=' + leadScore + '; comp=' + compScore + '; content=' + contentScore + '; action=' + safeAction + '; service=' + safeServiceType + '; company=' + safeCompanyName + '; weakLead=' + weakLead + '; parse=' + (data.parse_method || '') + '; repair=' + (data.repair_used || false) + '; expected_route=' + expRoute\n}}];\n"
      }
    },
    {
      "id": "rr000000-0000-0000-0000-000000000099",
      "name": "Append to Dynamic Route Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "position": [
        2620,
        200
      ],
      "parameters": {
        "authentication": "serviceAccount",
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "PASTE_SPREADSHEET_ID_HERE",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $json.route }}",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": []
        },
        "options": {}
      },
      "credentials": {
        "googleApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000,
      "onError": "continueRegularOutput",
      "alwaysOutputData": true
    }
  ],
  "connections": {
    "Manual Start": {
      "main": [
        [
          {
            "node": "Set Test Selector",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Test Selector": {
      "main": [
        [
          {
            "node": "Select Test Record",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select Test Record": {
      "main": [
        [
          {
            "node": "Build Primary Claude Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Primary Claude Request": {
      "main": [
        [
          {
            "node": "IF Skip Primary API?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Skip Primary API?": {
      "main": [
        [
          {
            "node": "Build Repair Request",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Claude Primary API Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude Primary API Request": {
      "main": [
        [
          {
            "node": "Parse Primary JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Primary JSON": {
      "main": [
        [
          {
            "node": "IF Primary Parse OK?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Primary Parse OK?": {
      "main": [
        [
          {
            "node": "Normalize + Route",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Repair Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Repair Request": {
      "main": [
        [
          {
            "node": "Claude Repair API Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude Repair API Request": {
      "main": [
        [
          {
            "node": "Parse Repaired JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Repaired JSON": {
      "main": [
        [
          {
            "node": "Normalize + Route",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize + Route": {
      "main": [
        [
          {
            "node": "Append to Dynamic Route Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "rr000000-resilient-router-v003-dynamic-patched-20260606"
}