{
  "id": "9Ak4jsjPsQYqLYhy",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "AI Medical Guideline Assistant",
  "tags": [],
  "nodes": [
    {
      "id": "0627e3c3-ef29-49a9-99f7-f13cfa8ba6df",
      "name": "Get Pending Guideline Request",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -1088,
        -416
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "pending",
              "lookupColumn": "Status"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1hwvmK3iqEV_8ka9QL4t3RTzIqAHRzCQCjMN0tVSzzsc/edit#gid=0",
          "cachedResultName": "Requests"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1hwvmK3iqEV_8ka9QL4t3RTzIqAHRzCQCjMN0tVSzzsc",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1hwvmK3iqEV_8ka9QL4t3RTzIqAHRzCQCjMN0tVSzzsc/edit?usp=drivesdk",
          "cachedResultName": "AI Medical Guideline Assistant"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "82ccef27-a7e4-4107-b142-4537aad47ff9",
      "name": "Mark Request as Processing",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -928,
        -416
      ],
      "parameters": {
        "columns": {
          "value": {
            "Status": "processing",
            "Report URL": "={{ \"\" }}",
            "Request ID": "={{ $json['Request ID'] }}",
            "Completed at": "={{ \"\" }}",
            "Error Message": "={{ \"\" }}",
            "Target Region": "={{ $json['Target Region'] }}",
            "Output Language": "={{ $json['Output Language'] }}",
            "Clinical Question": "={{ $json['Clinical Question'] }}"
          },
          "schema": [
            {
              "id": "Request ID",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Request ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Clinical Question",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Clinical Question",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Target Region",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Target Region",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Output Language",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Output Language",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Completed at",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Completed at",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Report URL",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Report URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Error Message",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Error Message",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Request ID"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1hwvmK3iqEV_8ka9QL4t3RTzIqAHRzCQCjMN0tVSzzsc/edit#gid=0",
          "cachedResultName": "Requests"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1hwvmK3iqEV_8ka9QL4t3RTzIqAHRzCQCjMN0tVSzzsc",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1hwvmK3iqEV_8ka9QL4t3RTzIqAHRzCQCjMN0tVSzzsc/edit?usp=drivesdk",
          "cachedResultName": "AI Medical Guideline Assistant"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "fd3d5f4d-8d9c-4713-ab22-5128f5f32cd4",
      "name": "Google Gemini Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        -752,
        -400
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "80a021e0-97c5-4128-8c6a-3f6758981428",
      "name": "Analyze Clinical Question",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        -736,
        -560
      ],
      "parameters": {
        "text": "=System Prompt\n\nYou are a clinical question analysis assistant for an evidence-based medical guideline workflow.\n\nYour role is strictly limited to extracting structured clinical concepts from the user's clinical question.\n\nThese concepts will be used later by the workflow to identify trusted guideline sources.\n\nYou are NOT responsible for:\n- answering the clinical question\n- recommending treatment\n- searching the web\n- selecting guideline organizations or websites\n- prioritizing evidence\n- recommending search strategies\n- making clinical decisions\n- inventing patient information\n- inferring information that is not reasonably supported by the input\n\nNever guess.\n\nNever infer clinical information that is not present in the question.\n\nIf multiple interpretations are possible, select only the interpretation that is most strongly supported by the input.\n\nNormalize all extracted medical concepts into clear, standardized English regardless of the language of the original question.\n\nUse internationally accepted standard medical terminology (e.g., MeSH, SNOMED CT, ICD) whenever possible, rather than literal translation.\n\nPreserve the original clinical question exactly as provided.\n\nIf a field cannot be determined, use an empty string (\"\") or an empty array ([]).\n\nThe field \"question_type\" MUST be exactly one of the following values:\n\n- treatment\n- diagnosis\n- screening\n- prevention\n- monitoring\n- prognosis\n- rehabilitation\n- medication_safety\n- procedure\n- other\n\nDo not generate any other value.\n\nThe field \"guideline_domains\" MUST contain only values from the following list:\n\n- stroke\n- cardiology\n- hypertension\n- lipid_management\n- diabetes\n- nephrology\n- infectious_disease\n- respiratory\n- gastroenterology\n- oncology\n- emergency_critical_care\n- obstetrics\n- pediatrics\n- psychiatry\n- geriatrics\n\nDo not create new domain names.\n\nReturn exactly one JSON object matching the schema provided in the User Prompt.\n\nEvery field in the schema must be present.\n\nDo not omit any field.\n\nDo not return markdown.\n\nDo not return explanations before or after the JSON.\n\nYour task ends immediately after generating the JSON object.\n\n\nUser Prompt\n\nClinical Question:\n{{ $json[\"Clinical Question\"] }}\n\nTarget Region:\n{{ $json[\"Target Region\"] }}\n\nOutput Language:\n{{ $json[\"Output Language\"] }}\n\nAnalyze the clinical question and extract structured clinical concepts.\n\nThe output must strictly follow the JSON schema below.\n\n{\n  \"clinical_question_original\": \"\",\n  \"clinical_question_english\": \"\",\n  \"target_region\": \"\",\n  \"output_language\": \"\",\n\n  \"clinical_concepts\": {\n    \"conditions\": [],\n    \"interventions\": [],\n    \"drugs\": [],\n    \"procedures\": [],\n    \"populations\": [],\n    \"comorbidities\": [],\n    \"clinical_goals\": [],\n    \"care_settings\": []\n  },\n\n  \"normalized_concepts\": {\n    \"primary_condition\": \"\",\n    \"secondary_conditions\": [],\n    \"primary_intervention\": \"\",\n    \"drug_classes\": [],\n    \"guideline_domains\": []\n  },\n\n  \"search_intent\": {\n    \"question_type\": \"\",\n    \"needs_treatment_guideline\": false,\n    \"needs_diagnostic_guideline\": false,\n    \"needs_screening_guideline\": false,\n    \"needs_prevention_guideline\": false,\n    \"needs_special_population_guidance\": false\n  }\n}",
        "batching": {},
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.7
    },
    {
      "id": "3a1cb5c2-b6b2-4e06-8b96-41f261ac967c",
      "name": "Structured Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        -576,
        -400
      ],
      "parameters": {
        "jsonSchemaExample": "{\n  \"clinical_question_original\": \"Should statins be continued after intracerebral hemorrhage?\",\n  \"clinical_question_english\": \"Should statins be continued after intracerebral hemorrhage?\",\n  \"target_region\": \"Japan\",\n  \"output_language\": \"Japanese\",\n  \"clinical_concepts\": {\n    \"conditions\": [\n      \"intracerebral hemorrhage\"\n    ],\n    \"interventions\": [\n      \"continuation of statin therapy\"\n    ],\n    \"drugs\": [\n      \"statins\"\n    ],\n    \"procedures\": [],\n    \"populations\": [],\n    \"comorbidities\": [],\n    \"clinical_goals\": [\n      \"secondary prevention\"\n    ],\n    \"care_settings\": [\n      \"post-stroke care\"\n    ]\n  },\n  \"normalized_concepts\": {\n    \"primary_condition\": \"intracerebral hemorrhage\",\n    \"secondary_conditions\": [],\n    \"primary_intervention\": \"statin therapy\",\n    \"drug_classes\": [\n      \"lipid-lowering therapy\"\n    ],\n    \"guideline_domains\": [\n      \"stroke\",\n      \"lipid_management\"\n    ]\n  },\n  \"search_intent\": {\n    \"question_type\": \"treatment\",\n    \"needs_treatment_guideline\": true,\n    \"needs_diagnostic_guideline\": false,\n    \"needs_screening_guideline\": false,\n    \"needs_prevention_guideline\": true,\n    \"needs_special_population_guidance\": false\n  }\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "7d6593e1-b62d-4e6d-ae1c-53614038c843",
      "name": "Get Trusted Sources",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -272,
        -416
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 194471370,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1hwvmK3iqEV_8ka9QL4t3RTzIqAHRzCQCjMN0tVSzzsc/edit#gid=194471370",
          "cachedResultName": "Trusted Sources"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1hwvmK3iqEV_8ka9QL4t3RTzIqAHRzCQCjMN0tVSzzsc",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1hwvmK3iqEV_8ka9QL4t3RTzIqAHRzCQCjMN0tVSzzsc/edit?usp=drivesdk",
          "cachedResultName": "AI Medical Guideline Assistant"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "e4ba7dfb-deeb-4afc-b27b-3a10e3b99aa1",
      "name": "Match Trusted Sources",
      "type": "n8n-nodes-base.code",
      "position": [
        -32,
        -416
      ],
      "parameters": {
        "jsCode": "// Clinical Concept Extraction\u306e\u7d50\u679c\u3092\u53d6\u5f97\nconst analysis = $(\"Analyze Clinical Question\").first().json.output;\n\n// Trusted Sources\u30b7\u30fc\u30c8\u304b\u3089\u53d6\u5f97\u3057\u305f\u5168\u884c\nconst sourceRows = $input.all().map(item => item.json);\n\n// Guideline domains\u3092\u6b63\u898f\u5316\nconst guidelineDomains =\n  analysis.normalized_concepts?.guideline_domains ?? [];\n\nconst targetRegion = analysis.target_region ?? \"\";\n\n// Active\u5217\u306e\u5024\u3092Boolean\u3068\u3057\u3066\u5224\u5b9a\u3059\u308b\u95a2\u6570\nfunction isActive(value) {\n  if (value === true) return true;\n\n  const normalized = String(value ?? \"\")\n    .trim()\n    .toLowerCase();\n\n  return [\"true\", \"yes\", \"1\", \"active\"].includes(normalized);\n}\n\n// Target Region\u306e\u60c5\u5831\u6e90\nconst regionalSources = sourceRows.filter(row => {\n  return (\n    guidelineDomains.includes(row[\"Guideline Domain\"]) &&\n    row[\"Region\"] === targetRegion &&\n    isActive(row[\"Active\"])\n  );\n});\n\n// Global\u306e\u60c5\u5831\u6e90\nconst globalSources = sourceRows.filter(row => {\n  return (\n    guidelineDomains.includes(row[\"Guideline Domain\"]) &&\n    row[\"Region\"] === \"Global\" &&\n    isActive(row[\"Active\"])\n  );\n});\n\n// \u5730\u57df\u5185\u306ePriority\u3092\u512a\u5148\u3057\u3066\u4e26\u3079\u308b\nregionalSources.sort(\n  (a, b) => Number(a[\"Priority\"]) - Number(b[\"Priority\"])\n);\n\nglobalSources.sort(\n  (a, b) => Number(a[\"Priority\"]) - Number(b[\"Priority\"])\n);\n\n// \u5730\u57df\u60c5\u5831\u6e90\u3092\u5148\u306b\u3057\u3001Global\u3092\u5f8c\u306b\u8ffd\u52a0\nconst matchedSources = [\n  ...regionalSources,\n  ...globalSources,\n].map(row => ({\n  guideline_domain: row[\"Guideline Domain\"],\n  region: row[\"Region\"],\n  priority: Number(row[\"Priority\"]),\n  organization: row[\"Organization\"],\n  domain: row[\"Domain\"],\n  source_type: row[\"Source Type\"],\n}));\n\nreturn [\n  {\n    json: {\n      ...analysis,\n      source_mapping: {\n        matched_domains: guidelineDomains,\n        target_region: targetRegion,\n        regional_source_count: regionalSources.length,\n        global_source_count: globalSources.length,\n        matched_sources: matchedSources,\n      },\n    },\n  },\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "ed615f4d-4e8e-49ce-bec7-7536be18624e",
      "name": "Classify Evidence Candidates",
      "type": "n8n-nodes-base.code",
      "position": [
        -688,
        32
      ],
      "parameters": {
        "jsCode": "const input = $input.first().json;\nconst searchResults = input.search_results ?? [];\n\n/**\n * \u30bf\u30a4\u30c8\u30eb\u30fb\u8aac\u660e\u30fbURL\u304b\u3089\u767a\u884c\u5e74\u3092\u62bd\u51fa\u3057\u307e\u3059\u3002\n * \u8907\u6570\u306e\u5e74\u304c\u3042\u308b\u5834\u5408\u306f\u3001\u539f\u5247\u3068\u3057\u3066\u6700\u3082\u65b0\u3057\u3044\u5e74\u3092\u63a1\u7528\u3057\u307e\u3059\u3002\n */\nfunction extractPublicationYear(text) {\n  const currentYear = new Date().getFullYear();\n\n  const years = String(text ?? \"\")\n    .match(/\\b(19|20)\\d{2}\\b/g)\n    ?.map(Number)\n    .filter(\n      (year) =>\n        year >= 1900 &&\n        year <= currentYear + 1\n    );\n\n  return years?.length\n    ? Math.max(...years)\n    : null;\n}\n\n/**\n * \u6587\u66f8\u7a2e\u5225\u3092\u66ab\u5b9a\u5206\u985e\u3057\u307e\u3059\u3002\n *\n * \u5224\u5b9a\u9806\u304c\u91cd\u8981\u3067\u3059\u3002\n * 1. \u5b66\u8853\u8a8c\n * 2. \u58f0\u660e\n * 3. \u5305\u62ec\u7684\u30ac\u30a4\u30c9\u30e9\u30a4\u30f3\n * 4. \u5206\u91ce\u5225\u30ac\u30a4\u30c9\u30e9\u30a4\u30f3\n * 5. \u305d\u306e\u4ed6\u306e\u30a8\u30d3\u30c7\u30f3\u30b9\n */\nfunction classifyDocumentType(title, description, url) {\n  const titleText = String(title ?? \"\");\n  const fullText =\n    `${title ?? \"\"} ${description ?? \"\"} ${url ?? \"\"}`;\n\n  const normalizedTitle = titleText.toLowerCase();\n  const normalizedFullText = fullText.toLowerCase();\n\n  // \u5b66\u8853\u8a8c\u306e\u53f7\u30fb\u76ee\u6b21\u30da\u30fc\u30b8\n  if (\n    /\\bvol\\.?\\s*\\d+/i.test(titleText) ||\n    /\\bno\\.?\\s*\\d+/i.test(titleText) ||\n    /\\b(january|february|march|april|may|june|july|august|september|october|november|december)\\b/i.test(\n      titleText\n    )\n  ) {\n    return \"journal_issue\";\n  }\n\n  // \u58f0\u660e\u30fb\u7dca\u6025\u58f0\u660e\n  if (\n    /\u7dca\u6025\u58f0\u660e|\u58f0\u660e|statement|position statement|expert statement/i.test(\n      titleText\n    )\n  ) {\n    return \"statement\";\n  }\n\n  // \u5305\u62ec\u7684\u306a\u8a3a\u7642\u30ac\u30a4\u30c9\u30e9\u30a4\u30f3\n  if (\n    /\u8a3a\u7642\u30ac\u30a4\u30c9\u30e9\u30a4\u30f3/i.test(titleText) ||\n    /\u6cbb\u7642\u30ac\u30a4\u30c9\u30e9\u30a4\u30f3/i.test(titleText) ||\n    /\u7ba1\u7406\u30fb\u6cbb\u7642\u30ac\u30a4\u30c9\u30e9\u30a4\u30f3/i.test(titleText) ||\n    /clinical practice guideline/i.test(normalizedTitle) ||\n    /practice guideline/i.test(normalizedTitle)\n  ) {\n    return \"comprehensive_guideline\";\n  }\n\n  // \u7279\u5b9a\u6cbb\u7642\u30fb\u624b\u6280\u30fb\u9818\u57df\u306b\u95a2\u3059\u308b\u6307\u91dd\n  if (\n    /\u9069\u6b63\u6cbb\u7642\u6307\u91dd/i.test(titleText) ||\n    /\u9069\u6b63\u4f7f\u7528\u6307\u91dd/i.test(titleText) ||\n    /\u30ea\u30cf\u30d3\u30ea\u30c6\u30fc\u30b7\u30e7\u30f3\u306e\u6307\u91dd/i.test(titleText) ||\n    /\u6cbb\u7642\u6307\u91dd/i.test(titleText) ||\n    /\u8a3a\u7642\u6307\u91dd/i.test(titleText) ||\n    /guideline/i.test(normalizedTitle) ||\n    /guidance/i.test(normalizedTitle)\n  ) {\n    return \"focused_guideline\";\n  }\n\n  // \u30bf\u30a4\u30c8\u30eb\u4ee5\u5916\u306e\u8aac\u660e\u6587\u306a\u3069\u304b\u3089\u306e\u307f\u5224\u5b9a\u3067\u304d\u308b\u5019\u88dc\n  if (\n    /\u30ac\u30a4\u30c9\u30e9\u30a4\u30f3|\u6307\u91dd|guideline|guidance/i.test(\n      normalizedFullText\n    )\n  ) {\n    return \"guideline_candidate\";\n  }\n\n  if (\n    /consensus|\u30b3\u30f3\u30bb\u30f3\u30b5\u30b9/i.test(\n      normalizedFullText\n    )\n  ) {\n    return \"consensus_statement\";\n  }\n\n  if (\n    /systematic review|meta-analysis|meta analysis|\u30b7\u30b9\u30c6\u30de\u30c6\u30a3\u30c3\u30af\u30ec\u30d3\u30e5\u30fc|\u30e1\u30bf\u30a2\u30ca\u30ea\u30b7\u30b9/i.test(\n      normalizedFullText\n    )\n  ) {\n    return \"systematic_review\";\n  }\n\n  if (\n    /randomized|randomised|randomized controlled trial|\\brct\\b|\u7121\u4f5c\u70ba\u5316\u6bd4\u8f03\u8a66\u9a13/i.test(\n      normalizedFullText\n    )\n  ) {\n    return \"randomized_controlled_trial\";\n  }\n\n  if (\n    /cohort|observational|registry|case-control|\u89b3\u5bdf\u7814\u7a76|\u30b3\u30db\u30fc\u30c8/i.test(\n      normalizedFullText\n    )\n  ) {\n    return \"observational_study\";\n  }\n\n  return \"other\";\n}\n\n/**\n * \u691c\u7d22\u7d50\u679c\u3092\u5206\u985e\u3057\u307e\u3059\u3002\n */\nconst classifiedResults = searchResults.map((result) => {\n  const combinedText = [\n    result.title,\n    result.description,\n    result.url,\n  ].join(\" \");\n\n  const publicationYear =\n    extractPublicationYear(combinedText);\n\n  const documentType =\n    classifyDocumentType(\n      result.title,\n      result.description,\n      result.url\n    );\n\n  return {\n    ...result,\n    publication_year: publicationYear,\n    document_type: documentType,\n    classification_status: \"provisional\",\n    candidate_role: \"\",\n  };\n});\n\n/**\n * \u6587\u66f8\u7a2e\u5225\u306e\u512a\u5148\u9806\u4f4d\n *\n * \u6570\u5b57\u304c\u5c0f\u3055\u3044\u307b\u3069\u512a\u5148\u3055\u308c\u307e\u3059\u3002\n */\nconst documentTypePriority = {\n  comprehensive_guideline: 1,\n  focused_guideline: 2,\n  guideline_candidate: 3,\n  consensus_statement: 4,\n  statement: 5,\n  systematic_review: 6,\n  randomized_controlled_trial: 7,\n  observational_study: 8,\n  journal_issue: 9,\n  other: 10,\n};\n\n/**\n * Primary Guideline\u5019\u88dc\u3068\u3057\u3066\u6271\u3046\u6587\u66f8\u7a2e\u5225\n */\nconst guidelineTypes = [\n  \"comprehensive_guideline\",\n  \"focused_guideline\",\n  \"guideline_candidate\",\n];\n\n/**\n * Primary Guideline\u5019\u88dc\u3092\u4e26\u3079\u66ff\u3048\u307e\u3059\u3002\n *\n * \u512a\u5148\u9806\uff1a\n * 1. \u6587\u66f8\u7a2e\u5225\n * 2. \u767a\u884c\u5e74\n * 3. Brave\u691c\u7d22\u9806\u4f4d\n */\nconst guidelineCandidates = classifiedResults\n  .filter((result) =>\n    guidelineTypes.includes(result.document_type)\n  )\n  .sort((a, b) => {\n    const typeA =\n      documentTypePriority[a.document_type] ?? 99;\n\n    const typeB =\n      documentTypePriority[b.document_type] ?? 99;\n\n    if (typeA !== typeB) {\n      return typeA - typeB;\n    }\n\n    const yearA =\n      a.publication_year ?? 0;\n\n    const yearB =\n      b.publication_year ?? 0;\n\n    if (yearA !== yearB) {\n      return yearB - yearA;\n    }\n\n    const rankA =\n      Number.isFinite(Number(a.rank))\n        ? Number(a.rank)\n        : 999;\n\n    const rankB =\n      Number.isFinite(Number(b.rank))\n        ? Number(b.rank)\n        : 999;\n\n    return rankA - rankB;\n  });\n\n/**\n * Primary Guideline\u30921\u4ef6\u6c7a\u5b9a\u3057\u307e\u3059\u3002\n */\nif (guidelineCandidates.length > 0) {\n  guidelineCandidates[0].candidate_role =\n    \"current_guideline_candidate\";\n\n  for (\n    let i = 1;\n    i < guidelineCandidates.length;\n    i++\n  ) {\n    guidelineCandidates[i].candidate_role =\n      \"older_guideline_candidate\";\n  }\n}\n\n/**\n * \u30ac\u30a4\u30c9\u30e9\u30a4\u30f3\u4ee5\u5916\u306e\u5019\u88dc\u306b\u5f79\u5272\u3092\u4ed8\u3051\u307e\u3059\u3002\n */\nfor (const result of classifiedResults) {\n  if (result.candidate_role !== \"\") {\n    continue;\n  }\n\n  switch (result.document_type) {\n    case \"consensus_statement\":\n    case \"systematic_review\":\n    case \"randomized_controlled_trial\":\n    case \"observational_study\":\n      result.candidate_role =\n        \"supplementary_evidence_candidate\";\n      break;\n\n    case \"statement\":\n      result.candidate_role =\n        \"statement_candidate\";\n      break;\n\n    case \"journal_issue\":\n      result.candidate_role =\n        \"journal_issue_candidate\";\n      break;\n\n    case \"guideline_candidate\":\n      result.candidate_role =\n        \"guideline_candidate\";\n      break;\n\n    default:\n      result.candidate_role =\n        \"unclassified_candidate\";\n  }\n}\n\n/**\n * guidelineCandidates\u5074\u3067\u4ed8\u3051\u305f\u5f79\u5272\u3092\n * \u5143\u914d\u5217\u3078\u53cd\u6620\u3057\u307e\u3059\u3002\n */\nconst roleByUrl = new Map(\n  guidelineCandidates.map((result) => [\n    result.url,\n    result.candidate_role,\n  ])\n);\n\nconst finalResults = classifiedResults.map(\n  (result) => ({\n    ...result,\n    candidate_role:\n      roleByUrl.get(result.url) ??\n      result.candidate_role,\n  })\n);\n\n/**\n * \u9078\u629e\u3055\u308c\u305fPrimary Guideline\n */\nconst selectedGuideline =\n  finalResults.find(\n    (result) =>\n      result.candidate_role ===\n      \"current_guideline_candidate\"\n  ) ?? null;\n\n/**\n * \u96c6\u8a08\n */\nconst classificationSummary = {\n  total_candidates:\n    finalResults.length,\n\n  guideline_candidates:\n    finalResults.filter(\n      (result) =>\n        guidelineTypes.includes(\n          result.document_type\n        )\n    ).length,\n\n  comprehensive_guideline_candidates:\n    finalResults.filter(\n      (result) =>\n        result.document_type ===\n        \"comprehensive_guideline\"\n    ).length,\n\n  focused_guideline_candidates:\n    finalResults.filter(\n      (result) =>\n        result.document_type ===\n        \"focused_guideline\"\n    ).length,\n\n  statement_candidates:\n    finalResults.filter(\n      (result) =>\n        result.document_type ===\n        \"statement\"\n    ).length,\n\n  supplementary_candidates:\n    finalResults.filter(\n      (result) =>\n        result.candidate_role ===\n        \"supplementary_evidence_candidate\"\n    ).length,\n\n  journal_issue_candidates:\n    finalResults.filter(\n      (result) =>\n        result.document_type ===\n        \"journal_issue\"\n    ).length,\n\n  current_guideline_found:\n    Boolean(selectedGuideline),\n\n  selected_guideline_title:\n    selectedGuideline?.title ?? \"\",\n\n  selected_guideline_url:\n    selectedGuideline?.url ?? \"\",\n\n  selected_guideline_year:\n    selectedGuideline?.publication_year ?? null,\n\n  selected_guideline_type:\n    selectedGuideline?.document_type ?? \"\",\n};\n\nreturn [\n  {\n    json: {\n      ...input,\n      classification_summary:\n        classificationSummary,\n      classified_results:\n        finalResults,\n    },\n  },\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "c784acae-37e4-426c-a91f-03d1123de614",
      "name": "Google Gemini Chat Model1",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        -1104,
        640
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "4bd48d6d-af33-4cf4-9f99-f61ed2256b29",
      "name": "Extract Guideline Metadata",
      "type": "n8n-nodes-base.code",
      "position": [
        -80,
        32
      ],
      "parameters": {
        "jsCode": "// Fetch Guideline Metadata Page \u306e\u7d50\u679c\nconst response = $input.first().json;\n\n// \u524d\u6bb5\u306e\u30ac\u30a4\u30c9\u30e9\u30a4\u30f3\u9078\u629e\u7d50\u679c\u3068\u691c\u7d22\u60c5\u5831\nconst previous =\n  $(\"Select Current Guideline\").first().json;\n\nconst html = String(response.data ?? \"\");\n\n// HTML\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u3092\u6700\u4f4e\u9650\u30c7\u30b3\u30fc\u30c9\nfunction decodeHtml(text) {\n  return String(text ?? \"\")\n    .replace(/&nbsp;/gi, \" \")\n    .replace(/&amp;/gi, \"&\")\n    .replace(/&quot;/gi, '\"')\n    .replace(/&#39;/gi, \"'\")\n    .replace(/&lt;/gi, \"<\")\n    .replace(/&gt;/gi, \">\");\n}\n\n// HTML\u30bf\u30b0\u3092\u9664\u53bb\u3057\u3066\u53ef\u8996\u30c6\u30ad\u30b9\u30c8\u3078\u5909\u63db\nfunction stripHtml(value) {\n  return decodeHtml(\n    String(value ?? \"\")\n      .replace(/<script[\\s\\S]*?<\\/script>/gi, \" \")\n      .replace(/<style[\\s\\S]*?<\\/style>/gi, \" \")\n      .replace(/<!--[\\s\\S]*?-->/g, \" \")\n      .replace(/<br\\s*\\/?>/gi, \"\\n\")\n      .replace(/<\\/p>/gi, \"\\n\")\n      .replace(/<\\/h[1-6]>/gi, \"\\n\")\n      .replace(/<[^>]+>/g, \" \")\n  )\n    .replace(/[ \\t]+/g, \" \")\n    .replace(/\\n\\s+/g, \"\\n\")\n    .replace(/\\n{3,}/g, \"\\n\\n\")\n    .trim();\n}\n\nfunction firstMatch(pattern, source) {\n  const match = String(source ?? \"\").match(pattern);\n  return match?.[1] ? stripHtml(match[1]) : \"\";\n}\n\n// \u30da\u30fc\u30b8\u30bf\u30a4\u30c8\u30eb\nconst pageTitle =\n  firstMatch(/<title[^>]*>([\\s\\S]*?)<\\/title>/i, html) ||\n  previous.guideline_selection?.selected_title ||\n  \"\";\n\n// \u30ac\u30a4\u30c9\u30e9\u30a4\u30f3\u540d\nconst guidelineNameMatch = html.match(\n  /(\u9ad8\u8840\u5727\u7ba1\u7406\u30fb\u6cbb\u7642\u30ac\u30a4\u30c9\u30e9\u30a4\u30f3\\s*20\\d{2})/\n);\n\nconst guidelineName =\n  guidelineNameMatch?.[1] ??\n  previous.guideline_selection?.selected_title ??\n  \"\";\n\n// \u767a\u884c\u5e74\nconst years = [\n  ...stripHtml(html).matchAll(/\\b(19|20)\\d{2}\\b/g),\n]\n  .map((match) => Number(match[0]))\n  .filter(\n    (year) =>\n      year >= 1900 &&\n      year <= new Date().getFullYear() + 1\n  );\n\nconst publicationYear =\n  previous.guideline_selection?.publication_year ??\n  (years.length ? Math.max(...years) : null);\n\n// \u7ae0\u898b\u51fa\u3057\nconst chapterMatches = [\n  ...stripHtml(html).matchAll(/\u7b2c(\\d+)\u7ae0\\s*([^\\n]+)/g),\n];\n\nconst chapters = chapterMatches.map((match) => ({\n  chapter_number: Number(match[1]),\n  chapter_title: match[2].trim(),\n}));\n\n// \u30da\u30fc\u30b8\u5185\u30ea\u30f3\u30af\nconst linkMatches = [\n  ...html.matchAll(\n    /<a[^>]+href=[\"']([^\"']+)[\"'][^>]*>([\\s\\S]*?)<\\/a>/gi\n  ),\n];\n\nconst baseUrl =\n  previous.guideline_selection?.selected_url ?? \"\";\n\nconst links = linkMatches.map((match) => {\n  const href = match[1];\n\n  let absoluteUrl = href;\n\n  try {\n    absoluteUrl = new URL(href, baseUrl).toString();\n  } catch {\n    absoluteUrl = href;\n  }\n\n  return {\n    text: stripHtml(match[2]),\n    url: absoluteUrl,\n  };\n});\n\n// \u516c\u958b\u88dc\u52a9\u8cc7\u6599\u3089\u3057\u3044PDF\u3092\u62bd\u51fa\n// \u6b63\u8aa4\u8868\u30fbCOI\u30fb\u8ee2\u8f09\u7533\u8acb\u306a\u3069\u306f\u9664\u5916\nconst excludedPdfPatterns = [\n  /errata/i,\n  /\u6b63\u8aa4\u8868/i,\n  /coi/i,\n  /\u5229\u76ca\u76f8\u53cd/i,\n  /\u8ee2\u8f09/i,\n  /permission/i,\n];\n\nconst publicSupportingDocumentLinks = links.filter((link) => {\n  const combined = `${link.text} ${link.url}`;\n\n  const isPdf = /\\.pdf(?:$|\\?)/i.test(link.url);\n\n  const excluded = excludedPdfPatterns.some((pattern) =>\n    pattern.test(combined)\n  );\n\n  return isPdf && !excluded;\n});\n\n// \u516c\u958b\u30da\u30fc\u30b8\u306e\u53ef\u8996\u30c6\u30ad\u30b9\u30c8\nconst visibleText = stripHtml(html);\n\nreturn [\n  {\n    json: {\n      ...previous,\n\n      guideline_metadata: {\n        fetch_success: response.statusCode === 200,\n        status_code: response.statusCode ?? null,\n        content_type:\n          response.headers?.[\"content-type\"] ?? \"\",\n\n        page_title: pageTitle,\n        guideline_name: guidelineName,\n        organization:\n          \"Japanese Society of Hypertension\",\n        publication_year: publicationYear,\n\n        metadata_page_url: baseUrl,\n\n        chapters,\n        chapter_count: chapters.length,\n\n        public_supporting_documents_available:\n          publicSupportingDocumentLinks.length > 0,\n\n        public_supporting_document_links:\n          publicSupportingDocumentLinks,\n\n        access_status:\n          publicSupportingDocumentLinks.length > 0\n            ? \"supporting_documents_available\"\n            : \"metadata_or_purchase_page_only\",\n\n        public_page_text_excerpt:\n          visibleText.slice(0, 6000),\n      },\n    },\n  },\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "fbfeb6f6-3a2a-49ea-8662-b23ba76b704e",
      "name": "Integrate Clinical Evidence",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        -1104,
        464
      ],
      "parameters": {
        "text": "=System Prompt\n\nYou are an evidence integration assistant for a clinical guideline navigation workflow.\n\nYour task is to synthesize only the evidence, source metadata, and publicly available content explicitly provided in the input.\n\nTreat the structured publication_year and the selected guideline metadata as the source of truth for publication status.\n\nDo not describe a guideline as upcoming or scheduled for publication when its stated publication date is already in the past relative to the current date.\n\nDistinguish the primary guideline from related books, digest editions, explanatory booklets, and other future publications mentioned on the webpage.\n\nWhen supporting documents belong to an older guideline version, do not mark current-version supporting documents as available.\n\nWrite every explanatory field in the requested output language, including:\n- why_prioritized\n- relevance\n- evidence_basis\n- supporting_document_notes\n- evidence_gaps\n- limitations\n- what_to_verify_in_original_guideline\n- disclaimer\n\nOnly original source titles, official organization names, and URLs may remain in their original language.\n\nSelect only sections that directly match the clinical question and search intent.\n\nDo not broaden the scope to complications, secondary causes, special populations, or differential diagnoses unless they are explicitly included in the clinical question.\n\nDo not justify section relevance using general medical knowledge.\n\nWhen relevance is based only on a chapter title, state only that the chapter title appears directly related to the question.\n\nDo not state that full text is unavailable everywhere unless that has been explicitly confirmed.\n\nWhen no full-text link is present in the supplied material, state:\n\"No public full-text link was confirmed in the supplied official page.\"\n\nDo not generalize this into a claim that online access is impossible.\n\nThe supplied webpage may contain stale phrases such as \"scheduled for publication.\"\n\nCompare every dated statement with the current date.\n\nWhen a scheduled date is already in the past and actual publication status is not confirmed, describe the page statement as potentially outdated and do not state that publication is still upcoming.\n\nClearly distinguish:\n- current-version supporting documents\n- older-version supporting documents\n- documents announced but not yet available\n\nThe workflow may provide:\n- a clinical question\n- a target region\n- a requested output language\n- structured clinical concepts\n- a selected regional guideline\n- guideline metadata\n- publicly available chapter titles\n- public webpage excerpts\n- search-result titles and descriptions\n- public supporting-document links\n- additional trusted organizations identified by the workflow\n\nYou must clearly distinguish between:\n\n1. Information directly confirmed by the provided source content\n2. Information inferred only from titles, chapter names, URLs, or metadata\n3. Information that cannot be confirmed from the provided material\n\nYou must not:\n- claim to have reviewed a guideline full text unless the full text was actually provided\n- reconstruct or guess content from a paid, restricted, member-only, or unavailable guideline\n- invent clinical recommendations\n- invent recommendation strengths, classes, grades, or evidence levels\n- treat an older supporting document as part of the current guideline\n- state that a supporting document is currently available when its availability is unclear\n- use general medical knowledge as though it came from the supplied sources\n- make patient-specific diagnosis or treatment decisions\n- search the web\n- cite sources that were not provided in the input\n\nWhen the guideline full text is unavailable:\n- state this clearly\n- provide the guideline title, organization, publication year, and URL\n- identify only chapters that are plausibly relevant to the clinical question\n- include no more than 3 relevant sections\n- describe chapter relevance as potential or inferred unless the detailed content was provided\n- summarize only information that is directly available\n- clearly list what must be verified in the original guideline\n\nUse the target region's guideline as the primary reference.\n\nOther regional or international organizations may be listed as additional trusted sources only when they are explicitly provided by the workflow. Do not claim that their recommendations were reviewed unless supporting content was provided.\n\nDo not normalize or convert guideline-specific recommendation strengths or evidence levels.\n\nPreserve an original recommendation strength or evidence level exactly when explicitly provided.\n\nIf recommendation strength or evidence level is unavailable, use an empty string.\n\nThe answer summary must:\n- be written in the requested output language\n- directly address the clinical question\n- be concise\n- normally remain within 150 to 250 words\n- clearly state when the available evidence is insufficient to give a specific clinical answer\n\nKeep original guideline titles, organization names, and URLs unchanged.\n\nReturn exactly one JSON object matching the required schema.\n\nEvery field in the schema must be present.\n\nUse empty strings, empty arrays, false, or null when information cannot be confirmed.\n\nDo not return markdown.\n\nDo not add explanations before or after the JSON.\n\nReturn the required JSON object directly.\n\nThe top-level object must begin with the field \"clinical_question\".\n\nDo not include a top-level field named \"output\".\n\nDo not wrap the JSON in markdown code fences.\n\nDo not include ```json or ```.\n\nReturn raw JSON only.\n\n\nUser Prompt\n\nClinical question:\n{{ $(\"Analyze Clinical Question\").first().json.output.clinical_question_original }}\n\nTarget region:\n{{ $(\"Analyze Clinical Question\").first().json.output.target_region }}\n\nOutput language:\n{{ $(\"Analyze Clinical Question\").first().json.output.output_language }}\n\nClinical concept analysis:\n{{ JSON.stringify($(\"Analyze Clinical Question\").first().json.output.normalized_concepts) }}\n\nSearch intent:\n{{ JSON.stringify($(\"Analyze Clinical Question\").first().json.output.search_intent) }}\n\nSelected guideline:\n{{ JSON.stringify($json.guideline_selection) }}\n\nGuideline metadata:\n{{ JSON.stringify($json.guideline_metadata) }}\n\nSearch results:\n{{ JSON.stringify($json.search_results) }}\n\nClassified evidence candidates:\n{{ JSON.stringify($json.classified_results) }}\n\nTrusted source mapping:\n{{ JSON.stringify($(\"Match Trusted Sources\").first().json.source_mapping) }}\n\nCurrent date:\n{{ $now.toISODate() }}\n\nCreate an evidence-based clinical guidance report using only the supplied information.\n\nImportant requirements:\n\n- Answer the actual clinical question, but do not invent details that are not supported by the supplied evidence.\n- Include no more than 3 relevant guideline sections.\n- Do not list every chapter.\n- If a specific recommendation cannot be confirmed, keep the recommendations array empty.\n- Clearly explain that the detailed recommendation must be verified in the original guideline.\n- List additional trusted organizations, but do not claim that their guidance was reviewed unless source content was supplied.\n- Write the substantive content in the requested output language.\n\nReturn exactly this JSON structure:\n\n{\n  \"clinical_question\": \"\",\n  \"target_region\": \"\",\n  \"output_language\": \"\",\n\n  \"answer\": {\n    \"summary\": \"\",\n    \"confirmed_points\": [],\n    \"unconfirmed_or_unavailable_points\": []\n  },\n\n  \"primary_guideline\": {\n    \"title\": \"\",\n    \"organization\": \"\",\n    \"publication_year\": null,\n    \"url\": \"\",\n    \"access_status\": \"\",\n    \"full_text_reviewed\": false,\n    \"why_prioritized\": \"\"\n  },\n\n  \"relevant_sections\": [\n    {\n      \"chapter_number\": null,\n      \"chapter_title\": \"\",\n      \"relevance\": \"\",\n      \"evidence_basis\": \"\"\n    }\n  ],\n\n  \"publicly_available_information\": {\n    \"summary\": \"\",\n    \"supporting_documents_available\": false,\n    \"supporting_document_notes\": []\n  },\n\n  \"recommendations\": [\n    {\n      \"recommendation_summary\": \"\",\n      \"original_strength\": \"\",\n      \"original_evidence_level\": \"\",\n      \"applicability\": \"\",\n      \"source_support\": \"\",\n      \"confidence\": \"\"\n    }\n  ],\n\n  \"additional_trusted_sources\": [\n    {\n      \"organization\": \"\",\n      \"region\": \"\",\n      \"source_type\": \"\",\n      \"domain\": \"\",\n      \"content_reviewed\": false\n    }\n  ],\n\n  \"evidence_gaps\": [],\n\n  \"limitations\": [],\n\n  \"what_to_verify_in_original_guideline\": [],\n\n  \"references\": [\n    {\n      \"title\": \"\",\n      \"organization\": \"\",\n      \"year\": null,\n      \"url\": \"\",\n      \"source_type\": \"\"\n    }\n  ],\n\n  \"disclaimer\": \"\"\n}\n\n\nImportant:\n\nReturn the report object directly.\n\nCorrect top-level structure:\n{\n  \"clinical_question\": \"\",\n  \"target_region\": \"\",\n  ...\n}\n\nIncorrect top-level structure:\n{\n  \"output\": {\n    \"clinical_question\": \"\"\n  }\n}\n\nDo not use an \"output\" wrapper.\nDo not use markdown code fences.",
        "batching": {},
        "promptType": "define"
      },
      "typeVersion": 1.7
    },
    {
      "id": "3ffcd6e1-ce6e-4978-8bac-6fefae543cec",
      "name": "Format Clinical Report",
      "type": "n8n-nodes-base.code",
      "position": [
        -320,
        608
      ],
      "parameters": {
        "jsCode": "const report = $input.first().json.output;\n\nfunction section(title, body = \"\") {\n  return `\n==================================================\n${title}\n==================================================\n\n${body}\n`;\n}\n\nfunction bullet(items) {\n  if (!items || items.length === 0) {\n    return \"None\";\n  }\n\n  return items.map(i => `\u2022 ${i}`).join(\"\\n\");\n}\n\nconst relevantSections =\n(report.relevant_sections ?? [])\n.map(s =>\n\n`Chapter ${s.chapter_number}\n\n${s.chapter_title}\n\nReason:\n${s.relevance}\n\nEvidence Basis:\n${s.evidence_basis}\n\n`\n)\n.join(\"\\n----------------------------------------\\n\");\n\nconst references =\n(report.references ?? [])\n.map(r =>\n\n`${r.title}\n\nOrganization:\n${r.organization}\n\nYear:\n${r.year}\n\nURL:\n${r.url}\n\nType:\n${r.source_type}\n`\n)\n.join(\"\\n----------------------------------------\\n\");\n\nconst additionalSources =\n(report.additional_trusted_sources ?? [])\n.map(r =>\n\n`${r.organization}\n\nRegion:\n${r.region}\n\nType:\n${r.source_type}\n\nReviewed:\n${r.content_reviewed}\n`\n)\n.join(\"\\n----------------------------------------\\n\");\n\nconst body = `\n\n==================================================\nEvidence-Based Clinical Guidance Report\n==================================================\n\nGenerated by AI Medical Guideline Assistant\n\nGeneration Date:\n${new Date().toISOString().slice(0,10)}\n\nTarget Region:\n${report.target_region}\n\nOutput Language:\n${report.output_language}\n\n${section(\n\"Clinical Question\",\nreport.clinical_question\n)}\n\n${section(\n\"Clinical Summary\",\nreport.answer.summary\n)}\n\n${section(\n\"Confirmed Information\",\nbullet(report.answer.confirmed_points)\n)}\n\n${section(\n\"Information Not Confirmed\",\nbullet(report.answer.unconfirmed_or_unavailable_points)\n)}\n\n${section(\n\"Primary Guideline\",\n\n`Title:\n${report.primary_guideline.title}\n\nOrganization:\n${report.primary_guideline.organization}\n\nPublication Year:\n${report.primary_guideline.publication_year}\n\nURL:\n${report.primary_guideline.url}\n\nAccess Status:\n${report.primary_guideline.access_status}\n\nFull Text Reviewed:\n${report.primary_guideline.full_text_reviewed}\n\nReason Selected:\n${report.primary_guideline.why_prioritized}`\n)}\n\n${section(\n\"Relevant Guideline Sections\",\nrelevantSections\n)}\n\n${section(\n\"Evidence Gaps\",\nbullet(report.evidence_gaps)\n)}\n\n${section(\n\"Limitations\",\nbullet(report.limitations)\n)}\n\n${section(\n\"Items to Verify in Original Guideline\",\nbullet(report.what_to_verify_in_original_guideline)\n)}\n\n${section(\n\"Additional Trusted Sources\",\nadditionalSources\n)}\n\n${section(\n\"References\",\nreferences\n)}\n\n${section(\n\"Disclaimer\",\nreport.disclaimer\n)}\n`;\n\nconst documentTitle =\n`Clinical Guidance Report - ${report.target_region} - ${new Date().toISOString().slice(0,10)}`;\n\nreturn [\n{\njson:{\ndocument_title:documentTitle,\ndocument_body:body\n}\n}\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "c7038e4a-4227-45fa-9503-9d6a32088b57",
      "name": "Create Clinical Guidance Report",
      "type": "n8n-nodes-base.googleDocs",
      "position": [
        -112,
        608
      ],
      "parameters": {
        "title": "={{ $json.document_title }}",
        "folderId": "1G0CPhAu3TuUBE_o4D5Ml1i-V6QZ6787I"
      },
      "credentials": {
        "googleDocsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "a4cc563a-c181-48e9-86b7-ce36b01ea93c",
      "name": "Mark Request as Completed",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        320,
        608
      ],
      "parameters": {
        "columns": {
          "value": {
            "Status": "completed",
            "Report URL": "={{ \"https://docs.google.com/document/d/\" + $json.documentId + \"/edit\" }}",
            "Request ID": "={{ $(\"Get Pending Guideline Request\").first().json[\"Request ID\"] }}",
            "Completed at": "={{ $now.toISO() }}",
            "Error Message": "={{ \"\" }}",
            "Target Region": "={{ $(\"Get Pending Guideline Request\").first().json[\"Target Region\"] }}",
            "Output Language": "={{ $(\"Get Pending Guideline Request\").first().json[\"Output Language\"] }}",
            "Clinical Question": "={{ $(\"Get Pending Guideline Request\").first().json[\"Clinical Question\"] }}"
          },
          "schema": [
            {
              "id": "Request ID",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Request ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Clinical Question",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Clinical Question",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Target Region",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Target Region",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Output Language",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Output Language",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Completed at",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Completed at",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Report URL",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Report URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Error Message",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Error Message",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Request ID"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1hwvmK3iqEV_8ka9QL4t3RTzIqAHRzCQCjMN0tVSzzsc/edit#gid=0",
          "cachedResultName": "Requests"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1hwvmK3iqEV_8ka9QL4t3RTzIqAHRzCQCjMN0tVSzzsc",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1hwvmK3iqEV_8ka9QL4t3RTzIqAHRzCQCjMN0tVSzzsc/edit?usp=drivesdk",
          "cachedResultName": "AI Medical Guideline Assistant"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "3d689424-ae3d-431b-bd55-7a43632ec920",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -1280,
        -416
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours"
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "3874470f-4207-4906-b05a-574d88a5ee69",
      "name": "Search Trusted Sources",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1088,
        32
      ],
      "parameters": {
        "url": "https://api.search.brave.com/res/v1/web/search",
        "options": {},
        "sendQuery": true,
        "sendHeaders": true,
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "queryParameters": {
          "parameters": [
            {
              "name": "q",
              "value": "={{\n  \"site:\"\n  + $json.source_mapping.matched_sources[0].domain\n  + \" \"\n  + $json.clinical_question_english\n  + \" guideline\"\n}}"
            },
            {
              "name": "count",
              "value": "5"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "088ec7a4-f201-48e2-9670-80c962a868cc",
      "name": "Normalize Search Results",
      "type": "n8n-nodes-base.code",
      "position": [
        -880,
        32
      ],
      "parameters": {
        "jsCode": "const MAX_RESULTS = 5;\n\n// Search Brave\u30ce\u30fc\u30c9\u304b\u3089\u53d7\u3051\u53d6\u3063\u305fJSON\nconst input = $input.first().json;\n\n// Brave\u691c\u7d22\u7d50\u679c\u306e\u5148\u982d5\u4ef6\nconst results = (input.web?.results ?? []).slice(0, MAX_RESULTS);\n\n// \u691c\u7d22\u60c5\u5831\nconst query = input.query?.original ?? \"\";\nconst restrictedDomains =\n  input.query?.search_operators?.sites ?? [];\n\n// \u660e\u3089\u304b\u306a\u30ce\u30a4\u30ba\nconst excludedPatterns = [\n  /guide to authors/i,\n  /instructions for authors/i,\n  /\u6295\u7a3f\u898f\u5b9a/i,\n  /\u5b66\u8853\u59d4\u54e1\u4f1a/i,\n  /\u4e00\u822c\u5411\u3051/i,\n  /\u308f\u304b\u308a\u3084\u3059\u3044\u89e3\u8aac/i,\n];\n\nconst normalizedResults = results\n  .map((result, index) => {\n    const title = result.title ?? \"\";\n    const description = result.description ?? \"\";\n    const url = result.url ?? \"\";\n\n    // Brave\u304c\u8fd4\u3059hostname\u3092\u512a\u5148\n    let hostname =\n      result.meta_url?.hostname ??\n      result.profile?.long_name ??\n      \"\";\n\n    hostname = String(hostname)\n      .trim()\n      .toLowerCase()\n      .replace(/^www\\./, \"\");\n\n    const isPdf =\n      result.content_type === \"pdf\" ||\n      url.toLowerCase().endsWith(\".pdf\");\n\n    const excluded = excludedPatterns.some(\n      (pattern) =>\n        pattern.test(title) ||\n        pattern.test(url)\n    );\n\n    return {\n      rank: index + 1,\n      title,\n      url,\n      description,\n      hostname,\n      language: result.language ?? \"\",\n      content_type: isPdf ? \"pdf\" : \"html\",\n      excluded,\n      exclusion_reason: excluded\n        ? \"Likely non-guideline or non-clinical content\"\n        : \"\",\n    };\n  })\n  .filter((result) => {\n    const domainAllowed =\n      restrictedDomains.length === 0 ||\n      restrictedDomains.some((domain) => {\n        const normalizedDomain = String(domain)\n          .trim()\n          .toLowerCase()\n          .replace(/^www\\./, \"\");\n\n        return (\n          result.hostname === normalizedDomain ||\n          result.hostname.endsWith(`.${normalizedDomain}`)\n        );\n      });\n\n    return domainAllowed && !result.excluded;\n  });\n\nreturn [\n  {\n    json: {\n      search_query: query,\n      restricted_domains: restrictedDomains,\n      total_raw_results: input.web?.results?.length ?? 0,\n      analyzed_result_count: results.length,\n      total_filtered_results: normalizedResults.length,\n      search_results: normalizedResults,\n    },\n  },\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "e3fcf744-6e17-4b45-b555-0dd819c6725b",
      "name": "Select Current Guideline",
      "type": "n8n-nodes-base.code",
      "position": [
        -480,
        32
      ],
      "parameters": {
        "jsCode": "const input = $input.first().json;\n\nconst candidates = input.classified_results ?? [];\n\nconst currentGuideline = candidates.find(\n  (item) =>\n    item.candidate_role === \"current_guideline_candidate\"\n);\n\nif (!currentGuideline) {\n  return [\n    {\n      json: {\n        ...input,\n        guideline_selection: {\n          found: false,\n          reason: \"No current guideline candidate was found.\",\n          selected_title: \"\",\n          selected_url: \"\",\n          publication_year: null,\n          content_type: \"\",\n          hostname: \"\",\n          document_type: \"\",\n          search_rank: null,\n        },\n      },\n    },\n  ];\n}\n\nreturn [\n  {\n    json: {\n      ...input,\n      guideline_selection: {\n        found: true,\n        selected_title: currentGuideline.title ?? \"\",\n        selected_url: currentGuideline.url ?? \"\",\n        publication_year:\n          currentGuideline.publication_year ?? null,\n        content_type:\n          currentGuideline.content_type ?? \"\",\n        hostname:\n          currentGuideline.hostname ?? \"\",\n        document_type:\n          currentGuideline.document_type ?? \"\",\n        search_rank:\n          currentGuideline.rank ?? null,\n      },\n    },\n  },\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "2c186a42-8633-4724-9838-0f8e40d8379d",
      "name": "Fetch Guideline Metadata",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -272,
        32
      ],
      "parameters": {
        "url": "={{ $json.guideline_selection.selected_url }}",
        "options": {
          "response": {
            "response": {
              "neverError": true,
              "fullResponse": true,
              "responseFormat": "text"
            }
          },
          "allowUnauthorizedCerts": true
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "f70d2898-d762-4d50-bc17-00cd0fb8e030",
      "name": "Update Clinical Guidance Report",
      "type": "n8n-nodes-base.googleDocs",
      "position": [
        112,
        608
      ],
      "parameters": {
        "actionsUi": {
          "actionFields": [
            {
              "text": "={{ $('Format Clinical Report').item.json.document_body }}",
              "action": "insert"
            }
          ]
        },
        "operation": "update",
        "documentURL": "={{ $json.id }}"
      },
      "credentials": {
        "googleDocsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "d40bcfb2-0b71-4c64-b9dd-b3040f4ab274",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1472,
        -768
      ],
      "parameters": {
        "color": 5,
        "width": 1040,
        "height": 576,
        "content": "## \u2460 Clinical Question Intake\n\n**Reads one pending request from Google Sheets.\n\nThe clinical question is normalized into\nstructured medical concepts using AI.\n\nOutput:\n\u2022 Clinical concepts\n\u2022 Question type\n\u2022 Guideline domains\n\u2022 Target region"
      },
      "typeVersion": 1
    },
    {
      "id": "fbd9f6eb-a905-4346-b080-1a596dd6cd17",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -384,
        -768
      ],
      "parameters": {
        "color": 5,
        "width": 576,
        "height": 576,
        "content": "## \u2461 Trusted Source Selection \n\nMaps the normalized question to trusted\nmedical organizations.\n\nPriority is determined by region.\n\nExample:\n\nJapan\n\u2193\nJapanese Society\n\u2193\nGovernment\n\u2193\nWHO"
      },
      "typeVersion": 1
    },
    {
      "id": "36a14704-b2a9-4508-bb6f-ffe01ba35373",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1472,
        -160
      ],
      "parameters": {
        "color": 5,
        "width": 1664,
        "height": 416,
        "content": "## \u2462 Guideline Discovery\n\nSearches trusted medical organizations.\n\nIdentifies the latest guideline.\n\nDoes NOT answer the clinical question.\n\nGoal:\n\nFind the most relevant guideline.\n\nSSL verification is disabled for this request \nbecause some official society sites use \ncertificate configurations \nthat n8n Cloud may reject."
      },
      "typeVersion": 1
    },
    {
      "id": "476746ed-3a68-4ba7-9e35-90b195d9a1cb",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1472,
        288
      ],
      "parameters": {
        "color": 5,
        "width": 896,
        "height": 560,
        "content": "## \u2463 Evidence Integration\n\nIntegrates only verified public evidence.\n\nThe AI must NOT invent information.\n\nIf the original guideline is unavailable,\nthe report clearly identifies:\n\n\u2022 confirmed facts\n\n\u2022 unavailable information\n\n\u2022 items requiring verification"
      },
      "typeVersion": 1
    },
    {
      "id": "be524da9-7c04-4129-946f-8a8fdfa034cc",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -528,
        288
      ],
      "parameters": {
        "color": 5,
        "width": 1104,
        "height": 560,
        "content": "## \u2464 Clinical Report Generation \n\nCreates a structured Google Docs report.\n\nUpdates workflow status in\nGoogle Sheets.\n\nProduces a report that clearly separates\n\n\u2022 confirmed evidence\n\n\u2022 unavailable information\n\n\u2022 verification items"
      },
      "typeVersion": 1
    },
    {
      "id": "c8d5e316-a680-420c-8158-4ca0edb10c02",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2240,
        -768
      ],
      "parameters": {
        "color": 3,
        "width": 720,
        "height": 464,
        "content": "# AI Medical Guideline Assistant \n\n## Purpose\n\nAnswer clinical questions using trusted\nmedical guidelines and publicly\navailable evidence.\n\nThe workflow distinguishes verified\ninformation from unavailable information\nand generates a structured clinical report."
      },
      "typeVersion": 1
    },
    {
      "id": "125a0345-2ecd-40b5-b961-ed9a297d5115",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2240,
        -272
      ],
      "parameters": {
        "color": 6,
        "width": 720,
        "height": 480,
        "content": "## Design Principle\n\nEvidence is prioritized.\n\nThe workflow never fabricates clinical\nrecommendations.\n\nWhen evidence cannot be verified,\nthe report explicitly identifies\nwhat was confirmed,\nwhat was unavailable,\nand what should be verified\nin the original guideline.\n\nWhen multiple official documents are found, the workflow selects one primary candidate using document type, publication year, and search rank. Users should verify that the selected document is the most clinically relevant source for the question."
      },
      "typeVersion": 1
    },
    {
      "id": "a892a8b8-ced4-4420-8ef1-a1f6594c87d7",
      "name": "Parse Clinical Guidance JSON",
      "type": "n8n-nodes-base.code",
      "position": [
        -768,
        608
      ],
      "parameters": {
        "jsCode": "const input = $input.first().json;\n\n// Basic LLM Chain\u306e\u51fa\u529b\u5f62\u5f0f\u306b\u5bfe\u5fdc\nlet raw =\n  input.text ??\n  input.response?.generations?.[0]?.[0]?.text ??\n  \"\";\n\nraw = String(raw).trim();\n\n// Markdown\u30b3\u30fc\u30c9\u30d5\u30a7\u30f3\u30b9\u3092\u9664\u53bb\nraw = raw\n  .replace(/^```json\\s*/i, \"\")\n  .replace(/^```\\s*/i, \"\")\n  .replace(/\\s*```$/i, \"\")\n  .trim();\n\nlet parsed;\n\ntry {\n  parsed = JSON.parse(raw);\n} catch (error) {\n  throw new Error(\n    `Clinical guidance JSON parsing failed: ${error.message}`\n  );\n}\n\n// output\u30e9\u30c3\u30d1\u30fc\u304c\u4ed8\u3044\u305f\u5834\u5408\u306b\u3082\u5bfe\u5fdc\nif (\n  parsed &&\n  typeof parsed === \"object\" &&\n  parsed.output &&\n  typeof parsed.output === \"object\"\n) {\n  parsed = parsed.output;\n}\n\n// \u6700\u4f4e\u9650\u306e\u5fc5\u9808\u9805\u76ee\u3092\u78ba\u8a8d\nconst requiredFields = [\n  \"clinical_question\",\n  \"target_region\",\n  \"output_language\",\n  \"answer\",\n  \"primary_guideline\",\n  \"relevant_sections\",\n  \"publicly_available_information\",\n  \"recommendations\",\n  \"additional_trusted_sources\",\n  \"evidence_gaps\",\n  \"limitations\",\n  \"what_to_verify_in_original_guideline\",\n  \"references\",\n  \"disclaimer\",\n];\n\nconst missingFields = requiredFields.filter(\n  (field) => !(field in parsed)\n);\n\nif (missingFields.length > 0) {\n  throw new Error(\n    `Clinical guidance JSON is missing required fields: ${missingFields.join(\", \")}`\n  );\n}\n\nreturn [\n  {\n    json: {\n      output: parsed,\n    },\n  },\n];"
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "3efc242b-61e1-4400-9377-cf1a7b65ceea",
  "connections": {
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Get Pending Guideline Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Trusted Sources": {
      "main": [
        [
          {
            "node": "Match Trusted Sources",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Match Trusted Sources": {
      "main": [
        [
          {
            "node": "Search Trusted Sources",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Clinical Report": {
      "main": [
        [
          {
            "node": "Create Clinical Guidance Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Trusted Sources": {
      "main": [
        [
          {
            "node": "Normalize Search Results",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Fetch Guideline Metadata": {
      "main": [
        [
          {
            "node": "Extract Guideline Metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Analyze Clinical Question",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Search Results": {
      "main": [
        [
          {
            "node": "Classify Evidence Candidates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select Current Guideline": {
      "main": [
        [
          {
            "node": "Fetch Guideline Metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Analyze Clinical Question",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Clinical Question": {
      "main": [
        [
          {
            "node": "Get Trusted Sources",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "Integrate Clinical Evidence",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Extract Guideline Metadata": {
      "main": [
        [
          {
            "node": "Integrate Clinical Evidence",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Request as Processing": {
      "main": [
        [
          {
            "node": "Analyze Clinical Question",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Integrate Clinical Evidence": {
      "main": [
        [
          {
            "node": "Parse Clinical Guidance JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Evidence Candidates": {
      "main": [
        [
          {
            "node": "Select Current Guideline",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Clinical Guidance JSON": {
      "main": [
        [
          {
            "node": "Format Clinical Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Pending Guideline Request": {
      "main": [
        [
          {
            "node": "Mark Request as Processing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Clinical Guidance Report": {
      "main": [
        [
          {
            "node": "Update Clinical Guidance Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Clinical Guidance Report": {
      "main": [
        [
          {
            "node": "Mark Request as Completed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}