From a919530cab64e3b2d4dfe412bf63c73db6ae8457 Mon Sep 17 00:00:00 2001
From: Josh Burman <jburman@yas.getyardstick.com>
Date: Wed, 27 Apr 2022 15:46:23 +0000
Subject: [PATCH 1/6] proper typing and bundle first ordering

---
 app/helpers/service_helper.py | 13 ++++++++++---
 app/models/bundle.py          |  3 +++
 app/models/item.py            |  1 +
 app/models/solver_run.py      | 10 +++++-----
 4 files changed, 19 insertions(+), 8 deletions(-)

diff --git a/app/helpers/service_helper.py b/app/helpers/service_helper.py
index cfae641..3617025 100644
--- a/app/helpers/service_helper.py
+++ b/app/helpers/service_helper.py
@@ -105,6 +105,8 @@ def key_to_uuid(key):
 
 def solution_items(variables: list, solver_run: SolverRun) -> Tuple[list]:
     form_items = []
+    form_bundles = []
+    final_items = []
     solver_variables = []
 
     for v in variables:
@@ -120,9 +122,14 @@ def solution_items(variables: list, solver_run: SolverRun) -> Tuple[list]:
                 bundle_id = v.name.replace('Bundle_', '')
                 bundle = solver_run.get_bundle(int(bundle_id))
 
-                if bundle:
-                    for item in bundle.items:
-                        if item: form_items.append(item)
+                if bundle: form_bundles.append(bundle)
+
+    for bundle in form_bundles:
+        for item in bundle.ordered_items:
+            final_items.append(item)
+
+    for item in form_items:
+        final_items.append(item)
 
     return form_items, solver_variables
 
diff --git a/app/models/bundle.py b/app/models/bundle.py
index 82fb127..7b15a9f 100644
--- a/app/models/bundle.py
+++ b/app/models/bundle.py
@@ -46,3 +46,6 @@ class Bundle(BaseModel):
             total += self.trf(solver_run.irt_model, target.theta)
 
         return total
+
+    def ordered_items(self) -> List[Item]:
+        return sorted(self.items, key=lambda x: x.position)
diff --git a/app/models/item.py b/app/models/item.py
index 260d612..f9f0a65 100644
--- a/app/models/item.py
+++ b/app/models/item.py
@@ -8,6 +8,7 @@ from lib.irt.item_information_function import ItemInformationFunction
 
 class Item(BaseModel):
     id: int
+    position: Optional[int]
     passage_id: Optional[int]
     workflow_state: Optional[str]
     attributes: List[Attribute]
diff --git a/app/models/solver_run.py b/app/models/solver_run.py
index ad54659..69550d0 100644
--- a/app/models/solver_run.py
+++ b/app/models/solver_run.py
@@ -14,7 +14,7 @@ from models.advanced_options import AdvancedOptions
 
 class SolverRun(BaseModel):
     items: List[Item] = []
-    bundles: list[Bundle] = []
+    bundles: List[Bundle] = []
     constraints: List[Constraint]
     irt_model: IRTModel
     objective_function: ObjectiveFunction
@@ -40,7 +40,7 @@ class SolverRun(BaseModel):
             if type == constraint.reference_attribute.type:
                 return constraint
 
-    def remove_items(self, items: list[Item]) -> bool:
+    def remove_items(self, items: List[Item]) -> bool:
         self.items = [item for item in self.items if item not in items]
         return True
 
@@ -100,7 +100,7 @@ class SolverRun(BaseModel):
         return next((constraint for constraint in self.constraints
                      if constraint.reference_attribute.id == name), None)
 
-    def unbundled_items(self) -> list:
+    def unbundled_items(self) -> List[Item]:
         # since the only bundles are based on passage id currently
         # in the future when we have more than just passage based bundles
         # we'll need to develop a more sophisticated way of handling this concern
@@ -113,7 +113,7 @@ class SolverRun(BaseModel):
         else:
             return self.items
 
