publicReferences(); return DB::table('geo_locations')->whereNull('deleted_at')->whereIn('location_type', ['province', 'district']) ->where(function ($q) use ($public) { $q->whereIn('id', (clone $public)->select('id')); if ($this->installed() && str_ends_with(DB::connection()->getDatabaseName(), '_test')) { // Macate/Vanduzi: expressly authorised QA references, never active registry changes. $q->orWhere(fn ($qa) => $qa->where('source_system', 'ine-demography-qa')->where('data_classification', 'public')->whereIn('parent_id', (clone $public)->select('id'))); } }); } public function installed(): bool { return (bool) config('demography.enabled', app()->environment('local', 'testing')) && Schema::hasTable('demographic_imports') && Schema::hasTable('demographic_observations'); } /** @return array */ public static function sectors(): array { return ['commerce' => 'Comércio e serviços', 'industry' => 'Indústria', 'agriculture' => 'Agricultura e agro-indústria', 'education' => 'Educação', 'health' => 'Saúde', 'general' => 'Contexto geral']; } /** @param list $territories * @return array */ public function analyse(array $territories, int $start, int $end, string $sector = 'general', ?string $edition = null): array { $result = ['state' => 'unavailable', 'start_year' => $start, 'end_year' => $end, 'sector' => $sector, 'territories' => [], 'differences' => [], 'comparisons' => [], 'limitations' => ['População residente não mede procura efectiva ou poder de compra.', 'Idade activa não demonstra disponibilidade ou qualificação de mão-de-obra.', 'Sem extrapolação: anos sem série oficial permanecem sem dados.', 'A demografia não altera impostos, incentivos ou probabilidades de sucesso.']]; if (! $this->installed() || $territories === []) { return $result; } $locations = $this->territories()->whereIn('id', $territories)->get(['id', 'name', 'location_type']); $records = DB::table('demographic_observations as o')->join('demographic_imports as b', 'b.id', '=', 'o.import_id') ->where('b.status', 'approved')->whereIn('o.geo_location_id', $locations->pluck('id'))->whereBetween('o.year', [$start, $end]) ->where('o.sex', 'all')->when($edition, fn ($q) => $q->where('b.id', $edition)) ->orderBy('o.year')->orderBy('o.import_id')->orderBy('o.id') ->get(['o.*', 'b.source', 'b.publication_version', 'b.sha256', 'b.file_name']); $issuesByImport = DB::table('demographic_imports')->whereIn('id', $records->pluck('import_id')->unique()->all()) ->get(['id', DB::raw("report->'issue_counts' AS issue_counts")])->keyBy('id'); foreach ($locations as $location) { $series = []; foreach ($records->where('geo_location_id', $location->id)->groupBy('import_id') as $importId => $rows) { $first = $rows->first(); $issueCounts = json_decode($issuesByImport->get($importId)->issue_counts ?? '{}', true, 512, JSON_THROW_ON_ERROR); $values = $rows->filter(function ($r) use ($sector) { if ($r->indicator !== 'population') { return in_array($sector, ['health', 'general'], true); } return $r->age_group === 'all' || in_array($sector, ['education', 'health', 'industry', 'agriculture', 'general'], true); })->map(fn ($r) => ['year' => $r->year, 'indicator' => $r->indicator, 'age_group' => $r->age_group, 'residence' => $r->residence, 'value' => $r->numeric_value, 'original_value' => $r->original_value, 'unit' => $r->unit, 'data_kind' => $r->data_kind, 'sheet' => $r->sheet, 'cell' => $r->cell, 'validation_status' => $r->validation_status])->values()->all(); $years = array_values(array_unique(array_column($values, 'year'))); $series[] = ['import_id' => $importId, 'source' => $first->source, 'version' => $first->publication_version, 'sha256' => $first->sha256, 'file_name' => $first->file_name, 'values' => $values, 'missing_years' => array_values(array_diff(range($start, $end), $years)), 'issue_counts' => $issueCounts]; } $result['territories'][] = ['id' => $location->id, 'name' => $location->name, 'level' => $location->location_type, 'series' => $series, 'factors_to_assess' => match ($sector) { 'industry' => ['Estrutura etária e evolução da população; verificar qualificações, mobilidade e emprego.'], 'agriculture' => ['População rural e mercados locais; relacionar com os clusters documentados.'], 'education' => ['Grupos etários da população-alvo; verificar cobertura e capacidade dos serviços.'], 'health' => ['Estrutura etária e estatísticas vitais com unidades conhecidas; verificar cobertura dos serviços.'], default => ['Dimensão e evolução da população; confrontar com rendimento, acesso e condições sectoriais.'], }]; } // Identify concurrent values without summing editions or choosing a supposedly newer one. foreach ($result['territories'] as $territory) { $byDimension = []; foreach ($territory['series'] as $series) { foreach ($series['values'] as $row) { $key = implode('|', [$row['year'], $row['indicator'], $row['age_group'], $row['residence'], $row['unit']]); $byDimension[$key][] = ['import_id' => $series['import_id'], 'value' => $row['value'], 'data_kind' => $row['data_kind']]; } } foreach ($byDimension as $key => $values) { if (count($values) > 1) { $result['differences'][] = ['territory_id' => $territory['id'], 'dimension' => $key, 'state' => count(array_unique(array_column($values, 'value'))) === 1 ? 'matching_values' : 'divergent_values', 'values' => $values]; } } } $result['state'] = $records->isEmpty() ? 'no_data' : 'available'; foreach ($result['territories'] as $i => $left) { foreach (array_slice($result['territories'], $i + 1) as $right) { foreach ($left['series'] as $a) { foreach ($right['series'] as $b) { foreach (array_unique([$start, $end]) as $year) { $isTotal = fn ($r) => $r['year'] === $year && $r['indicator'] === 'population' && $r['age_group'] === 'all' && $r['residence'] === 'all'; $x = array_values(array_filter($a['values'], $isTotal))[0] ?? null; $y = array_values(array_filter($b['values'], $isTotal))[0] ?? null; if (! $x || ! $y) { continue; } $comparable = $left['level'] === $right['level'] && $x['unit'] === $y['unit'] && $x['data_kind'] === $y['data_kind'] && $a['version'] === $b['version']; $delta = $comparable ? DB::selectOne('SELECT (?::numeric - ?::numeric)::text AS value', [$x['value'], $y['value']])->value : null; $result['comparisons'][] = ['year' => $year, 'left' => $left['name'], 'right' => $right['name'], 'left_value' => $x['value'], 'right_value' => $y['value'], 'left_import' => $a['import_id'], 'right_import' => $b['import_id'], 'difference' => $delta, 'explanation' => $comparable ? 'Diferença dos totais de população no mesmo ano, nível, tipo de dado, unidade e identificação de edição. Não mede procura efectiva.' : 'Âmbitos ou metadados distintos; não se calculou uma diferença como se fossem equivalentes.']; } } } } } return $result; } /** @param array $context * @return array */ public function forContext(array $context): array { $fields = $context['fields'] ?? []; $id = ($fields['district_id'] ?? null) ?: ($fields['province_id'] ?? null); // No inference from an influence area to a project's implantation territory. return $this->analyse($id ? [$id] : [], (int) date('Y'), (int) date('Y'), 'general'); } }