validmind 2.13.7__tar.gz → 2.13.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {validmind-2.13.7 → validmind-2.13.9}/PKG-INFO +1 -1
- {validmind-2.13.7 → validmind-2.13.9}/pyproject.toml +1 -1
- validmind-2.13.9/validmind/__version__.py +1 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/api_client.py +166 -25
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/CalibrationCurve.py +13 -1
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/PopulationStabilityIndex.py +31 -46
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/PrecisionRecallCurve.py +18 -40
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/ROCCurve.py +18 -41
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/WeakspotsDiagnosis.py +12 -7
- validmind-2.13.9/validmind/tests/model_validation/sklearn/_multiclass_proba.py +150 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/GINITable.py +19 -41
- validmind-2.13.7/validmind/__version__.py +0 -1
- {validmind-2.13.7 → validmind-2.13.9}/.gitignore +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/LICENSE +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/README.pypi.md +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/ai/test_descriptions.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/ai/utils.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/client.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/client_config.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/credentials_store.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/classification/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/classification/config.json +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/classification/customer_churn.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/classification/datasets/bank_customer_churn.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/classification/datasets/taiwan_credit.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/classification/taiwan_credit.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/cluster/digits.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/credit_risk/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/credit_risk/datasets/lending_club_biased.csv.gz +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/credit_risk/datasets/lending_club_loan_data_2007_2014_clean.csv.gz +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/credit_risk/lending_club.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/credit_risk/lending_club_bias.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/llm/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/llm/agent_dataset.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/llm/rag/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/llm/rag/datasets/rfp_existing_questions_client_1.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/llm/rag/datasets/rfp_existing_questions_client_2.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/llm/rag/datasets/rfp_existing_questions_client_3.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/llm/rag/datasets/rfp_existing_questions_client_4.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/llm/rag/datasets/rfp_existing_questions_client_5.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/llm/rag/rfp.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/nlp/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/nlp/cnn_dailymail.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/nlp/datasets/Covid_19.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/nlp/datasets/cnn_dailymail_100_with_predictions.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/nlp/datasets/cnn_dailymail_500_with_predictions.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/nlp/datasets/sentiments_with_predictions.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/nlp/twitter_covid_19.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/california_housing.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/california_housing.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred/CPIAUCSL.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred/CSUSHPISA.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred/DRSFRMACBS.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred/FEDFUNDS.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred/GDP.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred/GDPC1.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred/GS10.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred/GS3.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred/GS5.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred/MORTGAGE30US.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred/UNRATE.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred_loan_rates.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred_loan_rates_test_1.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred_loan_rates_test_2.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred_loan_rates_test_3.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred_loan_rates_test_4.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred_loan_rates_test_5.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/leanding_club_loan_rates.csv +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/fred.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/fred_timeseries.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/generate_california_housing_csv.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/lending_club.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/errors.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/experimental/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/experimental/agents.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/html_templates/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/html_templates/content_blocks.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/input_registry.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/logging.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/models/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/models/foundation.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/models/function.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/models/huggingface.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/models/metadata.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/models/pipeline.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/models/pytorch.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/models/r_model.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/models/sklearn.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/oidc_device.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/classification/AbsoluteError.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/classification/BrierScore.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/classification/CalibrationError.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/classification/ClassBalance.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/classification/Confidence.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/classification/Correctness.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/classification/LogLoss.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/classification/OutlierScore.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/classification/ProbabilityError.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/classification/Uncertainty.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/classification/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/AnswerRelevancy.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/ArgumentCorrectness.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/Bias.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/ContextualPrecision.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/ContextualRecall.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/ContextualRelevancy.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/Faithfulness.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/GEval.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/Hallucination.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/PlanAdherence.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/PlanQuality.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/Summarization.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/TaskCompletion.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/ToolCorrectness.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/template.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/classifier.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/cluster.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/embeddings.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/llm.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/nlp.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/parameters_optimization.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/regression.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/statsmodels_timeseries.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/summarization.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/tabular_datasets.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/text_data.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/time_series.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/__types__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/_store.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/comparison.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ACFandPACFPlot.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ADF.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/AutoAR.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/AutoMA.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/AutoStationarity.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/BivariateScatterPlots.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/BoxPierce.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ChiSquaredFeaturesTable.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ClassImbalance.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/DatasetDescription.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/DatasetSplit.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/DescriptiveStatistics.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/DickeyFullerGLS.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/Duplicates.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/EngleGrangerCoint.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/FeatureTargetCorrelationPlot.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/HighCardinality.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/HighPearsonCorrelation.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/IQROutliersBarPlot.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/IQROutliersTable.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/IsolationForestOutliers.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/JarqueBera.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/KPSS.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/LJungBox.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/LaggedCorrelationHeatmap.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/MissingValues.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/MissingValuesBarPlot.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/MutualInformation.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/PearsonCorrelationMatrix.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/PhillipsPerronArch.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ProtectedClassesCombination.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ProtectedClassesDescription.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ProtectedClassesDisparity.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ProtectedClassesThresholdOptimizer.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/RollingStatsPlot.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/RunsTest.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ScatterPlot.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ScoreBandDefaultRates.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/SeasonalDecompose.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ShapiroWilk.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/Skewness.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/SpreadPlot.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TabularCategoricalBarPlots.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TabularDateTimeHistograms.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TabularDescriptionTables.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TabularNumericalHistograms.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TargetRateBarPlots.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TimeSeriesDescription.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TimeSeriesDescriptiveStatistics.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TimeSeriesFrequency.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TimeSeriesHistogram.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TimeSeriesLinePlot.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TimeSeriesMissingValues.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TimeSeriesOutliers.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TooManyZeroValues.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/UniqueRows.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/WOEBinPlots.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/WOEBinTable.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ZivotAndrewsArch.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/nlp/CommonWords.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/nlp/Hashtags.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/nlp/LanguageDetection.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/nlp/Mentions.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/nlp/PolarityAndSubjectivity.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/nlp/Punctuations.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/nlp/Sentiment.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/nlp/StopWords.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/nlp/TextDescription.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/nlp/Toxicity.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/nlp/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/decorator.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/load.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/BertScore.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/BleuScore.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ClusterSizeDistribution.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ContextualRecall.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/FeaturesAUC.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/MeteorScore.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ModelMetadata.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ModelPredictionResiduals.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/RegardScore.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/RegressionResidualsPlot.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/RougeScore.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/TimeSeriesPredictionWithCI.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/TimeSeriesPredictionsPlot.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/TimeSeriesR2SquareBySegments.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/TokenDisparity.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ToxicityScore.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/ClusterDistribution.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/CosineSimilarityComparison.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/CosineSimilarityDistribution.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/CosineSimilarityHeatmap.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/DescriptiveAnalytics.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/EmbeddingsVisualization2D.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/EuclideanDistanceComparison.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/EuclideanDistanceHeatmap.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/PCAComponentsPairwisePlots.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/StabilityAnalysisKeyword.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/StabilityAnalysisRandomNoise.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/StabilityAnalysisSynonyms.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/StabilityAnalysisTranslation.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/TSNEComponentsPairwisePlots.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/utils.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ragas/AnswerCorrectness.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ragas/AspectCritic.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ragas/ContextEntityRecall.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ragas/ContextPrecision.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ragas/ContextPrecisionWithoutReference.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ragas/ContextRecall.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ragas/Faithfulness.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ragas/NoiseSensitivity.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ragas/ResponseRelevancy.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ragas/SemanticSimilarity.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ragas/utils.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/AdjustedMutualInformation.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/AdjustedRandIndex.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/ClassifierPerformance.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/ClassifierThresholdOptimization.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/ClusterCosineSimilarity.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/ClusterPerformanceMetrics.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/CompletenessScore.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/ConfusionMatrix.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/FeatureImportance.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/FowlkesMallowsScore.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/HomogeneityScore.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/HyperParametersTuning.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/KMeansClustersOptimization.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/MinimumAccuracy.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/MinimumF1Score.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/MinimumROCAUCScore.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/ModelParameters.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/ModelsPerformanceComparison.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/OverfitDiagnosis.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/PermutationFeatureImportance.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/RegressionErrors.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/RegressionErrorsComparison.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/RegressionPerformance.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/RegressionR2Square.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/RegressionR2SquareComparison.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/RobustnessDiagnosis.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/SHAPGlobalImportance.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/ScoreProbabilityAlignment.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/SilhouettePlot.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/TrainingTestDegradation.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/VMeasure.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/_diagnosis_metrics.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/AutoARIMA.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/CumulativePredictionProbabilities.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/DurbinWatsonTest.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/KolmogorovSmirnov.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/Lilliefors.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/PredictionProbabilitiesHistogram.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/RegressionCoeffs.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/RegressionFeatureSignificance.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/RegressionModelForecastPlot.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/RegressionModelForecastPlotLevels.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/RegressionModelSensitivityPlot.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/RegressionModelSummary.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/RegressionPermutationFeatureImportance.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/ScorecardHistogram.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/statsutils.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/CalibrationCurveDrift.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/ClassDiscriminationDrift.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/ClassImbalanceDrift.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/ClassificationAccuracyDrift.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/ConfusionMatrixDrift.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/CumulativePredictionProbabilitiesDrift.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/FeatureDrift.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/PredictionAcrossEachFeature.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/PredictionCorrelation.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/PredictionProbabilitiesHistogramDrift.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/PredictionQuantilesAcrossFeatures.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/ROCCurveDrift.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/ScoreBandsDrift.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/ScorecardHistogramDrift.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/TargetPredictionDistributionPlot.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/output.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/plots/BoxPlot.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/plots/CorrelationHeatmap.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/plots/HistogramPlot.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/plots/ViolinPlot.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/plots/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/prompt_validation/Bias.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/prompt_validation/Clarity.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/prompt_validation/Conciseness.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/prompt_validation/Delimitation.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/prompt_validation/NegativeInstruction.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/prompt_validation/Robustness.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/prompt_validation/Specificity.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/prompt_validation/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/prompt_validation/ai_powered_test.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/run.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/stats/CorrelationAnalysis.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/stats/DescriptiveStats.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/stats/NormalityTests.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/stats/OutlierDetection.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/stats/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/test_providers.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/utils.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/classification/Accuracy.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/classification/F1.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/classification/Precision.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/classification/ROC_AUC.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/classification/Recall.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/regression/AdjustedRSquaredScore.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/regression/GiniCoefficient.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/regression/HuberLoss.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/regression/KolmogorovSmirnovStatistic.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/regression/MeanAbsoluteError.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/regression/MeanAbsolutePercentageError.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/regression/MeanBiasDeviation.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/regression/MeanSquaredError.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/regression/QuantileLoss.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/regression/RSquaredScore.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/regression/RootMeanSquaredError.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/utils.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/dataset/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/dataset/dataset.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/dataset/utils.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/figure.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/html_progress.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/html_renderer.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/input.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/model.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/result/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/result/pii_filter.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/result/result.jinja +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/result/result.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/result/utils.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/test_suite/__init__.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/test_suite/runner.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/test_suite/summary.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/test_suite/test.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/test_suite/test_suite.py +0 -0
- {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/text_generation_summary.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: validmind
|
|
3
|
-
Version: 2.13.
|
|
3
|
+
Version: 2.13.9
|
|
4
4
|
Summary: ValidMind Library
|
|
5
5
|
Author-email: Andres Rodriguez <andres@validmind.ai>, Juan Martinez <juan@validmind.ai>, Anil Sorathiya <anil@validmind.ai>, Luis Pallares <luis@validmind.ai>, John Walz <john@validmind.ai>
|
|
6
6
|
License: DUAL LICENSE NOTICE
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.13.9"
|
|
@@ -11,6 +11,7 @@ import asyncio
|
|
|
11
11
|
import atexit
|
|
12
12
|
import json
|
|
13
13
|
import os
|
|
14
|
+
import threading
|
|
14
15
|
from io import BytesIO
|
|
15
16
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
16
17
|
from urllib.parse import urlencode, urljoin
|
|
@@ -42,6 +43,10 @@ _monitoring = False
|
|
|
42
43
|
_auth_mode = "api_key"
|
|
43
44
|
_access_token: Optional[str] = None
|
|
44
45
|
_oidc_login_context: Optional[Dict[str, str]] = None
|
|
46
|
+
# Expiry (ISO-8601) of the in-memory OIDC access token, for cheap request-path
|
|
47
|
+
# staleness checks; guarded together with the token by _oidc_refresh_lock.
|
|
48
|
+
_oidc_expires_at: Optional[str] = None
|
|
49
|
+
_oidc_refresh_lock = threading.Lock()
|
|
45
50
|
|
|
46
51
|
__api_session: Optional[aiohttp.ClientSession] = None
|
|
47
52
|
|
|
@@ -147,12 +152,21 @@ def _get_url(
|
|
|
147
152
|
async def _get(
|
|
148
153
|
endpoint: str, params: Optional[Dict[str, str]] = None
|
|
149
154
|
) -> Dict[str, Any]:
|
|
155
|
+
_ensure_fresh_oidc_token()
|
|
150
156
|
url = _get_url(endpoint, params)
|
|
151
157
|
session = _get_session()
|
|
152
158
|
|
|
153
159
|
async with session.get(url) as r:
|
|
160
|
+
# A 401 can still slip through if the token was revoked ahead of its
|
|
161
|
+
# stated expiry; force a refresh and retry once (safe — GET has no body).
|
|
162
|
+
if r.status == 401 and _ensure_fresh_oidc_token(force=True):
|
|
163
|
+
session = _get_session()
|
|
164
|
+
async with session.get(url) as retry:
|
|
165
|
+
if retry.status != 200:
|
|
166
|
+
_raise_for_api_error(retry.status, await retry.text())
|
|
167
|
+
return await retry.json()
|
|
154
168
|
if r.status != 200:
|
|
155
|
-
|
|
169
|
+
_raise_for_api_error(r.status, await r.text())
|
|
156
170
|
|
|
157
171
|
return await r.json()
|
|
158
172
|
|
|
@@ -163,6 +177,7 @@ async def _post(
|
|
|
163
177
|
data: Optional[Union[dict, FormData]] = None,
|
|
164
178
|
files: Optional[Dict[str, Tuple[str, BytesIO, str]]] = None,
|
|
165
179
|
) -> Dict[str, Any]:
|
|
180
|
+
_ensure_fresh_oidc_token()
|
|
166
181
|
url = _get_url(endpoint, params)
|
|
167
182
|
session = _get_session()
|
|
168
183
|
|
|
@@ -186,20 +201,29 @@ async def _post(
|
|
|
186
201
|
_data = data
|
|
187
202
|
|
|
188
203
|
async with session.post(url, data=_data) as r:
|
|
204
|
+
# Not retried on 401: the request body / upload stream may already be
|
|
205
|
+
# consumed. Proactive refresh above covers the expiry case; a 401 here
|
|
206
|
+
# means a genuinely rejected token, surfaced as a clear auth error.
|
|
189
207
|
if r.status != 200:
|
|
190
|
-
|
|
208
|
+
_raise_for_api_error(r.status, await r.text())
|
|
191
209
|
|
|
192
210
|
return await r.json()
|
|
193
211
|
|
|
194
212
|
|
|
195
213
|
def _ping() -> Dict[str, Any]:
|
|
196
214
|
"""Validates that we can connect to the ValidMind API (does not use the async session)."""
|
|
215
|
+
_ensure_fresh_oidc_token()
|
|
197
216
|
r = requests.get(
|
|
198
217
|
url=_get_url("ping"),
|
|
199
218
|
headers=_get_api_headers(),
|
|
200
219
|
)
|
|
220
|
+
if r.status_code == 401 and _ensure_fresh_oidc_token(force=True):
|
|
221
|
+
r = requests.get(
|
|
222
|
+
url=_get_url("ping"),
|
|
223
|
+
headers=_get_api_headers(),
|
|
224
|
+
)
|
|
201
225
|
if r.status_code != 200:
|
|
202
|
-
|
|
226
|
+
_raise_for_api_error(r.status_code, r.text)
|
|
203
227
|
|
|
204
228
|
client_info = r.json()
|
|
205
229
|
|
|
@@ -224,6 +248,36 @@ def _ping() -> Dict[str, Any]:
|
|
|
224
248
|
)
|
|
225
249
|
|
|
226
250
|
|
|
251
|
+
def _refresh_oidc_from_cache(
|
|
252
|
+
issuer: str,
|
|
253
|
+
client_id: str,
|
|
254
|
+
refresh_token: str,
|
|
255
|
+
scope: Optional[str],
|
|
256
|
+
audience: Optional[str] = None,
|
|
257
|
+
) -> Optional[Dict[str, Any]]:
|
|
258
|
+
"""Exchange a cached refresh token for new tokens, persisting the result.
|
|
259
|
+
|
|
260
|
+
Shared by the ``init()`` path (``_obtain_oidc_tokens``) and the request path
|
|
261
|
+
(``_ensure_fresh_oidc_token``) so their refresh behaviour can't drift. On
|
|
262
|
+
success the refreshed entry is cached and returned. On a failed refresh (e.g.
|
|
263
|
+
revoked token / ``invalid_grant``) the stale entry is deleted and None is
|
|
264
|
+
returned, so a dead refresh token isn't re-attempted; the caller decides how
|
|
265
|
+
to recover (``init()`` falls back to device flow, the request path stops).
|
|
266
|
+
"""
|
|
267
|
+
from .credentials_store import delete_cached_entry, upsert_cached_entry
|
|
268
|
+
from .oidc_device import try_refresh_cached_tokens
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
new_tokens = try_refresh_cached_tokens(
|
|
272
|
+
issuer, client_id, refresh_token, scope, audience=audience
|
|
273
|
+
)
|
|
274
|
+
except ValidMindAuthError:
|
|
275
|
+
delete_cached_entry(issuer, client_id, audience=audience)
|
|
276
|
+
return None
|
|
277
|
+
upsert_cached_entry(issuer, client_id, new_tokens, audience=audience)
|
|
278
|
+
return new_tokens
|
|
279
|
+
|
|
280
|
+
|
|
227
281
|
def _obtain_oidc_tokens(
|
|
228
282
|
issuer: str,
|
|
229
283
|
client_id: str,
|
|
@@ -232,14 +286,13 @@ def _obtain_oidc_tokens(
|
|
|
232
286
|
) -> Dict[str, Any]:
|
|
233
287
|
"""Return a credentials entry dict with access_token, expires_at, refresh_token, etc."""
|
|
234
288
|
from .credentials_store import (
|
|
235
|
-
delete_cached_entry,
|
|
236
289
|
get_cached_entry,
|
|
237
290
|
is_expired,
|
|
238
291
|
normalize_client_id,
|
|
239
292
|
normalize_issuer,
|
|
240
293
|
upsert_cached_entry,
|
|
241
294
|
)
|
|
242
|
-
from .oidc_device import run_device_flow
|
|
295
|
+
from .oidc_device import run_device_flow
|
|
243
296
|
|
|
244
297
|
norm_issuer = normalize_issuer(issuer)
|
|
245
298
|
norm_client_id = normalize_client_id(client_id)
|
|
@@ -247,20 +300,11 @@ def _obtain_oidc_tokens(
|
|
|
247
300
|
if cached and not is_expired(cached):
|
|
248
301
|
return cached
|
|
249
302
|
if cached and cached.get("refresh_token"):
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
cached["refresh_token"],
|
|
255
|
-
scope,
|
|
256
|
-
audience=audience,
|
|
257
|
-
)
|
|
258
|
-
upsert_cached_entry(
|
|
259
|
-
norm_issuer, norm_client_id, new_tokens, audience=audience
|
|
260
|
-
)
|
|
303
|
+
new_tokens = _refresh_oidc_from_cache(
|
|
304
|
+
norm_issuer, norm_client_id, cached["refresh_token"], scope, audience
|
|
305
|
+
)
|
|
306
|
+
if new_tokens is not None:
|
|
261
307
|
return new_tokens
|
|
262
|
-
except ValidMindAuthError:
|
|
263
|
-
delete_cached_entry(norm_issuer, norm_client_id, audience=audience)
|
|
264
308
|
tokens = run_device_flow(norm_issuer, norm_client_id, scope, audience=audience)
|
|
265
309
|
upsert_cached_entry(norm_issuer, norm_client_id, tokens, audience=audience)
|
|
266
310
|
return tokens
|
|
@@ -276,6 +320,100 @@ def _select_oidc_bearer_token(entry: Dict[str, Any]) -> str:
|
|
|
276
320
|
return entry["access_token"]
|
|
277
321
|
|
|
278
322
|
|
|
323
|
+
def _set_oidc_access_token(entry: Dict[str, Any]) -> None:
|
|
324
|
+
"""Adopt the bearer token from a credentials entry as the active token.
|
|
325
|
+
|
|
326
|
+
Records its expiry for cheap request-path staleness checks and drops the
|
|
327
|
+
pooled aiohttp session so the next request rebuilds it with the new token.
|
|
328
|
+
"""
|
|
329
|
+
global _access_token, _oidc_expires_at
|
|
330
|
+
_access_token = _select_oidc_bearer_token(entry)
|
|
331
|
+
_oidc_expires_at = entry.get("expires_at")
|
|
332
|
+
_invalidate_async_session()
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _clear_oidc_access_token() -> None:
|
|
336
|
+
"""Drop the in-memory OIDC token and pooled session after an unrecoverable refresh.
|
|
337
|
+
|
|
338
|
+
Pairs with deleting the cached entry: with no usable token in memory, the next
|
|
339
|
+
request fails fast at header build with the clear "run vm.init()" error instead
|
|
340
|
+
of sending a doomed request just to get a 401 back.
|
|
341
|
+
"""
|
|
342
|
+
global _access_token, _oidc_expires_at
|
|
343
|
+
_access_token = None
|
|
344
|
+
_oidc_expires_at = None
|
|
345
|
+
_invalidate_async_session()
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _oidc_token_is_stale() -> bool:
|
|
349
|
+
"""Cheap in-memory expiry check (no file I/O) using the 120s skew in is_expired."""
|
|
350
|
+
from .credentials_store import is_expired
|
|
351
|
+
|
|
352
|
+
return is_expired({"expires_at": _oidc_expires_at})
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _ensure_fresh_oidc_token(force: bool = False) -> bool:
|
|
356
|
+
"""Refresh the OIDC access token on the request path when it has expired.
|
|
357
|
+
|
|
358
|
+
The token captured at ``init()`` is otherwise reused unchanged for the life of
|
|
359
|
+
the process, so a session that outlives the provider's access-token lifetime
|
|
360
|
+
starts failing on every call until ``init()`` is re-run. This uses the cached
|
|
361
|
+
refresh token (requires ``offline_access``, the default scope) to obtain a new
|
|
362
|
+
access token in place. Returns True when a usable token is in place, False when
|
|
363
|
+
no refresh was possible (non-OIDC mode, no cached refresh token, or a failed
|
|
364
|
+
refresh — in which case the cached entry is dropped, matching init()).
|
|
365
|
+
"""
|
|
366
|
+
if _auth_mode != "oidc" or _oidc_login_context is None:
|
|
367
|
+
return False
|
|
368
|
+
# Hot path: skip file I/O and the lock while the current token is still valid.
|
|
369
|
+
if not force and not _oidc_token_is_stale():
|
|
370
|
+
return True
|
|
371
|
+
|
|
372
|
+
from .credentials_store import get_cached_entry, is_expired
|
|
373
|
+
|
|
374
|
+
ctx = _oidc_login_context
|
|
375
|
+
issuer = ctx["issuer"]
|
|
376
|
+
client_id = ctx["client_id"]
|
|
377
|
+
scope = ctx.get("scope")
|
|
378
|
+
audience = ctx.get("audience") or None
|
|
379
|
+
|
|
380
|
+
with _oidc_refresh_lock:
|
|
381
|
+
entry = get_cached_entry(issuer, client_id, audience=audience)
|
|
382
|
+
if entry is None:
|
|
383
|
+
return False
|
|
384
|
+
# Another caller may have refreshed the token while we waited on the lock.
|
|
385
|
+
if not force and not is_expired(entry):
|
|
386
|
+
_set_oidc_access_token(entry)
|
|
387
|
+
return True
|
|
388
|
+
refresh_token = entry.get("refresh_token")
|
|
389
|
+
if not refresh_token:
|
|
390
|
+
return False
|
|
391
|
+
# Shared with init(): on failure the cached entry is dropped so a
|
|
392
|
+
# bad/revoked refresh token isn't re-attempted on every request. Recovery
|
|
393
|
+
# is re-running vm.init() (surfaced by the clear 401 auth error); unlike
|
|
394
|
+
# init() the request path can't fall back to interactive device flow.
|
|
395
|
+
new_tokens = _refresh_oidc_from_cache(
|
|
396
|
+
issuer, client_id, refresh_token, scope, audience
|
|
397
|
+
)
|
|
398
|
+
if new_tokens is None:
|
|
399
|
+
# Hard failure: the cache entry was dropped; clear the in-memory token
|
|
400
|
+
# too so the next request fails fast with the clear re-auth message.
|
|
401
|
+
_clear_oidc_access_token()
|
|
402
|
+
return False
|
|
403
|
+
_set_oidc_access_token(new_tokens)
|
|
404
|
+
return True
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def _raise_for_api_error(status: int, text: str) -> None:
|
|
408
|
+
"""Raise a clear, actionable auth error for an OIDC 401, else defer to raise_api_error."""
|
|
409
|
+
if status == 401 and _auth_mode == "oidc":
|
|
410
|
+
raise ValidMindAuthError(
|
|
411
|
+
"ValidMind rejected the OAuth access token (HTTP 401); it may have "
|
|
412
|
+
"expired or been revoked. Run vm.init() again to re-authenticate."
|
|
413
|
+
)
|
|
414
|
+
raise_api_error(text)
|
|
415
|
+
|
|
416
|
+
|
|
279
417
|
def init(
|
|
280
418
|
api_key: Optional[str] = None,
|
|
281
419
|
api_secret: Optional[str] = None,
|
|
@@ -317,7 +455,7 @@ def init(
|
|
|
317
455
|
Can be set via env ``VM_OIDC_ISSUER``.
|
|
318
456
|
client_id (str, optional): OAuth public client id for device flow. Can be
|
|
319
457
|
set via env ``VM_OIDC_CLIENT_ID``.
|
|
320
|
-
scope (str, optional): OAuth scopes (default ``openid profile email``).
|
|
458
|
+
scope (str, optional): OAuth scopes (default ``openid profile email offline_access``).
|
|
321
459
|
Can be set via env ``VM_OIDC_SCOPE``.
|
|
322
460
|
audience (str, optional): Resource / API identifier for the access token
|
|
323
461
|
(e.g. Auth0 API Identifier). Use the same value the ValidMind backend
|
|
@@ -330,7 +468,7 @@ def init(
|
|
|
330
468
|
ValidMindAuthError: If OIDC configuration conflicts or login fails.
|
|
331
469
|
"""
|
|
332
470
|
global _api_key, _api_secret, _api_host, _model_cuid, _monitoring, _document
|
|
333
|
-
global _auth_mode, _access_token, _oidc_login_context
|
|
471
|
+
global _auth_mode, _access_token, _oidc_login_context, _oidc_expires_at
|
|
334
472
|
|
|
335
473
|
if api_key == "...":
|
|
336
474
|
# special case to detect when running a notebook placeholder (...)
|
|
@@ -392,7 +530,7 @@ def init(
|
|
|
392
530
|
_api_key = None
|
|
393
531
|
_api_secret = None
|
|
394
532
|
_api_host = resolved_host
|
|
395
|
-
scope_val = oidc_scope or "openid profile email"
|
|
533
|
+
scope_val = oidc_scope or "openid profile email offline_access"
|
|
396
534
|
from .credentials_store import normalize_audience
|
|
397
535
|
|
|
398
536
|
oidc_audience_val = normalize_audience(
|
|
@@ -402,20 +540,21 @@ def init(
|
|
|
402
540
|
entry = _obtain_oidc_tokens(
|
|
403
541
|
oidc_issuer, oidc_client_id, scope_val, audience=oidc_audience_opt
|
|
404
542
|
)
|
|
405
|
-
_access_token = _select_oidc_bearer_token(entry)
|
|
406
543
|
_oidc_login_context = {
|
|
407
544
|
"issuer": entry["issuer"],
|
|
408
545
|
"client_id": entry["client_id"],
|
|
409
546
|
"scope": scope_val,
|
|
410
547
|
"audience": entry.get("audience") or oidc_audience_val,
|
|
411
548
|
}
|
|
412
|
-
|
|
549
|
+
# Sets _access_token / _oidc_expires_at and invalidates the async session.
|
|
550
|
+
_set_oidc_access_token(entry)
|
|
413
551
|
else:
|
|
414
552
|
if env_key is None or env_secret is None:
|
|
415
553
|
raise MissingAPICredentialsError()
|
|
416
554
|
_auth_mode = "api_key"
|
|
417
555
|
_access_token = None
|
|
418
556
|
_oidc_login_context = None
|
|
557
|
+
_oidc_expires_at = None
|
|
419
558
|
_api_key = env_key
|
|
420
559
|
_api_secret = env_secret
|
|
421
560
|
_api_host = resolved_host
|
|
@@ -656,6 +795,7 @@ def _validate_log_text_context(
|
|
|
656
795
|
|
|
657
796
|
def generate_qualitative_text(text_generation_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
658
797
|
"""Generate qualitative text using the ValidMind AI API."""
|
|
798
|
+
_ensure_fresh_oidc_token()
|
|
659
799
|
r = requests.post(
|
|
660
800
|
url=_get_url("ai/generate/qualitative_text_generation"),
|
|
661
801
|
headers=_get_api_headers(),
|
|
@@ -663,7 +803,7 @@ def generate_qualitative_text(text_generation_data: Dict[str, Any]) -> Dict[str,
|
|
|
663
803
|
)
|
|
664
804
|
|
|
665
805
|
if r.status_code != 200:
|
|
666
|
-
|
|
806
|
+
_raise_for_api_error(r.status_code, r.text)
|
|
667
807
|
|
|
668
808
|
return r.json()
|
|
669
809
|
|
|
@@ -901,6 +1041,7 @@ def log_metric(
|
|
|
901
1041
|
|
|
902
1042
|
|
|
903
1043
|
def generate_test_result_description(test_result_data: Dict[str, Any]) -> str:
|
|
1044
|
+
_ensure_fresh_oidc_token()
|
|
904
1045
|
r = requests.post(
|
|
905
1046
|
url=_get_url("ai/generate/test_result_description"),
|
|
906
1047
|
headers=_get_api_headers(),
|
|
@@ -908,6 +1049,6 @@ def generate_test_result_description(test_result_data: Dict[str, Any]) -> str:
|
|
|
908
1049
|
)
|
|
909
1050
|
|
|
910
1051
|
if r.status_code != 200:
|
|
911
|
-
|
|
1052
|
+
_raise_for_api_error(r.status_code, r.text)
|
|
912
1053
|
|
|
913
1054
|
return r.json()
|
{validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/CalibrationCurve.py
RENAMED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# Refer to the LICENSE file in the root of this repository for details.
|
|
3
3
|
# SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial
|
|
4
4
|
|
|
5
|
+
import inspect
|
|
5
6
|
from typing import Tuple
|
|
6
7
|
|
|
7
8
|
import numpy as np
|
|
@@ -80,8 +81,19 @@ def CalibrationCurve(
|
|
|
80
81
|
"Calibration Curve is only supported for binary classification models"
|
|
81
82
|
)
|
|
82
83
|
|
|
84
|
+
# A two-class target encoded outside {0, 1} (e.g. {0, 4} or string labels)
|
|
85
|
+
# passes the guard above but hits sklearn's "pos_label is not specified" error.
|
|
86
|
+
# Pass the largest label as the positive class, matching the "largest label is
|
|
87
|
+
# positive" convention in _diagnosis_metrics.resolve_averaging; for {0, 1} this
|
|
88
|
+
# is byte-identical to sklearn's default. pos_label was added to
|
|
89
|
+
# calibration_curve in scikit-learn 1.1, so guard by signature inspection to
|
|
90
|
+
# keep pre-1.1 installs working (pyproject sets no scikit-learn floor).
|
|
91
|
+
kwargs = {}
|
|
92
|
+
if "pos_label" in inspect.signature(calibration_curve).parameters:
|
|
93
|
+
kwargs["pos_label"] = np.unique(dataset.y).max()
|
|
94
|
+
|
|
83
95
|
prob_true, prob_pred = calibration_curve(
|
|
84
|
-
dataset.y, dataset.y_prob(model), n_bins=n_bins
|
|
96
|
+
dataset.y, dataset.y_prob(model), n_bins=n_bins, **kwargs
|
|
85
97
|
)
|
|
86
98
|
|
|
87
99
|
# Create DataFrame for raw data
|
|
@@ -14,6 +14,8 @@ from validmind.errors import SkipTestError
|
|
|
14
14
|
from validmind.logging import get_logger
|
|
15
15
|
from validmind.vm_models import VMDataset, VMModel
|
|
16
16
|
|
|
17
|
+
from ._multiclass_proba import multiclass_proba, proba_matrix
|
|
18
|
+
|
|
17
19
|
logger = get_logger(__name__)
|
|
18
20
|
|
|
19
21
|
_PSI_PALETTE = ["#DE257E", "#1F77B4", "#2CA02C", "#FF7F0E", "#9467BD", "#8C564B"]
|
|
@@ -104,54 +106,37 @@ def _psi_table_rows(psi_results):
|
|
|
104
106
|
return rows_with_total, table_rows
|
|
105
107
|
|
|
106
108
|
|
|
107
|
-
def _multiclass_psi(datasets, model,
|
|
109
|
+
def _multiclass_psi(datasets, model, num_bins, mode):
|
|
108
110
|
"""One-vs-rest PSI for a multiclass model.
|
|
109
111
|
|
|
110
112
|
PSI needs a 1-D score distribution to compare across the two datasets. The
|
|
111
|
-
stored single probability column cannot represent every class, so
|
|
112
|
-
underlying estimator for the full per-class probability
|
|
113
|
-
ROC/PR curve tests)
|
|
114
|
-
|
|
113
|
+
stored single probability column cannot represent every class, so the shared
|
|
114
|
+
helper reaches the underlying estimator for the full per-class probability
|
|
115
|
+
matrix (mirroring the ROC/PR curve tests), aligning columns to the training
|
|
116
|
+
class order. The initial dataset drives the class alignment and the set of
|
|
117
|
+
classes actually present; the new dataset's matrix is validated against the
|
|
118
|
+
same class list. Models that cannot supply a matching matrix are skipped.
|
|
115
119
|
"""
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
try:
|
|
126
|
-
prob_initial = np.asarray(proba_fn(datasets[0].x_df()))
|
|
127
|
-
prob_new = np.asarray(proba_fn(datasets[1].x_df()))
|
|
128
|
-
except Exception as e:
|
|
129
|
-
raise SkipTestError(
|
|
130
|
-
"Multiclass Population Stability Index could not compute per-class "
|
|
131
|
-
f"probabilities ({type(e).__name__}). Skipping."
|
|
132
|
-
) from e
|
|
133
|
-
|
|
134
|
-
n_classes = len(classes)
|
|
135
|
-
for prob in (prob_initial, prob_new):
|
|
136
|
-
if prob.ndim != 2 or prob.shape[1] != n_classes:
|
|
137
|
-
raise SkipTestError(
|
|
138
|
-
"Multiclass Population Stability Index requires a per-class "
|
|
139
|
-
f"probability matrix with one column per class (got shape "
|
|
140
|
-
f"{getattr(prob, 'shape', None)} for {n_classes} classes). Skipping."
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
# predict_proba columns are ordered by sorted class label == np.unique.
|
|
120
|
+
aligned = multiclass_proba(model, datasets[0], "Population Stability Index")
|
|
121
|
+
prob_initial = aligned.y_prob
|
|
122
|
+
prob_new = proba_matrix(
|
|
123
|
+
model, datasets[1], aligned.class_list, "Population Stability Index"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
classes_present = aligned.classes_present
|
|
127
|
+
n_present = len(classes_present)
|
|
128
|
+
|
|
144
129
|
fig = make_subplots(
|
|
145
|
-
rows=
|
|
130
|
+
rows=n_present,
|
|
146
131
|
cols=1,
|
|
147
|
-
specs=[[{"secondary_y": True}] for _ in range(
|
|
148
|
-
subplot_titles=[f"Class {cls}" for cls in
|
|
132
|
+
specs=[[{"secondary_y": True}] for _ in range(n_present)],
|
|
133
|
+
subplot_titles=[f"Class {cls}" for cls in classes_present],
|
|
149
134
|
vertical_spacing=0.08,
|
|
150
135
|
)
|
|
151
136
|
|
|
152
137
|
tables = {}
|
|
153
138
|
raw_per_class = {}
|
|
154
|
-
for i, cls in enumerate(
|
|
139
|
+
for plot_i, (i, cls) in enumerate(zip(aligned.present_indices, classes_present)):
|
|
155
140
|
psi_results = calculate_psi(
|
|
156
141
|
prob_initial[:, i].copy(),
|
|
157
142
|
prob_new[:, i].copy(),
|
|
@@ -159,17 +144,17 @@ def _multiclass_psi(datasets, model, classes, num_bins, mode):
|
|
|
159
144
|
mode=mode,
|
|
160
145
|
)
|
|
161
146
|
x = list(range(len(psi_results)))
|
|
162
|
-
color = _PSI_PALETTE[
|
|
147
|
+
color = _PSI_PALETTE[plot_i % len(_PSI_PALETTE)]
|
|
163
148
|
fig.add_trace(
|
|
164
149
|
go.Bar(
|
|
165
150
|
x=x,
|
|
166
151
|
y=[d["percent_initial"] for d in psi_results],
|
|
167
152
|
name="Initial",
|
|
168
153
|
marker=dict(color="#DE257E"),
|
|
169
|
-
showlegend=
|
|
154
|
+
showlegend=plot_i == 0,
|
|
170
155
|
legendgroup="initial",
|
|
171
156
|
),
|
|
172
|
-
row=
|
|
157
|
+
row=plot_i + 1,
|
|
173
158
|
col=1,
|
|
174
159
|
secondary_y=False,
|
|
175
160
|
)
|
|
@@ -179,10 +164,10 @@ def _multiclass_psi(datasets, model, classes, num_bins, mode):
|
|
|
179
164
|
y=[d["percent_new"] for d in psi_results],
|
|
180
165
|
name="New",
|
|
181
166
|
marker=dict(color="#E8B1F8"),
|
|
182
|
-
showlegend=
|
|
167
|
+
showlegend=plot_i == 0,
|
|
183
168
|
legendgroup="new",
|
|
184
169
|
),
|
|
185
|
-
row=
|
|
170
|
+
row=plot_i + 1,
|
|
186
171
|
col=1,
|
|
187
172
|
secondary_y=False,
|
|
188
173
|
)
|
|
@@ -192,10 +177,10 @@ def _multiclass_psi(datasets, model, classes, num_bins, mode):
|
|
|
192
177
|
y=[d["psi"] for d in psi_results],
|
|
193
178
|
name="PSI",
|
|
194
179
|
line=dict(color=color),
|
|
195
|
-
showlegend=
|
|
180
|
+
showlegend=plot_i == 0,
|
|
196
181
|
legendgroup="psi",
|
|
197
182
|
),
|
|
198
|
-
row=
|
|
183
|
+
row=plot_i + 1,
|
|
199
184
|
col=1,
|
|
200
185
|
secondary_y=True,
|
|
201
186
|
)
|
|
@@ -211,7 +196,7 @@ def _multiclass_psi(datasets, model, classes, num_bins, mode):
|
|
|
211
196
|
fig.update_layout(
|
|
212
197
|
title="Population Stability Index (PSI) — one-vs-rest per class",
|
|
213
198
|
barmode="group",
|
|
214
|
-
height=300 *
|
|
199
|
+
height=300 * n_present,
|
|
215
200
|
)
|
|
216
201
|
|
|
217
202
|
return (
|
|
@@ -291,7 +276,7 @@ def PopulationStabilityIndex(
|
|
|
291
276
|
|
|
292
277
|
classes = np.unique(datasets[0].y)
|
|
293
278
|
if len(classes) > 2:
|
|
294
|
-
return _multiclass_psi(datasets, model,
|
|
279
|
+
return _multiclass_psi(datasets, model, num_bins, mode)
|
|
295
280
|
|
|
296
281
|
psi_results = calculate_psi(
|
|
297
282
|
datasets[0].y_prob(model).copy(),
|
|
@@ -7,13 +7,14 @@ from typing import Tuple
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
import plotly.graph_objects as go
|
|
9
9
|
from sklearn.metrics import average_precision_score, precision_recall_curve
|
|
10
|
-
from sklearn.preprocessing import label_binarize
|
|
11
10
|
|
|
12
11
|
from validmind import RawData, tags, tasks
|
|
13
12
|
from validmind.errors import SkipTestError
|
|
14
13
|
from validmind.models import FoundationModel
|
|
15
14
|
from validmind.vm_models import VMDataset, VMModel
|
|
16
15
|
|
|
16
|
+
from ._multiclass_proba import multiclass_proba
|
|
17
|
+
|
|
17
18
|
|
|
18
19
|
@tags(
|
|
19
20
|
"sklearn",
|
|
@@ -75,7 +76,7 @@ def PrecisionRecallCurve(
|
|
|
75
76
|
classes = np.unique(y_true)
|
|
76
77
|
|
|
77
78
|
if len(classes) > 2:
|
|
78
|
-
return _multiclass_pr_curve(model, dataset
|
|
79
|
+
return _multiclass_pr_curve(model, dataset)
|
|
79
80
|
|
|
80
81
|
precision, recall, _ = precision_recall_curve(y_true, dataset.y_prob(model))
|
|
81
82
|
|
|
@@ -105,53 +106,27 @@ def PrecisionRecallCurve(
|
|
|
105
106
|
|
|
106
107
|
|
|
107
108
|
def _multiclass_pr_curve(
|
|
108
|
-
model: VMModel, dataset: VMDataset
|
|
109
|
+
model: VMModel, dataset: VMDataset
|
|
109
110
|
) -> Tuple[go.Figure, RawData]:
|
|
110
111
|
"""One-vs-rest precision-recall curves for a multiclass model.
|
|
111
112
|
|
|
112
113
|
Needs the full per-class probability matrix, which the stored single
|
|
113
|
-
probability column cannot provide
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
probability column cannot provide; the shared helper reaches the underlying
|
|
115
|
+
estimator, aligns the probability columns to the training class order and
|
|
116
|
+
skips models that cannot supply a matching matrix.
|
|
116
117
|
"""
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
raw_model = getattr(model, "model", None)
|
|
121
|
-
proba_fn = getattr(raw_model, "predict_proba", None)
|
|
122
|
-
if not callable(proba_fn):
|
|
123
|
-
raise SkipTestError(
|
|
124
|
-
"Multiclass Precision-Recall Curve requires per-class probabilities "
|
|
125
|
-
"from the underlying model's predict_proba, which is not available "
|
|
126
|
-
"for this model (e.g. Foundation / metadata-only / precomputed "
|
|
127
|
-
"predictions). Skipping."
|
|
128
|
-
)
|
|
129
|
-
try:
|
|
130
|
-
y_prob = np.asarray(proba_fn(dataset.x_df()))
|
|
131
|
-
except Exception as e:
|
|
132
|
-
raise SkipTestError(
|
|
133
|
-
"Multiclass Precision-Recall Curve could not compute per-class "
|
|
134
|
-
f"probabilities ({type(e).__name__}). Skipping."
|
|
135
|
-
) from e
|
|
136
|
-
|
|
137
|
-
n_classes = len(classes)
|
|
138
|
-
if y_prob.ndim != 2 or y_prob.shape[1] != n_classes:
|
|
139
|
-
raise SkipTestError(
|
|
140
|
-
"Multiclass Precision-Recall Curve requires a per-class probability "
|
|
141
|
-
f"matrix with one column per class (got shape "
|
|
142
|
-
f"{getattr(y_prob, 'shape', None)} for {n_classes} classes). Skipping."
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
# One-hot the true labels in the same class order predict_proba columns use
|
|
146
|
-
# (sklearn orders predict_proba columns by sorted class label == np.unique).
|
|
147
|
-
y_bin = label_binarize(dataset.y.flatten(), classes=classes)
|
|
118
|
+
aligned = multiclass_proba(model, dataset, "Precision-Recall Curve")
|
|
119
|
+
y_bin = aligned.y_bin
|
|
120
|
+
y_prob = aligned.y_prob
|
|
148
121
|
|
|
149
122
|
traces = []
|
|
150
123
|
raw_precision = {}
|
|
151
124
|
raw_recall = {}
|
|
152
125
|
raw_ap = {}
|
|
153
126
|
palette = ["#DE257E", "#1F77B4", "#2CA02C", "#FF7F0E", "#9467BD", "#8C564B"]
|
|
154
|
-
for i, cls in enumerate(
|
|
127
|
+
for plot_i, (i, cls) in enumerate(
|
|
128
|
+
zip(aligned.present_indices, aligned.classes_present)
|
|
129
|
+
):
|
|
155
130
|
precision, recall, _ = precision_recall_curve(y_bin[:, i], y_prob[:, i])
|
|
156
131
|
ap = average_precision_score(y_bin[:, i], y_prob[:, i])
|
|
157
132
|
key = str(cls)
|
|
@@ -164,11 +139,14 @@ def _multiclass_pr_curve(
|
|
|
164
139
|
y=precision,
|
|
165
140
|
mode="lines",
|
|
166
141
|
name=f"Class {key} (AP = {ap:.2f})",
|
|
167
|
-
line=dict(color=palette[
|
|
142
|
+
line=dict(color=palette[plot_i % len(palette)]),
|
|
168
143
|
)
|
|
169
144
|
)
|
|
170
145
|
|
|
171
|
-
# Micro-average across
|
|
146
|
+
# Micro-average across the one-vs-rest decisions of the present classes.
|
|
147
|
+
present = aligned.present_indices
|
|
148
|
+
y_bin = y_bin[:, present]
|
|
149
|
+
y_prob = y_prob[:, present]
|
|
172
150
|
micro_precision, micro_recall, _ = precision_recall_curve(
|
|
173
151
|
y_bin.ravel(), y_prob.ravel()
|
|
174
152
|
)
|