Skip to main content

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_id UUID PK, company_id FK, name VARCHAR(50), color VARCHAR(7) default #6B7280, description VARCHAR(255), created_by FK users, created_at, updated_at
  • UNIQUE(company_id, name)

New table chemiq_product_tags: junction

  • id UUID PK, company_product_id FK → chemiq_company_product_catalog, tag_id FK → chemiq_company_tags, assigned_by FK users, assigned_at
  • UNIQUE(company_product_id, tag_id), CASCADE deletes

New table chemiq_ai_hazard_summaries: AI summaries

  • id UUID PK, company_product_id FK (unique index), summary_text TEXT, handling_guidance TEXT, risk_level VARCHAR(20) (low/moderate/high/extreme), key_hazards JSONB (array of top hazard phrases), model_used VARCHAR(50), generated_at, sds_document_id FK

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, CompanyTagResponse
  • TagAssignRequest (list of tag_ids)
  • AIHazardSummaryResponse
  • ClassificationDataResponse (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 tags
  • POST /companies/{company_id}/tags — create tag
  • PUT /tags/{tag_id} — update
  • DELETE /tags/{tag_id} — delete
  • POST /products/{product_id}/tags — assign tags
  • DELETE /products/{product_id}/tags/{tag_id} — remove tag
  • GET /products/{product_id}/classification — full classification data
  • POST /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_categories and tags in list response

EDIT app/api/v1/chemiq/dashboard.py (if exists):

  • Add tag_distribution and ensure hazard_category_distribution uses 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 include hazard_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:
    1. GHS Hazard Categories — read-only badges with pictogram icons derived from SDS. Shows "Awaiting SDS" if not parsed.
    2. Custom Tags — assigned tag chips (removable) + "Add Tag" button → TagManager popover
    3. 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

ActionFile
NEWtellus-ehs-hazcom-service/alembic/versions/XXXX_add_classification_tagging.py
NEWtellus-ehs-hazcom-service/app/db/models/chemiq/company_tag.py
NEWtellus-ehs-hazcom-service/app/db/models/chemiq/ai_hazard_summary.py
EDITtellus-ehs-hazcom-service/app/db/models/chemiq/chemiq_product_catalog.py
NEWtellus-ehs-hazcom-service/app/schemas/chemiq/tags.py
EDITtellus-ehs-hazcom-service/app/schemas/chemiq/inventory.py
NEWtellus-ehs-hazcom-service/app/services/chemiq/classification_service.py
NEWtellus-ehs-hazcom-service/app/services/chemiq/tag_service.py
EDITtellus-ehs-hazcom-service/app/services/chemiq/chemiq_service.py
NEWtellus-ehs-hazcom-service/app/api/v1/chemiq/tags.py
EDITtellus-ehs-hazcom-service/app/api/v1/chemiq/inventory.py
EDITtellus-ehs-hazcom-ui/src/types/index.ts
EDITtellus-ehs-hazcom-ui/src/services/api/chemiq.api.ts
NEWtellus-ehs-hazcom-ui/src/pages/chemiq/inventory/components/TagManager.tsx
NEWtellus-ehs-hazcom-ui/src/pages/chemiq/inventory/components/ClassificationSection.tsx
EDITtellus-ehs-hazcom-ui/src/pages/chemiq/inventory/ChemicalDetailsPage.tsx
EDITtellus-ehs-hazcom-ui/src/pages/chemiq/inventory/components/InventoryListTab.tsx

Verification

  1. Create a custom tag → appears in tag list
  2. Assign tag to a chemical → tag shows on details page and inventory list row
  3. Chemical with parsed SDS → hazard_categories auto-populated after parse
  4. Filter inventory by hazard category → correct results
  5. Filter inventory by custom tag → correct results
  6. Generate AI summary on a chemical with SDS → summary displays with risk level
  7. Dashboard shows hazard category and tag distribution charts
  8. Remove tag from chemical → tag disappears from views