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:
2025-10-16 21:14:10 +02:00
co-authored by Claude
parent adbfd23c26
commit 1bb117fd98
7 changed files with 776 additions and 7 deletions
+59 -1
View File
@@ -4,7 +4,7 @@ import shutil
import yaml
from datetime import datetime
from flask import current_app
from app.models.feedback import Feedback
from app.models.feedback import Feedback, AnalysisResult
from app.utils.file_validator import get_safe_filename
@@ -471,3 +471,61 @@ class FeedbackStorageService:
return None
return attachment_path
@staticmethod
def save_analysis(product_id, feedback_id, analysis_result):
"""Save AI analysis results to filesystem (T082)
Creates analysis.md file with formatted analysis results and updates
metadata with category and language information.
Args:
product_id: Product ID
feedback_id: Feedback ID
analysis_result: AnalysisResult instance with analysis data
Returns:
bool: True if saved successfully, False otherwise
"""
data_dir = current_app.config['DATA_DIR']
feedback_dir = os.path.join(data_dir, 'products', product_id, 'feedback', feedback_id)
if not os.path.exists(feedback_dir):
return False
# Create analysis.md file with formatted content (T083)
analysis_file = os.path.join(feedback_dir, 'analysis.md')
analysis_markdown = FeedbackStorageService._create_analysis_markdown(analysis_result)
with open(analysis_file, 'w', encoding='utf-8') as f:
f.write(analysis_markdown)
# Update metadata with category and language (T089)
metadata_file = os.path.join(feedback_dir, 'metadata.yaml')
if os.path.exists(metadata_file):
with open(metadata_file, 'r') as f:
metadata = yaml.safe_load(f)
# Store detected language
metadata['original_language'] = analysis_result.original_language
# Store category
metadata['category'] = analysis_result.category
with open(metadata_file, 'w') as f:
yaml.dump(metadata, f)
return True
@staticmethod
def _create_analysis_markdown(analysis_result):
"""Create formatted analysis markdown (T083)
Args:
analysis_result: AnalysisResult instance
Returns:
str: Formatted markdown content
"""
# Use the raw analysis from Claude, which is already formatted
return analysis_result.raw_analysis