{
  "id": "wf004b-asset-quality-gate-0000",
  "name": "WF-004b-asset-quality-gate",
  "nodes": [
    {
      "parameters": {
        "inputSource": "passthrough"
      },
      "id": "c4b00000-0000-0000-0000-000000000001",
      "name": "Execute Workflow Trigger",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.1,
      "position": [
        240,
        400
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "={{ `SELECT forbidden_phrases, prompt_prefix, fact_policy, tone FROM brand_profiles WHERE channel_id = ${$json.channel_id} LIMIT 1` }}",
        "options": {}
      },
      "id": "c4b00000-0000-0000-0000-000000000002",
      "name": "Fetch Brand Profile",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        460,
        400
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT pv.system_prompt, pv.user_prompt_template, pv.model_params FROM prompt_versions pv JOIN prompts p ON p.id = pv.prompt_id WHERE p.name = 'quality_gate' AND pv.version = p.current_version AND p.is_active = true LIMIT 1",
        "options": {}
      },
      "id": "c4b00000-0000-0000-0000-000000000003",
      "name": "Load Quality Gate Prompt",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        680,
        400
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const trigger = $('Execute Workflow Trigger').item.json;\nconst brand = $('Fetch Brand Profile').item.json;\nconst prompt = $input.item.json;\n\nconst userPrompt = (prompt.user_prompt_template || '')\n  .replace('{{ content_asset_json }}', JSON.stringify(trigger.asset || {}))\n  .replace(\"{{ brand_profile.forbidden_phrases | join(', ') }}\", (brand.forbidden_phrases || []).join(', '))\n  .replace('{{ brand_profile.fact_policy }}', brand.fact_policy || '')\n  .replace('{{ brand_profile.tone }}', brand.tone || '');\n\nreturn [{\n  json: {\n    asset_id: trigger.asset_id,\n    topic_id: trigger.topic_id,\n    topic_title: trigger.topic_title,\n    channel_id: trigger.channel_id,\n    system_prompt: prompt.system_prompt,\n    user_prompt: userPrompt,\n    task_type: 'quality_gate',\n    output_format: 'json',\n    max_tokens: 1500,\n    workflow_name: 'WF-004b-asset-quality-gate'\n  }\n}];"
      },
      "id": "c4b00000-0000-0000-0000-000000000004",
      "name": "Build QG Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        900,
        400
      ]
    },
    {
      "parameters": {
        "workflowId": {
          "__rl": true,
          "value": "2673a82a-e155-4d0c-85b4-92922b74849d",
          "mode": "id"
        },
        "options": {}
      },
      "id": "c4b00000-0000-0000-0000-000000000005",
      "name": "Call SUB-001 Pass1",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.1,
      "position": [
        1120,
        400
      ]
    },
    {
      "parameters": {
        "jsCode": "const raw = $input.item.json;\nconst ctx = $('Build QG Prompt').item.json;\n\nlet scores = raw.response_json;\nif (!scores || typeof scores !== 'object' || Array.isArray(scores)) {\n  try { scores = JSON.parse(raw.response_text); } catch (e) { scores = {}; }\n}\n\nconst factual_completeness = Number(scores.factual_completeness) || 0;\nconst citation_coverage    = Number(scores.citation_coverage) || 0;\nconst hook_strength        = Number(scores.hook_strength) || 0;\nconst readability          = Number(scores.readability) || 0;\nconst seo_completeness     = Number(scores.seo_completeness) || 0;\nconst brand_consistency    = Number(scores.brand_consistency) || 0;\n\nconst overall_score = Math.round((\n  factual_completeness * 0.20 +\n  citation_coverage    * 0.20 +\n  hook_strength        * 0.20 +\n  readability           * 0.15 +\n  seo_completeness      * 0.15 +\n  brand_consistency      * 0.10\n) * 100) / 100;\n\nreturn [{\n  json: {\n    asset_id: ctx.asset_id,\n    topic_id: ctx.topic_id,\n    topic_title: ctx.topic_title,\n    channel_id: ctx.channel_id,\n    factual_completeness, citation_coverage, hook_strength,\n    readability, seo_completeness, brand_consistency,\n    overall_score,\n    failure_reasons: Array.isArray(scores.failure_reasons) ? scores.failure_reasons : [],\n    llm_feedback: scores.llm_feedback || '',\n    provider_used: raw.provider_name || raw.provider_used || null\n  }\n}];"
      },
      "id": "c4b00000-0000-0000-0000-000000000006",
      "name": "Parse Scores Pass1",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1340,
        400
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c4b00000-0000-0000-0000-000000000007-c",
              "leftValue": "={{ $json.overall_score }}",
              "rightValue": 0.5,
              "operator": {
                "type": "number",
                "operation": "gte"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "c4b00000-0000-0000-0000-000000000007",
      "name": "Passed Pass1?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1560,
        400
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "={{ `INSERT INTO asset_quality_gates (asset_id, status, factual_completeness, citation_coverage, hook_strength, readability, seo_completeness, brand_consistency, overall_score, failure_reasons, llm_feedback, retry_count, provider_used, created_at, updated_at) VALUES (${$json.asset_id}, 'passed', ${$json.factual_completeness}, ${$json.citation_coverage}, ${$json.hook_strength}, ${$json.readability}, ${$json.seo_completeness}, ${$json.brand_consistency}, ${$json.overall_score}, ARRAY[${($json.failure_reasons||[]).map(r => `'${String(r).replace(/'/g, \"''\")}'`).join(',')}]::TEXT[], '${(($json.llm_feedback)||'').replace(/'/g, \"''\")}', 0, ${$json.provider_used ? `'${$json.provider_used}'` : 'NULL'}, NOW(), NOW()) RETURNING id` }}",
        "options": {}
      },
      "id": "c4b00000-0000-0000-0000-000000000008",
      "name": "Insert Quality Gate Passed",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        1780,
        280
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "c4b00000-r-1",
              "name": "passed",
              "value": true,
              "type": "boolean"
            },
            {
              "id": "c4b00000-r-2",
              "name": "asset_id",
              "value": "={{ $('Parse Scores Pass1').item.json.asset_id }}",
              "type": "number"
            },
            {
              "id": "c4b00000-r-3",
              "name": "overall_score",
              "value": "={{ $('Parse Scores Pass1').item.json.overall_score }}",
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "id": "c4b00000-0000-0000-0000-000000000009",
      "name": "Return Passed Pass1",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        2000,
        280
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "={{ `INSERT INTO asset_quality_gates (asset_id, status, factual_completeness, citation_coverage, hook_strength, readability, seo_completeness, brand_consistency, overall_score, failure_reasons, llm_feedback, retry_count, provider_used, created_at, updated_at) VALUES (${$json.asset_id}, 'failed', ${$json.factual_completeness}, ${$json.citation_coverage}, ${$json.hook_strength}, ${$json.readability}, ${$json.seo_completeness}, ${$json.brand_consistency}, ${$json.overall_score}, ARRAY[${($json.failure_reasons||[]).map(r => `'${String(r).replace(/'/g, \"''\")}'`).join(',')}]::TEXT[], '${(($json.llm_feedback)||'').replace(/'/g, \"''\")}', 1, ${$json.provider_used ? `'${$json.provider_used}'` : 'NULL'}, NOW(), NOW()) RETURNING id` }}",
        "options": {}
      },
      "id": "c4b00000-0000-0000-0000-000000000010",
      "name": "Insert Quality Gate Failed",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        1780,
        520
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const scores = $('Parse Scores Pass1').item.json;\nconst base = $('Build QG Prompt').item.json;\n\nconst correctivePrompt = base.user_prompt +\n  `\\n\\nPrevious evaluation failed with reasons: ${(scores.failure_reasons || []).join('; ')}. Re-evaluate carefully and address these specific issues in your scoring.`;\n\nreturn [{\n  json: {\n    asset_id: scores.asset_id,\n    topic_id: scores.topic_id,\n    topic_title: scores.topic_title,\n    channel_id: scores.channel_id,\n    system_prompt: base.system_prompt,\n    user_prompt: correctivePrompt,\n    task_type: 'quality_gate',\n    output_format: 'json',\n    max_tokens: 1500,\n    workflow_name: 'WF-004b-asset-quality-gate'\n  }\n}];"
      },
      "id": "c4b00000-0000-0000-0000-000000000011",
      "name": "Build Corrective Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2000,
        520
      ]
    },
    {
      "parameters": {
        "workflowId": {
          "__rl": true,
          "value": "2673a82a-e155-4d0c-85b4-92922b74849d",
          "mode": "id"
        },
        "options": {}
      },
      "id": "c4b00000-0000-0000-0000-000000000012",
      "name": "Call SUB-001 Pass2",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.1,
      "position": [
        2220,
        520
      ]
    },
    {
      "parameters": {
        "jsCode": "const raw = $input.item.json;\nconst ctx = $('Build Corrective Prompt').item.json;\n\nlet scores = raw.response_json;\nif (!scores || typeof scores !== 'object' || Array.isArray(scores)) {\n  try { scores = JSON.parse(raw.response_text); } catch (e) { scores = {}; }\n}\n\nconst factual_completeness = Number(scores.factual_completeness) || 0;\nconst citation_coverage    = Number(scores.citation_coverage) || 0;\nconst hook_strength        = Number(scores.hook_strength) || 0;\nconst readability          = Number(scores.readability) || 0;\nconst seo_completeness     = Number(scores.seo_completeness) || 0;\nconst brand_consistency    = Number(scores.brand_consistency) || 0;\n\nconst overall_score = Math.round((\n  factual_completeness * 0.20 +\n  citation_coverage    * 0.20 +\n  hook_strength        * 0.20 +\n  readability           * 0.15 +\n  seo_completeness      * 0.15 +\n  brand_consistency      * 0.10\n) * 100) / 100;\n\nreturn [{\n  json: {\n    asset_id: ctx.asset_id,\n    topic_id: ctx.topic_id,\n    topic_title: ctx.topic_title,\n    channel_id: ctx.channel_id,\n    factual_completeness, citation_coverage, hook_strength,\n    readability, seo_completeness, brand_consistency,\n    overall_score,\n    failure_reasons: Array.isArray(scores.failure_reasons) ? scores.failure_reasons : [],\n    llm_feedback: scores.llm_feedback || '',\n    provider_used: raw.provider_name || raw.provider_used || null\n  }\n}];"
      },
      "id": "c4b00000-0000-0000-0000-000000000013",
      "name": "Parse Scores Pass2",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2440,
        520
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c4b00000-0000-0000-0000-000000000014-c",
              "leftValue": "={{ $json.overall_score }}",
              "rightValue": 0.5,
              "operator": {
                "type": "number",
                "operation": "gte"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "c4b00000-0000-0000-0000-000000000014",
      "name": "Passed Pass2?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2660,
        520
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "={{ `UPDATE asset_quality_gates SET status = 'passed', factual_completeness = ${$json.factual_completeness}, citation_coverage = ${$json.citation_coverage}, hook_strength = ${$json.hook_strength}, readability = ${$json.readability}, seo_completeness = ${$json.seo_completeness}, brand_consistency = ${$json.brand_consistency}, overall_score = ${$json.overall_score}, failure_reasons = ARRAY[${($json.failure_reasons||[]).map(r => `'${String(r).replace(/'/g, \"''\")}'`).join(',')}]::TEXT[], llm_feedback = '${(($json.llm_feedback)||'').replace(/'/g, \"''\")}', updated_at = NOW() WHERE asset_id = ${$json.asset_id} RETURNING id` }}",
        "options": {}
      },
      "id": "c4b00000-0000-0000-0000-000000000015",
      "name": "Update Quality Gate Passed",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        2880,
        420
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "c4b00000-r2-1",
              "name": "passed",
              "value": true,
              "type": "boolean"
            },
            {
              "id": "c4b00000-r2-2",
              "name": "asset_id",
              "value": "={{ $('Parse Scores Pass2').item.json.asset_id }}",
              "type": "number"
            },
            {
              "id": "c4b00000-r2-3",
              "name": "overall_score",
              "value": "={{ $('Parse Scores Pass2').item.json.overall_score }}",
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "id": "c4b00000-0000-0000-0000-000000000016",
      "name": "Return Passed Pass2",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3100,
        420
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "={{ `UPDATE asset_quality_gates SET status = 'manual_review', factual_completeness = ${$json.factual_completeness}, citation_coverage = ${$json.citation_coverage}, hook_strength = ${$json.hook_strength}, readability = ${$json.readability}, seo_completeness = ${$json.seo_completeness}, brand_consistency = ${$json.brand_consistency}, overall_score = ${$json.overall_score}, failure_reasons = ARRAY[${($json.failure_reasons||[]).map(r => `'${String(r).replace(/'/g, \"''\")}'`).join(',')}]::TEXT[], llm_feedback = '${(($json.llm_feedback)||'').replace(/'/g, \"''\")}', updated_at = NOW() WHERE asset_id = ${$json.asset_id} RETURNING id` }}",
        "options": {}
      },
      "id": "c4b00000-0000-0000-0000-000000000017",
      "name": "Update Quality Gate Manual Review",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        2880,
        620
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const esc = s => String(s == null ? '' : s).replace(/_/g, ' ').replace(/[*`\\[]/g, '');\nconst s = $('Parse Scores Pass2').item.json;\nreturn [{\n  json: {\n    message: `\\u26a0\\ufe0f *QUALITY GATE FAILED \\u2014 Manual Review Required*\\n\\nAsset ID: ${s.asset_id}\\nTopic: ${esc(s.topic_title)}\\nScore: ${s.overall_score} / 0.70 required\\nFailures: ${(s.failure_reasons || []).map(esc).join(', ')}`,\n    parse_mode: 'Markdown',\n    inline_keyboard: [],\n    asset_id: s.asset_id\n  }\n}];"
      },
      "id": "c4b00000-0000-0000-0000-000000000018",
      "name": "Build Manual Review Alert",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3100,
        620
      ]
    },
    {
      "parameters": {
        "workflowId": {
          "__rl": true,
          "value": "sub003-telegram-notification-000000",
          "mode": "id"
        },
        "options": {}
      },
      "id": "c4b00000-0000-0000-0000-000000000019",
      "name": "Notify Manual Review",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.1,
      "position": [
        3320,
        620
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "c4b00000-r3-1",
              "name": "passed",
              "value": false,
              "type": "boolean"
            },
            {
              "id": "c4b00000-r3-2",
              "name": "asset_id",
              "value": "={{ $('Parse Scores Pass2').item.json.asset_id }}",
              "type": "number"
            },
            {
              "id": "c4b00000-r3-3",
              "name": "status",
              "value": "manual_review",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "c4b00000-0000-0000-0000-000000000020",
      "name": "Return Manual Review",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3540,
        620
      ]
    }
  ],
  "connections": {
    "Execute Workflow Trigger": {
      "main": [
        [
          {
            "node": "Fetch Brand Profile",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Brand Profile": {
      "main": [
        [
          {
            "node": "Load Quality Gate Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Quality Gate Prompt": {
      "main": [
        [
          {
            "node": "Build QG Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build QG Prompt": {
      "main": [
        [
          {
            "node": "Call SUB-001 Pass1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call SUB-001 Pass1": {
      "main": [
        [
          {
            "node": "Parse Scores Pass1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Scores Pass1": {
      "main": [
        [
          {
            "node": "Passed Pass1?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed Pass1?": {
      "main": [
        [
          {
            "node": "Insert Quality Gate Passed",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Insert Quality Gate Failed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert Quality Gate Passed": {
      "main": [
        [
          {
            "node": "Return Passed Pass1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert Quality Gate Failed": {
      "main": [
        [
          {
            "node": "Build Corrective Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Corrective Prompt": {
      "main": [
        [
          {
            "node": "Call SUB-001 Pass2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call SUB-001 Pass2": {
      "main": [
        [
          {
            "node": "Parse Scores Pass2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Scores Pass2": {
      "main": [
        [
          {
            "node": "Passed Pass2?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed Pass2?": {
      "main": [
        [
          {
            "node": "Update Quality Gate Passed",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Update Quality Gate Manual Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Quality Gate Passed": {
      "main": [
        [
          {
            "node": "Return Passed Pass2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Quality Gate Manual Review": {
      "main": [
        [
          {
            "node": "Build Manual Review Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Manual Review Alert": {
      "main": [
        [
          {
            "node": "Notify Manual Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notify Manual Review": {
      "main": [
        [
          {
            "node": "Return Manual Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}