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>
411 lines
15 KiB
Markdown
411 lines
15 KiB
Markdown
# 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
|