forked from elip12/Lines_Queueing
-
Notifications
You must be signed in to change notification settings - Fork 0
/
models.py
794 lines (673 loc) · 32.7 KB
/
models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
from otree.api import (
models,
widgets,
BaseConstants,
BaseSubsession,
BasePlayer,
Currency as c,
currency_range,
)
from otree_redwood.models import Group as RedwoodGroup
from . import config as config_py
import random
import json
from datetime import datetime
from collections import OrderedDict
from jsonfield import JSONField
"""
Eli Pandolfo <epandolf@ucsc.edu>
Notes to ask Kristian:
state of queue
num players
different num players in a group
"""
class Constants(BaseConstants):
name_in_url = 'Lines_Queueing'
participation_fee = c(5)
instructions = 'Lines_Queueing/CheatSheet.html'
instructions_swap = 'Lines_Queueing/CheatSheetSwap.html'
instructions_tili = 'Lines_Queueing/CheatSheetTili.html'
instructions_double = 'Lines_Queueing/CheatSheetDouble.html'
instructions_token = 'Lines_Queueing/CheatSheetToken.html'
config = config_py.export_data()
#for g in config:
# for r in g:
# print(r['settings'])
# for p in r['players']:
# print(p)
# print('\n')
#print('CONFIG EXPORTED')
num_rounds = len(config[0])
print('NUM_ROUNDS:', num_rounds)
num_players = sum([len(group[0]['players']) for group in config])
num_players = len(config[0][0]['players'])
print('NUM PLAYERS: ', num_players)
players_per_group = len(config[0][0]['players'])
print('PLAYERS PER GROUP: ', players_per_group)
#players_per_group = 4
# these will be displayed to players in the UI. Defined here for consistency and
# a central location
alert_messages = {
'cutting': 'You have cut the line',
'cutted': 'Someone has cut in front of you',
'requested': 'You have been requested to swap',
'tokenRequested': 'You have been requested to swap, for a token',
'requesting': 'You have requested to swap',
'accepted': 'Your swap request has been accepted',
'accepting': 'You have accepted a swap request',
'declined': 'Your swap request has been declined',
'declining': 'You have declined a swap request',
'unv_other': 'Requestee is currently in a trade',
'next_self': 'You have entered the service room.',
'next_queue': 'You have advanced one position in the queue',
'next_queue2': 'You have advanced one position in the queue ',
'bad_bid': 'Your bid must be between 0 and your current payoff',
'none': '',
}
class Player(BasePlayer):
time_Instructions = models.LongStringField()
time_Queue = models.LongStringField()
time_Service = models.LongStringField()
time_BP = models.LongStringField()
time_Results = models.LongStringField()
# amount of money player starts with
endowment = models.FloatField()
# tokens gained in token mode
tokens = models.IntegerField(default=0)
# position in queue player starts at
start_pos = models.IntegerField()
end_pos = models.IntegerField()
# time player takes to get serviced
service_time = models.FloatField()
# time player is waiting in the queue before being serviced
waiting_time = models.FloatField()
# $ per second player makes after being serviced, for gain mode
# OR $ per second player loses in waiting & service rooms, for lose mode
pay_rate = models.FloatField()
# Adding double action now (which is technically still swapping by bid, but
# for now will separate the two
# method by which players swap: bid, swap, or cut
swap_method = models.StringField()
# method by which players accumulate money: gain or lose
pay_method = models.StringField()
# money player leaves the round with
round_payoff = models.FloatField()
# discrete and messaging enabled/disabled
discrete = models.BooleanField()
messaging = models.BooleanField()
# c (cost per time not in service)
cost = models.FloatField()
waiting = models.FloatField(default=0)
def set_payoffs(self):
self.payoff = self.in_round(
self.session.vars['pr']).round_payoff
# self.participant.payoff += self.in_round(
# self.session.vars['pr']).round_payoff
# self.participant.payoff += self.payoff
print('in set payoffs')
print(self.participant.payoff)
print(self.round_payoff)
self.participant.payoff = self.round_payoff
print(self.participant.payoff)
# quiz question after the instruction and before the practice round. general quiz.
quiz1 = models.StringField(
choices=[
'Player user ID',
'Player position in the line'
],
widget=widgets.RadioSelect,
label='Question: What does the number mean in each circle on the screen?'
)
def quiz1_error_message(self, value):
if value != 'Player position in the line':
return 'This is the wrong answer. Please choose the correct one.'
quiz2 = models.StringField(
choices=[
'The one in front of you',
'The one behind you',
'Both the one in front of and behind you',
'Anyone in the line'
],
widget=widgets.RadioSelect,
label='Question: Which player can you interact with in each period?'
)
def quiz2_error_message(self, value):
if value != 'Both the one in front of and behind you':
return 'This is the wrong answer. Please choose the correct one.'
quiz3 = models.StringField(
choices=[
'That player is currently involved in other transactions.',
'That player rejects everyone’s transactions.',
'You are not allowed to switch in that period.'
],
widget=widgets.RadioSelect,
label='Question: In some cases your trade button turns grey and you cannot trade with players in front of you. Why does this happen?'
)
def quiz3_error_message(self, value):
if value != 'That player is currently involved in other transactions.':
return 'This is the wrong answer. Please choose the correct one.'
quiz4 = models.StringField(
choices=[
'Yes;Yes',
'No;Yes',
'Yes;No',
'No;No'
],
widget=widgets.RadioSelect,
label='Question: Is there anything you can do after you get served? Will your payoff change after you received your service value and leave the service room?'
)
def quiz4_error_message(self, value):
if value != 'No;No':
return 'This is the wrong answer. Please choose the correct one.'
quiz5 = models.StringField(
choices=[
'You get C since n is decreased by 1',
'You do not have any benefit',
'You get V immediately if you move forward'
],
widget=widgets.RadioSelect,
label='Question: What will you earn if you switch forward with someone else?'
)
def quiz5_error_message(self, value):
if value != 'You get C since n is decreased by 1':
return 'This is the wrong answer. Please choose the correct one.'
# quiz for TILI treatment
quiz9 = models.StringField(
choices=[
'49', '47', '43', '48'
],
widget=widgets.RadioSelect,
label='Question: Suppose you are playing Take/leave rule with a group of 6 players. Your final position is 4, endowment is 50, value is 10, and cost is 6. Your total gain from transactions is 5 so T=5. What is your final payoff?'
)
def quiz9_error_message(self, value):
if value != '47':
return 'This is the wrong answer. Please choose the correct one.'
quiz10 = models.StringField(
choices=[
'5', '6', 'Any price', 'Depends'
],
widget=widgets.RadioSelect,
label='Question: Under Take/leave rule, if your cost is 5, what is the maximum amount you can offer to avoid any loss?'
)
def quiz10_error_message(self, value):
if value != '5':
return 'This is the wrong answer. Please choose the correct one.'
quiz11 = models.StringField(
choices=[
'5', '4', 'Any price', 'Depends'
],
widget=widgets.RadioSelect,
label='Question: Under Take/leave rule, if your cost is 5, what is the minimum amount of offer you can accept to avoid any loss?'
)
def quiz11_error_message(self, value):
if value != '5':
return 'This is the wrong answer. Please choose the correct one.'
quiz12 = models.StringField(
choices=[
'5', '6', '7', '0'
],
widget=widgets.RadioSelect,
label='Question: Suppose you are playing Take/leave rule, The player behind you proposes to pay 7 to you and you accept it. How much will you receive?'
)
def quiz12_error_message(self, value):
if value != '7':
return 'This is the wrong answer. Please choose the correct one.'
# quiz for double treatment
quiz13 = models.StringField(
choices=[
'49', '47', '43', '48'
],
widget=widgets.RadioSelect,
label='Question: Suppose you are playing Double rule with a group of 6 players. Your final position is 4, endowment is 50, value is 10, and cost is 6. Your total gain from transactions is 5 so T=5. What is your final payoff?'
)
def quiz13_error_message(self, value):
if value != '47':
return 'This is the wrong answer. Please choose the correct one.'
quiz14 = models.StringField(
choices=[
'5', '6', 'Any price', 'Depends'
],
widget=widgets.RadioSelect,
label='Question: Under Double rule, if your cost is 5, what is the maximum amount you can offer to avoid any loss?'
)
def quiz14_error_message(self, value):
if value != '5':
return 'This is the wrong answer. Please choose the correct one.'
quiz15 = models.StringField(
choices=[
'5', '4', 'Any price', 'Depends'
],
widget=widgets.RadioSelect,
label='Question: Under Double rule, if your cost is 5, what is the minimum amount you need to ask to avoid any loss?'
)
def quiz15_error_message(self, value):
if value != '5':
return 'This is the wrong answer. Please choose the correct one.'
quiz16 = models.StringField(
choices=[
'Yes; 5', 'Yes; 4.5', 'Yes; 4', 'No; 0'
],
widget=widgets.RadioSelect,
label='Question: Suppose you are playing Double rule, you propose to pay 5 to the player in front of you and they propose to get paid 4. Will the transaction be accepted? How much will you transfer to that player? '
)
def quiz16_error_message(self, value):
if value != 'Yes; 4.5':
return 'This is the wrong answer. Please choose the correct one.'
# quiz for token treatment
quiz18 = models.StringField(
choices=[
'Yes', 'No'
],
widget=widgets.RadioSelect,
label='Question: Under Token rule, does the number of tokens you own affect your final payoff?'
)
def quiz18_error_message(self, value):
if value != 'No':
return 'This is the wrong answer. Please choose the correct one.'
quiz19 = models.StringField(
choices=[
'Yes', 'No'
],
widget=widgets.RadioSelect,
label='Question: Under Token rule, can you see others number of tokens before transaction?'
)
def quiz19_error_message(self, value):
if value != 'Yes':
return 'This is the wrong answer. Please choose the correct one.'
quiz20 = models.StringField(
choices=[
'When I switch with someone behind me.',
'When I switch with someone in front of me.'
],
widget=widgets.RadioSelect,
label='Question: Under Token rule, when will you earn tokens?'
)
def quiz20_error_message(self, value):
if value != 'When I switch with someone behind me.':
return 'This is the wrong answer. Please choose the correct one.'
quiz21 = models.StringField(
choices=[
'When I switch with someone behind me.',
'When I switch with someone in front of me.'
],
widget=widgets.RadioSelect,
label='Question: Under Token rule, when will you lose tokens?'
)
def quiz21_error_message(self, value):
if value != 'When I switch with someone in front of me.':
return 'This is the wrong answer. Please choose the correct one.'
class Group(RedwoodGroup):
cache = JSONField(null=True)
# needed for otree redwood; this should replace the need for the get_timeout_seconds method
# in pages.QueueService, but for some reason does has no effect. This is essentially a wrapper
# for the timeout_seconds variable anyway.
def period_length(self):
g_index = self.get_player_by_id(
1).participant.vars[self.round_number]['group']
return Constants.config[g_index][self.round_number - 1]['settings']['duration']
# takes in the data transferred back and forth by channels,
# and generates a list representing the queue, where each element in the list
# IMPORTANT: this list represents the the entire queue, including players in the service room,
# organized by when they arrived. This means that the 0th element in the returned list is the
# first person to have entered the service room, and the last element in the list is the person
# in the back of the queue.
def queue_state(self, data):
queue = {}
for p in self.get_players():
pp = data[str(p.id_in_group)]
queue[pp['pos']] = pp['id']
return [queue.get(k) for k in sorted(queue)]
def new_metadata(self, g_index, requester_id, requestee_id, swap_method):
m = OrderedDict()
m['subsession_id'] = self.subsession_id
m['round'] = self.round_number
m['group_id'] = g_index
m['requester_id'] = requester_id
m['requester_pos_init'] = 'N/A'
m['requester_pos_final'] = 'N/A'
m['requester_bid'] = 'N/A'
m['requestee_id'] = requestee_id
m['requestee_pos_init'] = 'N/A'
m['requestee_pos_final'] = 'N/A'
m['requestee_bid'] = 'N/A'
m['request_timestamp_absolute'] = 'N/A'
m['response_timestamp_absolute'] = 'N/A'
m['request_timestamp_relative'] = 'N/A'
m['response_timestamp_relative'] = 'N/A'
m['swap_method'] = swap_method
m['status'] = 'N/A'
m['message'] = 'N/A'
m['transaction_price'] = 'N/A'
return m
"""
On swap event: this is a method defined by redwood. It is called when channel.send() is
called in the javascript. That happens when
1) someone starts a trade request by pressing the trade button,
2) someone responds to a trade request by pressing the yes or no button,
3) someone enters the service room and the entire queue moves forward.
This method essentially defines a state machine. Each player has a state, represented by
a dictionary with keys:
id: id in group; a number from 1 to Constants.players_per_group,
pos: position in queue at time of input; a number from -Constants.players_per_group to
Constants.players_per_group,
in_trade: boolean - true if this player has
1) requested a trade and awaits a response;
2) has been requested and has not yet responded,
last_trade_request: timestamp of the last time this player clicked the trade button,
requested: if this player has been requested to swap, the id of the player who made
the request; None, or a number from 1 to Constants.players_per_group,
requesting: if this player has made a request to swap, the id of the player who the
request was made to; None, or a number from 1 to Constants.players_per_group,
accepted: status of trade acceptance; 2 if requesting/no response/not in trade,
1 if accepted, 0 if declined,
alert: the current alert displayed to a player; a value in Constants.alert_messages,
num_players_queue: the number of players who have not entered the service room at
time of input; a number from 0 to Constants.players_per_group,
num_players_service: the number of players who have entered the service room at
time of input; a number from 0 to Constants.players_per_group,
next; boolean: true if someone's service time has just run out, false otherwise;
this is true when someone has passed into the service room, and everyone in
the queue should move forward one position.
The state machine takes in the state of each player, and alters the states of that
player and other players accordingly.
Note that upon this method being called, only one player's state can be different than it was
directly before the method was called; because each time an event occurs,
(request, response, or next) this method gets called.
After updating all player's states, sends the data back to the client.
- Need to ensure that this is true; otherwise, we might need a queue of pending events
"""
def _on_swap_event(self, event=None, **kwargs):
duration = self.period_length()
start_time = event.value['start_time']
# updates states of all players involved in the most recent event that triggered this
# method call
for p in self.get_players():
"""
fields 'requesting', 'accepted', and 'next' of the player who initiated the event
will be updated client-side;
all other fields (the aggregate of which is the player state) are updated here
player states; every player in the round is in exactly one of these states upon the
initiation of an event (when this method gets called)
- reset: no event that involves this player has been initiated by the most recent
call to this method. There is no case for this, as the player's state
is not updated.
- service_clean: this player is not in trade and service time has run out
- service_dirty: this player is in trade and service time has run out.
This is an extension of service_clean.
- service_other: other player's service time has run out
- requesting_clean: player is not in_trade and requesting someone who is not in_trade
- requesting_dirty: player is not in_trade and requesting someone who is in_trade
the JS should make this impossible (disable trade button)
- accepting: player is in_trade and accepting
- declining: player is in_trade and declining
"""
# gets this player's dict from the transmitted event
p1 = event.value[str(p.id_in_group)]
g_index = p.participant.vars[self.round_number]['group']
swap_method = Constants.config[g_index][self.round_number - 1]['settings'][
'swap_method'
]
# someone has entered the service room
if p1['next'] == True:
if p1['pos'] == 0:
# service_clean
p1['alert'] = Constants.alert_messages['next_self']
# service_dirty
if p1['in_trade']:
p2_id = str(p1['requested'])
p2 = event.value[p2_id]
metadata = self.new_metadata(g_index, p2['id'], p1['id'], swap_method)
metadata['request_timestamp_absolute'] = p2['last_trade_request']
p1['in_trade'] = False
p2['in_trade'] = False
p1['requested'] = None
p2['requesting'] = None
p1['accepted'] = 2 # this should be unnecessary
p1['bid'] = None
p2['bid'] = None
metadata['transaction_price'] = 0
metadata['status'] = 'cancelled'
metadata['requester_pos_final'] = p2['pos']
metadata['requestee_pos_final'] = p1['pos']
timestamp = datetime.now().strftime('%s')
metadata['response_timestamp_absolute'] = timestamp
p2['last_trade_request'] = None
event.value[p2_id] = p2
event.value[str(p.id_in_group)] = p1
#metadata['queue'] = self.queue_state(event.value)
self.subsession.dump_metadata(duration, start_time, metadata)
# service_other
elif p1['pos'] > 0:
# this is the only case I know of where you can get the same alert twice in a row (except none)
# if you get the same alert twice in a row the alert will not display because the watch function
# that displays alerts only get called when the alert changes.
if p1['alert'] == Constants.alert_messages['next_queue']:
p1['alert'] = Constants.alert_messages['next_queue2']
else:
p1['alert'] = Constants.alert_messages['next_queue']
else:
p1['alert'] = Constants.alert_messages['none']
p1['next'] = False
# someone has initiated a trade request
elif not p1['in_trade'] and p1['requesting'] != None:
p2 = event.value[str(p1['requesting'])]
metadata = self.new_metadata(g_index, p1['id'], p2['id'], swap_method)
metadata['requester_pos_init'] = p1['pos']
metadata['requestee_pos_init'] = p2['pos']
timestamp = p1['last_trade_request']
metadata['request_timestamp_absolute'] = timestamp
if swap_method == 'cut':
temp = p2['pos']
for i in event.value:
if i != 'metadata' and i != str(p.id_in_group):
if (
event.value[i]['pos'] < p1['pos']
and event.value[i]['pos'] >= p2['pos']
):
event.value[i]['alert'] = Constants.alert_messages[
'cutted'
]
event.value[i]['pos'] += 1
p1['pos'] = temp
p1['alert'] = Constants.alert_messages['cutting']
metadata['requester_pos_final'] = p1['pos']
metadata['requestee_pos_final'] = p2['pos']
metadata['status'] = 'cut'
p1['requesting'] = None
p1['last_trade_request'] = None
event.value[str(p.id_in_group)] = p1
self.subsession.dump_metadata(duration, start_time, metadata)
else:
# requesting_clean
if not p2['in_trade']:
# print('CORRECT ')
message = p1.get('message')
# print(message)
p1['in_trade'] = True
p2['in_trade'] = True
p2['requested'] = p1['id']
# reworked double auction
if swap_method == 'double':
p2['other_bid'] = p1['bid']
else:
p2['bid'] = p1['bid']
p2['message'] = message
p1['alert'] = Constants.alert_messages['requesting']
p2['alert'] = Constants.alert_messages['requested']
event.value[str(p1['requesting'])] = p2
self.subsession.dump_metadata(duration, start_time, metadata)
# requesting_dirty; the js should prevent the logic from ever reaching this
else:
p1['requesting'] = None
p1['alert'] = Constants.alert_messages['unv_other']
# someone has responded to a trade request
elif p1['in_trade'] and p1['requested'] != None:
if p1['accepted'] != 2:
p2_id = str(p1['requested'])
p2 = event.value[p2_id]
metadata = self.new_metadata(g_index, p2_id, p1['id'], swap_method)
metadata['request_timestamp_absolute'] = p2['last_trade_request']
timestamp = datetime.now().strftime('%s')
metadata['response_timestamp_absolute'] = int(timestamp) * 1000
metadata['requester_bid'] = p2.get('bid', 'N/A')
metadata['requestee_bid'] = p1.get('bid', 'N/A')
# declining
if p1['accepted'] == 0:
p1['in_trade'] = False
p2['in_trade'] = False
p1['requested'] = None
p2['requesting'] = None
p1['accepted'] = 2
p1['alert'] = Constants.alert_messages['declining']
p2['alert'] = Constants.alert_messages['declined']
p2['bid'] = None
p1['bid'] = None
metadata['status'] = 'declined'
# accepting
elif p1['accepted'] == 1:
p1['in_trade'] = False
p2['in_trade'] = False
p1['requested'] = None
p2['requesting'] = None
p1['accepted'] = 2
temp = p1['pos']
p1['pos'] = p2['pos']
p2['pos'] = temp
p1['alert'] = Constants.alert_messages['accepting']
p2['alert'] = Constants.alert_messages['accepted']
# fix for typeError when accepting a swap during which
# the swapMethod is 'swap'
if swap_method == 'swap':
p2['bid'] = None
elif swap_method == 'token':
p2['tokens'] -= 1
p1['tokens'] += 1
p2['bid'] = -float(p1['bid'])
elif swap_method == 'take/Leave':
p2['bid'] = -float(p1['bid'])
else:
# reworked double auction
p2['other_bid'] = p1['bid']
av_bid = ( float(p1['bid']) + float(p2['bid']) ) / 2
p2['average_bid'] = -av_bid
p1['average_bid'] = av_bid
metadata['status'] = 'accepted'
metadata['requester_pos_final'] = p2['pos']
metadata['requestee_pos_final'] = p1['pos']
message = p1.get('message', 'N/A')
if message == '':
message = 'N/A'
metadata['message'] = message
if swap_method == 'take/Leave':
metadata['transaction_price'] = p1.get('bid')
elif swap_method == 'double':
metadata['transaction_price'] = p1.get('average_bid')
else:
metadata['transaction_price'] = 'N/A'
p2['last_trade_request'] = None
event.value[p2_id] = p2
event.value[str(p.id_in_group)] = p1
self.subsession.dump_metadata(duration, start_time, metadata)
event.value[str(p.id_in_group)] = p1 # partially redundant
# broadcast the updated data out to all subjects
self.send('swap', event.value)
# cache state of queue so that client pages will not reset on reload
self.cache = event.value
# manually save all updated fields to db. otree redwood thing
self.save()
class Subsession(BaseSubsession):
def dump_metadata(self, duration=None, start_time=None, metadata=None):
if metadata == None:
s = ','.join([
'subsession_id',
'round',
'group_id',
'requester_id',
'requester_pos_init',
'requester_pos_final',
'requester_bid',
'requestee_id',
'requestee_pos_init',
'requestee_pos_final',
'requestee_bid',
'request_timestamp_absolute',
'response_timestamp_absolute',
'request_timestamp_relative',
'response_timestamp_relative',
'swap_method',
'status',
'message',
'transaction_price',
])
else:
if metadata['request_timestamp_absolute'] != 'N/A':
metadata['request_timestamp_relative'] = \
int(metadata['request_timestamp_absolute']) / 1000 - start_time
if metadata['response_timestamp_absolute'] != 'N/A':
metadata['response_timestamp_relative'] = \
int(metadata['response_timestamp_absolute']) / 1000 - start_time
strvals = (str(e) for e in metadata.values())
s = ','.join(strvals)
fname = self.session.vars['data_fname'] + '_metadata.csv'
with open(fname, 'a+') as f:
f.write(s + '\n')
def creating_session(self):
if self.round_number == 1:
data_fname = f'Lines_Queueing/data/lines_queueing_{self.session.code}'
self.session.vars['metadata'] = {}
self.session.vars['metadata_requests'] = {}
self.session.vars['data_fname'] = data_fname
non_practice_rounds = []
# Constants.config[group number][round]
group0 = Constants.config[0]
for round_num, period in enumerate(group0):
if period['settings']['block_id'] is not 0:
non_practice_rounds.append(round_num+1)
self.session.vars['pr'] = random.choice(non_practice_rounds)
# self.session.vars['pr'] = random.randint(2, Constants.num_rounds)
# just dump header
self.dump_metadata()
self.group_randomly()
self.session.vars[self.round_number] = [{}
for i in range(len(self.get_groups()))]
for g_index, g in enumerate(self.get_groups()):
g_data = Constants.config[g_index][self.round_number - 1]['players']
# sets up each player's starting values
for p in g.get_players():
p.participant.vars[self.round_number] = {}
p.participant.vars[self.round_number]['pay_rate'] = g_data[
p.id_in_group - 1
]['pay_rate']
p.participant.vars[self.round_number]['c'] = g_data[p.id_in_group - 1][
'c'
]
p.participant.vars[self.round_number]['service_time'] = g_data[
p.id_in_group - 1
]['service_time']
p.participant.vars[self.round_number]['start_pos'] = g_data[
p.id_in_group - 1
]['start_pos']
p.participant.vars[self.round_number]['endowment'] = g_data[
p.id_in_group - 1
]['endowment']
p.participant.vars[self.round_number]['group'] = g_index
p_data = {
'id': p.id_in_group,
'pos': p.participant.vars[self.round_number]['start_pos'],
'in_trade': False,
'last_trade_request': None,
'requested': None,
'requesting': None,
'bid': None,
'accepted': 2,
'alert': Constants.alert_messages['none'],
'num_players_queue': Constants.num_players,
'num_players_service': 0,
'next': False,
'tokens': 0,
}
self.session.vars[self.round_number][g_index][p.id_in_group] = p_data
"""
metadata structure:
{ 'timestamp': {'bid': None/$, status': 'accepted/declined/cancelled/cut', 'requester': #, 'requestee': #, 'queue': [#,#,#...]}, ... }
"""