fix(plan): use structured alternatives objects in plan explain output per spec
CI / benchmark-publish (pull_request) Has been skipped
CI / push-validation (pull_request) Successful in 38s
CI / helm (pull_request) Successful in 45s
CI / build (pull_request) Successful in 1m0s
CI / lint (pull_request) Failing after 1m9s
CI / quality (pull_request) Successful in 1m29s
CI / typecheck (pull_request) Successful in 1m37s
CI / security (pull_request) Successful in 1m48s
CI / e2e_tests (pull_request) Successful in 3m45s
CI / integration_tests (pull_request) Failing after 4m34s
CI / unit_tests (pull_request) Failing after 4m56s
CI / coverage (pull_request) Has been skipped
CI / docker (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Failing after 1m14s
CI / status-check (pull_request) Failing after 15s

Convert alternatives_considered list of strings to structured objects with
index (1-based), description, and chosen fields in _build_explain_dict().
Rename output field from alternatives_considered to alternatives.
Update BDD tests in plan_explain.feature, plan_explain_cli_coverage.feature,
and plan_explain_steps.py to validate the new structured format.

Closes #9166
This commit is contained in:
2026-05-05 12:01:18 +00:00
parent 7164b04019
commit 7d450fd2b5
4 changed files with 31 additions and 5 deletions
+4 -2
View File
@@ -13,7 +13,7 @@ Feature: Plan explain and decision tree CLI commands
And the explain dict should contain key "question"
And the explain dict should contain key "chosen"
And the explain dict should contain key "type"
And the explain dict should contain key "alternatives_considered"
And the explain dict should contain key "alternatives"
And the explain dict should not contain key "rationale"
And the explain dict should not contain key "context_snapshot"
@@ -45,8 +45,10 @@ Feature: Plan explain and decision tree CLI commands
Scenario: Explain includes alternatives by default
Given a test decision with alternatives for explain
When I build the explain dict with default options
Then the explain dict should contain key "alternatives_considered"
Then the explain dict should contain key "alternatives"
And the alternatives list should have 2 items
And each alternative should have keys "index", "description", and "chosen"
And exactly one alternative should have chosen set to true
# ------------------------------------------------------------------
# plan explain - json format
+1 -1
View File
@@ -49,7 +49,7 @@ Feature: Plan explain and tree CLI command coverage
Given pec a mock DecisionService returning a decision with alternatives
When pec I invoke "explain" with the decision id
Then pec the exit code should be 0
And pec the output should contain "alternatives_considered"
And pec the output should contain "alternatives"
# ------------------------------------------------------------------
# tree_decisions_cmd - rich tree format
+18 -1
View File
@@ -322,10 +322,27 @@ def step_snapshot_has_key(context: Context, key: str) -> None:
@then("the alternatives list should have {count:d} items")
def step_alternatives_count(context: Context, count: int) -> None:
alts = context.pe_explain_dict["alternatives_considered"]
alts = context.pe_explain_dict["alternatives"]
assert len(alts) == count, f"Expected {count} alternatives, got {len(alts)}"
@then('each alternative should have keys "index", "description", and "chosen"')
def step_alternatives_have_required_keys(context: Context) -> None:
alts = context.pe_explain_dict["alternatives"]
for i, alt in enumerate(alts):
assert isinstance(alt, dict), f"Alternative {i} is not a dict: {alt!r}"
assert "index" in alt, f"Alternative {i} missing 'index' key: {list(alt.keys())}"
assert "description" in alt, f"Alternative {i} missing 'description' key: {list(alt.keys())}"
assert "chosen" in alt, f"Alternative {i} missing 'chosen' key: {list(alt.keys())}"
@then("exactly one alternative should have chosen set to true")
def step_exactly_one_chosen(context: Context) -> None:
alts = context.pe_explain_dict["alternatives"]
chosen_count = sum(1 for alt in alts if alt.get("chosen") is True)
assert chosen_count == 1, f"Expected exactly 1 chosen alternative, got {chosen_count}"
@then('the json output should contain "{text}"')
def step_json_contains(context: Context, text: str) -> None:
assert text in context.pe_json_output, f"Expected '{text}' in JSON output"
+8 -1
View File
@@ -3909,7 +3909,14 @@ def _build_explain_dict(
"is_correction": decision.is_correction,
"superseded": decision.is_superseded,
"created_at": decision.created_at.isoformat(),
"alternatives_considered": decision.alternatives_considered,
"alternatives": [
{
"index": i + 1,
"description": alt,
"chosen": alt == decision.chosen_option,
}
for i, alt in enumerate(decision.alternatives_considered)
],
}
if show_reasoning:
data["rationale"] = decision.rationale