{
  "nodes": [
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "2e1993fe-ae4d-46e0-be9f-6b02a18cc610",
              "name": "topics",
              "value": "AI, n8n",
              "type": "string"
            },
            {
              "id": "2c6e2dad-d54a-4387-9bf5-4751037f99d9",
              "name": "language",
              "value": "English",
              "type": "string"
            },
            {
              "id": "d1b520a6-2be1-4ca4-ac56-bd202cd91db1",
              "name": "minDaysBetween",
              "value": 0,
              "type": "number"
            },
            {
              "id": "f77efe55-ab66-465d-aa4e-34e63e8fab17",
              "name": "maxDaysBetween",
              "value": 5,
              "type": "number"
            },
            {
              "id": "eaf8e69e-3fe0-412e-a8b1-919a2ba76a47",
              "name": "model_provider",
              "value": "local_ministral",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": true,
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1296,
        160
      ],
      "id": "5e44fad8-70b4-43fc-9795-9b8587c61757",
      "name": "Set topics and language"
    },
    {
      "parameters": {
        "jsCode": "return $input.first().json.topics\n  .split(',')\n  .map(t => t.trim())\n  .filter(t => t)\n  .map(topic => ({ json: { topic } }));"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1072,
        736
      ],
      "id": "46665668-f2a1-4b83-af4b-62984620e05c",
      "name": "Build topic queries"
    },
    {
      "parameters": {
        "url": "https://serpapi.com/search.html",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpQueryAuth",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "engine",
              "value": "duckduckgo_news"
            },
            {
              "name": "q",
              "value": "={{ $json.topic }}"
            },
            {
              "name": "df",
              "value": "d"
            },
            {
              "name": "m",
              "value": "25"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1296,
        736
      ],
      "id": "1e2c70fd-e112-4e4b-8832-e3bbfdaa5ad7",
      "name": "Fetch news from SerpAPI (DuckDuckGo News)",
      "credentials": {
        "httpQueryAuth": {
          "id": "pzXxBOzu80uGcGUA",
          "name": "SerpAPI"
        }
      }
    },
    {
      "parameters": {
        "fieldToSplitOut": "results",
        "options": {}
      },
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        1520,
        736
      ],
      "id": "f6a55b74-77fa-4602-8a3c-6ad63f885f7d",
      "name": "Split SerpAPI results into articles"
    },
    {
      "parameters": {
        "operation": "upsert",
        "dataTableId": {
          "__rl": true,
          "value": "upN3829o5fkpmS67",
          "mode": "list",
          "cachedResultName": "News",
          "cachedResultUrl": "/projects/2mgqFGS1co9qm0Rg/datatables/upN3829o5fkpmS67"
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyName": "title",
              "keyValue": "={{ $json.title }}"
            },
            {
              "keyName": "url",
              "keyValue": "={{ $json.url }}"
            },
            {
              "keyName": "model_provider",
              "keyValue": "={{ $('Start').item.json.model_provider }}"
            },
            {
              "keyName": "topic",
              "keyValue": "={{ $('Build topic queries').item.json.topic }}"
            }
          ]
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "title": "={{ $json.title }}",
            "source": "={{ $json.source }}",
            "url": "={{ $json.url }}",
            "excerpt": "={{ $json.excerpt }}",
            "date": "={{ DateTime.fromSeconds(Number($json.date), {zone: 'utc'}).toISO() }}",
            "topic": "={{ $('Build topic queries').item.json.topic }}",
            "model_provider": "={{ $('Start').item.json.model_provider }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "title",
              "displayName": "title",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "source",
              "displayName": "source",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "url",
              "displayName": "url",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "excerpt",
              "displayName": "excerpt",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "date",
              "displayName": "date",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "relevance_score",
              "displayName": "relevance_score",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "readOnly": false,
              "removed": true
            },
            {
              "id": "novelty_score",
              "displayName": "novelty_score",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "readOnly": false,
              "removed": true
            },
            {
              "id": "credibility_score",
              "displayName": "credibility_score",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "readOnly": false,
              "removed": true
            },
            {
              "id": "impact_score",
              "displayName": "impact_score",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "readOnly": false,
              "removed": true
            },
            {
              "id": "score_reason",
              "displayName": "score_reason",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": true
            },
            {
              "id": "evaluatedAt",
              "displayName": "evaluatedAt",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "readOnly": false,
              "removed": true
            },
            {
              "id": "average_score",
              "displayName": "average_score",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "readOnly": false,
              "removed": true
            },
            {
              "id": "model_provider",
              "displayName": "model_provider",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "topic",
              "displayName": "topic",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {
          "dryRun": false
        }
      },
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1,
      "position": [
        1744,
        736
      ],
      "id": "654fa1bf-e32e-4cb3-bdcd-a0a628882eae",
      "name": "Upsert articles into News table"
    },
    {
      "parameters": {
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "value": "4ucNIYhHb6nIQbRD",
          "mode": "list",
          "cachedResultName": "Newsletters",
          "cachedResultUrl": "/projects/2mgqFGS1co9qm0Rg/datatables/4ucNIYhHb6nIQbRD"
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyName": "model_provider",
              "keyValue": "={{ $json.model_provider }}"
            },
            {
              "keyName": "topic",
              "keyValue": "={{ $json.topics }}"
            }
          ]
        },
        "returnAll": true
      },
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1,
      "position": [
        1040,
        1264
      ],
      "id": "12cee819-74a4-4430-bc9a-03b5bfda76e7",
      "name": "Get previous newsletters",
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "sortFieldsUi": {
          "sortField": [
            {
              "fieldName": "createdAt",
              "order": "descending"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.sort",
      "typeVersion": 1,
      "position": [
        1328,
        1264
      ],
      "id": "770eb4fd-4e32-47d5-8287-07199bc1418a",
      "name": "Sort newsletters by newest first",
      "alwaysOutputData": true,
      "notesInFlow": false,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.limit",
      "typeVersion": 1,
      "position": [
        1616,
        1312
      ],
      "id": "e4e799a5-076f-4f38-b8d5-dd9ced11f14a",
      "name": "Get most recent newsletter"
    },
    {
      "parameters": {
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "newsletters",
        "options": {}
      },
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1,
      "position": [
        2176,
        96
      ],
      "id": "0517d13e-952a-4bea-90c8-18cec83fa482",
      "name": "Aggregate previous newsletters into list"
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineAll",
        "options": {
          "clashHandling": {
            "values": {
              "resolveClash": "addSuffix"
            }
          }
        }
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        1024,
        1504
      ],
      "id": "77610a3f-81fe-4141-8d38-d6916d9315ee",
      "name": "Combine articles with last newsletter metadata"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "daf65a43-e643-4f64-a4ac-bc562418f4c8",
              "leftValue": "={{ $json.date_2 }}",
              "rightValue": "={{ $json.createdAt_1 ? $json.createdAt_1 : DateTime.fromISO('2024-01-01T00:00:00.000Z') }}",
              "operator": {
                "type": "dateTime",
                "operation": "after"
              }
            }
          ],
          "combinator": "or"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.2,
      "position": [
        1248,
        1504
      ],
      "id": "8029ad95-b5c2-4819-b7d8-15c278436e5b",
      "name": "Filter articles newer than last newsletter"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "4efef626-77be-4e29-8aa5-f9520cfa3127",
              "leftValue": "={{ $('Get most recent newsletter').item.json.createdAt ? $('Get most recent newsletter').item.json.createdAt: '2024-01-01T00:00:00.000Z' }}",
              "rightValue": "={{ $now.minus({ days: $('Start').item.json.minDaysBetween }) }}",
              "operator": {
                "type": "dateTime",
                "operation": "before"
              }
            },
            {
              "id": "6c5410f0-2e2e-4212-9779-bf3a3a4fb009",
              "leftValue": "",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "equals",
                "name": "filter.operator.equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1696,
        1504
      ],
      "id": "eabec80e-fc4a-4dc0-923c-bb852ba09c7f",
      "name": "Stop if last newsletter is too recent"
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineAll",
        "options": {}
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        2320,
        32
      ],
      "id": "e1056ff8-1b10-488d-ba2d-3523f98cd56d",
      "name": "Combine candidate articles with past newsletters"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "717c70de-dfc0-4ed5-86d6-3d1a612d0694",
              "leftValue": "={{ $json.output.decision }}",
              "rightValue": "YES",
              "operator": {
                "type": "string",
                "operation": "equals",
                "name": "filter.operator.equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2992,
        544
      ],
      "id": "0af9e62c-d0f9-4525-9f7d-ab6996fcbb0c",
      "name": "If AI decided to send newsletter"
    },
    {
      "parameters": {
        "fieldToSplitOut": "output.articles",
        "options": {}
      },
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        1952,
        1024
      ],
      "id": "57bd0ee7-3c5a-491b-a2d1-778796a8c3e6",
      "name": "Split selected articles for enrichment"
    },
    {
      "parameters": {
        "fieldsToAggregate": {
          "fieldToAggregate": [
            {
              "fieldToAggregate": "output"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1,
      "position": [
        1952,
        1520
      ],
      "id": "f43c1cc3-549f-496a-99c4-2e24fd78ae4b",
      "name": "Aggregate enriched articles"
    },
    {
      "parameters": {
        "dataTableId": {
          "__rl": true,
          "value": "4ucNIYhHb6nIQbRD",
          "mode": "list",
          "cachedResultName": "Newsletters",
          "cachedResultUrl": "/projects/2mgqFGS1co9qm0Rg/datatables/4ucNIYhHb6nIQbRD"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "content": "={{ $json.text }}",
            "model_provider": "={{ $('Start').item.json.model_provider }}",
            "topic": "={{ $('Start').item.json.topics }}"
          },
          "matchingColumns": [
            "content"
          ],
          "schema": [
            {
              "id": "content",
              "displayName": "content",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "topic",
              "displayName": "topic",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "model_provider",
              "displayName": "model_provider",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1,
      "position": [
        2832,
        1520
      ],
      "id": "8aeed8a6-f48d-4d69-a329-74d700abfd6e",
      "name": "Insert newsletter content into Newsletters table"
    },
    {
      "parameters": {
        "chatId": "89066090",
        "text": "={{ $json.content }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        3008,
        1520
      ],
      "id": "f41d106c-54d3-4030-aca9-f7346623a8c1",
      "name": "Send newsletter to Telegram",
      "webhookId": "ad9f37b6-fa2f-494f-a202-0989cafa1c50",
      "credentials": {
        "telegramApi": {
          "id": "9IRaKBYdepPJTo6P",
          "name": "Telegram account"
        }
      }
    },
    {
      "parameters": {
        "content": "## Schedule & Configuration\n\nTriggers the workflow and sets global parameters.\n\n- Scheduled runs: Schedule Trigger fires twice daily (09:00 and 17:00) and passes into Set topics and language with fixed defaults (topics, language, min/max days, local model).\n- Eval runs: When fetching a dataset row pulls up to 5 evaluation rows and Set remaining parameters overwrites model/cadence fields for cloud GPT-5.1 runs. Topics and language are taken from the Evaluation dataset rows and preserved downstream\n- Unified entrypoint: Both paths end at Start, so the rest of the workflow can run identically regardless of whether it was launched on a schedule or as an evaluation.",
        "height": 608,
        "width": 896,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        992,
        -128
      ],
      "id": "903c7139-0a09-4248-a882-f31d1d5713e4",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 0 9,17 * * *"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        1072,
        160
      ],
      "id": "ce229d57-7ef8-4dbc-be78-e76571c7a3c9",
      "name": "Schedule Trigger"
    },
    {
      "parameters": {
        "operation": "update",
        "dataTableId": {
          "__rl": true,
          "value": "upN3829o5fkpmS67",
          "mode": "list",
          "cachedResultName": "News",
          "cachedResultUrl": "/projects/2mgqFGS1co9qm0Rg/datatables/upN3829o5fkpmS67"
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyValue": "={{ $json.id}}"
            }
          ]
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "relevance_score": "={{ $json.relevance }}",
            "credibility_score": "={{ $json.credibility }}",
            "impact_score": "={{ $json.impact }}",
            "average_score": "={{ $json.average }}",
            "score_reason": "={{ $json.score_reason }}",
            "evaluatedAt": "={{ $now }}",
            "novelty_score": "={{ $json.novelty }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "title",
              "displayName": "title",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": true
            },
            {
              "id": "source",
              "displayName": "source",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": true
            },
            {
              "id": "url",
              "displayName": "url",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": true
            },
            {
              "id": "excerpt",
              "displayName": "excerpt",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": true
            },
            {
              "id": "date",
              "displayName": "date",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "readOnly": false,
              "removed": true
            },
            {
              "id": "relevance_score",
              "displayName": "relevance_score",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "novelty_score",
              "displayName": "novelty_score",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "credibility_score",
              "displayName": "credibility_score",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "impact_score",
              "displayName": "impact_score",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "score_reason",
              "displayName": "score_reason",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "evaluatedAt",
              "displayName": "evaluatedAt",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "average_score",
              "displayName": "average_score",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "readOnly": false,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1,
      "position": [
        3136,
        32
      ],
      "id": "bbe69f7f-f322-4725-ad56-9958d7dcb8f5",
      "name": "Update News with scores"
    },
    {
      "parameters": {
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "results",
        "options": {}
      },
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1,
      "position": [
        2400,
        544
      ],
      "id": "b687a1b4-9ed6-4fc1-9ee0-bc5b57e105f3",
      "name": "Aggregate sorted articles"
    },
    {
      "parameters": {
        "sortFieldsUi": {
          "sortField": [
            {
              "fieldName": "average_score",
              "order": "descending"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.sort",
      "typeVersion": 1,
      "position": [
        1952,
        544
      ],
      "id": "144128f8-4b70-42fa-b35b-bfd9f089ecd6",
      "name": "Sort based on score"
    },
    {
      "parameters": {
        "jsonSchemaExample": "{\n  \"id\": 2,\n  \"relevance\": 7,\n  \"novelty\": 5,\n  \"credibility\": 9,\n  \"impact\": 4,\n  \"score_reason\": \"Partially relevant to AI (diagnostic tools), but not directly tied to n8n. Novelty is moderate as it discusses AI in healthcare, which has been covered in previous newsletters. Credibility is high due to Financial Times' reputation. Impact is moderate as it highlights a niche trend in AI applications.\"\n}",
        "autoFix": true,
        "customizeRetryPrompt": true,
        "prompt": "Instructions:\n--------------\n{instructions}\n--------------\nCompletion:\n--------------\n{completion}\n--------------\n\nAbove, the Completion did not satisfy the constraints given in the Instructions.\nError:\n--------------\n{error}\n--------------\nIn addition, make sure the strings in the output JSON do not contain additional quotation marks (such as used for quotes) since these break the JSON parsing.\n\nPlease try again. Please only respond with an answer that satisfies the constraints laid out in the Instructions:"
      },
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [
        2544,
        240
      ],
      "id": "a175dfca-0057-4c3b-b32b-b4340b40e939",
      "name": "Structured Output Parser"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=You are a Relevance Agent for an AI newsletter.\n\nYou receive:\n\n* User topics: {{ $('Start').item.json.topics }}\n* One candidate news article with id\n* A compressed memory of previous newsletters (for novelty comparison)\n\nYour task: Score the article on 4 dimensions (0–10).\n\n## Scoring Criteria\n\n**relevance**\nHow directly relevant to user topics?\n0 = unrelated, 10 = core topic.\n\n**novelty**\nHow new is this compared to the past topics memory?\n0 = already covered, 10 = completely new.\n\n**credibility**\nSource reliability + factual reporting indicators.\n0 = spam/clickbait, 10 = reputable journalism.\n\n**impact**\nHow significant is this development for someone following these topics?\n0 = trivial, 10 = major development.\n\n## Calibration Rules (important)\n\nUse this scale distribution to keep scoring consistent across all independently scored articles:\n\n* Most articles should score **3–7** in relevance, novelty, and impact.\n* Scores **8–10** are reserved for *highly* relevant or major developments.\n* Scores **0–2** should be used only for clearly irrelevant, already-covered, or low-quality items.\n* Do not inflate scores.\n\nScore the article even if it seems unimportant. Do not skip.\nReturn the article id as well as a brief single-line explanation for the scores.\nIn score_reason, only use plain text characters that keep the JSON valid. Avoid anything that might break JSON syntax (especially unescaped double quotes, raw code, or markdown). If you need to quote something like a title or phrase, prefer single quotes or paraphrase it instead of using double quotes.\n\n---\n\nArticle to score:\n\n[{{ $json.id_2 }}]\nTitle: {{ $json.title_2 }}\nExcerpt: {{ $json.excerpt_2 }}\nSource: {{ $json.source_2 }}\n\n---\n\nPrevious newsletters (for novelty comparison): \n\n{{ $json.newsletters.map(x => 'Newsletter: ' + x.content).join('\\n---\\n') }}\n",
        "hasOutputParser": true,
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        2560,
        32
      ],
      "id": "06275d1f-f00d-4f19-a1d5-54eeab6020e8",
      "name": "Relevance Model"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=You are the Editorial Decision Agent for an AI newsletter.\n\n## Input\n\n**Ranked articles**: 15 pre-scored articles (relevance, novelty, credibility, impact, average)\n**Last newsletter**: {{ $('Get most recent newsletter').item.json.createdAt || 'Never' }}\n**Days since last**: {{ $('Get most recent newsletter').item.json.createdAt ? Math.floor($now.diff(DateTime.fromISO($('Get most recent newsletter').item.json.createdAt), 'days').days) : 999 }}\n**Min days between**: {{ $('Start').item.json.minDaysBetween }}\n**Max days between**: {{ $('Start').item.json.maxDaysBetween }}\n\n## Decision Rules\n\n**YES if ALL:**\n1. At least 3 articles with average_score >= 7\n2. At least 1 article with impact_score >= 8\n3. Articles form a coherent, valuable edition\n4. Not just variations of the same story\n\n**Forced YES if:**\n- Days since last > maxDaysBetween AND at least 1 article with relevance_score >= 6\n\n**Otherwise: NO**\n\n## Selection Rules (if YES)\n\nSelect 3-5 articles that:\n- Maximize topic diversity\n- Prioritize high impact\n- Balance novelty and credibility\n\nIn addition, provide a clear decision reason explanation and an edition summary with one sentence describing this edition.\n\nOnly use plain text characters that keep the return JSON valid. Avoid anything that might break JSON syntax (especially unescaped double quotes, raw code, or markdown). If you need to quote something like a title or phrase, prefer single quotes or paraphrase it instead of using double quotes.\n\nIf NO, return empty articles array.\n\n## Articles to evaluate\n\n{{ JSON.stringify($input.all().map(i => i.json), null, 2) }}",
        "hasOutputParser": true,
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        2640,
        544
      ],
      "id": "6ff6f901-2080-428e-865f-6d885ba558ed",
      "name": "Editorial Decision Model"
    },
    {
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"decision\": {\n      \"type\": \"string\",\n      \"enum\": [\"YES\", \"NO\"],\n      \"description\": \"Whether to create the newsletter edition\"\n    },\n    \"decision_reason\": {\n      \"type\": \"string\",\n      \"description\": \"Clear explanation of the decision\"\n    },\n    \"articles\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"title\": {\n            \"type\": \"string\",\n            \"description\": \"Article title\"\n          },\n          \"excerpt\": {\n            \"type\": \"string\",\n            \"description\": \"Article excerpt or summary\"\n          },\n          \"source\": {\n            \"type\": \"string\",\n            \"description\": \"Article source/publication name\"\n          },\n          \"url\": {\n            \"type\": \"string\",\n            \"description\": \"Article URL\"\n          },\n          \"selection_reason\": {\n            \"type\": \"string\",\n            \"description\": \"Why this article was selected\"\n          }\n        },\n        \"required\": [\"title\", \"excerpt\", \"source\", \"url\", \"selection_reason\"]\n      },\n      \"description\": \"Selected articles (empty if decision is NO)\"\n    },\n    \"edition_summary\": {\n      \"type\": \"string\",\n      \"description\": \"One sentence describing this edition\"\n    }\n  },\n  \"required\": [\"decision\", \"decision_reason\", \"articles\", \"edition_summary\"]\n}",
        "autoFix": true
      },
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [
        2592,
        736
      ],
      "id": "e4a52a69-a80f-4c0e-873c-e5fbbd49ef4f",
      "name": "Structured Output Parser1"
    },
    {
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n\t\"type\": \"object\",\n\t\"properties\": {\n\t\t\"title\": {\n\t\t\t\"type\": \"string\"\n\t\t},\n\t\t\"content\": {\n\t\t\t\"type\": \"string\"\n\t\t}\n\t}\n}",
        "autoFix": true
      },
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [
        2368,
        1216
      ],
      "id": "f5ee6e0d-443d-49f9-a2e0-12ab56c1c5d8",
      "name": "Parse enriched article JSON"
    },
    {
      "parameters": {
        "maxItems": 3
      },
      "type": "n8n-nodes-base.limit",
      "typeVersion": 1,
      "position": [
        1952,
        96
      ],
      "id": "f5375e91-228b-406e-8325-c1bdc35cb3d7",
      "name": "Limit previous newsletters to last 3"
    },
    {
      "parameters": {
        "jsCode": "// Get all input items\nconst items = $input.all();\n\n// Process each item\nreturn items.map(item => {\n  const data = item.json.output;\n  \n  // Calculate average of the four scores\n  const average = (data.relevance + data.novelty + data.credibility + data.impact) / 4;\n  \n  // Return flattened object with average added\n  return {\n    json: {\n      id: data.id,\n      relevance: data.relevance,\n      novelty: data.novelty,\n      credibility: data.credibility,\n      impact: data.impact,\n      average: average,\n      score_reason: data.score_reason\n    }\n  };\n});"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2912,
        32
      ],
      "id": "a7f3c407-093a-40d7-83c7-c3e726909221",
      "name": "Calculate Average Score"
    },
    {
      "parameters": {
        "maxItems": 15
      },
      "type": "n8n-nodes-base.limit",
      "typeVersion": 1,
      "position": [
        2176,
        544
      ],
      "id": "c95efd60-aa3f-4d65-8009-305ce05d3ee4",
      "name": "Limit to 15"
    },
    {
      "parameters": {
        "query": "={{\n  (() => {\n    const text = 'recent, reliable sources about: ' + $json.title + ' — ' + $json.excerpt;\n    return text.length > 400 ? text.slice(0, 397) + '...' : text;\n  })()\n}}",
        "options": {}
      },
      "type": "@tavily/n8n-nodes-tavily.tavily",
      "typeVersion": 1,
      "position": [
        2176,
        1024
      ],
      "id": "cb01eccc-4fce-4e59-9511-7a345aff63e5",
      "name": "Search",
      "credentials": {
        "tavilyApi": {
          "id": "AnWISxerNJuEULsk",
          "name": "Tavily account"
        }
      }
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=You are a research writer that updates short news summaries into concise, factual articles.\n\n**Input:**\nTitle: {{ $('Split selected articles for enrichment').item.json.title }}\nSummary: {{ $('Split selected articles for enrichment').item.json.excerpt }}\nSource: {{ $('Split selected articles for enrichment').item.json.source }}\nOriginal URL: {{ $('Split selected articles for enrichment').item.json.url }}\nLanguage: {{ $('Start').item.json.language }}\n\nYou also receive pre-fetched research snippets from a web search:\n\n{{ JSON.stringify($json.results) }}\n\n**Instructions:**\n\n1. Use ONLY the information above to refine the article.\n2. Update the **title** if a more accurate or engaging one exists.\n3. Write **1–2 sentences** summarizing the topic, combining the original summary and information from the research snippets.\n4. Translate the article if necessary to comply with the desired language {{ $('Start').item.json.language }}\n\nOnly use plain text characters that keep the return JSON valid. Avoid anything that might break JSON syntax (especially unescaped double quotes, raw code, or markdown). If you need to quote something like a title or phrase, prefer single quotes or paraphrase it instead of using double quotes.",
        "hasOutputParser": true,
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        2416,
        1024
      ],
      "id": "bc7dfccf-b232-4c74-a20b-7633526f864e",
      "name": "Research Model"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=You are a Formatting Agent producing newsletter output for Telegram.\n\nInput: {{ $json.output.length }} enriched articles\nLanguage: {{ $('Start').item.json.language }}\n\n## Constraints\n\n- **Maximum 4000 characters** (Telegram limit is 4096, keep buffer)\n- Write in {{ $('Start').item.json.language }}\n\n## Telegram Markdown Format\n\n- *bold* for article titles\n- _italic_ for emphasis\n- [Link Text](url) for sources\n- Separate articles with blank lines\n\n## Structure\n\nFor each article:\n\n*Title*\nSummary paragraph (2-3 sentences max)\n[Source](url)\n\nIf total exceeds 4000 chars, prioritize earlier articles and trim summaries.\n\nReturn just the formatted newsletter markdown without any explanation or notes.\n\n## Articles to format\n\n{{ JSON.stringify(\n  $json.output.map((o, i) => ({\n    title: o.title,\n    content: o.content,\n    source: $items(\"Split selected articles for enrichment\")[i].json.source,\n    url: $items(\"Split selected articles for enrichment\")[i].json.url,\n  }))\n) }}",
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        2176,
        1520
      ],
      "id": "597c6b5d-ae74-4c7e-881d-0e8bb8a9e98f",
      "name": "Formatting Model"
    },
    {
      "parameters": {
        "rules": {
          "rule": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "e0d38237-ecd5-45f2-a010-5927b7b51e6b",
                    "leftValue": "={{ $('Start').item.json.model_provider }}",
                    "rightValue": "local_ministral",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "modelIndex": 2,
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "503f7fe3-87e8-4a10-a945-266244b458ca",
                    "leftValue": "={{ $('Start').item.json.model_provider }}",
                    "rightValue": "cloud_gpt5.1",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            }
          ]
        }
      },
      "type": "@n8n/n8n-nodes-langchain.modelSelector",
      "typeVersion": 1,
      "position": [
        3392,
        960
      ],
      "id": "8b229939-eb6b-4ad1-8a70-9e51a831cb25",
      "name": "Model Selector"
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "gpt-5.1",
          "mode": "list",
          "cachedResultName": "gpt-5.1"
        },
        "responsesApiEnabled": false,
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.3,
      "position": [
        3552,
        1104
      ],
      "id": "142478ff-e352-47d9-9015-101ab3f0d9e9",
      "name": "OpenAI Chat Model",
      "credentials": {
        "openAiApi": {
          "id": "GBrPTO4vXXdExwp4",
          "name": "OpenAi account"
        }
      }
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1520,
        160
      ],
      "id": "98bc7cf2-b700-48c2-ab6b-46ab282d3d46",
      "name": "Start"
    },
    {
      "parameters": {
        "dataTableId": {
          "__rl": true,
          "value": "Nlg4NvP7qyFG5oag",
          "mode": "list",
          "cachedResultName": "Evaluation",
          "cachedResultUrl": "/projects/2mgqFGS1co9qm0Rg/datatables/Nlg4NvP7qyFG5oag"
        },
        "limitRows": true,
        "maxRows": 5
      },
      "type": "n8n-nodes-base.evaluationTrigger",
      "typeVersion": 4.7,
      "position": [
        1072,
        320
      ],
      "id": "6ac4f851-a7f9-40ab-aede-5322298412cc",
      "name": "When fetching a dataset row"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "d1b520a6-2be1-4ca4-ac56-bd202cd91db1",
              "name": "minDaysBetween",
              "value": 0,
              "type": "number"
            },
            {
              "id": "f77efe55-ab66-465d-aa4e-34e63e8fab17",
              "name": "maxDaysBetween",
              "value": 5,
              "type": "number"
            },
            {
              "id": "0f010980-6740-4a53-bdbb-ce260e85ad32",
              "name": "model_provider",
              "value": "cloud_gpt5.1",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": true,
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1296,
        320
      ],
      "id": "65a58480-1455-4b80-8884-c6f1bd1d8a09",
      "name": "Set remaining parameters"
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "gpt-5.1",
          "mode": "list",
          "cachedResultName": "gpt-5.1"
        },
        "responsesApiEnabled": false,
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.3,
      "position": [
        2384,
        2096
      ],
      "id": "9b6f7749-f0ae-4d44-bf90-777ca67550df",
      "name": "OpenAI Chat Model1",
      "credentials": {
        "openAiApi": {
          "id": "GBrPTO4vXXdExwp4",
          "name": "OpenAi account"
        }
      }
    },
    {
      "parameters": {
        "jsonSchemaExample": "  {\n    \"content_quality_score\": 5,\n    \"clarity_score\": 1,\n    \"formatting_score\": 3,\n    \"scoring_reason\": \"the reason for the scores\"\n  }",
        "autoFix": true
      },
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [
        2528,
        2096
      ],
      "id": "b7431b9b-0835-49a3-b57e-c9cc3ce8d882",
      "name": "Structured Output Parser2"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=You are an expert newsletter editor.\n\nYour task is to evaluate an AI-generated newsletter on three dimensions, each from 1 (very poor) to 5 (excellent):\n\n1. content_quality_score: How accurate, relevant, and complete is the content for the requested topics?\n2. clarity_score: How clear, coherent, and easy to understand is the text?\n3. formatting_score: How well is the newsletter formatted and scannable for a Telegram newsletter (headings, paragraphs, sources)?\nIn addition, you must provide:\n4. scoring_reason: A concise explanation of why you gave these three scores, referencing specific aspects of the newsletter (strengths, weaknesses, and key issues).\n\nThe user requested topics are:\n{{ $('Start').item.json.topics }}\n\nThe target language of the newsletter is:\n{{ $('Start').item.json.language }}\n\nHere is the newsletter to evaluate:\n\"\"\"\n{{ $json.text }}\n\"\"\"\n",
        "hasOutputParser": true,
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        2432,
        1936
      ],
      "id": "9529b3db-bc60-444a-a009-3dc82111a69b",
      "name": "Evaluator"
    },
    {
      "parameters": {
        "dataTableId": {
          "__rl": true,
          "value": "Nlg4NvP7qyFG5oag",
          "mode": "list",
          "cachedResultName": "Evaluation",
          "cachedResultUrl": "/projects/2mgqFGS1co9qm0Rg/datatables/Nlg4NvP7qyFG5oag"
        },
        "outputs": {
          "values": [
            {
              "outputName": "output",
              "outputValue": "={{ $json.text }}"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.evaluation",
      "typeVersion": 4.8,
      "position": [
        1952,
        1936
      ],
      "id": "b2145432-cb02-4334-a938-59ddc5365df1",
      "name": "Write outputs"
    },
    {
      "parameters": {
        "operation": "checkIfEvaluating"
      },
      "type": "n8n-nodes-base.evaluation",
      "typeVersion": 4.8,
      "position": [
        2528,
        1520
      ],
      "id": "b140f271-1fba-4619-86a2-df84d9cd9835",
      "name": "Check if evaluation"
    },
    {
      "parameters": {
        "operation": "checkIfEvaluating"
      },
      "type": "n8n-nodes-base.evaluation",
      "typeVersion": 4.8,
      "position": [
        1024,
        1120
      ],
      "id": "5308658f-1a0f-45fd-8014-52851fa1031b",
      "name": "Evaluation"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "efe5dcde-4f3f-41e3-ab61-75618f4625c5",
              "name": "newsletters",
              "value": "={{ [] }}",
              "type": "array"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1728,
        1104
      ],
      "id": "97cc05e9-8677-4b60-b993-4d7cc46a86d1",
      "name": "Empty Newsletter History"
    },
    {
      "parameters": {
        "operation": "setMetrics",
        "metric": "customMetrics",
        "metrics": {
          "assignments": [
            {
              "name": "content_quality_score",
              "value": "={{ $json.output.content_quality_score }}",
              "type": "number",
              "id": "8b33126f-71a6-4f9d-a5ed-2b4ec6d52c3f"
            },
            {
              "id": "b08452fa-f148-4acf-96b5-e52d1e7eb3eb",
              "name": "clarity_score",
              "value": "={{ $json.output.clarity_score }}",
              "type": "number"
            },
            {
              "id": "618476ec-d0b3-449d-8008-9d838996eb64",
              "name": "formatting_score",
              "value": "={{ $json.output.formatting_score }}",
              "type": "number"
            },
            {
              "id": "cca5fbff-1eb8-417d-9469-f81b795c4250",
              "name": "average_score",
              "value": "={{ ($json.output.content_quality_score + $json.output.clarity_score + $json.output.formatting_score) / 3 }}",
              "type": "number"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.evaluation",
      "typeVersion": 4.8,
      "position": [
        2912,
        1904
      ],
      "id": "0b66cc2a-acd6-416b-af9a-13356e75f1c2",
      "name": "Log metrics"
    },
    {
      "parameters": {
        "dataTableId": {
          "__rl": true,
          "value": "Nlg4NvP7qyFG5oag",
          "mode": "list",
          "cachedResultName": "Evaluation",
          "cachedResultUrl": "/projects/2mgqFGS1co9qm0Rg/datatables/Nlg4NvP7qyFG5oag"
        },
        "outputs": {
          "values": [
            {
              "outputName": "scoring_reason",
              "outputValue": "={{ $json.output.scoring_reason }}"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.evaluation",
      "typeVersion": 4.8,
      "position": [
        2912,
        2048
      ],
      "id": "5e1dd2b4-998f-4d41-8a4f-d641327d6aff",
      "name": "Write scoring reason"
    },
    {
      "parameters": {
        "content": "## Evaluation\n\n- Route newsletter into eval pipeline: Write outputs saves the generated Telegram newsletter text into the Evaluation table and passes it to the Evaluator.\n- LLM-based scoring with structured JSON: Evaluator (using OpenAI Chat Model1 + Structured Output Parser2) rates content quality, clarity, and formatting (1–5) and returns a scoring_reason.\n- Log metrics for analysis: Log metrics records the three scores plus average_score as evaluation metrics, while Write scoring reason stores the textual explanation back into the Evaluation table.",
        "height": 464,
        "width": 1408,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1920,
        1744
      ],
      "id": "01385259-de68-46be-a9bf-2016889a79c3",
      "name": "Sticky Note1"
    },
    {
      "parameters": {
        "content": "## Fetch News\n\n• Build topic queries: Splits comma-separated topics into individual search queries\n• Fetch news from [SerpAPI](https://serpapi.com): Queries `DuckDuckGo News` via SerpAPI for each topic (last 24h)\n• Split results: Extracts individual articles from API response\n• Upsert into News table: Stores articles",
        "height": 400,
        "width": 896,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        992,
        496
      ],
      "id": "77365da6-7b6d-4f78-9878-c8e86d9a0854",
      "name": "Sticky Note2"
    },
    {
      "parameters": {
        "content": "## News Filtering & Frequency Guardrails\n\n- Load prior context per topic/model: Eval runs inject an empty newsletters history so relevance/novelty and cadence logic run as if no prior editions exist.\n- Attach last-edition metadata to articles: Merge candidate articles with the latest newsletter and filter to only articles published after that edition, so only new stories move forward.\n- Enforce send cadence: Stop if last newsletter is too recent checks minDaysBetween and halts the pipeline entirely if the last newsletter was sent too recently.\n\n",
        "height": 800,
        "width": 896,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        992,
        912
      ],
      "id": "cf8a515c-7145-400d-98e4-5db2d3591c6f",
      "name": "Sticky Note3"
    },
    {
      "parameters": {
        "content": "## Relevance AI\n\n- Aggregate last 3 newsletters into a single newsletters list and attach it to each candidate article.\n- Call Relevance Model with topics + article + past newsletters to get structured scores (relevance, novelty, credibility, impact) and a score_reason.\n- Compute average_score from the four metrics and update the News table row (scores, reason, evaluatedAt, novelty_score, etc.) for each article.\n",
        "height": 480,
        "width": 1408,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1920,
        -128
      ],
      "id": "332121bb-c7a3-4d6f-91d1-1a1e9b53b464",
      "name": "Sticky Note4"
    },
    {
      "parameters": {
        "content": "## Editorial Decision AI\n\n- Sort & cap articles by average_score descending, keep top 15, and aggregate them into a single results list.\n- Editorial Decision Model evaluates the batch plus timing rules (days since last vs min/max) and returns structured JSON: decision, decision_reason, selected articles, and edition_summary.\n- If node checks decision === 'YES' and only then continues the flow to send a newsletter; NO stops this path.",
        "height": 480,
        "width": 1408,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1920,
        368
      ],
      "id": "c9366c44-fc80-4167-b126-f77e4ed000c6",
      "name": "Sticky Note5"
    },
    {
      "parameters": {
        "content": "## Research AI\n\n- Split editorial picks: Break output.articles array into one item per article to enrich each story individually.\n- Fetch fresh context: For each article, run Tavily Search with a trimmed title+excerpt query to get recent, reliable snippets.\n- Rewrite & structure: Research Model updates title + writes a 1–2 sentence, language-adjusted summary using only provided data, then Parse enriched article JSON enforces clean { title, content } JSON.\n",
        "height": 480,
        "width": 1408,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1920,
        864
      ],
      "id": "8dcb2523-93c8-4187-bb89-228f41024f6d",
      "name": "Sticky Note6"
    },
    {
      "parameters": {
        "content": "## LLM\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nModel Selector routes between a local Ollama model (ministral-3:3b-instruct-q4) and OpenAI gpt-5.1 based on model_provider (local_ministral vs cloud_gpt5.1), and is reused by all relevance/editorial/research/formatting + parser nodes.",
        "height": 480,
        "width": 368,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        3344,
        864
      ],
      "id": "82752b36-bcc6-4094-ad0b-ec9ce5c4e435",
      "name": "Sticky Note7"
    },
    {
      "parameters": {
        "content": "## Writer AI\n\n- Aggregate enriched stories: Collect all enriched article outputs into a single output array ready for formatting.\n- Format for Telegram: Formatting Model turns the articles into Telegram Markdown (titles, summaries, links) under ~4000 characters and in the chosen language.\n- Gate, store & deliver: Check if evaluation blocks real sends during eval; otherwise insert the formatted text into the Newsletters table and then send the newsletter message to Telegram.",
        "height": 352,
        "width": 1408,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1920,
        1360
      ],
      "id": "6c493c71-9fb8-446d-86d0-7d9135b08156",
      "name": "Sticky Note8"
    },
    {
      "parameters": {
        "model": "ministral-3:3b-instruct-2512-q4_K_M",
        "options": {
          "temperature": 0
        }
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOllama",
      "typeVersion": 1,
      "position": [
        3392,
        1104
      ],
      "id": "9945463b-19b8-43ad-966d-e99483ea675e",
      "name": "Ollama Chat Model",
      "credentials": {
        "ollamaApi": {
          "id": "SFwV4XBg9RlrOwgt",
          "name": "Ollama account"
        }
      }
    }
  ],
  "connections": {
    "Set topics and language": {
      "main": [
        [
          {
            "node": "Start",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build topic queries": {
      "main": [
        [
          {
            "node": "Fetch news from SerpAPI (DuckDuckGo News)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch news from SerpAPI (DuckDuckGo News)": {
      "main": [
        [
          {
            "node": "Split SerpAPI results into articles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split SerpAPI results into articles": {
      "main": [
        [
          {
            "node": "Upsert articles into News table",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upsert articles into News table": {
      "main": [
        [
          {
            "node": "Combine articles with last newsletter metadata",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Get previous newsletters": {
      "main": [
        [
          {
            "node": "Sort newsletters by newest first",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sort newsletters by newest first": {
      "main": [
        [
          {
            "node": "Get most recent newsletter",
            "type": "main",
            "index": 0
          },
          {
            "node": "Limit previous newsletters to last 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get most recent newsletter": {
      "main": [
        [
          {
            "node": "Combine articles with last newsletter metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate previous newsletters into list": {
      "main": [
        [
          {
            "node": "Combine candidate articles with past newsletters",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Combine articles with last newsletter metadata": {
      "main": [
        [
          {
            "node": "Filter articles newer than last newsletter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter articles newer than last newsletter": {
      "main": [
        [
          {
            "node": "Stop if last newsletter is too recent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Stop if last newsletter is too recent": {
      "main": [
        [
          {
            "node": "Combine candidate articles with past newsletters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine candidate articles with past newsletters": {
      "main": [
        [
          {
            "node": "Relevance Model",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If AI decided to send newsletter": {
      "main": [
        [
          {
            "node": "Split selected articles for enrichment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split selected articles for enrichment": {
      "main": [
        [
          {
            "node": "Search",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate enriched articles": {
      "main": [
        [
          {
            "node": "Formatting Model",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert newsletter content into Newsletters table": {
      "main": [
        [
          {
            "node": "Send newsletter to Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Set topics and language",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update News with scores": {
      "main": [
        [
          {
            "node": "Sort based on score",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate sorted articles": {
      "main": [
        [
          {
            "node": "Editorial Decision Model",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sort based on score": {
      "main": [
        [
          {
            "node": "Limit to 15",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Relevance Model",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Relevance Model": {
      "main": [
        [
          {
            "node": "Calculate Average Score",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Editorial Decision Model": {
      "main": [
        [
          {
            "node": "If AI decided to send newsletter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser1": {
      "ai_outputParser": [
        [
          {
            "node": "Editorial Decision Model",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Parse enriched article JSON": {
      "ai_outputParser": [
        [
          {
            "node": "Research Model",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Limit previous newsletters to last 3": {
      "main": [
        [
          {
            "node": "Aggregate previous newsletters into list",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Average Score": {
      "main": [
        [
          {
            "node": "Update News with scores",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Limit to 15": {
      "main": [
        [
          {
            "node": "Aggregate sorted articles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search": {
      "main": [
        [
          {
            "node": "Research Model",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Research Model": {
      "main": [
        [
          {
            "node": "Aggregate enriched articles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Formatting Model": {
      "main": [
        [
          {
            "node": "Check if evaluation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Model Selector": {
      "ai_languageModel": [
        [
          {
            "node": "Relevance Model",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Editorial Decision Model",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Research Model",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Formatting Model",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Structured Output Parser",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Structured Output Parser1",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Parse enriched article JSON",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Structured Output Parser2",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Model Selector",
            "type": "ai_languageModel",
            "index": 1
          }
        ]
      ]
    },
    "Start": {
      "main": [
        [
          {
            "node": "Build topic queries",
            "type": "main",
            "index": 0
          },
          {
            "node": "Evaluation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When fetching a dataset row": {
      "main": [
        [
          {
            "node": "Set remaining parameters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set remaining parameters": {
      "main": [
        [
          {
            "node": "Start",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "Evaluator",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser2": {
      "ai_outputParser": [
        [
          {
            "node": "Evaluator",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Evaluator": {
      "main": [
        [
          {
            "node": "Log metrics",
            "type": "main",
            "index": 0
          },
          {
            "node": "Write scoring reason",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write outputs": {
      "main": [
        [
          {
            "node": "Evaluator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if evaluation": {
      "main": [
        [
          {
            "node": "Write outputs",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Insert newsletter content into Newsletters table",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evaluation": {
      "main": [
        [
          {
            "node": "Empty Newsletter History",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Get previous newsletters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Empty Newsletter History": {
      "main": [
        [
          {
            "node": "Get most recent newsletter",
            "type": "main",
            "index": 0
          },
          {
            "node": "Limit previous newsletters to last 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log metrics": {
      "main": [
        []
      ]
    },
    "Ollama Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Model Selector",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {
    "Schedule Trigger": [
      {
        "timestamp": "2025-11-28T09:00:00.009+01:00",
        "Readable date": "November 28th 2025, 9:00:00 am",
        "Readable time": "9:00:00 am",
        "Day of week": "Friday",
        "Year": "2025",
        "Month": "November",
        "Day of month": "28",
        "Hour": "09",
        "Minute": "00",
        "Second": "00",
        "Timezone": "Europe/Berlin (UTC+01:00)"
      }
    ],
    "When fetching a dataset row": [
      {
        "topics": "productivity,habit building,deep work",
        "language": "en",
        "output": "---\n**India’s AI Burnout Crisis: Workload Surge and AI Overhead Drive Employee Exhaustion**\nIndia’s workforce is grappling with an AI burnout crisis, where increased workloads and unexpected tasks introduced by AI adoption are leading to exhaustion. Research reveals that 77% of workers report higher workloads since AI implementation, despite its benefits in problem-solving and career planning [News18](https://www.news18.com/tech/indias-ai-burnout-crisis-why-employees-are-exhausted-even-before-their-workday-begins-shil-ws-l-9767011.html).\n\n---\n\n**Boreout: The Silent Workplace Crisis of Chronic Boredom and Its Impact on Productivity**\nA growing trend in modern workplaces is *boreout*—chronic boredom and disengagement that reduce productivity and cost companies millions. Unlike burnout, boreout lacks physical exhaustion but still impacts employees and managers globally [Money.ca](https://www.msn.com/en-ca/money/topstories/there-s-a-new-workplace-crisis-crushing-productivity-and-costing-companies-hundreds-of-millions-can-you-relate-to-boreout/ar-AA1SfLrd).\n\n---\n**Holiday Subscription Scams: How to Spot and Avoid Hidden Charges This Season**\nHoliday season scams exploit free trials and long-term commitments, making it easy to overlook unexpected charges. Research highlights rising scams—like those targeting donations and gift subscriptions—demanding vigilance in evaluating services and canceling unused memberships promptly [Android Police](https://www.msn.com/en-us/money/other/im-done-falling-for-sneaky-subscription-traps-heres-how-to-avoid-them-this-holiday-season/ar-AA1SfIaj).\n\n---\n**India’s AI Overload Crisis: AI Adds Unseen Workload Before Employees Start Their Day**\nIndia’s workforce faces an AI burnout crisis due to 'AI overhead,' where employees perform extra tasks introduced by AI adoption. While AI aids problem-solving and career planning for 71% of workers, many lack clear guidance on its use, leading to higher workloads [News18](https://www.news18.com/tech/indias-ai-burnout-crisis-why-employees-are-exhausted-even-before-their-workday-begins-shil-ws-l-9767011.html).",
        "scoring_reason": null,
        "id": 5,
        "createdAt": "2025-12-11T14:16:10.488Z",
        "updatedAt": "2025-12-13T13:15:31.371Z",
        "row_id": 5,
        "row_number": 4
      }
    ]
  },
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "fc51693a50872213a409bf71fd2eb8368f2ab7e097191a8a420f9cbea2981999"
  }
}