def rig_participant(event): """ Rig the specified wheel for the specified participant. Default behavior is comical rigging (hidden == False) but hidden can be specified to indicate deceptive rigging (hidden == True) :param event: Lambda event containing the API Gateway request path parameters wheel_id and participant_id { "pathParameters": { "wheel_id": string ID of the wheel to rig (DDB Hash Key) "participant_id": string ID of the participant to rig (DDB Hash Key) }, "body": { "hidden": boolean indicates deceptive rigging if True, comical if False } } :return: response dictionary """ # By default, rigging the wheel isn't hidden but they can be wheel_id = event['pathParameters']['wheel_id'] participant_id = event['pathParameters']['participant_id'] hidden = bool(event['body'].get('hidden', False)) update = {'rigging': {'participant_id': participant_id, 'hidden': hidden}} Wheel.update_item(Key={'id': wheel_id}, **to_update_kwargs(update))
def select_participant(event): """ Indicates selection of a participant by the wheel. This will cause updates to the weights for all participants or removal of rigging if the wheel is rigged. :param event: Lambda event containing the API Gateway request path parameters wheel_id and participant_id { "pathParameters": { "wheel_id": string ID of the wheel to rig (DDB Hash Key) "participant_id": string ID of the participant to rig (DDB Hash Key) }, } :return: response dictionary """ wheel_id = event['pathParameters']['wheel_id'] participant_id = event['pathParameters']['participant_id'] wheel = Wheel.get_existing_item(Key={'id': wheel_id}) participant = WheelParticipant.get_existing_item(Key={ 'id': participant_id, 'wheel_id': wheel_id }) choice_algorithm.select_participant(wheel, participant) # Undo any rigging that has been set up Wheel.update_item(Key={'id': wheel['id']}, UpdateExpression='remove rigging')
def wrap_participant_creation(wheel, participant): participant['weight'] = 1 yield Wheel.update_item(Key={'id': wheel['id']}, **to_update_kwargs({ 'participant_count': wheel['participant_count'] + 1 }))
def wrap_participant_creation(wheel, participant): participant['weight'] = 1 yield count = 0 with WheelParticipant.batch_writer() as batch: for p in WheelParticipant.iter_query( KeyConditionExpression=Key('wheel_id').eq(wheel['id'])): count += 1 Wheel.update_item(Key={'id': wheel['id']}, **to_update_kwargs({'participant_count': count}))
def on_participant_deletion(wheel, participant): """ Normalize the remaining participant weights to account for participant removal. The ratio is based on the following: 1) The participant should be at weight=1 when it leaves the system (which is the same as it arrived) 2) That difference should be split by the remaining participants proportional by weight This ensures that 'weight=0' participants are still at weight=0 and that the sum of all weights is equal to the number of participants, so new additions are treated fairly :param wheel: Wheel dictionary: { "id": string ID of the wheel (DDB Hash Key), "name": string name of the wheel, "participant_count": number of participants in the wheel, } :param participant: Participant dictionary: { "id": string ID of the wheel (DDB Hash Key), "name": string name of the wheel, "url": Participant's URL, "wheel_id": string ID of the wheel the participant belongs to, } :return: None """ total_weight = participant['weight'] for p in WheelParticipant.iter_query( KeyConditionExpression=Key('wheel_id').eq(wheel['id'])): total_weight += p['weight'] weight = participant['weight'] remaining_weight = total_weight - weight # <-- no longer presumes existing weight balance via 'int(participant_count)' ratio = (1 + ((weight - 1) / remaining_weight)) if ( remaining_weight != 0) else 1 num_participants = Decimal(0) with WheelParticipant.batch_writer() as batch: for p in WheelParticipant.iter_query( KeyConditionExpression=Key('wheel_id').eq(wheel['id'])): if p['id'] != participant['id']: # This is cast to a string before turning into a decimal because of rounding/inexact guards in boto3 p['weight'] = Decimal( str(float(p['weight']) * float(ratio))) if (remaining_weight != 0) else 1 batch.put_item(Item=p) num_participants = num_participants + 1 Wheel.update_item(Key={'id': wheel['id']}, **to_update_kwargs( {'participant_count': num_participants}))
def unrig_participant(event): """ Remove rigging for the specified wheel :param event: Lambda event containing the API Gateway request path parameter wheel_id { "pathParameters": { "wheel_id": string ID of the wheel (DDB Hash Key) } } :return: response dictionary """ # By default, rigging the wheel isn't hidden but they can be wheel_id = event['pathParameters']['wheel_id'] Wheel.update_item(Key={'id': wheel_id}, UpdateExpression='remove rigging')
def select_participant(wheel, participant): """ Register the selection of a participant by updating the weights of all participants for a given wheel :param wheel: Wheel dictionary: { "id": string ID of the wheel (DDB Hash Key), "name": string name of the wheel, "participant_count": number of participants in the wheel, } :param participant: Participant dictionary: { "id": string ID of the participant (DDB Hash Key), "name": string name of the participant, "url": Participant's URL, "wheel_id": string ID of the wheel the participant belongs to, "weight": participant's weight in the selection algorithm } :return: None """ num_participants = 0 total_weight = Decimal(0) for p in WheelParticipant.iter_query( KeyConditionExpression=Key('wheel_id').eq(wheel['id'])): num_participants = num_participants + 1 total_weight += p['weight'] # Factor is the number by which all weights must be multiplied # so total weight will be equal to the number of participants. factor = Decimal(num_participants) / total_weight if num_participants > 1: weight_share = participant['weight'] / Decimal(num_participants - 1) with WheelParticipant.batch_writer() as batch: # Redistribute and normalize the weights. for p in WheelParticipant.iter_query( KeyConditionExpression=Key('wheel_id').eq(wheel['id'])): if p['id'] == participant['id']: p['weight'] = 0 else: p['weight'] += Decimal(weight_share) p['weight'] *= factor batch.put_item(Item=p) Wheel.update_item(Key={'id': wheel['id']}, **to_update_kwargs( {'participant_count': num_participants}))
def reset_wheel(wheel): """ Resets the weights of all participants in the wheel and updates the wheel's participant count :param wheel: Wheel dictionary: { "id": string ID of the wheel (DDB Hash Key), "name": string name of the wheel, "participant_count": number of participants in the wheel, } :return: None """ count = 0 with WheelParticipant.batch_writer() as batch: for p in WheelParticipant.iter_query(KeyConditionExpression=Key('wheel_id').eq(wheel['id'])): p['weight'] = get_sub_wheel_size(p['name']) batch.put_item(Item=p) count += 1 Wheel.update_item(Key={'id': wheel['id']}, **to_update_kwargs({'participant_count': count}))
def update_wheel(event): """ Update the name of the wheel and/or refresh its participant count :param event: Lambda event containing the API Gateway request path parameter wheel_id { "pathParameters": { "wheel_id": string ID of the wheel (DDB Hash Key) }, "body": { "id": string ID of the wheel (DDB Hash Key), "name": string name of the wheel, } } :return: response dictionary containing the updated wheel object if successful { "body": { "id": string ID of the wheel (DDB Hash Key), "name": string name of the wheel, "participant_count": number of participants in the wheel, "created_at": creation timestamp, "updated_at": updated timestamp, } } """ wheel_id = event['pathParameters']['wheel_id'] key = {'id': wheel_id} # Make sure wheel exists wheel = Wheel.get_existing_item(Key=key) name = event['body'].get('name', None) if not check_string(name): raise base.BadRequestError( "Updating a wheel requires a new name of at least 1 character in length" ) update = {'name': name, 'updated_at': get_utc_timestamp()} Wheel.update_item(Key=key, **to_update_kwargs(update)) # Represent the change locally for successful responses wheel.update(update) return wheel