{
  "id": "rnUtSAtXydhhNRb1",
  "name": "Octagon Commodities Quote Skill to Google Sheets - Final Field Capture",
  "tags": [],
  "nodes": [
    {
      "id": "1f90673d-0572-4c93-895c-eb64aee461d4",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        352,
        -176
      ],
      "parameters": {
        "width": 480,
        "height": 608,
        "content": "## Octagon Commodities Quote Skill to Google Sheets\n\n### How it works\n\n1. Manually triggers the commodities quote workflow.\n2. Fetches the live commodities-quote Skill from GitHub.\n3. Reads commodity symbols such as GCUSD, SIUSD, CLUSD, and NGUSD from Google Sheets.\n4. Builds an Octagon Agent prompt for each commodity symbol using the fetched Skill context.\n5. Retrieves real-time commodity quote data and writes structured fields back to Google Sheets.\n\n### Setup steps\n\n- [ ] Configure Google Sheets credentials.\n- [ ] Configure OpenAI credentials for prompt building.\n- [ ] Configure Octagon credentials.\n- [ ] Ensure your sheet includes a commodity symbol column such as `Symbol` or `Ticker`, plus the output columns used by the update node.\n\n### Customization\n\nUse this workflow for precious metals, energy, base metals, and agricultural commodities. Common symbols include GCUSD, SIUSD, CLUSD, NGUSD, HGUSD, ZCUSD, ZSUSD, and ZWUSD."
      },
      "typeVersion": 1
    },
    {
      "id": "a7299b37-e424-448c-928b-37cd751fb9a6",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        912,
        -64
      ],
      "parameters": {
        "color": 7,
        "height": 304,
        "content": "## Manual commodities trigger\n\nStarts the workflow manually for commodity quote enrichment."
      },
      "typeVersion": 1
    },
    {
      "id": "918f51c4-8f0e-4d0f-9722-57889ff1a991",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1184,
        -64
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 304,
        "content": "## Configure commodities skill\n\nSets the commodity quote task and fetches the live commodities-quote Skill from GitHub."
      },
      "typeVersion": 1
    },
    {
      "id": "bdecb5df-b071-4c7d-a033-9f1a23c4e56e",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1632,
        -64
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 304,
        "content": "## Read commodity symbols\n\nReads commodity symbols from Google Sheets and filters valid rows for quote lookup."
      },
      "typeVersion": 1
    },
    {
      "id": "ee161640-ff67-4b2f-abed-85340e7a03d5",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2080,
        -144
      ],
      "parameters": {
        "color": 7,
        "width": 768,
        "height": 384,
        "content": "## Process commodity quotes\n\nLoops through commodity symbols, builds Octagon Agent prompts, and retrieves real-time quote data."
      },
      "typeVersion": 1
    },
    {
      "id": "759683a8-ac49-4a10-aa02-6404928762c4",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3056,
        -176
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 400,
        "content": "## Update commodity results\n\nParses all quote fields, updates Google Sheets, and throttles execution between rows."
      },
      "typeVersion": 1
    },
    {
      "id": "8ecd7839-6964-4b36-be3c-095e5b0d9f48",
      "name": "Manual Workflow Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        960,
        64
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "f27fb441-c07b-4c7c-bc49-f5568a768294",
      "name": "Set Commodities Task and Skill URL",
      "type": "n8n-nodes-base.set",
      "position": [
        1232,
        64
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "27c3c6bc-7784-4b87-9af4-64aa5912b26f",
              "name": "task",
              "type": "string",
              "value": "Get real-time commodity quotes for the symbols in the Google Sheet. For each commodity symbol, build the best Octagon Agent prompt using the fetched commodities-quote Skill context. Return compact quote fields suitable for updating current price, change, day low/high, moving averages, year range, volume, previous close, quote time, and source/status."
            },
            {
              "id": "6464cc3d-eaeb-493c-8b64-a6278bb43de5",
              "name": "skills_url",
              "type": "string",
              "value": "https://raw.githubusercontent.com/OctagonAI/skills/main/skills/commodities-quote/SKILL.md"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "94f017f4-63de-4d33-8ede-6bafd4c9d5f6",
      "name": "Filter Valid Commodity Symbols",
      "type": "n8n-nodes-base.filter",
      "position": [
        1904,
        64
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "7e47b022-8b22-4cac-9c3e-72287615ef97",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.Symbol || $json.SYMBOL || $json.Ticker || $json.TICKER || $json['Commodity Symbol'] || $json.Commodity }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "18cd624b-9a72-4ac6-9092-b0fa10e14bd0",
      "name": "Loop Over Commodity Symbols",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        2128,
        64
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "9b79fae9-c6e3-41a5-8be7-5304a0c78d2f",
      "name": "Create Commodities Quote Prompt",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        2352,
        -16
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {},
        "responses": {
          "values": [
            {
              "content": "={{ \n(() => {\n  const inputs = $('Set Commodities Task and Skill URL').first().json;\n  const skillNode = $('Fetch Commodities Skill from GitHub').first().json;\n\n  const skillContent =\n    typeof skillNode === 'string'\n      ? skillNode\n      : skillNode.data || skillNode.body || skillNode.response || skillNode.text || JSON.stringify(skillNode);\n\n  const symbol = $json.Symbol || $json.SYMBOL || $json.Ticker || $json.TICKER || $json['Commodity Symbol'] || $json.Commodity || '';\n\n  return `You are a prompt builder for Octagon Agent.\n\nObjective:\nGiven the workflow task, the current Google Sheets commodity row, and the fetched commodities-quote SKILL.md context, produce the exact prompt that should be sent to Octagon Agent.\n\nRules:\n- Output only the final prompt text.\n- Do not include markdown fences.\n- Do not explain your reasoning.\n- Use the commodities-quote skill's intended workflow.\n- The prompt must ask Octagon Agent for a real-time commodity quote.\n- The prompt must be specific to the commodity symbol in the current row.\n- The prompt should request a compact JSON object that includes at least: symbol, commodity, current_price, change, day_low, day_high, fifty_day_avg, two_hundred_day_avg, year_low, year_high, trading_volume, previous_close, quote_time, and source/status.\n- If the commodity symbol is missing, output: ERROR: Missing commodity symbol.\n- CRITIAL: Include in your instruction to Octagon Agent to always use octagon-stock-agent\n\nWorkflow task:\n${inputs.task}\n\nCurrent Google Sheets commodity row:\n${JSON.stringify($json, null, 2)}\n\nResolved commodity symbol:\n${symbol}\n\nFetched commodities-quote SKILL.md from:\n${inputs.skills_url}\n\nFetched commodities-quote SKILL.md content:\n${skillContent}`;\n})()\n}}"
            }
          ]
        },
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "c5f6e606-cb55-497b-8006-81e94711cdb2",
      "name": "Execute Octagon Agent",
      "type": "n8n-nodes-octagon.octagonAgents",
      "position": [
        2704,
        -16
      ],
      "parameters": {
        "query": "={{ \n(() => {\n  const item = $json;\n\n  if (typeof item === 'string') return item;\n\n  if (item.output_text) return item.output_text;\n  if (item.text) return item.text;\n\n  if (item.message?.content) {\n    if (typeof item.message.content === 'string') return item.message.content;\n    if (Array.isArray(item.message.content)) {\n      return item.message.content.map(c => c.text || c.content || '').join('\\n').trim();\n    }\n  }\n\n  if (Array.isArray(item.content)) {\n    return item.content.map(c => c.text || c.content || '').join('\\n').trim();\n  }\n\n  if (item.query) {\n    try {\n      const q = typeof item.query === 'string' ? JSON.parse(item.query) : item.query;\n      const text = q?.output?.[0]?.content?.[0]?.text;\n      if (text) return text;\n    } catch (e) {\n      return item.query;\n    }\n  }\n\n  return JSON.stringify(item);\n})()\n}}",
        "additionalFields": {}
      },
      "credentials": {
        "octagonApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "205ccc0a-bc5b-4296-82e8-779ca46c9e10",
      "name": "Update Commodities in Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3104,
        -16
      ],
      "parameters": {
        "columns": {
          "value": {
            "Date": "={{ $json.Date }}",
            "Change": "={{ $json.Change }}",
            "Ticker": "={{ $json.Ticker || $json.Symbol }}",
            "Volume": "={{ $json.Volume }}",
            "Exchange": "={{ $json.Exchange }}",
            "Day Range": "={{ $json['Day Range'] }}",
            "52-Week Range": "={{ $json['52-Week Range'] }}",
            "Current Price": "={{ $json['Current Price'] }}",
            "Previous Close": "={{ $json['Previous Close'] }}",
            "Octagon Response": "={{ $json['Octagon Response'] }}"
          },
          "schema": [
            {
              "id": "Ticker",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Ticker",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Current Price",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Current Price",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Change",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Change",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Volume",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Volume",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Day Range",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Day Range",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "52-Week Range",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "52-Week Range",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Exchange",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Exchange",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Previous Close",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Previous Close",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Octagon Response",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Octagon Response",
              "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": [
            "Ticker"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1onmnZmFFBy3eqSpbIDgrswv1LQ34D1EievWM-B0R4y0/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1vLTwTsx3vHgYOKpYh7Lz6l3ImokIPN6jUKNQm0L1K-M",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1vLTwTsx3vHgYOKpYh7Lz6l3ImokIPN6jUKNQm0L1K-M/edit?usp=drivesdk",
          "cachedResultName": "COMMODITIES"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "dbe63998-0e07-48a6-aa6f-76d397f2d613",
      "name": "Wait 1 Second",
      "type": "n8n-nodes-base.wait",
      "position": [
        3328,
        64
      ],
      "parameters": {
        "amount": 1
      },
      "typeVersion": 1.1
    },
    {
      "id": "f2662ce8-81d9-4560-80ce-61d6112f3d17",
      "name": "Read Commodity Symbols in Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1680,
        64
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1vLTwTsx3vHgYOKpYh7Lz6l3ImokIPN6jUKNQm0L1K-M/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1vLTwTsx3vHgYOKpYh7Lz6l3ImokIPN6jUKNQm0L1K-M",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1vLTwTsx3vHgYOKpYh7Lz6l3ImokIPN6jUKNQm0L1K-M/edit?usp=drivesdk",
          "cachedResultName": "COMMODITIES"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "15baccae-457d-402c-84fc-726e1a43b35c",
      "name": "Fetch Commodities Skill from GitHub",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1456,
        64
      ],
      "parameters": {
        "url": "={{ $json.skills_url }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "aaf4a27e-cf57-488e-9a8a-3dad34b489db",
      "name": "Parse Commodities Quote Response",
      "type": "n8n-nodes-base.code",
      "position": [
        2928,
        -16
      ],
      "parameters": {
        "jsCode": "// Parse Octagon Agent commodity quote response and merge all quote fields back with the current Google Sheets row.\n// Works with JSON, markdown tables, plain text, and nested Octagon/OpenAI-style payloads.\n\nfunction getCurrentCommodityRow() {\n  try {\n    return $('Loop Over Commodity Symbols').item.json || {};\n  } catch (e) {}\n\n  try {\n    return $('Loop Over Commodity Symbols').first().json || {};\n  } catch (e) {}\n\n  return {};\n}\n\nfunction stringifySafe(value) {\n  if (typeof value === 'string') return value;\n  if (value == null) return '';\n  try {\n    return JSON.stringify(value, null, 2);\n  } catch (e) {\n    return String(value);\n  }\n}\n\nfunction unwrapText(value) {\n  if (value == null) return '';\n  if (typeof value === 'string') return value;\n\n  if (typeof value.analysis === 'string') return value.analysis;\n  if (typeof value.output_text === 'string') return value.output_text;\n  if (typeof value.text === 'string') return value.text;\n  if (typeof value.response === 'string') return value.response;\n  if (typeof value.result === 'string') return value.result;\n  if (typeof value.answer === 'string') return value.answer;\n\n  if (value.message?.content) {\n    if (typeof value.message.content === 'string') return value.message.content;\n    if (Array.isArray(value.message.content)) {\n      return value.message.content.map(c => c?.text || c?.content || c?.value || '').join('\\n').trim();\n    }\n  }\n\n  if (Array.isArray(value.content)) {\n    return value.content.map(c => c?.text || c?.content || c?.value || '').join('\\n').trim();\n  }\n\n  if (value.query) {\n    try {\n      const q = typeof value.query === 'string' ? JSON.parse(value.query) : value.query;\n      const text = q?.output?.[0]?.content?.[0]?.text;\n      if (text) return text;\n    } catch (e) {\n      if (typeof value.query === 'string') return value.query;\n    }\n  }\n\n  return stringifySafe(value);\n}\n\nfunction stripCodeFences(text) {\n  return String(text || '')\n    .trim()\n    .replace(/^```json\\s*/i, '')\n    .replace(/^```javascript\\s*/i, '')\n    .replace(/^```\\s*/i, '')\n    .replace(/```$/i, '')\n    .trim();\n}\n\nfunction parseJsonObjectFromText(text) {\n  const cleaned = stripCodeFences(text);\n  if (!cleaned) return {};\n\n  try {\n    const parsed = JSON.parse(cleaned);\n    if (parsed && typeof parsed === 'object') return parsed;\n  } catch (e) {}\n\n  const firstBrace = cleaned.indexOf('{');\n  const lastBrace = cleaned.lastIndexOf('}');\n  if (firstBrace >= 0 && lastBrace > firstBrace) {\n    const candidate = cleaned.slice(firstBrace, lastBrace + 1);\n    try {\n      const parsed = JSON.parse(candidate);\n      if (parsed && typeof parsed === 'object') return parsed;\n    } catch (e) {}\n  }\n\n  return {};\n}\n\nfunction flattenObject(obj, prefix = '', out = {}) {\n  if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return out;\n\n  for (const [key, value] of Object.entries(obj)) {\n    const cleanKey = String(key).trim();\n    const path = prefix ? `${prefix}.${cleanKey}` : cleanKey;\n\n    if (value && typeof value === 'object' && !Array.isArray(value)) {\n      flattenObject(value, path, out);\n    } else {\n      out[path] = value;\n      out[cleanKey] = value;\n    }\n  }\n\n  return out;\n}\n\nfunction normalizeKey(key) {\n  return String(key || '')\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '_')\n    .replace(/^_+|_+$/g, '');\n}\n\nfunction getFirst(obj, keys, fallback = '') {\n  if (!obj || typeof obj !== 'object') return fallback;\n\n  const flat = flattenObject(obj);\n  const normalized = {};\n\n  for (const [key, value] of Object.entries(flat)) {\n    normalized[normalizeKey(key)] = value;\n  }\n\n  for (const key of keys) {\n    const direct = flat[key];\n    if (direct !== undefined && direct !== null && direct !== '') return direct;\n\n    const norm = normalized[normalizeKey(key)];\n    if (norm !== undefined && norm !== null && norm !== '') return norm;\n  }\n\n  return fallback;\n}\n\nfunction extractMetric(text, metric) {\n  const escapedMetric = metric.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n  const tableRegex = new RegExp(`\\\\|\\\\s*${escapedMetric}\\\\s*\\\\|\\\\s*([^|]+)\\\\|`, 'i');\n  const tableMatch = String(text || '').match(tableRegex);\n  if (tableMatch) return tableMatch[1].trim();\n\n  const labelRegex = new RegExp(`${escapedMetric}\\\\s*[:\\\\-\u2013\u2014]\\\\s*([^\\\\n|]+)`, 'i');\n  const labelMatch = String(text || '').match(labelRegex);\n  if (labelMatch) return labelMatch[1].trim();\n\n  return '';\n}\n\nfunction firstMetric(text, labels) {\n  for (const label of labels) {\n    const value = extractMetric(text, label);\n    if (value) return value;\n  }\n  return '';\n}\n\nfunction joinRange(low, high) {\n  if (low && high) return `${low} - ${high}`;\n  return low || high || '';\n}\n\nconst original = getCurrentCommodityRow();\nconst octagonRaw = $input.first().json;\nconst raw = stringifySafe(octagonRaw);\nconst analysis = unwrapText(octagonRaw) || raw;\nconst parsed = parseJsonObjectFromText(analysis);\n\nconst symbol =\n  original.Symbol ||\n  original.SYMBOL ||\n  original.Ticker ||\n  original.TICKER ||\n  original['Commodity Symbol'] ||\n  getFirst(parsed, ['symbol', 'ticker', 'commodity_symbol', 'commoditySymbol', 'quote.symbol', 'quote.ticker']) ||\n  firstMetric(analysis, ['Symbol', 'Ticker', 'Commodity Symbol']);\n\nconst commodity =\n  original.Commodity ||\n  original.commodity ||\n  getFirst(parsed, ['commodity', 'name', 'commodity_name', 'commodityName', 'quote.commodity', 'quote.name']) ||\n  firstMetric(analysis, ['Commodity', 'Name']);\n\nconst currentPrice =\n  getFirst(parsed, ['current_price', 'currentPrice', 'price', 'last_price', 'lastPrice', 'last', 'quote.current_price', 'quote.price', 'quote.last_price']) ||\n  firstMetric(analysis, ['Current Price', 'Last Price', 'Price', 'Current']);\n\nconst change =\n  getFirst(parsed, ['change', 'price_change', 'priceChange', 'change_percent', 'changePercent', 'percent_change', 'percentChange', 'quote.change']) ||\n  firstMetric(analysis, ['Change', 'Price Change', 'Change %', 'Percent Change']);\n\nconst dayLow =\n  getFirst(parsed, ['day_low', 'dayLow', 'low', 'regularMarketDayLow', 'quote.day_low']) ||\n  firstMetric(analysis, ['Day Low', 'Low']);\n\nconst dayHigh =\n  getFirst(parsed, ['day_high', 'dayHigh', 'high', 'regularMarketDayHigh', 'quote.day_high']) ||\n  firstMetric(analysis, ['Day High', 'High']);\n\nconst dayRange =\n  getFirst(parsed, ['day_range', 'dayRange', 'regularMarketDayRange', 'quote.day_range']) ||\n  firstMetric(analysis, ['Day Range', 'Daily Range']) ||\n  joinRange(dayLow, dayHigh);\n\nconst yearLow =\n  getFirst(parsed, ['year_low', 'yearLow', '52_week_low', '52WeekLow', 'fifty_two_week_low', 'quote.year_low']) ||\n  firstMetric(analysis, ['Year Low', '52-Week Low', '52 Week Low']);\n\nconst yearHigh =\n  getFirst(parsed, ['year_high', 'yearHigh', '52_week_high', '52WeekHigh', 'fifty_two_week_high', 'quote.year_high']) ||\n  firstMetric(analysis, ['Year High', '52-Week High', '52 Week High']);\n\nconst week52Range =\n  getFirst(parsed, ['52_week_range', '52WeekRange', 'fifty_two_week_range', 'fiftyTwoWeekRange', 'week_52_range', 'quote.52_week_range']) ||\n  firstMetric(analysis, ['52W Range', '52-Week Range', '52 Week Range', 'Year Range']) ||\n  joinRange(yearLow, yearHigh);\n\nconst volume =\n  getFirst(parsed, ['volume', 'trading_volume', 'tradingVolume', 'regularMarketVolume', 'quote.volume']) ||\n  firstMetric(analysis, ['Trading Volume', 'Volume']);\n\nconst previousClose =\n  getFirst(parsed, ['previous_close', 'previousClose', 'prev_close', 'prevClose', 'regularMarketPreviousClose', 'quote.previous_close']) ||\n  firstMetric(analysis, ['Previous Close', 'Prev Close']);\n\nconst ma50 =\n  getFirst(parsed, ['50_day_avg', '50_day_average', 'fifty_day_avg', 'fifty_day_average', '50_day_ma', '50DayAverage', 'quote.50_day_avg']) ||\n  firstMetric(analysis, ['50-Day Avg', '50-Day Average', '50-Day MA', '50-Day Moving Average', '50 Day Average']);\n\nconst ma200 =\n  getFirst(parsed, ['200_day_avg', '200_day_average', 'two_hundred_day_avg', 'two_hundred_day_average', '200_day_ma', '200DayAverage', 'quote.200_day_avg']) ||\n  firstMetric(analysis, ['200-Day Avg', '200-Day Average', '200-Day MA', '200-Day Moving Average', '200 Day Average']);\n\nconst exchange =\n  getFirst(parsed, ['exchange', 'exchangeName', 'fullExchangeName', 'market', 'quote.exchange']) ||\n  firstMetric(analysis, ['Exchange', 'Market']);\n\nconst date =\n  getFirst(parsed, ['quote_time', 'quoteTime', 'as_of', 'asOf', 'timestamp', 'date', 'time', 'metadata.timestamp']) ||\n  firstMetric(analysis, ['As Of (UTC)', 'As Of', 'Quote Time', 'Timestamp', 'Date']) ||\n  octagonRaw.metadata?.timestamp ||\n  new Date().toISOString();\n\nconst sourceStatus =\n  getFirst(parsed, ['source_status', 'sourceStatus', 'source', 'status', 'provider', 'data_sources', 'dataSources']) ||\n  firstMetric(analysis, ['Data Sources', 'Source', 'Status', 'Source/Status']);\n\nreturn [{\n  json: {\n    row_number: original.row_number,\n    Symbol: symbol,\n    Ticker: symbol,\n    Commodity: commodity,\n    'Current Price': currentPrice,\n    Change: change,\n    'Day Low': dayLow,\n    'Day High': dayHigh,\n    'Day Range': dayRange,\n    'Year Low': yearLow,\n    'Year High': yearHigh,\n    '52-Week Range': week52Range,\n    'Trading Volume': volume,\n    Volume: volume,\n    'Previous Close': previousClose,\n    '50-Day Average': ma50,\n    '200-Day Average': ma200,\n    Exchange: exchange,\n    Date: date,\n    'Octagon Response': analysis,\n    'Octagon Source/Status': sourceStatus,\n    _parsedQuoteJson: parsed\n  }\n}];"
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "6c3df39b-b6b7-49aa-937e-99bd8788c731",
  "connections": {
    "Wait 1 Second": {
      "main": [
        [
          {
            "node": "Loop Over Commodity Symbols",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute Octagon Agent": {
      "main": [
        [
          {
            "node": "Parse Commodities Quote Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual Workflow Trigger": {
      "main": [
        [
          {
            "node": "Set Commodities Task and Skill URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Commodity Symbols": {
      "main": [
        [],
        [
          {
            "node": "Create Commodities Quote Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Commodities in Sheets": {
      "main": [
        [
          {
            "node": "Wait 1 Second",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Valid Commodity Symbols": {
      "main": [
        [
          {
            "node": "Loop Over Commodity Symbols",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Commodities Quote Prompt": {
      "main": [
        [
          {
            "node": "Execute Octagon Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Commodities Quote Response": {
      "main": [
        [
          {
            "node": "Update Commodities in Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Commodity Symbols in Sheets": {
      "main": [
        [
          {
            "node": "Filter Valid Commodity Symbols",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Commodities Task and Skill URL": {
      "main": [
        [
          {
            "node": "Fetch Commodities Skill from GitHub",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Commodities Skill from GitHub": {
      "main": [
        [
          {
            "node": "Read Commodity Symbols in Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}