zettelkasten.data_grounding¶
zettelkasten.data_grounding ¶
Data-grounded evidence: verify a claim by re-computing a statistic.
This is the generalist substrate described in
dev/documents/260707_report_data_grounded_evidence.md §3 (derivations +
registry + the expr hatch) and §4 (verification, hard vs soft failure modes).
It adds a THIRD grounding modality alongside quote (verbatim substring, see
:mod:zettelkasten.grounding) and equation (snapshot): a claim can be backed
by a re-computable statistic over a stored dataset, and the auditor confirms
it by re-running the computation rather than string-matching.
angelo owns the evidence representation, verification, and reporting. It never interprets what a derivation means, never composes beliefs — a derivation is an opaque, registered, deterministic function angelo can only invoke and compare (§1). Two ways to name a derivation:
exprhatch — a whitelisted mini-evaluator (mean:col/quantile:col:0.9/sum:col…) that re-verifies anywhere with no host registration, so every descriptive (measured) claim is portable (§3.3, §4).- registered derivation — host code (e.g. Freischutz) contributed via
:func:
register_derivation, same trust boundary as.cursor/agents.yaml(§3.1, §3.3). Fitted (inferred) claims live here.
Safety: the expr path NEVER uses eval/exec — it splits on : and
dispatches to an allow-list of reducers, with no attribute access, calls, or
imports. An unrecognized form or unknown column raises a clear ValueError.
Verification splits outcomes into two tiers (§4):
- hard violation — angelo could re-run and it disagreed
(
dataset_hash_mismatch,value_not_reproduced,nondeterministic_unpinned,validation_malformed). Blocks coverage like an unverified quote. - soft unavailable — angelo cannot re-run here (
derivation_unavailable,dataset_unavailable). Informational; the priorverified/verified_instamp is retained so a once-verified claim does not read as broken merely because you are browsing a repo where its derivation isn't installed.
:func:verify_data_grounding never raises for an ordinary failure — it returns the
structured outcome. It only lets truly unexpected internal errors propagate.
DerivationResult
dataclass
¶
The return contract of a derivation (§3.1).
Only value is required and verified. validation / confidence /
method_meta are opaque — angelo stores and surfaces them but never
computes with them (§2.2).
Source code in zettelkasten/data_grounding.py
RegisteredDerivation
dataclass
¶
A host-contributed derivation plus the metadata angelo verifies against.
Source code in zettelkasten/data_grounding.py
DerivationLookupError ¶
Bases: LookupError
Raised when (id, version) is not registered in this environment.
Distinct so callers can map it to the SOFT derivation_unavailable outcome
rather than a hard violation (§4).
register_derivation ¶
register_derivation(id: str, version: Any, *, fit: bool = False, deterministic: bool = True) -> Callable[[Callable[..., DerivationResult]], Callable[..., DerivationResult]]
Decorator registering a derivation callable keyed by (id, version) (§3.1).
The wrapped callable must have signature (table, selection, params) ->
DerivationResult. fit distinguishes a fitted (inferred) derivation
from a descriptive (measured) one; deterministic=False marks a
stochastic derivation that must pin an RNG seed in params (§3.2).
Source code in zettelkasten/data_grounding.py
get_derivation ¶
get_derivation(id: str, version: Any) -> RegisteredDerivation
Look up a registered derivation, raising :class:DerivationLookupError.
Source code in zettelkasten/data_grounding.py
eval_expr ¶
Evaluate a whitelisted expression over a resolved dataset table (§3.3).
Supported forms (SAFE — split on : and dispatch, never eval/exec,
no attribute access, no calls, no imports)::
mean:col median:col sum:col std:col count:col min:col max:col
quantile:col:<q> # q in [0, 1], linear interpolation (type 7)
table is a :class:zettelkasten.datasets.DatasetTable (anything exposing
columns and rows). Numeric reducers coerce cells to float and drop
missing/non-numeric cells; count counts non-missing cells of any dtype.
Raises ValueError for an unrecognized form or an unknown column.
LIMITATION (report item 10): because the form is split on :, a column
whose NAME contains a : cannot be addressed through this hatch (the split
would mis-parse it as reducer:col:extra). Such a column must be renamed,
or grounded via a registered derivation (which receives the raw table and
selects columns itself) instead of the expr hatch.
Source code in zettelkasten/data_grounding.py
apply_selection ¶
Apply a simple, deterministic where/columns filter (§4).
selection may be None/empty (identity) or a dict with:
where— a predicate string; a conjunction of simple comparisons joined byand(case-insensitive), eachcol <op> literalwithopone of>= <= == != > < =. This is deliberately minimal so descriptive claims re-verify anywhere; a richer predicate belongs in a registered derivation (which receives the raw table + selection and applies its own filter).columns— an optional list restricting/ordering columns.
Any other key raises ValueError (an unrecognized selection is a broken
pin, not silently ignored).
Source code in zettelkasten/data_grounding.py
table_content_hash ¶
Deterministic sha256: hash identifying a resolved table's contents.
Canonicalization (order-independent, so a benign row/column reordering of the
SAME data hashes identically): columns are sorted; each row's cells are
permuted to that sorted column order and normalized via :func:_canon_cell
(consistent float formatting, explicit NaN/null tokens); the normalized row
strings are then sorted. The digest covers the sorted column header plus the
sorted normalized rows.
Source code in zettelkasten/data_grounding.py
verify_data_grounding ¶
Re-run a method: data grounding and report the structured outcome (§4).
note is a claim :class:zettelkasten.graph.Note whose grounding is a
method: data citation; graph_dir is the source-folder path the note
lives in (ZettelGraph.path). Never raises for an ordinary failure — every
hard violation and soft unavailability is returned as a structured dict. Only
a truly unexpected internal bug propagates.
Steps: validate validation shape → re-resolve the dataset note →
content-hash the resolved table and compare to the pin → resolve the
derivation (expr hatch or registry) → re-run and compare value within
tolerance. A clean reproduce returns {verified: True, value: <recomputed>,
...}.
Source code in zettelkasten/data_grounding.py
665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 | |