Step 1 of 2

Select your
POS system

Upload your sales export and get AI-powered insights backed by your actual numbers.

1
In Toast, go to Reports in the left sidebar
2
Click SalesSales by Day — this gives daily revenue, orders & guest counts
3
Set your date range (90+ days recommended), then click Export to CSV
4
Optional: Also export Item Selection Report under Sales → Menu for item-level data
💡 Revy auto-detects Toast’s date format — just upload the CSV as-is, no editing needed.
1
Go to squareup.comReports in the left menu
2
Click Sales Summary or Transactions for a full date range
3
Set your date range to Last 90 Days or custom, then click ExportCSV
4
Upload the downloaded CSV directly to Revy
💡 The Transactions export gives the most data. Sales Summary also works well.
1
Log into clover.com › go to Reports
2
Select Sales from the report type dropdown
3
Choose your date range (90 days or more for best insights), click Export
4
Select CSV format and download
💡 Use the Payments report if Sales export is unavailable — both formats are supported.
1
In Lightspeed Restaurant, go to ReportsSales
2
Select Daily Sales Summary or Sales by Day
3
Set your date range, then click the Export button (top right) → CSV
4
Upload the CSV to Revy — no formatting required
💡 Lightspeed’s Business Insights export also works if you need more granular data.
1
Export any CSV or Excel file with a date column and a revenue/sales column
2
Revy auto-detects columns — common names like Date, Net Sales, Revenue, Total all work
3
Include as many columns as possible (orders, guests, items) for richer insights
4
90+ days of data gives the best anomaly detection and trend analysis
💡 Supported formats: .csv, .xlsx, .xls. Column names are flexible — Revy will figure it out.
Supported formats

Toast Sales by Day · Toast Item Selection · Square · Clover · Lightspeed · Any CSV or XLSX with date & revenue columns

Step 2 of 2

Upload your
sales report

Revy auto-detects your format. No editing needed.

💡 Upload the largest date range available — filter down after upload.
Drop your file here
or click to browse · CSV, XLSX, XLS
Analyzing file…
Reading file…
Parsing sales records
Calculating daily & weekly patterns
Detecting anomalies & trends
Running AI analysis
Running AI analysis

Sales Intelligence Report

