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>
This commit is contained in:
Hyungi Ahn
2026-03-26 12:32:36 +09:00
parent bec9579a8a
commit 084d3a8c63
14 changed files with 1564 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
-- DEVONthink 4 Smart Rule: AI 자동 분류
-- Inbox DB 새 문서 → Ollama 분류 → 태그 + 메타데이터 + 도메인 DB 이동
-- Smart Rule 설정: Event = On Import, 조건 = Tags is empty
on performSmartRule(theRecords)
tell application id "DNtp"
repeat with theRecord in theRecords
try
-- 1. 문서 텍스트 추출 (최대 4000자)
set docText to plain text of theRecord
set docUUID to uuid of theRecord
if length of docText > 4000 then
set docText to text 1 thru 4000 of docText
end if
if length of docText < 10 then
-- 텍스트가 너무 짧으면 건너뜀
set tags of theRecord to {"@상태/검토필요"}
continue repeat
end if
-- 2. 분류 프롬프트 로딩
set promptPath to (POSIX path of (path to home folder)) & "Documents/code/DEVONThink_my server/scripts/prompts/classify_document.txt"
set promptTemplate to do shell script "cat " & quoted form of promptPath
-- 문서 텍스트를 프롬프트에 삽입 (특수문자 이스케이프)
set escapedText to do shell script "echo " & quoted form of docText & " | sed 's/\\\\/\\\\\\\\/g; s/\"/\\\\\"/g; s/\\n/\\\\n/g' | head -c 4000"
-- 3. Ollama API 호출
set curlCmd to "curl -s --max-time 120 http://localhost:11434/api/generate -d '{\"model\": \"qwen3.5:35b-a3b-q4_K_M\", \"prompt\": " & quoted form of escapedText & ", \"stream\": false, \"format\": \"json\"}'"
set jsonResult to do shell script curlCmd
-- 4. JSON 파싱 (Python 사용)
set parseCmd to "echo " & quoted form of jsonResult & " | python3 -c \"
import sys, json
try:
r = json.loads(sys.stdin.read())
d = json.loads(r.get('response', '{}'))
tags = ','.join(d.get('tags', []))
db = d.get('domain_db', '00_Note_BOX')
grp = d.get('sub_group', '00_Inbox')
ch = d.get('sourceChannel', 'inbox_route')
origin = d.get('dataOrigin', 'external')
print(f'{db}|{grp}|{tags}|{ch}|{origin}')
except:
print('00_Note_BOX|00_Inbox||inbox_route|external')
\""
set classResult to do shell script parseCmd
set AppleScript's text item delimiters to "|"
set resultParts to text items of classResult
set targetDB to item 1 of resultParts
set targetGroup to item 2 of resultParts
set tagString to item 3 of resultParts
set sourceChannel to item 4 of resultParts
set dataOrigin to item 5 of resultParts
set AppleScript's text item delimiters to ""
-- 5. 태그 설정
if tagString is not "" then
set AppleScript's text item delimiters to ","
set tagList to text items of tagString
set AppleScript's text item delimiters to ""
set tags of theRecord to tagList
end if
-- 6. 커스텀 메타데이터 설정
add custom meta data sourceChannel for "sourceChannel" to theRecord
add custom meta data dataOrigin for "dataOrigin" to theRecord
add custom meta data (current date) for "lastAIProcess" to theRecord
add custom meta data "inbox_route" for "sourceChannel" to theRecord
-- 7. 대상 도메인 DB로 이동
set targetDatabase to missing value
repeat with db in databases
if name of db is targetDB then
set targetDatabase to db
exit repeat
end if
end repeat
if targetDatabase is not missing value then
set groupPath to "/" & targetGroup
set targetLocation to create location groupPath in targetDatabase
move record theRecord to targetLocation
end if
-- 8. GPU 서버 벡터 임베딩 비동기 전송
set embedScript to (POSIX path of (path to home folder)) & "Documents/code/DEVONThink_my server/venv/bin/python3"
set embedPy to (POSIX path of (path to home folder)) & "Documents/code/DEVONThink_my server/scripts/embed_to_chroma.py"
do shell script embedScript & " " & quoted form of embedPy & " " & quoted form of docUUID & " &> /dev/null &"
on error errMsg
-- 에러 시 로그 기록 + 검토필요 태그
set tags of theRecord to {"@상태/검토필요", "AI분류실패"}
do shell script "echo '[" & (current date) & "] [auto_classify] [ERROR] " & errMsg & "' >> ~/Documents/code/DEVONThink_my\\ server/logs/auto_classify.log"
end try
end repeat
end tell
end performSmartRule

View File

@@ -0,0 +1,71 @@
-- DEVONthink 4 Smart Rule: OmniFocus 연동
-- Projects DB 새 문서에서 TODO 패턴 감지 → OmniFocus 작업 생성
-- Smart Rule 설정: Event = On Import, DB = Projects
on performSmartRule(theRecords)
tell application id "DNtp"
repeat with theRecord in theRecords
try
set docText to plain text of theRecord
set docTitle to name of theRecord
set docUUID to uuid of theRecord
set docLink to reference URL of theRecord -- x-devonthink-item://UUID
-- TODO 패턴 감지: "TODO", "할일", "□", "[ ]", "FIXME"
set hasAction to false
if docText contains "TODO" or docText contains "할일" or docText contains "□" or docText contains "[ ]" or docText contains "FIXME" then
set hasAction to true
end if
if not hasAction then continue repeat
-- 액션 아이템 추출 (Python으로 파싱)
set extractCmd to "echo " & quoted form of docText & " | python3 -c \"
import sys, re
text = sys.stdin.read()
patterns = [
r'(?:TODO|FIXME|할일)[:\\s]*(.+?)(?:\\n|$)',
r'(?:□|\\[ \\])\\s*(.+?)(?:\\n|$)',
]
items = []
for p in patterns:
items.extend(re.findall(p, text, re.MULTILINE))
# 최대 5개, 중복 제거
seen = set()
for item in items[:10]:
item = item.strip()
if item and item not in seen:
seen.add(item)
print(item)
if len(seen) >= 5:
break
\""
set actionItems to paragraphs of (do shell script extractCmd)
if (count of actionItems) = 0 then continue repeat
-- OmniFocus에 작업 생성
tell application "OmniFocus"
tell default document
set taskIDs to {}
repeat with actionItem in actionItems
set taskName to docTitle & " — " & (contents of actionItem)
set newTask to make new inbox task with properties {name:taskName, note:"DEVONthink 문서: " & docLink}
set end of taskIDs to id of newTask
end repeat
end tell
end tell
-- DEVONthink 메타데이터에 OmniFocus Task ID 저장
set AppleScript's text item delimiters to ","
set taskIDString to taskIDs as text
set AppleScript's text item delimiters to ""
add custom meta data taskIDString for "omnifocusTaskID" to theRecord
on error errMsg
do shell script "echo '[" & (current date) & "] [omnifocus_sync] [ERROR] " & errMsg & "' >> ~/Documents/code/DEVONThink_my\\ server/logs/omnifocus_sync.log"
end try
end repeat
end tell
end performSmartRule