# AI Anomaly Detection — React Component Structure

## Directory Structure

```
frontend/src/
├── modules/
│   └── analytics/
│       └── ai/
│           ├── anomalyDetection/
│           │   ├── AnomalyDetectionDashboard.tsx      (Main container)
│           │   ├── components/
│           │   │   ├── AnomalyKPICards.tsx            (Quantified metrics)
│           │   │   ├── AnomalyTable.tsx               (Live anomalies list)
│           │   │   ├── AnomalyDetailPanel.tsx         (Detail view modal)
│           │   │   ├── SeverityMatrix.tsx             (Distribution chart)
│           │   │   ├── SensorHealthScorecard.tsx      (Trust engine)
│           │   │   ├── AnomalyHistoryTab.tsx          (30-day trends)
│           │   │   ├── AlertRulesConfig.tsx           (Thresholds editor)
│           │   │   ├── WorkOrderCreator.tsx           (Integration)
│           │   │   ├── AnomalyChart.tsx               (Time-series viz)
│           │   │   └── ExecutiveHealthScore.tsx       (Summary for mgmt)
│           │   ├── hooks/
│           │   │   ├── useAnomalyDetection.ts         (Real-time data fetch)
│           │   │   ├── useAnomalyDetail.ts            (Single anomaly)
│           │   │   └── useSensorTrust.ts              (Trust scoring)
│           │   ├── services/
│           │   │   ├── anomalyService.ts              (API calls)
│           │   │   ├── workOrderService.ts            (WO integration)
│           │   │   └── alertRuleService.ts            (Rules CRUD)
│           │   ├── types/
│           │   │   ├── anomaly.types.ts               (TS interfaces)
│           │   │   └── constants.ts                   (Severity/confidence)
│           │   └── styles/
│           │       └── anomalyDetection.module.css
│           │
│           ├── leakBurstClassifier/                   (Similar structure)
│           ├── pumpHealthModel/
│           ├── qualityRiskModel/
│           ├── demandForecasting/
│           ├── rootCauseAssistant/
│           ├── nrwAttribution/
│           └── energyOptimisation/
│
└── shared/
    ├── components/
    │   └── charts/
    │       └── AnomalyTimeSeriesChart.tsx
    └── utils/
        └── severityHelpers.ts
```

---

## Component Interfaces & Types

```typescript
// types/anomaly.types.ts

export interface Anomaly {
  id: string;
  assetId: string;
  assetName: string;
  assetType: 'flow_meter' | 'pressure_sensor' | 'chlorine' | 'ph' | 'turbidity' | 'level';
  location: {
    gps: { latitude: number; longitude: number };
    zone: string;
    dma: string;
  };
  detectedAt: Date;
  severity: number; // 0-10
  confidence: number; // 0-100 (%)
  measurement: {
    current: number;
    baseline: number;
    deviation: number; // percentage
    unit: string;
  };
  probableCauses: Array<{
    cause: string;
    likelihood: number; // percentage
    description: string;
  }>;
  status: 'active' | 'resolved' | 'pending_action' | 'monitoring';
  workOrderId?: string;
  auditTrail: AuditEntry[];
}

export interface AuditEntry {
  timestamp: Date;
  action: string;
  actor: {
    userId: string;
    name: string;
    role: string;
  };
  details: string;
}

export interface SensorHealth {
  sensorId: string;
  sensorName: string;
  trustScore: number; // 0-100
  status: 'healthy' | 'drift' | 'noisy' | 'failed';
  lastCalibration?: Date;
  anomalyCount7d: number;
  falsePositiveRate: number; // percentage
}

export interface AnomalyMetrics {
  activeCount: number;
  avgConfidence: number;
  mttrMinutes: number;
  falsePosRate: number;
  resolvedToday: number;
  criticalCount: number;
  highCount: number;
  mediumCount: number;
  lowCount: number;
}

export interface AlertRule {
  id: string;
  sensorType: string;
  threshold: {
    upper?: number;
    lower?: number;
    percentageDeviation?: number;
  };
  severity: 'low' | 'medium' | 'high' | 'critical';
  enabled: boolean;
  createdAt: Date;
  updatedBy: string;
}
```

---

## Key Components Implementation Outline

### **1. AnomalyDetectionDashboard.tsx** (Main Container)

