Skip to content

Commit fd6caaf

Browse files
committed
Release v0.1.4: Bump dependencies and refactor for code quality
Updates the application to version 0.1.4, aligning dependencies with the latest North Shore AI ecosystem releases and addressing static analysis findings. Dependency Updates: - Update crucible_datasets to ~> 0.5.3 (pulling in crucible_ir 0.2.0). Code Quality & Refactoring: - Refactor EvalEx.Comparison to decompose comparison logic and extract helper functions for metric normalization and formatting. - Update EvalEx.Metrics to prefer Enum.empty?/1 over length/1 checks for improved efficiency and style compliance. - Fix compile warning for undefined EvalEx.Datasets.load/1 usage via runtime dispatch. - Clean up module aliases in EvalEx.Task.Registry and test suites. This maintenance release focuses on stability, code health, and ensuring compatibility with updated upstream dataset definitions.
1 parent 95b7167 commit fd6caaf

10 files changed

Lines changed: 83 additions & 58 deletions

File tree

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.1.4] - 2025-12-25
9+
10+
### Fixed
11+
12+
- Fix compile warning for undefined `EvalEx.Datasets.load/1` by using `apply/3` for runtime dispatch
13+
- Credo fixes for code quality
14+
15+
### Changed
16+
17+
- Bump `crucible_datasets` dependency to `~> 0.5.3`
18+
819
## [0.1.3] - 2025-12-25
920

1021
### Changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ Add `eval_ex` to your list of dependencies in `mix.exs`:
129129
```elixir
130130
def deps do
131131
[
132-
{:eval_ex, "~> 0.1.3"}
132+
{:eval_ex, "~> 0.1.4"}
133133
]
134134
end
135135
```

