Frontend display configuration for the 10 material categories. Mirrors the Python category_field_registry.py on the MIVAA backend — the Python registry decides which fields the AI extracts; this TypeScript registry decides which fields the UI shows and how they're grouped.
File: src/lib/categoryFieldRegistry.ts (~1000 lines)
These match the material_categories DB table exactly:
tiles · wood · decor · furniture · general_materials · paint_wall_decor · heating · sanitary · kitchen · lighting
Adding an 11th category requires changes in three places: the DB enum, the Python registry, and this TypeScript registry. Drift between the three is a bug.
type UploadCategory = 'tiles' | 'wood' | 'decor' | 'furniture' | ...;
interface DisplaySection {
key: string; // React key + conditional logic id
label: string; // Section heading
fields: Array<{ key: string; label: string }>; // Ordered field list
}
interface CategoryDisplayConfig {
displayName: string; // Human-readable category name
themeColor: string; // Hex color for badge / accent
sections: DisplaySection[]; // Modal sections, in render order
controlledVocab: string[]; // AI-extracted slugs that map here
}
const CATEGORY_DISPLAY_REGISTRY: Record<UploadCategory, CategoryDisplayConfig>;
ProductDetailModal reads CATEGORY_DISPLAY_REGISTRY[product.category].sections and renders each section as a heading + key/value list. For each field in the section:
field.key in product.metadata (merged with properties and specifications)field.label: valueThis means the AI extraction can populate any subset of the fields and the UI degrades cleanly. To add a new field to the modal: register it in the section's fields array — no React changes needed.
The AI extractor returns a fine-grained material_category (e.g. floor_tile, oak_plank, pendant_lamp). resolveUploadCategory() maps these back to one of the 10 top-level categories using each config's controlledVocab array.
Example: any of floor_tile, wall_tile, bathroom_tile, porcelain_tile, ceramic_tile, tile, porcelain, ceramic resolves to tiles.
When adding a new fine-grained label upstream, add the slug to the matching controlledVocab here so products land in the right category.
Most categories share these section keys, in this order:
| Section | Typical fields |
|---|---|
appearance |
colors, primary_color_hex, patterns, texture, finish, visual_effect |
performance |
category-specific ratings (PEI, slip resistance, water absorption, fire rating, hardness, etc.) |
application |
recommended use, installation method, traffic level |
dimensions |
width, height, thickness, weight |
compliance |
certifications, standards (CE, EN, ISO, …) |
sustainability |
recyclability, VOC, eco labels |
Categories with niche needs add their own sections (e.g. wood → species; lighting → electrical; heating → thermal_output).
The exact section list per category is the source of truth in categoryFieldRegistry.ts — read that file when in doubt.
field.key unless the same key also gets renamed in the Python registry and any DB metadata that's already populated. Otherwise existing products silently lose that field in the UI.controlledVocab values — order doesn't matter (membership lookup), but consistency helps PR review.design-system.md).key not already used by other categories so cross-category logic (filters, aggregations) doesn't accidentally collide.mivaa-pdf-extractor/app/services/extraction/category_field_registry.pysrc/components/features/products/ProductDetailModal.tsxresolveUploadCategory() exported from the same categoryFieldRegistry.ts (re-exported by materialCategories.ts for legacy import paths)material_categories