From 1351a1bb7b5da8df4dd5d3b007e7b0cc5ed8cd48 Mon Sep 17 00:00:00 2001 From: tomaszrudowski <tomasz@innit.no> Date: Wed, 1 Jun 2022 13:33:01 +0200 Subject: [PATCH] Final state of analytic tools --- app/Http/Controllers/APIController.php | 304 ++++++++++++- app/Population.php | 1 + app/Voter.php | 1 + public/js/app.js | 424 ++++++++++++++++-- resources/js/components/population-index.vue | 33 +- resources/js/components/population-show.vue | 32 +- .../components/population-template-show.vue | 82 +++- routes/api_internal.php | 6 + 8 files changed, 815 insertions(+), 68 deletions(-) diff --git a/app/Http/Controllers/APIController.php b/app/Http/Controllers/APIController.php index 87d9734..59d1ac1 100644 --- a/app/Http/Controllers/APIController.php +++ b/app/Http/Controllers/APIController.php @@ -12,6 +12,7 @@ use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Storage; use Illuminate\Validation\ValidationException; class APIController extends Controller @@ -216,14 +217,22 @@ class APIController extends Controller if (!isset($template->parent_id)) { $newPopulationName = "[". $template->name . "].(" . $attributes['election_type'] . "-" . $nextChildId . ")"; } else { - $newPopulationName = $template->name . ".(" . $attributes['election_type'] . "-" . $nextChildId . ")"; + //TEMP + $newPopulationName = $template->name . ".(g2-" . $nextChildId . ")"; + //$newPopulationName = $template->name . ".(" . $attributes['election_type'] . "-" . $nextChildId . ")"; } $newPopulation->election_type = $attributes['election_type']; $newPopulation->name = $newPopulationName; $newPopulation->parent_id = $template->id; $newPopulation->forgetting_factor = $template->forgetting_factor; $newPopulation->follower_factor = $template->follower_factor; - $newPopulation->stage = $newPopulation->election_type === "d1" ? 'p' : 'l'; // 'performance'/'learning' default stage for child population + + // 'performance'/'learning' default stage for child population + if($newPopulation->election_type === "d1" || $newPopulation->election_type === "m") { + $newPopulation->stage = 'p'; + } else { + $newPopulation->stage = 'l'; + } $newPopulation->save(); foreach ($template->voters as $parentPopulationVoter) { @@ -241,8 +250,21 @@ class APIController extends Controller public function getPopulationTemplates(Request $request) { + $attributes = $request->validate([ + 'top_level_only' => 'nullable|boolean' + ]); + + $topLevelOnly = isset($attributes["top_level_only"]) ? $attributes["top_level_only"] : true; + $data = Population::with('voters', 'elections', 'childPopulations') ->whereNull('parent_id') + ->when(!$topLevelOnly,function($q) { + $q->orWhere(function($q) { + $q->where('stage','=','p') + ->where('election_type', '<>', 'd1'); + }); + }) + //->whereNull('parent_id') ->orderBy('id', 'desc') ->get() ->makeHidden(['elections', 'voters', 'childPopulations']); @@ -256,7 +278,13 @@ class APIController extends Controller public function getPopulationTemplate(int $template, Request $request) { $data = Population::where('id', '=', $template) - ->whereNull('parent_id') + ->where(function($q) { + $q->whereNull('parent_id') + ->orWhere(function($q) { + $q->where('stage','=','p') + ->where('election_type', '<>', 'd1'); + }); + }) ->with('elections', 'voters', 'childPopulations') ->firstOrFail() ->append(['elections_stats', 'voters_stats', 'children_count', 'child_populations_stats']) @@ -289,8 +317,8 @@ class APIController extends Controller ->append($toAppend) ->makeHidden('elections', 'voters'); - $data->forgetting_percent = 100 - (100 * Config::get('app.forgetting_factor', 0)); - $data->follower_factor = Config::get('app.follower_factor'); + //$data->forgetting_percent = 100 - (100 * Config::get('app.forgetting_factor', 0)); + //$data->follower_factor = Config::get('app.follower_factor'); return response()->json($data, Response::HTTP_OK); } @@ -473,6 +501,12 @@ class APIController extends Controller $omitElectionDetails = isset($attributes['omit_details']) && $attributes['omit_details']; $numberOfElections = isset($attributes['number']) ? $attributes['number'] : 1; + + $existingPopulation = Population::where('id', '=', $population) + ->where('parent_id', '=', $template) + ->with('voters') + ->firstOrFail(); + switch ($attributes['type']) { case 'm' : $electionMethod = 'runSingleMajorityElection'; @@ -481,20 +515,15 @@ class APIController extends Controller $electionMethod = 'runSingleDelegationElectionVersion1'; break; case 'd2' : - $electionMethod = 'runSingleDelegationElectionVersion2'; + $electionMethod = $existingPopulation->stage == 'l' ? 'runSingleDelegationElectionVersion2' : 'runSingleDelegationElectionVersion1'; break; case 'd3' : - $electionMethod = 'runSingleDelegationElectionVersion3'; + $electionMethod = $existingPopulation->stage == 'l' ? 'runSingleDelegationElectionVersion3' : 'runSingleDelegationElectionVersion1'; break; default : return response()->json('unknown election type', Response::HTTP_UNPROCESSABLE_ENTITY); } - $existingPopulation = Population::where('id', '=', $population) - ->where('parent_id', '=', $template) - ->with('voters') - ->firstOrFail(); - $data->population_name = $existingPopulation->name; @@ -984,7 +1013,7 @@ class APIController extends Controller $type = $attributes['election_type']; $metadata->election_type = $type; $templatePopulation = Population::where('id', '=', $template) - ->whereNull('parent_id') + //->whereNull('parent_id') ->with(['childPopulations' => function ($q) use ($type) { $q->where('election_type', '=', $type) ->with('elections.extension'); @@ -1127,6 +1156,255 @@ class APIController extends Controller } $templatePopulation->weights = $weightsAvgTimeline; + $endTime = microtime(true); + $metadata->process_time = round($endTime - $startTime, 5); + + return response()->json($templatePopulation, Response::HTTP_OK); + } + + + public function fetchAllPerformanceModeElections(int $template, Request $request) { + $startTime = microtime(true); + $metadata = new \stdClass(); + + $templatePopulation = Population::where('id', '=', $template) + //->whereNull('parent_id') + ->with(['childPopulations' => function ($q){ + $q->where('stage', '=', 'p') + ->with(['elections' => function ($q1) { + $q1->where('type', '=', 'd1') + ->with('extension'); + }]); + }]) + ->firstOrFail() + ->makeHidden('childPopulations'); + + $d2TypesCount = 0; + $d2Correct = 0; + $d2Incorrect = 0; + $d3TypesCount = 0; + $d3Correct = 0; + $d3Incorrect = 0; + + foreach ($templatePopulation->childPopulations as $childPopulation) { + $electionCount = $childPopulation->elections->count(); + $totalCorrect = $childPopulation->elections->sum('total_correct'); + $totalIncorrect = $childPopulation->elections->sum('total_incorrect'); + if ($childPopulation->election_type == 'd2') { + $d2TypesCount += $electionCount; + $d2Correct += $totalCorrect; + $d2Incorrect += $totalIncorrect; + } elseif($childPopulation->election_type == 'd2') { + $d3TypesCount += $electionCount; + $d3Correct += $totalCorrect; + $d3Incorrect += $totalIncorrect; + } + } + + $response = new \stdClass(); + $response->report_metadata = $metadata; + + $response->results = array(); + + $d2Votes = $d2Correct + $d2Incorrect; + + if ($d2Votes > 0) { + $d2Stats = new \stdClass(); + $d2Stats->election_type = 'd2'; + $d2Stats->election_count = $d2TypesCount; + $d2Stats->correct = $d2Correct; + $d2Stats->incorrect = $d2Incorrect; + $d2Stats->percent_correct = 100 * $d2Correct / $d2Votes; + $response->results['d2'] = $d2Stats; + } else { + $response->results['d2'] = null; + } + + $d3Votes = $d3Correct + $d3Incorrect; + + if ($d3Votes > 0) { + $d3Stats = new \stdClass(); + $d3Stats->election_type = 'd3'; + $d3Stats->election_count = $d3TypesCount; + $d3Stats->correct = $d3Correct; + $d3Stats->incorrect = $d3Incorrect; + $d3Stats->percent_correct = 100 * $d2Correct / $d3Votes; + $response->results['d3'] = $d3Stats; + } else { + $response->results['d3'] = null; + } + + $endTime = microtime(true); + $metadata->process_time = round($endTime - $startTime, 5); + + return response()->json($response, Response::HTTP_OK); + } + + /** + * Gets all performance mode results for d1,d2,d3 + * + * @param int $template + * @param Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function getTemplateElectionResultStats(int $template, Request $request) { + $startTime = microtime(true); + $metadata = new \stdClass(); + + try { + $attributes = $request->validate([ + 'election_type' => 'required|string|in:d1,d2,d3' + ]); + } catch (ValidationException $e) { + return response()->json([ + 'message' => 'Incorrect payload', + 'val_errors' => $e->errors() + ], Response::HTTP_UNPROCESSABLE_ENTITY); + } + $type = $attributes['election_type']; + $metadata->election_type = $type; + + $templatePopulation = Population::where('id', '=', $template) + //->whereNull('parent_id') + ->with(['childPopulations' => function ($q) use ($type) { + $q->where('election_type', '=', $type) + ->where('stage', '=', 'p') + ->with(['elections' => function ($q1) { + $q1->where('type', '=', 'd1') + ->with('extension'); + }]); + }]) + ->firstOrFail() + ->makeHidden('childPopulations'); + + $templatePopulation->report_metadata = $metadata; + + $countA = $templatePopulation->voters()->where('group', '=', 'A')->count(); + $countB = $templatePopulation->voters()->where('group', '=', 'B')->count(); + + $templatePopulation->no_of_voters = $countA + $countB; + + $templatePopulation->avg_expertise = $templatePopulation->voters()->average('expertise'); + $templatePopulation->max_expertise = $templatePopulation->voters()->max('expertise'); + + if ($templatePopulation->no_of_voters < 1) { + return response()->json(['error' => 'No voters in population'], Response::HTTP_NOT_FOUND); + } + + $templatePopulation->no_of_voters_a = $countA; + $templatePopulation->no_of_voters_b = $countB; + $templatePopulation->no_of_child_populations = $templatePopulation->childPopulations->count(); + + $first = true; + $minElectionCount = 0; + $maxElectionCount = 0; + $childPopulationCount = 0; + + $noumberOfVoters = $templatePopulation->no_of_voters; + $electionResultsGathered = array(); + + foreach ($templatePopulation->childPopulations as $childPopulation) { + $childPopulationCount++; + $electionCount = $childPopulation->elections->count(); + + if ($first) { + $minElectionCount = $electionCount; + $maxElectionCount = $electionCount; + $first = false; + } else { + if ($electionCount > $maxElectionCount) { + $maxElectionCount = $electionCount; + } + if ($electionCount < $minElectionCount) { + $minElectionCount = $electionCount; + } + } + $childPopulation->elections_count = $electionCount; + $childPopulation->makeHidden('elections'); + + foreach ($childPopulation->elections as $election) { + $correct = $election->total_correct; + $correctShare = $correct / $noumberOfVoters; + $correctPercent = 100 * $correctShare; + array_push($electionResultsGathered, $correctPercent); + } + + } + $templatePopulation->min_election_count = $minElectionCount; + $templatePopulation->max_election_count = $maxElectionCount; + + sort($electionResultsGathered); + $templatePopulation->all_elections_gathered = $electionResultsGathered; + + $logPath = 'election_logs/' . $templatePopulation->id . '_' . time() . '.json'; + + Storage::disk('local')->put($logPath, json_encode($electionResultsGathered)); + + $endTime = microtime(true); + $metadata->process_time = round($endTime - $startTime, 5); + + return response()->json($templatePopulation, Response::HTTP_OK); + + + $electionResults = array(); + $electionResultsDetails = array(); + $electionResultsGathered = array(); + + for ($i = 0; $i < $minElectionCount; $i++) { + $electionResults[$i] = 0; + } + + $distributionGathered = array(); + $distributionGatheredRounded5 = array(); + $distributionGatheredRounded10 = array(); + + for($i=0;$i<101;$i++){ + $distributionGathered[$i] = 0; + } + for($i=0;$i<21;$i++){ + $distributionGatheredRounded5[$i] = 0; + } + for($i=0;$i<11;$i++){ + $distributionGatheredRounded10[$i] = 0; + } + + foreach ($templatePopulation->childPopulations as $childPopulation) { + $childPopulation->exceeding_elections = $childPopulation->elections_count - $minElectionCount; + $childResults = array(); + + for ($i = 0; $i < $minElectionCount; $i++) { + $correct = $childPopulation->elections[$i]->total_correct; + $correctShare = $correct / $templatePopulation->no_of_voters; + $correctPercent = 100 * $correctShare; + $electionResults[$i] += $correctPercent; + array_push($childResults, $correctPercent); + array_push($electionResultsGathered, $correctPercent); + $distributionGathered[floor($correctPercent)]++; + $distributionGatheredRounded5[floor(20 * $correctShare)]++; + $distributionGatheredRounded10[floor(10 * $correctShare)]++; + } + array_push($electionResultsDetails, $childResults); + } +/* + $electionsAvgTimeline = array(); + for ($i = 0; $i < $minElectionCount; $i++) { + $newResult = $electionResults[$i] / $templatePopulation->no_of_child_populations; + array_push($electionsAvgTimeline, $newResult); + } +*/ + //sort($electionsAvgTimeline); + //$templatePopulation->elections_avg = $electionsAvgTimeline; + //$templatePopulation->elections_detailed = $electionResultsDetails; + sort($electionResultsGathered); + $templatePopulation->all_elections_gathered = $electionResultsGathered; + //$templatePopulation->elections_gathered_distribution = $distributionGathered; + //$templatePopulation->elections_gathered_distribution_5 = $distributionGatheredRounded5; + //$templatePopulation->elections_gathered_distribution_10 = $distributionGatheredRounded10; + + + + + $endTime = microtime(true); $metadata->process_time = round($endTime - $startTime, 5); diff --git a/app/Population.php b/app/Population.php index 2f3b627..bb9c655 100644 --- a/app/Population.php +++ b/app/Population.php @@ -97,6 +97,7 @@ class Population extends Model return [ 'groups' => $groups, 'no_of_voters' => $this->voters()->count(), + 'expertise_max' => $this->voters()->max('expertise'), 'expertise_average' => $this->voters()->average('expertise'), 'confidence_average' => $this->voters()->average('confidence'), 'following_average' => $this->voters()->average('following'), diff --git a/app/Voter.php b/app/Voter.php index 5d8cf51..28d5672 100644 --- a/app/Voter.php +++ b/app/Voter.php @@ -14,6 +14,7 @@ class Voter extends Model 'confidence', 'following', 'leadership', + 'reputation', 'group' ]; diff --git a/public/js/app.js b/public/js/app.js index 5117590..e338770 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -2544,6 +2544,11 @@ __webpack_require__.r(__webpack_exports__); // // // +// +// +// +// +// /* harmony default export */ __webpack_exports__["default"] = ({ @@ -2554,6 +2559,7 @@ __webpack_require__.r(__webpack_exports__); }, data: function data() { return { + show_top_level_only: true, current_population: null, feedback: null, creationFeedback: null, @@ -2639,6 +2645,12 @@ __webpack_require__.r(__webpack_exports__); }; } }, + watch: { + show_top_level_only: function show_top_level_only() { + this.feedback = this.show_top_level_only ? "Refreshing top level only" : "Refreshing including child populations in performance stage"; + this.fetchPopulationIndex(); + } + }, methods: { resetFeedback: function resetFeedback() { this.feedback = null; @@ -2658,14 +2670,16 @@ __webpack_require__.r(__webpack_exports__); var selectFirst = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; this.populations = []; - axios.get(route('internal.api.templates.index')).then(function (response) { + axios.get(route('internal.api.templates.index'), { + params: { + 'top_level_only': this.show_top_level_only ? 1 : 0 + } + }).then(function (response) { _this2.populations = response.data; if (selectFirst && _this2.populations[0]) { _this2.current_population = _this2.populations[0]; } - - console.log(_this2.current_population); })["catch"](function (err) { _this2.feedback = 'population data error'; }); @@ -3071,6 +3085,15 @@ __webpack_require__.r(__webpack_exports__); // // // +// +// +// +// +// +// +// +// +// @@ -3467,11 +3490,14 @@ __webpack_require__.r(__webpack_exports__); var labels = []; var percent_correct = []; var expertise = []; + var expertise_max = []; var avg_expertise = this.population_stats.voters_stats.expertise_average; + var max_expertise = this.population_stats.voters_stats.expertise_max; this.elections_timeline.elections.forEach(function (value, idx) { labels.push(idx); percent_correct.push(value); expertise.push(avg_expertise); + expertise_max.push(max_expertise); }); return { labels: labels, @@ -3487,6 +3513,12 @@ __webpack_require__.r(__webpack_exports__); fill: false, data: expertise, yAxisID: 'left-y-axis' + }, { + label: 'Max population Expertise', + borderColor: '#666666', + fill: false, + data: expertise_max, + yAxisID: 'left-y-axis' }] }; }, @@ -3962,6 +3994,47 @@ __webpack_require__.r(__webpack_exports__); // // // +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// @@ -3983,6 +4056,7 @@ __webpack_require__.r(__webpack_exports__); custom_number_elections: 1, running_elections_lock: false, last_elections_data: null, + performance_mode_results: null, election_type_selector: [{ value: 'm', text: 'Majority (m)', @@ -4126,6 +4200,9 @@ __webpack_require__.r(__webpack_exports__); resetFeedback: function resetFeedback() { this.feedback = null; }, + getTemplateLink: function getTemplateLink(templateId) { + return route('template.show', templateId); + }, fetchPopulationTemplate: function fetchPopulationTemplate() { var _this = this; @@ -4133,6 +4210,8 @@ __webpack_require__.r(__webpack_exports__); axios.get(route('internal.api.template.get', this.template_id)).then(function (response) { _this.feedback = 'population template details fetched'; _this.current_template = response.data; + + _this.updateTotalSums(); })["catch"](function (err) { _this.feedback = 'population template fetching error'; }); @@ -4217,13 +4296,27 @@ __webpack_require__.r(__webpack_exports__); }).then(function (response) { _this4.feedback = 'child population stats updated'; _this4.current_template.child_populations[key] = response.data; + + _this4.updateTotalSums(); })["catch"](function (err) { _this4.feedback = 'population stats fetching error'; }); }, - fetchWeightsTimeline: function fetchWeightsTimeline() { + updateTotalSums: function updateTotalSums() { var _this5 = this; + axios.get(route('internal.api.template.analytics.performance', { + "template": this.current_template.id + })).then(function (response) { + _this5.performance_mode_results = response.data.results; + _this5.feedback = 'performance mode results fetched'; + })["catch"](function (err) { + _this5.feedback = 'performance mode results fetching error'; + }); + }, + fetchWeightsTimeline: function fetchWeightsTimeline() { + var _this6 = this; + axios.get(route('internal.api.template.analytics.weights', { "template": this.current_template.id }), { @@ -4231,10 +4324,25 @@ __webpack_require__.r(__webpack_exports__); 'election_type': this.current_election_timeline_key.value } }).then(function (response) { - _this5.analytics_weights_timeline = response.data; - _this5.feedback = 'weights timeline fetched'; + _this6.analytics_weights_timeline = response.data; + _this6.feedback = 'weights timeline fetched'; })["catch"](function (err) { - _this5.feedback = 'weights timeline fetching error'; + _this6.feedback = 'weights timeline fetching error'; + }); + }, + switchToPerformanceStage: function switchToPerformanceStage(key) { + var _this7 = this; + + var population = this.current_template.child_populations[key]; + axios.post(route('internal.api.population.stage.update', { + "template": population.parent_id, + "population": population.id + })).then(function (response) { + _this7.fetchPopulationTemplate(); + + _this7.feedback = "Changed child population stage. Only elections that do not alter attributes are available."; + })["catch"](function (err) { + _this7.feedback = "Error while changing stage."; }); } } @@ -79987,32 +80095,58 @@ var render = function() { _c("div", { staticClass: "card-header" }, [_vm._v("Populations")]), _vm._v(" "), _c("div", { staticClass: "card-body" }, [ - _c("div", [ - _c( - "button", - { - attrs: { - "data-target": "#create-population-modal", - "data-toggle": "modal" + _c("div", { staticClass: "row" }, [ + _vm._m(0), + _vm._v(" "), + _c("div", { staticClass: "col-md-6 col-lg-6" }, [ + _c("input", { + directives: [ + { + name: "model", + rawName: "v-model", + value: _vm.show_top_level_only, + expression: "show_top_level_only" + } + ], + attrs: { type: "checkbox" }, + domProps: { + checked: Array.isArray(_vm.show_top_level_only) + ? _vm._i(_vm.show_top_level_only, null) > -1 + : _vm.show_top_level_only }, on: { - click: function($event) { - $event.preventDefault() - return _vm.addPopulation($event) + change: function($event) { + var $$a = _vm.show_top_level_only, + $$el = $event.target, + $$c = $$el.checked ? true : false + if (Array.isArray($$a)) { + var $$v = null, + $$i = _vm._i($$a, $$v) + if ($$el.checked) { + $$i < 0 && + (_vm.show_top_level_only = $$a.concat([$$v])) + } else { + $$i > -1 && + (_vm.show_top_level_only = $$a + .slice(0, $$i) + .concat($$a.slice($$i + 1))) + } + } else { + _vm.show_top_level_only = $$c + } } } - }, - [ - _vm._v( - "\n Add population template\n " - ) - ] - ) + }), + _vm._v(" "), + _c("label", { staticClass: "text-info" }, [ + _vm._v("Show only top level templates") + ]) + ]) ]), _vm._v(" "), _c("hr"), _vm._v(" "), - _vm._m(0), + _vm._m(1), _vm._v(" "), _c( "div", @@ -80035,6 +80169,13 @@ var render = function() { } }, [ + population.parent_id + ? _c("span", { staticClass: "badge badge-light" }, [ + _vm._v("child") + ]) + : _c("span", { staticClass: "badge badge-info" }, [ + _vm._v("top level") + ]), _vm._v( "\n " + _vm._s(population.name) + @@ -80151,7 +80292,7 @@ var render = function() { ]), _vm._v(" "), _c("table", [ - _vm._m(1), + _vm._m(2), _vm._v(" "), _c( "tbody", @@ -80290,13 +80431,34 @@ var render = function() { ) } var staticRenderFns = [ + function() { + var _vm = this + var _h = _vm.$createElement + var _c = _vm._self._c || _h + return _c("div", { staticClass: "col-md-6 col-lg-6" }, [ + _c( + "button", + { + attrs: { + "data-target": "#create-population-modal", + "data-toggle": "modal" + } + }, + [ + _vm._v( + "\n Add top level template\n " + ) + ] + ) + ]) + }, function() { var _vm = this var _h = _vm.$createElement var _c = _vm._self._c || _h return _c("div", [ _c("i", { staticClass: "text-info" }, [ - _vm._v("Select population for details.") + _vm._v("Select population template for details.") ]) ]) }, @@ -80356,7 +80518,7 @@ var render = function() { href: _vm.getTemplateLink(_vm.population_stats.parent_id) } }, - [_vm._v("Back to template")] + [_vm._v("Back to parent template")] ) ]), _vm._v(" "), @@ -80366,9 +80528,9 @@ var render = function() { ? _c("div", [ _vm.population_stats.stage === "l" ? _c("div", [ - _c("h5", [_vm._v("Learning stage")]), + _c("h6", [_vm._v("Learning stage")]), _vm._v(" "), - _c("h5", [_vm._v(_vm._s(_vm.election_name))]), + _c("h6", [_vm._v(_vm._s(_vm.election_name))]), _vm._v(" "), _c("div", { staticClass: "col-md-12 col-lg-12" }, [ _vm.population_stats.stage === "l" @@ -80391,7 +80553,7 @@ var render = function() { : _vm._e() ]), _vm._v(" "), - _c("h5", [_vm._v("Reputation modifiers:")]), + _c("h6", [_vm._v("Reputation modifiers:")]), _vm._v(" "), _c("div", { staticClass: "col-md-12 col-lg-12" }, [ _c("ul", [ @@ -80399,7 +80561,7 @@ var render = function() { _vm._v( "Forgetting percent: " + _vm._s( - _vm.population_stats.forgetting_percent + _vm.population_stats.forgetting_factor ) ) ]), @@ -80413,7 +80575,33 @@ var render = function() { ]) ]), _vm._v(" "), - _c("h5", [_vm._v("Elections")]), + _c("h6", [_vm._v("Expertise:")]), + _vm._v(" "), + _c("div", { staticClass: "col-md-12 col-lg-12" }, [ + _c("ul", [ + _c("li", { staticClass: "text-info" }, [ + _vm._v( + "Maximum: " + + _vm._s( + _vm.population_stats.voters_stats + .expertise_max + ) + ) + ]), + _vm._v(" "), + _c("li", { staticClass: "text-info" }, [ + _vm._v( + "Average: " + + _vm._s( + _vm.population_stats.voters_stats + .expertise_average + ) + ) + ]) + ]) + ]), + _vm._v(" "), + _c("h6", [_vm._v("Elections")]), _vm._v(" "), _c("div", { staticClass: "col-md-12 col-lg-12" }, [ _c("label", { staticClass: "text-info" }, [ @@ -80482,6 +80670,18 @@ var render = function() { _vm._v(" "), _c("h5", [_vm._v(_vm._s(_vm.election_name))]), _vm._v(" "), + _c("div", [ + _c( + "a", + { + attrs: { + href: _vm.getTemplateLink(_vm.population_id) + } + }, + [_vm._v("Switch to template view")] + ) + ]), + _vm._v(" "), _c("h5", [_vm._v("Elections")]), _vm._v(" "), _c("div", [ @@ -81782,7 +81982,25 @@ var render = function() { _vm._v(" "), _c("div", { staticClass: "card-body" }, [ _c("div", [ - _c("h5", [_vm._v("Reputation modifiers:")]), + _vm.current_template.parent_id + ? _c("div", [ + _c( + "a", + { + attrs: { + href: _vm.getTemplateLink( + _vm.current_template.parent_id + ) + } + }, + [_vm._v("Back to parent template")] + ) + ]) + : _vm._e(), + _vm._v(" "), + _c("hr"), + _vm._v(" "), + _c("h6", [_vm._v("Reputation modifiers:")]), _vm._v(" "), _c("div", { staticClass: "col-md-12 col-lg-12" }, [ _c("ul", [ @@ -81800,6 +82018,31 @@ var render = function() { ) ]) ]) + ]), + _vm._v(" "), + _c("h6", [_vm._v("Expertise:")]), + _vm._v(" "), + _c("div", { staticClass: "col-md-12 col-lg-12" }, [ + _c("ul", [ + _c("li", { staticClass: "text-info" }, [ + _vm._v( + "Maximum: " + + _vm._s( + _vm.current_template.voters_stats.expertise_max + ) + ) + ]), + _vm._v(" "), + _c("li", { staticClass: "text-info" }, [ + _vm._v( + "Average: " + + _vm._s( + _vm.current_template.voters_stats + .expertise_average + ) + ) + ]) + ]) ]) ]) ]) @@ -82443,6 +82686,88 @@ var render = function() { ) ]), _vm._v(" "), + _c("div", { staticClass: "row" }, [ + _c( + "div", + { staticClass: "col-md-12 col-lg-12" }, + [ + _c("h6", [ + _vm._v( + "Summary results of performance mode delegation voting" + ) + ]), + _vm._v(" "), + _vm.performance_mode_results + ? _c("div", [ + _c("ul", [ + _c("li", [ + _vm._v( + "\n d2:\n " + ), + _vm.performance_mode_results.d2 + ? _c( + "i", + { + staticClass: "text-primary" + }, + [ + _vm._v( + "\n Elections: " + + _vm._s( + _vm + .performance_mode_results + .d2.election_count + ) + + "\n , Correct percent: " + + _vm._s( + _vm + .performance_mode_results + .d2.percent_correct + ) + + "\n " + ) + ] + ) + : _c("span", [_vm._v("N/A")]) + ]), + _vm._v(" "), + _c("li", [ + _vm._v( + "\n d3:\n " + ), + _vm.performance_mode_results.d3 + ? _c( + "i", + { + staticClass: "text-primary" + }, + [ + _vm._v( + "\n Elections: " + + _vm._s( + _vm + .performance_mode_results + .d3.election_count + ) + + "\n , Correct percent: " + + _vm._s( + _vm + .performance_mode_results + .d3.percent_correct + ) + + "\n " + ) + ] + ) + : _c("span", [_vm._v("N/A")]) + ]) + ]) + ]) + : _vm._e() + ] + ) + ]), + _vm._v(" "), _c("div", { staticClass: "row" }, [ _c("div", { staticClass: "col-md-9 col-lg-9" }, [ _c("h6", [_vm._v("Last elections status: ")]), @@ -82617,7 +82942,32 @@ var render = function() { ]), _vm._v(" "), _c("td", [ - _vm._v(_vm._s(child_population.stage)) + _c("strong", [ + _vm._v(_vm._s(child_population.stage)) + ]), + _vm._v(" "), + child_population.stage === "l" + ? _c( + "button", + { + staticClass: + "btn btn-sm btn-warning", + on: { + click: function($event) { + $event.preventDefault() + return _vm.switchToPerformanceStage( + key + ) + } + } + }, + [ + _vm._v( + "\n >> P\n " + ) + ] + ) + : _vm._e() ]), _vm._v(" "), _vm._l( @@ -82686,7 +83036,9 @@ var render = function() { ) + " (" + _vm._s( - child_population.election_type + child_population.stage === "p" + ? "d1" + : child_population.election_type ) + ") election" ), diff --git a/resources/js/components/population-index.vue b/resources/js/components/population-index.vue index 80c126e..2b01783 100644 --- a/resources/js/components/population-index.vue +++ b/resources/js/components/population-index.vue @@ -19,17 +19,20 @@ <div class="card-header">Populations</div> <div class="card-body"> - <div> - <button v-on:click.prevent="addPopulation" - data-target="#create-population-modal" - data-toggle="modal" - > - Add population template - </button> + <div class="row"> + <div class="col-md-6 col-lg-6"> + <button data-target="#create-population-modal" data-toggle="modal"> + Add top level template + </button> + </div> + <div class="col-md-6 col-lg-6"> + <input type="checkbox" v-model="show_top_level_only"> + <label class="text-info">Show only top level templates</label> + </div> </div> <hr> <div> - <i class="text-info">Select population for details.</i> + <i class="text-info">Select population template for details.</i> </div> <div class="btn-group-vertical"> <span v-on:click.prevent="selectPopulation(population)" @@ -37,6 +40,8 @@ class="btn btn-outline-info" v-bind:class="{'btn-info text-white': (current_population != null && current_population.id == population.id)}" > + <span class="badge badge-light" v-if="population.parent_id">child</span> + <span class="badge badge-info" v-else>top level</span> {{population.name}} <i>voters: {{population.voters_stats.no_of_voters}},</i> <br><i>Number of child-populations: {{population.children_count}}</i> <br><i>Number of elections run on template: </i> @@ -159,6 +164,7 @@ components: {PopulationCreate, BarChart}, data() { return { + show_top_level_only: true, current_population: null, feedback: null, creationFeedback: null, @@ -254,6 +260,12 @@ } }, }, + watch: { + show_top_level_only: function() { + this.feedback = this.show_top_level_only ? "Refreshing top level only" : "Refreshing including child populations in performance stage"; + this.fetchPopulationIndex(); + } + }, methods: { resetFeedback() { this.feedback = null; @@ -270,12 +282,13 @@ }, fetchPopulationIndex(selectFirst = false) { this.populations = []; - axios.get(route('internal.api.templates.index')).then((response) => { + axios.get(route('internal.api.templates.index'), + {params: {'top_level_only': this.show_top_level_only ? 1 : 0}} + ).then((response) => { this.populations = response.data; if(selectFirst && this.populations[0]) { this.current_population = this.populations[0] } - console.log(this.current_population); }).catch((err) => { this.feedback = 'population data error'; }); diff --git a/resources/js/components/population-show.vue b/resources/js/components/population-show.vue index aef500e..1ee49cd 100644 --- a/resources/js/components/population-show.vue +++ b/resources/js/components/population-show.vue @@ -7,26 +7,33 @@ <div class="card-header">{{population_name}}</div> <div class="card-body" > <div> - <a :href="getTemplateLink(population_stats.parent_id)">Back to template</a> + <a :href="getTemplateLink(population_stats.parent_id)">Back to parent template</a> </div> <hr> <div v-if="stage_name"> <div v-if="population_stats.stage === 'l'"> - <h5>Learning stage</h5> - <h5>{{election_name}}</h5> + <h6>Learning stage</h6> + <h6>{{election_name}}</h6> <div class="col-md-12 col-lg-12"> <button v-if="population_stats.stage === 'l'" @click.prevent="switchToPerformanceStage()"> Finish Learning stage </button> </div> - <h5>Reputation modifiers:</h5> + <h6>Reputation modifiers:</h6> <div class="col-md-12 col-lg-12"> <ul> - <li class="text-info">Forgetting percent: {{population_stats.forgetting_percent}}</li> + <li class="text-info">Forgetting percent: {{population_stats.forgetting_factor}}</li> <li class="text-info">Follower multiplier: {{population_stats.follower_factor}}</li> </ul> </div> - <h5>Elections</h5> + <h6>Expertise:</h6> + <div class="col-md-12 col-lg-12"> + <ul> + <li class="text-info">Maximum: {{population_stats.voters_stats.expertise_max}}</li> + <li class="text-info">Average: {{population_stats.voters_stats.expertise_average}}</li> + </ul> + </div> + <h6>Elections</h6> <div class="col-md-12 col-lg-12"> <label class="text-info">Number of elections: </label> <input type="number" min="1" max="100" step="0" v-model="custom_number_elections" style="width:70px"> @@ -38,6 +45,8 @@ <div v-else-if="population_stats.stage === 'p'"> <h5>Performance stage</h5> <h5>{{election_name}}</h5> + <div> + <a :href="getTemplateLink(population_id)">Switch to template view</a></div> <h5>Elections</h5> <div> <label class="text-info">Number of elections: </label> @@ -799,11 +808,14 @@ let labels = []; let percent_correct = []; let expertise = []; + let expertise_max = []; let avg_expertise = this.population_stats.voters_stats.expertise_average; + let max_expertise = this.population_stats.voters_stats.expertise_max; this.elections_timeline.elections.forEach((value,idx) => { labels.push(idx); percent_correct.push(value); expertise.push(avg_expertise); + expertise_max.push(max_expertise); }); return { labels: labels, @@ -822,6 +834,14 @@ fill: false, data: expertise, yAxisID: 'left-y-axis' + }, + + { + label: 'Max population Expertise', + borderColor: '#666666', + fill: false, + data: expertise_max, + yAxisID: 'left-y-axis' } ] } diff --git a/resources/js/components/population-template-show.vue b/resources/js/components/population-template-show.vue index 19dc8db..55ba916 100644 --- a/resources/js/components/population-template-show.vue +++ b/resources/js/components/population-template-show.vue @@ -7,13 +7,24 @@ <div class="card-header">{{current_template.name}}</div> <div class="card-body"> <div> - <h5>Reputation modifiers:</h5> + <div v-if="current_template.parent_id"> + <a :href="getTemplateLink(current_template.parent_id)">Back to parent template</a> + </div> + <hr> + <h6>Reputation modifiers:</h6> <div class="col-md-12 col-lg-12"> <ul> <li class="text-info">Forgetting percent: {{current_template.forgetting_factor}}</li> <li class="text-info">Follower multiplier: {{current_template.follower_factor}}</li> </ul> </div> + <h6>Expertise:</h6> + <div class="col-md-12 col-lg-12"> + <ul> + <li class="text-info">Maximum: {{current_template.voters_stats.expertise_max}}</li> + <li class="text-info">Average: {{current_template.voters_stats.expertise_average}}</li> + </ul> + </div> </div> </div> </div> @@ -182,6 +193,31 @@ <div class="card-body"> <div class="col-md-12 col-lg-12"> <h5>Run quick elections of population primary type for selected child population</h5> + <div class="row"> + <div class="col-md-12 col-lg-12"> + <h6>Summary results of performance mode delegation voting</h6> + <div v-if="performance_mode_results"> + <ul> + <li> + d2: + <i v-if="performance_mode_results.d2" class="text-primary"> + Elections: {{performance_mode_results.d2.election_count}} + , Correct percent: {{performance_mode_results.d2.percent_correct}} + </i> + <span v-else>N/A</span> + </li> + <li> + d3: + <i v-if="performance_mode_results.d3" class="text-primary"> + Elections: {{performance_mode_results.d3.election_count}} + , Correct percent: {{performance_mode_results.d3.percent_correct}} + </i> + <span v-else>N/A</span> + </li> + </ul> + </div> + </div> + </div> <div class="row"> <div class="col-md-9 col-lg-9"> <h6>Last elections status: </h6> @@ -224,7 +260,12 @@ <td> <a class="btn btn-primary" :href="getChildPopulationLink(child_population.id)">{{child_population.name}}</a> </td> - <td>{{child_population.stage}}</td> + <td> + <strong>{{child_population.stage}}</strong> + <button v-if="child_population.stage === 'l'" class="btn btn-sm btn-warning" @click.prevent="switchToPerformanceStage(key)"> + >> P + </button> + </td> <td v-on:click.prevent="childPopulationRowClicked(child_population)" v-for="election_type in child_population.elections_stats"> <span v-if="election_type.count > 0">Count: {{election_type.count}} (avg corr: {{election_type.percent_correct.toFixed(2)}})</span> @@ -232,7 +273,7 @@ </td> <td> <button :disabled="running_elections_lock" class="btn btn-sm btn-success" @click.prevent="runElections(key, custom_number_elections)"> - Run {{custom_number_elections}} ({{child_population.election_type}}) election<span v-if="custom_number_elections > 1">s</span> + Run {{custom_number_elections}} ({{child_population.stage === 'p' ? 'd1' : child_population.election_type}}) election<span v-if="custom_number_elections > 1">s</span> </button> </td> </tr> @@ -270,6 +311,7 @@ custom_number_elections: 1, running_elections_lock: false, last_elections_data: null, + performance_mode_results: null, election_type_selector : [ { value: 'm', @@ -430,11 +472,15 @@ resetFeedback() { this.feedback = null; }, + getTemplateLink(templateId) { + return route('template.show', templateId); + }, fetchPopulationTemplate() { this.feedback = 'fetching population template details..'; axios.get(route('internal.api.template.get', this.template_id)).then((response) => { this.feedback = 'population template details fetched'; this.current_template = response.data; + this.updateTotalSums(); }).catch((err) => { this.feedback = 'population template fetching error'; }) @@ -509,10 +555,25 @@ }).then((response) => { this.feedback = 'child population stats updated'; this.current_template.child_populations[key] = response.data; + this.updateTotalSums(); }).catch((err) => { this.feedback = 'population stats fetching error'; }); }, + updateTotalSums() { + axios.get( + route( + 'internal.api.template.analytics.performance', + { + "template":this.current_template.id + } + )).then((response) => { + this.performance_mode_results = response.data.results; + this.feedback = 'performance mode results fetched'; + }).catch((err) => { + this.feedback = 'performance mode results fetching error'; + }); + }, fetchWeightsTimeline() { axios.get( route( @@ -530,6 +591,21 @@ }).catch((err) => { this.feedback = 'weights timeline fetching error'; }); + }, + switchToPerformanceStage(key) { + let population = this.current_template.child_populations[key]; + axios.post(route( + 'internal.api.population.stage.update', + { + "template":population.parent_id, + "population":population.id + } + )).then((response) => { + this.fetchPopulationTemplate(); + this.feedback = "Changed child population stage. Only elections that do not alter attributes are available."; + }).catch((err) => { + this.feedback = "Error while changing stage."; + }); } } } diff --git a/routes/api_internal.php b/routes/api_internal.php index 426d39b..17dc460 100644 --- a/routes/api_internal.php +++ b/routes/api_internal.php @@ -22,6 +22,12 @@ Route::get('/templates/{template}','APIController@getPopulationTemplate') Route::get('/templates/{template}/analytics/weights','APIController@getTemplateWeightsStats') ->name('internal.api.template.analytics.weights'); +Route::get('/templates/{template}/analytics/results','APIController@getTemplateElectionResultStats') + ->name('internal.api.template.analytics.results'); + +Route::get('/templates/{template}/analytics/sums','APIController@fetchAllPerformanceModeElections') + ->name('internal.api.template.analytics.performance'); + Route::post('/templates/{template}/populations','APIController@getChildPopulations') ->name('internal.api.populations.index'); -- GitLab