Skip to content

v0.8.11

Choose a tag to compare

@github-actions github-actions released this 04 Sep 15:55
· 26 commits to master since this release
a996a1e
  • topological_order no longer nondeterministic: the Kahn's-algorithm
    helpers in backend/ml_pipeline/_execution/graph_utils.py were seeded from
    a set of node ids, whose iteration order depends on insertion history —
    so topological_order(topological_order(nodes)) could return a different
    (still valid) order than topological_order(nodes), breaking idempotency
    and making execution order depend on how the node list happened to be
    built. The helpers now take a list[str] in deterministic order:
    topological_order passes input-list order (deduped via
    dict.fromkeys), and _collect_ancestors passes sorted() BFS results.
    Among simultaneously ready nodes the input-list order is preserved, so the
    result is deterministic and idempotent.

  • SonarCloud PR analysis flagged Vite build output: all 46 open issues on
    PR #146 were in static/ml_canvas/assets/*.js — minified frontend build
    artifacts that SonarCloud's PR analysis picked up because the files changed
    in the PR. static/ml_canvas/** is now in sonar.exclusions in
    sonar-project.properties, so build output is no longer analyzed (the
    scanner job in .github/workflows/pr_check.yml reads this file directly).

  • Drop-Rows canvas settings ignored (OC-13): the Drop Rows node's UI
    stores a 0–100% "drop rows missing more than X%" slider, but the converter
    sent keys the backend never read, so every canvas run silently ran as
    "drop any missing". DropMissingRows now accepts a missing_threshold
    percentage param (pandas + polars parity, mirroring DropMissingColumns):
    rows with more than X% of the subset missing are dropped, and the
    converter maps the checkbox / null / 0% to how="any". The
    DropMissingRowsArtifact gains the new field.

  • Iterative Imputer canvas estimator choices ignored (OC-14): the
    Imputation node's UI emits lowercase estimator aliases (decision_tree,
    extra_trees, knn, bayesian_ridge) and the converter forwards them
    verbatim, but _build_iterative_estimator in
    preprocessing/imputation/_common.py matched only the exact documented
    strings (DecisionTree, ExtraTrees, KNeighbors) — so every canvas run
    silently fell back to BayesianRidge regardless of the user's choice. The
    alias is now normalized (lowercased, non-alphanumerics stripped) before
    dispatch, so both the UI values and the documented aliases resolve to the
    same regressor; unknown names still fall back to BayesianRidge. Added
    JSON-driven alias cases for the four UI values.

  • MinMax/Robust scaler range controls ignored on canvas (OC-15): the
    Scaling node's UI stores the ranges as scalar fields
    (feature_range_min/feature_range_max, quantile_range_min/
    quantile_range_max), but the converter forwarded them verbatim while the
    backend reads tuple keys (feature_range/quantile_range) — so every
    canvas run silently used the defaults (0/1 and 25/75). The converter's
    scale_numeric_features branch now assembles the tuple keys from the
    scalar fields (with the same defaults); no backend change was needed.
    Added 4 vitest cases to pipelineConverter.test.ts.

  • Alias Replacement punctuation mode was a silent no-op (OC-19): the
    Standardize Values node's UI offers a punctuation mode ("Removes common
    punctuation characters from text"), but the backend applier in
    preprocessing/cleaning/alias.py only handled the alias-mapping modes —
    punctuation resolved to an empty mapping, so the node did nothing. Both
    engine paths now strip string.punctuation only (case and spaces preserved,
    matching the UI wording; NaN passes through unchanged). Added JSON-driven
    cases plus a pandas/polars NaN-passthrough parity test.

  • Value Replacement UI help text contradicted actual behavior (OC-20): the
    Standardize Values node's UI help text promised "If empty, applies to all
    compatible columns," but the backend no-ops on an empty columns list — the
    intended repo convention (user_picked_no_columns: unchecked boxes mean "do
    nothing for this node"). The help text now reads "Select columns to apply
    replacements to. If no columns are selected, this node does nothing." No
    backend change: the empty-columns no-op was already correct. Added a
    contract-locking test (test_apply_empty_columns_is_noop, pandas/polars)
    asserting empty columns + a mapping leaves data unchanged.

  • select_from_model's max_features was Python-only (OC-53): the
    backend reads config.get("max_features") and passes it to sklearn's
    SelectFromModel, but the Feature Selection node's UI rendered only a
    threshold field for the select_from_model method — so the feature-count
    cap was unreachable from the canvas. The UI now offers an optional
    "Max Features" numeric input for that method (empty = no cap, matching the
    backend's None default). No converter change: the feature_selection
    branch already forwards node.data unchanged. Added 2 vitest cases to
    pipelineConverter.test.ts.

  • Binning's "Precision (Decimals)" was UI-unreachable (OC-61): the
    backend (bucketing.py) reads config.get("precision", 3) and the canvas
    Binning node rendered a "Precision (Decimals)" input, but
    pipelineConverter.ts listed the GeneralBinning params explicitly and
    omitted precision, so the value was silently dropped. The converter now
    forwards precision (omitted when unset, matching the backend's default of
    3). Added 2 vitest cases to pipelineConverter.test.ts.

  • CalibratedClassifierCV base estimator discarded during tuning (OC-66):
    the tuning engine builds the meta-estimator from
    model_calculator.default_params, which hardcoded
    estimator=LogisticRegression — so the user's base_estimator selection
    was silently ignored whenever the node was tuned (it only took effect in
    plain fit). CalibratedClassifierCalculator now routes the selection
    through the structural-tuning hook (same mechanism as the ensemble
    calculators): prepare_tuning_params captures base_estimator (flat or
    nested params shape) and default_params resolves it via the
    BASE_ESTIMATORS factory, with a warn-and-fallback to
    logistic_regression for unknown keys. No backend change: both the
    fixed-run and tuned paths already call prepare_tuning_params and exclude
    STRUCTURAL_TUNING_KEYS from the search space. Added unit tests
    (capture, resolution, fallback) and an integration test asserting the tuned
    pipeline fits a RandomForestClassifier inside CalibratedClassifierCV.

  • KNN/Iterative imputers crashed on all-missing fitted columns (OC-16):
    sklearn's KNNImputer/IterativeImputer silently drop all-missing columns
    from transform() output when the column was all-missing at fit time,
    desyncing the artifact's columns list from the imputer's width and
    crashing _sklearn_transform_subset (IndexError in the polars branch,
    ValueError in the pandas branch). Both calculators now call the new
    drop_all_missing_columns helper in preprocessing/imputation/_common.py
    during fit: all-missing columns are dropped from the fit matrix (with a
    warning naming them) so the artifact stays in lockstep with the imputer's
    width, and fit returns an empty artifact when every configured column is
    all-missing (appliers pass that through as a no-op). Added 5 regression
    tests (parametrized over KNN + Iterative, pandas + polars).

  • SimpleImputer polars mean/median crashed on all-null columns (OC-17):
    fitting a SimpleImputer with mean/median on an entirely-null column
    stored fill_values[col] = None in the polars path (polars mean()/
    median() over an all-null column returns None), and _apply_polars
    then called pl.col(col).fill_null(None) — which raises ValueError: must specify either a fill value or strategy. The pandas path already skipped
    None fills, so the engines diverged (pandas left the column all-null;
    polars crashed). _apply_polars now passes the column through unchanged
    when its fill value is None, mirroring _apply_pandas. Added 3 regression
    tests (polars no-crash for mean and median, plus a pandas/polars
    engine-parity test).

  • Engine trusted config.nodes list order without verifying topological
    sort (OC-69):
    both predict_schemas and the engine's node loop iterated
    config.nodes as-is, but validate_no_cycles() only detects cycles — it
    never restores order. The canvas converter's BFS can emit an
    acyclic-but-misordered list (a merge node is enqueued when any parent is
    dequeued, not all), so a diamond merge fed by unequal-depth branches failed
    with a cryptic "Artifact not found" error and silent None schema
    degradation. A new public topological_order() in graph_utils.py (reusing
    the existing Kahn's-algorithm helpers) now re-sorts the node list at both
    consumer sites. Added 5 regression tests including a full engine e2e run of
    a misordered diamond.

  • Multiclass splits missing a class emitted binary-only metrics + null
    curve points (OC-35):
    three places decided binary-vs-multiclass from the
    labels present in y_true instead of the model's trained label set, so a
    3-class model evaluated on a split containing only two classes was
    misclassified as binary — it gained unweighted precision/recall/f1
    keys, lost a computable log_loss (sklearn raised "2 vs 3. Please provide
    labels"), and emitted ROC/PR curve points with null coordinates (NaN from
    roc_curve on the absent class's all-zero one-vs-rest target). The binary
    gate in _add_binary_unweighted_metrics now resolves model.classes_ and
    is binary iff len(classes_) == 2 (falling back to the unique-label count
    only when the model exposes no classes_); log_loss is now called with
    labels=classes (the full trained label set); and the per-class curve loop
    in evaluate_classification_model skips any class absent from the split.
    Added 3 regression tests.