daily folder structure: each date gets its own directory

This commit is contained in:
Beast
2026-06-13 09:48:49 +08:00
parent 75dbbee7cc
commit a921c40dba
8 changed files with 581 additions and 131 deletions

View File

@ -1,12 +1,12 @@
"""Daily digest analyzer - fetches today's memos and generates AI summary.""" """Daily digest analyzer - fetches today's memos and generates AI analysis."""
import json import json
import logging import logging
import os import os
import sys import sys
from datetime import datetime, timezone import subprocess
from datetime import datetime, timezone, timedelta
# Add parent dir to path for local imports
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from tools.config import load_secrets, get_output_dir from tools.config import load_secrets, get_output_dir
@ -16,40 +16,157 @@ from tools.formatter import format_daily_digest
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# System prompt for daily digest TZ_BEIJING = timezone(timedelta(hours=8))
DAILY_SYSTEM_PROMPT = """你是一个灵感整理助手。你的任务是将用户零散的灵感记录进行智能整理。 PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
请严格按照要求整理,输出纯 JSON不要代码块标记不要额外说明
{
"summary": "一段简短的今日灵感总结50字以内",
"categories": {
"分类名1": ["具体灵感项1", "具体灵感项2"],
"分类名2": ["具体灵感项3"]
},
"connections": ["跨主题关联发现1", "2-3条"],
"todos": ["待办事项1", "2-3条"]
}
分类原则根据内容自然归类分类名不超过4个字。
关联发现:识别不同灵感之间的关联、冲突或可串联的主题。
待办:从灵感中提取可执行的事项。"""
def parse_ai_response(text): def utc_to_beijing(ts_str):
"""Parse AI response, handling both pure JSON and code-block wrapped JSON.""" """Convert UTC timestamp string (ISO 8601 with Z) to Beijing time string."""
text = text.strip() try:
# Remove markdown code block wrappers if present dt = datetime.fromisoformat(ts_str.replace("Z", "+00:00"))
if text.startswith("```"): dt_bj = dt.astimezone(TZ_BEIJING)
lines = text.split("\n") return dt_bj.strftime("%Y-%m-%d %H:%M")
# Remove first and last ``` lines except (ValueError, AttributeError):
if lines[0].startswith("```"): return ts_str[:16].replace("T", " ") + " (UTC?)"
lines = lines[1:]
if lines and lines[-1].strip() == "```":
lines = lines[:-1]
text = "\n".join(lines).strip()
return json.loads(text)
def read_previous_digest(date):
"""Read the most recent previous daily digest (if exists) for continuity."""
daily_root = get_output_dir("daily")
# Try yesterday first, then go back up to 7 days
for days_back in range(1, 8):
prev_date = date - timedelta(days=days_back)
prev_dir = os.path.join(daily_root, prev_date.strftime("%Y-%m-%d"))
if not os.path.isdir(prev_dir):
continue
files = sorted([f for f in os.listdir(prev_dir) if f.endswith(".md")], reverse=True)
if not files:
continue
prev_file = os.path.join(prev_dir, files[0])
with open(prev_file, "r", encoding="utf-8") as f:
content = f.read()
# Extract just the AI analysis section, skip frontmatter
ai_section_start = content.find("## AI 分析")
if ai_section_start != -1:
# Find the "我的批注" section boundary
annotation_start = content.find("## 我的批注", ai_section_start)
if annotation_start != -1:
ai_section = content[ai_section_start:annotation_start].strip()
else:
ai_section = content[ai_section_start:].strip()
logger.info(
"Found previous digest from %s (%d chars)",
prev_date.strftime("%Y-%m-%d"), len(ai_section)
)
return prev_date, ai_section
else:
# No AI section found, return whole file
logger.info(
"Found previous digest from %s (no AI section found, using full)",
prev_date.strftime("%Y-%m-%d")
)
return prev_date, content
logger.info("No previous digest found in the last 7 days")
return None, None
# ============================================================
# System prompt for daily analysis
# ============================================================
DAILY_SYSTEM_PROMPT = """你是一个私人思考伙伴。你的任务是认真阅读用户今天的每一条灵感记录,结合之前的分析,写一篇有深度的分析文章。
核心原则:不做缩写,不做分类表,不写空话。
具体要求:
1. **引用原文**:分析每一条灵感时,必须先引用(或精炼复述)用户的原文,让用户一眼就知道"哦我在说这个"。引用要压缩但不失原意,不能断章取义。
2. **融会贯通,而非逐条罗列**:不要"第一条...第二条...第三条..."机械堆砌。要把所有灵感当作一个整体来思考——哪几条在说同一个主题?哪几条看似无关实则互补?把它们串起来写。文章是流淌的整体,不是并列的零件。
3. **承接延续之前的分析**:你会看到上一次的分析内容。今天的分析不能只写"今天的事",要把今天的灵感和昨天的分析结合起来——昨天讨论了什么?今天有什么进展?哪些问题有了答案?哪些问题还在延续?要让文章有跨日的时间纵深感。
4. **逐渐深入,有节奏感**:从表面现象往下挖。先指出用户说了什么,再追问为什么,再展开你的洞察。每一段都要比上一段深一层。
5. **有实质内容**:不要写"这是一个有价值的思考"这种废话。要说清楚为什么有价值,值在哪里,用户可以从这个方向挖到什么。
6. **关联要真实**:如果多条灵感确实指向同一个主题,就写一段贯通的分析。不要硬凑关联。
7. **待办要锋利**:待办事项不是"调研X""优化Y"这种万金油。要写到"具体做什么、什么时机做、做到什么程度"的颗粒度。
输出格式:纯 Markdown不要代码块包裹不要 JSON。
文章结构参考(不是模板,不必严格遵守):
### 1. 今日概览
一两句话点出今天思考的主旋律,并与之前的内容形成呼应。
### 2. 深入分析
融会贯通地写。引用原文要自然嵌入行文。注意与上次分析的承接关系。
### 3. 待办事项
具体、可执行的事项列表。
---
注意即便只有1条灵感也要写出深度。宁可写长不可简略。"""
def git_push(digest_file):
"""Commit and push the digest file to Gitea."""
try:
# git add
result = subprocess.run(
["git", "add", digest_file],
cwd=PROJECT_DIR,
capture_output=True, text=True, timeout=15
)
if result.returncode != 0:
logger.warning("git add failed: %s", result.stderr.strip())
return False
# git commit (check for changes first)
result = subprocess.run(
["git", "status", "--porcelain", "ai-insights/"],
cwd=PROJECT_DIR,
capture_output=True, text=True, timeout=10
)
if not result.stdout.strip():
logger.info("No changes to commit")
return True
date_str = datetime.now(TZ_BEIJING).strftime("%Y-%m-%d %H:%M")
result = subprocess.run(
["git", "commit", "-m", "daily digest " + date_str],
cwd=PROJECT_DIR,
capture_output=True, text=True, timeout=15
)
if result.returncode != 0 and "nothing to commit" not in result.stdout:
logger.warning("git commit failed: %s", result.stderr.strip())
return False
# git push
result = subprocess.run(
["git", "push", "origin", "main"],
cwd=PROJECT_DIR,
capture_output=True, text=True, timeout=30
)
if result.returncode != 0:
logger.warning("git push failed: %s", result.stderr.strip())
return False
# Extract remote result line
for line in result.stdout.split("\n"):
if "->" in line or "remote:" in line:
logger.info("Gitea push: %s", line.strip())
logger.info("Pushed to Gitea successfully")
return True
except subprocess.TimeoutExpired:
logger.error("git push timed out")
return False
except Exception as e:
logger.error("git push error: %s", e)
return False
def run(memos_client, llm_client, date=None): def run(memos_client, llm_client, date=None):
@ -63,82 +180,73 @@ def run(memos_client, llm_client, date=None):
if not memos: if not memos:
logger.info("No memos today, skipping") logger.info("No memos today, skipping")
# Still generate a minimal file content = format_daily_digest(date, ai_body="今天没有记录灵感。")
content = format_daily_digest( date_str = date.strftime("%Y-%m-%d")
date=date, daily_dir = os.path.join(get_output_dir("daily"), date_str)
categories={}, os.makedirs(daily_dir, exist_ok=True)
summary="今天没有记录灵感。", now_str = datetime.now(TZ_BEIJING).strftime("%H%M%S")
connections=[], filename = date_str + "_digest_" + now_str + ".md"
todos=[] filepath = os.path.join(daily_dir, filename)
)
output_dir = get_output_dir("daily")
filename = f"{date.strftime('%Y-%m-%d')}_digest.md"
filepath = os.path.join(output_dir, filename)
with open(filepath, "w", encoding="utf-8") as f: with open(filepath, "w", encoding="utf-8") as f:
f.write(content) f.write(content)
logger.info("Empty digest written to %s", filepath) logger.info("Empty digest written to %s", filepath)
# Still push
git_push(filepath)
return filepath, 0 return filepath, 0
# Step 2: Prepare prompt # Step 2: Prepare user prompt
memo_texts = [] memo_texts = []
for m in memos: for m in memos:
time_str = m["created_at"][:16].replace("T", " ") time_bj = utc_to_beijing(m["created_at"])
memo_texts.append(f"[{time_str}] {m['content']}") memo_texts.append("- **[" + time_bj + "]** " + m["content"].strip())
user_prompt = f"请分析以下灵感记录:\n\n" + "\n".join(memo_texts) user_prompt = "以下是我今天的灵感记录:\n\n" + "\n\n".join(memo_texts)
# Step 3: Call DeepSeek API # Step 3: Include previous analysis for continuity
prev_date, prev_analysis = read_previous_digest(date)
if prev_analysis:
user_prompt += (
"\n\n---\n\n"
"以下是我上一次的分析内容("
+ prev_date.strftime("%Y-%m-%d") + ""
"请结合今天的灵感一起思考,保持连续性:\n\n"
+ prev_analysis
)
user_prompt += "\n\n---\n\n请基于以上所有素材,写一篇有深度的分析文章。"
# Step 4: Call DeepSeek API
raw_response = llm_client.ask( raw_response = llm_client.ask(
system_prompt=DAILY_SYSTEM_PROMPT, system_prompt=DAILY_SYSTEM_PROMPT,
user_prompt=user_prompt, user_prompt=user_prompt,
temperature=0.3 temperature=0.5
) )
# Step 4: Parse and format # Step 5: Wrap with frontmatter and write
try: content = format_daily_digest(date, ai_body=raw_response)
data = parse_ai_response(raw_response)
except (json.JSONDecodeError, KeyError) as e:
logger.error("Failed to parse AI response: %s", e)
logger.error("Raw response: %s", raw_response[:200])
data = {
"summary": f"AI 解析失败,请查看原始 Memos。",
"categories": {"未分类": [m["content"] for m in memos]},
"connections": [],
"todos": []
}
categories = data.get("categories", {}) date_str = date.strftime("%Y-%m-%d")
connections = data.get("connections", []) daily_dir = os.path.join(get_output_dir("daily"), date_str)
todos = data.get("todos", []) os.makedirs(daily_dir, exist_ok=True)
summary = data.get("summary", "") now_str = datetime.now(TZ_BEIJING).strftime("%H%M%S")
filename = date_str + "_digest_" + now_str + ".md"
# Step 5: Format and write output filepath = os.path.join(daily_dir, filename)
content = format_daily_digest(
date=date,
categories=categories,
summary=summary,
connections=connections,
todos=todos
)
output_dir = get_output_dir("daily")
filename = f"{date.strftime('%Y-%m-%d')}_digest.md"
filepath = os.path.join(output_dir, filename)
with open(filepath, "w", encoding="utf-8") as f: with open(filepath, "w", encoding="utf-8") as f:
f.write(content) f.write(content)
logger.info( logger.info(
"Daily digest written to %s | %d memos | %d categories", "Daily digest written to %s | %d memos | %d chars analysis",
filepath, len(memos), len(categories) filepath, len(memos), len(raw_response)
) )
# Step 6: Auto-push to Gitea
git_push(filepath)
return filepath, len(memos) return filepath, len(memos)
def main(): def main():
"""CLI entry point."""
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s" format="%(asctime)s [%(levelname)s] %(name)s: %(message)s"
@ -161,7 +269,7 @@ def main():
) )
filepath, count = run(memos_client, llm_client) filepath, count = run(memos_client, llm_client)
print(f"Done: {filepath} ({count} memos)") print("Done: " + filepath + " (" + str(count) + " memos)")
if __name__ == "__main__": if __name__ == "__main__":

