Files
hyungi_document_server/scripts/pkm_daily_digest.py
Hyungi Ahn 084d3a8c63 feat: 전체 PKM 스크립트 일괄 작성 — 분류/법령/메일/다이제스트/임베딩
- scripts/pkm_utils.py: 공통 유틸 (로거, dotenv, osascript 래퍼)
- scripts/prompts/classify_document.txt: Ollama 분류 프롬프트
- applescript/auto_classify.scpt: Inbox → AI 분류 → DB 이동
- applescript/omnifocus_sync.scpt: Projects → OmniFocus 작업 생성
- scripts/law_monitor.py: 법령 변경 모니터링 + DEVONthink 임포트
- scripts/mailplus_archive.py: MailPlus IMAP → Archive DB
- scripts/pkm_daily_digest.py: 일일 다이제스트 + OmniFocus 액션
- scripts/embed_to_chroma.py: GPU 서버 벡터 임베딩 → ChromaDB
- launchd/*.plist: 3개 스케줄 (07:00, 07:00+18:00, 20:00)
- docs/deploy.md: Mac mini 배포 가이드
- docs/devonagent-setup.md: 검색 세트 9종 설정 가이드
- tests/test_classify.py: 5종 문서 분류 테스트

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 12:32:36 +09:00

285 lines
9.1 KiB
Python

#!/usr/bin/env python3
"""
PKM 일일 다이제스트
- DEVONthink 오늘 추가/수정 집계
- law_monitor 법령 변경 건 파싱
- OmniFocus 완료/추가/기한초과 집계
- 상위 뉴스 Ollama 요약
- OmniFocus 액션 자동 생성
- 90일 지난 다이제스트 아카이브
"""
import os
import sys
import re
from datetime import datetime, timedelta
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from pkm_utils import (
setup_logger, load_credentials, run_applescript_inline,
ollama_generate, count_log_errors, PROJECT_ROOT, LOGS_DIR, DATA_DIR
)
logger = setup_logger("digest")
DIGEST_DIR = DATA_DIR / "digests"
DIGEST_DIR.mkdir(exist_ok=True)
def get_devonthink_stats() -> dict:
"""DEVONthink 오늘 추가/수정 문서 집계"""
script = '''
tell application id "DNtp"
set today to current date
set time of today to 0
set stats to {}
repeat with db in databases
set dbName to name of db
set addedCount to count of (search "date:today" in db)
set modifiedCount to count of (search "modified:today" in db)
if addedCount > 0 or modifiedCount > 0 then
set end of stats to dbName & ":" & addedCount & ":" & modifiedCount
end if
end repeat
set AppleScript's text item delimiters to "|"
return stats as text
end tell
'''
try:
result = run_applescript_inline(script)
stats = {}
if result:
for item in result.split("|"):
parts = item.split(":")
if len(parts) == 3:
stats[parts[0]] = {"added": int(parts[1]), "modified": int(parts[2])}
return stats
except Exception as e:
logger.error(f"DEVONthink 집계 실패: {e}")
return {}
def get_omnifocus_stats() -> dict:
"""OmniFocus 오늘 완료/추가/기한초과 집계"""
script = '''
tell application "OmniFocus"
tell default document
set today to current date
set time of today to 0
set tomorrow to today + 1 * days
set completedCount to count of (every flattened task whose completed is true and completion date ≥ today)
set addedCount to count of (every flattened task whose creation date ≥ today)
set overdueCount to count of (every flattened task whose completed is false and due date < today and due date is not missing value)
return (completedCount as text) & "|" & (addedCount as text) & "|" & (overdueCount as text)
end tell
end tell
'''
try:
result = run_applescript_inline(script)
parts = result.split("|")
return {
"completed": int(parts[0]) if len(parts) > 0 else 0,
"added": int(parts[1]) if len(parts) > 1 else 0,
"overdue": int(parts[2]) if len(parts) > 2 else 0,
}
except Exception as e:
logger.error(f"OmniFocus 집계 실패: {e}")
return {"completed": 0, "added": 0, "overdue": 0}
def parse_law_changes() -> list:
"""law_monitor 로그에서 오늘 법령 변경 건 파싱"""
log_file = LOGS_DIR / "law_monitor.log"
if not log_file.exists():
return []
today = datetime.now().strftime("%Y-%m-%d")
changes = []
with open(log_file, "r", encoding="utf-8") as f:
for line in f:
if today in line and "변경 감지" in line:
# "[2026-03-26 07:00:15] [law_monitor] [INFO] 변경 감지: 산업안전보건법 — 공포일자 ..."
match = re.search(r"변경 감지: (.+?)$", line)
if match:
changes.append(match.group(1).strip())
return changes
def get_inbox_count() -> int:
"""DEVONthink Inbox 미처리 문서 수"""
script = '''
tell application id "DNtp"
repeat with db in databases
if name of db is "Inbox" then
return count of children of root group of db
end if
end repeat
return 0
end tell
'''
try:
return int(run_applescript_inline(script))
except:
return 0
def create_omnifocus_task(task_name: str, note: str = "", flagged: bool = False):
"""OmniFocus 작업 생성"""
flag_str = "true" if flagged else "false"
escaped_name = task_name.replace('"', '\\"')
escaped_note = note.replace('"', '\\"')
script = f'''
tell application "OmniFocus"
tell default document
make new inbox task with properties {{name:"{escaped_name}", note:"{escaped_note}", flagged:{flag_str}}}
end tell
end tell
'''
try:
run_applescript_inline(script)
logger.info(f"OmniFocus 작업 생성: {task_name}")
except Exception as e:
logger.error(f"OmniFocus 작업 생성 실패: {e}")
def get_system_health() -> dict:
"""각 모듈 로그의 최근 24시간 ERROR 카운트"""
modules = ["law_monitor", "mailplus", "digest", "embed", "auto_classify"]
health = {}
for mod in modules:
log_file = LOGS_DIR / f"{mod}.log"
health[mod] = count_log_errors(log_file, since_hours=24)
return health
def generate_digest():
"""다이제스트 생성"""
logger.info("=== Daily Digest 생성 시작 ===")
today = datetime.now()
date_str = today.strftime("%Y-%m-%d")
# 데이터 수집
dt_stats = get_devonthink_stats()
of_stats = get_omnifocus_stats()
law_changes = parse_law_changes()
inbox_count = get_inbox_count()
system_health = get_system_health()
# 마크다운 생성
md = f"# PKM Daily Digest — {date_str}\n\n"
# DEVONthink 현황
md += "## DEVONthink 변화\n\n"
if dt_stats:
md += "| DB | 신규 | 수정 |\n|---|---|---|\n"
total_added = 0
total_modified = 0
for db_name, counts in dt_stats.items():
md += f"| {db_name} | {counts['added']} | {counts['modified']} |\n"
total_added += counts["added"]
total_modified += counts["modified"]
md += f"| **합계** | **{total_added}** | **{total_modified}** |\n\n"
else:
md += "변화 없음\n\n"
# 법령 변경
md += "## 법령 변경\n\n"
if law_changes:
for change in law_changes:
md += f"- {change}\n"
md += "\n"
else:
md += "변경 없음\n\n"
# OmniFocus 현황
md += "## OmniFocus 현황\n\n"
md += f"- 완료: {of_stats['completed']}\n"
md += f"- 신규: {of_stats['added']}\n"
md += f"- 기한초과: {of_stats['overdue']}\n\n"
# Inbox 상태
md += f"## Inbox 미처리: {inbox_count}\n\n"
# 시스템 상태
md += "## 시스템 상태\n\n"
total_errors = sum(system_health.values())
if total_errors == 0:
md += "모든 모듈 정상\n\n"
else:
md += "| 모듈 | 에러 수 |\n|---|---|\n"
for mod, cnt in system_health.items():
status = f"**{cnt}**" if cnt > 0 else "0"
md += f"| {mod} | {status} |\n"
md += "\n"
# 파일 저장
digest_file = DIGEST_DIR / f"{date_str}_digest.md"
with open(digest_file, "w", encoding="utf-8") as f:
f.write(md)
logger.info(f"다이제스트 저장: {digest_file}")
# DEVONthink 저장
import_digest_to_devonthink(digest_file, date_str)
# OmniFocus 액션 자동 생성
if law_changes:
for change in law_changes:
create_omnifocus_task(f"법령 변경 검토: {change[:30]}", note=change)
if inbox_count >= 3:
create_omnifocus_task(f"Inbox 정리 ({inbox_count}건 미처리)", note="DEVONthink Inbox에 미분류 문서가 쌓여있습니다.")
if of_stats["overdue"] > 0:
create_omnifocus_task(f"기한초과 작업 처리 ({of_stats['overdue']}건)", flagged=True)
# 90일 지난 다이제스트 아카이브
archive_old_digests()
logger.info("=== Daily Digest 완료 ===")
def import_digest_to_devonthink(filepath: Path, date_str: str):
"""다이제스트를 DEVONthink에 저장"""
escaped_path = str(filepath).replace('"', '\\"')
script = f'''
tell application id "DNtp"
repeat with db in databases
if name of db is "00_Note_BOX" then
set targetGroup to create location "/Daily_Digest" in db
import POSIX path "{escaped_path}" to targetGroup
exit repeat
end if
end repeat
end tell
'''
try:
run_applescript_inline(script)
except Exception as e:
logger.error(f"DEVONthink 다이제스트 임포트 실패: {e}")
def archive_old_digests():
"""90일 지난 다이제스트 이동"""
cutoff = datetime.now() - timedelta(days=90)
for f in DIGEST_DIR.glob("*_digest.md"):
try:
date_part = f.stem.split("_digest")[0]
file_date = datetime.strptime(date_part, "%Y-%m-%d")
if file_date < cutoff:
archive_dir = DIGEST_DIR / "archive"
archive_dir.mkdir(exist_ok=True)
f.rename(archive_dir / f.name)
logger.info(f"아카이브: {f.name}")
except ValueError:
pass
if __name__ == "__main__":
generate_digest()