Passed
Push — feature/code-analysis ( e964aa...4fe35d )
by Jonathan
14:33
created

SosaStatistics::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * webtrees-lib: MyArtJaub library for webtrees
5
 *
6
 * @package MyArtJaub\Webtrees
7
 * @subpackage Sosa
8
 * @author Jonathan Jaubart <[email protected]>
9
 * @copyright Copyright (c) 2009-2022, Jonathan Jaubart
10
 * @license http://www.gnu.org/licenses/gpl.html GNU General Public License, version 3
11
 */
12
13
declare(strict_types=1);
14
15
namespace MyArtJaub\Webtrees\Module\Sosa\Http\RequestHandlers;
16
17
use Brick\Math\BigInteger;
18
use Brick\Math\RoundingMode;
19
use Fisharebest\Webtrees\Auth;
20
use Fisharebest\Webtrees\DefaultUser;
21
use Fisharebest\Webtrees\I18N;
22
use Fisharebest\Webtrees\Tree;
23
use Fisharebest\Webtrees\Validator;
24
use Fisharebest\Webtrees\Http\Exceptions\HttpNotFoundException;
25
use Fisharebest\Webtrees\Http\ViewResponseTrait;
26
use Fisharebest\Webtrees\Module\ModuleThemeInterface;
27
use Fisharebest\Webtrees\Services\ModuleService;
28
use Fisharebest\Webtrees\Services\RelationshipService;
29
use MyArtJaub\Webtrees\Module\Sosa\SosaModule;
30
use MyArtJaub\Webtrees\Module\Sosa\Services\SosaStatisticsService;
31
use Psr\Http\Message\ResponseInterface;
32
use Psr\Http\Message\ServerRequestInterface;
33
use Psr\Http\Server\RequestHandlerInterface;
34
35
/**
36
 * Request handler for displaying Sosa statistics
37
 *
38
 */