159
analyzers/first_panorama.py Normal file
View File

@ -0,0 +1,159 @@
"""First panorama analysis - reads entire Obsidian vault and generates AI analysis."""
import logging
import os
import sys
import subprocess
from datetime import datetime, timezone, timedelta
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from tools.config import load_secrets, get_output_dir
from tools.llm import DeepSeekClient
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s")
logger = logging.getLogger(__name__)
TZ_BEIJING = timezone(timedelta(hours=8))
PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
VAULT_DIR = "/home/ubuntu/obsidian-vault"
PANORAMA_PROMPT = """你是一个私人思考伙伴。现在,用户将他的整个 Obsidian 文库交给你。
你的任务:读完这个文库的目录结构和关键内容,写一篇全景分析文章。
核心原则:
1. **不要缩写,不要分类表,不要空话。**
2. **引用原文。** 分析中必须引用或精炼复述用户的原文。
3. **融会贯通。** 不按目录逐条罗列,而是找出贯穿的主题。
4. **有实质内容。** 说清楚你看出了什么模式,用户自己可能没有意识到什么。
5. **标题要诗意。** 这是一篇有仪式感的文章——第一次,有人完整阅读了另一个人的思想合集。
输出格式:纯 Markdown。
文章结构参考:
### 序章
### 上卷:你是谁
### 中卷:你的系统此时的样子
### 下卷:从这里你可以走向哪里
### 尾声
文库有602篇笔记主题涵盖投资交易101篇、阅读学习189篇、日记日志128篇、工作37篇、平台配置59篇、写作21篇、时间容器22篇等。"""
def build_vault_overview():
"""Build a structured overview of the vault."""
if not os.path.exists(VAULT_DIR):
return "Obsidian vault not found at " + VAULT_DIR
lines = []
lines.append("## 文库目录结构")
lines.append("")
for d in sorted(os.listdir(VAULT_DIR)):
dpath = os.path.join(VAULT_DIR, d)
if not os.path.isdir(dpath) or d.startswith("."):
continue
count = 0
for root, dirs, files in os.walk(dpath):
for f in files:
if f.endswith(".md"):
count += 1
lines.append("- " + d + "" + str(count) + "")
lines.append("")
lines.append("---")
lines.append("")
sections_to_read = [
("01 写作/随笔", 3),
("03 日志/日省录", 2),
("03 日志/交易日志", 2),
("00 Inbox", 5),
("05 投资交易", 5),
("02 阅读学习/微信读书阅读摘录", 3),
]
for section, max_files in sections_to_read:
section_path = os.path.join(VAULT_DIR, section)
if not os.path.exists(section_path):
continue
lines.append("## 来自 " + section)
lines.append("")
files = [f for f in os.listdir(section_path) if f.endswith(".md")]
files.sort()
for fname in files[:max_files]:
fpath = os.path.join(section_path, fname)
try:
with open(fpath, "r", encoding="utf-8", errors="ignore") as f:
content = f.read()
if content.startswith("---"):
idx = content.find("---", 3)
if idx != -1:
content = content[idx + 3:].strip()
if len(content) > 1200:
content = content[:1200] + "...\n[截断,全文" + str(len(content)) + "字]"
lines.append("### " + fname)
lines.append("")
lines.append(content)
lines.append("")
except Exception as e:
lines.append("### " + fname + " (error: " + str(e) + ")")
lines.append("")
return "\n".join(lines)
def git_push():
"""Commit and push to Gitea."""
try:
subprocess.run(["git", "add", "ai-insights/daily/"], cwd=PROJECT_DIR, capture_output=True, timeout=15)
result = subprocess.run(["git", "status", "--porcelain", "ai-insights/"], cwd=PROJECT_DIR, capture_output=True, text=True, timeout=10)
if not result.stdout.strip():
logger.info("No changes to commit")
return True
date_str = datetime.now(TZ_BEIJING).strftime("%Y-%m-%d %H:%M")
subprocess.run(["git", "commit", "-m", "panorama " + date_str], cwd=PROJECT_DIR, capture_output=True, timeout=15)
subprocess.run(["git", "push", "origin", "main"], cwd=PROJECT_DIR, capture_output=True, timeout=30)
logger.info("Pushed to Gitea")
return True
except Exception as e:
logger.error("git push error: %s", e)
return False
def main():
secrets = load_secrets()
llm = DeepSeekClient(
api_key=secrets["deepseek_api_key"],
model=secrets.get("deepseek_model", "deepseek-chat"),
temperature=0.6
)
logger.info("Building vault overview...")
overview = build_vault_overview()
logger.info("Vault overview built: %d chars", len(overview))
user_prompt = "以下是我的完整 Obsidian 文库,请认真阅读并写一篇全景分析文章:\n\n" + overview
logger.info("Calling DeepSeek API for panorama analysis...")
result = llm.ask(system_prompt=PANORAMA_PROMPT, user_prompt=user_prompt)
now = datetime.now(TZ_BEIJING)
content = "---\ndate: " + now.strftime("%Y-%m-%d") + "\ntype: panorama\ntags: [全景分析, 文库初析]\n---\n\n"
content += result
output_dir = get_output_dir("daily")
filename = "01_千川赴海_全景分析_AI版.md"
filepath = os.path.join(output_dir, filename)
with open(filepath, "w", encoding="utf-8") as f:
f.write(content)
logger.info("Written: %s (%d chars)", filepath, len(result))
print("Done: " + filepath + " (" + str(len(result)) + " chars)")
git_push()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,169 @@
"""First panorama analysis - reads entire Obsidian vault and generates AI analysis (v2)."""
import logging
import os
import sys
import subprocess
from datetime import datetime, timezone, timedelta
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from tools.config import load_secrets, get_output_dir
from tools.llm import DeepSeekClient
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s")
logger = logging.getLogger(__name__)
TZ_BEIJING = timezone(timedelta(hours=8))
PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
VAULT_DIR = "/home/ubuntu/obsidian-vault"
PANORAMA_PROMPT = """你是一个私人思考伙伴。现在,用户将他的整个 Obsidian 文库交给你。
你的任务:读完这个文库的目录结构和关键内容,写一篇全景分析文章。
核心原则:
1. **不要缩写,不要分类表,不要空话。**
2. **引用原文。** 分析中必须引用或精炼复述用户的原文。
3. **融会贯通。** 不按目录逐条罗列,而是找出贯穿的主题。
4. **有实质内容。** 说清楚你看出了什么模式,用户自己可能没有意识到什么。
5. **标题要诗意。** 这是一篇有仪式感的文章。
重要全文不少于7000字中文汉字。宁可写长不可简略。
文库有602篇笔记素材充足请充分利用。
输出格式:纯 Markdown。
文章结构参考:
### 序章
### 上卷:你是谁
### 中卷:你的系统此时的样子
### 下卷:从这里你可以走向哪里
### 尾声"""
def build_vault_overview():
"""Build a structured overview of the vault with more content."""
if not os.path.exists(VAULT_DIR):
return "Obsidian vault not found at " + VAULT_DIR
lines = []
lines.append("## 文库目录结构")
lines.append("")
for d in sorted(os.listdir(VAULT_DIR)):
dpath = os.path.join(VAULT_DIR, d)
if not os.path.isdir(dpath) or d.startswith("."):
continue
count = 0
for root, dirs, files in os.walk(dpath):
for f in files:
if f.endswith(".md"):
count += 1
lines.append("- " + d + "" + str(count) + "")
# List first few files
for root, dirs, files in os.walk(dpath):
for f in sorted(files)[:5]:
if f.endswith(".md"):
lines.append(" - " + f)
break
lines.append("")
lines.append("---")
lines.append("")
# Read more sections with more files
sections_to_read = [
("01 写作/随笔", 5),
("03 日志/日省录", 2),
("03 日志/交易日志", 2),
("03 日志/散步杂记", 1),
("00 Inbox", 8),
("05 投资交易", 8),
("02 阅读学习/微信读书阅读摘录", 5),
("07 时间容器", 3),
]
for section, max_files in sections_to_read:
section_path = os.path.join(VAULT_DIR, section)
if not os.path.exists(section_path):
continue
lines.append("## 来自 " + section)
lines.append("")
files = [f for f in os.listdir(section_path) if f.endswith(".md")]
files.sort()
for fname in files[:max_files]:
fpath = os.path.join(section_path, fname)
try:
with open(fpath, "r", encoding="utf-8", errors="ignore") as f:
content = f.read()
if content.startswith("---"):
idx = content.find("---", 3)
if idx != -1:
content = content[idx + 3:].strip()
if len(content) > 1500:
content = content[:1500] + "...\n[截断,全文" + str(len(content)) + "字]"
lines.append("### " + fname)
lines.append("")
lines.append(content)
lines.append("")
except Exception as e:
lines.append("### " + fname + " (error: " + str(e) + ")")
lines.append("")
return "\n".join(lines)
def git_push():
"""Commit and push to Gitea."""
try:
subprocess.run(["git", "add", "ai-insights/daily/"], cwd=PROJECT_DIR, capture_output=True, timeout=15)
result = subprocess.run(["git", "status", "--porcelain", "ai-insights/"], cwd=PROJECT_DIR, capture_output=True, text=True, timeout=10)
if not result.stdout.strip():
logger.info("No changes to commit")
return True
date_str = datetime.now(TZ_BEIJING).strftime("%Y-%m-%d %H:%M")
subprocess.run(["git", "commit", "-m", "panorama v2 " + date_str], cwd=PROJECT_DIR, capture_output=True, timeout=15)
subprocess.run(["git", "push", "origin", "main"], cwd=PROJECT_DIR, capture_output=True, timeout=30)
logger.info("Pushed to Gitea")
return True
except Exception as e:
logger.error("git push error: %s", e)
return False
def main():
secrets = load_secrets()
llm = DeepSeekClient(
api_key=secrets["deepseek_api_key"],
model=secrets.get("deepseek_model", "deepseek-chat"),
temperature=0.6
)
logger.info("Building vault overview...")
overview = build_vault_overview()
logger.info("Vault overview built: %d chars", len(overview))
user_prompt = "以下是我的完整 Obsidian 文库602篇笔记请基于所有素材写一篇不少于7000字的全景分析\n\n" + overview
logger.info("Calling DeepSeek API for panorama analysis v2...")
result = llm.ask(system_prompt=PANORAMA_PROMPT, user_prompt=user_prompt)
now = datetime.now(TZ_BEIJING)
content = "---\ndate: " + now.strftime("%Y-%m-%d") + "\ntype: panorama\ntags: [全景分析, 文库初析, v2]\n---\n\n"
content += result
output_dir = get_output_dir("daily")
filename = "02_千川赴海_全景分析_v2.md"
filepath = os.path.join(output_dir, filename)
with open(filepath, "w", encoding="utf-8") as f:
f.write(content)
logger.info("Written: %s (%d chars)", filepath, len(result))
print("Done: " + filepath + " (" + str(len(result)) + " chars)")
git_push()
if __name__ == "__main__":
main()

