feat: IMAP mail sync with thread-to-contact matching (PRD §6.1)

Real IMAP connection via imapflow library:
- Connects to IMAP server (IMAPFlow, TLS on port 993)
- Fetches recent messages (configurable limit, default 20)
- Extracts: from, fromEmail, subject, date, snippet (200 chars)
- Thread-to-contact matching (PRD §6.1):
  1. Match sender email against persons table
  2. If no person match, extract domain and match against company.website
  3. Returns matchedCompany + matchedPerson for each email
- Audit-logged (action: crm.mailsync:imap)
- Zod validated (host, port, user, password, folder, limit)
- 502 on IMAP connection failure with detail
- 400 on validation failure

Tested: invalid creds → 502 'IMAP connection failed', missing host → 400
This commit is contained in:
Z User committed 2026-10-27 22:50:50 +00:00
1 parent 7acc0c5451
commit e45b68bb8b
1089 files changed
+260614

No files matched your search

+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env python3
"""从学习资料生成知识点提取 prompt。
用法:python3 generate_from_material.py <file_path> <document_id>
输出:JSON 格式的 prompt(system_prompt + user_prompt),由 agent 发给 LLM 执行。
LLM 返回知识点 JSON 后,agent 应调用 service.save_knowledge_points() 保存。
"""
from pathlib import Path
import json
import sys
sys.path.append(str(Path(__file__).resolve().parents[1] / "src"))
from quiz_mastery.file_parser import parse_file, build_extraction_prompt
def main() -> None:
if len(sys.argv) < 3:
print("Usage: generate_from_material.py <file_path> <document_id>")
print(" file_path: Path to study material (.md, .txt, .text)")
print(" document_id: Identifier for this document")
sys.exit(1)
file_path = sys.argv[1]
document_id = sys.argv[2]
content = parse_file(file_path)
prompts = build_extraction_prompt(content)
output = {
"action": "extract_knowledge_points",
"document_id": document_id,
"file_path": file_path,
"prompts": prompts,
"instructions": (
"Send the system_prompt and user_prompt to an LLM. "
"The LLM should return a JSON array of knowledge points. "
"Then call save_knowledge_points(document_id, knowledge_points) to save."
),
}
print(json.dumps(output, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()