alpacalabsllc/product-data-cleanup
Clean a local FF&E CSV schedule by normalizing casing, dimensions, units, language, materials, and formatting. Use when asked to clean, fix, or standardize product data.
npx skills add https://github.com/AlpacaLabsLLC/skills-for-architects --skill product-data-cleanup
Takes a messy FF&E schedule and normalizes everything: casing, dimensions, units, language, materials vocabulary, currency formatting, and duplicates. Outputs a clean, consistent, spec-ready schedule.
Persistent cleanup operates on the nearest project's product-library.csv. Pasted tables may be previewed in Markdown but are not another persistent format.
The user provides a schedule in one of these ways:
product-library.csv under an ancestor containing PROJECT.md.If the input format is unclear, ask.
| Field | Rule | Example |
|-------|------|---------|
| Product Name | Title Case | eames lounge chair → Eames Lounge Chair |
| Brand | Title Case, preserve known abbreviations | HERMAN MILLER → Herman Miller, HAY → HAY |
| Collection | Title Case | cosm → Cosm |
| Category | Title Case, singular | chairs → Chair, TABLES → Table |
| Materials | Sentence case, lowercase after first word | MOLDED PLYWOOD, FULL GRAIN LEATHER → Molded plywood, full grain leather |
| Colors/Finishes | Title Case per item | walnut/black leather → Walnut / Black Leather |
Known brand abbreviations to preserve: HAY, USM, B&B, DWR, CB2, HBF, OFS, SitOnIt, 3form, ICF
Map free-text categories to the canonical vocabulary and alias table defined in ../../schema/product-schema.md. Read that file for the full mapping of variations (English, Spanish, legacy terms) to canonical category names.
If a category is ambiguous, keep the closest match and add a [?] flag for the user to review.
Splitting combined dimensions:
| Input | → W | → D | → H | → Unit |
|-------|-----|-----|-----|--------|
| 32 x 24 x 30 in | 32 | 24 | 30 | in |
| 80 × 60 × 75 cm | 80 | 60 | 75 | cm |
| W32 D24 H30 | 32 | 24 | 30 | (infer) |
| 32"W x 24"D x 30"H | 32 | 24 | 30 | in |
| Ancho: 80, Prof: 60, Alto: 75 cm | 80 | 60 | 75 | cm |
Dimension rules:
" as inches, ' as feet (convert to inches: 2'6" → 30)×, x, X, by, por as separatorsin. If values suggest cm (> 100 or explicit), use cm. If truly ambiguous, flag with [?].Do NOT convert units. Keep the original unit. Designers need the manufacturer's spec for ordering.
Detect the language of each field value and normalize to English unless the user specifies otherwise.
| Spanish (common in UY sources) | → English |
|-------------------------------|-----------|
| Silla | Chair (category) |
| Mesa | Table (category) |
| Escritorio | Desk (category) |
| Madera | Wood (material) |
| Cuero | Leather (material) |
| Acero | Steel (material) |
| Vidrio | Glass (material) |
| Tela | Fabric (material) |
| Mármol | Marble (material) |
| Roble | Oak (material) |
| Nogal | Walnut (material) |
| Blanco | White (color) |
| Negro | Black (color) |
| Natural | Natural (keep as-is) |
| Cromado | Chrome (finish) |
Rule: Translate category, material, and color/finish fields. Leave Product Name and Brand as-is (proper nouns).
If the user says "keep in Spanish" or specifies a target language, respect that.
Standardize common material terms:
| Variations | → Standard |
|-----------|------------|
| SS, Stainless, S/S | Stainless steel |
| Ply, Plywood, Mold ply | Molded plywood |
| MDF, Medium density | MDF |
| HPL, High pressure laminate | HPL |
| Lam, Laminate | Laminate |
| Fab, Textile | Fabric |
| COM, C.O.M. | COM (Customer's Own Material) |
| COL, C.O.L. | COL (Customer's Own Leather) |
| Powder coat, PC, Pwdr | Powder-coated |
| Chrm, Chrome plated | Chrome |
| Anodized alum, Anod. | Anodized aluminum |
| Ven, Veneer | Veneer |
| Sol. wood, Solid | Solid wood |
$, €, £, ¥) — store symbol as currency code in separate column. and , — detect locale: 1.234,56 is EU format, 1,234.56 is US)5695.00$ alone defaults to USD unless context suggests otherwise (UY site → UYU, EU site → EUR)wood / metal / glass → Wood, Metal, Glass (comma-separated)Read the input. Report: "Loaded N rows with M columns."
Map input columns to the canonical schema. If column mapping is ambiguous (e.g., a column called "Size" could be combined dimensions), ask the user.
Scan all rows and produce a summary:
## Cleanup Preview
- **Casing**: X product names need Title Case
- **Categories**: Y rows have non-standard categories (mapping: "chairs" → Seating, etc.)
- **Dimensions**: Z rows have combined dimensions to split
- **Language**: W rows have Spanish-language fields to translate
- **Materials**: V rows have non-standard material terms
- **Prices**: U rows need currency formatting cleanup
- **Duplicates**: T potential duplicate rows found
- **Empty fields**: S rows missing dimensions, R rows missing price
The issue summary is the change preview. Present selectable cleanup groups directly through the single confirmation gate; do not ask the same question first in prose.
Process every row through the active cleanup rules. Track every change made.
Show a before/after diff for a sample of changed rows (up to 5 examples). Then show the full cleaned table.
Report:
## Cleanup Complete
- Rows processed: N
- Changes made: X
- Flagged for review: Y (marked with [?])
Read ../../schema/product-schema.md and ../../schema/csv-conventions.md. For multiple changed rows, materialize the complete proposed 33-column CSV as a temporary or user-visible review file, validate that candidate, preview the whole change once, and use the single confirmation gate. After approval, invoke python3 "${CLAUDE_PLUGIN_ROOT}/skills/master-schedule/scripts/csv-library.py" import product --project <project-root> --source <review.csv> exactly once for one atomic replacement; never loop update. A genuinely single-record edit may instead invoke the same plugin-root helper's update command once with one uniquely matching stable field. Never overwrite an arbitrary input or hand-edit product-library.csv.
Take alpacalabsllc/product-data-cleanup from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.