94 lines
3.8 KiB
Markdown
94 lines
3.8 KiB
Markdown
# Research: Product Selection Landing Page
|
|||
|
|
|
||
|
|
**Branch**: `002-product-list` | **Date**: 2025-10-17
|
||
|
|
|
||
|
|
This document addresses technical decisions for the product selection landing page feature. Most infrastructure decisions were resolved in feature 001-build-an-application and are reused here.
|
||
|
|
|
||
|
|
## Existing Infrastructure (from 001-build-an-application)
|
||
|
|
|
||
|
|
The following technical decisions from feature 001 are reused without modification:
|
||
|
|
|
||
|
|
- **Flask 3.0+ with Jinja2**: Server-side rendering, no JavaScript
|
||
|
|
- **File-based storage**: Product configs in `data/products/*/config.yaml`
|
||
|
|
- **Product model**: Existing `app/models/product.py` with load methods
|
||
|
|
- **Template patterns**: Minimal HTML/CSS, Jinja2 auto-escaping for XSS prevention
|
||
|
|
- **Routing**: Flask route decorators, blueprint organization
|
||
|
|
- **Testing**: pytest + pytest-flask for contract and integration tests
|
||
|
|
|
||
|
|
**Reference**: See `/home/markus/workspace/reklamator/specs/001-build-an-application/research.md` for full details.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## New Technical Decision: Product List Retrieval & Sorting
|
||
|
|
|
||
|
|
### Decision: Extend Product model with `load_active()` class method
|
||
|
|
|
||
|
|
**Rationale**:
|
||
|
|
- Centralizes "active products only" filtering logic
|
||
|
|
- Enables reuse if other features need active product lists
|
||
|
|
- Encapsulates sorting algorithm in one place
|
||
|
|
- Follows existing Product model pattern (e.g., `load_all()`, `load_by_id()`)
|
||
|
|
|
||
|
|
**Implementation**:
|
||
|
|
|
||
|
|
```python
|
||
|
|
# app/models/product.py - add class method
|
||
|
|
@classmethod
|
||
|
|
def load_active(cls):
|
||
|
|
"""Load all active products, sorted alphabetically by name then product_id.
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
list[Product]: Active products with valid submission_url_slug, sorted by:
|
||
|
|
1. name (case-insensitive alphabetical)
|
||
|
|
2. product_id (alphabetical) as tiebreaker
|
||
|
|
|
||
|
|
Products with missing/invalid submission_url_slug are excluded.
|
||
|
|
"""
|
||
|
|
all_products = cls.load_all()
|
||
|
|
active = [p for p in all_products
|
||
|
|
if p.status == 'active' and p.submission_url_slug]
|
||
|
|
return sorted(active, key=lambda p: (p.name.lower(), p.product_id))
|
||
|
|
```
|
||
|
|
|
||
|
|
**Sorting Algorithm**:
|
||
|
|
- Primary sort: Product name (case-insensitive) - ensures alphabetical display
|
||
|
|
- Secondary sort: Product ID - provides stable ordering when names are identical
|
||
|
|
- Uses Python's built-in `sorted()` with tuple key for multi-level sorting
|
||
|
|
|
||
|
|
**Performance Analysis**:
|
||
|
|
- File I/O for 100 products: ~10-50ms (depends on disk speed)
|
||
|
|
- In-memory sorting: <1ms for 100 items
|
||
|
|
- Total expected latency: <100ms (well under 1-second SC-002 target)
|
||
|
|
- No caching needed for MVP (file reads are sufficiently fast)
|
||
|
|
|
||
|
|
**Filtering Logic**:
|
||
|
|
- `status == 'active'`: Per FR-003, only show active products
|
||
|
|
- `submission_url_slug`: Per FR-015, skip products with missing/invalid slugs
|
||
|
|
- Combined with `and` operator: both conditions must be true
|
||
|
|
|
||
|
|
**Alternatives Considered**:
|
||
|
|
|
||
|
|
1. **Sort in route handler**: Simpler but violates DRY if multiple routes need sorted product lists
|
||
|
|
2. **Database query with ORDER BY**: Contradicts file-based architecture decision from 001
|
||
|
|
3. **Pre-sorted cache**: Premature optimization - file reads are fast enough for 100 products
|
||
|
|
4. **Client-side sorting with JavaScript**: Violates no-JavaScript constraint from spec
|
||
|
|
|
||
|
|
**Edge Cases Handled**:
|
||
|
|
- No active products → Returns empty list (handled in template)
|
||
|
|
- Missing submission_url_slug → Product excluded from list (per FR-015)
|
||
|
|
- Identical product names → Sorted by product_id as tiebreaker
|
||
|
|
- Case-insensitive sorting → "Apple" and "apple" sort together
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Validation
|
||
|
|
|
||
|
|
All technical decisions align with:
|
||
|
|
- **FR-002**: Retrieves from file-based storage ✅
|
||
|
|
- **FR-003**: Filters for active status ✅
|
||
|
|
- **FR-009**: Sorts alphabetically with tiebreaker ✅
|
||
|
|
- **FR-015**: Skips invalid submission_url_slug ✅
|
||
|
|
- **SC-002**: <1 second load time for 100 products ✅
|
||
|
|
|
||
|
|
**Next Phase**: Proceed to Phase 1 (data-model.md, contracts, quickstart.md)
|