Initial monorepo structure

This commit is contained in:
2026-06-30 20:37:40 +02:00
commit 2f2b295531
121 changed files with 39171 additions and 0 deletions
+194
View File
@@ -0,0 +1,194 @@
# Implementation Plan: Call List Feature
## Overview
This plan implements the Call List feature for OrgMyLife — a dedicated phone-call tracking system with a new `CallItem` model, CRUD API, phone number extraction service, frontend "📞 Calls" tab, and Daily Digest integration. Tasks are ordered so each step builds on the previous, with no orphaned code.
## Tasks
- [x] 1. Database model and migration
- [x] 1.1 Add CallItem model to `app/models.py`
- Add `CallItem` SQLAlchemy class with fields: id, person, reason, phone_number, original_task_id (FK → tasks.id), priority, status, created_at
- Follow existing model patterns (Column types, server_default for timestamps)
- _Requirements: 3.1, 3.2, 3.3_
- [x] 1.2 Create Alembic migration `005_add_call_items.py`
- Create `call_items` table with all columns, FK constraint, and indexes on id and status
- Include `downgrade()` that drops the table and indexes
- _Requirements: 3.1, 3.2_
- [x] 2. Phone extractor service
- [x] 2.1 Create `app/services/phone_extractor.py`
- Implement `extract_phone_number(text: str) -> str | None` with regex patterns for: +49 international, 0xxx domestic, (0xxx) parenthesized area code
- Implement `normalize_phone_number(raw: str) -> str` to strip separators and standardize
- Implement `parse_phone_number(text: str) -> dict | None` to decompose into country_code, area_code, subscriber
- Return first match when multiple numbers found; return None when no match
- _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5_
- [ ]* 2.2 Write property test: Phone number extraction (Property 2)
- **Property 2: Phone number extraction from German formats**
- Generate random valid German phone numbers in +49, 0xxx, and (0xxx) formats embedded in arbitrary text
- Assert `extract_phone_number` returns a non-null result matching the first occurrence
- Use Hypothesis with `@settings(max_examples=100)`
- Place in `OrgMyLife/tests/test_phone_extractor.py`
- **Validates: Requirements 4.1, 4.2, 4.3, 4.4**
- [ ]* 2.3 Write property test: Phone number round-trip (Property 3)
- **Property 3: Phone number parse-format round-trip**
- Generate valid German phone number strings, parse → normalize → parse again
- Assert the two parse results are equivalent
- Use Hypothesis with `@settings(max_examples=100)`
- Place in `OrgMyLife/tests/test_phone_extractor.py`
- **Validates: Requirements 4.6**
- [x] 3. Call List API routes and service logic
- [x] 3.1 Add Pydantic models for Call List in `app/main.py`
- `CallItemCreate`: task_id (optional), person (optional), reason (optional), phone_number (optional), priority (default 3)
- `CallItemUpdate`: phone_number, priority, status, resolve_task (bool)
- `CallItemResponse`: id, person, reason, phone_number, original_task_id, priority, status, created_at
- Implement field truncation (person≤200, reason≤500, phone_number≤30) and priority clamping [1,4] in `model_post_init`
- _Requirements: 9.1, 9.2, 9.3, 9.4, 9.5, 9.6_
- [x] 3.2 Implement POST `/api/calls` endpoint
- If `task_id` provided: load task, extract sender name from description, extract phone number via `phone_extractor`, create CallItem with person=sender, reason=task.title, original_task_id=task.id
- If no `task_id`: require person and reason fields, return 422 if missing
- Assign default priority=3, status="pending"
- Require session auth (401 if unauthenticated)
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 2.1, 2.5_
- [x] 3.3 Implement GET `/api/calls` endpoint
- Return all CallItems with status="pending", ordered by priority ASC then created_at ASC
- Require session auth
- _Requirements: 2.2, 2.5_
- [x] 3.4 Implement PUT `/api/calls/{id}` endpoint
- Update specified fields (phone_number, priority, status)
- Validate status is "pending" or "done" only; reject others with 400
- If status="done" and resolve_task=true and original_task_id exists: mark original task as "completed" and trigger existing side effects (email archiving, Nextcloud sync)
- If status="done" and resolve_task=false: only update CallItem
- Return 404 if ID not found; require session auth
- _Requirements: 2.3, 2.5, 2.6, 3.3, 8.1, 8.2, 8.3_
- [x] 3.5 Implement DELETE `/api/calls/{id}` endpoint
- Remove CallItem from database
- Return 404 if ID not found; require session auth
- _Requirements: 2.4, 2.5, 2.6_
- [ ]* 3.6 Write property test: Field truncation (Property 5)
- **Property 5: Field length truncation**
- Generate strings longer than max lengths for person, reason, phone_number
- Assert stored values are truncated to 200, 500, 30 chars respectively and equal the first N chars of input
- Use Hypothesis with `@settings(max_examples=100)`
- Place in `OrgMyLife/tests/test_call_list.py`
- **Validates: Requirements 9.1, 9.2, 9.3**
- [ ]* 3.7 Write property test: Priority clamping (Property 6)
- **Property 6: Priority clamping**
- Generate arbitrary integers, assert stored priority is clamped to [1, 4]
- Use Hypothesis with `@settings(max_examples=100)`
- Place in `OrgMyLife/tests/test_call_list.py`
- **Validates: Requirements 9.4**
- [ ]* 3.8 Write property test: Status constraint (Property 7)
- **Property 7: Status constraint**
- Generate arbitrary status strings, assert only "pending" and "done" are accepted
- Use Hypothesis with `@settings(max_examples=100)`
- Place in `OrgMyLife/tests/test_call_list.py`
- **Validates: Requirements 3.3**
- [x] 4. Checkpoint - Ensure all tests pass
- Ensure all tests pass, ask the user if questions arise.
- [x] 5. Frontend: Calls tab and phone icon on tasks
- [x] 5.1 Add "📞 Calls" navigation button to `frontend/index.html`
- Insert new nav button between "Projects" and "Daily Digest"
- Add `#calls-view` section with call items list container
- _Requirements: 5.1_
- [x] 5.2 Implement Calls tab view in `frontend/js/app.js`
- Add `loadCalls()` function: fetch GET `/api/calls`, render list with person, reason, phone (as `tel:` link), priority badge
- Add "Mark Done" button per item (sends PUT with status="done", prompts about resolving original task if original_task_id exists)
- Add "Delete" button per item (sends DELETE)
- Add inline edit for phone_number field (sends PUT on blur/enter)
- Order items by priority then creation date
- Wire "📞 Calls" nav button to `switchTab("calls")`
- _Requirements: 5.2, 5.3, 5.4, 5.5, 5.6, 5.7_
- [x] 5.3 Add 📞 icon button to task items in Task List view
- Add phone icon button to existing task action buttons in `loadAllTasks()` render
- On click: send POST `/api/calls` with `{ task_id: <id> }`
- Show confirmation indicator (brief flash or text) on success
- _Requirements: 6.1, 6.2, 6.3_
- [x] 5.4 Add CSS styles for Calls view in `frontend/css/style.css`
- Style call item cards, tel: links, inline phone edit, priority badges
- Match existing card/list styling patterns
- _Requirements: 5.2, 5.3_
- [x] 6. Daily Digest integration
- [x] 6.1 Extend `generate_daily_digest()` in `app/services/digest.py`
- Import CallItem model
- Query all CallItems with status="pending"
- Add `"pending_calls"` section to response dict with person and reason for each item
- Omit section if no pending calls exist
- _Requirements: 7.1, 7.2, 7.3_
- [x] 6.2 Render pending calls in Digest frontend view
- In `loadDigest()`, render the `pending_calls` section when present in API response
- Display as a "📞 Calls to make" card with person name and reason per item
- _Requirements: 7.1, 7.2_
- [ ]* 6.3 Write property test: Digest includes all pending calls (Property 8)
- **Property 8: Digest includes all pending calls**
- Generate a non-empty set of pending CallItems, run `generate_daily_digest`
- Assert response contains `pending_calls` with an entry for each pending item (person + reason)
- Use Hypothesis with `@settings(max_examples=100)`
- Place in `OrgMyLife/tests/test_call_digest.py`
- **Validates: Requirements 7.1, 7.2**
- [x] 7. Create-from-task logic and completion flow tests
- [ ]* 7.1 Write property test: Create-from-task field mapping (Property 1)
- **Property 1: Create-from-task field mapping**
- Generate tasks with origin "gmail"/"dhive_email" and description starting with "From: {name}"
- Assert created CallItem has: person=extracted sender, reason=task.title, original_task_id=task.id, priority=3, status="pending"
- Use Hypothesis with `@settings(max_examples=100)`
- Place in `OrgMyLife/tests/test_call_list.py`
- **Validates: Requirements 1.1, 1.2, 1.3, 1.5**
- [ ]* 7.2 Write property test: Call list ordering (Property 4)
- **Property 4: Call list ordering**
- Generate sets of pending CallItems with varying priorities and creation dates
- Assert GET `/api/calls` returns items sorted by priority ASC, then created_at ASC
- Use Hypothesis with `@settings(max_examples=100)`
- Place in `OrgMyLife/tests/test_call_list.py`
- **Validates: Requirements 2.2**
- [ ]* 7.3 Write property test: Completion without task resolution (Property 9)
- **Property 9: Call completion without task resolution**
- Generate CallItems with original_task_id, mark as done with resolve_task=false
- Assert only CallItem status changes; original task status remains unchanged
- Use Hypothesis with `@settings(max_examples=100)`
- Place in `OrgMyLife/tests/test_call_list.py`
- **Validates: Requirements 8.3**
- [ ]* 7.4 Write unit tests for API edge cases
- Test 401 for unauthenticated requests to all /api/calls endpoints
- Test 404 for non-existent call item IDs
- Test 422 for POST without person/reason (when no task_id)
- Test DELETE removes item from database
- Test phone extractor returns None for text without numbers
- Place in `OrgMyLife/tests/test_call_list.py`
- _Requirements: 2.5, 2.6, 4.5, 9.5, 9.6_
- [x] 8. Final checkpoint - Ensure all tests pass
- Ensure all tests pass, ask the user if questions arise.
## Notes
- Tasks marked with `*` are optional and can be skipped for faster MVP
- Each task references specific requirements for traceability
- Checkpoints ensure incremental validation
- Property tests validate universal correctness properties from the design document (Hypothesis, min 100 examples)
- Unit tests validate specific examples and edge cases
- The project uses Python (FastAPI + SQLAlchemy + Alembic) for backend and vanilla HTML/CSS/JS for frontend
- Test files: `tests/test_phone_extractor.py`, `tests/test_call_list.py`, `tests/test_call_digest.py`