{
  "name": "REVOPS-LI-007 | 01 | Daily ghosted proposal detector",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * *"
            }
          ]
        }
      },
      "id": "0095ca17-0fab-47e6-a39d-14c4d3c0c246",
      "name": "Daily Proposal Scan",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {},
      "id": "37e3402b-ecd3-4f92-a5e7-cae3a98fffb1",
      "name": "Run Scan Manually",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        270,
        0
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const config={proposal_stage_ids:['REPLACE_PROPOSAL_STAGE_INTERNAL_ID'],min_age_days:5,max_age_days:7,activity_grace_minutes:15,high_value_threshold:25000,max_results:100,portal_id:'REPLACE_HUBSPOT_PORTAL_ID'};if(config.proposal_stage_ids.some(x=>x.includes('REPLACE_')))throw new Error('Set proposal_stage_ids to HubSpot internal stage IDs');return [{json:{config,body:{filterGroups:[{filters:[{propertyName:'dealstage',operator:'IN',values:config.proposal_stage_ids},{propertyName:'hs_is_closed',operator:'EQ',value:'false'}]}],properties:[\"dealname\",\"dealstage\",\"pipeline\",\"amount\",\"hs_is_closed\",\"hubspot_owner_id\",\"hs_last_activity_date\",\"hs_last_sales_activity_timestamp\",\"notes_last_updated\",\"hs_last_contacted\",\"lastmodifieddate\",\"revops_proposal_sent_at\",\"revops_proposal_status\",\"revops_proposal_link\",\"revops_proposal_value\",\"revops_proposal_summary\",\"revops_service_proposed\",\"revops_main_pain_point\",\"revops_expected_next_step\",\"revops_salesperson_notes\",\"revops_previous_objections\",\"revops_past_communication_summary\",\"revops_proposal_channel\",\"revops_ghosting_status\",\"revops_followup_sent_at\",\"revops_next_review_at\",\"revops_last_reply_at\",\"revops_salesperson_name\"],limit:config.max_results,sorts:['revops_proposal_sent_at']}}}];"
      },
      "id": "cfa01509-429f-4a3f-8748-57e142dadfde",
      "name": "Scanner Configuration",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        540,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.hubapi.com/crm/v3/objects/deals/search",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{ $json.body }}",
        "options": {
          "timeout": 30000,
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "75652309-c2a6-43a4-b943-4be0a9c14d81",
      "name": "Search Open Proposal Deals",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        810,
        0
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_HUBSPOT_CREDENTIAL",
          "name": "ATTRIBUTION HubSpot"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const r=$input.first().json,cfg=$('Scanner Configuration').first().json.config;if(r.statusCode!==200)throw new Error('HubSpot deal search failed: '+r.statusCode);if(Number(r.body.total||0)>cfg.max_results)throw new Error('Open proposal result cap exceeded; increase controlled batching before activation');return (r.body.results||[]).map(deal=>({json:{deal_id:String(deal.id),config:cfg}}));"
      },
      "id": "85062c67-6ee9-4540-97f6-25ea430f408e",
      "name": "Validate and Expand Deals",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1080,
        0
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "={{ 'https://api.hubapi.com/crm/v3/objects/deals/'+$json.deal_id+'?properties='+encodeURIComponent([\"dealname\",\"dealstage\",\"pipeline\",\"amount\",\"hs_is_closed\",\"hubspot_owner_id\",\"hs_last_activity_date\",\"hs_last_sales_activity_timestamp\",\"notes_last_updated\",\"hs_last_contacted\",\"lastmodifieddate\",\"revops_proposal_sent_at\",\"revops_proposal_status\",\"revops_proposal_link\",\"revops_proposal_value\",\"revops_proposal_summary\",\"revops_service_proposed\",\"revops_main_pain_point\",\"revops_expected_next_step\",\"revops_salesperson_notes\",\"revops_previous_objections\",\"revops_past_communication_summary\",\"revops_proposal_channel\",\"revops_ghosting_status\",\"revops_followup_sent_at\",\"revops_next_review_at\",\"revops_last_reply_at\",\"revops_salesperson_name\"].join(','))+'&associations=contacts,companies' }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 30000,
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "26a28375-a4d0-4f52-beb0-bbf538828479",
      "name": "Read Deal With Associations",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1350,
        0
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_HUBSPOT_CREDENTIAL",
          "name": "ATTRIBUTION HubSpot"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const G = (function ghostedLibrary(){\n  const text=v=>String(v??'').trim();\n  const bool=v=>['true','1','yes','on'].includes(text(v).toLowerCase());\n  const ms=v=>{if(v==null||v==='')return null;const n=Number(v);if(Number.isFinite(n)&&n>100000000000)return n;const d=Date.parse(v);return Number.isFinite(d)?d:null};\n  const iso=v=>{const n=ms(v);return n?new Date(n).toISOString():null};\n  const latest=(...values)=>values.map(ms).filter(Boolean).sort((a,b)=>b-a)[0]||null;\n  const assoc=(record,type)=>(record.associations?.[type]?.results||[]).map(x=>String(x.id));\n  function classify(deal,config,now=Date.now()){\n    const p=deal.properties||{},proposal=ms(p.revops_proposal_sent_at),reasons=[];\n    if(!proposal)return {action:'MANUAL',status:'MISSING_PROPOSAL_DATE',reasons:['MISSING_PROPOSAL_DATE']};\n    const age=(now-proposal)/86400000;if(age<config.min_age_days||age>config.max_age_days)return {action:'SKIP',status:'OUTSIDE_FOLLOWUP_WINDOW',age_days:age,reasons:[]};\n    if(!config.proposal_stage_ids.includes(text(p.dealstage))||bool(p.hs_is_closed))return {action:'SKIP',status:'DEAL_NO_LONGER_ELIGIBLE',age_days:age,reasons:[]};\n    const activity=latest(p.hs_last_activity_date,p.hs_last_sales_activity_timestamp,p.notes_last_updated,p.hs_last_contacted,p.revops_last_reply_at);\n    const meaningful=activity;\n    const grace=Number(config.activity_grace_minutes||15)*60000;if(meaningful&&meaningful>proposal+grace)return {action:'SKIP',status:'RECENT_ACTIVITY_FOUND',age_days:age,last_activity_at:new Date(meaningful).toISOString(),reasons:[]};\n    if(['FOLLOWED_UP','RE_ENGAGED','MANUAL_REVIEW','APPROVAL_PENDING','SENDING'].includes(text(p.revops_ghosting_status)))return {action:'SKIP',status:'ALREADY_HANDLED',age_days:age,reasons:[]};\n    const contacts=assoc(deal,'contacts'),companies=assoc(deal,'companies');\n    if(contacts.length!==1)reasons.push(contacts.length?'MULTIPLE_CONTACTS':'MISSING_CONTACT');if(companies.length>1)reasons.push('MULTIPLE_COMPANIES');\n    return {action:reasons.length?'MANUAL':'CONTEXT',status:reasons.length?'ASSOCIATION_REVIEW_REQUIRED':'CONTEXT_REQUIRED',age_days:age,proposal_sent_at:new Date(proposal).toISOString(),contacts,companies,last_activity_at:activity?new Date(activity).toISOString():null,reasons};\n  }\n  function finalize(deal,contact,company,openCompanyDeals,config,pre){\n    const reasons=[...(pre.reasons||[])],p=deal.properties||{},cp=contact?.properties||{},channel=text(p.revops_proposal_channel||'email').toLowerCase();\n    if(company&&openCompanyDeals>1)reasons.push('MULTIPLE_ACTIVE_COMPANY_DEALS');\n    if(channel==='email'&&!text(cp.email))reasons.push('MISSING_CONTACT_EMAIL');\n    if(channel==='email'&&(bool(cp.hs_email_optout)||bool(cp.revops_do_not_contact)))reasons.push('CONTACT_SUPPRESSED');\n    if(!['email','crm email'].includes(channel))reasons.push('CHANNEL_REQUIRES_MANUAL_SEND');\n    const amount=Number(p.amount||p.revops_proposal_value||0);if(!Number.isFinite(amount))reasons.push('INVALID_DEAL_VALUE');\n    const highValue=Number.isFinite(amount)&&amount>=config.high_value_threshold;\n    return {deal_id:String(deal.id),deal,contact:contact||null,company:company||null,proposal_sent_at:pre.proposal_sent_at,channel,amount:Number.isFinite(amount)?amount:0,high_value:highValue,requires_approval:highValue&&!reasons.length,action:reasons.length?'MANUAL':'READY',status:reasons.length?'MANUAL_REVIEW_REQUIRED':highValue?'APPROVAL_REQUIRED':'READY_TO_DRAFT',reasons};\n  }\n  function draft(c){const d=c.deal.properties||{},p=c.contact?.properties||{};const first=text(p.firstname)||'there';const proposal=text(d.revops_service_proposed||d.dealname||'proposal');const pain=text(d.revops_main_pain_point);const next=text(d.revops_expected_next_step)||'review any questions together';const objection=text(d.revops_previous_objections);let body=`Hi ${first},\\n\\nI wanted to follow up on the ${proposal} I sent over last week.`;if(pain)body+=` Based on our conversation about ${pain}, I wanted to make sure the proposal covers the priorities we discussed.`;if(objection)body+=` I’m also happy to clarify the points around ${objection}.`;body+=`\\n\\nWould it be helpful to ${next}? If priorities have changed, just let me know and I can adjust the direction.\\n\\nBest,\\n${text(d.revops_salesperson_name)||'Your sales representative'}`;return {subject:`Quick follow-up on ${text(d.dealname)||'the proposal'}`,body};}\n  function revalidate(c,deal,contact,config){const current=classify(deal,config,Date.now()),reasons=[];if(!['CONTEXT','READY'].includes(current.action)&&current.status!=='CONTEXT_REQUIRED')reasons.push('DEAL_CHANGED_OR_ACTIVITY_FOUND');if(iso(deal.properties?.revops_proposal_sent_at)!==iso(c.proposal_sent_at))reasons.push('PROPOSAL_DATE_CHANGED');if(text(deal.properties?.revops_followup_sent_at))reasons.push('FOLLOWUP_ALREADY_SENT');if(!contact||text(contact.properties?.email).toLowerCase()!==text(c.contact?.properties?.email).toLowerCase())reasons.push('CONTACT_EMAIL_CHANGED_OR_MISSING');if(bool(contact?.properties?.hs_email_optout)||bool(contact?.properties?.revops_do_not_contact))reasons.push('CONTACT_SUPPRESSED');return {safe:!reasons.length,reasons};}\n  return {text,bool,ms,iso,latest,assoc,classify,finalize,draft,revalidate};\n})();\nconst cfg=$('Scanner Configuration').first().json.config;return $input.all().map(item=>{if(item.json.statusCode!==200)throw new Error('Deal read failed: '+item.json.statusCode);const deal=item.json.body;return {json:{deal,pre:G.classify(deal,cfg),config:cfg}}});"
      },
      "id": "b22b42d7-99be-47fc-8daf-2a5925057c03",
      "name": "Classify Proposal Window",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1620,
        0
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const out=[];for(const x of $input.all().map(i=>i.json)){const contacts=x.pre.contacts||[],companies=x.pre.companies||[];out.push({json:{kind:'contact',id:contacts[0]||'0',...x}});out.push({json:{kind:'company',id:companies[0]||'0',...x}})}return out;"
      },
      "id": "03942c05-bd66-4715-ba2a-7a55fe289359",
      "name": "Build Contact and Company Reads",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1890,
        0
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "={{ 'https://api.hubapi.com/crm/v3/objects/'+($json.kind==='contact'?'contacts':'companies')+'/'+$json.id+'?properties='+encodeURIComponent(($json.kind==='contact'?[\"firstname\",\"lastname\",\"email\",\"phone\",\"hubspot_owner_id\",\"hs_email_optout\",\"revops_do_not_contact\"]:[\"name\",\"domain\",\"industry\",\"numberofemployees\",\"num_associated_deals\"]).join(',')) }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 30000,
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "aa912261-7569-4f4a-a6ed-8ea529fcce6f",
      "name": "Fetch Sales Context",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        0,
        300
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_HUBSPOT_CREDENTIAL",
          "name": "ATTRIBUTION HubSpot"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const req=$('Build Contact and Company Reads').all(),res=$input.all(),groups=new Map();for(let i=0;i<req.length;i++){const q=req[i].json,key=String(q.deal.id);if(!groups.has(key))groups.set(key,{deal:q.deal,pre:q.pre,config:q.config,contact:null,company:null});if(res[i].json.statusCode===200)groups.get(key)[q.kind]=res[i].json.body}return [...groups.values()].map(x=>({json:x}));"
      },
      "id": "cefaa7f7-79a9-43dd-ad6f-200815c60815",
      "name": "Assemble Deal Context",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        270,
        300
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "return $input.all().map(item=>{const x=item.json;const companyId=x.company?.id;const body=companyId?{filterGroups:[{filters:[{propertyName:'associations.company',operator:'EQ',value:String(companyId)},{propertyName:'hs_is_closed',operator:'EQ',value:'false'}]}],properties:['dealname','dealstage'],limit:100}:{filterGroups:[{filters:[{propertyName:'hs_object_id',operator:'EQ',value:'0'}]}],properties:['dealname'],limit:1};return {json:{...x,body}}});"
      },
      "id": "8accd1be-0eea-47a1-a1b7-2dd3b9526cc4",
      "name": "Build Open Company Deal Checks",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        540,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.hubapi.com/crm/v3/objects/deals/search",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{ $json.body }}",
        "options": {
          "timeout": 30000,
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "ba477391-fe2b-4589-bbe0-69b27a156bed",
      "name": "Check Other Open Company Deals",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        810,
        300
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_HUBSPOT_CREDENTIAL",
          "name": "ATTRIBUTION HubSpot"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const G = (function ghostedLibrary(){\n  const text=v=>String(v??'').trim();\n  const bool=v=>['true','1','yes','on'].includes(text(v).toLowerCase());\n  const ms=v=>{if(v==null||v==='')return null;const n=Number(v);if(Number.isFinite(n)&&n>100000000000)return n;const d=Date.parse(v);return Number.isFinite(d)?d:null};\n  const iso=v=>{const n=ms(v);return n?new Date(n).toISOString():null};\n  const latest=(...values)=>values.map(ms).filter(Boolean).sort((a,b)=>b-a)[0]||null;\n  const assoc=(record,type)=>(record.associations?.[type]?.results||[]).map(x=>String(x.id));\n  function classify(deal,config,now=Date.now()){\n    const p=deal.properties||{},proposal=ms(p.revops_proposal_sent_at),reasons=[];\n    if(!proposal)return {action:'MANUAL',status:'MISSING_PROPOSAL_DATE',reasons:['MISSING_PROPOSAL_DATE']};\n    const age=(now-proposal)/86400000;if(age<config.min_age_days||age>config.max_age_days)return {action:'SKIP',status:'OUTSIDE_FOLLOWUP_WINDOW',age_days:age,reasons:[]};\n    if(!config.proposal_stage_ids.includes(text(p.dealstage))||bool(p.hs_is_closed))return {action:'SKIP',status:'DEAL_NO_LONGER_ELIGIBLE',age_days:age,reasons:[]};\n    const activity=latest(p.hs_last_activity_date,p.hs_last_sales_activity_timestamp,p.notes_last_updated,p.hs_last_contacted,p.revops_last_reply_at);\n    const meaningful=activity;\n    const grace=Number(config.activity_grace_minutes||15)*60000;if(meaningful&&meaningful>proposal+grace)return {action:'SKIP',status:'RECENT_ACTIVITY_FOUND',age_days:age,last_activity_at:new Date(meaningful).toISOString(),reasons:[]};\n    if(['FOLLOWED_UP','RE_ENGAGED','MANUAL_REVIEW','APPROVAL_PENDING','SENDING'].includes(text(p.revops_ghosting_status)))return {action:'SKIP',status:'ALREADY_HANDLED',age_days:age,reasons:[]};\n    const contacts=assoc(deal,'contacts'),companies=assoc(deal,'companies');\n    if(contacts.length!==1)reasons.push(contacts.length?'MULTIPLE_CONTACTS':'MISSING_CONTACT');if(companies.length>1)reasons.push('MULTIPLE_COMPANIES');\n    return {action:reasons.length?'MANUAL':'CONTEXT',status:reasons.length?'ASSOCIATION_REVIEW_REQUIRED':'CONTEXT_REQUIRED',age_days:age,proposal_sent_at:new Date(proposal).toISOString(),contacts,companies,last_activity_at:activity?new Date(activity).toISOString():null,reasons};\n  }\n  function finalize(deal,contact,company,openCompanyDeals,config,pre){\n    const reasons=[...(pre.reasons||[])],p=deal.properties||{},cp=contact?.properties||{},channel=text(p.revops_proposal_channel||'email').toLowerCase();\n    if(company&&openCompanyDeals>1)reasons.push('MULTIPLE_ACTIVE_COMPANY_DEALS');\n    if(channel==='email'&&!text(cp.email))reasons.push('MISSING_CONTACT_EMAIL');\n    if(channel==='email'&&(bool(cp.hs_email_optout)||bool(cp.revops_do_not_contact)))reasons.push('CONTACT_SUPPRESSED');\n    if(!['email','crm email'].includes(channel))reasons.push('CHANNEL_REQUIRES_MANUAL_SEND');\n    const amount=Number(p.amount||p.revops_proposal_value||0);if(!Number.isFinite(amount))reasons.push('INVALID_DEAL_VALUE');\n    const highValue=Number.isFinite(amount)&&amount>=config.high_value_threshold;\n    return {deal_id:String(deal.id),deal,contact:contact||null,company:company||null,proposal_sent_at:pre.proposal_sent_at,channel,amount:Number.isFinite(amount)?amount:0,high_value:highValue,requires_approval:highValue&&!reasons.length,action:reasons.length?'MANUAL':'READY',status:reasons.length?'MANUAL_REVIEW_REQUIRED':highValue?'APPROVAL_REQUIRED':'READY_TO_DRAFT',reasons};\n  }\n  function draft(c){const d=c.deal.properties||{},p=c.contact?.properties||{};const first=text(p.firstname)||'there';const proposal=text(d.revops_service_proposed||d.dealname||'proposal');const pain=text(d.revops_main_pain_point);const next=text(d.revops_expected_next_step)||'review any questions together';const objection=text(d.revops_previous_objections);let body=`Hi ${first},\\n\\nI wanted to follow up on the ${proposal} I sent over last week.`;if(pain)body+=` Based on our conversation about ${pain}, I wanted to make sure the proposal covers the priorities we discussed.`;if(objection)body+=` I’m also happy to clarify the points around ${objection}.`;body+=`\\n\\nWould it be helpful to ${next}? If priorities have changed, just let me know and I can adjust the direction.\\n\\nBest,\\n${text(d.revops_salesperson_name)||'Your sales representative'}`;return {subject:`Quick follow-up on ${text(d.dealname)||'the proposal'}`,body};}\n  function revalidate(c,deal,contact,config){const current=classify(deal,config,Date.now()),reasons=[];if(!['CONTEXT','READY'].includes(current.action)&&current.status!=='CONTEXT_REQUIRED')reasons.push('DEAL_CHANGED_OR_ACTIVITY_FOUND');if(iso(deal.properties?.revops_proposal_sent_at)!==iso(c.proposal_sent_at))reasons.push('PROPOSAL_DATE_CHANGED');if(text(deal.properties?.revops_followup_sent_at))reasons.push('FOLLOWUP_ALREADY_SENT');if(!contact||text(contact.properties?.email).toLowerCase()!==text(c.contact?.properties?.email).toLowerCase())reasons.push('CONTACT_EMAIL_CHANGED_OR_MISSING');if(bool(contact?.properties?.hs_email_optout)||bool(contact?.properties?.revops_do_not_contact))reasons.push('CONTACT_SUPPRESSED');return {safe:!reasons.length,reasons};}\n  return {text,bool,ms,iso,latest,assoc,classify,finalize,draft,revalidate};\n})();\nconst contexts=$('Build Open Company Deal Checks').all(),responses=$input.all();return contexts.map((item,i)=>{const x=item.json,r=responses[i].json;if(r.statusCode!==200)throw new Error('Company deal check failed');let result;if(x.pre.action==='CONTEXT')result=G.finalize(x.deal,x.contact,x.company,Number(r.body.total||0),x.config,x.pre);else result={deal_id:String(x.deal.id),deal:x.deal,contact:x.contact,company:x.company,proposal_sent_at:x.pre.proposal_sent_at||null,action:x.pre.action,status:x.pre.status,reasons:x.pre.reasons||[],high_value:false,requires_approval:false};return {json:result}});"
      },
      "id": "d31e0755-a592-436a-b468-1919e643a640",
      "name": "Finalize Eligibility",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1080,
        300
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT ghosted_proposal.upsert_case($1::jsonb) AS result;",
        "options": {
          "queryReplacement": "={{ [JSON.stringify($json)] }}"
        }
      },
      "id": "af6e6fec-faea-422c-b829-c32c03814cd8",
      "name": "Persist Detection Decisions",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        1350,
        300
      ],
      "credentials": {
        "postgres": {
          "id": "REPLACE_POSTGRES_CREDENTIAL",
          "name": "ATTRIBUTION Postgres"
        }
      }
    },
    {
      "parameters": {
        "content": "## Daily detector\nConfigure proposal stage internal IDs. Reads all open deals in those stages, then applies the 5–7 day window and activity suppression.\nUses authoritative deal/contact/company reads. More than 100 open proposal deals stops the run instead of silently processing a partial page.\nDetection only writes the PostgreSQL audit/state store.",
        "width": 700,
        "height": 220
      },
      "id": "437b9301-ed77-424d-a06d-60590fe974a6",
      "name": "Workflow Guide",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        -270
      ]
    }
  ],
  "connections": {
    "Daily Proposal Scan": {
      "main": [
        [
          {
            "node": "Scanner Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scanner Configuration": {
      "main": [
        [
          {
            "node": "Search Open Proposal Deals",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Open Proposal Deals": {
      "main": [
        [
          {
            "node": "Validate and Expand Deals",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate and Expand Deals": {
      "main": [
        [
          {
            "node": "Read Deal With Associations",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Deal With Associations": {
      "main": [
        [
          {
            "node": "Classify Proposal Window",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Proposal Window": {
      "main": [
        [
          {
            "node": "Build Contact and Company Reads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Contact and Company Reads": {
      "main": [
        [
          {
            "node": "Fetch Sales Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Sales Context": {
      "main": [
        [
          {
            "node": "Assemble Deal Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Assemble Deal Context": {
      "main": [
        [
          {
            "node": "Build Open Company Deal Checks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Open Company Deal Checks": {
      "main": [
        [
          {
            "node": "Check Other Open Company Deals",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Other Open Company Deals": {
      "main": [
        [
          {
            "node": "Finalize Eligibility",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Finalize Eligibility": {
      "main": [
        [
          {
            "node": "Persist Detection Decisions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run Scan Manually": {
      "main": [
        [
          {
            "node": "Scanner Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "timezone": "Asia/Tbilisi",
    "saveExecutionProgress": true
  },
  "active": false,
  "pinData": {},
  "tags": []
}