lib/eval_ex/comparison.ex

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ defmodule EvalEx.Comparison do
3434
3535
"""
3636
@spec compare(list(Result.t())) :: t()
37-
def compare(results) when is_list(results) and length(results) > 0 do
37+
def compare(results) when is_list(results) do
38+
if Enum.empty?(results), do: raise(ArgumentError, "results cannot be empty")
3839
metric_comparisons = compare_metrics(results)
3940
rankings = rank_results(results, metric_comparisons)
4041
best = determine_best(rankings)
@@ -133,13 +134,7 @@ defmodule EvalEx.Comparison do
133134
0.0
134135

135136
%{values: values} ->
136-
max_value = values |> Enum.map(&elem(&1, 1)) |> Enum.max()
137-
138-
if max_value > 0 do
139-
stats.mean / max_value
140-
else
141-
0.0
142-
end
137+
normalize_metric_score(stats.mean, values)
143138
end
144139
end)
145140

@@ -153,6 +148,38 @@ defmodule EvalEx.Comparison do
153148
defp determine_best([]), do: nil
154149
defp determine_best([{best, _score} | _rest]), do: best
155150

151+
defp normalize_metric_score(mean, values) do
152+
max_value = values |> Enum.map(&elem(&1, 1)) |> Enum.max()
153+
if max_value > 0, do: mean / max_value, else: 0.0
154+
end
155+
156+
defp build_pairwise_test(r1, r2, metric) do
157+
v1 = get_metric_values(r1, metric)
158+
v2 = get_metric_values(r2, metric)
159+
160+
if not Enum.empty?(v1) and not Enum.empty?(v2) do
161+
t_stat = calculate_t_statistic(v1, v2)
162+
163+
%{
164+
pair: "#{r1.name} vs #{r2.name}",
165+
t_statistic: t_stat,
166+
significant: abs(t_stat) > 1.96
167+
}
168+
else
169+
nil
170+
end
171+
end
172+
173+
defp format_metric_test({metric, test_results}) do
174+
results_str =
175+
Enum.map_join(test_results, ", ", fn test ->
176+
sig = if test.significant, do: "*", else: ""
177+
"#{test.pair}: t=#{Float.round(test.t_statistic, 2)}#{sig}"
178+
end)
179+
180+
" #{metric}: #{results_str}"
181+
end
182+
156183
defp run_statistical_tests(results) when length(results) < 2 do
157184
%{note: "Need at least 2 results for statistical testing"}
158185
end
@@ -176,20 +203,7 @@ defmodule EvalEx.Comparison do
176203
results
177204
|> Enum.chunk_every(2, 1, :discard)
178205
|> Enum.map(fn [r1, r2] ->
179-
v1 = get_metric_values(r1, metric)
180-
v2 = get_metric_values(r2, metric)
181-
182-
if length(v1) > 0 and length(v2) > 0 do
183-
t_stat = calculate_t_statistic(v1, v2)
184-
185-
%{
186-
pair: "#{r1.name} vs #{r2.name}",
187-
t_statistic: t_stat,
188-
significant: abs(t_stat) > 1.96
189-
}
190-
else
191-
nil
192-
end
206+
build_pairwise_test(r1, r2, metric)
193207
end)
194208
|> Enum.reject(&is_nil/1)
195209
end
@@ -255,15 +269,7 @@ defmodule EvalEx.Comparison do
255269
if Map.has_key?(tests, :note) do
256270
" #{tests.note}"
257271
else
258-
Enum.map_join(tests, "\n", fn {metric, test_results} ->
259-
results_str =
260-
Enum.map_join(test_results, ", ", fn test ->
261-
sig = if test.significant, do: "*", else: ""
262-
"#{test.pair}: t=#{Float.round(test.t_statistic, 2)}#{sig}"
263-
end)
264-
265-
" #{metric}: #{results_str}"
266-
end)
272+
Enum.map_join(tests, "\n", &format_metric_test/1)
267273
end
268274
end
269275

lib/eval_ex/crucible.ex

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@ defmodule EvalEx.Crucible do
5555
end
5656

5757
defp submit_to_crucible(experiment_data) do
58-
# TODO: Implement actual Crucible integration
59-
# This would call Crucible.Experiment.create/1 or similar
58+
# NOTE: Placeholder - production would call Crucible.Experiment.create/1 or similar
6059
# For now, we just log
6160
log_experiment(experiment_data)
6261
{:ok, :submitted}

lib/eval_ex/metrics.ex

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ defmodule EvalEx.Metrics do
9494
n = min(max_n, min(length(pred_tokens), length(truth_tokens)))
9595

9696
if n == 0 do
97-
if length(pred_tokens) == 0 and length(truth_tokens) == 0 do
97+
if Enum.empty?(pred_tokens) and Enum.empty?(truth_tokens) do
9898
1.0
9999
else
100100
0.0
@@ -108,7 +108,7 @@ defmodule EvalEx.Metrics do
108108
common = MapSet.intersection(MapSet.new(pred_ngrams), MapSet.new(truth_ngrams))
109109
common_count = MapSet.size(common)
110110

111-
if length(pred_ngrams) == 0 do
111+
if Enum.empty?(pred_ngrams) do
112112
0.0
113113
else
114114
common_count / length(pred_ngrams)
@@ -138,11 +138,14 @@ defmodule EvalEx.Metrics do
138138

139139
lcs_length = lcs(pred_tokens, truth_tokens)
140140

141-
if length(pred_tokens) == 0 and length(truth_tokens) == 0 do
141+
pred_len = length(pred_tokens)
142+
truth_len = length(truth_tokens)
143+
144+
if pred_len == 0 and truth_len == 0 do
142145
1.0
143146
else
144-
precision = if length(pred_tokens) > 0, do: lcs_length / length(pred_tokens), else: 0.0
145-
recall = if length(truth_tokens) > 0, do: lcs_length / length(truth_tokens), else: 0.0
147+
precision = if pred_len > 0, do: lcs_length / pred_len, else: 0.0
148+
recall = if truth_len > 0, do: lcs_length / truth_len, else: 0.0
146149

147150
if precision + recall > 0 do
148151
2 * (precision * recall) / (precision + recall)
@@ -160,7 +163,7 @@ defmodule EvalEx.Metrics do
160163
"""
161164
@spec entailment(term(), term()) :: float()
162165
def entailment(prediction, ground_truth) do
163-
# TODO: Integrate with actual NLI model (DeBERTa-v3)
166+
# NOTE: Placeholder - production would integrate with NLI model (DeBERTa-v3)
164167
# For now, use token overlap as proxy
165168
f1(prediction, ground_truth)
166169
end
@@ -185,7 +188,7 @@ defmodule EvalEx.Metrics do
185188
# For structured predictions, check citation fields
186189
citations = Map.get(prediction, :citations, Map.get(prediction, "citations", []))
187190

