Merge branch 'address-importer-ban' into 'master'

Add service and command to import French addresses from BAN

See merge request Chill-Projet/chill-bundles!781
This commit is contained in:
Julien Fastré 2025-01-13 16:09:17 +00:00
commit cb5fd2b69d
5 changed files with 166 additions and 1 deletions

View File

@ -0,0 +1,5 @@
kind: Feature
body: Add address importer from french Base d'Adresse Nationale (BAN)
time: 2025-01-13T17:08:24.034500095+01:00
custom:
Issue: ""

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Command;
use Chill\MainBundle\Service\Import\AddressReferenceFromBAN;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class LoadAddressesFRFromBANCommand extends Command
{
protected static $defaultDescription = 'Import FR addresses from BAN (see https://adresses.data.gouv.fr';
public function __construct(private readonly AddressReferenceFromBAN $addressReferenceFromBAN)
{
parent::__construct();
}
protected function configure()
{
$this->setName('chill:main:address-ref-from-ban')
->addArgument('departementNo', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'a list of departement numbers')
->addOption('send-report-email', 's', InputOption::VALUE_REQUIRED, 'Email address where a list of unimported addresses can be send');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
dump(__METHOD__);
foreach ($input->getArgument('departementNo') as $departementNo) {
$output->writeln('Import addresses for '.$departementNo);
$this->addressReferenceFromBAN->import($departementNo, $input->hasOption('send-report-email') ? $input->getOption('send-report-email') : null);
}
return Command::SUCCESS;
}
}

View File

@ -311,7 +311,7 @@ final class AddressReferenceBaseImporter
$email = (new Email()) $email = (new Email())
->addTo($sendAddressReportToEmail) ->addTo($sendAddressReportToEmail)
->subject('Addresses that could not be imported') ->subject('Addresses that could not be imported')
->attachFromPath($attachmentPath); ->attach(file_get_contents($attachmentPath), sprintf('%s.gz', $path));
try { try {
$this->mailer->send($email); $this->mailer->send($email);

View File

@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Service\Import;
use League\Csv\Reader;
use League\Csv\Statement;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class AddressReferenceFromBAN
{
public function __construct(
private readonly HttpClientInterface $client,
private readonly AddressReferenceBaseImporter $baseImporter,
private readonly AddressToReferenceMatcher $addressToReferenceMatcher,
) {}
public function import(string $departementNo, ?string $sendAddressReportToEmail = null): void
{
if (!is_numeric($departementNo)) {
throw new \UnexpectedValueException('Could not parse this department number');
}
$url = sprintf('https://adresse.data.gouv.fr/data/ban/adresses/latest/csv/adresses-%s.csv.gz', $departementNo);
$response = $this->client->request('GET', $url);
if (200 !== $response->getStatusCode()) {
throw new \Exception('Could not download CSV: '.$response->getStatusCode());
}
$path = sys_get_temp_dir().'/'.$departementNo.'.csv.gz';
$file = fopen($path, 'w');
if (false === $file) {
throw new \Exception('Could not create temporary file');
}
foreach ($this->client->stream($response) as $chunk) {
fwrite($file, $chunk->getContent());
}
fclose($file);
// re-open it to read it
$csvDecompressed = gzopen($path, 'r');
$csv = Reader::createFromStream($csvDecompressed);
$csv->setDelimiter(';')->setHeaderOffset(0);
$stmt = Statement::create()
->process($csv, [
'id',
'id_fantoir',
'numero',
'rep',
'nom_voie',
'code_postal',
'code_insee',
'nom_commune',
'code_insee_ancienne_commune',
'nom_ancienne_commune',
'x',
'y',
'lon',
'lat',
'type_position',
'alias',
'nom_ld',
'libelle_acheminement',
'nom_afnor',
'source_position',
'source_nom_voie',
'certification_commune',
'cad_parcelles',
]);
foreach ($stmt as $record) {
$this->baseImporter->importAddress(
$record['id'],
$record['code_insee'],
$record['code_postal'],
$record['nom_voie'],
$record['numero'].' '.$record['rep'],
'BAN.'.$departementNo,
(float) $record['lat'],
(float) $record['lon'],
4326
);
}
$this->baseImporter->finalize(sendAddressReportToEmail: $sendAddressReportToEmail);
$this->addressToReferenceMatcher->checkAddressesMatchingReferences();
fclose($csvDecompressed);
unlink($path);
}
}

View File

@ -47,6 +47,12 @@ services:
tags: tags:
- { name: console.command } - { name: console.command }
Chill\MainBundle\Command\LoadAddressesFRFromBANCommand:
autoconfigure: true
autowire: true
tags:
- { name: console.command }
Chill\MainBundle\Command\LoadAddressesBEFromBestAddressCommand: Chill\MainBundle\Command\LoadAddressesBEFromBestAddressCommand:
autoconfigure: true autoconfigure: true
autowire: true autowire: true