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
+162
View File
@@ -0,0 +1,162 @@
"""Unit tests for AI analyzer"""
import pytest
from unittest.mock import Mock, patch, MagicMock
from app.services.ai_analyzer import AIAnalyzer, ClaudeAnalyzer
from app.models.feedback import AnalysisResult
@pytest.mark.unit
def test_ai_analyzer_interface():
"""T065: Unit test for AIAnalyzer interface
Verify that AIAnalyzer is an abstract base class
that cannot be instantiated directly
"""
with pytest.raises(TypeError):
# Should not be able to instantiate abstract base class
AIAnalyzer()
@pytest.mark.unit
def test_claude_analyzer_categorization(app):
"""T066: Unit test for ClaudeAnalyzer categorization
Verify that ClaudeAnalyzer correctly extracts category from AI response
"""
with app.app_context():
# Mock the Anthropic client to avoid initialization issues
with patch('app.services.ai_analyzer.anthropic.Anthropic'):
analyzer = ClaudeAnalyzer(api_key='test-key')
# Mock AI response with category
mock_response = Mock()
mock_response.content = [Mock(text="""
# Feedback Analysis
**Category**: bug
**Original Language**: en
**Summary**: User reports login issue
**Translation**: (same as original)
""")]
with patch.object(analyzer, '_call_claude_api', return_value=mock_response):
result = analyzer.analyze_feedback(
feedback_text="Login button doesn't work",
target_language='en',
product_id='test-product'
)
assert result.category == 'bug'
@pytest.mark.unit
def test_claude_analyzer_translation(app):
"""T067: Unit test for ClaudeAnalyzer translation
Verify that ClaudeAnalyzer correctly translates feedback
"""
with app.app_context():
# Mock the Anthropic client to avoid initialization issues
with patch('app.services.ai_analyzer.anthropic.Anthropic'):
analyzer = ClaudeAnalyzer(api_key='test-key')
# Mock AI response with translation
mock_response = Mock()
mock_response.content = [Mock(text="""
# Feedback Analysis
**Category**: feature_request
**Original Language**: de
**Summary**: User wants dark mode
**Translation**: I would like to have a dark mode for the application
""")]
with patch.object(analyzer, '_call_claude_api', return_value=mock_response):
result = analyzer.analyze_feedback(
feedback_text="Ich hätte gerne einen Dark Mode für die Anwendung",
target_language='en',
product_id='test-product'
)
assert result.translation == 'I would like to have a dark mode for the application'
assert result.original_language == 'de'
@pytest.mark.unit
def test_claude_analyzer_summary_generation(app):
"""T068: Unit test for ClaudeAnalyzer summary generation
Verify that ClaudeAnalyzer generates concise summaries
"""
with app.app_context():
# Mock the Anthropic client to avoid initialization issues
with patch('app.services.ai_analyzer.anthropic.Anthropic'):
analyzer = ClaudeAnalyzer(api_key='test-key')
# Mock AI response with summary
mock_response = Mock()
mock_response.content = [Mock(text="""
# Feedback Analysis
**Category**: complaint
**Original Language**: en
**Summary**: User experienced slow page load times during peak hours
**Translation**: (same as original)
""")]
with patch.object(analyzer, '_call_claude_api', return_value=mock_response):
long_feedback = """
I've been using your service for three months now, and I have to say
I'm quite disappointed with the performance during peak hours. Yesterday
evening around 8 PM, I tried to load the dashboard multiple times and
each time it took over 30 seconds. This is unacceptable for a paid service.
"""
result = analyzer.analyze_feedback(
feedback_text=long_feedback,
target_language='en',
product_id='test-product'
)
assert result.summary == 'User experienced slow page load times during peak hours'
assert len(result.summary) < len(long_feedback)
@pytest.mark.unit
def test_analysis_error_handling(app):
"""T069: Unit test for analysis error handling
Verify that ClaudeAnalyzer handles API errors gracefully
"""
with app.app_context():
# Mock the Anthropic client to avoid initialization issues
with patch('app.services.ai_analyzer.anthropic.Anthropic'):
analyzer = ClaudeAnalyzer(api_key='test-key')
# Test API timeout
with patch.object(analyzer, '_call_claude_api', side_effect=TimeoutError("API timeout")):
with pytest.raises(Exception) as exc_info:
analyzer.analyze_feedback(
feedback_text="Test feedback",
target_language='en',
product_id='test-product'
)
assert "timeout" in str(exc_info.value).lower() or "API" in str(exc_info.value)
# Test API error
with patch.object(analyzer, '_call_claude_api', side_effect=Exception("API Error")):
with pytest.raises(Exception):
analyzer.analyze_feedback(
feedback_text="Test feedback",
target_language='en',
product_id='test-product'
)