11 KiB
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
-
1. Database model and migration
-
1.1 Add CallItem model to
app/models.py- Add
CallItemSQLAlchemy 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
- Add
-
1.2 Create Alembic migration
005_add_call_items.py- Create
call_itemstable with all columns, FK constraint, and indexes on id and status - Include
downgrade()that drops the table and indexes - Requirements: 3.1, 3.2
- Create
-
-
2. Phone extractor service
-
2.1 Create
app/services/phone_extractor.py- Implement
extract_phone_number(text: str) -> str | Nonewith regex patterns for: +49 international, 0xxx domestic, (0xxx) parenthesized area code - Implement
normalize_phone_number(raw: str) -> strto strip separators and standardize - Implement
parse_phone_number(text: str) -> dict | Noneto 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
- Implement
-
* 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_numberreturns 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
-
-
3. Call List API routes and service logic
-
3.1 Add Pydantic models for Call List in
app/main.pyCallItemCreate: 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
-
3.2 Implement POST
/api/callsendpoint- If
task_idprovided: load task, extract sender name from description, extract phone number viaphone_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
- If
-
3.3 Implement GET
/api/callsendpoint- Return all CallItems with status="pending", ordered by priority ASC then created_at ASC
- Require session auth
- Requirements: 2.2, 2.5
-
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
-
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
-
-
4. Checkpoint - Ensure all tests pass
- Ensure all tests pass, ask the user if questions arise.
-
5. Frontend: Calls tab and phone icon on tasks
-
5.1 Add "📞 Calls" navigation button to
frontend/index.html- Insert new nav button between "Projects" and "Daily Digest"
- Add
#calls-viewsection with call items list container - Requirements: 5.1
-
5.2 Implement Calls tab view in
frontend/js/app.js- Add
loadCalls()function: fetch GET/api/calls, render list with person, reason, phone (astel: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
- Add
-
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/callswith{ task_id: <id> } - Show confirmation indicator (brief flash or text) on success
- Requirements: 6.1, 6.2, 6.3
- Add phone icon button to existing task action buttons in
-
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
-
-
6. Daily Digest integration
-
6.1 Extend
generate_daily_digest()inapp/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
-
6.2 Render pending calls in Digest frontend view
- In
loadDigest(), render thepending_callssection when present in API response - Display as a "📞 Calls to make" card with person name and reason per item
- Requirements: 7.1, 7.2
- In
-
* 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_callswith 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
-
-
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/callsreturns 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
-
-
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