Skip to content

Consuming a saved ProfilingResult

fit_profiler(...).save(path) is the producer. This guide is the consumer side: how to load a saved profiling_result.joblib and read back every artifact family — archetypes, held-out evaluation, mined rules, global explanations, EDA, per-class structure, and similar-case retrieval — without re-fitting the model.

In short

  • ProfilingResult.load(path) restores every computed artifact; nothing is refitted.
  • The full training X is never saved. Pass X= again to recompute an artifact.
  • Every family is optional: check for None before you read one.
  • A result from an incompatible schema is refused with StaleArtifactError; recompute it.
  • AI narration is stored separately; reattach it with attach_narration.

A runnable, end-to-end version of everything below ships as demo_consume_result.py, which produces a tiny fixture on first run and then reloads it fresh from disk. The demo is in the source checkout under examples/demos/; it is not part of the installed wheel.

Load

from parleml_mli import ProfilingResult

result = ProfilingResult.load("out/profiling_result.joblib")

The loaded object exposes the full ProfilingResult API — the same methods the live fit returns. Two things to know:

  • The full training X is never persisted on the result. Artifacts that need it (rule re-extraction, some plots) accept an explicit X= argument at call time; everything already computed (rule text, fingerprints, cached metrics) is self-contained.
  • If the result was saved with save(path, include_model=False), leaf-space retrieval raises at call time (the estimator is gone). Computed artifacts still load, but model-dependent operations such as native importance, glass-box structure and new predictions require a model.

load() checks the saved schema envelope and nested artifact schemas. An unstamped or incompatible artifact raises parleml_mli.StaleArtifactError; recompute it with the current library. Different recorded library versions produce warnings, so a saved profile is not a promise of compatibility across arbitrary upgrades. Attached AI narration is dropped by save() and must be reattached separately.

One-glance inventory

Start with the artifact summary — a presence-guarded headline of which families are populated and on which backend:

from parleml_mli._artifact_summary import format_artifact_summary

print(format_artifact_summary(result))
ProfilingResult artifacts  [backend=native]
====================================================
  archetypes            : 6  (binary)
  model_evaluation      : -
  rules                 : 23
  explanations          : pdp(4), permutation_importance
  eda                   : 2 alert(s), 0 leakage finding(s)
  stability             : -
  narration             : -

Reading each artifact family

Every family is presence-guarded, so a consumer that doesn't know how the result was produced can safely check before reading.

Archetypes

result.summary_df          # one row per surviving archetype (size + stats)
result.labels_             # cluster assignment per target-cohort row
result.clustering_result.n_clusters

Per-class structure

result.classes is a dict[str, ClassProfile] — a single entry for a binary run, one per class for multiclass. Index the result directly by class key:

for key in result.class_keys:
    profile = result[key]
    print(key, len(profile.cluster_ids()), profile.base_target_rate)

Mined rules

For a single-class (binary) run the ruleset lives on the top-level result.rules_ mirror; a multiclass run fans the rules out per class onto result[key].rules (a {engine: RuleSetSummary} map):

# binary / single-class
if result.rules_ is not None and result.rules_.default_engine_summary is not None:
    for rule in result.rules_.default_engine_summary.rules[:5]:
        print(rule.text)

# multiclass
for key in result.class_keys:
    for summary in result[key].rules.values():
        for rule in (summary.rules if summary else [])[:5]:
            print(key, rule.text)

Global explanations

expl = result.explanations_
if expl is not None:
    if expl.pdp:                       # dict: feature -> PDP curve
        print(list(expl.pdp))
    if expl.permutation_importance is not None:
        ...                            # scalar family: present or None

EDA & held-out evaluation

if result.eda_ is not None:
    print(len(result.eda_.alerts), "data-quality alert(s)")

if result.model_evaluation_ is not None:
    print(result.model_evaluation_)    # per-split Brier / ECE / calibration

Similar-case retrieval

Score brand-new rows against the saved archetypes — no re-fit, and in leaf space no model call is needed beyond the persisted estimator:

retrievals = result.retrieve_new_cases(X_new, top_k=3)
for r in retrievals:
    print(r.nearest_cluster, r.is_novel, r.distance_to_nearest_medoid)

When X_target_orig= was supplied at fit time, each retrieval also carries the business-readable nearest_original_features frame.

Re-rendering the report

A loaded result can regenerate its HTML/JSON report without a re-fit. The report is deterministic; to embed a narration the host stored, attach it first (see AI-written narration):

from parleml_mli.reporting import attach_narration

attach_narration(result, narration)   # optional: the stored narrate_result dict
result.generate_report(output_dir="out")

Next