Skip to content
Skillv1.0.0

daily-sales

Complete daily sales reconciliation workflow for Cork and Barrel NYC. Use this skill whenever the user mentions daily sales, do the sales, sales verification, reconcile sales, check the sales, do sale

by jonchoi17(0) 0 installs
Free
Sign in to install

Free account. Installing gives you the manifest plus copy-paste snippets.

See reviews

About

Imported from jonchoi17/cork-and-barrel-claude (skills/daily-sales/SKILL.md). Install upstream with npx skills add jonchoi17/cork-and-barrel-claude --skill daily-sales. Copyright stays with the author.

Daily Sales — Cork and Barrel NYC

⛔ ABSOLUTE RULES

Read these every run. They override everything else.

  1. NO FREESTYLING. Same steps, same order, every run. If the rule isn't here, ASK before deviating. Do not invent shortcuts, alternate paths, or "close enough" approximations.

  2. PARALLEL OR NOTHING. Every independent step runs in parallel. Open all platform tabs in one Chrome batch (one assistant message, multiple navigate/tabs_create_mcp calls — not one tab at a time), scrape ALL pages in one batch of javascript_tool / get_page_text calls (one message, N tool calls), write all Excel files in parallel. Combined with an invoices session: daily-sales scrape and invoice PDF reads can also fire in the same parallel batch (different tools, both independent). Never serialize what can be parallelized. A scrape-and-run cycle for a single day should be ≤ 5 minutes of wall time.

  3. SCRAPE FIRST, SCREENSHOT NEVER (in chat). Use javascript_tool / get_page_text / read_page for data. Screenshots may be used INTERNALLY only when scraping fails — but never share screenshot images in chat output. The user explicitly does not want to see them. If you cannot extract data, say so in text. Do not paste page renders.

  4. OUTPUT = FINAL TABLE ONLY. No interim narration ("now scraping UberEats…"), no screenshot dumps, no progress chatter, no "✓ done" between steps. The deliverable is the verification table. Anything that doesn't make the answer better gets cut.

  5. FLAG = LS-CORRECT vs PLATFORM diff > $1.00. Always show the diff. Always write the platform value to Excel (platform = ground truth per [feedback_dd_platform_truth.md]). Label that row FLAG ⚠️, not CORRECTED. CORRECTED is reserved for user-confirmed employee fixes only.

  6. DOUBLE-CHECK MANDATORY. Before reporting done: re-scrape every platform fresh (paginate fully), re-read every Excel value, confirm match. If anything differs, fix and re-check.

  7. DISCREPANCIES ARE YOUR FAULT. Until a fresh re-scrape proves otherwise. Never blame "employee error" or "LS changed" without proof.

  8. WHEN USER PREDICTS A DISCREPANCY THAT ISN'T THERE: tell them, don't FLAG. If they said "DD might be off by ~$X" and your scrape shows it isn't, just report "checked, no discrepancy on DD" — don't manufacture a flag.

  9. ⛔ DD EXCEL COL H = SUBTOTAL, SUM ALWAYS MATCHES LS. Col H of Door Dash Transactions.xlsx is per-order subtotal (pre-fee). The SUM of col H across a day's rows MUST equal the LS DoorDash value for that day. There is no legitimate reason for divergence — LS rings up DD subtotals and that's the only source for the LS DD field. If your scrape only gave you net amounts (DD UI now hides per-row subtotal in the 7-day list view), distribute the day's commission proportionally so the col-H SUM hits LS exactly: subtotal_i = net_i × (LS_subtotal_sum / sum_of_nets). Recompute col L (commission) from H − T after distribution. NEVER write col H values that don't sum to LS. (User directive 2026-05-18 after $3.67 drift on 5/17.)

  10. ⛔ ADOPT THE EXISTING TAB GROUP — REUSE ACROSS DAYS, NEVER SPAWN FRESH (updated 2026-07-05). The MCP tab group PERSISTS across conversations/days as long as its tabs stay open, and tabs_context_mcp returns that existing group. Do NOT blindly create 6 new tabs each run. Procedure at the START of every run:

    • (a) Inspect first. Call tabs_context_mcp with no args (or createIfEmpty:false). If it returns a group that already has tabs, THAT is your workspace — do not open a new window/group. Only pass createIfEmpty:true when there is genuinely no group at all.
    • (b) Build a domain→tabId map from the returned tabs. Match each platform by domain substring: ubereats.com→UB, doordash.com→DD, grubhub.com→GH, minibardelivery.com→MB, dashboard.instacart.com→IC, cityhive.net→CH, lightspeedapp.com/merchantos→LS.
    • (c) Reuse, then fill gaps. For each platform: if a matching tab exists, REUSE it via navigate to the dated URL. If not, first consume any spare blank tab (chrome://newtab/"New Tab"); only call tabs_create_mcp when no blank tab is left. NEVER create a second tab for a domain that already has one.
    • (d) Do NOT close tabs at end of run. No tabs_close_mcp on platform tabs — leaving them open is exactly what lets the NEXT day's run reuse them. Close only genuine duplicates.
    • (e) Wrong-browser fallback. If the MCP group is empty/fresh but the user says their platform tabs are already open in Chrome, those tabs are in a DIFFERENT connected browser instance. Call list_connected_browsersselect_browser (or switch_browser to let the user pick) to attach to the browser that has them, then redo (a). Do not just spawn new tabs.

    This supersedes the old "within a conversation" scope. Goal: ONE durable set of platform tabs, reused every day, minimal Chrome clutter.

  11. ⛔ CH ROLLOVER INCLUDES THE TIP (2026-05-21 incident). When a CityHive post-9PM order from the previous day rolls to today's sales day, its tip rolls with it. Do NOT compute D3 as "calendar-date only" tips and do NOT subtract the rollover tip from D3. The correct D3 = sum of (prev_day_post9 + target_day_pre9) tips, non-OOB, non-Uber. The script's sales_day_orders = prev_post9 + target_pre9 aggregation is correct — never "fix" it to calendar-only. K3 (LS Tips diff) is the truth check: with rollover-included D3, K3 matches LS within ~$0.01. Without rollover, K3 diverges by ~$50+ — that divergence is the signal that D3 is wrong, not that LS is wrong. See [feedback_ch_rollover_includes_tip.md].

  12. ⛔ RE-FETCH LS FRESH BEFORE FINAL TABLE (2026-05-21 incident). The script's LS snapshot can be stale by 5–30 minutes. Voids, refunds, and returns happen between script run and final report. Before presenting the final verification table, re-run the LS pull (CC, payments, tips, COGS, sale count) and update Excel if anything drifted. If sale count dropped → a sale was voided → adjust B2 (CC) and B11 (COGS) at minimum. NEVER trust the script's snapshot as the final answer. See [feedback_ls_refetch_before_final.md].

  13. ⛔ UNFULFILLED PLATFORM ORDERS = OPERATIONAL FLAG (2026-05-21 incident). If a UB/DD/GH order shows status "Unfulfilled" or "Cancelled" on the platform AND its amount equals the LS-vs-portal-gross difference, do NOT mark it ✅ EXACT just because LS-completed-only matches. Mark it ⚠️ FLAG (unfulfilled $X.XX) with the order ID. Unfulfilled = lost revenue + operational follow-up with the platform. Always surface to the user, never silently dismiss. See [feedback_unfulfilled_orders_flag.md].

  14. ⛔ GH CANCEL/RE-ORDER TIP DRIFT → USE PORTAL VALUE (2026-05-21 incident). When a GrubHub order is cancelled and re-placed at a different tip (e.g., 7:17 PM $5.99 tip cancelled → 7:23 PM $8.98 tip re-order), LS often captures the original tip but misses the update. Trust the GH PORTAL: G2 = scraped (subtotal + tip) of the valid orders (i.e., net the cancellation/replacement pair). Mark 🔧 CORRECTED with note "LS missed tip update on cancel/re-order pair." Do NOT leave G2 = LS value. See [feedback_gh_portal_overrides_ls.md].

  15. ⛔ K3 = LS TIPS, EXACTLY. B3 NEVER FLOORS (2026-05-26 incident). K3 (SUM of row 3 tips) must equal LS Tips total to the penny. After all platform tips are written (D3 CH, E3 UB, G3 GH, H3 MB), compute B3 = LS_Tips − D3 − E3 − G3 − H3 and write it AS-IS (decimal allowed, round to 2dp to dodge float artifacts). NEVER floor B3. If K3 ≠ LS Tips, B3 is wrong. The script handles this — do not "improve" B3 manually after the script runs. See [feedback_k3_b3_no_floor.md].

  16. ⛔ C6 EXCLUDES B3 — READ THE ACTUAL CELL, DO NOT RE-DERIVE (2026-05-26 incident). The C6 cell formula in Daily Sales Calculations.xlsx is =(D3+G3+H3)*0.9B3 (CC tips) is NOT in C6. CC tips don't pay out in cash to employees; only online tips × 0.9 do. B17 = floor(C6). Tip Report online tips = floor(C6) (same value, not a separate calc). Always read C6's actual cell formula before plugging anything anywhere. Never re-derive from documentation or memory — the cell is the source of truth. I2 = B16 − B17 − B19. See [feedback_c6_excludes_b3.md].

  17. ⛔ CASH PROVIDED LATER — NEVER BLOCK ON CASH (2026-05-28 user directive). When the user kicks off a daily-sales run WITHOUT a cash amount, do NOT pause, do NOT ask, do NOT wait. Run the script without --cash, complete every other step (LS pull, platform scrapes, Excel writes, Tip Report, verification table), and finish the run as if cash will be provided later. Surface a single one-line reminder near the end of the run ("Cash for {date} still needed — drop the amount and I'll re-run with --cash"). When the user later provides cash, re-run with --cash AMOUNT to fill B16/B17/B19/I2. This rule supersedes any tendency to "be helpful" by asking up front. The user's workflow: kick off the run while still counting cash; cash arrives minutes later.

  18. ⛔ VERIFY THE B17 SCRIPT OUTPUT MATCHES floor(C6) (2026-05-28 incident). Older copies of daily_sales.py computed c6 = (b3+d3+g3+h3)*0.9 instead of (d3+g3+h3)*0.9 — producing a B17 that's too high (and a wrong I2 cash calc). Canonical script is correct. If you find B17 ≠ floor(actual C6 cell value) after a script run, the running copy is stale → fix the running script, then patch B17 and recompute I2 = B16 − B17 − B19. Then verify Daily Sales Calculations.xlsx matches. See [feedback_b17_floor_c6.md].

  19. ⛔ DOORDASH ROWS NEED FULL PER-ORDER DATA — Col A (Transaction ID) + Col L (commission) ALWAYS (2026-06-02 incident, recurring). Every DD row written to Door Dash Transactions.xlsx MUST have: Col A = Transaction ID (long numeric, e.g. 17329704539), Col L = commission (negative, shows red), plus Col R/S for any customer discount / marketing credit. Two failure modes that recurred across days: (a) Col A left blank — the script used to hard-code None there (FIXED 2026-06-02; now writes tx_id); (b) Col L = 0 with no red — caused by a subtotal-only scrape that never captured per-order commission (5/29 & 5/30 had L=0, net was right so H+L≠T silently). Never use the old subtotal-only DD scraper. Scrape full per-order columns and paginate all pages (§1C). After the script runs it now prints a "DoorDash row validation FAILED" warning if any row has a blank tx_id or a dropped commission (net≠subtotal but commission=0) — if you see that warning, re-scrape and refill A/L/R/S before reporting done. Verify visually: every DD day must show red in the L column. See [feedback_dd_full_per_order_columns.md].

  20. ⛔ KEEP CANONICAL AND SESSION-SKILLS COPIES IN SYNC (2026-05-28 incident). Skill scripts and SKILL.md live in TWO places: canonical iCloud (~/Library/Mobile Documents/com~apple~CloudDocs/Shared invoices/claude code skills/skills/{name}/) and session-skills cache (~/Library/Application Support/Claude/local-agent-mode-sessions/skills-plugin/{uuid}/{uuid}/skills/{name}/). The session-skills copy is what the skill harness LOADS each run; the canonical iCloud copy is what the user EDITS. They can drift. After ANY edit to canonical, immediately cp the same file to the session-skills location AND verify md5 match. Same for any script fix. Do this before reporting "skill updated."

  21. ⛔ NEVER DELETE A VALID CITYHIVE ORDER — FLAG AND ASK (2026-06-11 user directive, after 6/9 incident). A CH order that is non-OOB and fulfilled must NEVER be deleted from the CityHive Transactions log, no matter what discrepancy it explains. On 6/9 the last order of the day (8:59 PM, $47.53) wasn't rung in LS because the store closed at 8:59 — the prior session DELETED it to force reconciliation. Wrong. Correct handling: FLAG it, ask the user; they will say roll it to the NEXT sales day (it gets rung next morning). A store-close rollover behaves exactly like a post-9PM rollover: row stays with its real timestamp at the end of its calendar day's block, EXCLUDED from that day's SUM, INCLUDED in the next day's SUM range, and its tip rolls with it into next day's D3 (rule 11). Only "declined" status orders get the OOB timestamp-only treatment.

  22. TAX/TIP RING RULES (user 2026-06-11): GH, DD, UB, and MB sales are rung in LS WITHOUT tax — tax is handled separately by each platform. Only direct LS sales and CityHive orders include the TIP in the rung amount. An LS-vs-portal overage of exactly subtotal×1.08875 means an employee rang the tax-included amount (e.g. DD 8EDC822E on 6/10: $75.09 rung vs $68.97 subtotal).

  23. ⛔ NEVER HALT THE RUN MID-PROCESS FOR A DISCREPANCY — BATCH QUESTIONS TO THE END (2026-06-16 user directive). When a flag/ambiguity comes up (CH mismatch, scheduled/voided order, platform gap, etc.), do NOT stop and ask immediately. Diagnose it, park it, and KEEP GOING — finish every other independent step (all platforms, every Excel write, the tip split, the full verification table) using best-effort/placeholder values where needed. Then surface ALL open questions together in ONE consolidated "needs your input" list AFTER the final table, each with the diagnosis already done and a note on what changes per answer. On 2026-06-16 I halted the whole 6/15 run for a single CityHive scheduled-order question before finishing the rest — the user does not want that. Extends rule 17 (don't block on cash). When the user answers, apply and proceed (don't re-gate).

  24. CITYHIVE "scheduled" STATE = FUTURE ORDER, NOT IN LS YET (2026-06-16 incident). CityHive orders can have multiple_product_order.state == "scheduled" (future delivery). daily_sales.py excludes only is_oob (manual://) and is_declined — it does NOT handle "scheduled", so it counts the scheduled order's total + tip in the CH platform sum, producing: CH platform > LS by exactly that order's amount, B3 negative, K3 ≠ LS by the scheduled tip. When a CH FLAG's gap matches a single order, query the CH API and check state. If "scheduled" it is NOT an error or missed ring — the order will ring in LS when fulfilled (watch for a double-count that day). Per rule 23, park it and ask at the end (default recommendation: exclude until fulfilled; it's the user's call). On 6/15 the user chose include all: D2 = CH platform value (not LS), the scheduled tip parked in E3 (UB) so it stays OUT of the employee payout C6=(D3+G3+H3)×0.9, B3 = true CC tip, so K3 exceeds LS by the scheduled tip by design.

  25. ⛔ LOGGED-OUT IS NEVER A BARRIER — AUTO-RE-LOGIN THE LS WEB UI (2026-07-12 user directive). A logged-out automation browser must NEVER become an OPEN ITEM or a reason a value is skipped. If the LS web UI (corkandbarrelnyc.retail.lightspeed.app) reads as logged-out when you go for the Sales Reports → Customer → Instacart cross-check, log back in automatically and retry — do not defer it, do not surface it as "needs your input." Credentials live in scripts/.env as LS_WEB_USERNAME / LS_WEB_PASSWORD (this is the WEB login, separate from LS_PERSONAL_TOKEN). Login flow: navigate to https://corkandbarrelnyc.retail.lightspeed.app/; if it shows the login screen, read the two env vars (python3 -c "import os; from dotenv import load_dotenv; load_dotenv('scripts/.env'); print(os.environ['LS_WEB_USERNAME']); print(os.environ['LS_WEB_PASSWORD'])" — one simple command, no compound &&), then fill the email + password fields via Chrome form_input and submit; wait for the dashboard, then proceed to Reports → Sales. ⛔ Use corkandbarrelnyc.retail.lightspeed.app, NEVER merchantos.com (that host reads as logged-out even when the session is fine — see [reference_ls_login_url.md]). This cross-check is SECONDARY: every written value already comes from the LS API token, and Instacart col D comes from the GMV tlog — so if login still can't complete after a genuine attempt, finish the run clean and note it as a one-line minor footnote (NOT an OPEN ITEM). See [feedback_ls_web_autologin.md].

  26. ⛔ INSTACART TLOG IS AUTO-POSTED ~T+1 — DOWNLOAD IT WHEN PRESENT, ELSE DEFER COL D (corrected 2026-07-13 after 7/12 incident; supersedes the 2026-07-12 "generate on demand" directive). The tlogs.v2.{DATE} transaction-log report is auto-generated by Instacart on a ~T+1 schedule (e.g. the 7/12 tlog file was created 7/13). It CANNOT be generated on demand — verified 2026-07-13: the IPP transaction-logs page has no Create/New-report control, and the only "Create report" builder is the separate Insights/analytics dataset (the lagging GMV that returns 0/partial for the run day), NOT the raw tlog. Correct handling: at the reports list (https://dashboard.instacart.com/partners/3761/warehouses/1798/reports/transaction-logs), if the rpt.4096*tlogs*{DATE}*.csv row is present, download it to ~/Downloads and run the script so col D fills this run. If it is not present yet (the run fired before the tlog posted), you cannot force it — do NOT fabricate col D from LS or the lagging Insights GMV. Leave col D blank, note it as an OPEN ITEM, and it fills automatically on any later re-run once the tlog posts (the script auto-globs ~/Downloads). The nightly routine now fires at 6 AM ET specifically so the prior day's tlog is normally already posted. Fallback only if col D is needed before the tlog posts AND the In-store transactions report (rpt.4094*in_store_transactions*{DATE}) IS present: sum its transaction_amt (= GMV/1.1 = col D directly). (History: the 2026-07-12 directive assumed on-demand tlog generation was possible; the 7/12 unattended run at 1 AM ET proved it is not — no generator exists and the tlog had not yet posted — so this rule was corrected and the run moved to 6 AM.)

  27. ⛔ CITYHIVE LOGGED-OUT IS NEVER A BARRIER — AUTO-RE-LOGIN, AND NEVER SHIP D3 = $0 (2026-07-22 user directive, after the 7/21 incident). CityHive credentials now live in scripts/.env as CITYHIVE_USERNAME / CITYHIVE_PASSWORD. daily_sales.py re-authenticates itself via POST https://api.cityhive.net/api/v1/users/sign_in with body {"user":{"email":…,"password":…}}, which returns result: 0 and sets a fresh _HiveNet_session cookie (_try_ch_login()_ch_reauth(), verified 2026-07-22). This is the CityHive analogue of ABSOLUTE RULE 25 — a logged-out CityHive must never be an OPEN ITEM.

    • The silent-failure shape to watch for: an expired session does NOT return 401. The orders API answers HTTP 200 with {"result":2,"description":"You must be logged in to perform this action."} and no data key, so a dead session looks exactly like a day with zero CityHive orders. The script then writes D3 = $0.00 and dumps the entire CityHive tip into B3. K3 still equals LS Tips, so the usual truth check does NOT catch it — but C6 = (D3+G3+H3)*0.9 collapses and the Tip Report underpays badly (7/21: $22 instead of $89).
    • Therefore: if LS CityHive (D2) is non-zero but CH tips (D3) come back $0.00, treat it as a failed fetch, not a tipless day. Re-auth and re-query before writing anything.
    • Do not stop at the first re-auth strategy that "succeeds." _try_refresh_ch_cookie() (pycookiecheat from Chrome) returns True whenever it finds any cookie — including the same dead one. The retry loop must fall through to a real credential login and re-query after each strategy. That was the exact 7/21 failure mode.
    • Last-resort derivation if CityHive is genuinely unreachable: split LS tips by payment type — page /api/register_sales?page_size=200&status=CLOSED, convert sale_date UTC→ET, filter to the date, and sum each sale's "tip" products keyed by register_sale_payments[].name. That yields D3 (CityHive), G3 (GrubHub) and B3 (Lightspeed Credit Card) directly, summing to LS Tips exactly. Cross-check G3 against the GrubHub portal scrape. This recovers tips only — not CityHive order rows, so CItyHive Transactions.xlsx stays unwritten and D2 stays UNVERIFIED. Prefer a real login every time.

STEP 0 — INPUTS (extract from user message)

User said Argument
Specific date(s) --date YYYY-MM-DD
Multiple days run once per day
Cash $ amount --cash AMOUNT
Cash tips apply only to Tip Report column D, never B16
Nothing about cash omit --cash; proceed with everything else; ONE-line reminder near end (rule 17)
Employees worked DERIVE from schedule below — do NOT ask

Standard employee schedule (do NOT ask, only respect overrides):

Day Employees
Monday Carlos, Wendy
Tuesday Carlos, Sonam
Wednesday Sonam, Wendy
Thursday Sonam, Wendy
Friday Carlos, Sonam
Saturday Carlos, Sonam
Sunday Carlos (alone)

STEP 1 — PARALLEL SCRAPE PHASE

One assistant message. All these steps run in parallel. Do not serialize.

1A. Setup check (only if first run of the day)

ls scripts/.env >/dev/null 2>&1 && python3 -c "import requests, openpyxl, dotenv, pycookiecheat" 2>&1 && echo "OK"

If missing → tell user, abort.

1B. Open all browser tabs in parallel

In ONE browser_batch, navigate every platform. Use the literal URLs below — date params get substituted.

Platform URL
UberEats https://merchants.ubereats.com/manager/payments/payouts-by-order?restaurantUUID=fce5efc1-37f6-47a6-b898-3dfeab4e0499&start={DATE}&end={DATE}&rangeType=1
DoorDash https://www.doordash.com/merchant/financials/transactions?store_id=1898885&start_date={DATE}&end_date={DATE} (URL params don't always stick — be ready to set custom range via calendar)
GrubHub https://restaurant.grubhub.com/financials/transactions/2886031 (filter rows in JS by date)
Minibar https://minibardelivery.com/services/supplier/orders/completed (no orders → MB=$0)
Instacart GMV (PRIMARY) Transaction Logs reporthttps://dashboard.instacart.com/partners/3761/warehouses/1798/reports/transaction-logs → find the rpt.4096...tlogs.v2.{DATE}...csv row → Download it yourself to ~/Downloads (fresh, from the portal — never reuse a stale Downloads file, never rely on the user). ⛔ The tlog is AUTO-POSTED by Instacart ~T+1 and CANNOT be generated on demand — the IPP has no tlog generator (ABSOLUTE RULE 26). If the rpt.4096...tlogs.v2.{DATE}...csv row is present, download it. If it is not posted yet, leave col D blank as an OPEN ITEM — do NOT fabricate it — and it fills on any later re-run once the tlog posts (the 6 AM ET run timing is chosen so the prior day's tlog is normally already posted). The script then auto-consumes it: daily_sales.py's get_instacart_data globs rpt.4096*tlogs*{DATE}*.csv, sums the GMV column, writes col D = GMV ÷ 1.1, and cross-checks vs LS — so have the tlog on disk for each day BEFORE running the script for that day and you never hand-patch col D. (CSV can't be fetched in-page — signed-URL CORS — so a real download is the only way; that's the documented routine.) ⛔ Do NOT use the Insights portal as the primary source — it lags 2-3 days (delivered-only, Pacific date) and returns 0/partial for recent days, which is exactly the day this skill runs. See [feedback_instacart_dashboard_lag.md].
Instacart GMV (fallback only) Only if generating the tlogs.v2.{DATE} report genuinely fails, use the In-store transactions report (rpt.4094...in_store_transactions...{DATE}) — sum each order's transaction_amt = store revenue = GMV/1.1 = col D directly. Last resort: Insights portal ...insights-portal/store-performance/completed-orders?from={DATE}&to={DATE}&dateRangePreset=custom&globalMetric=SUM_GMV (only reliable for days 3+ old). Leaving col D blank is only acceptable when NO report can be produced at all — and then it's an OPEN ITEM.
LS Sales Reports LS web UI = https://corkandbarrelnyc.retail.lightspeed.app/ (X-Series). ⛔ NOT merchantos.com — that redirects to an OIDC login and reads as logged-out (false "LS session expired"). If it shows the login screen, auto-re-login from LS_WEB_USERNAME/LS_WEB_PASSWORD in scripts/.env and retry — never treat logged-out as a barrier (ABSOLUTE RULE 25). Navigate to Reports → Sales, set Report type: Customer, Measure: Revenue incl tax, set date range, read the Instacart customer row's Revenue column. This is the LS-correct Instacart cross-check — never compute it from the API. Secondary check only: every written value already comes from the API token / GMV tlog, so if login still can't complete it's a one-line footnote, not an OPEN ITEM. (See [feedback_instacart_ls_lookup.md], [reference_ls_login_url.md], [feedback_ls_web_autologin.md].)

For multiple tabs, follow ABSOLUTE RULE 10: tabs_context_mcp first, reuse existing platform tabs by domain match, consume spare blank tabs, and only tabs_create_mcp for whatever domain has no tab yet. Hold the IDs, then navigate all in one batch. Do not close them at the end — they carry over to tomorrow's run.

1C. Extract data (parallel javascript_tool calls)

For each tab, run javascript_tool to extract the totals. Use these patterns — they're proven:

UberEats: Read "Sales (excl. tax)" from summary row. (NOT "Total Payout".)

DoorDash: Extract the FULL per-order row, not just the subtotal. Each DD row in --scraped MUST carry tx_id, order_id, subtotal, commission, cust_disc, mkt_credit, net. ⛔ Do NOT use a subtotal-only scraper — dropping per-order commission writes L=0 (no red) and a blank Col A (no Transaction ID). This silently broke 5/29–5/30 (see ABSOLUTE RULE 20).

Column indices in the DD transactions table: 0=Transaction ID, 1=Date, 2=Time, 3=Order ID, 7=Subtotal, 11=Commission, 14=Error charges, 15=Adjustments, 17=Customer discounts, 18=Marketing credit, 19=Net total.

(()=>{
  window.__dd=window.__dd||{};                 // accumulate across pages, keyed by tx
  const rows=[...document.querySelectorAll('table tr')].slice(1);
  const parse=v=>parseFloat(String(v).replace(/[$,]/g,''))||0;
  for(const r of rows){
    const c=[...r.querySelectorAll('td')].map(x=>x.innerText.trim());
    if(c.length<20) continue;
    window.__dd[c[0]]={tx_id:c[0],date:c[1],time:c[2],order_id:c[3],
      subtotal:parse(c[7]),commission:parse(c[11]),cust_disc:parse(c[17]),
      mkt_credit:parse(c[18]),net:parse(c[19])};
  }
  const t=document.body.innerText;
  return JSON.stringify({pagination:t.match(/\d+[-–]\d+ of \d+/g),total:Object.keys(window.__dd).length});
})()

PAGINATE FULLY. The portal shows ~20 rows/page over a rolling 7-day window (URL date params don't stick). Click each page button (aria-label="go to page N"), re-run the snippet to accumulate into window.__dd, then filter to the target date(s). If pagination shows "1-20 of 44", you have 3 pages — visit ALL of them. Then build doordash_orders from Object.values(window.__dd) filtered to the target date. Verify per-order identity subtotal + commission ≈ net (within ~$0.15 for DD's own rounding); cust_disc and mkt_credit normally offset to ~0 net effect (DoorDash-funded promos).

GrubHub: Match (date) (time) $sub — — $tax $tip regex on document.body.innerText, filter to target dates.

Minibar: Count completed orders for target date; if none, $0.

Instacart GMV (PRIMARY = Transaction Logs report, NOT the Insights portal): Go to Reports → Transaction logs, download the day's tlogs.v2.{DATE} CSV yourself, sum the GMV column → divide by 1.1, round to 2 decimals. If that day's row isn't listed yet, it has not been auto-posted yet — it CANNOT be generated on demand (ABSOLUTE RULE 26); leave col D blank as an OPEN ITEM and it fills on a later re-run once posted. The Insights "GMV (USD)" cell lags 2-3 days and shows 0/partial for yesterday — do not rely on it. Sanity check: report order COUNT must equal the LS Instacart customer order count; if it's short, the report is still settling.

LS Sales Reports (Instacart customer): If the tab reads as logged-out, auto-re-login from scripts/.env (LS_WEB_USERNAME/LS_WEB_PASSWORD) and retry — never skip on account of a logged-out UI (ABSOLUTE RULE 25). Locate the row for Customer = "Instacart" or similar, read the "Revenue" cell. This is your ls_instacart_correct value (secondary cross-check).

1D. Pre-populate scraped JSON

Build the --scraped JSON for the script:

{
  "ubereats_sales": <number>,
  "doordash_orders": [{"tx_id","date","time","order_id","subtotal","commission","cust_disc","mkt_credit","net"},],
  "grubhub_orders": [{"subtotal","tip"},],
  "minibar_total": <number>
}

Hold these values in working memory: ls_instacart_correct, scraped_ic_value (=GMV/1.1).


STEP 2 — RUN SCRIPT

python3 scripts/daily_sales.py --date YYYY-MM-DD --cash AMOUNT --scraped '{...}'

The script pulls LS API + CityHive API, reconciles against --scraped, writes Excel, prints discrepancies. Stream the output but do NOT report it back to the user — synthesize into the final table.

The script writes Daily Sales Calculations.xlsx TWICE every run — to the day's weekday slot {Weekday} M-D AND to SQ (automatic, see Step 3). Multi-day handling happens in Step 3.

The script's "Instacart Rev" output is INFORMATIONAL ONLY. Do not trust it as the LS-correct value. It sums total_price + total_tax and drifts from LS Sales Reports. Always use the scraped LS Sales Reports value as ls_instacart_correct.


STEP 3 — SHEET ROUTING

⛔ WEEKDAY RULE (automatic, user directive 2026-07-27 — supersedes the 2026-07-24 Thursday/Friday-only rule): EVERY day is written to BOTH its weekday slot {Weekday} M-D AND SQ. write_all_excel does this itself from TARGET_DATE.weekday(); the identical set of values goes to both sheets in one loop.

  • Seven weekday slots = a rolling one-week view. MondaySunday, one sheet each. Each run reuses the ONE existing sheet for that weekday (matched by name prefix, so a bare Monday or a stale Monday 7-20 both resolve) and re-stamps its tab with the current date. A weekday is therefore only ever overwritten by the NEXT same-weekday run — seven days later (e.g. Monday 7-27Monday 8-3). This is what preserves a full week of daily calculations.
  • SQ still always holds the MOST RECENT day reconciled. So after the 7/27 run you see 7/27 in both Monday 7-27 and SQ.
  • (/ is illegal in Excel tab names, so the date is written M-D.) A first-ever run for a weekday with no slot clones SQ to create it, preserving formulas/styling.
  • No --cash ⇒ the cash cells are CLEARED, not left stale. B16/B17/B19/I2 are set to None on a cash-less run. I2 sits inside K2 = SUM(B2:J2), so a leftover prior-day I2 silently inflates the day's total (found 2026-07-27: SQ held 7/25's $642/$36/$500/$106 next to 7/26's data). They fill correctly on the later --cash re-run.
  • Legacy dated sheets are gone (2026-07-27). The old ad-hoc M-D sheets (4-87-17, plus Thursday 7-23) were deleted at the user's request; only SQ, DEFAULT, Sheet1 and the seven weekday slots remain. Do NOT recreate bare M-D sheets. A backup of the pre-cleanup workbook is at Daily Sales Calculations BACKUP 2026-07-27.xlsx.

Multi-day rule: nothing extra to do — the weekday slots ARE the multi-day storage. Each day of a batch lands in its own weekday sheet automatically, so no manual M-D cloning is needed any more (that was only ever a workaround for SQ being a single scratch slot).

If user requested only one day → done; the weekday slot and SQ both have it. Skip to Step 4.

If user requested multiple days, process oldest first, newest last — run the script once per day in date order. Each run writes that day to its weekday slot and overwrites SQ, so when the batch finishes every day sits in its own weekday sheet and SQ holds the newest. ⛔ Batches spanning more than 7 days will overwrite a weekday slot twice; if that happens, say which day got clobbered rather than silently losing it. Also heed [feedback: backfill degrades after ~1 day] — for any date older than yesterday, --dry-run first, since platform scrapes expire and a blind re-run zeroes DD/UB/tips.


STEP 4 — APPLY PLATFORM-TRUTH CORRECTIONS

After script runs, for each platform compare scraped vs LS:

Diff Action Status label
$0.00 none ✅ EXACT
≤ $1.00 none ✅ PASS
> $1.00 Write scraped value to Excel cell (overwrite LS). Always show diff. ⚠️ FLAG
User explicitly said "employee error fix" Apply user's fix 🔧 CORRECTED
No platform source available LS value as-is UNVERIFIED
Only one source exists (CC, COGS) LS value SOURCE

Cells to potentially overwrite (SQ or M-D sheet):

  • D2 (CityHive) — if CH platform total ≠ LS
  • E2 (UberEats) — if UB scrape ≠ LS
  • F2 (DoorDash) — if DD scrape ≠ LS
  • G2 (GrubHub) — if GH scrape ≠ LS
  • Instacart D column → ALWAYS scraped GMV / 1.1, never LS

Do NOT touch B2 (CC), B11 (COGS), B3/D3/E3/G3/H3 (tips) unless user reports a specific employee error.

4A. DIAGNOSING A FLAG — one targeted query, don't re-scrape everything

When a platform FLAGs (LS ≠ portal), pull the LS per-order line items directly from the API (/register_sales?page_size=200&status=CLOSED, paginate, UTC→ET, filter to the date, print each register_sale_payments[].name + amount for the flagged platform) and diff that against the scrape. One query identifies which known cause it is:

  • Extra/missing order — count mismatch (e.g. a late order rung in LS but not yet on the portal).
  • Tax-included mis-ring — gap = one order's subtotal × 0.08875 (rule 22).
  • Cross-day DoorDash void — an order is +$X on day N and −$X on day N+1, never on the DD portal. Match LS each day (F2 = LS, Door Dash SUM = LS): include the +order on day N and a −void row on day N+1; their tx_id (Col A) is legitimately blank (the script's blank-tx warning is EXPECTED), and the two net to zero across the span — say so in the summary.
  • Declined CityHive order — CH FLAG + B3 negative + K3≠LS by the same amount → a state=="declined" order; the script now auto-excludes these (rule 21 / declined-orders memory).
  • Stale SPA read — a $0/empty scrape when LS is non-zero → reload the page and re-read before writing anything (don't write $0).

Don't re-investigate whether a documented source "works" each run — trust the established source, run ONE diff to identify the flag, fix it, move on.


STEP 5 — VERIFICATION TABLE (intermediate)

Render this table for each day. Format is non-negotiable.

📊 Daily Sales — YYYY-MM-DD (Day) — sheet `<SQ or M-D>`

| Cell | Source       | Excel       | Platform       | LS-correct  | Diff   | Status     |
|------|--------------|------------:|---------------:|------------:|-------:|------------|
| B2   | Credit Card  | $X,XXX.XX   | —              | $X,XXX.XX   | —      | SOURCE     |
| D2   | CityHive     | $X,XXX.XX   | $X,XXX.XX (API)| $X,XXX.XX   | $X.XX  | ✅/⚠️/🔧   |
| E2   | UberEats     | $X,XXX.XX   | $X,XXX.XX      | $X,XXX.XX   | $X.XX  | ✅/⚠️/🔧   |
| F2   | DoorDash     | $X,XXX.XX   | $X,XXX.XX      | $X,XXX.XX   | $X.XX  | ✅/⚠️/🔧   |
| G2   | GrubHub      | $XXX.XX     | $XXX.XX        | $XXX.XX     | $X.XX  | ✅/⚠️/🔧   |
| H2   | Minibar      | $0.00       | $0.00          | $0.00       | $0.00  | ✅ EXACT   |
| IC   | Instacart    | $XXX.XX     | GMV $XXX.XX    | $XXX.XX     | $X.XX  | ✅/⚠️      |
| B11  | COGS         | $X,XXX.XX   | —              | —           | —      | SOURCE     |
| K3   | Tips Total   | $XXX.XX     | —              | LS $XXX.XX  | $0.00  | ✅         |
| B16  | Cash         | $XXX        | —              | —           | —      | (input)    |
| I2   | Cash Calc    | $XXX        | —              | —           | —      | (calc)     |

Status definitions:

  • EXACT — diff = $0.00
  • PASS — diff ≤ $1.00 (show actual diff)
  • ⚠️ FLAG — diff > $1.00 (show diff; you wrote scraped value to Excel; user must investigate)
  • 🔧 CORRECTED — user-confirmed employee error fix only
  • UNVERIFIED — no independent source; LS value used; tell user clearly
  • SOURCE — only one source exists (CC, COGS, MB when no orders)

STEP 6 — TIP REPORT

File: ~/Library/CloudStorage/OneDrive-Personal/Documents/Tip Report.xlsx

Each sheet has its own row alignment — do not assume the year sheet's row numbers match employee sheets. Always look up the row by date.

Algorithm:

  1. Compute online_tips = floor((D3 + G3 + H3) * 0.9) — same as Daily Sales C6 (B3 EXCLUDED — see ABSOLUTE RULE 16). Best: read C6's actual computed value from Daily Sales Calculations.xlsx (data_only=True) and floor it.
  2. total = online_tips + cash_tips (cash_tips defaults to 0 if not given).
  3. Determine workers from schedule (or user override).
  4. Split total whole-dollar evenly. Remainder $1 → Carlos > Sonam > Wendy.
  5. Year sheet (e.g. "2026"): find row where col A = sales date. Write C = online_tips, D = cash_tips (or None if no cash tip — never 0).
  6. Each working employee's sheet: find row where col A = sales date. Write C = their share of total. Never write D on employee sheets.
  7. For employees who did NOT work that day: explicitly clear their cell on that date if anything is in it (cell.value = None).
  8. Read back values to verify before reporting.

Examples:

  • $81 between Sonam, Wendy → Sonam $41, Wendy $40
  • $100 between Carlos, Sonam, Wendy → Carlos $34, Sonam $33, Wendy $33
  • $119 between Carlos, Sonam → Carlos $60, Sonam $59

STEP 7 — END-OF-RUN DOUBLE-CHECK

Before final output:

  1. Re-scrape DoorDash + UberEats + GrubHub + Instacart GMV + LS Sales Reports IC fresh in one parallel batch.
  2. Read back Daily Sales Calculations sheet (SQ + any M-D), Door Dash Transactions, CityHive Transactions, Instacart, Tip Report.
  3. Compare each Excel value against fresh scrape.
  4. If anything differs → fix and re-check.
  5. Only then proceed to Step 8.

STEP 8 — FINAL OUTPUT

The verification table from Step 5 (all days, both intermediate-table format) is the LAST thing the user sees. No "Done!" message after, no summary, no chatter. The table = the deliverable.

Files written list goes ABOVE the tables. Single line each.

If invoices are also being processed in this session (combined run), the invoice pricing table replaces the daily-sales table as the final output — see invoices/SKILL.md.


EXCEL LAYOUTS (reference)

Daily Sales Calculations.xlsx, sheet SQ or M-D:

  • Row 2: B2=CC, D2=CH, E2=UB, F2=DD, G2=GH, H2=MB, I2=Cash(calc), J2=Check(0), K2=SUM
  • Row 3: B3=CC tips, D3=CH tips, E3=UB tips (from CH), G3=GH tips, H3=MB tips, K3=SUM (matches LS Tips total)
  • B11=COGS, B13=RSV (500 Fri/Sat else 250)
  • B3=LS_Tips − D3 − E3 − G3 − H3 (decimal, no floor — K3 must equal LS_Tips exactly).
  • B16=Cash (user input), C6==(D3+G3+H3)*0.9 (B3 excluded), B17=floor(C6), B19=Reserve (500 Fri/Sat else 300), I2=B16-B17-B19
  • Weekend = Friday OR Saturday only (dow in (4,5)). Sunday is NOT weekend.

Door Dash Transactions.xlsx, Sheet1:

  • Reverse chronological, latest first
  • Col A = Transaction ID (the long numeric ID, e.g. 17329704539). MUST be populated on every row — the established format puts the Transaction ID here. (Col D = the short 8-char order code, blue.) Leaving A blank = the "missing Order ID" bug.
  • Col H = subtotal · Col L = commission (negative) · Col R = customer discount (negative) · Col S = marketing credit (positive) · Col T = net. Identity: H + L ≈ T (within ~$0.15 for DD rounding; R+S offset to ~0).
  • "Red" = the number format "$"#,##0.00_);[Red]("$"#,##0.00) auto-colors NEGATIVE values red. Commission (L) is always negative → shows red. Discounts (R) are negative → red. If a day's L column shows NO red, commission was entered as 0 → it was dropped in the scrape → re-scrape with the full per-order extractor and refill A/L/R/S. Do not hand-set the font color; just write the correct negative value and the format colors it.
  • Col U = SUM(H:H) for that day on the day's LAST row only. This SUM MUST equal the LS DoorDash value for the day, exact to the penny.
  • If the DD UI list view only exposes net per row (current new UI behavior), distribute proportionally:
    • subtotal_i = round(net_i × LS_subtotal_sum / sum_of_nets, 2)
    • Adjust the largest row by ±0.01 to absorb rounding so SUM hits LS exactly
    • Recompute L_i = T_i − H_i (so commission is consistent per row)
  • NEVER write H values that don't sum to LS. If your SUM ≠ LS, your row values are wrong — fix before writing.

CItyHive Transactions.xlsx, active sheet:

  • 9PM ET cutoff: post-9PM orders roll to NEXT sales day
  • SUM range includes prev-day rollovers
  • SUM placement: row of LAST current-day order (incl. OOB rows)
  • Uber orders highlighted yellow

Instacart.xlsx:

  • Sheet M-YYYY (e.g. "5-2026"). If sheet missing for current month, copy previous month, clear column B AND D.
  • ⛔ Col D row = the row whose DATE column (A or C) equals the SALES date — match the date, NEVER use a positional day±N index. Sheets are inconsistent (some have a header at row 1, some put day 1 at row 1), so day+1 silently lands on the WRONG (next-day) row — e.g. for the 6-2026 sheet (row 1 = 6/1, no header), day+1 wrote 6/2's value onto the 6/3 row. User-reported recurring bug, fixed in daily_sales.py 2026-06-03 (now date-matches; errors out instead of guessing a row). When reconciling yesterday's sales, the value belongs on YESTERDAY's date row. Format: "$"#,##0.00
  • Only touch column D. Column B belongs to user.
  • Value = GMV ÷ 1.1, where GMV = the SUM of the GMV column in the Transaction Logs report (tlogs.v2.{DATE}) you download yourself. NOT the Insights portal (lags 2-3 days). NEVER fall back to LS. If the tlog report isn't listed yet, it has not auto-posted yet — it CANNOT be generated on demand (ABSOLUTE RULE 26); leave col D blank as an OPEN ITEM (it fills on a later re-run once posted). If col D is needed sooner and the In-store transactions report IS present, use its transaction_amt total (= GMV/1.1 directly).

Tip Report.xlsx:

  • Year sheet rows ≠ employee sheet rows — look up by date column A.
  • Never write decimals.
  • Never write 0 in column D — leave blank if no cash tip.

KNOWN ENVIRONMENT QUIRKS

  • LS API: /api/register_sales, page_size=200&page=N. sale_date is UTC — convert to ET before grouping.
  • LS payment names: "Lightspeed Credit Card", "CityHive", "UberEats", "DoorDash", "GrubHub", "Cash", "Minibar".
  • Instacart in LS: customer name (NOT a payment type) — never use API math; use Sales Reports per Step 1B.
  • CityHive cookie: auto-refreshes via pycookiecheat from Chrome _HiveNet_session cookie at api.cityhive.net.
  • GrubHub LS payment = subtotal + tip (tax goes to GrubHub).
  • UberEats: scrape "Sales (excl. tax)", NOT "Total Payout".
  • MCP Chrome doesn't share LS session with user's Chrome — if the LS UI (corkandbarrelnyc.retail.lightspeed.app) reads as logged-out in the MCP tab, do NOT ask the user and do NOT skip: auto-re-login from LS_WEB_USERNAME/LS_WEB_PASSWORD in scripts/.env (fill the email/password fields via form_input, submit, wait for dashboard), then continue (ABSOLUTE RULE 25). Logged-out is never a barrier and never an OPEN ITEM.

WHAT NOT TO DO

  • ❌ Do NOT serialize platform scrapes. Always parallel batch.
  • ❌ Do NOT take screenshots when JS extraction works.
  • ❌ Do NOT post screenshots to chat as part of normal flow.
  • ❌ Do NOT narrate "now scraping X" / "now writing Y". Final table only.
  • ❌ Do NOT use 🔧 CORRECTED unless the user explicitly told you what to fix.
  • ❌ Do NOT trust the script's "Instacart Rev" line as LS truth. Always cross-check via Sales Reports → Customer.
  • ❌ Do NOT report "all PASS" when any platform was UNVERIFIED — call it out.
  • ❌ Do NOT call the workflow done before the end-of-run double-check (Step 7).
  • ❌ Do NOT proceed to invoices, tip splits, or anything else before the verification table is presented.

Use it

Copy one of these into your project. Installing also returns the manifest and these snippets.

yaml
targets:
  - https://api.opensmartroute.ai/api/v1/registry/jonchoi17-cork-and-barrel-claude-daily-sales/manifest   # or paste the manifest below

Manifest

An Open Capability Manifest: the router reads it to know what this does, what it costs and when to pick it.

jonchoi17-cork-and-barrel-claude-daily-sales.ocm.jsonjson
{
  "ocm": "1",
  "id": "jonchoi17-cork-and-barrel-claude-daily-sales",
  "kind": "skill",
  "name": "daily-sales",
  "description": "Complete daily sales reconciliation workflow for Cork and Barrel NYC. Use this skill whenever the user mentions daily sales, do the sales, sales verification, reconcile sales, check the sales, do sales today, or any phrase referring to the end-of-day or start-of-day sales review process. This skill runs a Python script that pulls data from Lightspeed API, CityHive API, and platform CSVs — no browser required. It cross-verifies all values, calculates tips and cash reconciliation, and writes all 4 Excel files automatically. Also handles the Tip Report — splits tips among working employees and writes to Tip Report.xlsx (year sheet + individual employee sheets). Trigger even if the user only mentions one part of the workflow.",
  "publisher": "jonchoi17",
  "version": "1.0.0",
  "capabilities": {
    "domains": [
      "coding"
    ],
    "tags": [
      "skill-md",
      "github"
    ],
    "languages": [
      "en"
    ]
  },
  "quality_prior": 0.6,
  "examples": [
    "Complete daily sales reconciliation workflow for Cork and Barrel NYC. Use this skill whenever the user mentions daily sales, do the sales, sales verification, reconcile sales, check the sales, do sales today, or any phrase referring to the end-of-day or start-of-day sales review process. This skill runs a Python script that pulls data from Lightspeed API, CityHive API, and platform CSVs — no browser required. It cross-verifies all values, calculates tips and cash reconciliation, and writes all 4 Excel files automatically. Also handles the Tip Report — splits tips among working employees and writes to Tip Report.xlsx (year sheet + individual employee sheets). Trigger even if the user only mentions one part of the workflow."
  ],
  "primary": false,
  "metadata": {
    "source": {
      "provider": "github",
      "repository": "https://github.com/jonchoi17/cork-and-barrel-claude",
      "path": "skills/daily-sales/SKILL.md",
      "ref": "2597e06be32905815664ff39cefbefdade4d1c69",
      "url": "https://github.com/jonchoi17/cork-and-barrel-claude/blob/2597e06be32905815664ff39cefbefdade4d1c69/skills/daily-sales/SKILL.md",
      "key": "jonchoi17/cork-and-barrel-claude/skills/daily-sales/SKILL.md"
    }
  },
  "instructions": "# Daily Sales — Cork and Barrel NYC\n\n## ⛔ ABSOLUTE RULES\n\n**Read these every run. They override everything else.**\n\n1. **NO FREESTYLING.** Same steps, same order, every run. If the rule isn't here, ASK before deviating. Do not invent shortcuts, alternate paths, or \"close enough\" approximations.\n2. **PARALLEL OR NOTHING.** Every independent step runs in parallel. Open all platform tabs in one Chrome batch (one assistant message, multiple `navigate`/`tabs_create_mcp` calls — not one tab at a time), scrape ALL pages in one batch of `javascript_tool` / `get_page_text` calls (one message, N tool ca",
  "cost": {
    "context_tokens": 11099
  }
}

Fetch it by URL: GET /api/v1/registry/jonchoi17-cork-and-barrel-claude-daily-sales/manifest?version=1.0.0

Reviews

Star ratings from people who tried it. One review per account; edit yours any time.

No reviews yet. Install it, try it, and be the first to rate it.