diff --git a/app/helpers/common_helper.py b/app/helpers/common_helper.py index 8f3da37..d74ec0e 100644 --- a/app/helpers/common_helper.py +++ b/app/helpers/common_helper.py @@ -1,5 +1,14 @@ + def boolean_to_int(value: bool) -> int: if value: return 1 else: return 0 + + +def is_float(element: str) -> bool: + try: + float(element) + return True + except ValueError: + return False diff --git a/app/helpers/service_helper.py b/app/helpers/service_helper.py index 7d252c8..8e36fc0 100644 --- a/app/helpers/service_helper.py +++ b/app/helpers/service_helper.py @@ -3,7 +3,9 @@ import io import re from tokenize import String +from helpers import common_helper from models.item import Item +from models.solver_run import SolverRun def csv_to_item(items_csv_reader, solver_run): items = [] @@ -17,7 +19,7 @@ def csv_to_item(items_csv_reader, solver_run): item = {'attributes': []} # ensure that the b param is formatted correctly - if row[len(headers) - 1] != '' and is_float(row[len(headers) - 1]): + if row[len(headers) - 1] != '' and common_helper.is_float(row[len(headers) - 1]): for key, col in enumerate(headers): if solver_run.irt_model.formatted_b_param() == col: value = float(row[key]) @@ -100,11 +102,14 @@ def key_to_uuid(key): return re.split("_", key)[0] -def solution_items(variables, solver_run): +def solution_items(variables: list, solver_run: SolverRun) -> (list, list): form_items = [] + solver_variables = [] for v in variables: if v.varValue > 0: + solver_variables.append(v.name) + if 'Item_' in v.name: item_id = v.name.replace('Item_', '') item = solver_run.get_item(int(item_id)) @@ -118,17 +123,9 @@ def solution_items(variables, solver_run): for item in bundle.items: if item: form_items.append(item) - return form_items + return form_items, solver_variables def print_problem_variables(problem): # Uncomment this as needed in local dev # print(problem); - for v in problem.variables(): print(v.name, "=", v.varValue); - -# probably a better place for this... -def is_float(element: String) -> bool: - try: - float(element) - return True - except ValueError: - return False + for v in problem.variables(): print(v.name, "=", v.varValue) diff --git a/app/models/form.py b/app/models/form.py index 2077d08..e2842f2 100644 --- a/app/models/form.py +++ b/app/models/form.py @@ -1,13 +1,15 @@ from pydantic import BaseModel -from typing import List +from typing import List, TypeVar, Type from helpers import irt_helper +from models.solver_run import SolverRun from models.item import Item from models.target import Target from lib.irt.test_response_function import TestResponseFunction +_T = TypeVar("_T") class Form(BaseModel): items: List[Item] @@ -15,13 +17,15 @@ class Form(BaseModel): tif_results: List[Target] tcc_results: List[Target] status: str = 'Not Optimized' + solver_variables: List[str] @classmethod - def create(cls, items, solver_run, status): + def create(cls: Type[_T], items: list, solver_run: SolverRun, status: str, solver_variables: list) -> _T: return cls( items=items, cut_score=TestResponseFunction(solver_run.irt_model).calculate( items, theta=solver_run.theta_cut_score), tif_results=irt_helper.generate_tif_results(items, solver_run), tcc_results=irt_helper.generate_tcc_results(items, solver_run), - status=status) + status=status, + solver_variables=solver_variables) diff --git a/app/models/objective_function.py b/app/models/objective_function.py index 5ff9c26..5081f1d 100644 --- a/app/models/objective_function.py +++ b/app/models/objective_function.py @@ -10,6 +10,7 @@ class ObjectiveFunction(BaseModel): # likely with models representing each objective function type tif_targets: List[Target] tcc_targets: List[Target] + target_variance_percentage: int = 10 objective: AnyStr = "minimize" weight: Dict = {'tif': 1, 'tcc': 1} diff --git a/app/services/loft_service.py b/app/services/loft_service.py index 4cf9bba..d4ad4e1 100644 --- a/app/services/loft_service.py +++ b/app/services/loft_service.py @@ -105,6 +105,15 @@ class LoftService(Base): # Dynamic constraints.. currently we only support Metadata and Bundles(Cases/Passages) problem = solver_helper.build_constraints(self.solver_run, problem, items, bundles) + # form uniqueness constraints + for form in solution.forms: + form_item_options = [ + bundles[bundle.id] for bundle in self.solver_run.bundles + ] + [ + items[item.id] for item in self.solver_run.unbundled_items() + ] + problem += len(set(form.solver_variables)&set(form_item_options)) / float(len(set(form.solver_variables) | set(form_item_options))) * 100 >= 10 + logging.info('Creating TIF and TCC Elastic constraints') # Behold our very own Elastic constraints! @@ -181,10 +190,10 @@ class LoftService(Base): return solution - def add_form_to_solution(self, problem, solution): + def add_form_to_solution(self, problem: LpProblem, solution: Solution): # add return items and create as a form - form_items = service_helper.solution_items(problem.variables(), self.solver_run) - form = Form.create(form_items, self.solver_run, LpStatus[problem.status]) + form_items, solver_variables = service_helper.solution_items(problem.variables(), self.solver_run) + form = Form.create(form_items, self.solver_run, LpStatus[problem.status], solver_variables) solution.forms.append(form) diff --git a/app/services/solver_sandbox.py b/app/services/solver_sandbox.py index e87ff99..e00f294 100644 --- a/app/services/solver_sandbox.py +++ b/app/services/solver_sandbox.py @@ -19,6 +19,14 @@ class SolverSandbox: if ApplicationConfigs.local_dev_env: body = {'Records': [{'eventVersion': '2.1', 'eventSource': 'aws:s3', 'awsRegion': 'us-east-1', 'eventTime': '2022-03-17T13:51:22.708Z', 'eventName': 'ObjectCreated:Put', 'userIdentity': {'principalId': 'AIDAJDPLRKLG7UEXAMPLE'}, 'requestParameters': {'sourceIPAddress': '127.0.0.1'}, 'responseElements': {'x-amz-request-id': '25ecd478', 'x-amz-id-2': 'eftixk72aD6Ap51TnqcoF8eFidJG9Z/2'}, 's3': {'s3SchemaVersion': '1.0', 'configurationId': 'testConfigRule', 'bucket': {'name': 'measure-local-solver-ingest', 'ownerIdentity': {'principalId': 'A3NL1KOZZKExample'}, 'arn': 'arn:aws:s3:::measure-local-solver-ingest'}, 'object': {'key': '40f23de0-8827-013a-a353-0242ac120010_solver_run.tar.gz', 'size': 491, 'eTag': '"2b423d91e80d931302192e781b6bd47c"', 'versionId': None, 'sequencer': '0055AED6DCD90281E5'}}}]} + # CPNRE item bank with metadata and cases + # body = {'Records': [{'eventVersion': '2.1', 'eventSource': 'aws:s3', 'awsRegion': 'us-east-1', 'eventTime': '2022-03-23T18:49:42.979Z', 'eventName': 'ObjectCreated:Put', 'userIdentity': {'principalId': 'AIDAJDPLRKLG7UEXAMPLE'}, 'requestParameters': {'sourceIPAddress': '127.0.0.1'}, 'responseElements': {'x-amz-request-id': 'c4efd257', 'x-amz-id-2': 'eftixk72aD6Ap51TnqcoF8eFidJG9Z/2'}, 's3': {'s3SchemaVersion': '1.0', 'configurationId': 'testConfigRule', 'bucket': {'name': 'measure-local-solver-ingest', 'ownerIdentity': {'principalId': 'A3NL1KOZZKExample'}, 'arn': 'arn:aws:s3:::measure-local-solver-ingest'}, 'object': {'key': 'e8f38480-8d07-013a-5ee6-0242ac120010_solver_run.tar.gz', 'size': 12716, 'eTag': '"94189c36aef04dde3babb462442c3af3"', 'versionId': None, 'sequencer': '0055AED6DCD90281E5'}}}]} + + # LOFT item bank with metadata and cases + body = {'Records': [{'eventVersion': '2.1', 'eventSource': 'aws:s3', 'awsRegion': 'us-east-1', 'eventTime': '2022-03-22T19:36:53.568Z', 'eventName': 'ObjectCreated:Put', 'userIdentity': {'principalId': 'AIDAJDPLRKLG7UEXAMPLE'}, 'requestParameters': {'sourceIPAddress': '127.0.0.1'}, 'responseElements': {'x-amz-request-id': '61f320d0', 'x-amz-id-2': 'eftixk72aD6Ap51TnqcoF8eFidJG9Z/2'}, 's3': {'s3SchemaVersion': '1.0', 'configurationId': 'testConfigRule', 'bucket': {'name': 'measure-local-solver-ingest', 'ownerIdentity': {'principalId': 'A3NL1KOZZKExample'}, 'arn': 'arn:aws:s3:::measure-local-solver-ingest'}, 'object': {'key': '5971f500-8c45-013a-5d13-0242ac120010_solver_run.tar.gz', 'size': 619, 'eTag': '"a3cbba098e9f6a445cba6014e47ccaf9"', 'versionId': None, 'sequencer': '0055AED6DCD90281E5'}}}]} + + # Latest CPNRE Item Bank with metadata and cases + body = {'Records': [{'eventVersion': '2.1', 'eventSource': 'aws:s3', 'awsRegion': 'us-east-1', 'eventTime': '2022-03-24T15:47:54.652Z', 'eventName': 'ObjectCreated:Put', 'userIdentity': {'principalId': 'AIDAJDPLRKLG7UEXAMPLE'}, 'requestParameters': {'sourceIPAddress': '127.0.0.1'}, 'responseElements': {'x-amz-request-id': '1969b1ed', 'x-amz-id-2': 'eftixk72aD6Ap51TnqcoF8eFidJG9Z/2'}, 's3': {'s3SchemaVersion': '1.0', 'configurationId': 'testConfigRule', 'bucket': {'name': 'measure-local-solver-ingest', 'ownerIdentity': {'principalId': 'A3NL1KOZZKExample'}, 'arn': 'arn:aws:s3:::measure-local-solver-ingest'}, 'object': {'key': 'ab40ca20-8db7-013a-a88f-0242ac120013_solver_run.tar.gz', 'size': 24111, 'eTag': '"718a1a17b5dd5219b8e179bfd1ddf1ca"', 'versionId': None, 'sequencer': '0055AED6DCD90281E5'}}}]} LoftService(body).process() def yosh_loop(): @@ -94,7 +102,6 @@ class SolverSandbox: } items = LpVariable.dicts('Item', Items, cat='Binary') - drift = 0 max_drift = 25# 25% elasticity