source: src/main/webapp/utilstable.xhtml@ 40

Last change on this file since 40 was 40, checked in by bart, 3 years ago

Refactor to help reusing partiesserver.

File size: 15.5 KB
RevLine 
[40]1<?xml version="1.0" encoding="UTF-8"?>
2<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
3<head>
4<title>Show tournament results table</title>
5<link rel="stylesheet" type="text/css" href="style.css" />
6</head>
7<style>
8canvas {
9 -moz-user-select: none;
10 -webkit-user-select: none;
11 -ms-user-select: none;
12}
13</style>
14<body>
15 <div class="noscript">
16 <h2 style="color: #ff0000">Seems your browser doesn't support
17 Javascript! Websockets rely on Javascript being enabled. Please
18 enable Javascript and reload this page!</h2>
19 </div>
20 <h1 id="header">Tournament results</h1>
21
22 Progress:
23 <div id="progress">Waiting for log file name</div>
24 <br />
25
26 <table id="outcomes">
27 <thead>
28 <tr>
29 <th align="center">sess / agree</th>
30 <th align="center">accepted bid in session</th>
31 <th align="center">party utility - penalty</th>
32 </tr>
33 </thead>
34 <tbody id="outcomeslist">
35 <!-- one row for each session run added by script -->
36 </tbody>
37
38 </table>
39</body>
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56<!-- Script to get/update the table using the query given in the URL. -->
57<script type="application/javascript">
58
59
60
61
62
63 <![CDATA[
64 "use strict";
65
66
67
68 var ws = null;
69 var SHAOP=false;
70 /* dict with key=profile URL and value = the downloaded (json) profile */
71 var profiles = {};
72
73 <!-- Most functions are reactive programming written out-->
74
75 /**
76 Called when the page opens.
77 */
78 function init() {
79
80 var id=document.location.search;
81 id = location.search.split('?')[1]
82 if (id==undefined) return;
83
84 // load the log file with given id
85 document.getElementById('header').innerHTML="Tournament results table of "+id;
86
87 var url = "log/"+id+".json";
88 setStatus("Downloading file "+url);
89 var request=new XMLHttpRequest();
90 request.responseType = 'text';
91 request.open('Get', url)
92 request.onload=function() {
93 if (request.status!=200) {
94 setStatus("Failed to fetch "+url+":" +request.status+" "+request.statusText)
95 return;
96 }
97 processLogFile(JSON.parse(request.response));
98 }
99 request.send();
100 }
101
102
103 /**
104 Figure out the protocol contents and parse accordingly.
105 */
106 function processLogFile(json) {
107 if (json['AllPermutationsState']!=undefined) {
108 processAPP(json['AllPermutationsState'], 'AllPermutationsSettings');
109 } else if (json['AllPermutationsLearnState']!=undefined) {
110 processAPP(json['AllPermutationsLearnState'],'AllPermutationsLearnSettings');
111 } else
112 {
113 setStatus("Unknown log file contents "+Object.keys(json));
114 }
115 }
116
117 /**
118 Handle AllPermutationsState protocol result.
119 @param json the json contents of the file inside the top level header
120 @param settingsname the name of the expected settings object inside,
121 eg 'AllPermutationSettings'
122 */
123 function processAPP(json, settingsname) {
124 setStatus("processing the log file");
125
126 if (json['toursettings'][settingsname]['sessionsettings']['SHAOPSettings']!=undefined) {
127 SHAOP=true;
128 }
129
130 // collect all profiles from all profileslists
131 var profiles= [];
132 for (const profilelist of json['toursettings'][settingsname]['profileslists']) {
133 profiles = profiles.concat(profilelist['ProfileList']);
134 }
135 getAllProfiles(json, profiles);
136 }
137
138 /**
139 Step 1, get all profiles. Repeats recursiuvely till profileurls is empty.
140 Note just calling the function again without recurse would be better.
141 Problem is that we call this after we got the profile to ensure we
142 have all profiles at the end.
143 @param profileurls the list of profile urls that need to be fetched.
144 @param profiles a dictionary of profiles . Key is profileurl, value the profile. Should
145 be initially {}
146 returns only after all profiles have been fetched.
147 */
148 function getAllProfiles(json, profileurls) {
149 for (var n in profileurls) {
150 getProfile(profileurls[n], function() {fillRows(json)} );
151 }
152 }
153
154 /**
155 This function returns immediately and will add the profile to the profiles dictionary some time later.
156 @param profurl the profile URL may have ?partial filter.
157 @param callwhendone function is called (without arguments) when profiles contains no pending anymore
158 */
159 function getProfile(profurl, callwhendone) {
160 profiles[profurl]='pending'; // store ORIGINAL URL
161 var profileurl = profurl.split('?',1)[0]; // hacky, remove complete query.
162
163 setStatus("fetching profile "+profileurl);
164 var ws;
165 if ('WebSocket' in window) {
166 ws = new WebSocket(profileurl);
167 } else if ('MozWebSocket' in window) {
168 ws = new MozWebSocket(profileurl);
169 } else {
170 setStatus('Fatal: WebSocket is not supported by this browser. Please use a newer browser');
171 return;
172 }
173 ws.onmessage = function (event) {
174 ws.close();
175 var profile = JSON.parse(event.data);
176 if (profile['LinearAdditiveUtilitySpace']==undefined) {
177 setStatus('Fatal: profile '+profileurl+" does not contain a LinearAdditiveUtilitySpace.");
178 return;
179 }
180
181 profiles[profurl]=profile['LinearAdditiveUtilitySpace'];
182
183 checkAllProfiles(callwhendone);
184 };
185 ws.onerror=function(event) {
186 ws.close();
187 setStatus('Error fetching profile '+uri+':'+event);
188 }
189 }
190
191 /**
192 Check if there are still pending profiles. If not, callwhendone().
193 */
194 function checkAllProfiles(callwhendone) {
195 for (var key in profiles) {
196 if (profiles[key] == 'pending') return;
197 }
198 callwhendone();
199 }
200
201 /**
202 Step 2, fill the table with util values
203 @param profiles a dictionary of profiles . Key is profileurl, value the profile. Should
204 be initially {}
205 */
206 function fillRows(json)
207 {
208 setStatus("Computing utilities");
209 var table = document.getElementById("outcomeslist");
210 table.innerHTML="";
211 var results = json['results'];
212
213 for (var nr in results) {
214 var result = results[nr];
215 fillBids(table, nr, result);
216 }
217 setStatus("done");
218 }
219
220 /**
221 Add rows for all the bids in the result. Notice that some protocols allow multiple bids to be accepted per session.
222 @param table the table to add the bid results to
223 @param nr the session number
224 @param results the results for this sess. This is a dict with key: partyid (eg "party83+127_0_1_1)
225 and the value the accepted issuevalues (the bid contents). Parties that did not reach an agreement
226 are not in the list.
227 */
228 function fillBids(table, nr, result) {
229 // collect unique bids in agreements.
230 var agreements = result['agreements'];
231
232 //if (Object.keys(agreements).length == 0) {
233 // fillRow(table.insertRow(-1), nr, result, {});
234 // return;
235 //}
236
237 // collect all bids, plus "null" if there are parties without agreements.
238 var bids = [];
239 const participants = result['participants'];
240 for (var pid in participants) {
241 if (pid in agreements) {
242 bids=addSet(bids, agreements[pid]['issuevalues']);
243 } else {
244 bids=addSet(bids, null)
245 }
246 }
247
248 var dealnr='A'.charCodeAt(0);
249 for (const bid of bids) {
250 fillRow(table.insertRow(-1), nr+String.fromCharCode(dealnr++), result, bid);
251 }
252 }
253
254
255
256
257 /**
258 @param row the table row object in the document
259 @param nr the row number
260 @param result a single json result from the APP results section.
261 Contains participants, agreements and error
262 @param agreedbid the bid to show in this row. dict, with hte issuevalues from the bid.
263 If null, this should show parties that did not reach an agreement.
264 */
265 function fillRow(row, nr, result, agreedbid) {
266 row.insertCell(0).innerHTML = nr;
267
268 if (result['error']!=null) {
269 var errstr="session failed:";
270 var err=result['error'];
271 if ("geniusweb.protocol.ProtocolException" in err) {
272 errstr=errstr+err["geniusweb.protocol.ProtocolException"]['cause']['message'];
273 }
274 row.insertCell(-1).innerHTML=errstr;
275 } else {
276 if (agreedbid==null) {
277 row.insertCell(-1).innerHTML = "No deal";
278 } else {
279 row.insertCell(-1).innerHTML = JSON.stringify(agreedbid);
280 }
281 }
282 // fill in the columns. If SHAOP, only the even parties
283 // FIXME this can't be with a MAP
284 const participants = result['participants'];
285 for (var pid in participants) {
286 var partyandprofile = result['participants'][pid];
287 var profile =partyandprofile['profile'];
288 var penalty=result['penalties'][pid];
289 if (penalty==undefined) penalty=0;
290 // make short name for readability
291 var partyname = partyandprofile['party']['partyref'];
292 partyname = partyname.split('/');
293 partyname = partyname[partyname.length-1];
294
295 // we set bid to agreedbid only if party accepted agreedbid.
296 var bid = {};
297 if (pid in result['agreements'] && agreedbid!=null &&
298 deepEqual(agreedbid, result['agreements'][pid]['issuevalues'] ) )
299 bid = agreedbid;
300 addUtilityCell(row.insertCell(-1), bid, partyname, profile, penalty);
301 }
302 }
303
304 /**
305 @param cell the table cell to put the result in
306 @param agreedbid the bid that was agreed on
307 @param partyname the short name of the party
308 @param profileurl the profile url to use for the evaluation of the bid
309 @param bid the accepted bid, not null.
310 @param penalty costs made by the party, to be subtracted from total util.
311 typically these are elicitation costs.
312 */
313 function addUtilityCell(cell, agreedbid, partyname, profileurl, penalty) {
314 var util = utility(profiles[profileurl],agreedbid);
315 var rUtil = Math.round(util*1000000)/1000000;
316 var rPenalty = Math.round( (penalty )*1000000)/1000000 ;
317 cell.innerHTML = rUtil + "-" + rPenalty + " :"+partyname;
318
319 }
320
321
322
323
324
325 /******************* Private support funcs****************/
326
327 /**
328 Set the progress/status text
329 */
330 function setStatus(text) {
331 document.getElementById('progress').innerHTML=text;
332 }
333
334 /*
335 @param sessions a list of IDs (Strings).
336 */
337 function update(sessions) {
338 var table = document.getElementById("sessionsTable");
339 table.innerHTML="";
340 for(var session of sessions) {
341 var row = table.insertRow(-1); //-1 = end
342 row.insertCell(0).innerHTML = session;
343 }
344
345 }
346
347
348
349
350 /**
351 Compute utilityes
352 @param profile the linear additive utility space
353 @param issueValues the bid containing dict with values for the issues
354 @param isResBidAlternative true if the reservation bid is usable as alternative bid
355 . If true, and the issueValues list is empty/null, this returns the utility
356 of the reservation bid instead.
357 @return utility of issueValues. Returns 0 if profile =null or no bid available
358
359 */
360 function utility(profile, issueValues, isResBidAlternative) {
361 if (profile==null) return 0;
362
363 // check if we need the reservationbid.
364 if (issueValues==null || Object.keys(issueValues).length==0) {
365 var resBid = profile['reservationBid'];
366 if (resBid==null) return 0;
367 issueValues = resBid['issuevalues'];
368 }
369
370 if (issueValues==null) return 0;
371
372 var util=0;
373 var weights = profile['issueWeights'];
374
375 for (var issue in issueValues) {
376 util = util + weights[issue] * evaluate(profile['issueUtilities'][issue], issueValues[issue] );
377 }
378 return util;
379 }
380
381 /**
382 Bit hacky, javascript version of evaluator of utility space.
383 Would be much nicer if we could use java here
384 */
385 function evaluate (utilfunc, value) {
386 if (utilfunc == undefined) {
387 return 0;
388 }
389 if (utilfunc['NumberValueSetUtilities']!=undefined) {
390 // it's numeric issue. Compute
391 var minval = utilfunc['NumberValueSetUtilities']['lowValue'];
392 var minutil = utilfunc['NumberValueSetUtilities']['lowUtility'];
393 var maxval = utilfunc['NumberValueSetUtilities']['highValue'];
394 var maxutil = utilfunc['NumberValueSetUtilities']['highUtility'];
395
396 var alfa = (value-minval) / (maxval - minval) ;
397 alfa = Math.max(Math.min(alfa, 1),0);
398 return alfa * maxutil + (1-alfa) * minutil;
399 }
400 if (utilfunc['DiscreteValueSetUtilities']!=undefined) {
401 // it's discrete issue. Compute
402 if (value in utilfunc['DiscreteValueSetUtilities']['valueUtilities'])
403 return utilfunc['DiscreteValueSetUtilities']['valueUtilities'][value];
404 return 0;
405 }
406 setStatus("Unknown utility function type "+Object.keys(utilfunc));
407
408 }
409
410 /************ util functions to compare dict objects **************/
411 /**
412 @return true if object1, object2 are 'deep equal'. That means,
413 if they are dicts then we check that the keys and their values are equal,
414 recursively.
415 */
416 function deepEqual(object1, object2) {
417 if (object1==null) {
418 return object2==null;
419 }
420 if (object2==null) return false;
421 const keys1 = Object.keys(object1);
422 const keys2 = Object.keys(object2);
423
424 if (keys1.length !== keys2.length) {
425 return false;
426 }
427
428 for (const key of keys1) {
429 const val1 = object1[key];
430 const val2 = object2[key];
431 const areObjects = isObject(val1) && isObject(val2);
432 if (
433 areObjects && !deepEqual(val1, val2) || !areObjects && val1 !== val2
434 ) {
435 return false;
436 }
437 }
438
439 return true;
440 }
441
442 function isObject(object) {
443 return object != null && typeof object === 'object';
444 }
445
446 /**
447 @param list the list to add value to
448 @param value the value to add
449 @return list with the value added, but only if not already in list.
450 Uses deepEqual to check equality of list members
451 */
452 function addSet(list, value) {
453 for (const v of list) {
454 if (deepEqual(v, value))
455 return list;
456 }
457 return list.concat(value);
458 }
459
460 ]]>
461
462
463
464
465 <![CDATA[
466 "use strict";
467 /*
468 This is called when the window DOM is loaded. window.onload runs BEFORE the window is loaded
469 and therefore is too early to remove the noscript message.
470 */
471 document.addEventListener("DOMContentLoaded", function() {
472 // Remove elements with "noscript" class - <noscript> is not allowed in XHTML
473 var noscripts = document.getElementsByClassName("noscript");
474 for (var i = 0; i < noscripts.length; i++) {
475 noscripts[i].parentNode.removeChild(noscripts[i]);
476 }
477 init();
478
479 }, false);
480 ]]>
481
482
483
484</script>
485
486
487</html>
Note: See TracBrowser for help on using the repository browser.