Coverage for app \ storage \ feedback.py: 20%

65 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-11-30 09:36 +0100

1""" 

2Feedback logger for storing positive user feedback 

3 

4Logs successful interactions to be used for: 

5- RAG context enrichment (learning from good examples) 

6- Future fine-tuning or distillation 

7- Automata pattern discovery (Plasticity V2) 

8""" 

9import logging 

10import sqlite3 

11from datetime import datetime 

12from typing import Optional, List, Dict, Any 

13from pathlib import Path 

14 

15from app.config import settings 

16 

17logger = logging.getLogger(__name__) 

18 

19 

20class FeedbackLogger: 

21 """Logs successful user-approved interactions""" 

22 

23 def __init__(self, db_path: Optional[Path] = None): 

24 """ 

25 Initialize feedback logger 

26 Args: 

27 db_path: Path to SQLite database 

28 """ 

29 self.db_path = db_path or settings.db_path 

30 self._ensure_db() 

31 

32 def _ensure_db(self): 

33 """Ensure the feedback table exists in the database""" 

34 try: 

35 self.db_path.parent.mkdir(parents=True, exist_ok=True) 

36 conn = sqlite3.connect(self.db_path) 

37 cursor = conn.cursor() 

38 

39 cursor.execute(""" 

40 CREATE TABLE IF NOT EXISTS feedback_log ( 

41 id INTEGER PRIMARY KEY AUTOINCREMENT, 

42 timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, 

43 task TEXT NOT NULL, 

44 language TEXT NOT NULL, 

45 request_code TEXT NOT NULL, 

46 response_code TEXT, 

47 response_explanation TEXT, 

48 source TEXT DEFAULT 'user_cli', 

49 processed_for_rag BOOLEAN DEFAULT 0, 

50 UNIQUE(task, request_code, response_code) 

51 ) 

52 """) 

53 

54 conn.commit() 

55 conn.close() 

56 logger.info("Feedback log table initialized successfully.") 

57 except Exception as e: 

58 logger.error(f"Failed to initialize feedback_log table: {e}") 

59 raise 

60 

61 def log_feedback( 

62 self, 

63 task: str, 

64 language: str, 

65 request_code: str, 

66 response_code: Optional[str], 

67 response_explanation: Optional[str], 

68 source: str = "user_cli" 

69 ) -> bool: 

70 """ 

71 Log a positive feedback entry. 

72 

73 Returns: 

74 bool: True if a new entry was created, False otherwise (e.g., duplicate). 

75 """ 

76 try: 

77 conn = sqlite3.connect(self.db_path) 

78 cursor = conn.cursor() 

79 

80 cursor.execute(""" 

81 INSERT INTO feedback_log ( 

82 task, language, request_code, response_code, response_explanation, source 

83 ) VALUES (?, ?, ?, ?, ?, ?) 

84 ON CONFLICT(task, request_code, response_code) DO NOTHING 

85 """, ( 

86 task, 

87 language, 

88 request_code, 

89 response_code, 

90 response_explanation, 

91 source 

92 )) 

93 

94 conn.commit() 

95 changes = conn.total_changes 

96 conn.close() 

97 

98 if changes > 0: 

99 logger.info("Logged new feedback entry.") 

100 return True 

101 else: 

102 logger.warning("Attempted to log duplicate feedback entry. Ignored.") 

103 return False 

104 

105 except Exception as e: 

106 logger.error(f"Failed to log feedback: {e}") 

107 return False 

108 

109 def get_unprocessed_feedback(self) -> List[Dict[str, Any]]: 

110 """Retrieve all feedback entries that have not been processed for RAG yet.""" 

111 try: 

112 conn = sqlite3.connect(self.db_path) 

113 conn.row_factory = sqlite3.Row 

114 cursor = conn.cursor() 

115 

116 cursor.execute(""" 

117 SELECT * FROM feedback_log WHERE processed_for_rag = 0 

118 """) 

119 

120 unprocessed = [dict(row) for row in cursor.fetchall()] 

121 conn.close() 

122 return unprocessed 

123 

124 except Exception as e: 

125 logger.error(f"Failed to get unprocessed feedback: {e}") 

126 return [] 

127 

128 def mark_feedback_as_processed(self, ids: List[int]): 

129 """Mark feedback entries as processed for RAG by their IDs.""" 

130 if not ids: 

131 return 

132 

133 try: 

134 conn = sqlite3.connect(self.db_path) 

135 cursor = conn.cursor() 

136 

137 # Using parameter substitution to prevent SQL injection 

138 placeholders = ', '.join('?' for _ in ids) 

139 query = f"UPDATE feedback_log SET processed_for_rag = 1 WHERE id IN ({placeholders})" 

140 

141 cursor.execute(query, ids) 

142 conn.commit() 

143 conn.close() 

144 

145 logger.info(f"Marked {len(ids)} feedback entries as processed for RAG.") 

146 

147 except Exception as e: 

148 logger.error(f"Failed to mark feedback as processed: {e}")