{
  "schema_version": "1.0",
  "nodes": [
    {
      "id": "feed",
      "type": "input/data",
      "label": "Supplier feed",
      "parameters": {
        "dataConfig": {
          "assetPath": "__SUPPLIER_FEED_CSV__",
          "format": "auto",
          "headerRow": 1,
          "locale": "de-DE",
          "formulaPolicy": "cached_value",
          "emptyBehavior": "fail",
          "maxRows": 1000
        }
      }
    },
    {
      "id": "rows",
      "type": "enumerate/data_rows",
      "label": "Each row",
      "parameters": {
        "rowMapping": {
          "columns": [
            {
              "sourceColumn": "sku",
              "outputPort": "sku",
              "outputType": "text",
              "selected": true,
              "label": "sku",
              "conversion": "strict"
            }
          ]
        }
      }
    },
    {
      "id": "check",
      "type": "utility/json_schema_validate",
      "label": "Check the row",
      "parameters": {
        "schema_source": "inline",
        "schema_definition": "{\n  \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n  \"title\": \"Supplier product row\",\n  \"type\": \"object\",\n  \"required\": [\n    \"sku\",\n    \"name\",\n    \"category\",\n    \"material\",\n    \"weight_kg\",\n    \"price_eur\",\n    \"stock\"\n  ],\n  \"properties\": {\n    \"sku\": {\n      \"type\": \"string\",\n      \"pattern\": \"^AR-[0-9]{4}$\",\n      \"description\": \"The supplier article code, AR- and four digits\"\n    },\n    \"name\": {\n      \"type\": \"string\",\n      \"minLength\": 3,\n      \"maxLength\": 80\n    },\n    \"category\": {\n      \"enum\": [\n        \"backpacks\",\n        \"tents\",\n        \"sleeping-bags\",\n        \"apparel\",\n        \"cookware\"\n      ],\n      \"description\": \"One of the shop categories, in English\"\n    },\n    \"material\": {\n      \"type\": \"string\",\n      \"minLength\": 3\n    },\n    \"weight_kg\": {\n      \"type\": \"number\",\n      \"exclusiveMinimum\": 0,\n      \"maximum\": 25\n    },\n    \"price_eur\": {\n      \"type\": \"number\",\n      \"exclusiveMinimum\": 0,\n      \"description\": \"Wholesale price in euros\"\n    },\n    \"stock\": {\n      \"type\": \"integer\",\n      \"minimum\": 0\n    },\n    \"notes\": {\n      \"type\": \"string\"\n    }\n  }\n}",
        "mode": "route",
        "max_errors": 10
      }
    },
    {
      "id": "sku_ok",
      "type": "utility/extract",
      "label": "SKU",
      "parameters": {
        "path": "sku",
        "expected_type": "text",
        "on_missing": "fail"
      }
    },
    {
      "id": "name",
      "type": "utility/extract",
      "label": "Name",
      "parameters": {
        "path": "name",
        "expected_type": "text",
        "on_missing": "fail"
      }
    },
    {
      "id": "category",
      "type": "utility/extract",
      "label": "Category",
      "parameters": {
        "path": "category",
        "expected_type": "text",
        "on_missing": "fail"
      }
    },
    {
      "id": "price",
      "type": "utility/extract",
      "label": "Wholesale price",
      "parameters": {
        "path": "price_eur",
        "expected_type": "number",
        "on_missing": "fail"
      }
    },
    {
      "id": "stock",
      "type": "utility/extract",
      "label": "Stock",
      "parameters": {
        "path": "stock",
        "expected_type": "number",
        "on_missing": "fail"
      }
    },
    {
      "id": "weight",
      "type": "utility/extract",
      "label": "Weight",
      "parameters": {
        "path": "weight_kg",
        "expected_type": "number",
        "on_missing": "fail"
      }
    },
    {
      "id": "retail",
      "type": "utility/calculate",
      "label": "Retail price",
      "parameters": {
        "expression": "round(wholesale * 1.9, 0) - 0.05",
        "decimals": 2
      }
    },
    {
      "id": "record",
      "type": "text/format",
      "label": "Row as text",
      "parameters": {
        "format": "json",
        "json_style": "compact"
      }
    },
    {
      "id": "copy",
      "type": "ai/text_generation",
      "label": "Product copy",
      "parameters": {
        "system_prompt": "You write the product description of an outdoor-gear web shop, in English, from the supplier's record of one product. Two or three sentences, at most 55 words: what the product is for and why it is good at it, using only the material, weight and notes you are given. Plain, concrete, confident; no superlatives, no exclamation marks, no invented features, certifications or measurements. Do not mention the price, the stock or the article code. Plain text only.",
        "temperature": 0.5,
        "max_tokens": 2000
      },
      "model": {
        "model": "gpt-5.6-luna",
        "params": {
          "reasoning_effort": "low",
          "max_tokens": 2000
        }
      }
    },
    {
      "id": "catalog",
      "type": "aggregate/csv",
      "label": "Shop catalog",
      "parameters": {
        "csvAggregateConfig": {
          "delimiter": ",",
          "quoteValues": true,
          "includeHeader": true,
          "encoding": "utf-8",
          "outputName": "catalog",
          "columns": [
            {
              "name": "SKU",
              "inputPort": "sku",
              "type": "text",
              "whenAbsent": "skip_row"
            },
            {
              "name": "Title",
              "inputPort": "title",
              "type": "text"
            },
            {
              "name": "Category",
              "inputPort": "category",
              "type": "text"
            },
            {
              "name": "Price (EUR)",
              "inputPort": "price",
              "type": "number"
            },
            {
              "name": "Stock",
              "inputPort": "stock",
              "type": "number"
            },
            {
              "name": "Weight (kg)",
              "inputPort": "weight",
              "type": "number"
            },
            {
              "name": "Description",
              "inputPort": "description",
              "type": "text"
            }
          ]
        }
      }
    },
    {
      "id": "only_rejected",
      "type": "utility/filter",
      "label": "Only rejected rows",
      "parameters": {
        "invert": true
      }
    },
    {
      "id": "errors_text",
      "type": "text/format",
      "label": "Errors as text",
      "parameters": {
        "format": "json",
        "json_style": "compact"
      }
    },
    {
      "id": "row_text",
      "type": "text/format",
      "label": "Row as sent",
      "parameters": {
        "format": "json",
        "json_style": "compact"
      }
    },
    {
      "id": "fix_facts",
      "type": "text/template",
      "label": "What to fix",
      "parameters": {
        "template": "Row as the supplier sent it: [row]\nWhat the check found (JSON Schema errors): [errors]",
        "placeholders": [
          {
            "name": "row",
            "inputPort": "row"
          },
          {
            "name": "errors",
            "inputPort": "errors"
          }
        ]
      }
    },
    {
      "id": "fix_note",
      "type": "ai/text_generation",
      "label": "Correction request",
      "parameters": {
        "system_prompt": "You write one line of a correction list that an online shop sends to its supplier about one row of the supplier's product feed. Write each problem the check found in exactly this form: <column>: sent \"<value as sent>\", expected <what is expected>. Separate two problems with \"; \". An empty value is written as sent \"\". The shop's categories are: backpacks, tents, sleeping-bags, apparel, cookware. Article codes are AR- followed by four digits. Prices use a decimal comma in the feed. Only the problems in the errors; nothing else. At most 40 words, plain English, no greeting, no please, no final period.",
        "temperature": 0,
        "max_tokens": 2000
      },
      "model": {
        "model": "gpt-5.6-luna",
        "params": {
          "reasoning_effort": "low",
          "max_tokens": 2000
        }
      }
    },
    {
      "id": "corrections",
      "type": "aggregate/csv",
      "label": "Corrections for the supplier",
      "parameters": {
        "csvAggregateConfig": {
          "delimiter": ";",
          "quoteValues": true,
          "includeHeader": true,
          "encoding": "utf-8",
          "outputName": "corrections",
          "columns": [
            {
              "name": "Line",
              "inputPort": "line",
              "type": "number"
            },
            {
              "name": "sku",
              "inputPort": "sku",
              "type": "text"
            },
            {
              "name": "To fix",
              "inputPort": "request",
              "type": "text",
              "whenAbsent": "skip_row"
            },
            {
              "name": "Check details",
              "inputPort": "details",
              "type": "text"
            }
          ]
        }
      }
    },
    {
      "id": "line",
      "type": "utility/calculate",
      "label": "Line in the file",
      "parameters": {
        "expression": "row_index + 2"
      }
    },
    {
      "id": "rejected",
      "type": "aggregate/json",
      "label": "Rows to fix",
      "parameters": {
        "emptyBehavior": "empty",
        "jsonAggregateConfig": {
          "prettyPrint": true,
          "outputName": "rows_to_fix",
          "columns": [
            {
              "name": "line",
              "inputPort": "line",
              "type": "number"
            },
            {
              "name": "sku",
              "inputPort": "sku",
              "type": "text"
            },
            {
              "name": "to_fix",
              "inputPort": "request",
              "type": "text",
              "whenAbsent": "skip_row"
            }
          ]
        }
      }
    },
    {
      "id": "to_fix_count",
      "type": "utility/calculate",
      "label": "Rows to fix",
      "parameters": {
        "expression": "count(rows_to_fix[*].line)"
      }
    },
    {
      "id": "listed_count",
      "type": "utility/calculate",
      "label": "Rows listed",
      "parameters": {
        "expression": "received - to_fix"
      }
    },
    {
      "id": "fmt_received",
      "type": "text/format",
      "label": "Received as text",
      "parameters": {
        "format": "auto"
      }
    },
    {
      "id": "fmt_listed",
      "type": "text/format",
      "label": "Listed as text",
      "parameters": {
        "format": "auto"
      }
    },
    {
      "id": "fmt_list",
      "type": "text/format",
      "label": "List as text",
      "parameters": {
        "format": "json",
        "json_style": "indented"
      }
    },
    {
      "id": "email_facts",
      "type": "text/template",
      "label": "Email facts",
      "parameters": {
        "template": "Rows received in today's feed: [received]\nRows listed in the shop: [listed]\nRows to fix (JSON, line = line in the CSV file): [list]",
        "placeholders": [
          {
            "name": "received",
            "inputPort": "received"
          },
          {
            "name": "listed",
            "inputPort": "listed"
          },
          {
            "name": "list",
            "inputPort": "list"
          }
        ]
      }
    },
    {
      "id": "email",
      "type": "ai/text_generation",
      "label": "Email to the supplier",
      "parameters": {
        "system_prompt": "You write the email that the buyer of Trailhead Supply, an outdoor-gear web shop, sends to Alpenrand Outdoor after importing their daily product feed. Start with a subject line (Subject: ...). Thank them briefly, give the two counts exactly as written, then list every row to fix as \"Line <line> (<sku, or no article code>): <to_fix>\", copying each to_fix text word for word. Ask for the corrected rows in tomorrow's feed. If there is nothing to fix, say that every row was listed. Friendly, brief, plain text, signed \"Trailhead Supply — Purchasing\". Invent nothing.",
        "temperature": 0.3,
        "max_tokens": 2000
      },
      "model": {
        "model": "gpt-5.6-luna",
        "params": {
          "reasoning_effort": "low",
          "max_tokens": 2000
        }
      }
    },
    {
      "id": "out_email",
      "type": "output/text",
      "label": "Email to the supplier",
      "parameters": {
        "output_name": "supplier_email"
      }
    },
    {
      "id": "out_catalog",
      "type": "output/csv",
      "label": "Catalog",
      "parameters": {
        "output_name": "catalog"
      }
    },
    {
      "id": "out_corrections",
      "type": "output/csv",
      "label": "Corrections",
      "parameters": {
        "output_name": "corrections"
      }
    }
  ],
  "connections": [
    {
      "from_node": "feed",
      "from_output": "dataset",
      "to_node": "rows",
      "to_input": "dataset"
    },
    {
      "from_node": "rows",
      "from_output": "row",
      "to_node": "check",
      "to_input": "document"
    },
    {
      "from_node": "check",
      "from_output": "normalized_json",
      "to_node": "sku_ok",
      "to_input": "json"
    },
    {
      "from_node": "check",
      "from_output": "normalized_json",
      "to_node": "name",
      "to_input": "json"
    },
    {
      "from_node": "check",
      "from_output": "normalized_json",
      "to_node": "category",
      "to_input": "json"
    },
    {
      "from_node": "check",
      "from_output": "normalized_json",
      "to_node": "price",
      "to_input": "json"
    },
    {
      "from_node": "check",
      "from_output": "normalized_json",
      "to_node": "stock",
      "to_input": "json"
    },
    {
      "from_node": "check",
      "from_output": "normalized_json",
      "to_node": "weight",
      "to_input": "json"
    },
    {
      "from_node": "price",
      "from_output": "value",
      "to_node": "retail",
      "to_input": "wholesale"
    },
    {
      "from_node": "check",
      "from_output": "normalized_json",
      "to_node": "record",
      "to_input": "value"
    },
    {
      "from_node": "record",
      "from_output": "text",
      "to_node": "copy",
      "to_input": "prompt"
    },
    {
      "from_node": "sku_ok",
      "from_output": "value",
      "to_node": "catalog",
      "to_input": "sku"
    },
    {
      "from_node": "name",
      "from_output": "value",
      "to_node": "catalog",
      "to_input": "title"
    },
    {
      "from_node": "category",
      "from_output": "value",
      "to_node": "catalog",
      "to_input": "category"
    },
    {
      "from_node": "retail",
      "from_output": "result",
      "to_node": "catalog",
      "to_input": "price"
    },
    {
      "from_node": "stock",
      "from_output": "value",
      "to_node": "catalog",
      "to_input": "stock"
    },
    {
      "from_node": "weight",
      "from_output": "value",
      "to_node": "catalog",
      "to_input": "weight"
    },
    {
      "from_node": "copy",
      "from_output": "result",
      "to_node": "catalog",
      "to_input": "description"
    },
    {
      "from_node": "check",
      "from_output": "valid",
      "to_node": "only_rejected",
      "to_input": "condition"
    },
    {
      "from_node": "check",
      "from_output": "errors",
      "to_node": "only_rejected",
      "to_input": "value"
    },
    {
      "from_node": "only_rejected",
      "from_output": "value",
      "to_node": "errors_text",
      "to_input": "value"
    },
    {
      "from_node": "rows",
      "from_output": "row",
      "to_node": "row_text",
      "to_input": "value"
    },
    {
      "from_node": "row_text",
      "from_output": "text",
      "to_node": "fix_facts",
      "to_input": "row"
    },
    {
      "from_node": "errors_text",
      "from_output": "text",
      "to_node": "fix_facts",
      "to_input": "errors"
    },
    {
      "from_node": "fix_facts",
      "from_output": "result",
      "to_node": "fix_note",
      "to_input": "prompt"
    },
    {
      "from_node": "rows",
      "from_output": "row_index",
      "to_node": "line",
      "to_input": "row_index"
    },
    {
      "from_node": "line",
      "from_output": "result",
      "to_node": "corrections",
      "to_input": "line"
    },
    {
      "from_node": "rows",
      "from_output": "sku",
      "to_node": "corrections",
      "to_input": "sku"
    },
    {
      "from_node": "fix_note",
      "from_output": "result",
      "to_node": "corrections",
      "to_input": "request"
    },
    {
      "from_node": "errors_text",
      "from_output": "text",
      "to_node": "corrections",
      "to_input": "details"
    },
    {
      "from_node": "line",
      "from_output": "result",
      "to_node": "rejected",
      "to_input": "line"
    },
    {
      "from_node": "rows",
      "from_output": "sku",
      "to_node": "rejected",
      "to_input": "sku"
    },
    {
      "from_node": "fix_note",
      "from_output": "result",
      "to_node": "rejected",
      "to_input": "request"
    },
    {
      "from_node": "rejected",
      "from_output": "result",
      "to_node": "to_fix_count",
      "to_input": "rows_to_fix"
    },
    {
      "from_node": "feed",
      "from_output": "row_count",
      "to_node": "listed_count",
      "to_input": "received"
    },
    {
      "from_node": "to_fix_count",
      "from_output": "result",
      "to_node": "listed_count",
      "to_input": "to_fix"
    },
    {
      "from_node": "feed",
      "from_output": "row_count",
      "to_node": "fmt_received",
      "to_input": "value"
    },
    {
      "from_node": "listed_count",
      "from_output": "result",
      "to_node": "fmt_listed",
      "to_input": "value"
    },
    {
      "from_node": "rejected",
      "from_output": "result",
      "to_node": "fmt_list",
      "to_input": "value"
    },
    {
      "from_node": "fmt_received",
      "from_output": "text",
      "to_node": "email_facts",
      "to_input": "received"
    },
    {
      "from_node": "fmt_listed",
      "from_output": "text",
      "to_node": "email_facts",
      "to_input": "listed"
    },
    {
      "from_node": "fmt_list",
      "from_output": "text",
      "to_node": "email_facts",
      "to_input": "list"
    },
    {
      "from_node": "email_facts",
      "from_output": "result",
      "to_node": "email",
      "to_input": "prompt"
    },
    {
      "from_node": "email",
      "from_output": "result",
      "to_node": "out_email",
      "to_input": "text"
    },
    {
      "from_node": "catalog",
      "from_output": "result",
      "to_node": "out_catalog",
      "to_input": "csv"
    },
    {
      "from_node": "corrections",
      "from_output": "result",
      "to_node": "out_corrections",
      "to_input": "csv"
    }
  ]
}
