Implement Phase 4: AI-Powered Feedback Analysis (User Story 2)
Implement automatic AI analysis of feedback submissions using Claude API, including language detection, categorization, summarization, and translation to product owner's preferred language. Tasks Completed (T065-T092): - T065-T069: Unit tests for AI analyzer (5 tests) - T070: Integration test for full AI analysis workflow - T071: Created AIAnalyzer abstract base class interface - T072: Added AnalysisResult dataclass to feedback model - T073: Implemented ClaudeAnalyzer with Anthropic SDK - T074: Integrated Claude API with 45s timeout - T075: Designed single-call analysis prompt - T076: Language detection implementation - T077: Category extraction with validation - T078: Summary generation (1-2 sentences) - T079: Translation extraction - T080: Timeout handling for Claude API - T081: API error handling with proper exceptions - T082: Analysis storage to analysis.md file - T083: Formatted markdown output for analysis - T084: Background analysis trigger on submission - T085: Non-blocking async analysis via threading - T086: Status update to 'analyzing' before analysis - T087: Status update to 'analyzed' on success - T088: Status update to 'analysis_failed' on error - T089: Metadata update with category and language - T090: Environment configuration for ANTHROPIC_API_KEY - T091: Verification that original content.txt preserved (FR-016) - T092: Verification that images not analyzed via OCR (FR-021) Features: - Abstract AIAnalyzer interface for multiple AI providers - ClaudeAnalyzer implementation using Anthropic API - Background threading for non-blocking analysis - Flask app context management in background threads - Comprehensive error handling and status tracking - Original content preservation (FR-016 compliance) - Image storage without OCR (FR-021 compliance) Testing: - 5 unit tests for AI analyzer components - 2 integration tests for full analysis workflow - All 46 tests passing (1 skipped) - Mock-based testing to avoid API calls Files Changed: - app/models/feedback.py: Added AnalysisResult dataclass - app/routes/submission.py: Background analysis integration - app/services/ai_analyzer.py: NEW - AI analysis service - app/services/feedback_storage.py: Analysis storage methods - tests/unit/test_ai_analyzer.py: NEW - Unit tests (5 tests) - tests/integration/test_ai_analysis_flow.py: NEW - Integration tests (2 tests) - tests/integration/test_feedback_submission_flow.py: Threading mock added 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
"""Submission routes - anonymous feedback submission"""
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, flash, abort
|
||||
import threading
|
||||
import os
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, flash, abort, current_app
|
||||
from app.models.product import Product
|
||||
from app.services.feedback_storage import FeedbackStorageService
|
||||
from app.services.ai_analyzer import ClaudeAnalyzer
|
||||
from app.utils.file_validator import validate_file, scan_file_for_viruses
|
||||
|
||||
|
||||
@@ -86,15 +89,91 @@ def submit(product_slug):
|
||||
files=files if files else None
|
||||
)
|
||||
|
||||
# Trigger background analysis (T084, T085)
|
||||
if feedback_text: # Only analyze if there's text content
|
||||
_trigger_background_analysis(feedback, feedback_text, product)
|
||||
|
||||
return render_template('submission/success.html',
|
||||
product=product,
|
||||
feedback_id=feedback.feedback_id)
|
||||
|
||||
except Exception as e:
|
||||
# Log error
|
||||
from flask import current_app
|
||||
current_app.logger.error(f"Error saving feedback: {e}")
|
||||
|
||||
return render_template('submission/error.html',
|
||||
product=product,
|
||||
error_message="An error occurred while saving your feedback. Please try again."), 500
|
||||
|
||||
|
||||
def _trigger_background_analysis(feedback, feedback_text, product):
|
||||
"""Trigger background AI analysis task (T084)
|
||||
|
||||
Args:
|
||||
feedback: Feedback instance
|
||||
feedback_text: Feedback text content
|
||||
product: Product instance
|
||||
"""
|
||||
# Get the current app instance to pass to background thread
|
||||
app = current_app._get_current_object()
|
||||
|
||||
# Run analysis in background thread
|
||||
thread = threading.Thread(
|
||||
target=_analyze_feedback_background,
|
||||
args=(app, feedback.product_id, feedback.feedback_id, feedback_text, product.owner_language)
|
||||
)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
|
||||
def _analyze_feedback_background(app, product_id, feedback_id, feedback_text, target_language):
|
||||
"""Background task for AI analysis (T086-T088)
|
||||
|
||||
This runs in a separate thread to avoid blocking the submission response.
|
||||
|
||||
Args:
|
||||
app: Flask app instance for application context
|
||||
product_id: Product ID
|
||||
feedback_id: Feedback ID
|
||||
feedback_text: Feedback text to analyze
|
||||
target_language: Target language for translation
|
||||
"""
|
||||
# Run within Flask application context
|
||||
with app.app_context():
|
||||
try:
|
||||
# Update status to "analyzing" (T086)
|
||||
FeedbackStorageService.update_feedback_status_by_id(
|
||||
product_id, feedback_id, 'analyzing'
|
||||
)
|
||||
|
||||
# Get API key from environment
|
||||
api_key = os.getenv('ANTHROPIC_API_KEY')
|
||||
|
||||
if not api_key:
|
||||
raise Exception("ANTHROPIC_API_KEY not configured")
|
||||
|
||||
# Initialize analyzer
|
||||
analyzer = ClaudeAnalyzer(api_key=api_key)
|
||||
|
||||
# Analyze feedback
|
||||
result = analyzer.analyze_feedback(
|
||||
feedback_text=feedback_text,
|
||||
target_language=target_language,
|
||||
product_id=product_id
|
||||
)
|
||||
|
||||
# Save analysis results
|
||||
FeedbackStorageService.save_analysis(product_id, feedback_id, result)
|
||||
|
||||
# Update status to "analyzed" (T087)
|
||||
FeedbackStorageService.update_feedback_status_by_id(
|
||||
product_id, feedback_id, 'analyzed'
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
# Update status to "analysis_failed" on error (T088)
|
||||
FeedbackStorageService.update_feedback_status_by_id(
|
||||
product_id, feedback_id, 'analysis_failed'
|
||||
)
|
||||
# Log error
|
||||
print(f"Analysis failed for feedback {feedback_id}: {e}")
|
||||
|
||||
Reference in New Issue
Block a user