Classification & Tagging Feature (CLASSIFICATION)
Overview
Add chemical classification and tagging across 3 tiers:
- Starter: Manual custom company tags on chemicals
- Standard: Auto-categorization from SDS parsed hazard data (H-codes → hazard classes)
- Pro: AI-generated plain-English hazard summaries
Visible in: inventory list (badges + filters), details page (new section), dashboard (charts).
Step 1: Alembic Migration — New Tables + Column
Single migration file.
New table chemiq_company_tags: company-scoped custom tags
tag_idUUID PK,company_idFK,nameVARCHAR(50),colorVARCHAR(7) default#6B7280,descriptionVARCHAR(255),created_byFK users,created_at,updated_at- UNIQUE(company_id, name)
New table chemiq_product_tags: junction
idUUID PK,company_product_idFK →chemiq_company_product_catalog,tag_idFK →chemiq_company_tags,assigned_byFK users,assigned_at- UNIQUE(company_product_id, tag_id), CASCADE deletes
New table chemiq_ai_hazard_summaries: AI summaries
idUUID PK,company_product_idFK (unique index),summary_textTEXT,handling_guidanceTEXT,risk_levelVARCHAR(20) (low/moderate/high/extreme),key_hazardsJSONB (array of top hazard phrases),model_usedVARCHAR(50),generated_at,sds_document_idFK
New column on chemiq_company_product_catalog:
hazard_categories JSONB DEFAULT '[]'— denormalized array like["Flammable", "Acute Toxicity", "Corrosive"]
Step 2: Backend Models
NEW app/db/models/chemiq/company_tag.py: CompanyTag, ProductTag SQLAlchemy models
EDIT app/db/models/chemiq/chemiq_product_catalog.py: Add hazard_categories column, relationships to ProductTag and AIHazardSummary
NEW app/db/models/chemiq/ai_hazard_summary.py: AIHazardSummary model
Step 3: Backend Schemas
NEW app/schemas/chemiq/tags.py:
CompanyTagCreate,CompanyTagUpdate,CompanyTagResponseTagAssignRequest(list of tag_ids)AIHazardSummaryResponseClassificationDataResponse(hazard_categories + tags + ai_summary combined)
EDIT app/schemas/chemiq/inventory.py: Add hazard_categories: list[str] and tags: list[CompanyTagResponse] to inventory list item response
Step 4: Backend Services
NEW app/services/chemiq/classification_service.py:
class ClassificationService:
def derive_hazard_categories(self, company_product_id):
"""Post-SDS-parse hook. Reads sds_hazard_info → joins H-codes to
ghs_hazard_codes reference → extracts unique hazard_class values →
writes to product_catalog.hazard_categories."""
def generate_ai_summary(self, company_product_id):
"""Reads SDS sections + hazard info → LLM prompt → stores summary,
handling_guidance, risk_level in ai_hazard_summaries."""
def get_classification(self, company_product_id):
"""Returns combined hazard_categories + tags + ai_summary."""
NEW app/services/chemiq/tag_service.py:
list_tags(company_id),create_tag(),update_tag(),delete_tag()assign_tags(company_product_id, tag_ids),remove_tags()
EDIT app/services/chemiq/chemiq_service.py (or SDS parse callback):
- After SDS parse completes, call
classification_service.derive_hazard_categories()
Step 5: Backend API Routes
NEW app/api/v1/chemiq/tags.py:
GET /companies/{company_id}/tags— list tagsPOST /companies/{company_id}/tags— create tagPUT /tags/{tag_id}— updateDELETE /tags/{tag_id}— deletePOST /products/{product_id}/tags— assign tagsDELETE /products/{product_id}/tags/{tag_id}— remove tagGET /products/{product_id}/classification— full classification dataPOST /products/{product_id}/ai-summary— generate/regenerate AI summary
EDIT app/api/v1/chemiq/inventory.py:
- Add query params:
hazard_category: Optional[str],tag_id: Optional[UUID] - Filter using JSONB contains for categories, subquery for tags
- Include
hazard_categoriesandtagsin list response
EDIT app/api/v1/chemiq/dashboard.py (if exists):
- Add
tag_distributionand ensurehazard_category_distributionuses the new denormalized column
Step 6: Frontend Types
EDIT tellus-ehs-hazcom-ui/src/types/index.ts:
interface CompanyTag { id: string; name: string; color: string; description?: string; product_count?: number; }
interface AIHazardSummary { summary_text: string; handling_guidance?: string; risk_level?: 'low'|'moderate'|'high'|'extreme'; key_hazards?: string[]; generated_at: string; }
// Extend ChemicalInventory with: hazard_categories: string[]; tags: CompanyTag[];
Step 7: Frontend API Functions
EDIT tellus-ehs-hazcom-ui/src/services/api/chemiq.api.ts:
listCompanyTags(),createTag(),updateTag(),deleteTag()assignTags(),removeTag()getClassification(),generateAISummary()- Update
listChemicals()filter params to includehazard_category,tag_id
Step 8: Frontend — TagManager Component
NEW tellus-ehs-hazcom-ui/src/pages/chemiq/inventory/components/TagManager.tsx:
- Popover/dropdown listing company tags with color dots
- Checkboxes for assigned tags
- Inline "Create new tag" with name + color picker
- Used from details page Classification section
Step 9: Frontend — Classification Section on Details Page
EDIT ChemicalDetailsPage.tsx:
- Add
<ClassificationSection>component between existing sections - Three subsections:
- GHS Hazard Categories — read-only badges with pictogram icons derived from SDS. Shows "Awaiting SDS" if not parsed.
- Custom Tags — assigned tag chips (removable) + "Add Tag" button → TagManager popover
- AI Hazard Summary — card with summary text, handling guidance, risk level badge. "Generate" / "Regenerate" button. Loading spinner.
NEW tellus-ehs-hazcom-ui/src/pages/chemiq/inventory/components/ClassificationSection.tsx
Step 10: Frontend — Inventory List Enhancements
EDIT InventoryListTab.tsx:
- Add small hazard category pictogram icons (16px) in each row (max 3-4 icons, overflow as +N)
- Add custom tag pills (colored dots + name, max 2, overflow as +N)
- Add "Hazard Category" dropdown filter (values from GHS hazard classes: Flammable, Corrosive, Toxic, Oxidizer, etc.)
- Add "Tag" dropdown filter (populated from company tags endpoint)
- Extend FilterChip for active tag/category filters
Step 11: Frontend — Dashboard Charts
EDIT dashboard component (create if needed):
- "Hazard Categories" bar/donut chart from
hazard_category_distribution - "Tags" distribution chart from
tag_distribution
Files Summary
| Action | File |
|---|---|
| NEW | tellus-ehs-hazcom-service/alembic/versions/XXXX_add_classification_tagging.py |
| NEW | tellus-ehs-hazcom-service/app/db/models/chemiq/company_tag.py |
| NEW | tellus-ehs-hazcom-service/app/db/models/chemiq/ai_hazard_summary.py |
| EDIT | tellus-ehs-hazcom-service/app/db/models/chemiq/chemiq_product_catalog.py |
| NEW | tellus-ehs-hazcom-service/app/schemas/chemiq/tags.py |
| EDIT | tellus-ehs-hazcom-service/app/schemas/chemiq/inventory.py |
| NEW | tellus-ehs-hazcom-service/app/services/chemiq/classification_service.py |
| NEW | tellus-ehs-hazcom-service/app/services/chemiq/tag_service.py |
| EDIT | tellus-ehs-hazcom-service/app/services/chemiq/chemiq_service.py |
| NEW | tellus-ehs-hazcom-service/app/api/v1/chemiq/tags.py |
| EDIT | tellus-ehs-hazcom-service/app/api/v1/chemiq/inventory.py |
| EDIT | tellus-ehs-hazcom-ui/src/types/index.ts |
| EDIT | tellus-ehs-hazcom-ui/src/services/api/chemiq.api.ts |
| NEW | tellus-ehs-hazcom-ui/src/pages/chemiq/inventory/components/TagManager.tsx |
| NEW | tellus-ehs-hazcom-ui/src/pages/chemiq/inventory/components/ClassificationSection.tsx |
| EDIT | tellus-ehs-hazcom-ui/src/pages/chemiq/inventory/ChemicalDetailsPage.tsx |
| EDIT | tellus-ehs-hazcom-ui/src/pages/chemiq/inventory/components/InventoryListTab.tsx |
Verification
- Create a custom tag → appears in tag list
- Assign tag to a chemical → tag shows on details page and inventory list row
- Chemical with parsed SDS →
hazard_categoriesauto-populated after parse - Filter inventory by hazard category → correct results
- Filter inventory by custom tag → correct results
- Generate AI summary on a chemical with SDS → summary displays with risk level
- Dashboard shows hazard category and tag distribution charts
- Remove tag from chemical → tag disappears from views