All checks were successful
Deploy mindnet to llm-node / deploy (push) Successful in 3s
182 lines
6.1 KiB
Python
182 lines
6.1 KiB
Python
|
|
"""
|
|
note_payload.py — Mindnet core payload builder (v0.5, 2025-11-08)
|
|
|
|
Purpose
|
|
-------
|
|
Builds a **JSON-serializable** payload dict for a single note to be stored in
|
|
the `<prefix>_notes` collection. The function is defensive against both
|
|
attribute- and dict-like ParsedNote inputs, unknown kwargs, and ensures
|
|
`retriever_weight` is always present as a float.
|
|
|
|
Key guarantees
|
|
--------------
|
|
- Accepts extra positional/keyword args without error (for importer compatibility).
|
|
- Tolerant of attribute vs dict access for ParsedNote.
|
|
- Always sets 'retriever_weight' in the payload (float).
|
|
- Never includes non-serializable objects (functions, PosixPath, datetime, etc.).
|
|
|
|
Public API
|
|
----------
|
|
make_note_payload(parsed_note, *args, retriever_weight=None, vault_root=None, type_defaults=None, **kwargs) -> dict
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
from pathlib import Path
|
|
from typing import Any, Dict, Optional, Union, Iterable, Mapping
|
|
import datetime, math
|
|
|
|
Json = Union[None, bool, int, float, str, list, dict]
|
|
|
|
# ------------------------- helpers -------------------------
|
|
|
|
def _is_mapping(x: Any) -> bool:
|
|
return isinstance(x, Mapping)
|
|
|
|
def _get(obj: Any, *names: str, default: Any=None) -> Any:
|
|
"""Try attribute lookup, then mapping (dict) lookup, first hit wins."""
|
|
for n in names:
|
|
if hasattr(obj, n):
|
|
try:
|
|
return getattr(obj, n)
|
|
except Exception:
|
|
pass
|
|
if _is_mapping(obj) and n in obj:
|
|
try:
|
|
return obj[n]
|
|
except Exception:
|
|
pass
|
|
return default
|
|
|
|
def _to_float(x: Any, default: float=1.0) -> float:
|
|
if x is None:
|
|
return float(default)
|
|
if isinstance(x, (int, float)) and math.isfinite(x):
|
|
return float(x)
|
|
try:
|
|
s = str(x).strip().replace(',', '.')
|
|
return float(s)
|
|
except Exception:
|
|
return float(default)
|
|
|
|
def _ensure_list(x: Any) -> list:
|
|
if x is None:
|
|
return []
|
|
if isinstance(x, list):
|
|
return x
|
|
if isinstance(x, (set, tuple)):
|
|
return list(x)
|
|
return [x]
|
|
|
|
def _sanitize(obj: Any) -> Json:
|
|
"""Recursively convert to JSON-serializable primitives; drop callables."""
|
|
if obj is None or isinstance(obj, (bool, int, float, str)):
|
|
return obj
|
|
if callable(obj):
|
|
return None
|
|
if isinstance(obj, (list, tuple, set)):
|
|
return [_sanitize(v) for v in obj]
|
|
if isinstance(obj, dict):
|
|
out = {}
|
|
for k, v in obj.items():
|
|
if callable(v):
|
|
continue
|
|
out[str(k)] = _sanitize(v)
|
|
return out
|
|
if isinstance(obj, Path):
|
|
return str(obj)
|
|
if isinstance(obj, datetime.datetime):
|
|
return obj.isoformat()
|
|
if hasattr(obj, "__str__"):
|
|
try:
|
|
return str(obj)
|
|
except Exception:
|
|
return None
|
|
return None
|
|
|
|
def _compute_retriever_weight(explicit: Any, frontmatter: dict, type_defaults: Optional[dict], note_type: Optional[str]) -> float:
|
|
if explicit is not None:
|
|
return _to_float(explicit, 1.0)
|
|
# common frontmatter keys
|
|
for key in ("retriever_weight", "retriever.weight", "retrieverWeight"):
|
|
if key in frontmatter:
|
|
return _to_float(frontmatter.get(key), 1.0)
|
|
# type defaults map like: {"concept": {"retriever_weight": 0.9}, ...}
|
|
if type_defaults and note_type:
|
|
tdef = type_defaults.get(note_type) or {}
|
|
for key in ("retriever_weight", "retriever.weight", "retrieverWeight"):
|
|
if key in tdef:
|
|
return _to_float(tdef.get(key), 1.0)
|
|
return 1.0
|
|
|
|
# ------------------------- public API -------------------------
|
|
|
|
def make_note_payload(parsed_note: Any,
|
|
*args,
|
|
retriever_weight: Optional[float]=None,
|
|
vault_root: Optional[str]=None,
|
|
type_defaults: Optional[dict]=None,
|
|
**kwargs) -> Dict[str, Json]:
|
|
"""
|
|
Build a JSON-safe payload for the note.
|
|
|
|
Parameters (tolerant; unknown args are ignored)
|
|
----------
|
|
parsed_note : object or dict
|
|
Expected fields/keys (best-effort): note_id/id, title, type, path/rel_path,
|
|
frontmatter, tags, aliases, chunk_profile.
|
|
retriever_weight : float|None
|
|
Overrides frontmatter/type-defaults if provided.
|
|
vault_root : str|None
|
|
Optional; used to produce a normalized relative path.
|
|
type_defaults : dict|None
|
|
Optional map for per-type defaults.
|
|
|
|
Returns
|
|
-------
|
|
dict suitable for Qdrant payload
|
|
"""
|
|
fm = _get(parsed_note, "frontmatter", "fm", default={})
|
|
if not isinstance(fm, dict):
|
|
fm = {}
|
|
|
|
note_id = _get(parsed_note, "note_id", "id", default=fm.get("id"))
|
|
title = _get(parsed_note, "title", default=fm.get("title"))
|
|
ntype = _get(parsed_note, "type", default=fm.get("type"))
|
|
raw_path = _get(parsed_note, "path", "rel_path", "relpath", default=fm.get("path"))
|
|
tags = _ensure_list(_get(parsed_note, "tags", default=fm.get("tags")))
|
|
aliases = _ensure_list(_get(parsed_note, "aliases", default=fm.get("aliases")))
|
|
chunk_profile = _get(parsed_note, "chunk_profile", "profile", default=fm.get("chunk_profile"))
|
|
created = _get(parsed_note, "created", default=fm.get("created"))
|
|
updated = _get(parsed_note, "updated", default=fm.get("updated"))
|
|
|
|
# normalize path relative to vault root if both available
|
|
rel_path = raw_path
|
|
if raw_path and vault_root:
|
|
try:
|
|
rel_path = str(Path(raw_path)).replace(str(Path(vault_root)), "").lstrip("/\\")
|
|
except Exception:
|
|
rel_path = str(raw_path)
|
|
|
|
rw = _compute_retriever_weight(retriever_weight, fm, type_defaults, ntype)
|
|
|
|
payload = {
|
|
"note_id": note_id,
|
|
"title": title,
|
|
"type": ntype,
|
|
"path": rel_path or raw_path,
|
|
"tags": tags,
|
|
"aliases": aliases,
|
|
"chunk_profile": chunk_profile,
|
|
"created": created,
|
|
"updated": updated,
|
|
"retriever_weight": float(rw),
|
|
}
|
|
|
|
# Add selected FM fields if present (safe subset)
|
|
for key in ("status", "priority", "owner", "source"):
|
|
if key in fm:
|
|
payload[key] = fm.get(key)
|
|
|
|
return _sanitize(payload)
|