Files
cleveragents-core/features/steps/context_fragment_models_steps.py
T
freemo 10abf8985e
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 15s
CI / quality (pull_request) Successful in 19s
CI / build (pull_request) Successful in 29s
CI / security (pull_request) Successful in 33s
CI / typecheck (pull_request) Successful in 35s
CI / unit_tests (pull_request) Successful in 2m15s
CI / docker (pull_request) Successful in 39s
CI / integration_tests (pull_request) Successful in 3m4s
CI / coverage (pull_request) Successful in 5m1s
CI / lint (push) Successful in 12s
CI / build (push) Successful in 15s
CI / quality (push) Successful in 16s
CI / security (push) Successful in 31s
CI / typecheck (push) Successful in 33s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 3m20s
CI / integration_tests (push) Successful in 4m10s
CI / docker (push) Successful in 1m3s
CI / coverage (push) Successful in 4m20s
CI / benchmark-publish (push) Successful in 16m0s
CI / benchmark-regression (pull_request) Successful in 31m40s
feat(acms): add ContextFragment and ScoredFragment data models
Created ScoredFragment frozen model wrapping ContextFragment with
composite_score, score_breakdown, and rank fields. Added pipeline-
specific fragment models in domain/contexts/ with proper equality
based on uko_uri + detail_depth for deduplication support.

ISSUES CLOSED: #538
2026-03-05 14:11:23 +00:00

406 lines
12 KiB
Python

