Every week I copy numbers out of exports into a report. Build me a small page that does it: I paste the exports in, and it tells me what moved. It runs only on my device, so nothing I paste goes anywhere.
Before you start
Ask me one question, then stop and wait: "Besides newsletter sends and LinkedIn posts, is there one more number your weekly report always has? Name it and its CSV columns, or say 'use the sample'."
If I name one, add a third paste box for it with the same treatment as the newsletters (latest value against the average of the earlier rows), and tell me in one line what you added. If I say "use the sample", build exactly what's below and nothing more.
Steps
Build one HTML file with everything inline: plain HTML, CSS and JavaScript. No libraries, no chart packages, no fonts or scripts from the internet, and no network requests of any kind. It must work with wifi off. Don't save anything to the browser's storage. Draw charts with inline SVG. Keep the code plain: one short function per job (read the CSV, work out a newsletter, work out LinkedIn, draw a chart). Before you hand it over, read the script through once for unmatched brackets or quotes, because one typo stops every button. If this app can run code, check the script with it (for example
node --check).Read the CSVs. The first row is the header; match columns by name, in any order and any case. Allow quoted fields with commas inside. Skip any row whose number columns aren't whole numbers (blank, "n/a", text), and say under the results how many rows were skipped and from which box. Commas inside numbers ("18,240") count as numbers.
Newsletters (columns
newsletter, send_date, delivered, opens, clicks). For each newsletter, in the order they first appear:- the latest send is the row with the latest
send_date; - open rate = opens ÷ delivered and click rate = clicks ÷ delivered, shown as percentages with one decimal (39.0%);
- the average is the plain mean of that newsletter's open rates (and click rates) on its earlier sends, not a total divided by a total;
- show the latest open rate, its average, and the word up or down with an arrow (▲ up, ▼ down), then the same for clicks;
- a small SVG line chart of that newsletter's open rate across all its sends, oldest on the left, the latest point marked. A newsletter with only one send shows its rates and "no earlier sends to compare".
- the latest send is the row with the latest
LinkedIn (columns
date, post, impressions, clicks, reactions). Last week is the 7 days ending on the latest date in the file, counting that day; the week before is the 7 days before that. Show impressions and clicks for each week, with thousands separators (18,630), whether each went up or down, and the top post of last week by clicks, by its title.What moved. Three short plain-English lines under the heading "What moved", each naming the newsletter or LinkedIn and the change, biggest first. Facts from the numbers only; no guesses about why.
The screen, in this order: the title "Weekly numbers"; a box labeled "Newsletter sends (CSV)"; a box labeled "LinkedIn posts (CSV)"; a Show my numbers button; a Load sample button; the results: one card per newsletter side by side (they wrap to one column on a phone), then the LinkedIn card, then What moved. At the very top, put a line that reads "Not running: open this file in a browser such as Chrome, Safari or Edge. Previews and text editors don't run the buttons." The script hides that line as soon as it loads. Load sample fills both boxes with exactly this made-up data and shows the numbers at once.
Newsletter sends:
newsletter,send_date,delivered,opens,clicks Recycling Weekly,2026-09-15,18240,6384,912 Recycling Weekly,2026-09-22,18310,6226,860 Recycling Weekly,2026-09-29,18402,6624,957 Recycling Weekly,2026-10-06,18455,6460,886 Recycling Weekly,2026-10-13,18520,7223,1185 Plastics Brief,2026-09-16,12105,4963,726 Plastics Brief,2026-09-23,12160,5107,778 Plastics Brief,2026-09-30,12201,5003,744 Plastics Brief,2026-10-07,12248,5266,796 Plastics Brief,2026-10-14,12290,4548,590 Electronics Digest,2026-09-17,9870,3455,434 Electronics Digest,2026-09-24,9902,3664,475 Electronics Digest,2026-10-01,9935,3378,417 Electronics Digest,2026-10-08,9961,3586,458 Electronics Digest,2026-10-15,10004,3602,510LinkedIn posts:
date,post,impressions,clicks,reactions 2026-10-02,Call for speakers is open,4210,96,58 2026-10-03,Plastics Brief: bale prices this week,3120,71,34 2026-10-06,Photo recap from the plant tour,5480,63,141 2026-10-08,Early-bird registration ends Friday,3890,112,41 2026-10-09,Five charts on battery collection,6020,154,88 2026-10-10,Meet the new sponsor lounge,2750,38,29 2026-10-13,Webinar replay: sorting robots,4980,131,63 2026-10-15,Award finalists announced,7340,205,212 2026-10-16,Electronics Digest: a recycler opens a second site,3560,89,37Check your work against the sample before you hand the file over: Recycling Weekly's latest open rate is 39.0% against an average of 35.0% (up), Plastics Brief's is 37.0% against 41.7% (down), LinkedIn last week had 18,630 impressions and 463 clicks against 18,510 and 400 the week before, and the top post is "Award finalists announced". If any of those come out different, fix the math before you hand it over.
Hand it over. Give me the file as a download if this app can make one; otherwise show the code. Then tell me how to open it, using only the lines that fit my device and this app:
- If this app has a preview where buttons work (a canvas, artifact or preview pane), use it.
- On a computer: save it as
weekly-numbers.htmland open it in Chrome, Safari or Edge. If you copy the code into a file yourself, use a plain-text editor; on a Mac, in TextEdit choose Format, then Make Plain Text, before you save. - On a phone: a saved file opens as a preview, and previews don't run buttons. Use this app's preview, or open the file on a computer. Then say, word for word: "Press Load sample. If the numbers match the ones I checked, it works. Next week, export your sends and posts as CSV, paste them in, and press Show my numbers."
End with a reminder that the page only knows what's in the export: say which numbers it can't see (unsubscribes, revenue, anything not in the CSV), and suggest running the Make it pass prompt next to test it with tricky exports.
Branding
Style it in my company's colors if I've shared a brand guide. Otherwise use the default look (Uplift Duo): page #FAF9F6, text #1A1A1A, buttons #E85D04 with white text, white cards with a 1px #E0DDD5 border, up in green #1F7A4D, down in red #B42318. System fonts only, so it works offline. Never make up a palette, and don't put a company name on the page.
Rules
- No network requests, external files or storage. If something I ask for would need the internet (a live API, a login), say so and leave it out; the export is the connection.
- Never invent a number. Everything on the page comes from the pasted CSVs.
- Keep it to one file I can email to a coworker.