personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 166 lines 5.6 kB view raw
1# SPDX-License-Identifier: AGPL-3.0-only 2# Copyright (c) 2026 sol pbc 3 4"""Event formatting for journal event JSONL files.""" 5 6import logging 7import re 8from datetime import datetime 9from pathlib import Path 10from typing import Any 11 12 13def format_events( 14 entries: list[dict], 15 context: dict | None = None, 16) -> tuple[list[dict], dict]: 17 """Format event JSONL entries to markdown chunks. 18 19 This is the formatter function used by the formatters registry. 20 21 Args: 22 entries: Raw JSONL entries (one event per line) 23 context: Optional context with: 24 - file_path: Path to JSONL file (for extracting facet name and day) 25 26 Returns: 27 Tuple of (chunks, meta) where: 28 - chunks: List of dicts with keys: 29 - timestamp: int (unix ms) 30 - markdown: str 31 - source: dict (original event entry) 32 - meta: Dict with optional "header" and "error" keys 33 """ 34 ctx = context or {} 35 file_path = ctx.get("file_path") 36 meta: dict[str, Any] = {} 37 chunks: list[dict[str, Any]] = [] 38 skipped_count = 0 39 40 # Extract facet name and day from path 41 facet_name = "unknown" 42 day_str: str | None = None 43 44 if file_path: 45 file_path = Path(file_path) 46 47 # Extract facet name from path: facets/{facet}/events/YYYYMMDD.jsonl 48 path_str = str(file_path) 49 facet_match = re.search(r"facets/([^/]+)/events", path_str) 50 if facet_match: 51 facet_name = facet_match.group(1) 52 53 # Extract day from filename 54 if file_path.stem.isdigit() and len(file_path.stem) == 8: 55 day_str = file_path.stem 56 57 # Calculate base timestamp (midnight of the event day) in milliseconds 58 base_ts = 0 59 if day_str: 60 try: 61 dt = datetime.strptime(day_str, "%Y%m%d") 62 base_ts = int(dt.timestamp() * 1000) 63 except ValueError: 64 pass 65 66 # Build header 67 if day_str: 68 formatted_day = f"{day_str[:4]}-{day_str[4:6]}-{day_str[6:8]}" 69 meta["header"] = f"# Events for '{facet_name}' facet on {formatted_day}" 70 else: 71 meta["header"] = f"# Events for '{facet_name}' facet" 72 73 # Format each event as a chunk 74 for event in entries: 75 # Skip entries without title 76 title = event.get("title") 77 if not title: 78 skipped_count += 1 79 continue 80 81 event_type = event.get("type", "event").capitalize() 82 occurred = event.get("occurred", True) 83 84 # Calculate timestamp from day + start time 85 ts = base_ts 86 start_time = event.get("start", "") 87 if start_time and base_ts: 88 try: 89 # Parse HH:MM:SS or HH:MM 90 time_parts = start_time.split(":") 91 hours = int(time_parts[0]) 92 minutes = int(time_parts[1]) if len(time_parts) > 1 else 0 93 seconds = int(time_parts[2]) if len(time_parts) > 2 else 0 94 ts = base_ts + (hours * 3600 + minutes * 60 + seconds) * 1000 95 except (ValueError, IndexError): 96 pass 97 98 # Build markdown 99 type_prefix = "Planned " if not occurred else "" 100 lines = [f"### {type_prefix}{event_type}: {title}\n", ""] 101 102 # Time range (24h format, strip seconds for display) 103 end_time = event.get("end", "") 104 time_label = "Occurred" if occurred else "Scheduled" 105 if start_time: 106 start_display = start_time[:5] if len(start_time) >= 5 else start_time 107 if end_time: 108 end_display = end_time[:5] if len(end_time) >= 5 else end_time 109 lines.append(f"**Time {time_label}:** {start_display} - {end_display}") 110 else: 111 lines.append(f"**Time {time_label}:** {start_display}") 112 113 # Participants 114 participants = event.get("participants", []) 115 if participants and isinstance(participants, list): 116 participants_label = ( 117 "Expected Participants" if not occurred else "Participants" 118 ) 119 lines.append(f"**{participants_label}:** {', '.join(participants)}") 120 121 # For future-dated event rows, show when they were created (from source path) 122 if not occurred: 123 source = event.get("source", "") 124 # Extract YYYYMMDD from source path like "20240101/talents/agent.md" 125 source_match = re.match(r"(\d{8})/", source) 126 if source_match: 127 created_day = source_match.group(1) 128 created_formatted = ( 129 f"{created_day[:4]}-{created_day[4:6]}-{created_day[6:8]}" 130 ) 131 lines.append(f"**Created on:** {created_formatted}") 132 133 lines.append("") 134 135 # Summary 136 summary = event.get("summary", "") 137 if summary: 138 lines.append(summary) 139 lines.append("") 140 141 # Details 142 details = event.get("details", "") 143 if details: 144 lines.append(details) 145 lines.append("") 146 147 chunks.append( 148 { 149 "timestamp": ts, 150 "markdown": "\n".join(lines), 151 "source": event, 152 } 153 ) 154 155 # Report skipped entries 156 if skipped_count > 0: 157 error_msg = f"Skipped {skipped_count} entries missing 'title' field" 158 if file_path: 159 error_msg += f" in {file_path}" 160 meta["error"] = error_msg 161 logging.info(error_msg) 162 163 # Indexer metadata - agent is always "event" for events 164 meta["indexer"] = {"agent": "event"} 165 166 return chunks, meta