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.
Files changed (378) hide show
  1. {validmind-2.13.7 → validmind-2.13.9}/PKG-INFO +1 -1
  2. {validmind-2.13.7 → validmind-2.13.9}/pyproject.toml +1 -1
  3. validmind-2.13.9/validmind/__version__.py +1 -0
  4. {validmind-2.13.7 → validmind-2.13.9}/validmind/api_client.py +166 -25
  5. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/CalibrationCurve.py +13 -1
  6. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/PopulationStabilityIndex.py +31 -46
  7. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/PrecisionRecallCurve.py +18 -40
  8. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/ROCCurve.py +18 -41
  9. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/WeakspotsDiagnosis.py +12 -7
  10. validmind-2.13.9/validmind/tests/model_validation/sklearn/_multiclass_proba.py +150 -0
  11. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/GINITable.py +19 -41
  12. validmind-2.13.7/validmind/__version__.py +0 -1
  13. {validmind-2.13.7 → validmind-2.13.9}/.gitignore +0 -0
  14. {validmind-2.13.7 → validmind-2.13.9}/LICENSE +0 -0
  15. {validmind-2.13.7 → validmind-2.13.9}/README.pypi.md +0 -0
  16. {validmind-2.13.7 → validmind-2.13.9}/validmind/__init__.py +0 -0
  17. {validmind-2.13.7 → validmind-2.13.9}/validmind/ai/test_descriptions.py +0 -0
  18. {validmind-2.13.7 → validmind-2.13.9}/validmind/ai/utils.py +0 -0
  19. {validmind-2.13.7 → validmind-2.13.9}/validmind/client.py +0 -0
  20. {validmind-2.13.7 → validmind-2.13.9}/validmind/client_config.py +0 -0
  21. {validmind-2.13.7 → validmind-2.13.9}/validmind/credentials_store.py +0 -0
  22. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/__init__.py +0 -0
  23. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/classification/__init__.py +0 -0
  24. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/classification/config.json +0 -0
  25. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/classification/customer_churn.py +0 -0
  26. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/classification/datasets/bank_customer_churn.csv +0 -0
  27. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/classification/datasets/taiwan_credit.csv +0 -0
  28. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/classification/taiwan_credit.py +0 -0
  29. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/cluster/digits.py +0 -0
  30. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/credit_risk/__init__.py +0 -0
  31. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/credit_risk/datasets/lending_club_biased.csv.gz +0 -0
  32. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/credit_risk/datasets/lending_club_loan_data_2007_2014_clean.csv.gz +0 -0
  33. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/credit_risk/lending_club.py +0 -0
  34. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/credit_risk/lending_club_bias.py +0 -0
  35. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/llm/__init__.py +0 -0
  36. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/llm/agent_dataset.py +0 -0
  37. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/llm/rag/__init__.py +0 -0
  38. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/llm/rag/datasets/rfp_existing_questions_client_1.csv +0 -0
  39. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/llm/rag/datasets/rfp_existing_questions_client_2.csv +0 -0
  40. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/llm/rag/datasets/rfp_existing_questions_client_3.csv +0 -0
  41. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/llm/rag/datasets/rfp_existing_questions_client_4.csv +0 -0
  42. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/llm/rag/datasets/rfp_existing_questions_client_5.csv +0 -0
  43. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/llm/rag/rfp.py +0 -0
  44. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/nlp/__init__.py +0 -0
  45. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/nlp/cnn_dailymail.py +0 -0
  46. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/nlp/datasets/Covid_19.csv +0 -0
  47. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/nlp/datasets/cnn_dailymail_100_with_predictions.csv +0 -0
  48. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/nlp/datasets/cnn_dailymail_500_with_predictions.csv +0 -0
  49. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/nlp/datasets/sentiments_with_predictions.csv +0 -0
  50. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/nlp/twitter_covid_19.py +0 -0
  51. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/__init__.py +0 -0
  52. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/california_housing.py +0 -0
  53. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/california_housing.csv +0 -0
  54. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred/CPIAUCSL.csv +0 -0
  55. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred/CSUSHPISA.csv +0 -0
  56. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred/DRSFRMACBS.csv +0 -0
  57. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred/FEDFUNDS.csv +0 -0
  58. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred/GDP.csv +0 -0
  59. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred/GDPC1.csv +0 -0
  60. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred/GS10.csv +0 -0
  61. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred/GS3.csv +0 -0
  62. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred/GS5.csv +0 -0
  63. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred/MORTGAGE30US.csv +0 -0
  64. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred/UNRATE.csv +0 -0
  65. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred_loan_rates.csv +0 -0
  66. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred_loan_rates_test_1.csv +0 -0
  67. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred_loan_rates_test_2.csv +0 -0
  68. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred_loan_rates_test_3.csv +0 -0
  69. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred_loan_rates_test_4.csv +0 -0
  70. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/fred_loan_rates_test_5.csv +0 -0
  71. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/datasets/leanding_club_loan_rates.csv +0 -0
  72. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/fred.py +0 -0
  73. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/fred_timeseries.py +0 -0
  74. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/generate_california_housing_csv.py +0 -0
  75. {validmind-2.13.7 → validmind-2.13.9}/validmind/datasets/regression/lending_club.py +0 -0
  76. {validmind-2.13.7 → validmind-2.13.9}/validmind/errors.py +0 -0
  77. {validmind-2.13.7 → validmind-2.13.9}/validmind/experimental/__init__.py +0 -0
  78. {validmind-2.13.7 → validmind-2.13.9}/validmind/experimental/agents.py +0 -0
  79. {validmind-2.13.7 → validmind-2.13.9}/validmind/html_templates/__init__.py +0 -0
  80. {validmind-2.13.7 → validmind-2.13.9}/validmind/html_templates/content_blocks.py +0 -0
  81. {validmind-2.13.7 → validmind-2.13.9}/validmind/input_registry.py +0 -0
  82. {validmind-2.13.7 → validmind-2.13.9}/validmind/logging.py +0 -0
  83. {validmind-2.13.7 → validmind-2.13.9}/validmind/models/__init__.py +0 -0
  84. {validmind-2.13.7 → validmind-2.13.9}/validmind/models/foundation.py +0 -0
  85. {validmind-2.13.7 → validmind-2.13.9}/validmind/models/function.py +0 -0
  86. {validmind-2.13.7 → validmind-2.13.9}/validmind/models/huggingface.py +0 -0
  87. {validmind-2.13.7 → validmind-2.13.9}/validmind/models/metadata.py +0 -0
  88. {validmind-2.13.7 → validmind-2.13.9}/validmind/models/pipeline.py +0 -0
  89. {validmind-2.13.7 → validmind-2.13.9}/validmind/models/pytorch.py +0 -0
  90. {validmind-2.13.7 → validmind-2.13.9}/validmind/models/r_model.py +0 -0
  91. {validmind-2.13.7 → validmind-2.13.9}/validmind/models/sklearn.py +0 -0
  92. {validmind-2.13.7 → validmind-2.13.9}/validmind/oidc_device.py +0 -0
  93. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/__init__.py +0 -0
  94. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/classification/AbsoluteError.py +0 -0
  95. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/classification/BrierScore.py +0 -0
  96. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/classification/CalibrationError.py +0 -0
  97. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/classification/ClassBalance.py +0 -0
  98. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/classification/Confidence.py +0 -0
  99. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/classification/Correctness.py +0 -0
  100. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/classification/LogLoss.py +0 -0
  101. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/classification/OutlierScore.py +0 -0
  102. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/classification/ProbabilityError.py +0 -0
  103. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/classification/Uncertainty.py +0 -0
  104. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/classification/__init__.py +0 -0
  105. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/AnswerRelevancy.py +0 -0
  106. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/ArgumentCorrectness.py +0 -0
  107. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/Bias.py +0 -0
  108. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/ContextualPrecision.py +0 -0
  109. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/ContextualRecall.py +0 -0
  110. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/ContextualRelevancy.py +0 -0
  111. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/Faithfulness.py +0 -0
  112. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/GEval.py +0 -0
  113. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/Hallucination.py +0 -0
  114. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/PlanAdherence.py +0 -0
  115. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/PlanQuality.py +0 -0
  116. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/Summarization.py +0 -0
  117. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/TaskCompletion.py +0 -0
  118. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/ToolCorrectness.py +0 -0
  119. {validmind-2.13.7 → validmind-2.13.9}/validmind/scorers/llm/deepeval/__init__.py +0 -0
  120. {validmind-2.13.7 → validmind-2.13.9}/validmind/template.py +0 -0
  121. {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/__init__.py +0 -0
  122. {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/classifier.py +0 -0
  123. {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/cluster.py +0 -0
  124. {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/embeddings.py +0 -0
  125. {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/llm.py +0 -0
  126. {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/nlp.py +0 -0
  127. {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/parameters_optimization.py +0 -0
  128. {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/regression.py +0 -0
  129. {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/statsmodels_timeseries.py +0 -0
  130. {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/summarization.py +0 -0
  131. {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/tabular_datasets.py +0 -0
  132. {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/text_data.py +0 -0
  133. {validmind-2.13.7 → validmind-2.13.9}/validmind/test_suites/time_series.py +0 -0
  134. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/__init__.py +0 -0
  135. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/__types__.py +0 -0
  136. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/_store.py +0 -0
  137. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/comparison.py +0 -0
  138. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ACFandPACFPlot.py +0 -0
  139. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ADF.py +0 -0
  140. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/AutoAR.py +0 -0
  141. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/AutoMA.py +0 -0
  142. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/AutoStationarity.py +0 -0
  143. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/BivariateScatterPlots.py +0 -0
  144. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/BoxPierce.py +0 -0
  145. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ChiSquaredFeaturesTable.py +0 -0
  146. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ClassImbalance.py +0 -0
  147. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/DatasetDescription.py +0 -0
  148. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/DatasetSplit.py +0 -0
  149. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/DescriptiveStatistics.py +0 -0
  150. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/DickeyFullerGLS.py +0 -0
  151. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/Duplicates.py +0 -0
  152. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/EngleGrangerCoint.py +0 -0
  153. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/FeatureTargetCorrelationPlot.py +0 -0
  154. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/HighCardinality.py +0 -0
  155. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/HighPearsonCorrelation.py +0 -0
  156. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/IQROutliersBarPlot.py +0 -0
  157. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/IQROutliersTable.py +0 -0
  158. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/IsolationForestOutliers.py +0 -0
  159. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/JarqueBera.py +0 -0
  160. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/KPSS.py +0 -0
  161. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/LJungBox.py +0 -0
  162. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/LaggedCorrelationHeatmap.py +0 -0
  163. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/MissingValues.py +0 -0
  164. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/MissingValuesBarPlot.py +0 -0
  165. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/MutualInformation.py +0 -0
  166. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/PearsonCorrelationMatrix.py +0 -0
  167. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/PhillipsPerronArch.py +0 -0
  168. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ProtectedClassesCombination.py +0 -0
  169. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ProtectedClassesDescription.py +0 -0
  170. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ProtectedClassesDisparity.py +0 -0
  171. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ProtectedClassesThresholdOptimizer.py +0 -0
  172. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/RollingStatsPlot.py +0 -0
  173. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/RunsTest.py +0 -0
  174. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ScatterPlot.py +0 -0
  175. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ScoreBandDefaultRates.py +0 -0
  176. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/SeasonalDecompose.py +0 -0
  177. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ShapiroWilk.py +0 -0
  178. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/Skewness.py +0 -0
  179. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/SpreadPlot.py +0 -0
  180. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TabularCategoricalBarPlots.py +0 -0
  181. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TabularDateTimeHistograms.py +0 -0
  182. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TabularDescriptionTables.py +0 -0
  183. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TabularNumericalHistograms.py +0 -0
  184. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TargetRateBarPlots.py +0 -0
  185. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TimeSeriesDescription.py +0 -0
  186. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TimeSeriesDescriptiveStatistics.py +0 -0
  187. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TimeSeriesFrequency.py +0 -0
  188. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TimeSeriesHistogram.py +0 -0
  189. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TimeSeriesLinePlot.py +0 -0
  190. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TimeSeriesMissingValues.py +0 -0
  191. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TimeSeriesOutliers.py +0 -0
  192. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/TooManyZeroValues.py +0 -0
  193. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/UniqueRows.py +0 -0
  194. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/WOEBinPlots.py +0 -0
  195. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/WOEBinTable.py +0 -0
  196. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/ZivotAndrewsArch.py +0 -0
  197. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/__init__.py +0 -0
  198. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/nlp/CommonWords.py +0 -0
  199. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/nlp/Hashtags.py +0 -0
  200. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/nlp/LanguageDetection.py +0 -0
  201. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/nlp/Mentions.py +0 -0
  202. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/nlp/PolarityAndSubjectivity.py +0 -0
  203. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/nlp/Punctuations.py +0 -0
  204. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/nlp/Sentiment.py +0 -0
  205. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/nlp/StopWords.py +0 -0
  206. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/nlp/TextDescription.py +0 -0
  207. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/nlp/Toxicity.py +0 -0
  208. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/data_validation/nlp/__init__.py +0 -0
  209. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/decorator.py +0 -0
  210. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/load.py +0 -0
  211. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/BertScore.py +0 -0
  212. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/BleuScore.py +0 -0
  213. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ClusterSizeDistribution.py +0 -0
  214. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ContextualRecall.py +0 -0
  215. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/FeaturesAUC.py +0 -0
  216. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/MeteorScore.py +0 -0
  217. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ModelMetadata.py +0 -0
  218. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ModelPredictionResiduals.py +0 -0
  219. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/RegardScore.py +0 -0
  220. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/RegressionResidualsPlot.py +0 -0
  221. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/RougeScore.py +0 -0
  222. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/TimeSeriesPredictionWithCI.py +0 -0
  223. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/TimeSeriesPredictionsPlot.py +0 -0
  224. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/TimeSeriesR2SquareBySegments.py +0 -0
  225. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/TokenDisparity.py +0 -0
  226. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ToxicityScore.py +0 -0
  227. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/__init__.py +0 -0
  228. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/ClusterDistribution.py +0 -0
  229. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/CosineSimilarityComparison.py +0 -0
  230. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/CosineSimilarityDistribution.py +0 -0
  231. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/CosineSimilarityHeatmap.py +0 -0
  232. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/DescriptiveAnalytics.py +0 -0
  233. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/EmbeddingsVisualization2D.py +0 -0
  234. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/EuclideanDistanceComparison.py +0 -0
  235. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/EuclideanDistanceHeatmap.py +0 -0
  236. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/PCAComponentsPairwisePlots.py +0 -0
  237. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/StabilityAnalysisKeyword.py +0 -0
  238. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/StabilityAnalysisRandomNoise.py +0 -0
  239. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/StabilityAnalysisSynonyms.py +0 -0
  240. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/StabilityAnalysisTranslation.py +0 -0
  241. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/TSNEComponentsPairwisePlots.py +0 -0
  242. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/embeddings/utils.py +0 -0
  243. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ragas/AnswerCorrectness.py +0 -0
  244. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ragas/AspectCritic.py +0 -0
  245. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ragas/ContextEntityRecall.py +0 -0
  246. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ragas/ContextPrecision.py +0 -0
  247. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ragas/ContextPrecisionWithoutReference.py +0 -0
  248. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ragas/ContextRecall.py +0 -0
  249. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ragas/Faithfulness.py +0 -0
  250. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ragas/NoiseSensitivity.py +0 -0
  251. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ragas/ResponseRelevancy.py +0 -0
  252. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ragas/SemanticSimilarity.py +0 -0
  253. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/ragas/utils.py +0 -0
  254. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/AdjustedMutualInformation.py +0 -0
  255. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/AdjustedRandIndex.py +0 -0
  256. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/ClassifierPerformance.py +0 -0
  257. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/ClassifierThresholdOptimization.py +0 -0
  258. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/ClusterCosineSimilarity.py +0 -0
  259. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/ClusterPerformanceMetrics.py +0 -0
  260. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/CompletenessScore.py +0 -0
  261. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/ConfusionMatrix.py +0 -0
  262. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/FeatureImportance.py +0 -0
  263. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/FowlkesMallowsScore.py +0 -0
  264. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/HomogeneityScore.py +0 -0
  265. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/HyperParametersTuning.py +0 -0
  266. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/KMeansClustersOptimization.py +0 -0
  267. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/MinimumAccuracy.py +0 -0
  268. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/MinimumF1Score.py +0 -0
  269. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/MinimumROCAUCScore.py +0 -0
  270. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/ModelParameters.py +0 -0
  271. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/ModelsPerformanceComparison.py +0 -0
  272. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/OverfitDiagnosis.py +0 -0
  273. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/PermutationFeatureImportance.py +0 -0
  274. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/RegressionErrors.py +0 -0
  275. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/RegressionErrorsComparison.py +0 -0
  276. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/RegressionPerformance.py +0 -0
  277. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/RegressionR2Square.py +0 -0
  278. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/RegressionR2SquareComparison.py +0 -0
  279. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/RobustnessDiagnosis.py +0 -0
  280. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/SHAPGlobalImportance.py +0 -0
  281. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/ScoreProbabilityAlignment.py +0 -0
  282. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/SilhouettePlot.py +0 -0
  283. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/TrainingTestDegradation.py +0 -0
  284. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/VMeasure.py +0 -0
  285. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/__init__.py +0 -0
  286. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/sklearn/_diagnosis_metrics.py +0 -0
  287. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/AutoARIMA.py +0 -0
  288. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/CumulativePredictionProbabilities.py +0 -0
  289. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/DurbinWatsonTest.py +0 -0
  290. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/KolmogorovSmirnov.py +0 -0
  291. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/Lilliefors.py +0 -0
  292. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/PredictionProbabilitiesHistogram.py +0 -0
  293. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/RegressionCoeffs.py +0 -0
  294. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/RegressionFeatureSignificance.py +0 -0
  295. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/RegressionModelForecastPlot.py +0 -0
  296. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/RegressionModelForecastPlotLevels.py +0 -0
  297. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/RegressionModelSensitivityPlot.py +0 -0
  298. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/RegressionModelSummary.py +0 -0
  299. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/RegressionPermutationFeatureImportance.py +0 -0
  300. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/ScorecardHistogram.py +0 -0
  301. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/__init__.py +0 -0
  302. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/model_validation/statsmodels/statsutils.py +0 -0
  303. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/CalibrationCurveDrift.py +0 -0
  304. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/ClassDiscriminationDrift.py +0 -0
  305. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/ClassImbalanceDrift.py +0 -0
  306. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/ClassificationAccuracyDrift.py +0 -0
  307. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/ConfusionMatrixDrift.py +0 -0
  308. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/CumulativePredictionProbabilitiesDrift.py +0 -0
  309. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/FeatureDrift.py +0 -0
  310. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/PredictionAcrossEachFeature.py +0 -0
  311. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/PredictionCorrelation.py +0 -0
  312. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/PredictionProbabilitiesHistogramDrift.py +0 -0
  313. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/PredictionQuantilesAcrossFeatures.py +0 -0
  314. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/ROCCurveDrift.py +0 -0
  315. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/ScoreBandsDrift.py +0 -0
  316. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/ScorecardHistogramDrift.py +0 -0
  317. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/ongoing_monitoring/TargetPredictionDistributionPlot.py +0 -0
  318. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/output.py +0 -0
  319. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/plots/BoxPlot.py +0 -0
  320. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/plots/CorrelationHeatmap.py +0 -0
  321. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/plots/HistogramPlot.py +0 -0
  322. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/plots/ViolinPlot.py +0 -0
  323. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/plots/__init__.py +0 -0
  324. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/prompt_validation/Bias.py +0 -0
  325. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/prompt_validation/Clarity.py +0 -0
  326. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/prompt_validation/Conciseness.py +0 -0
  327. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/prompt_validation/Delimitation.py +0 -0
  328. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/prompt_validation/NegativeInstruction.py +0 -0
  329. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/prompt_validation/Robustness.py +0 -0
  330. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/prompt_validation/Specificity.py +0 -0
  331. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/prompt_validation/__init__.py +0 -0
  332. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/prompt_validation/ai_powered_test.py +0 -0
  333. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/run.py +0 -0
  334. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/stats/CorrelationAnalysis.py +0 -0
  335. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/stats/DescriptiveStats.py +0 -0
  336. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/stats/NormalityTests.py +0 -0
  337. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/stats/OutlierDetection.py +0 -0
  338. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/stats/__init__.py +0 -0
  339. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/test_providers.py +0 -0
  340. {validmind-2.13.7 → validmind-2.13.9}/validmind/tests/utils.py +0 -0
  341. {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/__init__.py +0 -0
  342. {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/classification/Accuracy.py +0 -0
  343. {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/classification/F1.py +0 -0
  344. {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/classification/Precision.py +0 -0
  345. {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/classification/ROC_AUC.py +0 -0
  346. {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/classification/Recall.py +0 -0
  347. {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/regression/AdjustedRSquaredScore.py +0 -0
  348. {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/regression/GiniCoefficient.py +0 -0
  349. {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/regression/HuberLoss.py +0 -0
  350. {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/regression/KolmogorovSmirnovStatistic.py +0 -0
  351. {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/regression/MeanAbsoluteError.py +0 -0
  352. {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/regression/MeanAbsolutePercentageError.py +0 -0
  353. {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/regression/MeanBiasDeviation.py +0 -0
  354. {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/regression/MeanSquaredError.py +0 -0
  355. {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/regression/QuantileLoss.py +0 -0
  356. {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/regression/RSquaredScore.py +0 -0
  357. {validmind-2.13.7 → validmind-2.13.9}/validmind/unit_metrics/regression/RootMeanSquaredError.py +0 -0
  358. {validmind-2.13.7 → validmind-2.13.9}/validmind/utils.py +0 -0
  359. {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/__init__.py +0 -0
  360. {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/dataset/__init__.py +0 -0
  361. {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/dataset/dataset.py +0 -0
  362. {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/dataset/utils.py +0 -0
  363. {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/figure.py +0 -0
  364. {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/html_progress.py +0 -0
  365. {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/html_renderer.py +0 -0
  366. {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/input.py +0 -0
  367. {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/model.py +0 -0
  368. {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/result/__init__.py +0 -0
  369. {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/result/pii_filter.py +0 -0
  370. {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/result/result.jinja +0 -0
  371. {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/result/result.py +0 -0
  372. {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/result/utils.py +0 -0
  373. {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/test_suite/__init__.py +0 -0
  374. {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/test_suite/runner.py +0 -0
  375. {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/test_suite/summary.py +0 -0
  376. {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/test_suite/test.py +0 -0
  377. {validmind-2.13.7 → validmind-2.13.9}/validmind/vm_models/test_suite/test_suite.py +0 -0
  378. {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.7
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "validmind"
3
- version = "2.13.7"
3
+ version = "2.13.9"
4
4
  description = "ValidMind Library"
5
5
  readme = "README.pypi.md"
6
6
  requires-python = ">=3.9,<3.15"
@@ -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
- raise_api_error(await r.text())
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
- raise_api_error(await r.text())
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
- raise_api_error(r.text)
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, try_refresh_cached_tokens
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
- try:
251
- new_tokens = try_refresh_cached_tokens(
252
- norm_issuer,
253
- norm_client_id,
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
- _invalidate_async_session()
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
- raise_api_error(r.text)
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
- raise_api_error(r.text)
1052
+ _raise_for_api_error(r.status_code, r.text)
912
1053
 
913
1054
  return r.json()
@@ -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, classes, num_bins, mode):
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 we ask the
112
- underlying estimator for the full per-class probability matrix (mirroring the
113
- ROC/PR curve tests). Models without a usable ``predict_proba`` (metadata-only
114
- / precomputed single-column predictions) are skipped rather than crashed.
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
- raw_model = getattr(model, "model", None)
117
- proba_fn = getattr(raw_model, "predict_proba", None)
118
- if not callable(proba_fn):
119
- raise SkipTestError(
120
- "Multiclass Population Stability Index requires per-class "
121
- "probabilities from the underlying model's predict_proba, which is "
122
- "not available for this model (e.g. metadata-only / precomputed "
123
- "predictions). Skipping."
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=n_classes,
130
+ rows=n_present,
146
131
  cols=1,
147
- specs=[[{"secondary_y": True}] for _ in range(n_classes)],
148
- subplot_titles=[f"Class {cls}" for cls in classes],
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(classes):
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[i % len(_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=i == 0,
154
+ showlegend=plot_i == 0,
170
155
  legendgroup="initial",
171
156
  ),
172
- row=i + 1,
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=i == 0,
167
+ showlegend=plot_i == 0,
183
168
  legendgroup="new",
184
169
  ),
185
- row=i + 1,
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=i == 0,
180
+ showlegend=plot_i == 0,
196
181
  legendgroup="psi",
197
182
  ),
198
- row=i + 1,
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 * n_classes,
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, classes, num_bins, mode)
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, classes)
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, classes: np.ndarray
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, so we ask the model for it directly.
114
- Models without a usable ``predict_proba`` (Foundation/metadata-only,
115
- precomputed single-column probabilities) are skipped rather than crashed.
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
- # The VMModel wrapper's predict_proba is binary-only (it returns just the
118
- # positive-class column), so reach the underlying estimator for the full
119
- # per-class probability matrix.
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(classes):
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[i % len(palette)]),
142
+ line=dict(color=palette[plot_i % len(palette)]),
168
143
  )
169
144
  )
170
145
 
171
- # Micro-average across all one-vs-rest decisions.
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
  )