demeter

Autonomous Hydroponic Intelligence

backend/node_server/controllers/farmController.js (3625B)


  1const { randomUUID: uuidv4 } = require('crypto'); // ✅ Use built-in Node module
  2const { client, COLLECTION_NAME } = require('../config/db');
  3
  4// --- LOGIC: Create New Memory ---
  5const addMemory = async (req, res) => {
  6    try {
  7        const { timestamp, sensors, metadata, crop_id, sequence_number, action_taken, outcome } = req.body;
  8
  9        // Mock Vector (Replace with real embedding model in production)
 10        const vector = Array.from({ length: 4 }, () => Math.random());
 11
 12        const payload = {
 13            timestamp: timestamp || new Date().toISOString(),
 14            sensors,
 15            ...metadata,
 16            crop_id: crop_id || "UNKNOWN_CROP",
 17            sequence_number: sequence_number || 1,
 18            action_taken: action_taken || "PENDING_ACTION",
 19            outcome: outcome || "PENDING_OBSERVATION"
 20        };
 21
 22        const pointId = uuidv4();
 23
 24        await client.upsert(COLLECTION_NAME, {
 25            points: [{
 26                id: pointId,
 27                vector: vector,
 28                payload: payload
 29            }]
 30        });
 31
 32        res.json({ success: true, id: pointId, message: "Memory stored successfully" });
 33    } catch (e) {
 34        console.error(e);
 35        res.status(500).json({ error: e.message });
 36    }
 37};
 38
 39// --- LOGIC: Dashboard (Unique crops + Latest State) ---
 40const getDashboard = async (req, res) => {
 41    try {
 42        const latestCropsMap = new Map();
 43        let nextOffset = null;
 44        let keepFetching = true;
 45
 46        // Scroll through all data
 47        while (keepFetching) {
 48            const result = await client.scroll(COLLECTION_NAME, {
 49                limit: 100,
 50                with_payload: true,
 51                with_vector: false, // Save bandwidth
 52                offset: nextOffset,
 53            });
 54
 55            // "Group By" Logic in JavaScript
 56            for (const point of result.points) {
 57                const p = point.payload;
 58                const cropId = p.crop_id;
 59                const seq = p.sequence_number || 0;
 60
 61                // Check if we have this crop, or if this point is "newer" (higher sequence)
 62                if (!latestCropsMap.has(cropId)) {
 63                    latestCropsMap.set(cropId, point);
 64                } else {
 65                    const currentMax = latestCropsMap.get(cropId).payload.sequence_number || 0;
 66                    if (seq > currentMax) {
 67                        latestCropsMap.set(cropId, point);
 68                    }
 69                }
 70            }
 71
 72            nextOffset = result.next_page_offset;
 73            if (!nextOffset) keepFetching = false;
 74        }
 75
 76        const data = Array.from(latestCropsMap.values());
 77        res.json(data);
 78    } catch (e) {
 79        res.status(500).json({ error: e.message+"adfasdfads" });
 80    }
 81};
 82
 83// --- LOGIC: Get History for One Crop ---
 84const getCropHistory = async (req, res) => {
 85    try {
 86        const { cropId } = req.params;
 87        
 88        // This filter is fast because we added the Index in db.js
 89        const result = await client.scroll(COLLECTION_NAME, {
 90            filter: {
 91                must: [{ key: "crop_id", match: { value: cropId } }]
 92            },
 93            limit: 1000, 
 94            with_payload: true,
 95            with_vector: false
 96        });
 97
 98        // Sort Descending by Sequence Number
 99        const sorted = result.points.sort((a, b) => 
100            (b.payload.sequence_number || 0) - (a.payload.sequence_number || 0)
101        );
102
103        res.json(sorted);
104    } catch (e) {
105        res.status(500).json({ error: e.message });
106    }
107};
108
109
110
111module.exports = { addMemory, getDashboard, getCropHistory };