# 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