Your restaurant intelligence report
Free plan: Charts & data always free. Upgrade to Pro to unlock AI insights, anomaly detection, and trend warnings.
Re-analyzing selected date range…
var state={pos:null,posName:null,allRows:[],fileMin:null,fileMax:null,filterStart:null,filterEnd:null,salesData:null,report:null,reportType:null,isPro:false,proEmail:null}; document.getElementById('yr').textContent=new Date().getFullYear(); function setCookie(n,v,d){var e=new Date();e.setTime(e.getTime()+(d*864e5));document.cookie=n+'='+encodeURIComponent(v)+';expires='+e.toUTCString()+';path=/;SameSite=Lax';} function getCookie(n){var k=n+'=',ca=document.cookie.split(';');for(var i=0;i(i+1)*18)document.getElementById('ps-'+i).classList.add('done');} } function parseCSV(text){ var lines=text.trim().split('\n').filter(function(l){return l.trim();}); if(lines.length<2)return[]; var headers=lines[0].split(',').map(function(h){return h.replace(/"/g,'').trim().toLowerCase();}); return lines.slice(1).map(function(line){ var vals=[],cur='',inQ=false; for(var i=0;i-1||keys.some(function(k){return k.includes('net sales');}))return'sales_by_day'; if(keys.some(function(k){return k.includes('item name')||k.includes('menu item');}))return'item_selection'; return'generic'; } function parseToastDate(raw){ var s=String(raw).trim().replace(/[^0-9]/g,''); if(s.length===8){var dt=new Date(parseInt(s.slice(0,4)),parseInt(s.slice(4,6))-1,parseInt(s.slice(6,8)));if(!isNaN(dt.getTime()))return dt;} var dt2=new Date(raw);return isNaN(dt2.getTime())?null:dt2; } function attachDates(rows,rt){ return rows.map(function(row){ var parsed=null; if(rt==='sales_by_day'){parsed=parseToastDate(row['yyyymmdd']||row['date']||row['business date']||'');} else{var keys=Object.keys(row);for(var i=0;i0&&rev===0)rev=v;}}); if(rt!=='sales_by_day')orders=1; if(rev>0)data.totalRevenue+=rev; data.totalOrders+=orders||1;data.totalGuests+=guests; var d=row._date; if(d){ var dk=d.toISOString().slice(0,10); data.byDay[dk]=(data.byDay[dk]||0)+rev; var dow=DN[d.getDay()];data.byDOW[dow]=(data.byDOW[dow]||0)+rev; var mk=d.toLocaleString('default',{month:'short'})+" '"+String(d.getFullYear()).slice(2); data.byMonth[mk]=(data.byMonth[mk]||0)+rev; data.dates.push(d); data.dailyRows.push({date:d,dateKey:dk,rev:rev,orders:orders,guests:guests,avgCheck:orders>0?(rev/orders):0}); } }); data.dailyRows.sort(function(a,b){return a.date-b.date;}); var dowC={}; data.dailyRows.forEach(function(r){var dow=DN[r.date.getDay()];if(!dowC[dow])dowC[dow]={rev:0,count:0,orders:0,guests:0};dowC[dow].rev+=r.rev;dowC[dow].count++;dowC[dow].orders+=r.orders;dowC[dow].guests+=r.guests;}); data.dowAvg={}; Object.keys(dowC).forEach(function(d){var c=dowC[d];data.dowAvg[d]={avg:c.rev/c.count,count:c.count,orders:c.orders/c.count,guests:c.guests/c.count};}); var ds=Object.entries(data.dowAvg).sort(function(a,b){return b[1].avg-a[1].avg;}); data.peakDOW=ds[0]?ds[0][0]:'N/A';data.slowDOW=ds[ds.length-1]?ds[ds.length-1][0]:'N/A'; var mid=Math.floor(data.dailyRows.length/2); var a1=mid?data.dailyRows.slice(0,mid).reduce(function(s,r){return s+r.rev;},0)/mid:0; var a2=(data.dailyRows.length-mid)?data.dailyRows.slice(mid).reduce(function(s,r){return s+r.rev;},0)/(data.dailyRows.length-mid):0; data.trendPct=a1>0?((a2-a1)/a1*100):0;data.trendDir=data.trendPct>=0?'up':'down'; var revs=data.dailyRows.map(function(r){return r.rev;}); var mean=revs.reduce(function(s,v){return s+v;},0)/(revs.length||1); var std=Math.sqrt(revs.reduce(function(s,v){return s+Math.pow(v-mean,2);},0)/(revs.length||1)); data.anomalies=data.dailyRows.filter(function(r){return Math.abs(r.rev-mean)>1.8*std;}).map(function(r){return{date:r.date,rev:r.rev,mean:mean,type:r.rev>mean?'spike':'drop'};}); var wkMap={}; data.dailyRows.forEach(function(r){var dow=r.date.getDay();var mon=new Date(r.date);mon.setDate(r.date.getDate()-((dow+6)%7));var wk=mon.toISOString().slice(0,10);if(!wkMap[wk])wkMap[wk]={rev:0,orders:0,guests:0,days:0,weekStart:mon};wkMap[wk].rev+=r.rev;wkMap[wk].orders+=r.orders;wkMap[wk].guests+=r.guests;wkMap[wk].days++;}); data.weeks=Object.entries(wkMap).sort(function(a,b){return a[0]0;}).slice(-3).reverse(); var uniq=[...new Set(data.dates.map(function(d){return d.toDateString();}))].length||1; data.dailyAvg=(data.totalRevenue/uniq).toFixed(2); data.avgCheck=data.totalOrders>0?(data.totalRevenue/data.totalOrders).toFixed(2):'0'; var topQ=data.dailyRows.slice().sort(function(a,b){return b.rev-a.rev;}).slice(0,Math.max(1,Math.floor(data.dailyRows.length*0.25))); data.bestDayAvg=(topQ.reduce(function(s,r){return s+r.rev;},0)/topQ.length).toFixed(2); var _gap=parseFloat(data.bestDayAvg)-parseFloat(data.dailyAvg);if(isNaN(_gap)||_gap<0)_gap=0; data.dailyGap=_gap.toFixed(2);data.monthlyOpportunity=Math.round(_gap*30*0.5); var _dn=['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],_dc={}; topQ.forEach(function(r){var d=_dn[r.date.getDay()];_dc[d]=(_dc[d]||0)+1;}); data.bestDOWpattern=Object.entries(_dc).sort(function(a,b){return b[1]-a[1];}).slice(0,2).map(function(e){return e[0];}).join(' and '); data.avgGuests=uniq>0?(data.totalGuests/uniq).toFixed(1):'0'; data.daysCovered=uniq; if(data.dates.length>0){ var ts=data.dates.map(function(d){return d.getTime();}); data.startDate=new Date(Math.min.apply(null,ts));data.endDate=new Date(Math.max.apply(null,ts)); data.dateRangeLabel=data.startDate.toLocaleDateString('en-US',{month:'short',day:'numeric'})+' \u2013 '+data.endDate.toLocaleDateString('en-US',{month:'short',day:'numeric',year:'numeric'}); }else data.dateRangeLabel='All dates'; return data; } function buildPrompt(sd){ var DO=['Mon','Tue','Wed','Thu','Fri','Sat','Sun']; var dowL=DO.filter(function(d){return sd.dowAvg[d];}).map(function(d){ var v=sd.dowAvg[d]; return d+': $'+v.avg.toFixed(0)+'/day avg ('+v.count+' days'+( v.orders>0?', '+v.orders.toFixed(1)+' avg orders':'')+')'; }).join('\n'); var wkL=sd.weeks.map(function(w,i){ var prev=i>0?sd.weeks[i-1]:null; var wow=prev&&prev.rev>0?((w.rev-prev.rev)/prev.rev*100):null; return 'Week of '+w.weekStart.toLocaleDateString('en-US',{month:'short',day:'numeric'})+': $'+w.rev.toFixed(0)+(wow!==null?' ('+(wow>=0?'+':'')+wow.toFixed(1)+'% WoW)':''); }).join('\n'); var bst=sd.bestDays.slice(0,5).map(function(r){ var dow=['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][r.date.getDay()]; return r.date.toLocaleDateString('en-US',{month:'short',day:'numeric'})+' ('+dow+'): $'+r.rev.toFixed(0); }).join(', '); var wst=sd.worstDays.slice(0,5).map(function(r){ var dow=['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][r.date.getDay()]; return r.date.toLocaleDateString('en-US',{month:'short',day:'numeric'})+' ('+dow+'): $'+r.rev.toFixed(0); }).join(', '); var anomL=sd.anomalies.length?sd.anomalies.map(function(a){ var dow=['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][a.date.getDay()]; return a.date.toLocaleDateString('en-US',{month:'short',day:'numeric'})+' ('+dow+'): $'+a.rev.toFixed(0)+' -- '+(a.type==='spike'?'SPIKE':'DROP')+' vs avg $'+a.mean.toFixed(0); }).join('\n'):'None'; return 'You are a sharp restaurant business analyst. Analyze this data and respond with EXACTLY this format -- four sections with these exact headers, each with 4-5 bullet points starting with *. Every bullet must reference a specific date, dollar amount, or day name. No generic advice.\n\n##INSIGHTS##\n* [insight]\n\n##ANOMALIES##\n* [anomaly]\n\n##SPECIALS##\n* [special]\n\n##OPPORTUNITY##\n* [opportunity]\n\nDATA SUMMARY ('+sd.daysCovered+' days, '+sd.dateRangeLabel+'):\n- Total: $'+sd.totalRevenue.toFixed(0)+'\n- Daily Avg: $'+sd.dailyAvg+'\n- Best-Day Avg (top 25%): $'+sd.bestDayAvg+'\n- Daily Gap: $'+sd.dailyGap+'/day\n- Monthly Opportunity (close half gap): $'+sd.monthlyOpportunity+'\n- Avg Check: $'+sd.avgCheck+'\n- Orders: '+sd.totalOrders+', Guests: '+sd.totalGuests+'\n- Trend: '+Math.abs(sd.trendPct).toFixed(1)+'% '+(sd.trendDir==='up'?'up':'down')+'\n- Peak DOW: '+sd.peakDOW+' | Slow DOW: '+sd.slowDOW+'\n\nTOP 5 DAYS:\n'+bst+'\n\nWORST 5 DAYS:\n'+wst+'\n\nDOW AVERAGES:\n'+dowL+'\n\nWEEK-OVER-WEEK:\n'+wkL+'\n\nANOMALIES:\n'+anomL; } function extractJSON(raw){ if(!raw)return null; // Try JSON first (legacy) try{ var c=raw.replace(/```json|```/gi,'').trim(); var s=c.indexOf('{'),e=c.lastIndexOf('}'); if(s>-1&&e>-1){var parsed=JSON.parse(c.slice(s,e+1));if(parsed.insights)return parsed;} }catch(err){} // Parse section headers var sections={insights:null,anomalies:null,specials:null,opportunity:null}; var keys=['INSIGHTS','ANOMALIES','SPECIALS','OPPORTUNITY']; var mapped={INSIGHTS:'insights',ANOMALIES:'anomalies',SPECIALS:'specials',OPPORTUNITY:'opportunity'}; for(var i=0;i=firstHalf?'up':'down'; var peakAvg=sd.dowAvg[sd.peakDOW]?Math.round(sd.dowAvg[sd.peakDOW].avg).toLocaleString():'--'; var slowAvg=sd.dowAvg[sd.slowDOW]?Math.round(sd.dowAvg[sd.slowDOW].avg).toLocaleString():'--'; var slowCount=sd.dowAvg[sd.slowDOW]?sd.dowAvg[sd.slowDOW].count:sd.daysCovered/7; var peakCount=sd.dowAvg[sd.peakDOW]?sd.dowAvg[sd.peakDOW].count:sd.daysCovered/7; var upsellEst=Math.round(sd.totalOrders*0.3*5).toLocaleString(); var weeklyLift=Math.round(10*slowCount).toLocaleString(); var checkLift=Math.round(parseFloat(sd.avgCheck)*0.15).toLocaleString(); var monthlyFmt=parseInt(sd.monthlyOpportunity).toLocaleString(); var yearlyFmt=Math.round(sd.monthlyOpportunity*12).toLocaleString(); var gapFmt=parseFloat(sd.dailyGap).toLocaleString(); var anomList=sd.anomalies.length?sd.anomalies.slice(0,3).map(function(a){ var dow=['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][a.date.getDay()]; return a.date.toLocaleDateString('en-US',{month:'short',day:'numeric'})+' ('+dow+') -- $'+Math.round(a.rev).toLocaleString()+', '+(a.type==='spike'?'spike':'drop')+' vs $'+Math.round(a.mean).toLocaleString()+' avg'; }):''; return{ insights:( '* Revenue ran $'+halfDiff+' '+halfDir+' in the second half of this period vs the first. '+(halfDir==='up'?'Momentum is building.':'Worth understanding what shifted.')+'\n' +'* Best day: '+bstStr+'. Worst day: '+wstStr+'. The $'+Math.round((bst&&wst)?bst.rev-wst.rev:0).toLocaleString()+' gap between them is not luck -- it shows what this location can do when conditions are right.\n' +'* '+sd.peakDOW+' ($'+peakAvg+' avg) vs '+sd.slowDOW+' ($'+slowAvg+' avg). A 4x difference between your best and worst day of week is a significant operational opportunity.\n' +'* At $'+sd.avgCheck+' avg check, a $'+checkLift+' upsell on 1 in 4 tables adds ~$'+upsellEst+' over '+sd.daysCovered+' days without adding a single new customer.' ), anomalies:( (anomList.length?anomList.map(function(s){return '* '+s;}).join('\n')+'\n':'') +'* Strongest days: '+sd.bestDays.slice(0,3).map(function(r){return r.date.toLocaleDateString('en-US',{month:'short',day:'numeric'})+' $'+Math.round(r.rev).toLocaleString();}).join(', ')+'\n' +'* Weakest days: '+sd.worstDays.slice(0,3).map(function(r){return r.date.toLocaleDateString('en-US',{month:'short',day:'numeric'})+' $'+Math.round(r.rev).toLocaleString();}).join(', ') ), specials:( '* '+sd.slowDOW+' is your floor at $'+slowAvg+'/day. A targeted happy hour or limited special on '+sd.slowDOW+' evenings could realistically add $'+Math.round(parseFloat(sd.avgCheck)*3).toLocaleString()+'-$'+Math.round(parseFloat(sd.avgCheck)*6).toLocaleString()+' per week.\n' +'* '+sd.peakDOW+' at $'+peakAvg+' carries the week. Make sure you are fully staffed, menu is tight, and the experience is consistent -- this is your brand day.\n' +'* The gap between '+sd.peakDOW+' and '+sd.slowDOW+' is $'+(Math.round(Math.abs((sd.dowAvg[sd.peakDOW]?sd.dowAvg[sd.peakDOW].avg:0)-(sd.dowAvg[sd.slowDOW]?sd.dowAvg[sd.slowDOW].avg:0)))).toLocaleString()+'/day. Even moving '+sd.slowDOW+' halfway to your weekly average adds $'+weeklyLift+' back over this period.\n' +'* Consider a consistent weekly promotion on '+sd.slowDOW+' -- same time, same offer, marketed in advance so regulars plan around it.' ), opportunity:( '* Your daily avg is $'+sd.dailyAvg+'. Your top-quartile days avg $'+sd.bestDayAvg+'. The $'+gapFmt+' gap is not a ceiling -- you have already cleared it '+Math.round(sd.dailyRows.length*0.25)+' times in this dataset.\n' +'* Close half that gap and you add $'+monthlyFmt+'/month, $'+yearlyFmt+'/year. That is real money: an extra employee, a marketing budget, or margin you are not currently capturing.\n' +'* '+sd.bestDOWpattern+' drive your best numbers. Understand exactly why -- is it volume, check size, or both -- then replicate those conditions on your slowest days.\n' +'* At $'+sd.avgCheck+' avg check, your leverage is in check size, not just covers. One additional item per table on '+sd.slowDOW+' could close a meaningful portion of the gap.' ) }; } async function runAI(sd){ if(!state.isPro){renderInsights(null,false,true);return;} document.getElementById('reanalyze-note').classList.add('show'); renderInsights(null,true,false); try{var res=await fetch('/.netlify/functions/analyze',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({prompt:buildPrompt(sd)})});if(!res.ok){var errBody=await res.text();console.error('analyze HTTP error:',res.status,errBody);throw new Error('HTTP '+res.status+': '+errBody);}var d=await res.json();var parsed=extractJSON(d.raw);if(!parsed)console.error('Parse failed, raw:',d.raw);state.report=parsed||fallbackReport(sd);} catch(e){console.error('analyze error:',e.message||e);state.report=fallbackReport(sd);} document.getElementById('reanalyze-note').classList.remove('show'); renderInsights(state.report,false,false); } async function processFile(file){ showScreen('processing'); document.getElementById('proc-title').textContent='Analyzing '+file.name+'\u2026'; setProgress(10,'Reading file\u2026'); var rows=[]; try{if(/\.csv$/i.test(file.name)){rows=parseCSV(await file.text());}else{var buf=await file.arrayBuffer();var wb=XLSX.read(buf,{type:'array'});var ws=wb.Sheets[wb.SheetNames[0]];rows=XLSX.utils.sheet_to_json(ws,{defval:''}).map(function(r){var lo={};Object.entries(r).forEach(function(e){lo[e[0].toLowerCase().trim()]=String(e[1]);});return lo;});}}catch(e){console.error(e);} setProgress(25,'Detecting format\u2026'); var rt=detectReportType(rows);state.reportType=rt; setProgress(40,'Parsing dates\u2026'); var dr=attachDates(rows,rt); var vd=dr.map(function(r){return r._date;}).filter(Boolean); if(vd.length){var ts=vd.map(function(d){return d.getTime();});state.fileMin=new Date(Math.min.apply(null,ts));state.fileMax=new Date(Math.max.apply(null,ts));state.filterStart=state.fileMin;state.filterEnd=state.fileMax;} state.allRows=dr; setProgress(60,'Detecting anomalies\u2026'); state.salesData=extractSalesData(dr,rt); setProgress(78,'Running AI\u2026'); await runAI(state.salesData); setProgress(100,'Complete'); setTimeout(function(){renderResults(state.salesData);showScreen('results');},300); } function filterRows(s,e){var st=s.getTime(),en=e.getTime()+86399999;return state.allRows.filter(function(r){return !r._date||(r._date.getTime()>=st&&r._date.getTime()<=en);});} function fmtDate(d){return d?d.toISOString().slice(0,10):'';} function parseDate(s){return s?new Date(s+'T00:00:00'):null;} function onDateChange(){var s=parseDate(document.getElementById('date-start').value);var e=parseDate(document.getElementById('date-end').value);if(s&&e&&s<=e)applyDateFilter(s,e);} function applyDateFilter(start,end){state.filterStart=start;state.filterEnd=end;updateDateCount();updatePresetHighlight();state.salesData=extractSalesData(filterRows(start,end),state.reportType);renderResults(state.salesData);runAI(state.salesData);} function updateDateCount(){document.getElementById('date-count').textContent=Math.round((state.filterEnd-state.filterStart)/86400000)+1+' days';} function buildPresets(){ var min=state.fileMin,max=state.fileMax; var ps=[{label:'All time',s:min,e:max},{label:'Last 7d',s:new Date(Math.max(min.getTime(),max.getTime()-6*86400000)),e:max},{label:'Last 30d',s:new Date(Math.max(min.getTime(),max.getTime()-29*86400000)),e:max},{label:'Last 90d',s:new Date(Math.max(min.getTime(),max.getTime()-89*86400000)),e:max}]; var c=document.getElementById('presets');c.innerHTML=''; ps.forEach(function(p){var btn=document.createElement('button');btn.className='preset-btn';btn.textContent=p.label;btn.onclick=function(){applyDateFilter(p.s,p.e);document.getElementById('date-start').value=fmtDate(p.s);document.getElementById('date-end').value=fmtDate(p.e);};c.appendChild(btn);}); } function updatePresetHighlight(){ var s=fmtDate(state.filterStart),mx=fmtDate(state.fileMax); var starts=[fmtDate(state.fileMin),fmtDate(new Date(Math.max(state.fileMin.getTime(),state.fileMax.getTime()-6*86400000))),fmtDate(new Date(Math.max(state.fileMin.getTime(),state.fileMax.getTime()-29*86400000))),fmtDate(new Date(Math.max(state.fileMin.getTime(),state.fileMax.getTime()-89*86400000)))]; document.querySelectorAll('.preset-btn').forEach(function(btn,i){btn.classList.toggle('active',starts[i]===s&&mx===fmtDate(state.filterEnd));}); } function $h(tag,cls,html,style){var el=document.createElement(tag);if(cls)el.className=cls;if(html)el.innerHTML=html;if(style)el.style.cssText=style;return el;} function makeInsightCard(label,color,icon,content,loading,locked){ var wrap=$h('div','insight-wrap'); var inner=$h('div',locked?'insight-locked':''); var card=$h('div','insight-card');card.style.borderTopColor=color; card.appendChild($h('div','card-title tag',''+icon+' '+label+'')); var body=$h('div',''); if(loading){body.innerHTML=[100,85,92,70,88].map(function(w,i){return '
';}).join('');} else if(content){var bd=$h('div','insight-body');bd.textContent=content.replace(/^\* /gm,'\u2022 ');body.appendChild(bd);} card.appendChild(body);inner.appendChild(card); if(locked){var ov=$h('div','lock-overlay');ov.innerHTML='
🔒
Pro Insight
Upgrade to unlock '+label.toLowerCase()+' from your real data.
';var btn=$h('button','lock-btn','Unlock for $19.99/mo');btn.onclick=openModal;ov.appendChild(btn);inner.appendChild(ov);} wrap.appendChild(inner);return wrap; } function renderInsights(report,loading,locked){ var right=document.getElementById('insight-right');right.innerHTML=''; right.appendChild(makeInsightCard('Tailored Insights','#F0624D','↗',report?report.insights:null,loading,locked)); right.appendChild(makeInsightCard('Anomaly Detection','#C0392B','⚠',report?report.anomalies:null,loading,locked)); var bg=document.getElementById('bottom-grid');bg.innerHTML=''; bg.appendChild(makeInsightCard('Day-of-Week Specials','#2D6A4F','▲',report?report.specials:null,loading,locked)); bg.appendChild(makeInsightCard('Revenue Opportunity','#1D3557','△',report?report.opportunity:null,loading,locked)); } function renderResults(sd){ document.getElementById('res-tag').textContent=(state.posName||'')+' \u00b7 '+sd.dateRangeLabel+' \u00b7 '+sd.daysCovered+' days'; document.getElementById('res-date').textContent='Generated '+new Date().toLocaleDateString('en-US',{month:'long',day:'numeric',year:'numeric'}); if(state.fileMin&&state.fileMax){ var ds=document.getElementById('date-start'),de=document.getElementById('date-end'); ds.value=fmtDate(state.filterStart||state.fileMin);ds.min=fmtDate(state.fileMin);ds.max=fmtDate(state.fileMax); de.value=fmtDate(state.filterEnd||state.fileMax);de.min=fmtDate(state.fileMin);de.max=fmtDate(state.fileMax); buildPresets();updateDateCount();updatePresetHighlight(); document.getElementById('date-bar').style.display='flex'; } var kr=document.getElementById('kpi-row');kr.innerHTML=''; function kpi(label,val,sub,trend){var cls=trend==='up'?'up':trend==='down'?'down':'neu';var arr=trend==='up'?'\u2191':trend==='down'?'\u2193':'\u2013';kr.appendChild($h('div','kpi','
'+label+'
'+val+'
'+arr+' '+sub+'
'));} kpi('Total Revenue','$'+Math.round(sd.totalRevenue).toLocaleString(),sd.daysCovered+' days','neu'); kpi('Daily Average','$'+Math.round(sd.dailyAvg).toLocaleString(),'daily average',sd.trendDir==='up'?'up':'down'); kpi('Avg Check','$'+sd.avgCheck,'per order','neu'); kpi('Total Orders',sd.totalOrders.toLocaleString(),'across '+sd.daysCovered+' days','neu'); if(sd.totalGuests>0)kpi('Avg Guests/Day',sd.avgGuests,'per trading day','neu'); kpi('Trend',sd.trendDir==='up'?'\u25b2 Up':'\u25bc Down',Math.abs(sd.trendPct).toFixed(1)+'% vs first half of period',sd.trendDir==='up'?'up':'down'); var col=document.getElementById('chart-col');col.innerHTML=''; var DO=['Mon','Tue','Wed','Thu','Fri','Sat','Sun']; var dd=DO.filter(function(d){return sd.dowAvg[d];}).map(function(d){return{d:d,v:sd.dowAvg[d].avg};}); if(dd.length){ var maxD=Math.max.apply(null,dd.map(function(x){return x.v;})); var c1=$h('div','card');c1.appendChild($h('div','card-title tag','Avg revenue by day of week')); dd.forEach(function(x){var hi=x.v===maxD;var b=$h('div','mini-bar');b.innerHTML='
'+x.d+'$'+Math.round(x.v).toLocaleString()+'
';c1.appendChild(b);}); col.appendChild(c1); } if(sd.bestDays&&sd.bestDays.length){ var maxB=sd.bestDays[0].rev;var c2=$h('div','card');c2.appendChild($h('div','card-title tag','Top revenue days')); sd.bestDays.forEach(function(r,i){var dow=['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][r.date.getDay()];var lbl=r.date.toLocaleDateString('en-US',{month:'short',day:'numeric'})+' ('+dow+')';var b=$h('div','mini-bar');b.innerHTML='
'+lbl+'$'+Math.round(r.rev).toLocaleString()+'
';c2.appendChild(b);}); col.appendChild(c2); } renderInsights(state.report,false,!state.isPro); var ws=document.getElementById('weekly-section');ws.innerHTML=''; if(sd.dailyRows&&sd.dailyRows.length){ var tc=$h('div','card','','margin-bottom:16px');tc.appendChild($h('div','card-title tag','Daily breakdown')); var tbl=$h('table','weekly-table'); var hg=sd.totalGuests>0; tbl.innerHTML='DateDayRevenueOrders'+(hg?'GuestsAvg Check':'')+''; var tbody=document.createElement('tbody'); var maxR=Math.max.apply(null,sd.dailyRows.map(function(r){return r.rev;})); var minR=Math.min.apply(null,sd.dailyRows.filter(function(r){return r.rev>0;}).map(function(r){return r.rev;})); sd.dailyRows.slice().reverse().forEach(function(r){ var dow=['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][r.date.getDay()]; var ds=r.date.toLocaleDateString('en-US',{month:'short',day:'numeric',year:'numeric'}); var isBest=r.rev===maxR,isWorst=r.rev===minR&&minR!==maxR; var isAnom=sd.anomalies.some(function(a){return a.date.toDateString()===r.date.toDateString();}); var tr=document.createElement('tr');if(isAnom)tr.style.background='#FEF0EE'; tr.innerHTML=''+ds+(isAnom?' ':'')+''+dow+''+(isBest?'\u25b2 ':isWorst?'\u25bc ':'')+' $'+Math.round(r.rev).toLocaleString()+''+r.orders+''+(hg?''+r.guests+'$'+r.avgCheck.toFixed(2)+'':''); tbody.appendChild(tr); }); tbl.appendChild(tbody);tc.appendChild(tbl); var FREE_ROWS=7; if(!state.isPro&&sd.dailyRows.length>FREE_ROWS){ var wrap=$h('div','table-lock-wrap'); var visCard=$h('div','card','','margin-bottom:16px'); visCard.appendChild($h('div','card-title tag','Daily breakdown')); var visTbl=$h('table','weekly-table'); visTbl.innerHTML=tbl.querySelector('thead').outerHTML; var visTbody=document.createElement('tbody'); var allRows=Array.from(tbl.querySelectorAll('tbody tr')); allRows.slice(0,FREE_ROWS).forEach(function(r){visTbody.appendChild(r.cloneNode(true));}); visTbl.appendChild(visTbody); visCard.appendChild(visTbl); wrap.appendChild(visCard); var ov=$h('div','table-blur-overlay'); ov.innerHTML='
🔒
Pro unlocks all '+sd.dailyRows.length+' days
See every day, every anomaly.
'; wrap.appendChild(ov); ws.appendChild(wrap); } else { ws.appendChild(tc); } } var ms=document.getElementById('monthly-section');ms.innerHTML=''; var monthEntries=Object.entries(sd.byMonth).sort(function(a,b){ var mn=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; var pa=a[0].split(' '),pb=b[0].split(' '); var ya=parseInt('20'+pa[1].replace("'",'')),yb=parseInt('20'+pb[1].replace("'","")); var ma=mn.indexOf(pa[0]),mb2=mn.indexOf(pb[0]); return ya!==yb?ya-yb:ma-mb2; }).slice(-12); if(monthEntries.length>1){ var mc=$h('div','card');mc.appendChild($h('div','card-title tag','Monthly revenue trend')); var W=540,H=110,PL=6,PR=6,PT=14,PB=26; var vals=monthEntries.map(function(e){return e[1];}); var labels=monthEntries.map(function(e){return e[0];}); var minV=Math.min.apply(null,vals),maxV=Math.max.apply(null,vals),rng=maxV-minV||1; function gx(i){return PL+(i/(vals.length-1))*(W-PL-PR);} function gy(v){return PT+(1-(v-minV)/rng)*(H-PT-PB);} var pts=vals.map(function(v,i){return gx(i).toFixed(1)+','+gy(v).toFixed(1);}).join(' '); var aB=gy(minV-rng*0.05); var aPts=vals.map(function(v,i){return gx(i).toFixed(1)+','+gy(v).toFixed(1);}).join(' ')+' '+gx(vals.length-1).toFixed(1)+','+aB+' '+gx(0).toFixed(1)+','+aB; var NS='http://www.w3.org/2000/svg'; var svg=document.createElementNS(NS,'svg'); svg.setAttribute('viewBox','0 0 '+W+' '+H); svg.setAttribute('style','width:100%;height:'+H+'px;display:block;overflow:visible'); var poly=document.createElementNS(NS,'polygon'); poly.setAttribute('points',aPts);poly.setAttribute('fill','rgba(240,98,77,0.10)');svg.appendChild(poly); var line=document.createElementNS(NS,'polyline'); line.setAttribute('points',pts);line.setAttribute('fill','none');line.setAttribute('stroke','#F0624D'); line.setAttribute('stroke-width','2');line.setAttribute('stroke-linejoin','round');line.setAttribute('stroke-linecap','round'); svg.appendChild(line); var maxV2=Math.max.apply(null,vals),minV2=Math.min.apply(null,vals); vals.forEach(function(v,i){ var isLast=(i===vals.length-1),isFirst=(i===0); var isMax=(v===maxV2),isMin=(v===minV2); var showVal=isFirst||isLast||isMax||isMin; var dot=document.createElementNS(NS,'circle'); dot.setAttribute('cx',gx(i).toFixed(1));dot.setAttribute('cy',gy(v).toFixed(1)); dot.setAttribute('r',isLast||isMax?4:2.5);dot.setAttribute('fill','#F0624D'); dot.setAttribute('stroke','#fff');dot.setAttribute('stroke-width','1.5'); svg.appendChild(dot); if(isFirst||isLast||i%Math.ceil(vals.length/4)===0){ var lbl=document.createElementNS(NS,'text'); lbl.setAttribute('x',gx(i).toFixed(1));lbl.setAttribute('y',H-4); lbl.setAttribute('text-anchor',isFirst?'start':isLast?'end':'middle'); lbl.setAttribute('font-size','10');lbl.setAttribute('fill','#8A877F'); lbl.textContent=labels[i];svg.appendChild(lbl); } if(showVal){ var nowM=new Date(); var isPartial=isLast&&(nowM.getFullYear()===2000+parseInt(labels[i].split("'")[1])&&nowM.getMonth()===(['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'].indexOf(labels[i].split(' ')[0]))); var fmtV='$'+Math.round(v/1000*10)/10+'k'+(isPartial?' *':''); var anchor=isFirst?'start':isLast?'end':'middle'; var xOff=isFirst?0:isLast?-4:0; var yOff=isMax?-8:10; var valLbl=document.createElementNS(NS,'text'); valLbl.setAttribute('x',(gx(i)+xOff).toFixed(1)); valLbl.setAttribute('y',(gy(v)+yOff).toFixed(1)); valLbl.setAttribute('text-anchor',anchor); valLbl.setAttribute('font-size','10'); valLbl.setAttribute('fill',isMax?'#2D6A4F':isMin?'#C0392B':'#F0624D'); valLbl.setAttribute('font-weight','bold'); valLbl.textContent=fmtV;svg.appendChild(valLbl); if(isPartial){ var noteLbl=document.createElementNS(NS,'text'); noteLbl.setAttribute('x',(gx(i)-4).toFixed(1)); noteLbl.setAttribute('y',(gy(v)-18).toFixed(1)); noteLbl.setAttribute('text-anchor','end'); noteLbl.setAttribute('font-size','9'); noteLbl.setAttribute('fill','#B0ADA6'); noteLbl.textContent='month in progress';svg.appendChild(noteLbl); } } }); mc.appendChild(svg);ms.appendChild(mc); } } dy>