# 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.