45
tools/deepseek_balance.py Normal file
View File

@ -0,0 +1,45 @@
"""Fetch DeepSeek API balance and save to nav page data directory."""
import json
import os
import requests
from datetime import datetime, timezone, timedelta
API_KEY = "sk-bbca4a0380d549389f0d27cdea0b5228"
OUTPUT = "/var/www/nav/data/balance.json"
tz = timezone(timedelta(hours=8))
now = datetime.now(tz).strftime("%Y-%m-%d %H:%M")
try:
resp = requests.get(
"https://api.deepseek.com/user/balance",
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=10,
)
if resp.status_code == 200:
data = resp.json()
info = data.get("balance_infos", [{}])[0]
result = {
"available": data.get("is_available", False),
"total_balance": info.get("total_balance", "0.00"),
"granted_balance": info.get("granted_balance", "0.00"),
"topped_up_balance": info.get("topped_up_balance", "0.00"),
"currency": info.get("currency", "CNY"),
"updated_at": now,
"status": "ok",
}
else:
result = {
"status": "error",
"message": f"HTTP {resp.status_code}",
"updated_at": now,
}
except Exception as e:
result = {"status": "error", "message": str(e), "updated_at": now}
os.makedirs(os.path.dirname(OUTPUT), exist_ok=True)
with open(OUTPUT, "w") as f:
json.dump(result, f, ensure_ascii=False, indent=2)
print(f"Written: {result.get('total_balance', 'error')}")

