{
  "name": "KG_Build_Level_1",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 6
            }
          ]
        }
      },
      "id": "trigger-schedule",
      "name": "ScheduleTrigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1,
      "position": [
        250,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// \u68c0\u67e5\u5f53\u524d\u56fe\u8c31\u5b8c\u6574\u5ea6\nconst { Pool } = require('pg');\nconst neo4j = require('neo4j-driver');\n\nconst pgPool = new Pool({\n  host: 'postgres',\n  port: 5432,\n  database: 'nvda',\n  user: 'user',\n  password: 'pass'\n});\n\nconst driver = neo4j.driver(\n  'bolt://neo4j:7687',\n  neo4j.auth.basic('neo4j', 'nvidia2024kg')\n);\n\nasync function checkCompleteness() {\n  const session = driver.session();\n  \n  try {\n    // \u7edf\u8ba1\u5404\u7c7b\u8282\u70b9\n    const companyCount = await session.run('MATCH (c:Company) RETURN count(c) as n').then(r => r.records[0].get('n').toNumber());\n    const productCount = await session.run('MATCH (p:Product) RETURN count(p) as n').then(r => r.records[0].get('n').toNumber());\n    const techCount = await session.run('MATCH (t:Technology) RETURN count(t) as n').then(r => r.records[0].get('n').toNumber());\n    const relCount = await session.run('MATCH ()-[r]->() RETURN count(r) as n').then(r => r.records[0].get('n').toNumber());\n    \n    // \u68c0\u67e5\u7f3a\u5931\u5173\u7cfb\u7684\u516c\u53f8\n    const missingSuppliers = await session.run(`\n      MATCH (c:Company)\n      WHERE NOT (c)-[:SUPPLIES_TO]-() AND c.company_id <> 'NVDA'\n      RETURN c.company_id as id, c.name_cn as name\n    `).then(r => r.records.map(rec => ({id: rec.get('id'), name: rec.get('name')})));\n    \n    // \u8ba1\u7b97\u5b8c\u6574\u5ea6\u767e\u5206\u6bd4\n    const companyCompleteness = Math.min(companyCount / 30 * 100, 100);\n    const productCompleteness = Math.min(productCount / 50 * 100, 100);\n    const relationCompleteness = Math.min(relCount / 100 * 100, 100);\n    \n    return [{\n      json: {\n        timestamp: new Date().toISOString(),\n        completeness: {\n          companies: { current: companyCount, target: 30, pct: companyCompleteness },\n          products: { current: productCount, target: 50, pct: productCompleteness },\n          relations: { current: relCount, target: 100, pct: relationCompleteness },\n          overall: (companyCompleteness + productCompleteness + relationCompleteness) / 3\n        },\n        gaps: {\n          missingSuppliers: missingSuppliers.map(c => c.id),\n          missingSuppliersCount: missingSuppliers.length\n        },\n        needsExpansion: companyCount < 30 || productCount < 50 || relCount < 100\n      }\n    }];\n  } finally {\n    await session.close();\n  }\n}\n\nreturn checkCompleteness();"
      },
      "id": "check-completeness",
      "name": "CheckCompleteness",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        450,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "condition-1",
              "leftValue": "={{ $json.needsExpansion }}",
              "rightValue": "true",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ]
        }
      },
      "id": "should-expand",
      "name": "NeedsExpansion",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        650,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// \u6839\u636e\u7f3a\u5931\u6570\u636e\u751f\u6210\u4efb\u52a1\u961f\u5217\nconst gaps = $input.first().json.gaps;\nconst completeness = $input.first().json.completeness;\n\nconst tasks = [];\n\n// \u4f18\u5148\u5904\u7406\u7f3a\u5931\u4f9b\u5e94\u5546\u5173\u7cfb\u7684\u516c\u53f8\nif (gaps.missingSuppliersCount > 0) {\n  for (const companyId of gaps.missingSuppliers.slice(0, 3)) {\n    tasks.push({\n      taskType: 'enrich_company_relations',\n      priority: 'high',\n      targetCompany: companyId,\n      description: `\u67e5\u627e ${companyId} \u7684\u4f9b\u5e94\u5173\u7cfb`\n    });\n  }\n}\n\n// \u5982\u679c\u516c\u53f8\u6570\u91cf\u4e0d\u8db3\uff0c\u6dfb\u52a0\u65b0\u516c\u53f8\nif (completeness.companies.current < 30) {\n  tasks.push({\n    taskType: 'discover_companies',\n    priority: 'medium',\n    sector: 'upstream_materials',\n    description: '\u53d1\u73b0\u4e0a\u6e38\u6750\u6599\u516c\u53f8'\n  });\n}\n\n// \u5982\u679c\u4ea7\u54c1\u6570\u91cf\u4e0d\u8db3\nif (completeness.products.current < 50) {\n  tasks.push({\n    taskType: 'discover_products',\n    priority: 'medium',\n    company: 'NVDA',\n    description: '\u53d1\u73b0NVIDIA\u65b0\u4ea7\u54c1'\n  });\n}\n\nreturn tasks.map(t => ({ json: t }));"
      },
      "id": "generate-tasks",
      "name": "GenerateTasks",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        850,
        200
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.DATA_SERVICE_URL }}/api/kg/enrich",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "taskType",
              "value": "={{ $json.taskType }}"
            },
            {
              "name": "targetCompany",
              "value": "={{ $json.targetCompany }}"
            },
            {
              "name": "priority",
              "value": "={{ $json.priority }}"
            }
          ]
        },
        "options": {}
      },
      "id": "enrich-kg",
      "name": "EnrichKnowledgeGraph",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        1050,
        200
      ]
    },
    {
      "parameters": {
        "jsCode": "// \u9a8c\u8bc1\u65b0\u6dfb\u52a0\u7684\u6570\u636e\nconst neo4j = require('neo4j-driver');\nconst driver = neo4j.driver('bolt://neo4j:7687', neo4j.auth.basic('neo4j', 'nvidia2024kg'));\n\nasync function validate() {\n  const session = driver.session();\n  try {\n    // \u68c0\u67e5\u65b0\u6dfb\u52a0\u7684\u5173\u7cfb\u662f\u5426\u6709\u5b8c\u6574\u7684\u5c5e\u6027\n    const result = await session.run(`\n      MATCH ()-[r:SUPPLIES_TO]->()\n      WHERE r.dependency_score IS NULL OR r.substitutability IS NULL\n      RETURN count(r) as incomplete\n    `);\n    \n    const incomplete = result.records[0].get('incomplete').toNumber();\n    \n    return [{\n      json: {\n        validated: incomplete === 0,\n        incompleteRelations: incomplete,\n        message: incomplete > 0 ? `${incomplete} \u6761\u5173\u7cfb\u7f3a\u5c11\u5b8c\u6574\u5c5e\u6027` : '\u6240\u6709\u5173\u7cfb\u5df2\u5b8c\u6574\u6807\u6ce8'\n      }\n    }];\n  } finally {\n    await session.close();\n  }\n}\n\nreturn validate();"
      },
      "id": "validate-data",
      "name": "ValidateNewData",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1250,
        200
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.DATA_SERVICE_URL }}/webhook/kg-progress",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "event",
              "value": "kg_build_iteration_complete"
            },
            {
              "name": "completeness",
              "value": "={{ $json }}"
            }
          ]
        }
      },
      "id": "notify-progress",
      "name": "NotifyProgress",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        1450,
        200
      ]
    },
    {
      "parameters": {
        "jsCode": "// \u68c0\u67e5\u662f\u5426\u8fbe\u5230 Level 1 \u5b8c\u6574\u5ea6\nconst completeness = $input.first().json.completeness;\n\nconst level1Complete = \n  completeness.companies.current >= 30 &&\n  completeness.products.current >= 50 &&\n  completeness.relations.current >= 100;\n\nreturn [{\n  json: {\n    level1Complete,\n    message: level1Complete ? '\u2705 Level 1 \u77e5\u8bc6\u56fe\u8c31\u6784\u5efa\u5b8c\u6210' : '\u23f3 \u7ee7\u7eed\u6784\u5efa\u4e2d...',\n    nextCheck: level1Complete ? null : '6\u5c0f\u65f6\u540e',\n    stats: completeness\n  }\n}];"
      },
      "id": "check-level1",
      "name": "CheckLevel1Complete",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        850,
        400
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "condition-1",
              "leftValue": "={{ $json.level1Complete }}",
              "rightValue": "true",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ]
        }
      },
      "id": "is-complete",
      "name": "IsLevel1Complete",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1050,
        400
      ]
    },
    {
      "parameters": {
        "to": "{{ $env.ALERT_EMAIL }}",
        "subject": "\ud83c\udfaf NVIDIA KG Level 1 \u6784\u5efa\u5b8c\u6210",
        "text": "={{ $json.message }}\\n\\n\u7edf\u8ba1:\\n- \u516c\u53f8: {{ $json.stats.companies.current }}/{{ $json.stats.companies.target }}\\n- \u4ea7\u54c1: {{ $json.stats.products.current }}/{{ $json.stats.products.target }}\\n- \u5173\u7cfb: {{ $json.stats.relations.current }}/{{ $json.stats.relations.target }}\\n\\n\u6574\u4f53\u5b8c\u6574\u5ea6: {{ $json.stats.overall.toFixed(1) }}%"
      },
      "id": "send-completion-email",
      "name": "SendCompletionEmail",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [
        1250,
        300
      ]
    },
    {
      "parameters": {
        "to": "{{ $env.ALERT_EMAIL }}",
        "subject": "\ud83d\udcca NVIDIA KG \u6784\u5efa\u8fdb\u5ea6\u62a5\u544a",
        "text": "={{ $json.message }}\\n\\n\u5f53\u524d\u8fdb\u5ea6:\\n- \u516c\u53f8: {{ $json.stats.companies.current }}/{{ $json.stats.companies.target }} ({{ $json.stats.companies.pct.toFixed(1) }}%)\\n- \u4ea7\u54c1: {{ $json.stats.products.current }}/{{ $json.stats.products.target }} ({{ $json.stats.products.pct.toFixed(1) }}%)\\n- \u5173\u7cfb: {{ $json.stats.relations.current }}/{{ $json.stats.relations.target }} ({{ $json.stats.relations.pct.toFixed(1) }}%)\\n\\n\u4e0b\u6b21\u68c0\u67e5: {{ $json.nextCheck }}"
      },
      "id": "send-progress-email",
      "name": "SendProgressEmail",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [
        1250,
        500
      ]
    }
  ],
  "connections": {
    "ScheduleTrigger": {
      "main": [
        [
          {
            "node": "CheckCompleteness",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "CheckCompleteness": {
      "main": [
        [
          {
            "node": "NeedsExpansion",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "NeedsExpansion": {
      "main": [
        [
          {
            "node": "GenerateTasks",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "CheckLevel1Complete",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GenerateTasks": {
      "main": [
        [
          {
            "node": "EnrichKnowledgeGraph",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "EnrichKnowledgeGraph": {
      "main": [
        [
          {
            "node": "ValidateNewData",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ValidateNewData": {
      "main": [
        [
          {
            "node": "NotifyProgress",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "CheckLevel1Complete": {
      "main": [
        [
          {
            "node": "IsLevel1Complete",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IsLevel1Complete": {
      "main": [
        [
          {
            "node": "SendCompletionEmail",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "SendProgressEmail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "saveDataErrorExecution": "all",
    "saveDataSuccessExecution": "all",
    "saveExecutionProgress": true,
    "saveManualExecutions": true,
    "timezone": "Asia/Shanghai"
  },
  "staticData": null,
  "tags": [
    "knowledge-graph",
    "p0",
    "auto-build"
  ]
}