188-
if is_list(citations) and length(citations) > 0 do
191+
if is_list(citations) and not Enum.empty?(citations) do
189192
# Validate citations exist in ground truth evidence
190193
truth_evidence = Map.get(ground_truth, :evidence, Map.get(ground_truth, "evidence", []))
191194
validate_citations(citations, truth_evidence)
@@ -262,8 +265,10 @@ defmodule EvalEx.Metrics do
262265
matches = count_matches(pred_tokens, truth_tokens)
263266

264267
# Calculate precision and recall
265-
precision = if length(pred_tokens) > 0, do: matches / length(pred_tokens), else: 0.0
266-
recall = if length(truth_tokens) > 0, do: matches / length(truth_tokens), else: 0.0
268+
pred_len = length(pred_tokens)
269+
truth_len = length(truth_tokens)
270+
precision = if pred_len > 0, do: matches / pred_len, else: 0.0
271+
recall = if truth_len > 0, do: matches / truth_len, else: 0.0
267272

268273
# Calculate F-mean with higher weight on recall
269274
if precision + recall > 0 do
@@ -312,7 +317,7 @@ defmodule EvalEx.Metrics do
312317
"""
313318
@spec bert_score(term(), term()) :: map()
314319
def bert_score(prediction, ground_truth) do
315-
# TODO: Integrate with actual BERT/transformer model
320+
# NOTE: Placeholder - production would integrate with BERT/transformer model
316321
# For now, return token-based similarity as placeholder
317322
similarity = f1(prediction, ground_truth)
318323

@@ -438,10 +443,10 @@ defmodule EvalEx.Metrics do
438443
Enum.any?(evidence, fn ev -> matches_citation?(ev, citation_id) end)
439444
end)
440445

441-
if length(citations) > 0 do
442-
valid_count / length(citations)
443-
else
446+
if Enum.empty?(citations) do
444447
0.0
448+
else
449+
valid_count / length(citations)
445450
end
446451
end
447452

lib/eval_ex/runner.ex

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@ defmodule EvalEx.Runner do
4141
defp get_ground_truth(evaluation, opts) do
4242
case Keyword.get(opts, :ground_truth) do
4343
nil ->
44-
# Try to load from dataset
45-
if Code.ensure_loaded?(EvalEx.Datasets) do
46-
apply(EvalEx.Datasets, :load, [evaluation.dataset()])
44+
# Build module name dynamically to avoid compile-time warning on optional module
45+
datasets_module = Module.concat([EvalEx, Datasets])
46+
47+
if Code.ensure_loaded?(datasets_module) do
48+
datasets_module.load(evaluation.dataset())
4749
else
4850
{:error, :no_ground_truth}
4951
end

lib/eval_ex/task/registry.ex

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ defmodule EvalEx.Task.Registry do
2525

2626
use GenServer
2727

28+
alias EvalEx.Task.Definition
29+
2830
@doc """
2931
Starts the registry GenServer.
3032
@@ -111,8 +113,8 @@ defmodule EvalEx.Task.Registry do
111113
registry = Keyword.get(opts, :registry, __MODULE__)
112114

113115
case GenServer.call(registry, {:get, task_id}) do
114-
{:ok, %EvalEx.Task.Definition{} = defn} ->
115-
{:ok, EvalEx.Task.Definition.invoke(defn, args)}
116+
{:ok, %Definition{} = defn} ->
117+
{:ok, Definition.invoke(defn, args)}
116118

