How this store was built

Glaze & Grid is a demo store for a fictional tile shop. It shows what is hard to get right when goods are sold by size: turning a room size into whole boxes, retail and trade prices side by side, and messy supplier files turned into clean product data. Everything here is sample data: no real orders, customers or sales.

Version 1 of this page, for phase 1.

What phase 1 contains

  • The storefront. A home page with a tile wall drawn at true scale, a shop with search and filters, 17 product pages with an area calculator on each tile page, four help pages and this page. The pages are static, and nothing loads from outside the site: the fonts are self-hosted.
  • The catalogue. 17 products and 80 variations in six groups: porcelain looks (5 products, 24 variations), wood-look and outdoor (2, 10), wall and feature (4, 20), stone (1, 4), fixing (4, 21) and the sample box (1, 1).
  • Box and pallet data. 58 variations are tiles sold by the m², in whole boxes. Each has its size, thickness, pieces and m² per box, box weight, boxes per pallet and stock split into shade lots: 108 lots in all.
  • Four prices for each tile and fixing. 1 to 9 boxes, 10+ boxes (about 6% less), a full pallet (about 12% less) and trade (about 18% less, at any quantity).
  • Sample values, labelled. Slip ratings, frost resistance, underfloor heating and adhesive classes are sample data, not test results. There are no customer reviews or star ratings anywhere.

Two messy supplier files

The catalogue comes from two fictional suppliers whose files are messy on purpose, like real price lists.

A porcelain maker's price and stock list, exported from a spreadsheet:

  • EU numbers ("1.268,00"), sizes written many ways ("600x600", "600 x 600 mm", "60X120 cm", "Hex 15") and units written m2, M2, m² or sqm
  • prices with the euro sign before or after, some per box instead of per m²
  • SKUs in lower case, with trailing spaces or with spaces instead of dashes
  • the same row twice, plus an updates block at the bottom with newer stock and a new price
  • five broken rows: a missing price, too many columns, a row cut short, "n/a" and "POA"

Its 81 data rows give 73 variations in 15 products. One row is an exact copy, two are replaced by later rows, and five are skipped with a reason.

A zellige workshop in Fez, in French with an Arabic notes column:

  • prices in Moroccan dirhams ("480,00 DH", "DH 500,00", "540 DH"), with thousands split by a space, a no-break space or a narrow no-break space
  • sizes like "10/10" and thickness like "10 à 12 mm"
  • stock per kiln batch in m², and lead times like "sur commande (4 sem.)"
  • a newer price for one glaze further down, and one glaze marked discontinued

Its 8 data rows give 6 variations in 1 product. Add the sample box, which the store makes itself: 73 + 6 + 1 = 80 variations.

How the data is cleaned

The cleaning rules are written down per supplier: what each column means, how to read numbers, sizes and stock, how SKUs are built, and which row wins when an item appears twice (the lower one). Prices follow fixed rules: supplier cost times a markup for the material (1.6 to 1.8), rounded up to a price ending in .90. Dirhams become euros at a fixed demo rate of 10.85 MAD per euro (not a market rate), plus 30% for shipping and import costs.

Every variation keeps an import trace: the line number and raw text of its source row, and each change the importer has to make, such as size '600x600' → 60 x 60 cm, or price 73,15 per box / 2.88 m² → 25.4 per m².

What is done so far: the clean catalogue and the two messy files come from one master list, so the right answer for every row is known, and the expected dry-run counts are written into the import rules. The importer that reads the messy files on its own comes in phase 2. Its test: rebuild this exact catalogue.

The calculator

Type a room size in metres or feet and pick a pattern. You get whole boxes and the full kit.

  • Waste (sample rules): 10% straight, 12% brick bond, 15% herringbone, 12% for the travertine set, plus 5% for handmade tiles and 2% for tiles 120 cm long or more. Herringbone needs a rectangle at least twice as long as it is wide.
  • Prices: full pallets at the pallet price, other boxes at the 10+ or box price. Trade buyers get the trade price on every box and kit item.
  • The kit: adhesive (grey or white, 20 kg bags), grout from tile size, thickness and joint width (5 kg bags), edge trims by tile thickness (2.5 m lengths) and levelling clips for large tiles (packs of 100 or 500).
  • Shade lots: it picks the smallest single lot that covers the order, and warns when no single lot does or when stock runs short.

25 test cases fix 667 expected values, from box counts to the last cent. A Python reference calculator passes all 25, and the JavaScript calculator on the product pages is tested against the same cases. Box counts are rounded to 6 decimals before rounding up, because float maths can add a box: 10.8 m² of 20 mm pavers in boxes of 0.72 m² is exactly 15 boxes, but in JavaScript 10.8 / 0.72 gives 15.000000000000002, and a plain round-up says 16.

Images and checks

  • 55 product swatches at 1200 px (40 tile and stone, 15 fixing and sample box): 20 made from 12 free CC0 (public domain) materials, 35 drawn by code.
  • 19 scale drawings: 16 show each tile size next to a door and a 1 m rule, 3 show the trims in cross-section. Plus 20 tile sheets for the wall on the home page.
  • 3 room pictures, rendered in Blender with each tile's size, joint width and grout colour from the store data. The rooms, bath and kitchen units are modelled by code; the props, other materials and daylight are free CC0 assets from Poly Haven and ambientCG.
  • A licence log with a row for every image file: source, asset ID, licence and how it was made.
  • A data check runs 16 tests on the product data, the rules and both supplier files, from box and pallet fields to banned claim words. All 16 pass.

What comes next

Phase 2: the real WooCommerce store, with the same data, started in your browser on click (WordPress Playground):

  • an importer with a dry run that shows what would change, row by row, before anything is saved; then Apply in batches, with a log and an undo file
  • a trade login: trade prices for trade buyers only, order by SKU or a pasted list, quotes that turn into orders, pay on account, reorders from the same shade lot
  • a cart that checks boxes and prices again on the server
  • a store chat that answers from the products and help pages, with numbered sources

Phase 3: text, languages and feed:

  • product text drafted by AI from the specs and reviewed before it goes live, with every number checked against the data
  • French and Arabic pages, with Arabic right to left
  • a product feed, and a report that compares the raw supplier data with the clean data
  • this page, updated with numbers from real runs