-    def select_items_by_percent(self, percent: int) -> list[Item]:
+    def select_items_by_percent(self, percent: int) -> List[Item]:
         items = self.unbundled_items()
         total_items = len(items)
         selected_items_amount = round(total_items - (total_items *
@@ -121,7 +121,7 @@ class SolverRun(BaseModel):
 
         return random.sample(items, selected_items_amount)
 
-    def select_bundles_by_percent(self, percent: int) -> list[Bundle]:
+    def select_bundles_by_percent(self, percent: int) -> List[Bundle]:
         total_bundles = len(self.bundles)
         selected_bundles_amount = round(total_bundles - (total_bundles *
                                                          (percent / 100)))

From a3ca2339538eb08c82e2c57ebc1e179da947bb6e Mon Sep 17 00:00:00 2001
From: Josh Burman <jburman@yas.getyardstick.com>
Date: Wed, 27 Apr 2022 15:54:59 +0000
Subject: [PATCH 2/6] enforce bundle first ordering if bundles ordering is true

---
 app/helpers/service_helper.py | 3 ++-
 app/models/solver_run.py      | 1 +
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/app/helpers/service_helper.py b/app/helpers/service_helper.py
index 3617025..8acd664 100644
--- a/app/helpers/service_helper.py
+++ b/app/helpers/service_helper.py
@@ -125,7 +125,8 @@ def solution_items(variables: list, solver_run: SolverRun) -> Tuple[list]:
                 if bundle: form_bundles.append(bundle)
 
     for bundle in form_bundles:
-        for item in bundle.ordered_items:
+        items = bundle.ordered_items if solver_run.bundle_first_ordering else bundle.items
+        for item in items:
             final_items.append(item)
 
     for item in form_items:
diff --git a/app/models/solver_run.py b/app/models/solver_run.py
index 69550d0..79eb9ed 100644
--- a/app/models/solver_run.py
+++ b/app/models/solver_run.py
@@ -15,6 +15,7 @@ from models.advanced_options import AdvancedOptions
 class SolverRun(BaseModel):
     items: List[Item] = []
     bundles: List[Bundle] = []
+    bundle_first_ordering: bool = True
     constraints: List[Constraint]
     irt_model: IRTModel
     objective_function: ObjectiveFunction

From 35a5ab061b8d0cc8ec0993087ecf8599d6469244 Mon Sep 17 00:00:00 2001
From: Josh Burman <jburman@yas.getyardstick.com>
Date: Wed, 27 Apr 2022 16:03:14 +0000
Subject: [PATCH 3/6] final items need to be returned

---
 app/helpers/service_helper.py |  2 +-
 app/services/loft_service.py  | 10 +++++-----
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/app/helpers/service_helper.py b/app/helpers/service_helper.py
index 8acd664..7f74d1e 100644
--- a/app/helpers/service_helper.py
+++ b/app/helpers/service_helper.py
@@ -132,7 +132,7 @@ def solution_items(variables: list, solver_run: SolverRun) -> Tuple[list]:
     for item in form_items:
         final_items.append(item)
 
-    return form_items, solver_variables
+    return final_items, solver_variables
 
 def print_problem_variables(problem):
     # Uncomment this as needed in local dev
diff --git a/app/services/loft_service.py b/app/services/loft_service.py
index d854291..0174158 100644
--- a/app/services/loft_service.py
+++ b/app/services/loft_service.py
@@ -83,11 +83,11 @@ class LoftService(Base):
                                      lowBound=0,
                                      upBound=1,
                                      cat='Binary')
-            bundles = LpVariable.dicts(
-                "Bundle", [bundle.id for bundle in selected_bundles],
-                lowBound=0,
-                upBound=1,
-                cat='Binary')
+            bundles = LpVariable.dicts("Bundle",
+                                       [bundle.id for bundle in selected_bundles],
+                                       lowBound=0,
+                                       upBound=1,
+                                       cat='Binary')
 
             logging.info(f'Generating Solution for Form {form_number}')
 

From a44a2162b0fea56faf0366aed10d95757b64d4da Mon Sep 17 00:00:00 2001
From: Josh Burman <jburman@yas.getyardstick.com>
Date: Wed, 27 Apr 2022 18:38:50 +0000
Subject: [PATCH 4/6] make sorted lambda variable more descriptive

---
 app/models/bundle.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/models/bundle.py b/app/models/bundle.py
index 7b15a9f..475d710 100644
--- a/app/models/bundle.py
+++ b/app/models/bundle.py
@@ -48,4 +48,4 @@ class Bundle(BaseModel):
         return total
 
     def ordered_items(self) -> List[Item]:
-        return sorted(self.items, key=lambda x: x.position)
+        return sorted(self.items, key=lambda item: item.position)

From f92f48700215a0f5191d4cd3276dd598cef215d3 Mon Sep 17 00:00:00 2001
From: Joshua Burman <jburman@meazurelearning.com>
Date: Wed, 11 May 2022 15:11:37 -0400
Subject: [PATCH 5/6] quick fix to call method...hah python

---
 app/helpers/service_helper.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/app/helpers/service_helper.py b/app/helpers/service_helper.py
index 7f74d1e..589ade6 100644
--- a/app/helpers/service_helper.py
+++ b/app/helpers/service_helper.py
@@ -125,7 +125,8 @@ def solution_items(variables: list, solver_run: SolverRun) -> Tuple[list]:
                 if bundle: form_bundles.append(bundle)
 
     for bundle in form_bundles:
-        items = bundle.ordered_items if solver_run.bundle_first_ordering else bundle.items
+        items = bundle.ordered_items(
+        ) if solver_run.bundle_first_ordering else bundle.items
         for item in items:
             final_items.append(item)
 

From 2576e69783a4b6143fc2b4b9ba35013a3ad9106b Mon Sep 17 00:00:00 2001
From: Joshua Burman <jburman@meazurelearning.com>
Date: Wed, 11 May 2022 15:12:45 -0400
Subject: [PATCH 6/6] increment version

---
 app/main.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/main.py b/app/main.py
index 1459f59..b2dfc00 100644
--- a/app/main.py
+++ b/app/main.py
@@ -25,7 +25,7 @@ class ServiceListener(Consumer):
 
 
 def main():
-    logging.info('Starting Solver Service: Tokyo Drift (v1.2)...')
+    logging.info('Starting Solver Service: Tokyo Drift (v1.3.1)...')
 
     # ToDo: Figure out a much better way of doing this.
     # LocalStack wants 'endpoint_url', while prod doesnt :(