Coverage for src/alprina_cli/services/insights_service.py: 11%
104 statements
« prev ^ index » next coverage.py v7.11.3, created at 2025-11-14 11:27 +0100
« prev ^ index » next coverage.py v7.11.3, created at 2025-11-14 11:27 +0100
1"""
2Insights Service - Provides security analytics and trend analysis
3"""
4from typing import Dict, Any, Optional, List
5from datetime import datetime, timedelta
6from ..api.services.neon_service import NeonService
9class InsightsService:
10 """Service for generating security insights and recommendations"""
12 def __init__(self):
13 self.db = NeonService()
15 def get_weekly_summary(self, user_id: str) -> Dict[str, Any]:
16 """
17 Get weekly security summary for a user
19 Returns:
20 Dictionary with weekly stats including new findings, scan count, etc.
21 """
22 try:
23 # Get critical findings this week
24 critical_query = """
25 SELECT COUNT(*) as count
26 FROM alerts
27 WHERE user_id = %s
28 AND severity = 'critical'
29 AND created_at >= NOW() - INTERVAL '7 days'
30 """
31 critical_result = self.db.execute_query(critical_query, (user_id,))
32 critical_count = critical_result[0]['count'] if critical_result else 0
34 # Get high findings this week
35 high_query = """
36 SELECT COUNT(*) as count
37 FROM alerts
38 WHERE user_id = %s
39 AND severity = 'high'
40 AND created_at >= NOW() - INTERVAL '7 days'
41 """
42 high_result = self.db.execute_query(high_query, (user_id,))
43 high_count = high_result[0]['count'] if high_result else 0
45 # Get total scans this week
46 scans_query = """
47 SELECT COUNT(*) as count
48 FROM scans
49 WHERE user_id = %s
50 AND created_at >= NOW() - INTERVAL '7 days'
51 """
52 scans_result = self.db.execute_query(scans_query, (user_id,))
53 scans_count = scans_result[0]['count'] if scans_result else 0
55 # Get total vulnerabilities found this week
56 vulns_query = """
57 SELECT COALESCE(SUM(
58 COALESCE(critical_count, 0) +
59 COALESCE(high_count, 0) +
60 COALESCE(medium_count, 0) +
61 COALESCE(low_count, 0)
62 ), 0) as total_vulnerabilities
63 FROM scans
64 WHERE user_id = %s
65 AND created_at >= NOW() - INTERVAL '7 days'
66 """
67 vulns_result = self.db.execute_query(vulns_query, (user_id,))
68 total_vulns = int(vulns_result[0]['total_vulnerabilities']) if vulns_result else 0
70 return {
71 'critical_findings': critical_count,
72 'high_findings': high_count,
73 'total_scans': scans_count,
74 'total_vulnerabilities': total_vulns,
75 'period': 'last_7_days'
76 }
78 except Exception as e:
79 print(f"❌ Error getting weekly summary: {e}")
80 return {
81 'critical_findings': 0,
82 'high_findings': 0,
83 'total_scans': 0,
84 'total_vulnerabilities': 0,
85 'period': 'last_7_days'
86 }
88 def get_most_scanned_target(self, user_id: str, days: int = 30) -> Optional[Dict[str, Any]]:
89 """
90 Get the most frequently scanned target
92 Args:
93 user_id: User ID
94 days: Number of days to look back (default: 30)
96 Returns:
97 Dictionary with target and scan count, or None
98 """
99 try:
100 query = """
101 SELECT target, COUNT(*) as scan_count
102 FROM scans
103 WHERE user_id = %s
104 AND created_at >= NOW() - INTERVAL '%s days'
105 AND target IS NOT NULL
106 AND target != ''
107 GROUP BY target
108 ORDER BY scan_count DESC
109 LIMIT 1
110 """
111 result = self.db.execute_query(query, (user_id, days))
113 if result and len(result) > 0:
114 return {
115 'target': result[0]['target'],
116 'scan_count': result[0]['scan_count']
117 }
119 return None
121 except Exception as e:
122 print(f"❌ Error getting most scanned target: {e}")
123 return None
125 def get_security_trend(self, user_id: str) -> Dict[str, Any]:
126 """
127 Calculate security trend (improving or declining)
128 Compares current week vs previous week
130 Returns:
131 Dictionary with trend direction and percentage change
132 """
133 try:
134 # Current week (last 7 days)
135 current_query = """
136 SELECT
137 COALESCE(SUM(COALESCE(critical_count, 0)), 0) as critical,
138 COALESCE(SUM(COALESCE(high_count, 0)), 0) as high,
139 COALESCE(SUM(COALESCE(medium_count, 0)), 0) as medium
140 FROM scans
141 WHERE user_id = %s
142 AND created_at >= NOW() - INTERVAL '7 days'
143 """
144 current_result = self.db.execute_query(current_query, (user_id,))
146 # Previous week (7-14 days ago)
147 previous_query = """
148 SELECT
149 COALESCE(SUM(COALESCE(critical_count, 0)), 0) as critical,
150 COALESCE(SUM(COALESCE(high_count, 0)), 0) as high,
151 COALESCE(SUM(COALESCE(medium_count, 0)), 0) as medium
152 FROM scans
153 WHERE user_id = %s
154 AND created_at >= NOW() - INTERVAL '14 days'
155 AND created_at < NOW() - INTERVAL '7 days'
156 """
157 previous_result = self.db.execute_query(previous_query, (user_id,))
159 if not current_result or not previous_result:
160 return {
161 'trend': 'stable',
162 'direction': 'none',
163 'change_percentage': 0,
164 'message': 'Not enough data for trend analysis'
165 }
167 # Calculate weighted score (critical=3, high=2, medium=1)
168 current_score = (
169 int(current_result[0]['critical']) * 3 +
170 int(current_result[0]['high']) * 2 +
171 int(current_result[0]['medium']) * 1
172 )
174 previous_score = (
175 int(previous_result[0]['critical']) * 3 +
176 int(previous_result[0]['high']) * 2 +
177 int(previous_result[0]['medium']) * 1
178 )
180 # Calculate change
181 if previous_score == 0:
182 if current_score == 0:
183 return {
184 'trend': 'stable',
185 'direction': 'none',
186 'change_percentage': 0,
187 'message': 'No security issues detected'
188 }
189 else:
190 return {
191 'trend': 'declining',
192 'direction': 'down',
193 'change_percentage': 100,
194 'message': 'New security issues detected this week'
195 }
197 change_percentage = ((current_score - previous_score) / previous_score) * 100
199 # Determine trend (negative change = improving, positive = declining)
200 if change_percentage < -10:
201 trend = 'improving'
202 direction = 'up'
203 message = f"Security improved by {abs(int(change_percentage))}% this week"
204 elif change_percentage > 10:
205 trend = 'declining'
206 direction = 'down'
207 message = f"Security declined by {int(change_percentage)}% this week"
208 else:
209 trend = 'stable'
210 direction = 'none'
211 message = "Security posture is stable"
213 return {
214 'trend': trend,
215 'direction': direction,
216 'change_percentage': abs(int(change_percentage)),
217 'message': message,
218 'current_score': current_score,
219 'previous_score': previous_score
220 }
222 except Exception as e:
223 print(f"❌ Error calculating security trend: {e}")
224 return {
225 'trend': 'stable',
226 'direction': 'none',
227 'change_percentage': 0,
228 'message': 'Unable to calculate trend'
229 }
231 def get_recommendations(self, user_id: str) -> List[Dict[str, Any]]:
232 """
233 Generate smart security recommendations based on scan history
235 Returns:
236 List of recommendation dictionaries
237 """
238 try:
239 recommendations = []
241 # Check for critical findings
242 critical_query = """
243 SELECT COUNT(*) as count
244 FROM alerts
245 WHERE user_id = %s
246 AND severity = 'critical'
247 AND is_read = false
248 """
249 critical_result = self.db.execute_query(critical_query, (user_id,))
250 critical_unread = critical_result[0]['count'] if critical_result else 0
252 if critical_unread > 0:
253 recommendations.append({
254 'priority': 'critical',
255 'title': 'Urgent: Address Critical Security Issues',
256 'description': f'You have {critical_unread} unread critical security finding{"s" if critical_unread != 1 else ""}. These require immediate attention.',
257 'action': 'View Critical Alerts',
258 'action_url': '/dashboard/alerts?severity=critical'
259 })
261 # Check scan frequency
262 recent_scans_query = """
263 SELECT COUNT(*) as count
264 FROM scans
265 WHERE user_id = %s
266 AND created_at >= NOW() - INTERVAL '7 days'
267 """
268 recent_result = self.db.execute_query(recent_scans_query, (user_id,))
269 recent_scans = recent_result[0]['count'] if recent_result else 0
271 if recent_scans == 0:
272 recommendations.append({
273 'priority': 'medium',
274 'title': 'No Recent Security Scans',
275 'description': 'You haven\'t run any security scans this week. Regular scanning helps identify vulnerabilities early.',
276 'action': 'Run a Scan Now',
277 'action_url': '/dashboard'
278 })
279 elif recent_scans < 3:
280 recommendations.append({
281 'priority': 'low',
282 'title': 'Increase Scan Frequency',
283 'description': 'Consider running security scans more frequently to catch issues early. Aim for at least weekly scans.',
284 'action': 'Schedule Regular Scans',
285 'action_url': '/dashboard/settings'
286 })
288 # Check for most common vulnerability types
289 vuln_types_query = """
290 SELECT
291 COALESCE(SUM(COALESCE(critical_count, 0)), 0) as critical,
292 COALESCE(SUM(COALESCE(high_count, 0)), 0) as high,
293 COALESCE(SUM(COALESCE(medium_count, 0)), 0) as medium
294 FROM scans
295 WHERE user_id = %s
296 AND created_at >= NOW() - INTERVAL '30 days'
297 """
298 vuln_result = self.db.execute_query(vuln_types_query, (user_id,))
300 if vuln_result:
301 total_vulns = (
302 int(vuln_result[0]['critical']) +
303 int(vuln_result[0]['high']) +
304 int(vuln_result[0]['medium'])
305 )
307 if total_vulns > 20:
308 recommendations.append({
309 'priority': 'high',
310 'title': 'High Volume of Vulnerabilities',
311 'description': f'Detected {total_vulns} vulnerabilities in the last 30 days. Consider implementing automated security controls.',
312 'action': 'View Scan History',
313 'action_url': '/dashboard/scans'
314 })
316 # If no recommendations, add a positive one
317 if len(recommendations) == 0:
318 recommendations.append({
319 'priority': 'info',
320 'title': 'Security Looking Good!',
321 'description': 'Your security posture is strong. Keep up the regular scanning and monitoring.',
322 'action': 'View Analytics',
323 'action_url': '/dashboard/analytics'
324 })
326 return recommendations
328 except Exception as e:
329 print(f"❌ Error generating recommendations: {e}")
330 return [{
331 'priority': 'info',
332 'title': 'Welcome to Alprina',
333 'description': 'Start by running your first security scan to get personalized recommendations.',
334 'action': 'Get Started',
335 'action_url': '/dashboard'
336 }]
338 def get_top_vulnerable_targets(self, user_id: str, limit: int = 5) -> List[Dict[str, Any]]:
339 """
340 Get targets with the most vulnerabilities
342 Args:
343 user_id: User ID
344 limit: Number of targets to return
346 Returns:
347 List of targets with vulnerability counts
348 """
349 try:
350 query = """
351 SELECT
352 target,
353 COALESCE(SUM(COALESCE(critical_count, 0)), 0) as critical,
354 COALESCE(SUM(COALESCE(high_count, 0)), 0) as high,
355 COALESCE(SUM(COALESCE(medium_count, 0)), 0) as medium,
356 COALESCE(SUM(COALESCE(low_count, 0)), 0) as low,
357 COUNT(*) as scan_count,
358 MAX(created_at) as last_scan
359 FROM scans
360 WHERE user_id = %s
361 AND created_at >= NOW() - INTERVAL '30 days'
362 AND target IS NOT NULL
363 AND target != ''
364 GROUP BY target
365 ORDER BY (
366 COALESCE(SUM(COALESCE(critical_count, 0)), 0) * 3 +
367 COALESCE(SUM(COALESCE(high_count, 0)), 0) * 2 +
368 COALESCE(SUM(COALESCE(medium_count, 0)), 0) * 1
369 ) DESC
370 LIMIT %s
371 """
372 results = self.db.execute_query(query, (user_id, limit))
374 if not results:
375 return []
377 targets = []
378 for row in results:
379 targets.append({
380 'target': row['target'],
381 'critical': int(row['critical']),
382 'high': int(row['high']),
383 'medium': int(row['medium']),
384 'low': int(row['low']),
385 'total_vulnerabilities': int(row['critical']) + int(row['high']) + int(row['medium']) + int(row['low']),
386 'scan_count': int(row['scan_count']),
387 'last_scan': row['last_scan'].isoformat() if row['last_scan'] else None
388 })
390 return targets
392 except Exception as e:
393 print(f"❌ Error getting vulnerable targets: {e}")
394 return []