← back to Dw Signup Fulfillment
verification/tk10836/verify-units-not-lines.js
59 lines
#!/usr/bin/env node
// TK-10836/TK-11366: the store's own cart copy says "10 samples or fewer ship free (no tracking);
// over 10 units include a shipping charge." The free rate's condition is TOTAL_PRICE <= $45.00,
// and 10 x $4.25 = $42.50 <= $45 < 11 x $4.25 = $46.75. So the rule is UNIT-count, enforced by price.
// MY EARLIER COHORT COUNTED LINE ITEMS, NOT UNITS. Redo it by UNITS.
const fs=require('fs'),os=require('os');
for (const line of fs.readFileSync(os.homedir()+'/Projects/secrets-manager/.env','utf8').split('\n')) {
const m=line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)$/); if(!m) continue;
let v=m[2].trim().replace(/^['"]|['"]$/g,''); if(!process.env[m[1]]) process.env[m[1]]=v;
}
const STORE=process.env.SHOPIFY_STORE_DOMAIN, TOKEN=process.env.SHOPIFY_FULL_ACCESS_TOKEN, API='2024-10';
async function gql(q,v={}){const r=await fetch(`https://${STORE}/admin/api/${API}/graphql.json`,{method:'POST',
headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v})});
const j=await r.json(); if(j.errors)console.error('ERR',JSON.stringify(j.errors).slice(0,300)); return j.data;}
const Q=`query($c:String){ orders(first:40, after:$c, query:"created_at:>2026-06-01", sortKey:CREATED_AT){
pageInfo{hasNextPage endCursor}
nodes{ name createdAt totalShippingPriceSet{shopMoney{amount}} shippingLine{title}
shippingAddress{countryCodeV2}
lineItems(first:30){ nodes{ sku title quantity
originalTotalSet{shopMoney{amount}} discountedTotalSet{shopMoney{amount}} } } } } }`;
(async()=>{
let c=null, rows=[];
do{ const d=await gql(Q,{c}); if(!d) break;
for(const o of d.orders.nodes){
const lis=o.lineItems.nodes; if(!lis.length) continue;
if(!lis.every(li=>/(^|-)sample/i.test(li.sku||'')||/sample|memo|swatch/i.test(li.title||''))) continue;
if(o.shippingAddress?.countryCodeV2!=='US') continue;
const units=lis.reduce((s,li)=>s+li.quantity,0);
const listPrice=lis.reduce((s,li)=>s+Number(li.originalTotalSet?.shopMoney?.amount||0),0);
const charged=lis.reduce((s,li)=>s+Number(li.discountedTotalSet?.shopMoney?.amount||0),0);
rows.push({name:o.name,date:o.createdAt.slice(0,10),lines:lis.length,units,listPrice,charged,
ship:Number(o.totalShippingPriceSet.shopMoney.amount),rate:o.shippingLine?.title||'(none)'});
} c=d.orders.pageInfo.hasNextPage?d.orders.pageInfo.endCursor:null; }while(c);
const T=45.00;
console.log(`US sample-only orders since 2026-06-01: n=${rows.length}\n`);
console.log('### Does LIST price (pre-discount) crossing $45.00 predict paying for shipping? ###');
for(const [lab,f] of [['LIST <= $45 (<=10 units)',r=>r.listPrice<=T],['LIST > $45 (>10 units)',r=>r.listPrice>T]]){
const s=rows.filter(f), free=s.filter(r=>r.ship===0).length;
console.log(` ${lab}: n=${s.length} free=${free} (${s.length?(100*free/s.length).toFixed(0):0}%) paid=${s.length-free}`);
}
console.log('\n### Same split, but by CHARGED (post-discount) price ###');
for(const [lab,f] of [['CHARGED <= $45',r=>r.charged<=T],['CHARGED > $45',r=>r.charged>T]]){
const s=rows.filter(f), free=s.filter(r=>r.ship===0).length;
console.log(` ${lab}: n=${s.length} free=${free} (${s.length?(100*free/s.length).toFixed(0):0}%)`);
}
console.log('\n### Median UNITS per order, before vs after 2026-08-18 ###');
const med=a=>{a=[...a].sort((x,y)=>x-y);return a.length?a[Math.floor(a.length/2)]:0;};
for(const [lab,lo,hi] of [['Jun 1 - Aug 17','2026-06-01','2026-08-17'],['Aug 18 - Sep 10','2026-08-18','2026-09-30']]){
const s=rows.filter(r=>r.date>=lo&&r.date<=hi);
const over=s.filter(r=>r.listPrice>T).length;
console.log(` ${lab}: n=${s.length} medianUnits=${med(s.map(r=>r.units))} over-$45=${over} (${s.length?(100*over/s.length).toFixed(0):0}%) free=${(100*s.filter(r=>r.ship===0).length/(s.length||1)).toFixed(0)}%`);
}
console.log('\n### PAID orders that were UNDER $45 list (i.e. NOT explained by the >10-unit rule) ###');
const anom=rows.filter(r=>r.ship>0&&r.listPrice<=T);
console.log(` n=${anom.length}`);
for(const r of anom.slice(-18)) console.log(` ${r.name} ${r.date} units=${r.units} list=$${r.listPrice.toFixed(2)} charged=$${r.charged.toFixed(2)} ship=$${r.ship.toFixed(2)} "${r.rate}"`);
fs.writeFileSync('verification/tk10836/units-analysis.json',JSON.stringify({generated:new Date().toISOString(),rows},null,2));
})();