feat: tag-aware daily digest - tags in AI prompt, grouping, frontmatter, cross-day continuity
This commit is contained in:
@ -1,12 +1,14 @@
|
||||
"""Memos API client - fetch memos using Connect RPC protocol."""
|
||||
|
||||
import logging
|
||||
from collections import Counter
|
||||
from datetime import datetime, timezone, timedelta
|
||||
|
||||
TZ_BEIJING = timezone(timedelta(hours=8))
|
||||
from re import findall
|
||||
|
||||
import requests
|
||||
|
||||
TZ_BEIJING = timezone(timedelta(hours=8))
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -45,13 +47,11 @@ class MemosClient:
|
||||
def list_memos(self, days=1, page_size=100):
|
||||
"""Fetch memos from the last N days (Beijing time reference) via Connect RPC."""
|
||||
user = self.get_user_id()
|
||||
# Use Beijing time as reference for "last N days"
|
||||
now_bj = datetime.now(TZ_BEIJING)
|
||||
since_bj = now_bj - timedelta(days=days)
|
||||
since_utc = since_bj.astimezone(timezone.utc)
|
||||
since = since_utc.isoformat()
|
||||
|
||||
# Connect RPC uses POST with JSON body
|
||||
payload = {
|
||||
"pageSize": page_size,
|
||||
"filter": f"creator == '{user}'",
|
||||
@ -68,7 +68,6 @@ class MemosClient:
|
||||
memos = data.get("memos", [])
|
||||
logger.info("Memos API OK | fetched %d memos", len(memos))
|
||||
|
||||
# Filter by time client-side (Beijing time reference)
|
||||
results = []
|
||||
for m in memos:
|
||||
created = m.get("createTime", "")
|
||||
@ -88,9 +87,7 @@ class MemosClient:
|
||||
"visibility": m.get("visibility", ""),
|
||||
})
|
||||
|
||||
logger.info(
|
||||
"Filtered to %d memos since %s", len(results), since[:10]
|
||||
)
|
||||
logger.info("Filtered to %d memos since %s", len(results), since[:10])
|
||||
return results
|
||||
|
||||
def list_all_memos_from_range(self, start_date, end_date, page_size=200):
|
||||
@ -110,7 +107,6 @@ class MemosClient:
|
||||
memos = data.get("memos", [])
|
||||
logger.info("Memos API OK | fetched %d memos total", len(memos))
|
||||
|
||||
# Client-side date range filtering
|
||||
results = []
|
||||
for m in memos:
|
||||
created = m.get("createTime", "")
|
||||
@ -136,8 +132,76 @@ class MemosClient:
|
||||
)
|
||||
return results
|
||||
|
||||
def list_memos_by_tag(self, tag, page_size=200):
|
||||
"""Fetch all memos containing a specific tag.
|
||||
|
||||
Useful for retrieving memos across multiple days related to a specific
|
||||
topic (e.g. a training course that spans a full week).
|
||||
"""
|
||||
payload = {"pageSize": page_size}
|
||||
|
||||
resp = self.session.post(
|
||||
f"{self.base_url}/memos.api.v1.MemoService/ListMemos",
|
||||
json=payload,
|
||||
timeout=15
|
||||
)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
memos = data.get("memos", [])
|
||||
|
||||
results = []
|
||||
for m in memos:
|
||||
content_text = m.get("content", "")
|
||||
tags = self._extract_tags(content_text)
|
||||
if tag not in tags:
|
||||
continue
|
||||
|
||||
results.append({
|
||||
"id": m.get("name", "").split("/")[-1],
|
||||
"content": content_text,
|
||||
"created_at": m.get("createTime", ""),
|
||||
"tags": tags,
|
||||
"visibility": m.get("visibility", ""),
|
||||
})
|
||||
|
||||
logger.info("Found %d memos with tag #%s", len(results), tag)
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def aggregate_tags(memos):
|
||||
"""Count tag frequency across a list of memos.
|
||||
|
||||
Returns dict of {tag: count}, sorted by frequency descending.
|
||||
"""
|
||||
counter = Counter()
|
||||
for m in memos:
|
||||
for tag in m.get("tags", []):
|
||||
counter[tag] += 1
|
||||
return dict(counter.most_common())
|
||||
|
||||
@staticmethod
|
||||
def group_memos_by_tag(memos):
|
||||
"""Group memos by their tags.
|
||||
|
||||
Returns dict of {tag: [memo_list]}, with "无标签" key for memos
|
||||
without any tags. A memo can appear under multiple tags.
|
||||
"""
|
||||
groups = {}
|
||||
untagged = []
|
||||
for m in memos:
|
||||
tags = m.get("tags", [])
|
||||
if not tags:
|
||||
untagged.append(m)
|
||||
continue
|
||||
for tag in tags:
|
||||
if tag not in groups:
|
||||
groups[tag] = []
|
||||
groups[tag].append(m)
|
||||
if untagged:
|
||||
groups["无标签"] = untagged
|
||||
return groups
|
||||
|
||||
@staticmethod
|
||||
def _extract_tags(content):
|
||||
"""Extract #tags from memo content."""
|
||||
import re
|
||||
return re.findall(r"#(\w[\w\-]*)", content)
|
||||
return findall(r"#(\w[\w\-]*)", content)
|
||||
|
||||
Reference in New Issue
Block a user