Переглянути джерело

feat: implement PalmOilService for image analysis, archiving, and historical record management

Dr-Swopt 2 тижнів тому
батько
коміт
a40df7e1f2
2 змінених файлів з 66 додано та 0 видалено
  1. 48 0
      src/palm-oil/palm-oil.service.ts
  2. 18 0
      src/palm-oil/vision.gateway.ts

+ 48 - 0
src/palm-oil/palm-oil.service.ts

@@ -113,6 +113,54 @@ export class PalmOilService {
     });
   }
 
+  async getBatchDetails(batchId: string): Promise<{
+    batchId: string;
+    frameCount: number;
+    totalDetections: number;
+    avgInferenceMs: number;
+    avgProcessingMs: number;
+    batchStart: Date | null;
+    batchEnd: Date | null;
+    classTally: Record<string, number>;
+    frames: History[];
+  }> {
+    const frames = await this.historyRepository.find({
+      where: { batch_id: batchId },
+      order: { created_at: 'ASC' },
+    });
+
+    if (!frames.length) {
+      return { batchId, frameCount: 0, totalDetections: 0, avgInferenceMs: 0, avgProcessingMs: 0, batchStart: null, batchEnd: null, classTally: {}, frames: [] };
+    }
+
+    const frameCount = frames.length;
+    const totalDetections = frames.reduce((s, f) => s + (f.total_count ?? 0), 0);
+    const avgInferenceMs = parseFloat((frames.reduce((s, f) => s + (f.inference_ms ?? 0), 0) / frameCount).toFixed(2));
+    const avgProcessingMs = parseFloat((frames.reduce((s, f) => s + (f.processing_ms ?? 0), 0) / frameCount).toFixed(2));
+
+    const classTally: Record<string, number> = {};
+    for (const frame of frames) {
+      const summary = frame.industrial_summary as Record<string, number>;
+      if (summary && typeof summary === 'object') {
+        for (const [cls, count] of Object.entries(summary)) {
+          classTally[cls] = (classTally[cls] ?? 0) + (count ?? 0);
+        }
+      }
+    }
+
+    return {
+      batchId,
+      frameCount,
+      totalDetections,
+      avgInferenceMs,
+      avgProcessingMs,
+      batchStart: frames[0].created_at,
+      batchEnd: frames[frameCount - 1].created_at,
+      classTally,
+      frames,
+    };
+  }
+
   async getRecordByArchiveId(archiveId: string): Promise<History | null> {
     return this.historyRepository.findOne({ where: { archive_id: archiveId } });
   }

+ 18 - 0
src/palm-oil/vision.gateway.ts

@@ -77,6 +77,10 @@ interface HistoryDeletePayload {
   archiveId: string;
 }
 
+interface BatchDetailsPayload {
+  batchId: string;
+}
+
 interface EdgeResultPayload {
   frame: string;
   filename?: string;
@@ -251,6 +255,20 @@ export class VisionGateway
           break;
         }
 
+        // ── History:getBatchDetails ────────────────────────────────────────
+        case 'History:getBatchDetails': {
+          const { batchId } = (payload ?? {}) as BatchDetailsPayload;
+
+          if (!batchId) {
+            replyError('No batchId provided');
+            return;
+          }
+
+          const result = await this.palmOilService.getBatchDetails(batchId);
+          reply(result);
+          break;
+        }
+
         // ── Chat:send ──────────────────────────────────────────────────────
         case 'Chat:send': {
           const { message } = (payload ?? {}) as ChatPayload;