```typescript
export const AnomalyDetectionDashboard: React.FC = () => {
  const [activeTab, setActiveTab] = useState<'live' | 'history' | 'config'>('live');
  const { anomalies, metrics, loading } = useAnomalyDetection();
  const [selectedAnomaly, setSelectedAnomaly] = useState<Anomaly | null>(null);

  return (
    <div className="anomaly-dashboard">
      <Header />
      <AnomalyKPICards metrics={metrics} />
      
      <Tabs activeTab={activeTab} onChange={setActiveTab}>
        <TabPane label="Live Anomalies">
          <div className="live-container">
            <AnomalyTable 
              data={anomalies}
              onSelect={setSelectedAnomaly}
            />
            <SeverityMatrix data={anomalies} />
          </div>
        </TabPane>
        
        <TabPane label="History & Trends">
          <AnomalyHistoryTab />
        </TabPane>
        
        <TabPane label="Configuration">
          <AlertRulesConfig />
          <SensorHealthScorecard />
        </TabPane>
      </Tabs>

      {selectedAnomaly && (
        <AnomalyDetailPanel
          anomaly={selectedAnomaly}
          onClose={() => setSelectedAnomaly(null)}
        />
      )}
    </div>
  );
};
```

---

### **2. AnomalyKPICards.tsx** (Quantified Metrics)

```typescript
interface Props {
  metrics: AnomalyMetrics;
}

export const AnomalyKPICards: React.FC<Props> = ({ metrics }) => {
  return (
    <div className="kpi-row">
      <KPICard
        label="ACTIVE ANOMALIES"
        value={metrics.activeCount}
        trend={+3}
        trendType="up"
        color="danger"
      />
      <KPICard
        label="AVG CONFIDENCE"
        value={`${metrics.avgConfidence.toFixed(1)}%`}
        trend={+2.5}
        trendType="up"
        color="success"
        subtext="Excellent"
      />
      <KPICard
        label="MTTR (Minutes)"
        value={metrics.mttrMinutes.toFixed(1)}
        trend={-25}
        trendType="down"
        color="info"
        subtext="↓ 25% vs month"
      />
      <KPICard
        label="FALSE POS RATE"
        value={`${metrics.falsePosRate.toFixed(1)}%`}
        trend={-0.7}
        trendType="down"
        color="success"
        subtext="↓ from 3.5%"
      />
    </div>
  );
};
```

---

### **3. AnomalyTable.tsx** (Live List)

```typescript
interface Props {
  data: Anomaly[];
  onSelect: (anomaly: Anomaly) => void;
}

export const AnomalyTable: React.FC<Props> = ({ data, onSelect }) => {
  const [sortBy, setSortBy] = useState<'severity' | 'confidence' | 'time'>('severity');

  const columns = [
    {
      key: 'asset',
      label: 'ASSET',
      render: (row: Anomaly) => (
        <div>
          <div className="font-bold">{row.assetName}</div>
          <div className="text-sm text-gray-500">{row.location.dma}</div>
        </div>
      ),
    },
    {
      key: 'severity',
      label: 'SEVERITY',
      render: (row: Anomaly) => (
        <SeverityBadge score={row.severity} />
      ),
    },
    {
      key: 'confidence',
      label: 'CONFIDENCE',
      render: (row: Anomaly) => (
        <ConfidenceBadge score={row.confidence} />
      ),
    },
    {
      key: 'type',
      label: 'TYPE',
      render: (row: Anomaly) => (
        <TrendIndicator value={row.measurement.deviation} />
      ),
    },
    {
      key: 'value',
      label: 'VALUE',
      render: (row: Anomaly) => (
        <span>
          {row.measurement.current} {row.measurement.unit}
          <span className="text-gray-500 text-sm ml-2">
            (vs {row.measurement.baseline})
          </span>
        </span>
      ),
    },
    {
      key: 'action',
      label: 'ACTION',
      render: (row: Anomaly) => (
        <ActionButtons anomaly={row} onDetail={onSelect} />
      ),
    },
  ];

  return (
    <Table
      columns={columns}
      data={data.sort((a, b) => {
        switch (sortBy) {
          case 'severity':
            return b.severity - a.severity;
          case 'confidence':
            return b.confidence - a.confidence;
          case 'time':
            return new Date(b.detectedAt).getTime() - new Date(a.detectedAt).getTime();
        }
      })}
      onRowClick={(row) => onSelect(row)}
      className="anomaly-table"
    />
  );
};
```

---

### **4. AnomalyDetailPanel.tsx** (Modal Detail View)

