Skip to content

Commit a7cc700

Browse files
authored
Merge pull request #29 from StrategyLogic:release/action-condition
Simplify workflow and improve action conditions
2 parents 6be99b0 + 10937e6 commit a7cc700

8 files changed

Lines changed: 102 additions & 31 deletions

File tree

.github/workflows/release.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,9 @@ jobs:
2323
python-version: "3.12"
2424
- name: Install build dependencies
2525
run: |
26-
python -m pip install --upgrade pip
27-
python -m pip install -e .
28-
python -m pip install -e ".[dev]"
29-
python -m pip install twine
26+
pip install --upgrade pip
27+
pip install -e .
28+
pip install twine
3029
- name: Build distribution artifacts
3130
run: |
3231
python -m build --sdist --wheel

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
![Codecov](https://img.shields.io/codecov/c/github/StrategyLogic/omen) [![Package](https://img.shields.io/github/actions/workflow/status/StrategyLogic/omen/package.yml)](https://img.shields.io/github/actions/workflow/status/StrategyLogic/omen/package.yml) ![License](https://img.shields.io/pypi/l/omenai) ![Downloads](https://img.shields.io/pepy/dt/omenai) ![PyPI Version](https://img.shields.io/pypi/v/omenai)
77
</div>
88

9-
[**Omen**](https://github.com/StrategyLogic/omen) (Chinese: 爻) is a strategic reasoning engine based on **Explainable AI**. It leverages **ontological modeling** to understand the phenomena and essence of the strategic world, and **counterfactual analysis** to simulate the known and unknown in decision-making scenarios, generating verifiable, comparable, and explainable decision support for decision-makers.
9+
[**Omen**](https://github.com/StrategyLogic/omen) (Chinese: 爻) is a strategic reasoning engine based on **Explainable AI**. It leverages **ontological modeling** to understand the phenomena and essence of the strategic world, and **counterfactual analysis** to simulate the known and unknown in decision-making scenarios, generating verifiable, traceable, and explainable strategic insights for decision-makers.
1010

1111
[Concepts](docs/concepts.md) | [Quick Start](docs/quick-start.md) | [Case Templates](docs/case-template.md) | [Roadmap](docs/roadmap.md)
1212

README.zh.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
</div>
99

10-
[**Omen**](https://github.com/StrategyLogic/omen) (中文:爻)是基于**可解释AI**的战略推演引擎。它以**本体论建模**理解战略世界中的现象与本质,以**反事实分析**模拟决策情景中的已知与未知,为决策者生成可验证、可比较和可解释的决策依据
10+
[**Omen**](https://github.com/StrategyLogic/omen) (中文:爻)是基于**可解释AI**的战略推演引擎。它以**本体论建模**理解战略世界中的现象与本质,以**反事实分析**模拟决策情景中的已知与未知,为决策者生成可验证、可追溯和可解释的战略洞察
1111

1212
[核心概念](docs/concepts.md) | [快速开始](docs/quick-start.md) | [案例模板](docs/case-template.md) | [项目路线图](docs/roadmap.md)
1313

src/omen/analysis/actor/derivation_trace.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,37 @@ def _normalize_items(items: list[Any], *, bucket: str) -> list[dict[str, Any]]:
9898

9999
def build_linked_evidence_refs(reason_chain: dict[str, Any]) -> list[dict[str, Any]]:
100100
conclusions = extract_conclusion_buckets(dict(reason_chain.get("conclusions") or {}))
101+
steps = [item for item in (reason_chain.get("steps") or []) if isinstance(item, dict)]
102+
default_reason_step_ids = [
103+
str(item.get("step_id") or "").strip()
104+
for item in steps
105+
if str(item.get("step_type") or "").strip()
106+
in {"required_or_warning_or_blocking", "strategic_conditions"}
107+
and str(item.get("step_id") or "").strip()
108+
]
109+
default_activation_step_ids = [
110+
str(item.get("step_id") or "").strip()
111+
for item in steps
112+
if str(item.get("step_type") or "").strip() == "constraint_activation"
113+
and str(item.get("step_id") or "").strip()
114+
]
115+
116+
if not default_reason_step_ids:
117+
fallback = str(steps[-1].get("step_id") or "").strip() if steps else ""
118+
if fallback:
119+
default_reason_step_ids = [fallback]
120+
101121
refs: list[dict[str, Any]] = []
102122
for bucket in ("required", "warning", "blocking"):
103123
for index, item in enumerate(list(conclusions.get(bucket) or []), start=1):
104124
if not isinstance(item, dict):
105125
continue
106126
reason_ids = [str(x).strip() for x in (item.get("reason_step_ids") or []) if str(x).strip()]
107127
activation_ids = [str(x).strip() for x in (item.get("activation_step_ids") or []) if str(x).strip()]
128+
if not reason_ids:
129+
reason_ids = list(default_reason_step_ids)
130+
if bucket == "blocking" and not activation_ids:
131+
activation_ids = list(default_activation_step_ids)
108132
refs.append(
109133
{
110134
"evidence_id": f"{bucket}_{index}",
@@ -298,9 +322,11 @@ def build_reason_chain_view_model_artifact(
298322
scenario_key = str(row.get("scenario_key") or "")
299323
reason_chain = dict(row.get("reason_chain") or {})
300324
steps = list(reason_chain.get("steps") or [])
325+
ordered_step_node_ids: list[str] = []
301326
for step in steps:
302327
step_id = str(step.get("step_id") or "").strip()
303328
node_id = f"{scenario_key}:{step_id}"
329+
ordered_step_node_ids.append(node_id)
304330
graph_nodes.append(
305331
{
306332
"id": node_id,
@@ -312,6 +338,16 @@ def build_reason_chain_view_model_artifact(
312338
}
313339
)
314340

341+
# Keep DAG connected in workshop mode even when conclusions are empty.
342+
for index in range(1, len(ordered_step_node_ids)):
343+
graph_edges.append(
344+
{
345+
"from": ordered_step_node_ids[index - 1],
346+
"to": ordered_step_node_ids[index],
347+
"edge_type": "next",
348+
}
349+
)
350+
315351
conclusions = dict(reason_chain.get("conclusions") or {})
316352
for bucket in ("required", "warning", "blocking"):
317353
for index, item in enumerate(list(conclusions.get(bucket) or []), start=1):

src/omen/cli/case.py

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from omen.analysis.actor.derivation import (
1212
derive_actor_path,
13-
derive_strategic_freedom_conditions,
13+
get_simulate_reasoning_order,
1414
)
1515
from omen.analysis.actor.derivation_trace import (
1616
extract_conclusion_buckets,
@@ -146,17 +146,21 @@ def run_deterministic_simulate_from_pack(
146146
capability_scores=capability_templates.get(scenario_key, {}),
147147
capability_fit=capability_fit["fit"],
148148
)
149-
strategic_conditions = derive_strategic_freedom_conditions(
150-
scenario_key=scenario_key,
151-
actor_derivation=actor_derivation,
152-
selected_dimensions=selected_dimensions,
153-
resistance_baseline=scenario["resistance_baseline"],
154-
capability_fit=capability_fit["fit"],
155-
)
156149
confidence_level, missing_reasons = apply_partial_evidence_confidence_policy(
157150
evidence_refs=[],
158151
scenario_key=scenario_key,
159152
)
153+
strategic_score = calculate_strategic_freedom_factor(
154+
capability_fit=capability_fit["fit"],
155+
resistance_baseline=scenario["resistance_baseline"],
156+
)
157+
strategic_conditions = {
158+
"score": strategic_score,
159+
"required": [],
160+
"warning": [],
161+
"blocking": [],
162+
"reasoning_order": list(get_simulate_reasoning_order()),
163+
}
160164
derivation_trace = build_actor_derivation_trace(
161165
scenario_key=scenario_key,
162166
scenario_ontology=scene,
@@ -165,10 +169,6 @@ def run_deterministic_simulate_from_pack(
165169
strategic_conditions=strategic_conditions,
166170
missing_evidence_reasons=missing_reasons,
167171
)
168-
strategic_score = calculate_strategic_freedom_factor(
169-
capability_fit=capability_fit["fit"],
170-
resistance_baseline=scenario["resistance_baseline"],
171-
)
172172

173173
scenario_results.append(
174174
{
@@ -246,7 +246,12 @@ def run_deterministic_simulate_from_pack(
246246
}
247247
llm_payload = try_generate_scenario_reason_chain_via_llm(
248248
scenario_json=llm_scenario_input,
249-
actor_profile_json={"actor_profile_ref": actor_profile_ref},
249+
actor_profile_json={
250+
"actor_profile_ref": actor_profile_ref,
251+
"actor_derivation": dict(result.get("actor_derivation") or {}),
252+
"selected_dimensions": list((result.get("selected_dimensions") or {}).get("selected_dimension_keys") or []),
253+
"resistance": dict(result.get("resistance") or {}),
254+
},
250255
planning_query_json={},
251256
situation_markdown="",
252257
config_path=config_path,
@@ -318,12 +323,11 @@ def run_deterministic_simulate_from_pack(
318323
key = str(result.get("scenario_key") or "")
319324
reason_chain = chain_by_key.get(key, {})
320325
buckets = extract_conclusion_buckets(dict(reason_chain.get("conclusions") or {}))
321-
if any(buckets[name] for name in ("required", "warning", "blocking")):
322-
freedom = dict(result.get("strategic_freedom") or {})
323-
freedom["required"] = [str(item.get("text") or "").strip() for item in buckets["required"] if str(item.get("text") or "").strip()]
324-
freedom["warning"] = [str(item.get("text") or "").strip() for item in buckets["warning"] if str(item.get("text") or "").strip()]
325-
freedom["blocking"] = [str(item.get("text") or "").strip() for item in buckets["blocking"] if str(item.get("text") or "").strip()]
326-
result["strategic_freedom"] = freedom
326+
freedom = dict(result.get("strategic_freedom") or {})
327+
freedom["required"] = [str(item.get("text") or "").strip() for item in buckets["required"] if str(item.get("text") or "").strip()]
328+
freedom["warning"] = [str(item.get("text") or "").strip() for item in buckets["warning"] if str(item.get("text") or "").strip()]
329+
freedom["blocking"] = [str(item.get("text") or "").strip() for item in buckets["blocking"] if str(item.get("text") or "").strip()]
330+
result["strategic_freedom"] = freedom
327331
result["evidence_refs"] = build_linked_evidence_refs(reason_chain)
328332

329333
if result["evidence_refs"]:

tests/contract/test_reason_chain_view_model_contract.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,3 @@ def test_reason_chain_view_model_contract_in_workshop_mode(tmp_path: Path, monke
114114
assert nodes
115115
assert edges
116116
assert any(str(node.get("node_type") or "") == "reason_step" for node in nodes)
117-
assert any(str(node.get("node_type") or "") == "claim" for node in nodes)

tests/integration/test_reason_chain_evidence_refs.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,7 @@ def test_reason_chain_evidence_refs_link_to_reason_steps(tmp_path: Path, monkeyp
102102
assert rows
103103
for row in rows:
104104
evidence_refs = list(row.get("evidence_refs") or [])
105-
assert evidence_refs
106-
assert all(list(item.get("reason_step_ids") or []) for item in evidence_refs)
107-
for item in evidence_refs:
108-
if item.get("bucket") == "blocking":
109-
assert list(item.get("activation_step_ids") or [])
105+
assert not evidence_refs
106+
assert str(row.get("confidence_level") or "") == "reduced-confidence"
107+
missing = list((row.get("derivation_trace") or {}).get("missing_evidence_reasons") or [])
108+
assert missing
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from omen.analysis.actor.derivation_trace import build_linked_evidence_refs
2+
3+
4+
def test_evidence_refs_backfill_reason_step_ids_from_reason_chain_steps() -> None:
5+
reason_chain = {
6+
"steps": [
7+
{"step_id": "step_1", "step_type": "seed"},
8+
{"step_id": "step_2", "step_type": "constraint_activation"},
9+
{"step_id": "step_5", "step_type": "required_or_warning_or_blocking"},
10+
],
11+
"conclusions": {
12+
"required": [
13+
{"text": "Must unblock alliance dependency"}
14+
],
15+
"warning": [
16+
{"text": "Risk of migration delay"}
17+
],
18+
"blocking": [
19+
{"text": "Cannot proceed under veto pressure"}
20+
],
21+
},
22+
}
23+
24+
refs = build_linked_evidence_refs(reason_chain)
25+
assert refs
26+
27+
required = next(item for item in refs if item.get("bucket") == "required")
28+
warning = next(item for item in refs if item.get("bucket") == "warning")
29+
blocking = next(item for item in refs if item.get("bucket") == "blocking")
30+
31+
assert required.get("reason_step_ids") == ["step_5"]
32+
assert warning.get("reason_step_ids") == ["step_5"]
33+
assert blocking.get("reason_step_ids") == ["step_5"]
34+
assert blocking.get("activation_step_ids") == ["step_2"]

0 commit comments

Comments
 (0)