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

Last change on this file since 24 was 24, checked in by bart, 4 years ago

minor fixes to improve extendability

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