39
class SosaStatistics implements RequestHandlerInterface
40
{
41
    use ViewResponseTrait;
42
43
    private ?SosaModule $module;
44
    private RelationshipService $relationship_service;
45
46
    /**
47
     * Constructor for AncestorsList Request Handler
48
     *
49
     * @param ModuleService $module_service
50
     */
51
    public function __construct(ModuleService $module_service, RelationshipService $relationship_service)
52
    {
53
        $this->module = $module_service->findByInterface(SosaModule::class)->first();
54
        $this->relationship_service = $relationship_service;
55
    }
56
57
    /**
58
     * {@inheritDoc}
59
     * @see \Psr\Http\Server\RequestHandlerInterface::handle()
60
     */
61
    public function handle(ServerRequestInterface $request): ResponseInterface
62
    {
63
        if ($this->module === null) {
64
            throw new HttpNotFoundException(I18N::translate('The attached module could not be found.'));
65
        }
66
67
        $tree = Validator::attributes($request)->tree();
68
        $user = Auth::check() ? Validator::attributes($request)->user() : new DefaultUser();
69
70
        /** @var SosaStatisticsService $sosa_stats_service */
71
        $sosa_stats_service = app()->makeWith(SosaStatisticsService::class, ['tree' => $tree, 'user' => $user]);
72
73
        return $this->viewResponse($this->module->name() . '::statistics-page', [
74
            'module_name'       =>  $this->module->name(),
75
            'title'             =>  I18N::translate('Sosa Statistics'),
76
            'tree'              =>  $tree,
77
            'theme'             =>  app(ModuleThemeInterface::class),
78
            'root_indi'         =>  $sosa_stats_service->rootIndividual(),
79
            'general_stats'     =>  $this->statisticsGeneral($sosa_stats_service),
80
            'generation_stats'  =>  $this->statisticsByGenerations($sosa_stats_service),
81
            'generation_depth'  =>  $sosa_stats_service->generationDepthStatsAtGeneration(1)->first(),
82
            'multiple_sosas'    =>  $sosa_stats_service->topMultipleAncestorsWithNoTies(10)->groupBy('sosa_count'),
83
            'sosa_dispersion_g2' =>  $sosa_stats_service->ancestorsDispersionForGeneration(2),
84
            'sosa_dispersion_g3' =>  $sosa_stats_service->ancestorsDispersionForGeneration(3),
85
            'gen_depth_g3'      =>  $sosa_stats_service->generationDepthStatsAtGeneration(3),
86
            'relationship_service'  =>  $this->relationship_service,
87
        ]);
88
    }
89
90
    /**
91
     * Retrieve and compute the global statistics of ancestors for the tree.
92
     * Statistics include the number of ancestors, the number of different ancestors, pedigree collapse...
93
     *
94
     * @param SosaStatisticsService $sosa_stats_service
95
     * @return array<string, int|float>
96
     */
97
    private function statisticsGeneral(SosaStatisticsService $sosa_stats_service): array
98
    {
99
        $ancestors_count = $sosa_stats_service->totalAncestors();
100
        $ancestors_distinct_count = $sosa_stats_service->totalDistinctAncestors();
101
        $individual_count = $sosa_stats_service->totalIndividuals();
102
103
        return [
104
            'sosa_count'    =>  $ancestors_count,
105
            'distinct_count'    =>  $ancestors_distinct_count,
106
            'sosa_rate' =>  $this->safeDivision(
107
                BigInteger::of($ancestors_distinct_count),
108
                BigInteger::of($individual_count)
109
            ),
110
            'mean_gen_time'         =>  $sosa_stats_service->meanGenerationTime()
111
        ];
112
    }
113
114
    /**
115
     * Retrieve and compute the statistics of ancestors by generations.
116
     * Statistics include the number of ancestors, the number of different ancestors, cumulative statistics...
117
     *
118
     * @param SosaStatisticsService $sosa_stats_service
119
     * @return array<int, array<string, int|float>>
120
     */
121
    private function statisticsByGenerations(SosaStatisticsService $sosa_stats_service): array
122
    {
123
        $stats_by_gen = $sosa_stats_service->statisticsByGenerations();
124
125
        $generation_stats = array();
126
127
        foreach ($stats_by_gen as $gen => $stats_gen) {
128
            $gen_diff = $gen > 1 ?
129
                (int) $stats_gen['diffSosaTotalCount'] - (int) $stats_by_gen[$gen - 1]['diffSosaTotalCount'] :
130
                1;
131
            $generation_stats[$gen] = array(
132
                'gen_min_birth' => $stats_gen['firstBirth'] ?? (int) $stats_gen['firstEstimatedBirth'],
133
                'gen_max_birth' => $stats_gen['lastBirth'] ?? (int) $stats_gen['lastEstimatedBirth'],
134
                'theoretical' => BigInteger::of(2)->power($gen - 1)->toInt(),
135
                'known' => (int) $stats_gen['sosaCount'],
136
                'perc_known' => $this->safeDivision(
137
                    BigInteger::of((int) $stats_gen['sosaCount']),
138
                    BigInteger::of(2)->power($gen - 1)
139
                ),
140
                'missing' => $gen > 1 ?
141
                    2 * (int) $stats_by_gen[$gen - 1]['sosaCount'] - (int) $stats_gen['sosaCount'] :
142
                    0,
143
                'perc_missing' => $gen > 1 ?
144
                    1 - $this->safeDivision(
145
                        BigInteger::of((int) $stats_gen['sosaCount']),
146
                        BigInteger::of(2 * (int) $stats_by_gen[$gen - 1]['sosaCount'])
147
                    ) :
148
                    0,
149
                'total_known' => (int) $stats_gen['sosaTotalCount'],
150
                'perc_total_known' => $this->safeDivision(
151
                    BigInteger::of((int) $stats_gen['sosaTotalCount']),
152
                    BigInteger::of(2)->power($gen)->minus(1)
153
                ),
154
                'different' => $gen_diff,
155
                'perc_different' => $this->safeDivision(
156
                    BigInteger::of($gen_diff),
157
                    BigInteger::of((int) $stats_gen['sosaCount'])
158
                ),
159
                'total_different' => (int) $stats_gen['diffSosaTotalCount']
160
            );
161
        }
162
163
        return $generation_stats;
164
    }
165
166
    /**
167
     * Return the result of a division, and a default value if denominator is 0
168
     *
169
     * @param BigInteger $p Numerator
170
     * @param BigInteger $q Denominator
171
     * @param int $scale Rounding scale
172
     * @param float $default Value if denominator is 0
173
     * @return float
174
     */
175
    private function safeDivision(BigInteger $p, BigInteger $q, int $scale = 10, float $default = 0): float
176
    {
177
        return $q->isZero() ? $default : $p->toBigDecimal()->dividedBy($q, $scale, RoundingMode::HALF_DOWN)->toFloat();
178
    }
179
}
180