"""Step definitions for features/context_fragment_models.feature.
Tests the ScoredFragment pipeline model and ContextFragment strategy_source
field directly in-memory -- no database required.
"""
from __future__ import annotations
from typing import Any
from behave import given, then, when
from behave.runner import Context
from pydantic import ValidationError
from cleveragents.domain.contexts.fragment import ScoredFragment
from cleveragents.domain.models.core.context_fragment import (
ContextFragment,
FragmentProvenance,
)
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
_DEFAULT_PROVENANCE = FragmentProvenance(resource_uri="test://default")
def _make_fragment(**kwargs: Any) -> ContextFragment:
"""Create a ContextFragment with sensible test defaults."""
kwargs.setdefault("uko_node", "test://default")
kwargs.setdefault("content", "test content")
kwargs.setdefault("token_count", 0)
kwargs.setdefault("provenance", _DEFAULT_PROVENANCE)
return ContextFragment(**kwargs)
# ---------------------------------------------------------------------------
# Given steps
# ---------------------------------------------------------------------------
@given(
'a context fragment with uko_node "{uko}" and content "{content}"'
" and token_count {tokens:d} and relevance {rel:g}"
)
def step_given_fragment_with_relevance(
context: Context,
uko: str,
content: str,
tokens: int,
rel: float,
) -> None:
context.fragment = _make_fragment(
uko_node=uko,
content=content,
token_count=tokens,
relevance_score=rel,
)
@given(
'a context fragment with uko_node "{uko}" and content "{content}"'
' and token_count {tokens:d} and strategy_source "{strategy}"'
)
def step_given_fragment_with_strategy(
context: Context,
uko: str,
content: str,
tokens: int,
strategy: str,
) -> None:
context.fragment = _make_fragment(
uko_node=uko,
content=content,
token_count=tokens,
strategy_source=strategy,
)
@given('two scored fragments with uko_node "{uko}" and detail_depth {depth:d}')
def step_given_two_same_scored_fragments(
context: Context,
uko: str,
depth: int,
) -> None:
frag_a = _make_fragment(
uko_node=uko,
content="content A",
detail_depth=depth,
token_count=10,
)
frag_b = _make_fragment(
uko_node=uko,
content="content B",
detail_depth=depth,
token_count=20,
)
context.scored_a = ScoredFragment.from_fragment(frag_a, composite_score=0.5)
context.scored_b = ScoredFragment.from_fragment(frag_b, composite_score=0.9)
@given('a scored fragment with uko_node "{uko}" and detail_depth {depth:d}')
def step_given_scored_fragment_first(
context: Context,
uko: str,
depth: int,
) -> None:
frag = _make_fragment(
uko_node=uko,
content="content",
detail_depth=depth,
token_count=10,
)
context.scored_a = ScoredFragment.from_fragment(frag, composite_score=0.5)
@given('another scored fragment with uko_node "{uko}" and detail_depth {depth:d}')
def step_given_scored_fragment_second(
context: Context,
uko: str,
depth: int,
) -> None:
frag = _make_fragment(
uko_node=uko,
content="content",
detail_depth=depth,
token_count=10,
)
context.scored_b = ScoredFragment.from_fragment(frag, composite_score=0.7)
@given(
'a scored fragment with uko_node "{uko}" and detail_depth {depth:d}'
" and score {score:g}"
)
def step_given_scored_fragment_with_score(
context: Context,
uko: str,
depth: int,
score: float,
) -> None:
frag = _make_fragment(
uko_node=uko,
content="content",
detail_depth=depth,
token_count=10,
)
context.scored_a = ScoredFragment.from_fragment(frag, composite_score=score)
@given(
'another scored fragment with uko_node "{uko}" and detail_depth {depth:d}'
" and score {score:g}"
)
def step_given_another_scored_fragment_with_score(
context: Context,
uko: str,
depth: int,
score: float,
) -> None:
frag = _make_fragment(
uko_node=uko,
content="content",
detail_depth=depth,
token_count=10,
)
context.scored_b = ScoredFragment.from_fragment(frag, composite_score=score)
@given('three scored fragments where two share uko_node "{uko}" detail_depth {depth:d}')
def step_given_three_scored_fragments(
context: Context,
uko: str,
depth: int,
) -> None:
frag_a = _make_fragment(
uko_node=uko,
content="content A",
detail_depth=depth,
token_count=10,
)
frag_b = _make_fragment(
uko_node=uko,
content="content B",
detail_depth=depth,
token_count=20,
)
frag_c = _make_fragment(
uko_node="project://app/other.py",
content="content C",
detail_depth=depth,
token_count=30,
)
context.scored_list = [
ScoredFragment.from_fragment(frag_a, composite_score=0.5),
ScoredFragment.from_fragment(frag_b, composite_score=0.9),
ScoredFragment.from_fragment(frag_c, composite_score=0.7),
]
# ---------------------------------------------------------------------------
# When steps
# ---------------------------------------------------------------------------
@when("I create a scored fragment with composite_score {score:g}")
def step_when_create_scored(context: Context, score: float) -> None:
try:
context.scored = ScoredFragment(
fragment=context.fragment,
composite_score=score,
)
context.error = None
except (ValidationError, ValueError) as exc:
context.scored = None
context.error = exc
@when(
"I create a scored fragment with composite_score {score:g}"
" and rank {rank:d} and breakdown:"
)
def step_when_create_scored_with_breakdown(
context: Context,
score: float,
rank: int,
) -> None:
breakdown: dict[str, float] = {}
assert context.table is not None
for row in context.table:
breakdown[row["component"]] = float(row["score"])
context.scored = ScoredFragment(
fragment=context.fragment,
composite_score=score,
score_breakdown=breakdown,
rank=rank,
)
@when("I create a scored fragment from relevance")
def step_when_create_from_relevance(context: Context) -> None:
context.scored = ScoredFragment.from_relevance(context.fragment)
@when("I create a scored fragment from factory with score {score:g} and rank {rank:d}")
def step_when_create_from_factory(
context: Context,
score: float,
rank: int,
) -> None:
context.scored = ScoredFragment.from_fragment(
context.fragment,
composite_score=score,
rank=rank,
)
@when("I create a scored fragment with an invalid breakdown component {score:g}")
def step_when_create_invalid_breakdown(context: Context, score: float) -> None:
try:
context.scored = ScoredFragment(
fragment=context.fragment,
composite_score=0.5,
score_breakdown={"bad": score},
)
context.error = None
except (ValidationError, ValueError) as exc:
context.scored = None
context.error = exc
@when("I create a scored fragment with composite_score {score:g} and rank {rank:d}")
def step_when_create_scored_with_rank(
context: Context,
score: float,
rank: int,
) -> None:
try:
context.scored = ScoredFragment(
fragment=context.fragment,
composite_score=score,
rank=rank,
)
context.error = None
except (ValidationError, ValueError) as exc:
context.scored = None
context.error = exc
@when("I add them to a set")
def step_when_add_to_set(context: Context) -> None:
context.scored_set = set(context.scored_list)
# ---------------------------------------------------------------------------
# Then steps
# ---------------------------------------------------------------------------
@then("the scored fragment composite_score should be {expected:g}")
def step_then_composite_score(context: Context, expected: float) -> None:
assert context.scored is not None, "No scored fragment created"
assert context.scored.composite_score == expected, (
f"Expected composite_score={expected}, got {context.scored.composite_score}"
)
@then("the scored fragment rank should be {expected:d}")
def step_then_rank(context: Context, expected: int) -> None:
assert context.scored is not None, "No scored fragment created"
assert context.scored.rank == expected, (
f"Expected rank={expected}, got {context.scored.rank}"
)
@then("the scored fragment score_breakdown should be empty")
def step_then_breakdown_empty(context: Context) -> None:
assert context.scored is not None, "No scored fragment created"
assert context.scored.score_breakdown == {}, (
f"Expected empty breakdown, got {context.scored.score_breakdown}"
)
@then('the scored fragment uko_node should be "{expected}"')
def step_then_scored_uko_node(context: Context, expected: str) -> None:
assert context.scored is not None, "No scored fragment created"
assert context.scored.uko_node == expected, (
f"Expected uko_node={expected}, got {context.scored.uko_node}"
)
@then("the scored fragment detail_depth should be {expected:d}")
def step_then_scored_detail_depth(context: Context, expected: int) -> None:
assert context.scored is not None, "No scored fragment created"
assert context.scored.detail_depth == expected, (
f"Expected detail_depth={expected}, got {context.scored.detail_depth}"
)
@then("the scored fragment token_count should be {expected:d}")
def step_then_scored_token_count(context: Context, expected: int) -> None:
assert context.scored is not None, "No scored fragment created"
assert context.scored.token_count == expected, (
f"Expected token_count={expected}, got {context.scored.token_count}"
)
@then('the scored fragment breakdown "{key}" should be {expected:g}')
def step_then_breakdown_value(context: Context, key: str, expected: float) -> None:
assert context.scored is not None, "No scored fragment created"
assert key in context.scored.score_breakdown, (
f"Key '{key}' not in score_breakdown: {context.scored.score_breakdown}"
)
actual = context.scored.score_breakdown[key]
assert actual == expected, f"Expected breakdown['{key}']={expected}, got {actual}"
@then("the two scored fragments should be equal")
def step_then_scored_equal(context: Context) -> None:
assert context.scored_a == context.scored_b, "Expected scored fragments to be equal"
@then("the two scored fragments should not be equal")
def step_then_scored_not_equal(context: Context) -> None:
assert context.scored_a != context.scored_b, (
"Expected scored fragments to NOT be equal"
)
@then("the two scored fragments should have the same hash")
def step_then_scored_same_hash(context: Context) -> None:
assert hash(context.scored_a) == hash(context.scored_b), (
"Expected scored fragments to have the same hash"
)
@then("the set should contain {expected:d} unique scored fragments")
def step_then_set_size(context: Context, expected: int) -> None:
assert len(context.scored_set) == expected, (
f"Expected {expected} unique fragments, got {len(context.scored_set)}"
)
@then("modifying the scored fragment composite_score should raise an error")
def step_then_frozen(context: Context) -> None:
try:
context.scored.composite_score = 0.99 # type: ignore[misc]
raise AssertionError("Expected ValidationError for frozen model")
except ValidationError:
pass
@then("the fragment strategy_source should be empty")
def step_then_strategy_source_empty(context: Context) -> None:
assert context.fragment.strategy_source == "", (
f"Expected empty strategy_source, got '{context.fragment.strategy_source}'"
)
@then('the fragment strategy_source should be "{expected}"')
def step_then_strategy_source(context: Context, expected: str) -> None:
assert context.fragment.strategy_source == expected, (
f"Expected strategy_source='{expected}', "
f"got '{context.fragment.strategy_source}'"
)