View File

@ -1,55 +1,26 @@
"""Markdown formatter - converts AI analysis results to beautiful .md files.""" """Markdown formatter -- wraps AI-generated body with frontmatter and annotations."""
from datetime import datetime from datetime import datetime
def format_daily_digest(date, categories, summary, connections, todos): def format_daily_digest(date, ai_body):
"""Format daily digest markdown file.""" """Wrap AI-generated Markdown body with frontmatter."""
date_str = date.strftime("%Y-%m-%d") date_str = date.strftime("%Y-%m-%d")
weekday = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"][date.weekday()] weekday = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"][date.weekday()]
lines = [] lines = []
lines.append("---") lines.append("---")
lines.append(f"date: {date_str}") lines.append("date: " + date_str)
lines.append("type: daily-digest") lines.append("type: daily-digest")
lines.append("tags: [灵感收集器, 每日总结]") lines.append("tags: [灵感收集器, 每日总结]")
lines.append("---") lines.append("---")
lines.append("") lines.append("")
lines.append(f"# {date_str} 灵感摘要 · {weekday}") lines.append("# " + date_str + " 灵感摘要 · " + weekday)
lines.append("") lines.append("")
# Summary
lines.append("## AI 分析(自动生成,请勿编辑)") lines.append("## AI 分析(自动生成,请勿编辑)")
lines.append("") lines.append("")
lines.append(summary) lines.append(ai_body)
lines.append("") lines.append("")
# Categories
if categories:
lines.append("### 分类概览")
lines.append("")
for cat, items in categories.items():
count = len(items) if isinstance(items, list) else items
lines.append(f"- **{cat}**{count}")
lines.append("")
# Connections
if connections:
lines.append("### 关联发现")
lines.append("")
for conn in connections:
lines.append(f"- {conn}")
lines.append("")
# Todos
if todos:
lines.append("### 待办")
lines.append("")
for todo in todos:
lines.append(f"- [ ] {todo}")
lines.append("")
# User annotation area
lines.append("---") lines.append("---")
lines.append("") lines.append("")
lines.append("## 我的批注") lines.append("## 我的批注")
@ -68,18 +39,16 @@ def format_weekly_trend(start_date, end_date, stats, highlights, insight):
lines = [] lines = []
lines.append("---") lines.append("---")
lines.append(f"date: {start_date.year}-W{week_number}") lines.append("date: " + str(start_date.year) + "-W" + str(week_number))
lines.append("type: weekly-trend") lines.append("type: weekly-trend")
lines.append("tags: [灵感收集器, 每周趋势]") lines.append("tags: [灵感收集器, 每周趋势]")
lines.append("---") lines.append("---")
lines.append("") lines.append("")
lines.append(f"# 第 {week_number} 周灵感趋势({week_start} - {week_end}") lines.append("# 第 " + str(week_number) + " 周灵感趋势(" + week_start + " - " + week_end + "")
lines.append("") lines.append("")
# Stats
lines.append("## AI 分析(自动生成,请勿编辑)") lines.append("## AI 分析(自动生成,请勿编辑)")
lines.append("") lines.append("")
lines.append(f"本周共记录 **{stats.get('total', 0)}** 条灵感。") lines.append("本周共记录 **" + str(stats.get('total', 0)) + "** 条灵感。")
lines.append("") lines.append("")
if stats.get("daily_counts"): if stats.get("daily_counts"):
@ -87,22 +56,23 @@ def format_weekly_trend(start_date, end_date, stats, highlights, insight):
lines.append("") lines.append("")
for day, count in stats["daily_counts"].items(): for day, count in stats["daily_counts"].items():
bar = "" * count if count > 0 else "" bar = "" * count if count > 0 else ""
lines.append(f"- {day}{bar} {count}") lines.append("- " + day + "" + bar + " " + str(count) + "")
lines.append("") lines.append("")
if stats.get("categories"): if stats.get("categories"):
lines.append("### 主题分布") lines.append("### 主题分布")
lines.append("") lines.append("")
total = stats.get("total", 1)
for cat, count in stats["categories"].items(): for cat, count in stats["categories"].items():
pct = count / stats["total"] * 100 if stats["total"] else 0 pct = count / total * 100 if total else 0
lines.append(f"- **{cat}**{count}条({pct:.0f}%") lines.append("- **" + cat + "**" + str(count) + "条(" + str(int(pct)) + "%")
lines.append("") lines.append("")
if highlights: if highlights:
lines.append("### 本周亮点") lines.append("### 本周亮点")
lines.append("") lines.append("")
for h in highlights: for h in highlights:
lines.append(f"- {h}") lines.append("- " + h)
lines.append("") lines.append("")
if insight: if insight:
@ -111,7 +81,6 @@ def format_weekly_trend(start_date, end_date, stats, highlights, insight):
lines.append(insight) lines.append(insight)
lines.append("") lines.append("")
# User annotation
lines.append("---") lines.append("---")
lines.append("") lines.append("")
lines.append("## 我的批注") lines.append("## 我的批注")