Files
AIPass/src/aipass/daemon/apps/handlers/update/data_loader.py
T
18acb30f83 chore(system): session 34 — maintenance + multi-branch work (#70)
* fix(ai_mail): resolve relative mailbox_path bug + archive dead handlers

Fix get_user_by_email() and get_all_users() returning relative mailbox_path
values from registry instead of absolute paths (causing doubled paths in reply).
Added path resolution matching get_current_user() pattern.

Archive 7 unused handler files to apps/.archive/ (gitignored):
- pending_work.py — planned feature, never wired
- lock_utils.py — superseded by inbox_lock.py
- data_ops.py — consumer (error_monitor) was archived
- config_generator.py, users/load.py, registry/validate.py — scaffolded, never used
- trigger/error_handler.py — superseded by trigger branch's event system

Added test_user_paths.py (13 tests) covering absolute path resolution.

Co-Authored-By: @ai_mail <ai_mail@aipass>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(spawn): seedgo compliance cleanup — 93% to 98%

Archive legacy files (dev.local.md, ai_mail.local/, .seed/). Update META
Modified dates on 22 files. Update README architecture tree and freshness.
Fix 15 Pyright unused warnings. Add introspection no-args gates to 6
modules. Wire json_handler + log_operation to 15 operational files. Add
seedgo bypass for 5 pure data files and log_structure. Remove deprecated
dev.local.md from builder template.

Co-Authored-By: @spawn <spawn@aipass>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(system): session 34 — maintenance day, multi-branch work

System changes:
- setup.sh: symlink install replaces .bashrc PATH export for drone/seedgo
- .claude/settings.json: deny list moved to per-branch settings.local.json
- .gitignore: updated
- CLAUDE.md culture doc: stale refs fixed (Cortex→spawn, id.json→passport.json)

Branch work (accumulated from 15-branch rounds):
- daemon: stale term cleanup, plugin removals, json handler modernization
- drone: module refactor (discovery, registry, router, resolver, config)
- commons: 50+ file cleanup (imports, handlers, modules)
- flow: template loader, plan type system, command parser

Per-branch settings.local.json: reworked deny lists — allow PR workflow,
deny destructive git ops (reset, rebase, merge, clean, force push, rm -rf).
Fixed pattern syntax from :* to * glob.

Co-Authored-By: @devpulse <devpulse@aipass>

---------

Co-authored-by: @ai_mail <ai_mail@aipass>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: @spawn <spawn@aipass>
Co-authored-by: @devpulse <devpulse@aipass>
2026-03-17 16:45:05 -07:00

108 lines
3.3 KiB
Python

# =================== AIPass ====================
# Name: data_loader.py
# Description: DAEMON Data Loading Handler
# Version: 1.0.0
# Created: 2026-01-29
# Modified: 2026-01-29
# =============================================
"""
Handler for loading DAEMON data from inbox and local files.
"""
import json
from pathlib import Path
from typing import Dict, Any, List
from aipass.daemon.apps.handlers.json import json_handler
# =============================================
# CONSTANTS
# =============================================
_DAEMON_ROOT = Path(__file__).resolve().parents[3] # src/aipass/daemon/
INBOX_PATH = _DAEMON_ROOT / "ai_mail.local" / "inbox.json"
LOCAL_PATH = _DAEMON_ROOT / "DAEMON.local.json"
# =============================================
# DATA LOADING
# =============================================
def load_inbox() -> Dict[str, Any]:
"""Load inbox.json and return parsed data."""
json_handler.log_operation("data_loaded")
if not INBOX_PATH.exists():
return {"messages": [], "total_messages": 0, "unread_count": 0}
try:
with open(INBOX_PATH, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception:
return {"messages": [], "total_messages": 0, "unread_count": 0}
def load_local() -> Dict[str, Any]:
"""Load DAEMON.local.json and return parsed data."""
if not LOCAL_PATH.exists():
return {"sessions": [], "active_tasks": {}}
try:
with open(LOCAL_PATH, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception:
return {"sessions": [], "active_tasks": {}}
# =============================================
# DIGEST ANALYSIS
# =============================================
def categorize_messages(messages: List[Dict[str, Any]]) -> Dict[str, List[Dict[str, Any]]]:
"""
Categorize inbox messages by status.
Returns:
Dict with keys: new, opened, actionable, informational
"""
categories: Dict[str, List[Dict[str, Any]]] = {
"new": [],
"opened": [],
"actionable": [],
"informational": []
}
for msg in messages:
status = msg.get("status", "new")
subject = msg.get("subject", "").upper()
if status == "new":
categories["new"].append(msg)
elif status == "opened":
categories["opened"].append(msg)
if any(kw in subject for kw in ["TASK:", "BUILD:", "FIX:", "PROPOSAL:", "REQUEST:"]):
categories["actionable"].append(msg)
elif any(kw in subject for kw in ["INFO", "RE:", "FYI", "NOTIFICATION"]):
categories["informational"].append(msg)
return categories
def get_session_summary(local_data: Dict[str, Any]) -> Dict[str, Any]:
"""Extract session summary from local.json."""
sessions = local_data.get("sessions", [])
active_tasks = local_data.get("active_tasks", {})
return {
"total_sessions": len(sessions),
"today_focus": active_tasks.get("today_focus", "None"),
"recently_completed": active_tasks.get("recently_completed", []),
"latest_session": sessions[0] if sessions else None
}
def get_escalations(messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Find messages that need escalation."""
return [m for m in messages if "BLOCKED" in m.get("subject", "").upper()
or "URGENT" in m.get("subject", "").upper()]