Current section
Files
Jump to
Current section
Files
lib/crucible/tinkex/quality_validator.ex
defmodule Crucible.Tinkex.QualityValidator do
@moduledoc """
Quality target validation for Tinkex experiments.
This module validates evaluation results against predefined quality targets
based on CNS3 experimental research. It supports continuous monitoring
during training and final validation after evaluation.
## Default Quality Targets
Based on CNS3 experiments:
| Metric | Target | Description |
|--------|--------|-------------|
| schema_compliance | >= 0.95 | Outputs conform to expected structure |
| citation_accuracy | >= 0.95 | Only valid citations from source corpus |
| mean_entailment | >= 0.50 | Claims supported by evidence |
| overall_pass_rate | >= 0.45 | Combined semantic quality |
## Examples
# Validate evaluation results
results = %{
schema_compliance: 0.96,
citation_accuracy: 0.97,
mean_entailment: 0.55,
overall_pass_rate: 0.48
}
validation = Crucible.Tinkex.QualityValidator.validate(results)
# => %{passed: true, assessments: [...]}
# Check if all targets pass
Crucible.Tinkex.QualityValidator.passed?(results)
# => true
"""
@default_targets %{
schema_compliance: 0.95,
citation_accuracy: 0.95,
mean_entailment: 0.50,
overall_pass_rate: 0.45
}
@secondary_targets %{
entailment_pass_rate: 0.45,
similarity_pass_rate: 0.35
}
@training_targets %{
loss_reduction: 0.95,
citation_invalid_rate: 0.0,
convergence_steps: 500
}
@doc """
Validates evaluation results against quality targets.
## Options
- `:targets` - Custom quality targets map (overrides defaults)
- `:include_secondary` - Include secondary targets (default: false)
## Examples
iex> results = %{schema_compliance: 0.96, citation_accuracy: 0.97, mean_entailment: 0.55, overall_pass_rate: 0.48}
iex> validation = Crucible.Tinkex.QualityValidator.validate(results)
iex> validation.passed
true
"""
@spec validate(map(), keyword()) :: map()
def validate(results, opts \\ []) when is_map(results) do
custom_targets = Keyword.get(opts, :targets)
targets =
if custom_targets do
# Use only custom targets when provided
custom_targets
else
if Keyword.get(opts, :include_secondary, false) do
Map.merge(@default_targets, @secondary_targets)
else
@default_targets
end
end
assessments =
Enum.map(targets, fn {metric, target} ->
actual = Map.get(results, metric, 0)
passed = actual >= target
%{
metric: metric,
target: target,
actual: actual,
passed: passed,
delta: Float.round(actual - target, 4)
}
end)
passed_assessments = Enum.filter(assessments, & &1.passed)
failed_assessments = Enum.reject(assessments, & &1.passed)
%{
passed: length(failed_assessments) == 0,
passed_count: length(passed_assessments),
failed_count: length(failed_assessments),
total_count: length(assessments),
assessments: assessments,
passed_assessments: passed_assessments,
failed_assessments: failed_assessments,
summary: generate_summary(passed_assessments, failed_assessments)
}
end
@doc """
Checks if all quality targets pass.
## Examples
iex> results = %{schema_compliance: 0.96, citation_accuracy: 0.97, mean_entailment: 0.55, overall_pass_rate: 0.48}
iex> Crucible.Tinkex.QualityValidator.passed?(results)
true
iex> results = %{schema_compliance: 0.80}
iex> Crucible.Tinkex.QualityValidator.passed?(results)
false
"""
@spec passed?(map(), keyword()) :: boolean()
def passed?(results, opts \\ []) do
validate(results, opts).passed
end
@doc """
Validates training metrics against training targets.
## Examples
iex> metrics = %{loss_reduction: 0.98, citation_invalid_rate: 0.0, final_step: 320}
iex> validation = Crucible.Tinkex.QualityValidator.validate_training(metrics)
iex> validation.passed
true
"""
@spec validate_training(map(), keyword()) :: map()
def validate_training(metrics, opts \\ []) when is_map(metrics) do
custom_targets = Keyword.get(opts, :targets, %{})
targets = Map.merge(@training_targets, custom_targets)
assessments =
Enum.map(targets, fn {metric, target} ->
actual = Map.get(metrics, metric, 0)
# For convergence_steps, lower is better
passed =
if metric == :convergence_steps do
actual <= target
else
actual >= target
end
%{
metric: metric,
target: target,
actual: actual,
passed: passed
}
end)
passed_count = Enum.count(assessments, & &1.passed)
%{
passed: passed_count == length(assessments),
passed_count: passed_count,
total_count: length(assessments),
assessments: assessments
}
end
@doc """
Returns the default quality targets.
## Examples
iex> targets = Crucible.Tinkex.QualityValidator.default_targets()
iex> targets.schema_compliance
0.95
"""
@spec default_targets() :: map()
def default_targets, do: @default_targets
@doc """
Returns secondary quality targets.
## Examples
iex> targets = Crucible.Tinkex.QualityValidator.secondary_targets()
iex> targets.entailment_pass_rate
0.45
"""
@spec secondary_targets() :: map()
def secondary_targets, do: @secondary_targets
@doc """
Returns training quality targets.
## Examples
iex> targets = Crucible.Tinkex.QualityValidator.training_targets()
iex> targets.loss_reduction
0.95
"""
@spec training_targets() :: map()
def training_targets, do: @training_targets
@doc """
Creates a monitoring callback for continuous quality checking.
## Options
- `:warn_threshold` - Threshold below target to trigger warning
- `:on_warning` - Callback function for warnings
- `:on_failure` - Callback function for failures
## Examples
callback = Crucible.Tinkex.QualityValidator.monitor_callback(
warn_threshold: 0.1,
on_warning: fn metrics -> Logger.warn("Warning: " <> inspect(metrics)) end
)
"""
@spec monitor_callback(keyword()) :: (map() -> :ok | :warn | :fail)
def monitor_callback(opts \\ []) do
warn_threshold = Keyword.get(opts, :warn_threshold, 0.1)
on_warning = Keyword.get(opts, :on_warning, fn _ -> :ok end)
on_failure = Keyword.get(opts, :on_failure, fn _ -> :ok end)
fn metrics ->
validation = validate(metrics)
cond do
validation.passed ->
:ok
Enum.any?(validation.assessments, fn a ->
not a.passed and a.delta > -warn_threshold
end) ->
on_warning.(validation.failed_assessments)
:warn
true ->
on_failure.(validation.failed_assessments)
:fail
end
end
end
@doc """
Generates a human-readable report of validation results.
## Examples
iex> results = %{schema_compliance: 0.96, citation_accuracy: 0.97, mean_entailment: 0.55, overall_pass_rate: 0.48}
iex> report = Crucible.Tinkex.QualityValidator.report(results)
iex> String.contains?(report, "PASSED")
true
"""
@spec report(map(), keyword()) :: String.t()
def report(results, opts \\ []) do
validation = validate(results, opts)
status = if validation.passed, do: "PASSED", else: "FAILED"
header = """
# Quality Validation Report
**Status**: #{status}
**Passed**: #{validation.passed_count}/#{validation.total_count}
## Assessments
"""
assessments_text =
validation.assessments
|> Enum.map(fn a ->
status_icon = if a.passed, do: "[OK]", else: "[FAIL]"
delta_text = if a.delta >= 0, do: "+#{a.delta}", else: "#{a.delta}"
"- #{status_icon} #{a.metric}: #{a.actual} (target: #{a.target}, delta: #{delta_text})"
end)
|> Enum.join("\n")
header <> assessments_text
end
# Private helpers
defp generate_summary(passed, failed) do
total = length(passed) + length(failed)
if length(failed) == 0 do
"All #{total} quality targets met"
else
failed_names =
failed
|> Enum.map(& &1.metric)
|> Enum.join(", ")
"#{length(passed)}/#{total} targets met. Failed: #{failed_names}"
end
end
end