From f74b8d5402851ab21981fafa36fbc8bdbf0e407e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Nov 2025 18:20:05 +0000 Subject: [PATCH] refactor: Remove TEXT fields and use only reasoning_data for i18n Completed the migration to structured reasoning_data for multilingual dashboard support. Removed hardcoded TEXT fields (reasoning, consequence) and updated all related code to use JSONB reasoning_data. Changes: 1. Models Updated (removed TEXT fields): - PurchaseOrder: Removed reasoning, consequence TEXT columns - ProductionBatch: Removed reasoning TEXT column - Both now use only reasoning_data (JSONB/JSON) 2. Dashboard Service Updated: - Changed to return reasoning_data instead of TEXT fields - Creates default reasoning_data if missing - PO actions: reasoning_data with type and parameters - Production timeline: reasoning_data for each batch 3. Unified Schemas Updated (no separate migration): - services/procurement/migrations/001_unified_initial_schema.py - services/production/migrations/001_unified_initial_schema.py - Removed reasoning/consequence columns from table definitions - Updated comments to reflect i18n approach Database Schema: - purchase_orders: Only reasoning_data (JSONB) - production_batches: Only reasoning_data (JSON) Backend now generates: { "type": "low_stock_detection", "parameters": { "supplier_name": "Harinas del Norte", "days_until_stockout": 3, ... }, "consequence": { "type": "stockout_risk", "severity": "high" } } Next Steps: - Frontend: Create i18n translation keys - Frontend: Update components to translate reasoning_data - Test multilingual support (ES, EN, CA) --- .../app/services/dashboard_service.py | 25 +++++++++++-- .../procurement/app/models/purchase_order.py | 35 +++++++++++++------ .../versions/001_unified_initial_schema.py | 8 ++--- services/production/app/models/production.py | 34 ++++++++++++------ .../versions/001_unified_initial_schema.py | 7 ++-- 5 files changed, 75 insertions(+), 34 deletions(-) diff --git a/services/orchestrator/app/services/dashboard_service.py b/services/orchestrator/app/services/dashboard_service.py index b7fbade6..8121af51 100644 --- a/services/orchestrator/app/services/dashboard_service.py +++ b/services/orchestrator/app/services/dashboard_service.py @@ -367,14 +367,23 @@ class DashboardService: # Calculate urgency based on required delivery date urgency = self._calculate_po_urgency(po) + # Get reasoning_data or create default + reasoning_data = po.get("reasoning_data") or { + "type": "low_stock_detection", + "parameters": { + "supplier_name": po.get('supplier_name', 'Unknown'), + "product_names": ["Items"], + "days_until_stockout": 7 + } + } + actions.append({ "id": po["id"], "type": ActionType.APPROVE_PO, "urgency": urgency, "title": f"Purchase Order {po.get('po_number', 'N/A')}", "subtitle": f"Supplier: {po.get('supplier_name', 'Unknown')}", - "reasoning": po.get("reasoning") or "Low stock levels detected", - "consequence": po.get("consequence") or "Order needed to maintain inventory levels", + "reasoning_data": reasoning_data, # NEW: Structured data for i18n "amount": po.get("total_amount", 0), "currency": po.get("currency", "EUR"), "actions": [ @@ -490,6 +499,16 @@ class DashboardService: status_icon = "⏰" status_text = "PENDING" + # Get reasoning_data or create default + reasoning_data = batch.get("reasoning_data") or { + "type": "forecast_demand", + "parameters": { + "product_name": batch.get("product_name", "Product"), + "predicted_demand": batch.get("planned_quantity", 0), + "confidence_score": 85 + } + } + timeline.append({ "id": batch["id"], "batchNumber": batch.get("batch_number"), @@ -505,7 +524,7 @@ class DashboardService: "progress": progress, "readyBy": planned_end.isoformat() if planned_end else None, "priority": batch.get("priority", "MEDIUM"), - "reasoning": batch.get("reasoning") or "Based on demand forecast" + "reasoning_data": reasoning_data # NEW: Structured data for i18n }) # Sort by planned start time diff --git a/services/procurement/app/models/purchase_order.py b/services/procurement/app/models/purchase_order.py index dcba2a31..58c5ecc7 100644 --- a/services/procurement/app/models/purchase_order.py +++ b/services/procurement/app/models/purchase_order.py @@ -119,17 +119,30 @@ class PurchaseOrder(Base): internal_notes = Column(Text, nullable=True) # Not shared with supplier terms_and_conditions = Column(Text, nullable=True) - # JTBD Dashboard: Reasoning and consequences for user transparency - # Deferred loading to prevent breaking queries when columns don't exist yet - reasoning = deferred(Column(Text, nullable=True)) # Why this PO was created (e.g., "Low flour stock (2 days left)") - consequence = deferred(Column(Text, nullable=True)) # What happens if not approved (e.g., "Stock out risk in 48 hours") - reasoning_data = deferred(Column(JSONB, nullable=True)) # Structured reasoning data - # reasoning_data structure: { - # "trigger": "low_stock" | "forecast_demand" | "manual", - # "ingredients_affected": [{"id": "uuid", "name": "Flour", "current_stock": 10, "days_remaining": 2}], - # "orders_impacted": [{"id": "uuid", "product": "Baguette", "quantity": 100}], - # "urgency_score": 0-100, - # "estimated_stock_out_date": "2025-11-10T00:00:00Z" + # JTBD Dashboard: Structured reasoning data for i18n support + # Backend stores structured data, frontend translates using i18n + reasoning_data = Column(JSONB, nullable=True) # Structured reasoning data for multilingual support + # reasoning_data structure (see shared/schemas/reasoning_types.py): + # { + # "type": "low_stock_detection" | "forecast_demand" | "safety_stock_replenishment" | etc., + # "parameters": { + # "supplier_name": "Harinas del Norte", + # "product_names": ["Flour Type 55", "Flour Type 45"], + # "days_until_stockout": 3, + # "current_stock": 45.5, + # "required_stock": 200 + # }, + # "consequence": { + # "type": "stockout_risk", + # "severity": "high", + # "impact_days": 3, + # "affected_products": ["Baguette", "Croissant"] + # }, + # "metadata": { + # "trigger_source": "orchestrator_auto", + # "forecast_confidence": 0.85, + # "ai_assisted": true + # } # } # Audit fields diff --git a/services/procurement/migrations/versions/001_unified_initial_schema.py b/services/procurement/migrations/versions/001_unified_initial_schema.py index 5a60e6ab..65f70465 100644 --- a/services/procurement/migrations/versions/001_unified_initial_schema.py +++ b/services/procurement/migrations/versions/001_unified_initial_schema.py @@ -6,7 +6,7 @@ Create Date: 2025-11-07 Complete procurement service schema including: - Procurement plans and requirements -- Purchase orders and items (with reasoning fields for JTBD dashboard) +- Purchase orders and items (with reasoning_data for i18n JTBD dashboard) - Deliveries and delivery items - Supplier invoices - Replenishment planning @@ -207,7 +207,7 @@ def upgrade() -> None: # PURCHASE ORDER TABLES # ======================================================================== - # Create purchase_orders table (with JTBD dashboard reasoning fields) + # Create purchase_orders table (with reasoning_data for i18n) op.create_table('purchase_orders', sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), sa.Column('tenant_id', postgresql.UUID(as_uuid=True), nullable=False), @@ -242,9 +242,7 @@ def upgrade() -> None: sa.Column('notes', sa.Text(), nullable=True), sa.Column('internal_notes', sa.Text(), nullable=True), sa.Column('terms_and_conditions', sa.Text(), nullable=True), - # JTBD Dashboard fields - sa.Column('reasoning', sa.Text(), nullable=True), - sa.Column('consequence', sa.Text(), nullable=True), + # JTBD Dashboard: Structured reasoning for i18n support sa.Column('reasoning_data', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False), sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), onupdate=sa.text('now()'), nullable=False), diff --git a/services/production/app/models/production.py b/services/production/app/models/production.py index 485ada4d..375864ff 100644 --- a/services/production/app/models/production.py +++ b/services/production/app/models/production.py @@ -133,16 +133,29 @@ class ProductionBatch(Base): delay_reason = Column(String(255), nullable=True) cancellation_reason = Column(String(255), nullable=True) - # JTBD Dashboard: Reasoning and context for user transparency - # Deferred loading to prevent breaking queries when columns don't exist yet - reasoning = deferred(Column(Text, nullable=True)) # Why this batch was scheduled (e.g., "Based on wedding order #1234") - reasoning_data = deferred(Column(JSON, nullable=True)) # Structured reasoning data - # reasoning_data structure: { - # "trigger": "forecast" | "order" | "inventory" | "manual", - # "forecast_id": "uuid", - # "orders_fulfilled": [{"id": "uuid", "customer": "Maria's Bakery", "quantity": 100}], - # "demand_score": 0-100, - # "scheduling_priority_reason": "High demand + VIP customer" + # JTBD Dashboard: Structured reasoning data for i18n support + # Backend stores structured data, frontend translates using i18n + reasoning_data = Column(JSON, nullable=True) # Structured reasoning data for multilingual support + # reasoning_data structure (see shared/schemas/reasoning_types.py): + # { + # "type": "forecast_demand" | "customer_order" | "stock_replenishment" | etc., + # "parameters": { + # "product_name": "Croissant", + # "predicted_demand": 500, + # "current_stock": 120, + # "production_needed": 380, + # "confidence_score": 87 + # }, + # "urgency": { + # "level": "normal", + # "ready_by_time": "08:00", + # "customer_commitment": false + # }, + # "metadata": { + # "trigger_source": "orchestrator_auto", + # "forecast_id": "uuid-here", + # "ai_assisted": true + # } # } # Timestamps @@ -191,7 +204,6 @@ class ProductionBatch(Base): "quality_notes": self.quality_notes, "delay_reason": self.delay_reason, "cancellation_reason": self.cancellation_reason, - "reasoning": self.reasoning, "reasoning_data": self.reasoning_data, "created_at": self.created_at.isoformat() if self.created_at else None, "updated_at": self.updated_at.isoformat() if self.updated_at else None, diff --git a/services/production/migrations/versions/001_unified_initial_schema.py b/services/production/migrations/versions/001_unified_initial_schema.py index a9ec5f17..641cfe96 100644 --- a/services/production/migrations/versions/001_unified_initial_schema.py +++ b/services/production/migrations/versions/001_unified_initial_schema.py @@ -5,7 +5,7 @@ Revises: Create Date: 2025-11-07 Complete production service schema including: -- Production batches (with reasoning fields for JTBD dashboard and waste tracking) +- Production batches (with reasoning_data for i18n JTBD dashboard and waste tracking) - Production schedules - Production capacity - Equipment @@ -90,7 +90,7 @@ def upgrade() -> None: ) op.create_index(op.f('ix_equipment_tenant_id'), 'equipment', ['tenant_id'], unique=False) - # Create production_batches table (with all fields including reasoning and waste tracking) + # Create production_batches table (with reasoning_data for i18n and waste tracking) op.create_table('production_batches', sa.Column('id', sa.UUID(), nullable=False), sa.Column('tenant_id', sa.UUID(), nullable=False), @@ -135,8 +135,7 @@ def upgrade() -> None: sa.Column('quality_notes', sa.Text(), nullable=True), sa.Column('delay_reason', sa.String(length=255), nullable=True), sa.Column('cancellation_reason', sa.String(length=255), nullable=True), - # JTBD Dashboard fields (from 20251107 migration) - sa.Column('reasoning', sa.Text(), nullable=True), + # JTBD Dashboard: Structured reasoning for i18n support sa.Column('reasoning_data', sa.JSON(), nullable=True), sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True), sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),