-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Expand file tree
/
Copy pathadvice.py
More file actions
115 lines (89 loc) · 3.44 KB
/
advice.py
File metadata and controls
115 lines (89 loc) · 3.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import logging
import uuid
from datetime import datetime, timezone
from typing import List, Dict, Any, Optional
from google.cloud import firestore
from ._client import db
logger = logging.getLogger(__name__)
USERS_COLLECTION = 'users'
ADVICE_SUBCOLLECTION = 'advice'
def _collection_ref(uid: str):
return db.collection(USERS_COLLECTION).document(uid).collection(ADVICE_SUBCOLLECTION)
def create_advice(uid: str, data: Dict[str, Any]) -> Dict[str, Any]:
"""Create a new advice document. Returns the created document with id."""
advice_id = str(uuid.uuid4())
now = datetime.now(timezone.utc)
doc_data = {
'content': data['content'],
'category': data.get('category', 'other'),
'confidence': data.get('confidence', 0.5),
'is_read': False,
'is_dismissed': False,
'created_at': now,
}
for optional_field in ('reasoning', 'source_app', 'context_summary', 'current_activity'):
if data.get(optional_field) is not None:
doc_data[optional_field] = data[optional_field]
_collection_ref(uid).document(advice_id).set(doc_data)
doc_data['id'] = advice_id
return doc_data
def get_advice(
uid: str,
limit: int = 100,
offset: int = 0,
category: Optional[str] = None,
include_dismissed: bool = False,
) -> List[Dict[str, Any]]:
"""Query advice, ordered by created_at DESC."""
query = _collection_ref(uid).order_by('created_at', direction=firestore.Query.DESCENDING)
if not include_dismissed:
query = query.where(filter=firestore.FieldFilter('is_dismissed', '==', False))
if category:
query = query.where(filter=firestore.FieldFilter('category', '==', category))
query = query.offset(offset).limit(limit)
results = []
for doc in query.stream():
data = doc.to_dict()
data['id'] = doc.id
results.append(data)
return results
def update_advice(uid: str, advice_id: str, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Update an advice document (is_read, is_dismissed). Returns updated doc."""
doc_ref = _collection_ref(uid).document(advice_id)
update_data = {'updated_at': datetime.now(timezone.utc)}
if 'is_read' in data:
update_data['is_read'] = data['is_read']
if 'is_dismissed' in data:
update_data['is_dismissed'] = data['is_dismissed']
try:
doc_ref.update(update_data)
except Exception as e:
if hasattr(e, 'code') and e.code == 404:
return None
raise
doc = doc_ref.get()
if doc.exists:
result = doc.to_dict()
result['id'] = doc.id
return result
return None
def delete_advice(uid: str, advice_id: str) -> bool:
"""Delete an advice document. Returns True on success."""
_collection_ref(uid).document(advice_id).delete()
return True
def mark_all_advice_read(uid: str) -> int:
"""Mark all unread, non-dismissed advice as read. Returns count of marked items."""
query = _collection_ref(uid).where(
filter=firestore.FieldFilter('is_dismissed', '==', False)
).where(
filter=firestore.FieldFilter('is_read', '==', False)
).limit(1000)
count = 0
now = datetime.now(timezone.utc)
for doc in query.stream():
try:
doc.reference.update({'is_read': True, 'updated_at': now})
count += 1
except Exception:
logger.warning('Failed to mark advice %s as read for uid=%s', doc.id, uid)
return count