@@ -636,6 +636,120 @@ void recommendAssignmentWithUnassigned(SolutionManagerSource SolutionManagerSour
636636 });
637637 }
638638
639+ @ ParameterizedTest
640+ @ EnumSource (SolutionManagerSource .class )
641+ void recommendAssignmentWithUnassignedMatchCount (SolutionManagerSource SolutionManagerSource ) {
642+ int valueSize = 3 ;
643+ var solution = TestdataAllowsUnassignedSolution .generateSolution (valueSize , 3 );
644+ var uninitializedEntity = solution .getEntityList ().get (2 );
645+ uninitializedEntity .setValue (null );
646+
647+ // At this point, entity 0 and entity 2 are unassigned.
648+ // Entity 1 is assigned to value #1.
649+ // But only entity2 should be processed for recommendations.
650+ var solutionManager = SolutionManagerSource .createSolutionManager (SOLVER_FACTORY_UNASSIGNED );
651+ assertThat (solutionManager ).isNotNull ();
652+ var recommendationList =
653+ solutionManager .recommendAssignment (solution , uninitializedEntity , TestdataAllowsUnassignedEntity ::getValue ,
654+ ScoreAnalysisFetchPolicy .FETCH_MATCH_COUNT );
655+
656+ // Three values means there need to be four recommendations, one extra for unassigned.
657+ assertThat (recommendationList ).hasSize (valueSize + 1 );
658+ /*
659+ * The calculator penalizes how many entities have the same value as another entity.
660+ * Therefore the recommendation to assign value 0 and value 2 need to come first and in the order of the placer,
661+ * as it means two entities no longer share a value, improving the score.
662+ */
663+ var recommendation1 = recommendationList .get (0 );
664+ assertSoftly (softly -> {
665+ softly .assertThat (recommendation1 .proposition ()).isEqualTo (solution .getValueList ().get (0 ));
666+ // Two entities no longer share null value; two less matches.
667+ softly .assertThat (recommendation1 .scoreAnalysisDiff ()
668+ .score ()).isEqualTo (SimpleScore .of (2 ));
669+ softly .assertThat (recommendation1 .scoreAnalysisDiff ().constraintMap ())
670+ .extractingFromEntries (e -> e .getValue ().matchCount ())
671+ .first ()
672+ .isEqualTo (-2 );
673+ });
674+ var recommendation2 = recommendationList .get (1 );
675+ assertSoftly (softly -> {
676+ softly .assertThat (recommendation2 .proposition ()).isEqualTo (solution .getValueList ().get (2 ));
677+ softly .assertThat (recommendation2 .scoreAnalysisDiff ()
678+ .score ()).isEqualTo (SimpleScore .of (2 ));
679+ });
680+ // The other two recommendations need to come in order of the placer; so null, then value #1.
681+ var recommendation3 = recommendationList .get (2 );
682+ assertSoftly (softly -> {
683+ softly .assertThat (recommendation3 .proposition ()).isEqualTo (null );
684+ softly .assertThat (recommendation3 .scoreAnalysisDiff ()
685+ .score ()).isEqualTo (SimpleScore .ZERO );
686+ });
687+ var recommendation4 = recommendationList .get (3 );
688+ assertSoftly (softly -> {
689+ softly .assertThat (recommendation4 .proposition ()).isEqualTo (solution .getValueList ().get (1 ));
690+ softly .assertThat (recommendation4 .scoreAnalysisDiff ()
691+ .score ()).isEqualTo (SimpleScore .ZERO );
692+ });
693+ // Ensure the original solution is in its original state.
694+ assertSoftly (softly -> {
695+ softly .assertThat (uninitializedEntity .getValue ()).isNull ();
696+ softly .assertThat (solution .getEntityList ().get (0 ).getValue ()).isNull ();
697+ softly .assertThat (solution .getEntityList ().get (1 ).getValue ()).isEqualTo (solution .getValueList ().get (1 ));
698+ softly .assertThat (solution .getEntityList ().get (2 ).getValue ()).isNull ();
699+ softly .assertThat (solution .getScore ()).isNull ();
700+ });
701+ }
702+
703+ @ ParameterizedTest
704+ @ EnumSource (SolutionManagerSource .class )
705+ void recommendAssignmentWithUnassignedFetchAllVsFetchMatchCount (SolutionManagerSource SolutionManagerSource ) {
706+ int valueSize = 3 ;
707+ var solution = TestdataAllowsUnassignedSolution .generateSolution (valueSize , 3 );
708+ var uninitializedEntity = solution .getEntityList ().get (2 );
709+ uninitializedEntity .setValue (null );
710+
711+ // At this point, entity 0 and entity 2 are unassigned.
712+ // Entity 1 is assigned to value #1.
713+ // But only entity2 should be processed for recommendations.
714+ var solutionManager = SolutionManagerSource .createSolutionManager (SOLVER_FACTORY_UNASSIGNED );
715+ assertThat (solutionManager ).isNotNull ();
716+ var matchCountRecommendationList =
717+ solutionManager .recommendAssignment (solution , uninitializedEntity , TestdataAllowsUnassignedEntity ::getValue ,
718+ ScoreAnalysisFetchPolicy .FETCH_MATCH_COUNT );
719+
720+ var justificationsRecommendationList =
721+ solutionManager .recommendAssignment (solution , uninitializedEntity , TestdataAllowsUnassignedEntity ::getValue ,
722+ ScoreAnalysisFetchPolicy .FETCH_ALL );
723+
724+ // Three values means there need to be four recommendations, one extra for unassigned.
725+ assertThat (matchCountRecommendationList ).hasSize (valueSize + 1 );
726+ assertThat (justificationsRecommendationList ).hasSize (valueSize + 1 );
727+ /*
728+ * The calculator penalizes how many entities have the same value as another entity.
729+ * Therefore the recommendation to assign value 0 and value 2 need to come first and in the order of the placer,
730+ * as it means two entities no longer share a value, improving the score.
731+ */
732+ var matchCountRecommendation1 = matchCountRecommendationList .get (0 );
733+ var justificationsRecommendation1 = justificationsRecommendationList .get (0 );
734+
735+ assertSoftly (softly -> {
736+ softly .assertThat (matchCountRecommendation1 .proposition ()).isEqualTo (solution .getValueList ().get (0 ));
737+ softly .assertThat (justificationsRecommendation1 .proposition ()).isEqualTo (solution .getValueList ().get (0 ));
738+
739+ softly .assertThat (matchCountRecommendation1 .scoreAnalysisDiff ()
740+ .score ()).isEqualTo (SimpleScore .of (2 )); // Two entities no longer share null value.
741+ softly .assertThat (justificationsRecommendation1 .scoreAnalysisDiff ()
742+ .score ()).isEqualTo (SimpleScore .of (2 )); // Two entities no longer share null value.
743+
744+ // The matchCount is expected to be the same in the case of both FETCH_ALL and FETCH_MATCH_COUNT
745+ int matchCountForFetchMatchCount = matchCountRecommendation1 .scoreAnalysisDiff ()
746+ .constraintMap ().values ().iterator ().next ().matchCount ();
747+ int matchCountForFetchAll = justificationsRecommendation1 .scoreAnalysisDiff ()
748+ .constraintMap ().values ().iterator ().next ().matchCount ();
749+ softly .assertThat (matchCountForFetchMatchCount ).isEqualTo (matchCountForFetchAll );
750+ });
751+ }
752+
639753 @ ParameterizedTest
640754 @ EnumSource (SolutionManagerSource .class )
641755 void recommendAssignmentWithAllAssigned (SolutionManagerSource SolutionManagerSource ) {
0 commit comments