{
  "version": "1.0.0",
  "exported_at": "2026-06-01T00:00:00.000Z",
  "project": {
    "name": "Yahoo Finance Scraper",
    "description": "Scrapes Yahoo Finance Singapore stock profile, holders, and sustainability/ESG-style pages for organization stock symbols. Uses a multi-URL navigation loop; currently preloaded for KO across profile, holders, and sustainability URLs. Add three URLs per additional symbol to navigate.urls[]. Yahoo may redirect sustainability pages or omit ESG fields, so ESG columns can be blank.",
    "color": "bg-[#4589ff]",
    "template_id": "ai-generated"
  },
  "blocks": [
    {
      "block_id": "navigate-1",
      "block_type": "process",
      "title": "Navigate",
      "description": "Go to a URL",
      "position_x": 120,
      "position_y": 240,
      "config": {
        "urls": [
          "https://sg.finance.yahoo.com/quote/KO/profile?p=KO",
          "https://sg.finance.yahoo.com/quote/KO/holders?p=KO",
          "https://sg.finance.yahoo.com/quote/KO/sustainability?p=KO"
        ],
        "color": "bg-[#4589ff]"
      }
    },
    {
      "block_id": "wait-for-page-load-1",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 480,
      "position_y": 240,
      "config": {
        "timeout": 30,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 840,
      "position_y": 240,
      "config": {
        "selector": "[data-testid=\"quote-hdr\"], [data-testid=\"quote-title\"], h1",
        "timeout": 30,
        "visible": true,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 1200,
      "position_y": 240,
      "config": {
        "rowSelector": "body",
        "fileName": "yahoo_finance_scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#42be65]",
        "columns": [
          {
            "name": "Keyword",
            "selector": "(function(){return decodeURIComponent((location.pathname.split('/')[2]||'').trim());})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Company_name",
            "selector": "(function(){const sym=decodeURIComponent((location.pathname.split('/')[2]||'').trim());let m=document.title.match(/^(.+?)\\s*\\(([^)]+)\\)/);if(m&&(!sym||m[2].toUpperCase()===sym.toUpperCase()))return m[1].trim();const candidates=Array.from(document.querySelectorAll('[data-testid=\"quote-title\"] h1,[data-testid=\"quote-hdr\"] h1,h1')).map(h=>h.textContent.trim()).filter(Boolean);let txt=candidates.find(t=>t.includes('('+sym+')')&&!/^Yahoo Finance$/i.test(t))||candidates.find(t=>!/^Yahoo Finance$/i.test(t))||'';return txt.replace(/\\s*\\([^)]*\\)\\s*$/,'').trim();})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Company_address",
            "selector": "(function(){const text=document.body.innerText;const title=document.title.match(/^(.+?)\\s*\\([^)]+\\)/);const company=title?title[1].trim():'';const sections=[...document.querySelectorAll('section')].map(s=>s.innerText).filter(t=>/Sector:\\s*/.test(t)&&/Industry:\\s*/.test(t));const s=sections[0]||text;const lines=s.split('\\n').map(x=>x.trim()).filter(Boolean);const idx=lines.findIndex(x=>/^Sector:/.test(x));let out=idx>0?lines.slice(Math.max(0,idx-6),idx):[];out=out.filter(x=>x!==company&&!/^Yahoo Finance$/i.test(x));return out.join(' | ');})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "StockExchangeInformation",
            "selector": "(function(){const e=document.querySelector('[data-testid=\"quote-hdr\"] .exchange');return e?e.innerText.replace(/\\s+/g,' ').trim():'';})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Sector",
            "selector": "(function(){const text=document.body.innerText;let m=text.match(/Sector:\\s*([^\\n]+)/i);if(m)return m[1].trim();m=text.match(/Overview\\s+[^\\n]*\\/\\s*([^\\n]+)/i);return m?m[1].trim():'';})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Industry",
            "selector": "(function(){const text=document.body.innerText;let m=text.match(/Industry:\\s*([^\\n]+)/i);if(m)return m[1].trim();m=text.match(/Overview\\s+([^\\/\\n]+)\\s*\\//i);return m?m[1].trim():'';})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Full_time_employees",
            "selector": "(function(){const text=document.body.innerText;let m=text.match(/Full-time employees:\\s*([^\\n]+)/i);if(m)return m[1].trim();m=text.match(/([0-9,]+)\\s+Full-time employees/i);return m?m[1].trim():'';})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Key_executives",
            "selector": "(function(){const t=[...document.querySelectorAll('table')].find(t=>/Name\\s*Title\\s*Pay/i.test(t.innerText));return t?[...t.querySelectorAll('tbody tr')].map(r=>[...r.cells].map(c=>c.innerText.trim()).join(' ')).join(' | '):'';})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Description",
            "selector": "(function(){const text=document.body.innerText;const a=text.indexOf('Description');if(a<0)return '';const b=text.indexOf('Corporate governance',a);return text.slice(a+'Description'.length,b>a?b:undefined).replace(/\\s*\\n\\s*/g,' ').trim();})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Corporate_governance",
            "selector": "(function(){const text=document.body.innerText;const a=text.indexOf('Corporate governance');if(a<0)return '';let b=text.indexOf('Upcoming events',a);if(b<0)b=text.indexOf('Recent events',a);if(b<0)b=text.indexOf('Related tickers',a);return text.slice(a+'Corporate governance'.length,b>a?b:undefined).replace(/\\s*\\n\\s*/g,' | ').trim();})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Page_URL",
            "selector": "(function(){return location.pathname.includes('/profile')?location.href:'';})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Major_Holders",
            "selector": "(function(){if(!location.pathname.includes('/holders'))return '';const text=document.body.innerText;const m=text.match(/Breakdown\\s*([\\s\\S]*?)Top institutional holders/i);return m?m[1].replace(/\\s*\\n\\s*/g,' | ').trim():'';})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Top_Institutional_Holders",
            "selector": "(function(){if(!location.pathname.includes('/holders'))return '';const tables=[...document.querySelectorAll('table')].filter(t=>/Holder\\s*Shares\\s*Date reported/i.test(t.innerText));const t=tables[0];return t?[...t.querySelectorAll('tbody tr')].map(r=>[...r.cells].map(c=>c.innerText.trim()).join(' ')).join(' | '):'';})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Top_Mutual_Fund_Holders",
            "selector": "(function(){if(!location.pathname.includes('/holders'))return '';const tables=[...document.querySelectorAll('table')].filter(t=>/Holder\\s*Shares\\s*Date reported/i.test(t.innerText));const t=tables[1];return t?[...t.querySelectorAll('tbody tr')].map(r=>[...r.cells].map(c=>c.innerText.trim()).join(' ')).join(' | '):'';})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Page_URL1",
            "selector": "(function(){return location.pathname.includes('/holders')?location.href:'';})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Insider_roster",
            "selector": "(function(){if(!location.pathname.includes('/holders'))return '';const text=document.body.innerText;const m=text.match(/Insider roster\\s*([\\s\\S]*?)(?:Insider transactions|Major holders|Top institutional holders)/i);return m?m[1].replace(/\\s*\\n\\s*/g,' | ').trim():'';})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Field1",
            "selector": "(function(){const text=document.body.innerText;let m=text.match(/Total ESG risk score[\\s\\S]*?(?=\\n\\d+\\s*Significant controversy level|\\nControversy level|$)/i);if(m)return m[0].replace(/\\s*\\n\\s*/g,' | ').trim();m=text.match(/Environment risk score[\\s\\S]*?(?:Governance risk score\\s*[^\\n]*)/i);return m?m[0].replace(/\\s*\\n\\s*/g,' | ').trim():'';})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Controversy_level",
            "selector": "(function(){const text=document.body.innerText;let m=text.match(/\\d+\\s*Significant controversy level/i);if(m)return m[0].trim();m=text.match(/Controversy level\\s*[^\\n]*/i);return m?m[0].trim():'';})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Page_URL2",
            "selector": "(function(){return (!location.pathname.includes('/profile')&&!location.pathname.includes('/holders'))?location.href:'';})()",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 1560,
      "position_y": 240,
      "config": {
        "color": "bg-[#ff832b]"
      }
    }
  ],
  "connections": [
    {
      "from_block_id": "navigate-1",
      "from_connector_id": "right",
      "to_block_id": "wait-for-page-load-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-page-load-1",
      "from_connector_id": "right",
      "to_block_id": "wait-for-element-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-element-1",
      "from_connector_id": "right",
      "to_block_id": "structured-export-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "structured-export-1",
      "from_connector_id": "right",
      "to_block_id": "loop-continue-1",
      "to_connector_id": "left"
    }
  ],
  "canvas_elements": [
    {
      "id": "group-load",
      "element_type": "group",
      "title": "Page Load",
      "color": "#08bdba",
      "position_x": 48,
      "position_y": 136,
      "width": 1040,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "wait-for-element-1"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 1128,
      "position_y": 136,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "structured-export-1"
        ]
      }
    },
    {
      "id": "group-pagination",
      "element_type": "group",
      "title": "Pagination Loop",
      "color": "#ff832b",
      "position_x": 1488,
      "position_y": 136,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Scrapes Yahoo Finance Singapore stock profile, holders, and sustainability/ESG-style pages for organization stock symbols. Uses a multi-URL navigation loop; currently preloaded for KO across profile, holders, and sustainability URLs. Add three URLs per additional symbol to navigate.urls[]. Yahoo may redirect sustainability pages or omit ESG fields, so ESG columns can be blank.",
      "color": "#f1c21b",
      "position_x": 80,
      "position_y": 20,
      "width": 480,
      "height": 160,
      "z_index": 22,
      "data": {}
    },
    {
      "id": "note-block-navigate-1",
      "element_type": "note",
      "title": "Note: Navigate",
      "content": "Multi-URL loop over 3 pages. Pair with loop-continue at the end of each iteration.",
      "color": "#ee5396",
      "position_x": 320,
      "position_y": 220,
      "width": 328,
      "height": 107,
      "z_index": 22,
      "data": {
        "block_id": "navigate-1"
      }
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Structured export with JS columns (Keyword, Company_name, Company_address, StockExchangeInformation, Sector). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 1400,
      "position_y": 220,
      "width": 340,
      "height": 138,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-1"
      }
    },
    {
      "id": "note-block-loop-continue-1",
      "element_type": "note",
      "title": "Note: Loop Continue",
      "content": "Loop Continue advances a multi-URL or multi-text loop. Place at the end of the loop body with a clear back-edge to the loop start.",
      "color": "#ee5396",
      "position_x": 1760,
      "position_y": 220,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}