Files
Reklamator/specs/002-product-list/data-model.md
T

148 lines
4.3 KiB
Markdown
Raw Normal View History

# Data Model: Product Selection Landing Page
**Feature**: 002-product-list | **Date**: 2025-10-17
## Overview
This feature reuses the existing **Product** entity from feature 001-build-an-application with a minor extension. No new entities are created.
## Existing Entity: Product
**Source**: `app/models/product.py` (from feature 001)
**Attributes** (relevant to this feature):
| Attribute | Type | Required | Description |
|-----------|------|----------|-------------|
| `product_id` | str | Yes | Unique identifier (e.g., "001-acme-app") |
| `name` | str | Yes | Display name for the product |
| `submission_url_slug` | str | Yes | URL segment for submission form |
| `status` | str | Yes | "active" or "archived" |
| `description` | str | No | Brief product description (optional) |
**Storage**: File-based YAML at `data/products/{product-id}/config.yaml`
**Example**:
```yaml
product_id: "001-acme-app"
name: "Acme Application"
submission_url_slug: "acme-app"
status: "active"
description: "Enterprise resource planning system"
```
---
## Model Extension
### New Class Method: `load_active()`
**Purpose**: Load and return only active products with valid submission URLs, sorted for display.
**Signature**:
```python
@classmethod
def load_active(cls) -> list[Product]:
"""Load all active products, sorted alphabetically by name then product_id."""
```
**Returns**: List of Product instances where:
- `status == 'active'`
- `submission_url_slug` is present and valid
- Sorted by: `(name.lower(), product_id)`
**Implementation Location**: `app/models/product.py`
**Usage Example**:
```python
from app.models.product import Product
# In route handler
products = Product.load_active()
# Returns sorted list of active products ready for display
```
---
## Data Flow
### Landing Page Load Sequence
1. **Request**: User navigates to `/`
2. **Route Handler**: `app/routes/landing.py`
- Calls `Product.load_active()`
3. **Data Loading**: Product model
- Reads all `data/products/*/config.yaml` files
- Filters: `status == 'active'` AND `submission_url_slug` exists
- Sorts: By `(name.lower(), product_id)`
4. **Response**: Render template with product list
- Pass `products` to `templates/landing/index.html`
- Template loops over products, displays name/description/link
### Diagram
```
User → GET / → landing.py → Product.load_active() → [Product, Product, ...]
Templates (Jinja2) → HTML Response → User
```
---
## Validation Rules
### Product Visibility (for landing page)
A product is **visible** on the landing page if and only if:
1. `status == 'active'` (FR-003)
2. `submission_url_slug` is not None/empty (FR-015)
Products failing either condition are **excluded** from the list.
### Sorting Rules (FR-009)
Products are sorted by:
1. **Primary**: `name` (case-insensitive alphabetical)
2. **Secondary**: `product_id` (alphabetical, for ties)
**Examples**:
- Input: `[{"name": "Zebra", "product_id": "001"}, {"name": "Apple", "product_id": "002"}]`
- Output: `[{"name": "Apple", ...}, {"name": "Zebra", ...}]`
- Input: `[{"name": "App", "product_id": "002"}, {"name": "App", "product_id": "001"}]`
- Output: `[{"name": "App", "product_id": "001"}, {"name": "App", "product_id": "002"}]`
---
## No New Entities
This feature introduces **no new entities**. All data structures are reused from feature 001:
- Product entity (extended with `load_active()` method only)
- File-based YAML storage (unchanged)
- No database tables, no new data files
---
## Testing Considerations
### Test Data Requirements
For comprehensive testing, create products with:
- Various statuses: "active", "archived"
- With/without descriptions
- With/without valid `submission_url_slug`
- Duplicate names (to test secondary sort)
- Various name cases ("Apple", "apple", "APPLE")
### Expected Behaviors
| Scenario | Expected Result |
|----------|----------------|
| Product with `status: active` | Included in list |
| Product with `status: archived` | Excluded from list |
| Product with missing `submission_url_slug` | Excluded from list |
| Products with same name | Sorted by product_id |
| Empty product directory | Returns empty list |
**Reference Tests**: See `tests/contract/test_landing_routes.py` for data model validation tests.