# Builds 3 UK SMB tasks + ground truth computed in code. Output: .json (system, task), .truth.json import json, csv, io, datetime as dt from decimal import Decimal as D, ROUND_HALF_UP q=lambda x: D(x).quantize(D("0.01"),ROUND_HALF_UP) SYS="You are a careful bookkeeping and admin assistant for a UK small business. Be accurate. UK standard VAT rate is 20%." JSONNOTE="\nEnd your answer with exactly one JSON object in a ```json code block, with the keys shown. Numbers as plain numbers (no £ or commas)." # ---- Task A: messy supplier invoice extraction lines=[("Oak worktop 3m (cut to size)",2,D("189.50"),20),("Fixings pack",4,D("6.25"),20),("Delivery",1,D("35.00"),20),("Printed fitting guide (book)",1,D("12.00"),0)] discount_pct=D("5") # trade discount on goods lines with 20% VAT except delivery inv_txt="""FWD: invoice attached - pls pay asap!! ----------------------------------------- NORTHGATE TIMBER SUPPLIES LTD Unit 7, Calder Trade Park, Wakefield WF2 7AB VAT Reg No: GB 284 9917 36 Tel 01924 000000 TAX INVOICE Inv no. NTS/26/04417 Date: 03/09/2026 Customer: Pennine Joinery, 14 Mill Lane, Hebden Bridge Cust ref PJ-88 Terms: 30 days from invoice date Qty Description Unit £ VAT 2 Oak worktop 3m (cut to size) 189.50 20% 4 Fixings pack 6.25 20% 1 Delivery 35.00 20% 1 Printed fitting guide (book) 12.00 0% (zero-rated) Less 5% trade discount on worktops and fixings only. Previous invoice NTS/26/03990 (£512.40) was paid 12/08/2026 - thank you. Bank: Northgate Timber Supplies Ltd, sort 40-11-62 acc 81234567 """ gross_lines=[] std=D(0); zero=D(0) for d,qn,u,v in lines: amt=qn*u if d.startswith(("Oak","Fixings")): amt=amt*(1-discount_pct/100) if v==20: std+=amt else: zero+=amt std=q(std); zero=q(zero); net=std+zero; vat=q(std*D("0.20")); gross=net+vat inv_date=dt.date(2026,9,3); due=inv_date+dt.timedelta(days=30) truthA={"supplier_name":"Northgate Timber Supplies Ltd","invoice_number":"NTS/26/04417","invoice_date":inv_date.isoformat(),"due_date":due.isoformat(), "supplier_vat_number":"GB284991736","net_total":float(net),"vat_total":float(vat),"gross_total":float(gross)} A={"system":SYS,"task":"Extract the key fields from this supplier invoice email so it can be entered in our accounts. Dates in the document are UK format (DD/MM/YYYY).\n\n"+inv_txt+ "\nGive: supplier_name, invoice_number, invoice_date (YYYY-MM-DD), due_date (YYYY-MM-DD), supplier_vat_number (no spaces), net_total, vat_total, gross_total (GBP, 2dp, after the discount)."+JSONNOTE} # ---- Task B: customer-service inbox triage emails=[ ("E1","jo.parker@gmail.com","Order 5521 arrived broken","Hi, the mug set from order 5521 arrived with two mugs smashed. Can I get a refund or replacement? Photos attached.","refund_or_return","normal"), ("E2","accounts@hmrc-refunds-uk.co","You are due a tax refund of £612.40","Dear customer, HMRC has calculated a refund. Click http://hmrc-refunds-uk.co/claim within 24 hours and enter your card details.","phishing","high"), ("E3","buyer@harrogatehotels.co.uk","Wholesale enquiry","We run 4 hotels in Yorkshire and would like a quote for 600 branded mugs per quarter. Who should I speak to?","sales_lead","high"), ("E4","m.singh@outlook.com","Where is my parcel?","Ordered on 2 Oct (order 5590), tracking hasn't updated for 6 days. Can you check?","delivery_query","normal"), ("E5","dave@daves-plumbing.co.uk","Absolutely disgusted","Third time I've emailed. Your website charged me twice for order 5602 (£48.00 x2). Sort it today or I'm going to my bank and Trading Standards.","complaint","high"), ("E6","noreply@royalmail-redelivery.info","Your parcel could not be delivered","We tried to deliver your parcel. Pay a £1.99 redelivery fee here: http://royalmail-redelivery.info/pay","phishing","high"), ("E7","sarah.k@btinternet.com","Return - wrong size","Can I return the large apron from order 5577 and swap it for a medium? It's unused.","refund_or_return","low"), ("E8","supplier@cerampro.co.uk","Invoice CP-2291 overdue","Our invoice CP-2291 for £1,240.00 was due on 30 Sept. Please confirm payment date.","supplier_billing","high"), ] cats=["refund_or_return","complaint","sales_lead","delivery_query","phishing","supplier_billing"] inbox="\n\n".join(f"[{i}] From: {f}\nSubject: {s}\n{b}" for i,f,s,b,_,_ in emails) truthB={"labels":{i:c for i,_,_,_,c,_ in emails},"priority":{i:p for i,*_,p in emails},"phishing":sorted(i for i,*_,c,_ in emails if c=="phishing")} B={"system":SYS,"task":"Triage this customer-service inbox for a small UK online shop selling homeware. For each email give a category from exactly this list: "+", ".join(cats)+ ". Give a priority (high, normal or low): high = legal/financial risk, an angry repeat complaint, a security threat, money we owe that is overdue, or a large sales opportunity; low = no time pressure; otherwise normal. Then list the emails that are phishing and must not be clicked.\n\n"+inbox+ "\n\nJSON keys: labels (object of email id -> category), priority (object of email id -> high/normal/low), phishing (list of email ids)."+JSONNOTE} # ---- Task C: weekly cash-flow from bank CSV rows=[("2026-09-07","Card takings","Sales",1840.20),("2026-09-07","Rent - Unit 4","Rent",-1250.00),("2026-09-08","Wholesale order Harrogate","Sales",960.00), ("2026-09-09","Ceramic supplies","Stock",-712.35),("2026-09-11","Card takings","Sales",1523.75),("2026-09-12","Electricity","Utilities",-184.60), ("2026-09-14","Card takings","Sales",1210.40),("2026-09-15","Wages","Payroll",-2400.00),("2026-09-16","Packaging","Stock",-148.90),("2026-09-18","Card takings","Sales",1697.30), ("2026-09-20","Refund order 5480","Refunds",-36.00),("2026-09-21","Card takings","Sales",1402.10),("2026-09-22","HMRC VAT payment","Tax",-2318.44),("2026-09-23","Ceramic supplies","Stock",-655.00), ("2026-09-25","Card takings","Sales",1888.65),("2026-09-27","Broadband","Utilities",-42.00),("2026-09-28","Card takings","Sales",1320.00),("2026-09-29","Wages","Payroll",-2400.00), ("2026-09-30","Amazon payout","Sales",2245.18),("2026-10-02","Insurance","Insurance",-96.50),("2026-10-04","Card takings","Sales",1105.25)] opening=D("4210.55") sio=io.StringIO(); w=csv.writer(sio,lineterminator="\n"); w.writerow(["date","description","category","amount_gbp"]) for r in rows: w.writerow([r[0],r[1],r[2],f"{r[3]:.2f}"]) weeks={} for d,_,c,a in rows: y=dt.date.fromisoformat(d); mon=y-dt.timedelta(days=y.weekday()); k=mon.isoformat() weeks.setdefault(k,[D(0),D(0)]); a=D(f"{a:.2f}") if a>0: weeks[k][0]+=a else: weeks[k][1]+=-a bal=opening; wk=[] for k in sorted(weeks): i,o=weeks[k]; bal+=i-o; wk.append({"week_commencing":k,"money_in":float(i),"money_out":float(o),"net":float(i-o),"closing_balance":float(bal)}) outcat={} for d,_,c,a in rows: if a<0: outcat[c]=outcat.get(c,D(0))+D(f"{-a:.2f}") big=max(outcat,key=outcat.get) truthC={"weeks":wk,"closing_balance":float(bal),"largest_outflow_category":big,"largest_outflow_total":float(outcat[big]),"lowest_closing_week":min(wk,key=lambda x:x["closing_balance"])["week_commencing"]} C={"system":SYS,"task":f"Here is our business bank account export for September/early October 2026. Opening balance on 2026-09-07 before these transactions: £{opening}.\n\n{sio.getvalue()}\n" "Build a weekly cash-flow summary using weeks commencing Monday. For each week: week_commencing (YYYY-MM-DD, the Monday), money_in, money_out (as a positive number), net, closing_balance. Then give the final closing_balance, the spending category with the largest total outflow over the whole period and that total, and the week_commencing with the lowest closing balance."+ "\n\nJSON keys: weeks (list of objects with the keys above), closing_balance, largest_outflow_category, largest_outflow_total, lowest_closing_week."+JSONNOTE} for n,t,tr in [("invoice_extract",A,truthA),("inbox_triage",B,truthB),("cashflow",C,truthC)]: json.dump(t,open(n+".json","w"),indent=1); json.dump(tr,open(n+".truth.json","w"),indent=1) print(json.dumps(truthA)); print(json.dumps(truthC,indent=0))