From 2f9a0c773ce6a46ee93d5b05c306ae13473c678c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Jul 2026 17:57:20 +0000 Subject: [PATCH 1/6] Scaffold enhanced idealization MVP --- README.md | 39 +- enhanced_idealization/__init__.py | 6 + enhanced_idealization/__main__.py | 32 ++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 542 bytes .../__pycache__/engine.cpython-312.pyc | Bin 0 -> 19228 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 11680 bytes enhanced_idealization/engine.py | 360 ++++++++++++++++++ enhanced_idealization/models.py | 209 ++++++++++ enhanced_idealization/reporting.py | 36 ++ examples/product_strategy.json | 135 +++++++ pyproject.toml | 16 + tests/__pycache__/test_engine.cpython-312.pyc | Bin 0 -> 6878 bytes tests/test_engine.py | 170 +++++++++ 13 files changed, 1002 insertions(+), 1 deletion(-) create mode 100644 enhanced_idealization/__init__.py create mode 100644 enhanced_idealization/__main__.py create mode 100644 enhanced_idealization/__pycache__/__init__.cpython-312.pyc create mode 100644 enhanced_idealization/__pycache__/engine.cpython-312.pyc create mode 100644 enhanced_idealization/__pycache__/models.cpython-312.pyc create mode 100644 enhanced_idealization/engine.py create mode 100644 enhanced_idealization/models.py create mode 100644 enhanced_idealization/reporting.py create mode 100644 examples/product_strategy.json create mode 100644 pyproject.toml create mode 100644 tests/__pycache__/test_engine.cpython-312.pyc create mode 100644 tests/test_engine.py diff --git a/README.md b/README.md index 812e029..5268417 100644 --- a/README.md +++ b/README.md @@ -1 +1,38 @@ -# https-github.com-TheAlgorithms-Python-blob-master-CONTRIBUTING.md-coding-style \ No newline at end of file +# Enhanced Idealization Platform + +This repository now contains a Python MVP for an enhanced idealization platform. +It helps users define success, model constraints, generate multiple candidate +futures, stress test them across scenarios, and receive ranked recommendations +with confidence, tradeoffs, and reasoning traces. + +## What it does + +- captures goals, criteria, constraints, and context in a structured request +- generates multiple candidate strategies from a baseline or explicit blueprints +- adapts scoring based on user profiles and weighted success criteria +- simulates candidate performance under future scenarios +- explains why one recommendation outranks another +- compares the current recommendation against previous runs + +## Project layout + +- `/enhanced_idealization/models.py` - data models and request parsing +- `/enhanced_idealization/engine.py` - candidate generation, simulation, scoring +- `/enhanced_idealization/reporting.py` - plain-text recommendation rendering +- `/enhanced_idealization/__main__.py` - command-line entry point +- `/examples/product_strategy.json` - sample input +- `/tests/test_engine.py` - automated tests + +## Run the sample + +```bash +cd /home/runner/work/https-github.com-TheAlgorithms-Python-blob-master-CONTRIBUTING.md-coding-style/https-github.com-TheAlgorithms-Python-blob-master-CONTRIBUTING.md-coding-style +python -m enhanced_idealization examples/product_strategy.json --profile enterprise_ops +``` + +## Run the tests + +```bash +cd /home/runner/work/https-github.com-TheAlgorithms-Python-blob-master-CONTRIBUTING.md-coding-style/https-github.com-TheAlgorithms-Python-blob-master-CONTRIBUTING.md-coding-style +python -m unittest discover -s tests +``` diff --git a/enhanced_idealization/__init__.py b/enhanced_idealization/__init__.py new file mode 100644 index 0000000..5c517ea --- /dev/null +++ b/enhanced_idealization/__init__.py @@ -0,0 +1,6 @@ +"""Enhanced idealization platform package.""" + +from .engine import EnhancedIdealizationEngine +from .models import IdealizationRequest, IdealizationResult + +__all__ = ["EnhancedIdealizationEngine", "IdealizationRequest", "IdealizationResult"] diff --git a/enhanced_idealization/__main__.py b/enhanced_idealization/__main__.py new file mode 100644 index 0000000..97a5d58 --- /dev/null +++ b/enhanced_idealization/__main__.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from .engine import EnhancedIdealizationEngine +from .models import IdealizationRequest +from .reporting import render_text_report + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Run the enhanced idealization platform.") + parser.add_argument("input_path", type=Path, help="Path to a JSON request file.") + parser.add_argument( + "--profile", + help="Optional profile name to apply to the recommendation process.", + ) + return parser + + +def main() -> None: + parser = build_parser() + args = parser.parse_args() + payload = json.loads(args.input_path.read_text(encoding="utf-8")) + request = IdealizationRequest.from_dict(payload) + result = EnhancedIdealizationEngine().recommend(request, profile_name=args.profile) + print(render_text_report(result)) + + +if __name__ == "__main__": + main() diff --git a/enhanced_idealization/__pycache__/__init__.cpython-312.pyc b/enhanced_idealization/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6ec521776ae6559d5b42978aa4e18064ad90423a GIT binary patch literal 542 zcmb_Yy-ve07_^iAprS5xgQXjagp&sVApxpTmx8KxAd8j8CQ0j`;y6O-##6Af@hnV? zWI+Nkuz|XD!l_zOcD~_!KHYcHcTde`1DF}_U5&rZ`(A$5W39;iz>s@DfDZ^H7K7aK zEm(4!*}URcc-5~$5CKwofPO8nI=VDaq5l2cD*LQv)1N;UCft*0r95*=qFH)Jbb;jtiYMKkn>eH5iFB?Mm0?-F=U%}y9V|Z6r+eZHZ0MDp^ literal 0 HcmV?d00001 diff --git a/enhanced_idealization/__pycache__/engine.cpython-312.pyc b/enhanced_idealization/__pycache__/engine.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0bf6770490721a9a39af8fb4c46fba45adb9b5ac GIT binary patch literal 19228 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 literal 0 HcmV?d00001 diff --git a/enhanced_idealization/engine.py b/enhanced_idealization/engine.py new file mode 100644 index 0000000..afebdce --- /dev/null +++ b/enhanced_idealization/engine.py @@ -0,0 +1,360 @@ +from __future__ import annotations + +from statistics import mean, pstdev + +from .models import ( + Candidate, + CandidateBlueprint, + Constraint, + IdealizationRequest, + IdealizationResult, + Recommendation, + Scenario, + ScenarioAssessment, + UserProfile, + clamp, +) + +DEFAULT_STRATEGIES: dict[str, dict[str, float]] = { + "Balanced Horizon": { + "feasibility": 0.08, + "efficiency": 0.07, + "goal_alignment": 0.08, + "risk_resilience": 0.06, + "adaptability": 0.06, + "collaboration": 0.05, + }, + "Bold Leap": { + "innovation": 0.14, + "goal_alignment": 0.08, + "efficiency": 0.04, + "risk_resilience": -0.07, + "cost_efficiency": -0.06, + "feasibility": -0.03, + }, + "Resilient Core": { + "risk_resilience": 0.14, + "adaptability": 0.11, + "feasibility": 0.06, + "collaboration": 0.03, + "innovation": -0.02, + }, + "Lean Efficiency": { + "cost_efficiency": 0.14, + "efficiency": 0.12, + "feasibility": 0.05, + "collaboration": -0.02, + "innovation": -0.03, + }, + "Collaborative Orbit": { + "collaboration": 0.15, + "adaptability": 0.08, + "goal_alignment": 0.06, + "efficiency": 0.03, + "cost_efficiency": -0.01, + }, +} + + +class EnhancedIdealizationEngine: + def recommend( + self, request: IdealizationRequest, profile_name: str | None = None + ) -> IdealizationResult: + profile = self._select_profile(request, profile_name) + criteria_weights = self._adapt_weights(request, profile) + candidates = self._build_candidates(request, profile) + + recommendations = [] + for candidate in candidates: + base_score, constraint_status = self._score_candidate( + candidate.metrics, request.constraints, criteria_weights + ) + scenario_assessments = [ + self._simulate(candidate, scenario, request.constraints, criteria_weights) + for scenario in request.scenarios + ] + scenario_score = ( + self._weighted_scenario_score(scenario_assessments, request.scenarios) + if scenario_assessments + else base_score + ) + future_readiness = mean( + candidate.metrics.get(metric, 0.0) + for metric in ("adaptability", "innovation", "collaboration") + ) + total_score = round( + (base_score * 0.55) + (scenario_score * 0.35) + (future_readiness * 0.10), 4 + ) + confidence = self._compute_confidence( + candidate.metrics, request.constraints, scenario_assessments, candidate + ) + tradeoffs = self._build_tradeoffs(candidate.metrics) + explanation = self._build_explanation( + candidate.name, + total_score, + base_score, + scenario_score, + confidence, + tradeoffs, + constraint_status, + ) + reasoning_trace = self._build_reasoning_trace( + request, profile, candidate, constraint_status, scenario_assessments + ) + recommendations.append( + Recommendation( + rank=0, + candidate=candidate, + total_score=total_score, + base_score=base_score, + scenario_score=scenario_score, + confidence=confidence, + explanation=explanation, + tradeoffs=tradeoffs, + reasoning_trace=reasoning_trace, + scenario_assessments=scenario_assessments, + constraint_status=constraint_status, + ) + ) + + recommendations.sort(key=lambda item: (item.total_score, item.confidence), reverse=True) + ranked = [ + Recommendation( + rank=index + 1, + candidate=item.candidate, + total_score=item.total_score, + base_score=item.base_score, + scenario_score=item.scenario_score, + confidence=item.confidence, + explanation=item.explanation, + tradeoffs=item.tradeoffs, + reasoning_trace=item.reasoning_trace, + scenario_assessments=item.scenario_assessments, + constraint_status=item.constraint_status, + ) + for index, item in enumerate(recommendations) + ] + return IdealizationResult( + request_title=request.title, + applied_profile=profile.name if profile else None, + recommendations=ranked, + comparison_notes=self._compare_with_history(request, ranked), + criteria_weights=criteria_weights, + ) + + def _select_profile( + self, request: IdealizationRequest, profile_name: str | None + ) -> UserProfile | None: + if profile_name is None: + return request.profiles[0] if request.profiles else None + for profile in request.profiles: + if profile.name == profile_name: + return profile + raise ValueError(f"Unknown profile: {profile_name}") + + def _adapt_weights( + self, request: IdealizationRequest, profile: UserProfile | None + ) -> dict[str, float]: + raw_weights: dict[str, float] = {} + for criterion in request.criteria: + weight = criterion.weight + if profile: + weight += profile.weight_adjustments.get(criterion.name, 0.0) + if any( + constraint.locked and constraint.metric == criterion.name + for constraint in request.constraints + ): + weight += 0.15 + raw_weights[criterion.name] = max(weight, 0.01) + + total = sum(raw_weights.values()) or 1.0 + return {name: round(weight / total, 4) for name, weight in raw_weights.items()} + + def _build_candidates( + self, request: IdealizationRequest, profile: UserProfile | None + ) -> list[Candidate]: + candidates: list[Candidate] = [] + + if request.candidate_blueprints: + for blueprint in request.candidate_blueprints: + metrics = self._apply_profile_bias(dict(blueprint.metrics), profile) + candidates.append( + Candidate( + name=blueprint.name, + description=blueprint.description, + metrics=metrics, + strategy="custom blueprint", + assumptions=blueprint.assumptions, + facts=blueprint.facts, + collaboration_notes=blueprint.collaboration_notes, + ) + ) + + for strategy, deltas in DEFAULT_STRATEGIES.items(): + metrics = {key: value for key, value in request.baseline_metrics.items()} + for metric, delta in deltas.items(): + metrics[metric] = clamp(metrics.get(metric, 0.5) + delta) + metrics = self._apply_profile_bias(metrics, profile) + assumptions = [ + "Baseline metrics realistically represent the current state.", + "The chosen strategy can be executed without adding hidden constraints.", + ] + facts = [ + f"Generated from the '{strategy}' strategy pattern.", + f"Objective focus: {request.objective}.", + ] + candidates.append( + Candidate( + name=strategy, + description=f"{strategy} prioritizes a distinct path toward {request.success_definition}.", + metrics=metrics, + strategy="generated strategy", + assumptions=assumptions, + facts=facts, + collaboration_notes=list(request.collaboration_notes), + ) + ) + + deduplicated: dict[str, Candidate] = {} + for candidate in candidates: + deduplicated[candidate.name] = candidate + return list(deduplicated.values()) + + def _apply_profile_bias( + self, metrics: dict[str, float], profile: UserProfile | None + ) -> dict[str, float]: + if profile is None: + return {key: clamp(value) for key, value in metrics.items()} + adjusted = dict(metrics) + for metric, delta in profile.metric_bias.items(): + adjusted[metric] = clamp(adjusted.get(metric, 0.5) + delta) + return {key: clamp(value) for key, value in adjusted.items()} + + def _score_candidate( + self, + metrics: dict[str, float], + constraints: list[Constraint], + criteria_weights: dict[str, float], + ) -> tuple[float, list[str]]: + base = sum(metrics.get(name, 0.0) * weight for name, weight in criteria_weights.items()) + status: list[str] = [] + penalty = 0.0 + for constraint in constraints: + if constraint.is_satisfied(metrics): + status.append(f"pass: {constraint.name}") + continue + severity = 0.35 if constraint.locked else 0.15 + penalty += severity + status.append(f"fail: {constraint.name}") + return round(clamp(base - penalty), 4), status + + def _simulate( + self, + candidate: Candidate, + scenario: Scenario, + constraints: list[Constraint], + criteria_weights: dict[str, float], + ) -> ScenarioAssessment: + adjusted = dict(candidate.metrics) + for metric, delta in scenario.metric_adjustments.items(): + adjusted[metric] = clamp(adjusted.get(metric, 0.5) + delta) + score, status = self._score_candidate(adjusted, constraints, criteria_weights) + failures = [item.replace("fail: ", "") for item in status if item.startswith("fail: ")] + notes = [scenario.narrative] if scenario.narrative else [] + if failures: + notes.append("Scenario exposes constraint pressure.") + return ScenarioAssessment( + scenario_name=scenario.name, + score=score, + adjusted_metrics=adjusted, + notes=notes, + constraint_failures=failures, + ) + + def _weighted_scenario_score( + self, assessments: list[ScenarioAssessment], scenarios: list[Scenario] + ) -> float: + total_weight = sum(scenario.probability for scenario in scenarios) or 1.0 + weighted = 0.0 + for assessment, scenario in zip(assessments, scenarios, strict=True): + weighted += assessment.score * scenario.probability + return round(weighted / total_weight, 4) + + def _compute_confidence( + self, + metrics: dict[str, float], + constraints: list[Constraint], + assessments: list[ScenarioAssessment], + candidate: Candidate, + ) -> float: + margins = [constraint.margin(metrics) for constraint in constraints] or [0.5] + base = clamp(mean(clamp(margin, -1.0, 1.0) for margin in margins) / 1.5 + 0.5) + scenario_scores = [assessment.score for assessment in assessments] or [0.5] + stability = 1.0 - clamp(pstdev(scenario_scores) if len(scenario_scores) > 1 else 0.0) + evidence = len(candidate.facts) / max(len(candidate.facts) + len(candidate.assumptions), 1) + confidence = (base * 0.4) + (stability * 0.4) + (evidence * 0.2) + return round(clamp(confidence), 4) + + def _build_tradeoffs(self, metrics: dict[str, float]) -> list[str]: + ordered = sorted(metrics.items(), key=lambda item: item[1], reverse=True) + strengths = [name for name, _ in ordered[:2]] + weaknesses = [name for name, _ in ordered[-2:]] + return [ + f"Strength concentrated in {strengths[0]} and {strengths[1]}.", + f"Tradeoff pressure remains around {weaknesses[0]} and {weaknesses[1]}.", + ] + + def _build_explanation( + self, + candidate_name: str, + total_score: float, + base_score: float, + scenario_score: float, + confidence: float, + tradeoffs: list[str], + constraint_status: list[str], + ) -> str: + constraint_summary = "all constraints satisfied" if all( + item.startswith("pass:") for item in constraint_status + ) else "some constraints require attention" + return ( + f"{candidate_name} scores {total_score:.3f} overall " + f"(base {base_score:.3f}, scenario {scenario_score:.3f}) with " + f"{confidence:.3f} confidence; {constraint_summary}. {tradeoffs[0]} {tradeoffs[1]}" + ) + + def _build_reasoning_trace( + self, + request: IdealizationRequest, + profile: UserProfile | None, + candidate: Candidate, + constraint_status: list[str], + assessments: list[ScenarioAssessment], + ) -> list[str]: + stressed = [assessment.scenario_name for assessment in assessments if assessment.constraint_failures] + trace = [ + f"Objective: {request.objective}", + f"Success definition: {request.success_definition}", + f"Candidate path: {candidate.name} ({candidate.strategy})", + f"Profile applied: {profile.name if profile else 'none'}", + f"Constraint status: {', '.join(constraint_status) if constraint_status else 'none'}", + ] + if stressed: + trace.append(f"Stress scenarios: {', '.join(stressed)}") + if candidate.collaboration_notes: + trace.append( + f"Collaboration inputs: {'; '.join(candidate.collaboration_notes)}" + ) + return trace + + def _compare_with_history( + self, request: IdealizationRequest, recommendations: list[Recommendation] + ) -> list[str]: + if not request.history or not recommendations: + return [] + previous_best = max(entry.score for entry in request.history) + current_best = recommendations[0].total_score + delta = round(current_best - previous_best, 4) + if delta >= 0: + return [f"Current top recommendation improves on prior best by {delta:.3f} points."] + return [f"Current top recommendation trails prior best by {abs(delta):.3f} points."] diff --git a/enhanced_idealization/models.py b/enhanced_idealization/models.py new file mode 100644 index 0000000..db71aae --- /dev/null +++ b/enhanced_idealization/models.py @@ -0,0 +1,209 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any + + +def clamp(value: float, lower: float = 0.0, upper: float = 1.0) -> float: + return max(lower, min(upper, value)) + + +@dataclass(frozen=True) +class Criterion: + name: str + weight: float + target: str = "maximize" + + @staticmethod + def from_dict(data: dict[str, Any]) -> "Criterion": + return Criterion( + name=data["name"], + weight=float(data.get("weight", 1.0)), + target=data.get("target", "maximize"), + ) + + +@dataclass(frozen=True) +class Constraint: + name: str + metric: str + minimum: float | None = None + maximum: float | None = None + locked: bool = False + + def is_satisfied(self, metrics: dict[str, float]) -> bool: + value = metrics.get(self.metric, 0.0) + if self.minimum is not None and value < self.minimum: + return False + if self.maximum is not None and value > self.maximum: + return False + return True + + def margin(self, metrics: dict[str, float]) -> float: + value = metrics.get(self.metric, 0.0) + lower_margin = value - self.minimum if self.minimum is not None else 1.0 + upper_margin = self.maximum - value if self.maximum is not None else 1.0 + return min(lower_margin, upper_margin) + + @staticmethod + def from_dict(data: dict[str, Any]) -> "Constraint": + return Constraint( + name=data["name"], + metric=data["metric"], + minimum=data.get("minimum"), + maximum=data.get("maximum"), + locked=bool(data.get("locked", False)), + ) + + +@dataclass(frozen=True) +class Scenario: + name: str + metric_adjustments: dict[str, float] = field(default_factory=dict) + probability: float = 1.0 + narrative: str = "" + + @staticmethod + def from_dict(data: dict[str, Any]) -> "Scenario": + return Scenario( + name=data["name"], + metric_adjustments={ + key: float(value) for key, value in data.get("metric_adjustments", {}).items() + }, + probability=float(data.get("probability", 1.0)), + narrative=data.get("narrative", ""), + ) + + +@dataclass(frozen=True) +class UserProfile: + name: str + weight_adjustments: dict[str, float] = field(default_factory=dict) + metric_bias: dict[str, float] = field(default_factory=dict) + + @staticmethod + def from_dict(data: dict[str, Any]) -> "UserProfile": + return UserProfile( + name=data["name"], + weight_adjustments={ + key: float(value) for key, value in data.get("weight_adjustments", {}).items() + }, + metric_bias={key: float(value) for key, value in data.get("metric_bias", {}).items()}, + ) + + +@dataclass(frozen=True) +class CandidateBlueprint: + name: str + description: str + metrics: dict[str, float] + assumptions: list[str] = field(default_factory=list) + facts: list[str] = field(default_factory=list) + collaboration_notes: list[str] = field(default_factory=list) + + @staticmethod + def from_dict(data: dict[str, Any]) -> "CandidateBlueprint": + return CandidateBlueprint( + name=data["name"], + description=data["description"], + metrics={key: clamp(float(value)) for key, value in data.get("metrics", {}).items()}, + assumptions=list(data.get("assumptions", [])), + facts=list(data.get("facts", [])), + collaboration_notes=list(data.get("collaboration_notes", [])), + ) + + +@dataclass(frozen=True) +class HistoryEntry: + label: str + score: float + summary: str = "" + + @staticmethod + def from_dict(data: dict[str, Any]) -> "HistoryEntry": + return HistoryEntry( + label=data["label"], + score=float(data["score"]), + summary=data.get("summary", ""), + ) + + +@dataclass(frozen=True) +class IdealizationRequest: + title: str + domain: str + objective: str + success_definition: str + baseline_metrics: dict[str, float] + criteria: list[Criterion] + constraints: list[Constraint] + scenarios: list[Scenario] + profiles: list[UserProfile] = field(default_factory=list) + candidate_blueprints: list[CandidateBlueprint] = field(default_factory=list) + collaboration_notes: list[str] = field(default_factory=list) + history: list[HistoryEntry] = field(default_factory=list) + + @staticmethod + def from_dict(data: dict[str, Any]) -> "IdealizationRequest": + return IdealizationRequest( + title=data["title"], + domain=data["domain"], + objective=data["objective"], + success_definition=data["success_definition"], + baseline_metrics={ + key: clamp(float(value)) for key, value in data.get("baseline_metrics", {}).items() + }, + criteria=[Criterion.from_dict(item) for item in data.get("criteria", [])], + constraints=[Constraint.from_dict(item) for item in data.get("constraints", [])], + scenarios=[Scenario.from_dict(item) for item in data.get("scenarios", [])], + profiles=[UserProfile.from_dict(item) for item in data.get("profiles", [])], + candidate_blueprints=[ + CandidateBlueprint.from_dict(item) for item in data.get("candidate_blueprints", []) + ], + collaboration_notes=list(data.get("collaboration_notes", [])), + history=[HistoryEntry.from_dict(item) for item in data.get("history", [])], + ) + + +@dataclass(frozen=True) +class Candidate: + name: str + description: str + metrics: dict[str, float] + strategy: str + assumptions: list[str] = field(default_factory=list) + facts: list[str] = field(default_factory=list) + collaboration_notes: list[str] = field(default_factory=list) + + +@dataclass(frozen=True) +class ScenarioAssessment: + scenario_name: str + score: float + adjusted_metrics: dict[str, float] + notes: list[str] + constraint_failures: list[str] + + +@dataclass(frozen=True) +class Recommendation: + rank: int + candidate: Candidate + total_score: float + base_score: float + scenario_score: float + confidence: float + explanation: str + tradeoffs: list[str] + reasoning_trace: list[str] + scenario_assessments: list[ScenarioAssessment] + constraint_status: list[str] + + +@dataclass(frozen=True) +class IdealizationResult: + request_title: str + applied_profile: str | None + recommendations: list[Recommendation] + comparison_notes: list[str] + criteria_weights: dict[str, float] diff --git a/enhanced_idealization/reporting.py b/enhanced_idealization/reporting.py new file mode 100644 index 0000000..b75cc96 --- /dev/null +++ b/enhanced_idealization/reporting.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +from .models import IdealizationResult + + +def render_text_report(result: IdealizationResult) -> str: + lines = [ + f"Enhanced Idealization Report: {result.request_title}", + f"Applied profile: {result.applied_profile or 'none'}", + "Adaptive criteria weights:", + ] + for name, weight in sorted(result.criteria_weights.items()): + lines.append(f" - {name}: {weight:.3f}") + + if result.comparison_notes: + lines.append("Comparison:") + lines.extend(f" - {note}" for note in result.comparison_notes) + + lines.append("Recommendations:") + for recommendation in result.recommendations: + lines.append( + f"{recommendation.rank}. {recommendation.candidate.name} " + f"(score={recommendation.total_score:.3f}, confidence={recommendation.confidence:.3f})" + ) + lines.append(f" {recommendation.explanation}") + lines.append(" Reasoning trace:") + for item in recommendation.reasoning_trace: + lines.append(f" - {item}") + lines.append(" Scenario assessments:") + for assessment in recommendation.scenario_assessments: + failures = ", ".join(assessment.constraint_failures) if assessment.constraint_failures else "none" + lines.append( + f" - {assessment.scenario_name}: score={assessment.score:.3f}, " + f"constraint_failures={failures}" + ) + return "\n".join(lines) diff --git a/examples/product_strategy.json b/examples/product_strategy.json new file mode 100644 index 0000000..c073828 --- /dev/null +++ b/examples/product_strategy.json @@ -0,0 +1,135 @@ +{ + "title": "Immense Decision Intelligence Workspace", + "domain": "product strategy", + "objective": "Design a platform that generates idealized product futures for enterprise teams.", + "success_definition": "Deliver explainable, resilient, and high-alignment recommendations faster than manual planning.", + "baseline_metrics": { + "feasibility": 0.66, + "efficiency": 0.62, + "goal_alignment": 0.7, + "risk_resilience": 0.58, + "adaptability": 0.6, + "collaboration": 0.57, + "cost_efficiency": 0.59, + "innovation": 0.64 + }, + "criteria": [ + { "name": "feasibility", "weight": 0.17 }, + { "name": "efficiency", "weight": 0.14 }, + { "name": "goal_alignment", "weight": 0.22 }, + { "name": "risk_resilience", "weight": 0.16 }, + { "name": "adaptability", "weight": 0.11 }, + { "name": "collaboration", "weight": 0.08 }, + { "name": "cost_efficiency", "weight": 0.06 }, + { "name": "innovation", "weight": 0.06 } + ], + "constraints": [ + { + "name": "minimum feasibility", + "metric": "feasibility", + "minimum": 0.55, + "locked": true + }, + { + "name": "minimum resilience", + "metric": "risk_resilience", + "minimum": 0.5 + } + ], + "scenarios": [ + { + "name": "budget compression", + "probability": 0.45, + "metric_adjustments": { + "cost_efficiency": 0.08, + "innovation": -0.04, + "feasibility": -0.03 + }, + "narrative": "Budgets tighten and leaders want faster proof of value." + }, + { + "name": "rapid demand surge", + "probability": 0.35, + "metric_adjustments": { + "adaptability": 0.08, + "collaboration": 0.05, + "efficiency": -0.03 + }, + "narrative": "Adoption accelerates and the platform must scale across teams." + }, + { + "name": "compliance expansion", + "probability": 0.2, + "metric_adjustments": { + "risk_resilience": -0.06, + "feasibility": -0.02, + "goal_alignment": 0.03 + }, + "narrative": "Regulatory requirements grow and auditability becomes more important." + } + ], + "profiles": [ + { + "name": "enterprise_ops", + "weight_adjustments": { + "risk_resilience": 0.08, + "collaboration": 0.04, + "innovation": -0.02 + }, + "metric_bias": { + "risk_resilience": 0.05, + "collaboration": 0.04 + } + }, + { + "name": "innovation_lab", + "weight_adjustments": { + "innovation": 0.08, + "adaptability": 0.03, + "cost_efficiency": -0.03 + }, + "metric_bias": { + "innovation": 0.05, + "adaptability": 0.04 + } + } + ], + "candidate_blueprints": [ + { + "name": "Evidence Grid", + "description": "A measured platform centered on auditable recommendations and team consensus.", + "metrics": { + "feasibility": 0.74, + "efficiency": 0.63, + "goal_alignment": 0.79, + "risk_resilience": 0.73, + "adaptability": 0.67, + "collaboration": 0.76, + "cost_efficiency": 0.58, + "innovation": 0.61 + }, + "assumptions": [ + "Teams will share structured input consistently." + ], + "facts": [ + "The blueprint preserves full reasoning trails.", + "The blueprint prioritizes cross-team visibility." + ], + "collaboration_notes": [ + "Operations wants strong auditability.", + "Product leadership wants clear option comparison." + ] + } + ], + "collaboration_notes": [ + "Design wants visual tradeoff comparison.", + "Strategy wants the system to compare multiple futures at once." + ], + "history": [ + { + "label": "manual planning baseline", + "score": 0.69, + "summary": "Past process relied on manual workshops and slower tradeoff analysis." + } + ] +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0797571 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,16 @@ +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.build_meta" + +[project] +name = "enhanced-idealization" +version = "0.1.0" +description = "A Python MVP for generating and evaluating idealized future-state options." +readme = "README.md" +requires-python = ">=3.11" +authors = [ + { name = "GitHub Copilot Task Agent" } +] + +[tool.setuptools] +packages = ["enhanced_idealization"] diff --git a/tests/__pycache__/test_engine.cpython-312.pyc b/tests/__pycache__/test_engine.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9df8b411df8a214977778f694cf9b917a8301793 GIT binary patch literal 6878 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 IdealizationRequest: + return IdealizationRequest.from_dict( + { + "title": "Platform strategy", + "domain": "product", + "objective": "Recommend the best future-state platform direction.", + "success_definition": "Produce explainable, resilient, and scalable options.", + "baseline_metrics": { + "feasibility": 0.65, + "efficiency": 0.63, + "goal_alignment": 0.69, + "risk_resilience": 0.56, + "adaptability": 0.61, + "collaboration": 0.58, + "cost_efficiency": 0.57, + "innovation": 0.66, + }, + "criteria": [ + {"name": "feasibility", "weight": 0.17}, + {"name": "efficiency", "weight": 0.15}, + {"name": "goal_alignment", "weight": 0.2}, + {"name": "risk_resilience", "weight": 0.16}, + {"name": "adaptability", "weight": 0.11}, + {"name": "collaboration", "weight": 0.08}, + {"name": "cost_efficiency", "weight": 0.07}, + {"name": "innovation", "weight": 0.06}, + ], + "constraints": [ + { + "name": "minimum feasibility", + "metric": "feasibility", + "minimum": 0.55, + "locked": True, + }, + { + "name": "minimum resilience", + "metric": "risk_resilience", + "minimum": 0.5, + }, + ], + "scenarios": [ + { + "name": "scale pressure", + "probability": 0.5, + "metric_adjustments": { + "adaptability": 0.06, + "efficiency": -0.03, + "collaboration": 0.04, + }, + }, + { + "name": "budget squeeze", + "probability": 0.5, + "metric_adjustments": { + "cost_efficiency": 0.08, + "feasibility": -0.04, + "innovation": -0.05, + }, + }, + ], + "profiles": [ + { + "name": "enterprise", + "weight_adjustments": {"risk_resilience": 0.08, "collaboration": 0.03}, + "metric_bias": {"risk_resilience": 0.05, "collaboration": 0.03}, + }, + { + "name": "innovation", + "weight_adjustments": {"innovation": 0.08, "adaptability": 0.03}, + "metric_bias": {"innovation": 0.05, "adaptability": 0.04}, + }, + ], + "candidate_blueprints": [ + { + "name": "Safety Mesh", + "description": "Resilient operating model for regulated growth.", + "metrics": { + "feasibility": 0.75, + "efficiency": 0.61, + "goal_alignment": 0.73, + "risk_resilience": 0.8, + "adaptability": 0.65, + "collaboration": 0.7, + "cost_efficiency": 0.55, + "innovation": 0.58, + }, + "facts": ["Proven governance workflow."], + "assumptions": ["Regulated clients dominate adoption."], + }, + { + "name": "Vision Sprint", + "description": "Higher upside model with more innovation leverage.", + "metrics": { + "feasibility": 0.58, + "efficiency": 0.64, + "goal_alignment": 0.72, + "risk_resilience": 0.51, + "adaptability": 0.75, + "collaboration": 0.57, + "cost_efficiency": 0.54, + "innovation": 0.84, + }, + "facts": ["Strong prototype signal."], + "assumptions": ["Teams accept more delivery volatility."], + }, + ], + "history": [{"label": "previous state", "score": 0.67}], + } + ) + + +class EnhancedIdealizationEngineTests(unittest.TestCase): + def setUp(self) -> None: + self.engine = EnhancedIdealizationEngine() + self.request = build_request() + + def test_returns_ranked_recommendations(self) -> None: + result = self.engine.recommend(self.request, profile_name="enterprise") + self.assertGreaterEqual(len(result.recommendations), 5) + self.assertEqual(result.recommendations[0].rank, 1) + self.assertGreaterEqual( + result.recommendations[0].total_score, result.recommendations[-1].total_score + ) + self.assertTrue(result.comparison_notes) + + def test_personalization_changes_recommendation_weights(self) -> None: + enterprise_result = self.engine.recommend(self.request, profile_name="enterprise") + innovation_result = self.engine.recommend(self.request, profile_name="innovation") + self.assertNotEqual( + enterprise_result.criteria_weights["innovation"], + innovation_result.criteria_weights["innovation"], + ) + enterprise_scores = { + item.candidate.name: item.total_score for item in enterprise_result.recommendations + } + innovation_scores = { + item.candidate.name: item.total_score for item in innovation_result.recommendations + } + self.assertGreater( + innovation_scores["Vision Sprint"], + enterprise_scores["Vision Sprint"], + ) + + def test_scenario_stress_testing_is_included(self) -> None: + result = self.engine.recommend(self.request) + top = result.recommendations[0] + self.assertEqual(len(top.scenario_assessments), 2) + self.assertTrue(any("Objective:" in item for item in top.reasoning_trace)) + + def test_low_margin_candidates_reduce_confidence(self) -> None: + result = self.engine.recommend(self.request, profile_name="enterprise") + recommendation_by_name = { + item.candidate.name: item for item in result.recommendations + } + self.assertLess( + recommendation_by_name["Vision Sprint"].confidence, + recommendation_by_name["Safety Mesh"].confidence, + ) + + +if __name__ == "__main__": + unittest.main() From 45c2064c3cf761244cc5d5ece62904b723c731b9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Jul 2026 17:57:45 +0000 Subject: [PATCH 2/6] Ignore generated Python cache files --- .gitignore | 4 ++++ .../__pycache__/__init__.cpython-312.pyc | Bin 542 -> 0 bytes .../__pycache__/engine.cpython-312.pyc | Bin 19228 -> 0 bytes .../__pycache__/models.cpython-312.pyc | Bin 11680 -> 0 bytes tests/__pycache__/test_engine.cpython-312.pyc | Bin 6878 -> 0 bytes 5 files changed, 4 insertions(+) delete mode 100644 enhanced_idealization/__pycache__/__init__.cpython-312.pyc delete mode 100644 enhanced_idealization/__pycache__/engine.cpython-312.pyc delete mode 100644 enhanced_idealization/__pycache__/models.cpython-312.pyc delete mode 100644 tests/__pycache__/test_engine.cpython-312.pyc diff --git a/.gitignore b/.gitignore index e5cbb64..de9b34b 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,7 @@ google-services.json # Android Profiling *.hprof + +# Python cache files +__pycache__/ +*.py[cod] diff --git a/enhanced_idealization/__pycache__/__init__.cpython-312.pyc b/enhanced_idealization/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 6ec521776ae6559d5b42978aa4e18064ad90423a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 542 zcmb_Yy-ve07_^iAprS5xgQXjagp&sVApxpTmx8KxAd8j8CQ0j`;y6O-##6Af@hnV? zWI+Nkuz|XD!l_zOcD~_!KHYcHcTde`1DF}_U5&rZ`(A$5W39;iz>s@DfDZ^H7K7aK zEm(4!*}URcc-5~$5CKwofPO8nI=VDaq5l2cD*LQv)1N;UCft*0r95*=qFH)Jbb;jtiYMKkn>eH5iFB?Mm0?-F=U%}y9V|Z6r+eZHZ0MDp^ diff --git a/enhanced_idealization/__pycache__/engine.cpython-312.pyc b/enhanced_idealization/__pycache__/engine.cpython-312.pyc deleted file mode 100644 index 0bf6770490721a9a39af8fb4c46fba45adb9b5ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19228 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 diff --git a/tests/__pycache__/test_engine.cpython-312.pyc b/tests/__pycache__/test_engine.cpython-312.pyc deleted file mode 100644 index 9df8b411df8a214977778f694cf9b917a8301793..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6878 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 Date: Thu, 16 Jul 2026 17:58:25 +0000 Subject: [PATCH 3/6] Clarify README usage paths --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5268417..eb4e4a3 100644 --- a/README.md +++ b/README.md @@ -26,13 +26,13 @@ with confidence, tradeoffs, and reasoning traces. ## Run the sample ```bash -cd /home/runner/work/https-github.com-TheAlgorithms-Python-blob-master-CONTRIBUTING.md-coding-style/https-github.com-TheAlgorithms-Python-blob-master-CONTRIBUTING.md-coding-style +cd /path/to/enhanced-idealization python -m enhanced_idealization examples/product_strategy.json --profile enterprise_ops ``` ## Run the tests ```bash -cd /home/runner/work/https-github.com-TheAlgorithms-Python-blob-master-CONTRIBUTING.md-coding-style/https-github.com-TheAlgorithms-Python-blob-master-CONTRIBUTING.md-coding-style +cd /path/to/enhanced-idealization python -m unittest discover -s tests ``` From e4be54c9cdfdd04b79a11c6f170d7fc28560d0bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Jul 2026 17:59:03 +0000 Subject: [PATCH 4/6] Harden scoring edge cases --- enhanced_idealization/engine.py | 15 ++++++++++++--- tests/test_engine.py | 11 +++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/enhanced_idealization/engine.py b/enhanced_idealization/engine.py index afebdce..faa1ced 100644 --- a/enhanced_idealization/engine.py +++ b/enhanced_idealization/engine.py @@ -291,14 +291,23 @@ def _compute_confidence( base = clamp(mean(clamp(margin, -1.0, 1.0) for margin in margins) / 1.5 + 0.5) scenario_scores = [assessment.score for assessment in assessments] or [0.5] stability = 1.0 - clamp(pstdev(scenario_scores) if len(scenario_scores) > 1 else 0.0) - evidence = len(candidate.facts) / max(len(candidate.facts) + len(candidate.assumptions), 1) + evidence_total = len(candidate.facts) + len(candidate.assumptions) + evidence = ( + len(candidate.facts) / evidence_total if evidence_total else 0.5 + ) confidence = (base * 0.4) + (stability * 0.4) + (evidence * 0.2) return round(clamp(confidence), 4) def _build_tradeoffs(self, metrics: dict[str, float]) -> list[str]: ordered = sorted(metrics.items(), key=lambda item: item[1], reverse=True) - strengths = [name for name, _ in ordered[:2]] - weaknesses = [name for name, _ in ordered[-2:]] + names = [name for name, _ in ordered] + if not names: + return [ + "Strength data is not available yet.", + "Tradeoff data is not available yet.", + ] + strengths = names[:2] if len(names) > 1 else [names[0], names[0]] + weaknesses = names[-2:] if len(names) > 1 else [names[0], names[0]] return [ f"Strength concentrated in {strengths[0]} and {strengths[1]}.", f"Tradeoff pressure remains around {weaknesses[0]} and {weaknesses[1]}.", diff --git a/tests/test_engine.py b/tests/test_engine.py index 3e41a11..651940c 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py @@ -165,6 +165,17 @@ def test_low_margin_candidates_reduce_confidence(self) -> None: recommendation_by_name["Safety Mesh"].confidence, ) + def test_tradeoff_builder_handles_sparse_metrics(self) -> None: + self.assertEqual( + self.engine._build_tradeoffs({}), + [ + "Strength data is not available yet.", + "Tradeoff data is not available yet.", + ], + ) + tradeoffs = self.engine._build_tradeoffs({"feasibility": 0.8}) + self.assertEqual(len(tradeoffs), 2) + if __name__ == "__main__": unittest.main() From 5a92df64a347cb003a6ed66ac83bca705f6f2aee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Jul 2026 17:59:45 +0000 Subject: [PATCH 5/6] Document scoring constants --- enhanced_idealization/engine.py | 34 +++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/enhanced_idealization/engine.py b/enhanced_idealization/engine.py index faa1ced..bb4a964 100644 --- a/enhanced_idealization/engine.py +++ b/enhanced_idealization/engine.py @@ -55,6 +55,18 @@ }, } +SCORE_WEIGHT_BASE = 0.55 +SCORE_WEIGHT_SCENARIO = 0.35 +SCORE_WEIGHT_FUTURE = 0.10 +LOCKED_CONSTRAINT_WEIGHT_BOOST = 0.15 +LOCKED_CONSTRAINT_PENALTY = 0.35 +UNLOCKED_CONSTRAINT_PENALTY = 0.15 +CONFIDENCE_MARGIN_SCALE = 1.5 +CONFIDENCE_MARGIN_BASELINE = 0.5 +CONFIDENCE_WEIGHT_BASE = 0.4 +CONFIDENCE_WEIGHT_STABILITY = 0.4 +CONFIDENCE_WEIGHT_EVIDENCE = 0.2 + class EnhancedIdealizationEngine: def recommend( @@ -83,7 +95,10 @@ def recommend( for metric in ("adaptability", "innovation", "collaboration") ) total_score = round( - (base_score * 0.55) + (scenario_score * 0.35) + (future_readiness * 0.10), 4 + (base_score * SCORE_WEIGHT_BASE) + + (scenario_score * SCORE_WEIGHT_SCENARIO) + + (future_readiness * SCORE_WEIGHT_FUTURE), + 4, ) confidence = self._compute_confidence( candidate.metrics, request.constraints, scenario_assessments, candidate @@ -164,7 +179,7 @@ def _adapt_weights( constraint.locked and constraint.metric == criterion.name for constraint in request.constraints ): - weight += 0.15 + weight += LOCKED_CONSTRAINT_WEIGHT_BOOST raw_weights[criterion.name] = max(weight, 0.01) total = sum(raw_weights.values()) or 1.0 @@ -243,7 +258,7 @@ def _score_candidate( if constraint.is_satisfied(metrics): status.append(f"pass: {constraint.name}") continue - severity = 0.35 if constraint.locked else 0.15 + severity = LOCKED_CONSTRAINT_PENALTY if constraint.locked else UNLOCKED_CONSTRAINT_PENALTY penalty += severity status.append(f"fail: {constraint.name}") return round(clamp(base - penalty), 4), status @@ -276,7 +291,7 @@ def _weighted_scenario_score( ) -> float: total_weight = sum(scenario.probability for scenario in scenarios) or 1.0 weighted = 0.0 - for assessment, scenario in zip(assessments, scenarios, strict=True): + for assessment, scenario in zip(assessments, scenarios): weighted += assessment.score * scenario.probability return round(weighted / total_weight, 4) @@ -288,14 +303,21 @@ def _compute_confidence( candidate: Candidate, ) -> float: margins = [constraint.margin(metrics) for constraint in constraints] or [0.5] - base = clamp(mean(clamp(margin, -1.0, 1.0) for margin in margins) / 1.5 + 0.5) + base = clamp( + mean(clamp(margin, -1.0, 1.0) for margin in margins) / CONFIDENCE_MARGIN_SCALE + + CONFIDENCE_MARGIN_BASELINE + ) scenario_scores = [assessment.score for assessment in assessments] or [0.5] stability = 1.0 - clamp(pstdev(scenario_scores) if len(scenario_scores) > 1 else 0.0) evidence_total = len(candidate.facts) + len(candidate.assumptions) evidence = ( len(candidate.facts) / evidence_total if evidence_total else 0.5 ) - confidence = (base * 0.4) + (stability * 0.4) + (evidence * 0.2) + confidence = ( + (base * CONFIDENCE_WEIGHT_BASE) + + (stability * CONFIDENCE_WEIGHT_STABILITY) + + (evidence * CONFIDENCE_WEIGHT_EVIDENCE) + ) return round(clamp(confidence), 4) def _build_tradeoffs(self, metrics: dict[str, float]) -> list[str]: From e98cfaeeff9413a2ed3837473d715c74b12efc8f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Jul 2026 18:00:26 +0000 Subject: [PATCH 6/6] Clarify confidence baselines --- enhanced_idealization/engine.py | 18 +++++++++++++----- tests/test_engine.py | 9 +++++++-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/enhanced_idealization/engine.py b/enhanced_idealization/engine.py index bb4a964..afc1092 100644 --- a/enhanced_idealization/engine.py +++ b/enhanced_idealization/engine.py @@ -66,6 +66,9 @@ CONFIDENCE_WEIGHT_BASE = 0.4 CONFIDENCE_WEIGHT_STABILITY = 0.4 CONFIDENCE_WEIGHT_EVIDENCE = 0.2 +DEFAULT_CONSTRAINT_MARGIN = 0.5 +DEFAULT_SCENARIO_SCORE = 0.5 +DEFAULT_EVIDENCE_SCORE = 0.5 class EnhancedIdealizationEngine: @@ -302,16 +305,16 @@ def _compute_confidence( assessments: list[ScenarioAssessment], candidate: Candidate, ) -> float: - margins = [constraint.margin(metrics) for constraint in constraints] or [0.5] + margins = [constraint.margin(metrics) for constraint in constraints] or [DEFAULT_CONSTRAINT_MARGIN] base = clamp( mean(clamp(margin, -1.0, 1.0) for margin in margins) / CONFIDENCE_MARGIN_SCALE + CONFIDENCE_MARGIN_BASELINE ) - scenario_scores = [assessment.score for assessment in assessments] or [0.5] + scenario_scores = [assessment.score for assessment in assessments] or [DEFAULT_SCENARIO_SCORE] stability = 1.0 - clamp(pstdev(scenario_scores) if len(scenario_scores) > 1 else 0.0) evidence_total = len(candidate.facts) + len(candidate.assumptions) evidence = ( - len(candidate.facts) / evidence_total if evidence_total else 0.5 + len(candidate.facts) / evidence_total if evidence_total else DEFAULT_EVIDENCE_SCORE ) confidence = ( (base * CONFIDENCE_WEIGHT_BASE) @@ -328,8 +331,13 @@ def _build_tradeoffs(self, metrics: dict[str, float]) -> list[str]: "Strength data is not available yet.", "Tradeoff data is not available yet.", ] - strengths = names[:2] if len(names) > 1 else [names[0], names[0]] - weaknesses = names[-2:] if len(names) > 1 else [names[0], names[0]] + if len(names) == 1: + return [ + f"Strength currently centers on {names[0]}.", + f"Tradeoff analysis needs additional metrics beyond {names[0]}.", + ] + strengths = names[:2] + weaknesses = names[-2:] return [ f"Strength concentrated in {strengths[0]} and {strengths[1]}.", f"Tradeoff pressure remains around {weaknesses[0]} and {weaknesses[1]}.", diff --git a/tests/test_engine.py b/tests/test_engine.py index 651940c..618bc53 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py @@ -173,8 +173,13 @@ def test_tradeoff_builder_handles_sparse_metrics(self) -> None: "Tradeoff data is not available yet.", ], ) - tradeoffs = self.engine._build_tradeoffs({"feasibility": 0.8}) - self.assertEqual(len(tradeoffs), 2) + self.assertEqual( + self.engine._build_tradeoffs({"feasibility": 0.8}), + [ + "Strength currently centers on feasibility.", + "Tradeoff analysis needs additional metrics beyond feasibility.", + ], + ) if __name__ == "__main__":