demeter
Autonomous Hydroponic Intelligencegit clone https://github.com/maydayv7/demeter
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 };