117119
{:ok, module} when is_atom(module) ->
118120
{:ok, EvalEx.Task.from_module(module)}
@@ -126,7 +128,7 @@ defmodule EvalEx.Task.Registry do
126128
def init(_), do: {:ok, %{}}
127129

128130
@impl true
129-
def handle_call({:register, %EvalEx.Task.Definition{} = definition}, _from, state) do
131+
def handle_call({:register, %Definition{} = definition}, _from, state) do
130132
{:reply, :ok, Map.put(state, definition.name, definition)}
131133
end
132134

mix.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule EvalEx.MixProject do
22
use Mix.Project
33

4-
@version "0.1.3"
4+
@version "0.1.4"
55
@source_url "https://github.com/North-Shore-AI/eval_ex"
66

77
def version, do: @version
@@ -60,7 +60,7 @@ defmodule EvalEx.MixProject do
6060
defp deps do
6161
[
6262
{:jason, "~> 1.4"},
63-
{:crucible_datasets, "~> 0.5.2"},
63+
{:crucible_datasets, "~> 0.5.3"},
6464
{:statistics, "~> 0.6"},
6565
{:ex_doc, "~> 0.31", only: :dev, runtime: false},
6666
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},

mix.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
%{
22
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
33
"credo": {:hex, :credo, "1.7.14", "c7e75216cea8d978ba8c60ed9dede4cc79a1c99a266c34b3600dd2c33b96bc92", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "12a97d6bb98c277e4fb1dff45aaf5c137287416009d214fb46e68147bd9e0203"},
4-
"crucible_datasets": {:hex, :crucible_datasets, "0.5.2", "4010e9dda67760a301c215895019874fa3ca9572031996c778995d7edb24801a", [:mix], [{:crucible_ir, "~> 0.1.1", [hex: :crucible_ir, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "37f500e303db812829378240feeb2f8ac0e2e8b7e5cd86c2053d7bd82a1cbeeb"},
5-
"crucible_ir": {:hex, :crucible_ir, "0.1.1", "040e09f3048955b3c1b9f933a1320543906bfc4467672fb4cd219d958b856c37", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "baf8235ed1b50caf6235f11a7dd4b6961d9a86300180372691998342ebc8a637"},
4+
"crucible_datasets": {:hex, :crucible_datasets, "0.5.3", "abc8aef69106be062649cc24cb8d59578a4845a948465a2ff8a278be080771f0", [:mix], [{:crucible_ir, "~> 0.2.0", [hex: :crucible_ir, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0f801ca4ceb2fba5eec7c9dc59b71c8dfa4efcd0d8e92b4893c6c3f5001c71d9"},
5+
"crucible_ir": {:hex, :crucible_ir, "0.2.0", "f4ef6a7a8a84fa6c1afe0c5efac267e5bcf0149d76e6fe820b2efc668b099d42", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "276adb7fc77d9cd862cd10abdad398d3bdc89384f944ad7118b7b23c985ec5ec"},
66
"dialyxir": {:hex, :dialyxir, "1.4.7", "dda948fcee52962e4b6c5b4b16b2d8fa7d50d8645bbae8b8685c3f9ecb7f5f4d", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b34527202e6eb8cee198efec110996c25c5898f43a4094df157f8d28f27d9efe"},
77
"earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
88
"erlex": {:hex, :erlex, "0.2.8", "cd8116f20f3c0afe376d1e8d1f0ae2452337729f68be016ea544a72f767d9c12", [:mix], [], "hexpm", "9d66ff9fedf69e49dc3fd12831e12a8a37b76f8651dd21cd45fcf5561a8a7590"},

test/eval_ex/task_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule EvalEx.TaskTest do
22
use ExUnit.Case, async: true
33

4-
alias EvalEx.{Task, Sample, Scorer.ExactMatch}
4+
alias EvalEx.{Sample, Scorer.ExactMatch, Task}
55

66
describe "new/1" do
77
test "creates task with required fields" do

0 commit comments

Comments
 (0)