```typescript
interface Props {
  anomaly: Anomaly;
  onClose: () => void;
}

export const AnomalyDetailPanel: React.FC<Props> = ({ anomaly, onClose }) => {
  const [showWorkOrderModal, setShowWorkOrderModal] = useState(false);

  return (
    <Modal isOpen title={`Anomaly Detail — ${anomaly.assetName}`} onClose={onClose}>
      <div className="detail-panel">
        {/* Asset Info */}
        <SectionCard title="Asset Information">
          <DetailRow label="Location" value={`GPS: ${anomaly.location.gps.latitude}° N, ${anomaly.location.gps.longitude}° E`} />
          <DetailRow label="Detected" value={formatTime(anomaly.detectedAt)} />
        </SectionCard>

        {/* Severity & Confidence */}
        <SectionCard title="Severity Assessment">
          <SeverityGauge score={anomaly.severity} confidence={anomaly.confidence} />
        </SectionCard>

        {/* Measurement Chart */}
        <SectionCard title="Measurement Chart">
          <AnomalyChart anomaly={anomaly} />
        </SectionCard>

        {/* Probable Causes */}
        <SectionCard title="Probable Causes (Ranked by Likelihood)">
          {anomaly.probableCauses.map((cause, idx) => (
            <CauseRow key={idx} cause={cause} />
          ))}
        </SectionCard>

        {/* Audit Trail */}
        <SectionCard title="Audit Trail">
          <AuditTrail entries={anomaly.auditTrail} />
        </SectionCard>

        {/* Actions */}
        <div className="actions">
          <Button 
            variant="primary" 
            onClick={() => setShowWorkOrderModal(true)}
            icon="truck"
          >
            Create Work Order
          </Button>
          <Button variant="secondary">Monitor</Button>
          <Button variant="ghost">Dismiss</Button>
        </div>
      </div>

      {showWorkOrderModal && (
        <WorkOrderCreator anomaly={anomaly} onClose={() => setShowWorkOrderModal(false)} />
      )}
    </Modal>
  );
};
```

---

### **5. Hooks: useAnomalyDetection.ts**

```typescript
export const useAnomalyDetection = () => {
  const [anomalies, setAnomalies] = useState<Anomaly[]>([]);
  const [metrics, setMetrics] = useState<AnomalyMetrics | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // WebSocket connection for real-time updates
    const ws = new WebSocket(`${API_BASE_URL}/ws/anomalies`);

    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      setAnomalies(data.anomalies);
      setMetrics(data.metrics);
    };

    ws.onopen = () => {
      anomalyService.getActiveAnomalies().then((data) => {
        setAnomalies(data);
        calculateMetrics(data);
        setLoading(false);
      });
    };

    return () => ws.close();
  }, []);

  return { anomalies, metrics, loading };
};

const calculateMetrics = (anomalies: Anomaly[]): AnomalyMetrics => {
  const active = anomalies.filter(a => a.status === 'active');
  return {
    activeCount: active.length,
    avgConfidence: active.reduce((sum, a) => sum + a.confidence, 0) / active.length,
    mttrMinutes: calculateMTTR(anomalies),
    falsePosRate: calculateFalsePositives(anomalies),
    resolvedToday: anomalies.filter(a => a.status === 'resolved').length,
    criticalCount: active.filter(a => a.severity >= 9.0).length,
    highCount: active.filter(a => a.severity >= 7.0 && a.severity < 9.0).length,
    mediumCount: active.filter(a => a.severity >= 5.0 && a.severity < 7.0).length,
    lowCount: active.filter(a => a.severity < 5.0).length,
  };
};
```

---

### **6. Services: anomalyService.ts**

