Skip to content
This repository was archived by the owner on Jul 17, 2024. It is now read-only.

Commit edc23d0

Browse files
committed
feat: add summary to score analysis
1 parent e6bb0f7 commit edc23d0

File tree

2 files changed

+44
-1
lines changed

2 files changed

+44
-1
lines changed

tests/test_solution_manager.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,21 @@ def assert_score_analysis(problem: Solution, score_analysis: ScoreAnalysis):
127127
assert_constraint_analysis(problem, constraint_analysis)
128128

129129

130+
def assert_score_analysis_summary(score_analysis: ScoreAnalysis):
131+
summary = score_analysis.summary
132+
assert "Explanation of score (3):" in summary
133+
assert "Constraint matches:" in summary
134+
assert "3: constraint (Maximize Value) has 3 matches:" in summary
135+
assert "1: justified with" in summary
136+
137+
match = score_analysis.constraint_analyses[0]
138+
match_summary = match.summary
139+
assert "Explanation of score (3):" in match_summary
140+
assert "Constraint matches:" in match_summary
141+
assert "3: constraint (Maximize Value) has 3 matches:" in match_summary
142+
assert "1: justified with" in match_summary
143+
144+
130145
def assert_solution_manager(solution_manager: SolutionManager[Solution]):
131146
problem: Solution = Solution([Entity('A', 1), Entity('B', 1), Entity('C', 1)], [1, 2, 3])
132147
assert problem.score is None
@@ -140,6 +155,9 @@ def assert_solution_manager(solution_manager: SolutionManager[Solution]):
140155
score_analysis = solution_manager.analyze(problem)
141156
assert_score_analysis(problem, score_analysis)
142157

158+
score_analysis = solution_manager.analyze(problem)
159+
assert_score_analysis_summary(score_analysis)
160+
143161

144162
def test_solver_manager_score_manager():
145163
with SolverManager.create(SolverFactory.create(solver_config)) as solver_manager:

timefold-solver-python-core/src/main/python/score/_score_analysis.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,14 +445,19 @@ class ConstraintAnalysis(Generic[Score_]):
445445
but still non-zero constraint weight; non-empty if constraint has matches.
446446
This is a list to simplify access to individual elements,
447447
but it contains no duplicates just like `set` wouldn't.
448-
448+
summary : str
449+
Returns a diagnostic text
450+
that explains part of the score quality through the ConstraintAnalysis API.
449451
"""
450452
_delegate: '_JavaConstraintAnalysis[Score_]'
451453

452454
def __init__(self, delegate: '_JavaConstraintAnalysis[Score_]'):
453455
self._delegate = delegate
454456
delegate.constraintRef()
455457

458+
def __str__(self):
459+
return self.summary
460+
456461
@property
457462
def constraint_ref(self) -> ConstraintRef:
458463
return ConstraintRef(package_name=self._delegate.constraintRef().packageName(),
@@ -479,6 +484,9 @@ def matches(self) -> list[MatchAnalysis[Score_]]:
479484
def score(self) -> Score_:
480485
return to_python_score(self._delegate.score())
481486

487+
@property
488+
def summary(self) -> str:
489+
return self._delegate.summarize()
482490

483491
class ScoreAnalysis:
484492
"""
@@ -510,6 +518,16 @@ class ScoreAnalysis:
510518
constraint_analyses : list[ConstraintAnalysis]
511519
Individual ConstraintAnalysis instances that make up this ScoreAnalysis.
512520
521+
summary : str
522+
Returns a diagnostic text
523+
that explains the solution through the `ConstraintMatch` API
524+
to identify which constraints cause that score quality.
525+
526+
In case of an infeasible solution, this can help diagnose the cause of that.
527+
Do not parse the return value, its format may change without warning.
528+
Instead, to provide this information in a UI or a service,
529+
use `constraint_analyses` and convert those into a domain-specific API.
530+
513531
Notes
514532
-----
515533
the constructors of this record are off-limits.
@@ -520,6 +538,9 @@ class ScoreAnalysis:
520538
def __init__(self, delegate: '_JavaScoreAnalysis'):
521539
self._delegate = delegate
522540

541+
def __str__(self):
542+
return self.summary
543+
523544
@property
524545
def score(self) -> 'Score':
525546
return to_python_score(self._delegate.score())
@@ -541,6 +562,10 @@ def constraint_analyses(self) -> list[ConstraintAnalysis]:
541562
list['_JavaConstraintAnalysis[Score]'], self._delegate.constraintAnalyses())
542563
]
543564

565+
@property
566+
def summary(self) -> str:
567+
return self._delegate.summarize()
568+
544569

545570
__all__ = ['ScoreExplanation',
546571
'ConstraintRef', 'ConstraintMatch', 'ConstraintMatchTotal',

0 commit comments

Comments
 (0)