Add implementation planning artifacts for anonymous feedback platform
Complete Phase 0 (Research) and Phase 1 (Design) of implementation planning workflow: - plan.md: Technical context, constitution check, and project structure - research.md: Technology decisions for AI integration, file storage, authentication, security - data-model.md: Domain entities with validation rules and state transitions - contracts/: API specifications for submission, dashboard, and admin endpoints - quickstart.md: Developer setup guide with test-first workflow - CLAUDE.md: Updated agent context with tech stack All constitutional requirements validated. Ready for task generation phase. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,410 @@
|
||||
# Data Model: Anonymous Feedback Platform (Reklamator)
|
||||
|
||||
**Branch**: `001-build-an-application` | **Date**: 2025-10-15
|
||||
|
||||
This document defines the domain entities, their attributes, relationships, validation rules, and state transitions for the Reklamator application.
|
||||
|
||||
## Entity Overview
|
||||
|
||||
```
|
||||
Product (1) ----< (N) Feedback
|
||||
| |
|
||||
| |---< (N) Attachment
|
||||
| |
|
||||
| |---- (1) AnalysisResult
|
||||
|
|
||||
|----< (N) ProductOwner
|
||||
|
||||
Administrator (manages all entities)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Entity Definitions
|
||||
|
||||
### 1. Feedback Submission
|
||||
|
||||
**Description**: Represents a single feedback item submitted by an anonymous user.
|
||||
|
||||
**Storage Location**: `data/products/{product_id}/feedback/{feedback_id}/`
|
||||
|
||||
#### Attributes
|
||||
|
||||
| Field | Type | Required | Validation | Description |
|
||||
|-------|------|----------|------------|-------------|
|
||||
| `id` | UUID v4 | Yes | Auto-generated | Unique identifier |
|
||||
| `product_id` | String | Yes | Must reference existing product | Associated product identifier |
|
||||
| `original_text` | String | Yes* | 1-10,000 characters | Original feedback text (*empty if file-only submission) |
|
||||
| `original_language` | String (ISO 639-1) | No | 2-char code | Detected language (e.g., "en", "de", "ja") |
|
||||
| `submission_timestamp` | ISO 8601 DateTime | Yes | Auto-generated | When feedback was submitted (UTC) |
|
||||
| `status` | Enum | Yes | See Status enum below | Current processing/review status |
|
||||
| `category` | Enum | No | See Category enum below | AI-assigned category (null if analysis pending/failed) |
|
||||
| `attachment_count` | Integer | Yes | 0-3 | Number of attached files |
|
||||
|
||||
#### Status Enum
|
||||
- `submitted` - Initial state after successful submission
|
||||
- `analyzing` - AI analysis in progress
|
||||
- `analysis_failed` - AI analysis encountered error
|
||||
- `analyzed` - AI analysis completed successfully
|
||||
- `reviewed` - Product owner has reviewed
|
||||
- `in_progress` - Product owner marked as being worked on
|
||||
- `resolved` - Product owner marked as resolved
|
||||
- `rejected` - Product owner marked as not actionable
|
||||
|
||||
#### Category Enum (AI-assigned)
|
||||
- `idea` - New concept or suggestion
|
||||
- `feature_request` - Request for specific functionality
|
||||
- `bug` - Problem or defect report
|
||||
- `complaint` - Negative feedback about existing functionality
|
||||
|
||||
#### Validation Rules
|
||||
- FR-002: `original_text` max length 10,000 characters
|
||||
- FR-011: At least one of (`original_text`, `attachment_count > 0`) must be true
|
||||
- FR-012: Cannot be empty (no text AND no attachments)
|
||||
- FR-027: `submission_timestamp` immutable after creation
|
||||
|
||||
#### State Transitions
|
||||
```
|
||||
submitted → analyzing → analyzed → {reviewed, in_progress, resolved, rejected}
|
||||
↓
|
||||
analysis_failed (terminal state until manual retry)
|
||||
```
|
||||
|
||||
#### File Representation (metadata.yaml)
|
||||
```yaml
|
||||
id: "a3f2c1d5-8b4e-4f1a-9c2d-7e6f5a4b3c2d"
|
||||
product_id: "001-acme-app"
|
||||
original_language: "en"
|
||||
submission_timestamp: "2025-10-15T14:32:10Z"
|
||||
status: "analyzed"
|
||||
category: "bug"
|
||||
attachment_count: 2
|
||||
attachments:
|
||||
- filename: "screenshot.png"
|
||||
size_bytes: 245678
|
||||
mime_type: "image/png"
|
||||
- filename: "error_log.txt"
|
||||
size_bytes: 1234
|
||||
mime_type: "text/plain"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Product/Service
|
||||
|
||||
**Description**: Represents a product or service for which feedback can be collected.
|
||||
|
||||
**Storage Location**: `data/products/{product_id}/config.yaml`
|
||||
|
||||
#### Attributes
|
||||
|
||||
| Field | Type | Required | Validation | Description |
|
||||
|-------|------|----------|------------|-------------|
|
||||
| `id` | String | Yes | Unique, URL-safe slug | Product identifier (e.g., "001-acme-app") |
|
||||
| `name` | String | Yes | 1-100 characters | Display name |
|
||||
| `description` | String | No | Max 500 characters | Product description |
|
||||
| `target_language` | String (ISO 639-1) | Yes | 2-char code | Preferred language for AI translations |
|
||||
| `submission_url_slug` | String | Yes | URL-safe, unique | URL path for submission form (e.g., "/submit/acme-app") |
|
||||
| `created_date` | ISO 8601 Date | Yes | Auto-generated | When product was registered |
|
||||
| `status` | Enum | Yes | "active" or "archived" | Current status |
|
||||
| `assigned_owner_ids` | List[String] | No | Must reference existing users | Product owner user IDs |
|
||||
|
||||
#### Validation Rules
|
||||
- FR-046: `id` must be unique across all products
|
||||
- FR-047: `target_language` must be valid ISO 639-1 code
|
||||
- FR-049: `submission_url_slug` must be unique and URL-safe (alphanumeric + hyphens)
|
||||
- FR-053: Cannot accept new feedback if `status` is "archived"
|
||||
|
||||
#### State Transitions
|
||||
```
|
||||
active ⇄ archived (bidirectional, admin only)
|
||||
```
|
||||
|
||||
#### File Representation (config.yaml)
|
||||
```yaml
|
||||
id: "001-acme-app"
|
||||
name: "Acme Mobile App"
|
||||
description: "Customer feedback for Acme's flagship mobile application"
|
||||
target_language: "en"
|
||||
submission_url_slug: "acme-app"
|
||||
created_date: "2025-10-01"
|
||||
status: "active"
|
||||
assigned_owner_ids:
|
||||
- "owner-001"
|
||||
- "owner-002"
|
||||
statistics:
|
||||
total_feedback_count: 127
|
||||
last_submission: "2025-10-15T14:32:10Z"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Analysis Result
|
||||
|
||||
**Description**: Represents the AI-generated analysis of a feedback submission.
|
||||
|
||||
**Storage Location**: `data/products/{product_id}/feedback/{feedback_id}/analysis.md`
|
||||
**Additional Storage**: Original text stored in `content.txt` for reference
|
||||
|
||||
#### Attributes (Markdown Format)
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `translated_text` | Markdown | Yes* | Feedback translated to target language (*if different from original) |
|
||||
| `summary` | Markdown | Yes | Concise 2-3 sentence summary in target language |
|
||||
| `detected_category` | Enum | Yes | Category assigned by AI (idea/feature_request/bug/complaint) |
|
||||
| `confidence_score` | Float (0.0-1.0) | Yes | AI confidence in categorization |
|
||||
| `analysis_timestamp` | ISO 8601 DateTime | Yes | When analysis completed |
|
||||
| `model_used` | String | Yes | AI model identifier (e.g., "claude-3-haiku-20240307") |
|
||||
| `error_message` | String | No | Error details if analysis failed |
|
||||
|
||||
#### Validation Rules
|
||||
- FR-014: `summary` should be 2-3 sentences maximum
|
||||
- FR-015: `translated_text` required unless original language = target language
|
||||
- FR-016: Original text preserved in `content.txt` alongside analysis
|
||||
- FR-017: Detected language stored in feedback metadata.yaml `original_language`
|
||||
|
||||
#### File Representation (analysis.md)
|
||||
```markdown
|
||||
# Feedback Analysis
|
||||
|
||||
**Analyzed**: 2025-10-15T14:35:22Z
|
||||
**Model**: claude-3-haiku-20240307
|
||||
**Category**: bug (confidence: 0.92)
|
||||
**Original Language**: de → **Target Language**: en
|
||||
|
||||
## Summary
|
||||
|
||||
User reports that the app crashes when uploading large files. This appears to be a bug affecting the file upload module, preventing users from submitting documents over 5MB.
|
||||
|
||||
## Translation
|
||||
|
||||
**Original (German):**
|
||||
> Die App stürzt ab, wenn ich versuche, große Dateien hochzuladen. Jedes Mal wenn ich eine PDF über 5MB hochlade, friert die App ein und schließt sich.
|
||||
|
||||
**Translated (English):**
|
||||
The app crashes when I try to upload large files. Every time I upload a PDF over 5MB, the app freezes and closes.
|
||||
|
||||
## Attachments
|
||||
|
||||
- screenshot.png (245 KB)
|
||||
- error_log.txt (1 KB)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Attachment
|
||||
|
||||
**Description**: Represents a file (document or image) uploaded with feedback.
|
||||
|
||||
**Storage Location**: `data/products/{product_id}/feedback/{feedback_id}/attachments/{filename}`
|
||||
|
||||
#### Attributes (stored in feedback metadata.yaml)
|
||||
|
||||
| Field | Type | Required | Validation | Description |
|
||||
|-------|------|----------|------------|-------------|
|
||||
| `filename` | String | Yes | Sanitized, max 255 chars | Original filename (sanitized for safety) |
|
||||
| `size_bytes` | Integer | Yes | Max 10,485,760 (10MB) | File size in bytes |
|
||||
| `mime_type` | String | Yes | See allowed types | Validated MIME type |
|
||||
| `upload_timestamp` | ISO 8601 DateTime | Yes | Auto-generated | When file was uploaded |
|
||||
| `virus_scan_status` | Enum | Yes | "clean" or "infected" | ClamAV scan result |
|
||||
|
||||
#### Allowed MIME Types
|
||||
- Documents: `application/pdf`, `application/vnd.openxmlformats-officedocument.wordprocessingml.document` (DOCX), `text/plain`
|
||||
- Images: `image/jpeg`, `image/png`, `image/gif`, `image/webp`
|
||||
|
||||
#### Validation Rules
|
||||
- FR-004: Maximum 3 attachments per feedback
|
||||
- FR-006: Maximum 10MB per file
|
||||
- FR-026: Preserve original filename (sanitized)
|
||||
- FR-059: Sanitize filename to prevent directory traversal
|
||||
- FR-060: Must pass ClamAV virus scan before storage
|
||||
|
||||
#### Security Sanitization
|
||||
- Remove directory traversal patterns: `../`, `..\\`, absolute paths
|
||||
- Replace unsafe characters: `<>:"|?*`
|
||||
- Limit filename length to 255 characters
|
||||
- If duplicate filename, append counter: `file.pdf` → `file_2.pdf`
|
||||
|
||||
---
|
||||
|
||||
### 5. Product Owner
|
||||
|
||||
**Description**: Represents an authenticated user responsible for reviewing feedback for one or more products.
|
||||
|
||||
**Storage Location**: `data/users.yaml`
|
||||
|
||||
#### Attributes
|
||||
|
||||
| Field | Type | Required | Validation | Description |
|
||||
|-------|------|----------|------------|-------------|
|
||||
| `id` | String | Yes | Unique | User identifier (e.g., "owner-001") |
|
||||
| `email` | String | Yes | Valid email, unique | Login email address |
|
||||
| `password_hash` | String (bcrypt) | Yes | bcrypt format | Hashed password (never store plaintext) |
|
||||
| `name` | String | Yes | 1-100 characters | Display name |
|
||||
| `role` | Enum | Yes | "product_owner" | User role (always "product_owner" for this entity) |
|
||||
| `assigned_product_ids` | List[String] | Yes | Must reference existing products | Products this owner can access |
|
||||
| `created_date` | ISO 8601 Date | Yes | Auto-generated | Account creation date |
|
||||
| `last_login` | ISO 8601 DateTime | No | Auto-updated | Last successful login |
|
||||
|
||||
#### Validation Rules
|
||||
- FR-048: Can be assigned to multiple products
|
||||
- FR-056: Must authenticate to access dashboard
|
||||
- FR-063: Passwords hashed with bcrypt (cost factor 12)
|
||||
- FR-033: Can only view feedback for `assigned_product_ids`
|
||||
|
||||
#### File Representation (users.yaml entry)
|
||||
```yaml
|
||||
users:
|
||||
- id: "owner-001"
|
||||
email: "jane.smith@example.com"
|
||||
password_hash: "$2b$12$KIXxBt5H4vE2zT9vN8FqOe9Jx..."
|
||||
name: "Jane Smith"
|
||||
role: "product_owner"
|
||||
assigned_product_ids:
|
||||
- "001-acme-app"
|
||||
- "002-beta-service"
|
||||
created_date: "2025-09-15"
|
||||
last_login: "2025-10-15T09:23:11Z"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. Administrator
|
||||
|
||||
**Description**: Represents a privileged user who can register products, assign owners, and manage system configuration.
|
||||
|
||||
**Storage Location**: `data/users.yaml` (same file as Product Owners)
|
||||
|
||||
#### Attributes
|
||||
|
||||
| Field | Type | Required | Validation | Description |
|
||||
|-------|------|----------|------------|-------------|
|
||||
| `id` | String | Yes | Unique | User identifier (e.g., "admin-001") |
|
||||
| `email` | String | Yes | Valid email, unique | Login email address |
|
||||
| `password_hash` | String (bcrypt) | Yes | bcrypt format | Hashed password |
|
||||
| `name` | String | Yes | 1-100 characters | Display name |
|
||||
| `role` | Enum | Yes | "admin" | User role (always "admin" for this entity) |
|
||||
| `assigned_product_ids` | List[String] | Yes | Empty list | Empty = access to all products |
|
||||
| `created_date` | ISO 8601 Date | Yes | Auto-generated | Account creation date |
|
||||
| `last_login` | ISO 8601 DateTime | No | Auto-updated | Last successful login |
|
||||
|
||||
#### Validation Rules
|
||||
- Admin role grants full access regardless of `assigned_product_ids`
|
||||
- FR-045: Can create/modify/archive products
|
||||
- FR-048: Can assign/unassign product owners
|
||||
- Same authentication requirements as Product Owner (FR-056, FR-063)
|
||||
|
||||
#### File Representation (users.yaml entry)
|
||||
```yaml
|
||||
users:
|
||||
- id: "admin-001"
|
||||
email: "admin@reklamator.local"
|
||||
password_hash: "$2b$12$vL3Fx9..."
|
||||
name: "System Administrator"
|
||||
role: "admin"
|
||||
assigned_product_ids: [] # Empty = all access
|
||||
created_date: "2025-09-01"
|
||||
last_login: "2025-10-15T10:45:33Z"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Domain Rules & Invariants
|
||||
|
||||
### Cross-Entity Rules
|
||||
|
||||
1. **Product-Feedback Relationship** (1:N)
|
||||
- Every Feedback must reference exactly one valid Product
|
||||
- Product can have zero or many Feedback items
|
||||
- Archived products cannot receive new feedback (FR-053)
|
||||
|
||||
2. **Feedback-Attachment Relationship** (1:N)
|
||||
- Feedback can have 0-3 Attachments (FR-004, FR-010)
|
||||
- Attachments cannot exist without parent Feedback (cascade delete)
|
||||
|
||||
3. **Feedback-AnalysisResult Relationship** (1:1)
|
||||
- Every analyzed Feedback has exactly one AnalysisResult
|
||||
- AnalysisResult created asynchronously after Feedback submission
|
||||
- Original content preserved even if analysis fails (FR-020)
|
||||
|
||||
4. **Product-Owner Relationship** (N:M)
|
||||
- Product can have 1 or more assigned Product Owners (FR-048)
|
||||
- Product Owner can be assigned to multiple Products
|
||||
- Admin users bypass assignment logic (implicit access to all)
|
||||
|
||||
5. **Anonymity Constraint** (Global)
|
||||
- No IP addresses stored in Feedback metadata (FR-055, SC-010)
|
||||
- No session tracking for anonymous submissions
|
||||
- Rate limiting uses IP for abuse prevention only (not persisted)
|
||||
|
||||
### Deletion Rules
|
||||
|
||||
- **Feedback Deletion**: Deletes metadata.yaml, content.txt, analysis.md, and all attachments/
|
||||
- **Product Archival**: Sets status to "archived", preserves all feedback (FR-052)
|
||||
- **Product Owner Removal**: Unassigns from products, does not delete feedback
|
||||
- **Cascade Protection**: Cannot delete Product with active feedback (archive instead)
|
||||
|
||||
---
|
||||
|
||||
## Storage Implementation Notes
|
||||
|
||||
### Directory Structure Example
|
||||
|
||||
```
|
||||
data/
|
||||
├── users.yaml # All users (admins + owners)
|
||||
└── products/
|
||||
├── 001-acme-app/
|
||||
│ ├── config.yaml # Product metadata
|
||||
│ └── feedback/
|
||||
│ ├── a3f2c1d5-8b4e-.../
|
||||
│ │ ├── metadata.yaml # Feedback + attachment metadata
|
||||
│ │ ├── content.txt # Original feedback text
|
||||
│ │ ├── analysis.md # AI analysis report
|
||||
│ │ └── attachments/
|
||||
│ │ ├── screenshot.png
|
||||
│ │ └── error_log.txt
|
||||
│ └── b7e1f3d2-4a9c-.../
|
||||
│ ├── metadata.yaml
|
||||
│ ├── content.txt
|
||||
│ └── analysis.md # No attachments/ for this one
|
||||
└── 002-beta-service/
|
||||
├── config.yaml
|
||||
└── feedback/
|
||||
└── ...
|
||||
```
|
||||
|
||||
### File Format Standards
|
||||
|
||||
- **YAML**: UTF-8 encoding, 2-space indentation, explicit type declarations
|
||||
- **Markdown**: CommonMark specification, UTF-8 encoding
|
||||
- **Text Files**: UTF-8 encoding with BOM handling
|
||||
|
||||
### Indexing Strategy (Performance)
|
||||
|
||||
For dashboard performance (SC-008, SC-014):
|
||||
- Cache product feedback counts in `config.yaml` statistics
|
||||
- Implement pagination (50 items per page)
|
||||
- Load metadata.yaml only, defer analysis.md loading until detail view
|
||||
- File modification times used for sorting (newest first per FR-041)
|
||||
|
||||
---
|
||||
|
||||
## Validation Summary by Functional Requirement
|
||||
|
||||
| FR | Validation Location | Rule |
|
||||
|----|---------------------|------|
|
||||
| FR-002 | Feedback.original_text | Max 10,000 characters |
|
||||
| FR-004 | Feedback.attachment_count | 0-3 attachments |
|
||||
| FR-006 | Attachment.size_bytes | Max 10MB per file |
|
||||
| FR-011 | Feedback validation | At least text OR files required |
|
||||
| FR-012 | Feedback validation | Cannot be completely empty |
|
||||
| FR-046 | Product.id | Must be unique |
|
||||
| FR-048 | Product.assigned_owner_ids | 1+ owners required |
|
||||
| FR-053 | Product status check | Reject if archived |
|
||||
| FR-063 | User.password_hash | bcrypt with cost 12 |
|
||||
|
||||
---
|
||||
|
||||
**Next Steps**: Define API contracts in `/contracts/` directory
|
||||
Reference in New Issue
Block a user