```typescript
export class AnomalyService {
  private api = new ApiClient();

  // Fetch active anomalies (real-time)
  async getActiveAnomalies(): Promise<Anomaly[]> {
    return this.api.get('/api/ai/anomalies/active');
  }

  // Fetch single anomaly detail
  async getAnomalyDetail(id: string): Promise<Anomaly> {
    return this.api.get(`/api/ai/anomalies/${id}`);
  }

  // Get historical anomalies (30-day)
  async getAnomalyHistory(days: number = 30): Promise<Anomaly[]> {
    return this.api.get(`/api/ai/anomalies/history?days=${days}`);
  }

  // Create/update alert rule
  async saveAlertRule(rule: AlertRule): Promise<AlertRule> {
    return rule.id
      ? this.api.put(`/api/ai/alert-rules/${rule.id}`, rule)
      : this.api.post('/api/ai/alert-rules', rule);
  }

  // Get sensor health scores
  async getSensorHealth(): Promise<SensorHealth[]> {
    return this.api.get('/api/ai/sensors/health');
  }

  // Dismiss anomaly with reason
  async dismissAnomaly(anomalyId: string, reason: string): Promise<void> {
    return this.api.post(`/api/ai/anomalies/${anomalyId}/dismiss`, { reason });
  }

  // Export anomaly report
  async exportReport(format: 'csv' | 'pdf', dateRange: { start: Date; end: Date }): Promise<Blob> {
    return this.api.get('/api/ai/anomalies/export', {
      format,
      startDate: dateRange.start,
      endDate: dateRange.end,
    });
  }
}
```

---

## API Endpoints Required

```
GET    /api/ai/anomalies/active              → Active anomalies (real-time)
GET    /api/ai/anomalies/{id}                → Single anomaly detail
GET    /api/ai/anomalies/history             → 30-day history
POST   /api/ai/anomalies/{id}/dismiss        → Dismiss with reason
GET    /api/ai/alert-rules                   → List all rules
POST   /api/ai/alert-rules                   → Create rule
PUT    /api/ai/alert-rules/{id}              → Update rule
DELETE /api/ai/alert-rules/{id}              → Delete rule
GET    /api/ai/sensors/health                → Sensor trust scores
GET    /api/ai/anomalies/metrics             → KPI summary
GET    /api/ai/anomalies/export              → Download report
POST   /api/work-orders                      → Create WO from anomaly
WS     /ws/anomalies                         → Real-time WebSocket
```

---

## Database Schema (Backend)

```sql
CREATE TABLE anomalies (
  id UUID PRIMARY KEY,
  asset_id UUID NOT NULL,
  asset_name VARCHAR(255),
  asset_type VARCHAR(50),
  location_gps POINT,
  location_zone VARCHAR(100),
  location_dma VARCHAR(100),
  detected_at TIMESTAMP,
  severity DECIMAL(3,1),
  confidence DECIMAL(5,2),
  measurement_current DECIMAL(10,3),
  measurement_baseline DECIMAL(10,3),
  measurement_deviation DECIMAL(5,2),
  measurement_unit VARCHAR(20),
  probable_causes JSONB,
  status VARCHAR(50),
  work_order_id UUID,
  created_at TIMESTAMP,
  updated_at TIMESTAMP,
  FOREIGN KEY (work_order_id) REFERENCES work_orders(id)
);

CREATE TABLE audit_trail (
  id UUID PRIMARY KEY,
  anomaly_id UUID NOT NULL,
  action VARCHAR(255),
  actor_user_id UUID,
  actor_name VARCHAR(255),
  actor_role VARCHAR(50),
  details TEXT,
  created_at TIMESTAMP,
  FOREIGN KEY (anomaly_id) REFERENCES anomalies(id) ON DELETE CASCADE
);

CREATE TABLE alert_rules (
  id UUID PRIMARY KEY,
  sensor_type VARCHAR(100),
  threshold_upper DECIMAL(10,3),
  threshold_lower DECIMAL(10,3),
  percentage_deviation DECIMAL(5,2),
  severity VARCHAR(50),
  enabled BOOLEAN DEFAULT TRUE,
  created_at TIMESTAMP,
  updated_by UUID,
  FOREIGN KEY (updated_by) REFERENCES users(id)
);

CREATE TABLE sensor_health (
  id UUID PRIMARY KEY,
  sensor_id UUID,
  sensor_name VARCHAR(255),
  trust_score DECIMAL(5,2),
  status VARCHAR(50),
  last_calibration TIMESTAMP,
  anomaly_count_7d INT,
  false_positive_rate DECIMAL(5,2),
  updated_at TIMESTAMP
);
```

---

## Testing Strategy

```
Unit Tests:
✓ calculateMetrics() accuracy
✓ Severity/confidence badge colors
✓ Trend indicator logic
✓ MTTR calculation

Integration Tests:
✓ WebSocket connection lifecycle
✓ Real-time anomaly push updates
✓ Work order creation from anomaly
✓ Alert rule save/update flow

E2E Tests:
✓ View live anomalies → detail → create WO
✓ Filter by severity/confidence
✓ Export report flow
✓ Dismiss anomaly with audit trail
```

