const { query } = require('../database/db'); class AiInsight { /** * Get AI insight by cycle and kandang * @param {string} cycleId - Cycle ID * @param {string|null} kandangId - Kandang ID (null for all kandangs) * @returns {Promise} Insight object or null if not found or expired */ static async getByCycleAndKandang(cycleId, kandangId = null) { try { const result = await query( `SELECT * FROM ai_insights WHERE cycle_id = $1 AND ($2::text IS NULL AND kandang_id IS NULL OR kandang_id = $2) AND expires_at > NOW() ORDER BY generated_at DESC LIMIT 1`, [cycleId, kandangId] ); if (result.rows.length === 0) return null; return this.formatInsight(result.rows[0]); } catch (error) { console.error('Error getting AI insight:', error); throw error; } } /** * Create or update AI insight * @param {Object} insightData - Insight data * @returns {Promise} Created/updated insight */ static async upsert(insightData) { try { const { cycleId, kandangId, insightText, version = 'v1' } = insightData; // Calculate expiry date (6 hours from now) const expiresAt = new Date(); expiresAt.setHours(expiresAt.getHours() + 6); // Check if insight already exists const existing = await this.getByCycleAndKandang(cycleId, kandangId); if (existing) { // Update existing const result = await query( `UPDATE ai_insights SET insight_text = $1, version = $2, generated_at = NOW(), expires_at = $3, updated_at = NOW() WHERE id = $4 RETURNING *`, [insightText, version, expiresAt, existing.id] ); return this.formatInsight(result.rows[0]); } else { // Create new const result = await query( `INSERT INTO ai_insights (cycle_id, kandang_id, insight_text, version, generated_at, expires_at) VALUES ($1, $2, $3, $4, NOW(), $5) RETURNING *`, [cycleId, kandangId, insightText, version, expiresAt] ); return this.formatInsight(result.rows[0]); } } catch (error) { console.error('Error upserting AI insight:', error); throw error; } } /** * Delete expired insights * @returns {Promise} Number of deleted rows */ static async cleanupExpired() { try { const result = await query( 'DELETE FROM ai_insights WHERE expires_at < NOW()', [] ); return result.rowCount; } catch (error) { console.error('Error cleaning up expired insights:', error); throw error; } } /** * Format database row to API response format * @param {Object} row - Database row * @returns {Object} Formatted insight */ static formatInsight(row) { return { id: row.id, cycleId: row.cycle_id, kandangId: row.kandang_id, insightText: row.insight_text, version: row.version, generatedAt: row.generated_at, expiresAt: row.expires_at, createdAt: row.created_at, updatedAt: row.updated_at, }; } } module.exports = AiInsight;