diff --git a/.env.sample b/.env.sample index 8fceec6c..ea3fb694 100644 --- a/.env.sample +++ b/.env.sample @@ -49,3 +49,4 @@ DISTRICT_TO_SNAPSHOT= # Comma separated list of district to snapshot (used only MATOMO_URL= MATOMO_SITE_ID= MATOMO_TOKEN_AUTH= +IS_GENERATE_BANID_ON_ASSEMBLY= # Set to true to generate banId on assembly diff --git a/docker-compose.yml b/docker-compose.yml index ee137314..21a5791c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -74,6 +74,7 @@ services: - MATOMO_TOKEN_AUTH=${MATOMO_TOKEN_AUTH} - FORCE_DOWNLOAD_CONTOUR= - FORCE_DOWNLOAD_DATASETS= + - IS_GENERATE_BANID_ON_ASSEMBLY=${IS_GENERATE_BANID_ON_ASSEMBLY} ports: - "${PORT:-5000}:5000" volumes: diff --git a/lib/compose/index.cjs b/lib/compose/index.cjs index a9676e88..1fe01dbc 100644 --- a/lib/compose/index.cjs +++ b/lib/compose/index.cjs @@ -1,6 +1,7 @@ /* eslint-disable complexity */ const {chain, omit, pick, keyBy} = require('lodash') const bluebird = require('bluebird') +const {v4: uuidv4} = require('uuid') const {getCommune: getCommuneCOG, getRegion, getDepartement} = require('../util/cog.cjs') @@ -26,6 +27,8 @@ const communesLocauxAdresses = require(`../../${communesLocauxAdressesDataPath}` const locauxAdressesIndex = keyBy(communesLocauxAdresses, 'codeCommune') +const IS_GENERATE_BANID_ON_ASSEMBLY = process.env.IS_GENERATE_BANID_ON_ASSEMBLY === 'true' + async function getSourceData(sourceName, codeCommune) { const adresses = await source(sourceName).getAdresses(codeCommune) const prepareData = require(`./sources/${sourceName}.cjs`) @@ -51,123 +54,135 @@ async function getBalData(codeCommune, revision) { } async function composeCommune(codeCommune, ignoreIdConfig) { - const communeCOG = getCommuneCOG(codeCommune) - const commune = await getCommune(codeCommune) - - if (!ignoreIdConfig && commune?.withBanId) { - console.info(`La commune ${codeCommune} est gérée avec Ban ID => composition ignorée`) - return false - } - - const compositionOptions = commune?.compositionOptions || {} - - if (!communeCOG) { - throw new Error(`La commune ${codeCommune} n’existe pas.`) - } + try { + const communeCOG = getCommuneCOG(codeCommune) + const commune = await getCommune(codeCommune) - const currentRevision = await getCurrentRevision(codeCommune) + if (!ignoreIdConfig && commune?.withBanId) { + console.info(`La commune ${codeCommune} est gérée avec Ban ID => composition ignorée`) + return false + } - if (!compositionOptions.force && currentRevision && commune?.idRevision === currentRevision?._id) { - console.log(`${codeCommune} | révision source inchangée => composition ignorée`) - return false - } + const compositionOptions = commune?.compositionOptions || {} - const isBAL = Boolean(currentRevision) + if (!communeCOG) { + throw new Error(`La commune ${codeCommune} n’existe pas.`) + } - // La bloc suivant garantit qu'il n'y a pas de retour en arrière. - // Ne sera plus nécessaire quand l'API de dépôt contiendra 100% des BAL contenues dans la BAN - if (!isBAL && commune?.typeComposition === 'bal') { - console.log(`${codeCommune} | passage de 'bal' à 'assemblage' interdit => composition ignorée`) - return false - } + const currentRevision = await getCurrentRevision(codeCommune) - const balData = isBAL && await getBalData(codeCommune, currentRevision) - const multiSourcesData = !isBAL && await getMultiSourcesData(codeCommune) + if (!compositionOptions.force && currentRevision && commune?.idRevision === currentRevision?._id) { + console.log(`${codeCommune} | révision source inchangée => composition ignorée`) + return false + } - const pseudoCodeVoieGenerator = await createPseudoCodeVoieGenerator(codeCommune) + const isBAL = Boolean(currentRevision) - const composeVoiesOptions = { - codeCommune, - pseudoCodeVoieGenerator, - forceCertification: commune?.forceCertification - } + // La bloc suivant garantit qu'il n'y a pas de retour en arrière. + // Ne sera plus nécessaire quand l'API de dépôt contiendra 100% des BAL contenues dans la BAN + if (!isBAL && commune?.typeComposition === 'bal') { + console.log(`${codeCommune} | passage de 'bal' à 'assemblage' interdit => composition ignorée`) + return false + } - const composedVoies = isBAL - ? await BAL.buildVoies(balData, composeVoiesOptions) - : await MS.buildVoies(multiSourcesData, composeVoiesOptions) + const balData = isBAL && await getBalData(codeCommune, currentRevision) + const multiSourcesData = !isBAL && await getMultiSourcesData(codeCommune) - const voies = composedVoies.map(v => ({ - type: 'voie', - ...omit(v, 'numeros') - })) + const pseudoCodeVoieGenerator = await createPseudoCodeVoieGenerator(codeCommune) - const numeros = chain(composedVoies) - .map(v => v.numeros.map(n => ({ + const composeVoiesOptions = { codeCommune, - idVoie: v.idVoie, - ...n - }))) - .flatten() - .value() + pseudoCodeVoieGenerator, + forceCertification: commune?.forceCertification + } - const existingVoiesIds = new Set(chain(composedVoies).map('idVoie').uniq().value()) - const buildLieuxDitsOptions = {codeCommune, pseudoCodeVoieGenerator, existingVoiesIds} + const composedVoies = isBAL + ? await BAL.buildVoies(balData, composeVoiesOptions) + : await MS.buildVoies(multiSourcesData, composeVoiesOptions) - const lieuxDits = isBAL - ? await BAL.buildLieuxDits(balData, buildLieuxDitsOptions) - : await MS.buildLieuxDits(multiSourcesData, buildLieuxDitsOptions) + const voies = composedVoies.map(v => ({ + type: 'voie', + ...omit(v, 'numeros') + })) - const voiesToPersist = [...voies, ...lieuxDits] + const existingVoiesIds = new Set(chain(composedVoies).map('idVoie').uniq().value()) + const buildLieuxDitsOptions = {codeCommune, pseudoCodeVoieGenerator, existingVoiesIds} - const nbNumeros = voies.reduce((acc, voie) => acc + voie.nbNumeros, 0) - const nbNumerosCertifies = numeros.filter(n => n.certifie).length + const lieuxDits = isBAL + ? await BAL.buildLieuxDits(balData, buildLieuxDitsOptions) + : await MS.buildLieuxDits(multiSourcesData, buildLieuxDitsOptions) - let districtID - if (isBAL) { - const balAddressVersion = getBalAddressVersion(balData.adresses[0]) - districtID = digestIDsFromBalAddress(balData.adresses[0], balAddressVersion)?.districtID - } - - if (!geo[codeCommune]) { - console.warn(`La commune ${codeCommune} n'a pas de données géographiques associées dans le fichier geo.json`) - } + let districtID + if (isBAL) { + const balAddressVersion = getBalAddressVersion(balData.adresses[0]) + districtID = digestIDsFromBalAddress(balData.adresses[0], balAddressVersion)?.districtID + } else { + districtID = IS_GENERATE_BANID_ON_ASSEMBLY ? uuidv4() : undefined + } - const communeRecord = { - banId: districtID, - nomCommune: communeCOG.nom, - population: communeCOG.population, - departement: pick(getDepartement(communeCOG.departement), 'nom', 'code'), - region: pick(getRegion(communeCOG.region), 'nom', 'code'), - codesPostaux: communeCOG.codesPostaux || [], - displayBBox: geo[codeCommune]?.bbox, - typeCommune: communeCOG.type, - nbNumeros, - nbNumerosCertifies, - nbVoies: voies.length, - nbLieuxDits: lieuxDits.length, - typeComposition: isBAL ? 'bal' : 'assemblage', - idRevision: currentRevision?._id, - dateRevision: currentRevision?.publishedAt, - withBanId: false - } + const voiesToPersist = !isBAL && IS_GENERATE_BANID_ON_ASSEMBLY + ? [...voies.map(v => ({banIdDistrict: districtID, ...v})), ...lieuxDits.map(ld => ({banIdDistrict: districtID, ...ld}))] + : [...voies, ...lieuxDits] + + const numeros = chain(composedVoies) + .map(v => v.numeros.map(n => ({ + codeCommune, + idVoie: v.idVoie, + ...(!isBAL && IS_GENERATE_BANID_ON_ASSEMBLY ? {banId: uuidv4()} : {}), + ...(!isBAL && IS_GENERATE_BANID_ON_ASSEMBLY ? {banIdMainCommonToponym: v.banId,} : {}), + ...(!isBAL && IS_GENERATE_BANID_ON_ASSEMBLY ? {banIdDistrict: districtID} : {}), + ...n + }))) + .flatten() + .value() + + const nbNumeros = voies.reduce((acc, voie) => acc + voie.nbNumeros, 0) + const nbNumerosCertifies = numeros.filter(n => n.certifie).length + + if (!geo[codeCommune]) { + console.warn(`La commune ${codeCommune} n'a pas de données géographiques associées dans le fichier geo.json`) + } - if (codeCommune in locauxAdressesIndex) { - const nbAdressesAttendues = locauxAdressesIndex[codeCommune].nbAdressesLocaux - const ratio = Math.round((nbNumeros / nbAdressesAttendues) * 100) - const deficitAdresses = (communeCOG.population < 2000 && communeCOG.population > 0) - ? ratio < 50 : undefined + const communeRecord = { + banId: districtID, + nomCommune: communeCOG.nom, + population: communeCOG.population, + departement: pick(getDepartement(communeCOG.departement), 'nom', 'code'), + region: pick(getRegion(communeCOG.region), 'nom', 'code'), + codesPostaux: communeCOG.codesPostaux || [], + displayBBox: geo[codeCommune]?.bbox, + typeCommune: communeCOG.type, + nbNumeros, + nbNumerosCertifies, + nbVoies: voies.length, + nbLieuxDits: lieuxDits.length, + typeComposition: isBAL ? 'bal' : 'assemblage', + idRevision: currentRevision?._id, + dateRevision: currentRevision?.publishedAt, + withBanId: false + } - communeRecord.analyseAdressage = { - nbAdressesAttendues, - ratio, - deficitAdresses + if (codeCommune in locauxAdressesIndex) { + const nbAdressesAttendues = locauxAdressesIndex[codeCommune].nbAdressesLocaux + const ratio = Math.round((nbNumeros / nbAdressesAttendues) * 100) + const deficitAdresses = (communeCOG.population < 2000 && communeCOG.population > 0) + ? ratio < 50 : undefined + + communeRecord.analyseAdressage = { + nbAdressesAttendues, + ratio, + deficitAdresses + } } - } - await pseudoCodeVoieGenerator.save() + await pseudoCodeVoieGenerator.save() - await saveCommuneData(codeCommune, {commune: communeRecord, voies: voiesToPersist, numeros}) - return true + await saveCommuneData(codeCommune, {commune: communeRecord, voies: voiesToPersist, numeros}) + return true + } catch (error) { + console.error(`Erreur lors de la composition de la commune ${codeCommune}: ${error.message}`) + return false + } } module.exports = composeCommune diff --git a/lib/compose/strategies/multi-sources/lieux-dits.cjs b/lib/compose/strategies/multi-sources/lieux-dits.cjs index d93c0f44..1488f848 100644 --- a/lib/compose/strategies/multi-sources/lieux-dits.cjs +++ b/lib/compose/strategies/multi-sources/lieux-dits.cjs @@ -1,8 +1,17 @@ +const {v4: uuidv4} = require('uuid') + +const IS_GENERATE_BANID_ON_ASSEMBLY = process.env.IS_GENERATE_BANID_ON_ASSEMBLY === 'true' + async function buildLieuxDits(sourcesData, {existingVoiesIds}) { const cadastreData = sourcesData.find(d => d.source === 'cadastre') if (cadastreData) { - return cadastreData.lieuxDits.filter(ld => !existingVoiesIds.has(ld.idVoie)) + const lieuxDitsData = cadastreData.lieuxDits.filter(ld => !existingVoiesIds.has(ld.idVoie)) + const banId = IS_GENERATE_BANID_ON_ASSEMBLY ? uuidv4() : undefined + return lieuxDitsData.map(ld => ({ + banId, + ...ld + })) } return [] diff --git a/lib/compose/strategies/multi-sources/voies.cjs b/lib/compose/strategies/multi-sources/voies.cjs index de307152..ea9467ca 100644 --- a/lib/compose/strategies/multi-sources/voies.cjs +++ b/lib/compose/strategies/multi-sources/voies.cjs @@ -1,5 +1,6 @@ const {chain, first, flatten} = require('lodash') const {feature} = require('@turf/turf') +const {v4: uuidv4} = require('uuid') const {rewriteSuffixes} = require('@etalab/adresses-util/lib/numeros') const {computeBufferedBbox, getCenterFromPoints, derivePositionProps} = require('../../../util/geo.cjs') @@ -11,6 +12,8 @@ const computeGroups = require('../../processors/compute-groups.cjs') const {buildNumero} = require('./numero.cjs') +const IS_GENERATE_BANID_ON_ASSEMBLY = process.env.IS_GENERATE_BANID_ON_ASSEMBLY === 'true' + function normalizeSuffixeKey(suffixe) { if (!suffixe) { return '' @@ -113,7 +116,10 @@ function buildVoies(multiSourcesData, {codeCommune, pseudoCodeVoieGenerator}) { const centroid = getCenterFromPoints(positions) const {lon, lat, x, y, tiles} = derivePositionProps(centroid, 10, 14) + const banId = IS_GENERATE_BANID_ON_ASSEMBLY ? uuidv4() : undefined + return { + banId, groupId, idVoie: idVoieFantoir, idVoieFantoir, diff --git a/package.json b/package.json index aee4c792..0766d5e0 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "pumpify": "^2.0.1", "rev-hash": "^3.0.0", "sequelize": "^6.31.1", + "uuid": "^10.0.0", "vt-pbf": "^3.1.3", "worker-farm": "^1.7.0", "yup": "^1.0.2" diff --git a/yarn.lock b/yarn.lock index 0b363afb..4bd9ea47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8416,6 +8416,11 @@ uuid@8.3.2, uuid@^8.3.0, uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uuid@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294" + integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ== + uuidv4@^6.2.13: version "6.2.13" resolved "https://registry.yarnpkg.com/uuidv4/-/uuidv4-6.2.13.tgz#8f95ec5ef22d1f92c8e5d4c70b735d1c89572cb7"