Files
enterprise-addons/esg/models/esg_database.py
T

637 lines
30 KiB
Python

import contextlib
import datetime
import json
import logging
import math
import re
import requests
from io import BytesIO
from lxml import html
from openpyxl import load_workbook
from requests.exceptions import HTTPError, RequestException
from odoo import Command, api, fields, models
from odoo.exceptions import ValidationError
from odoo.tools import format_date
from odoo.tools.float_utils import float_compare
_logger = logging.getLogger(__name__)
class EsgDatabase(models.Model):
_name = 'esg.database'
_description = 'Database'
name = fields.Char(required=True)
url = fields.Char()
image = fields.Binary()
last_update = fields.Date()
latest_version = fields.Date()
color = fields.Integer(compute='_compute_kanban_display')
kanban_text = fields.Char(compute='_compute_kanban_display')
can_be_downloaded = fields.Boolean(compute='_compute_can_be_downloaded')
@api.depends('latest_version', 'last_update')
def _compute_kanban_display(self):
for db in self:
db.color = False
db.kanban_text = self.env._("Not loaded")
if not db.last_update:
continue
formatted_date = format_date(self.env, db.last_update, date_format='MMMM y')
db.kanban_text = self.env._("Updated on: %(date)s", date=formatted_date)
if db.last_update >= db.latest_version:
db.color = 7
else:
db.color = 2
def _compute_can_be_downloaded(self):
ademe_db = self.env.ref('esg.esg_database_ademe')
ipcc_db = self.env.ref('esg.esg_database_ipcc')
for database in self:
if database in (ademe_db, ipcc_db):
database.can_be_downloaded = True
else:
database.can_be_downloaded = False
@api.ondelete(at_uninstall=False)
def _prevent_database_deletion(self):
ademe_db = self.env.ref('esg.esg_database_ademe')
ipcc_db = self.env.ref('esg.esg_database_ipcc')
if ademe_db in self:
raise ValidationError(self.env._("You can't delete the ADEME database."))
if ipcc_db in self:
raise ValidationError(self.env._("You can't delete the IPCC database."))
def action_load_data(self):
self.ensure_one()
if self == self.env.ref('esg.esg_database_ademe'):
result = self._action_import_ademe_file()
elif self == self.env.ref('esg.esg_database_ipcc'):
result = self._action_import_efdb_from_ipcc()
else:
raise ValidationError(self.env._("Database file is missing"))
if result is True:
self.last_update = self.latest_version
action_view_emission_factor = self.env['ir.actions.actions']._for_xml_id('esg.action_view_emission_factor')
action_view_emission_factor['domain'] = [('database_id', '=', self.id)]
return action_view_emission_factor
def _external_api_call(self, request_url):
ademe_api_url = 'https://data.ademe.fr/data-fair/api/v1/datasets/base-carboner'
if not request_url.startswith(ademe_api_url):
raise ValidationError(self.env._('Invalid URL.'))
response_error = {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'type': 'warning',
},
}
try:
request_response = requests.request(
'GET',
request_url,
timeout=(30, 30),
)
request_response.raise_for_status()
except (ValueError, HTTPError, RequestException) as exception:
response_error['params']['message'] = self.env._(
"Server returned an unexpected error: %(error)s",
error=(request_response.text or str(exception)),
)
return response_error
try:
response_text = request_response.text
response_data = json.loads(response_text)
except json.JSONDecodeError:
response_error['params']['message'] = self.env._("JSON response could not be decoded.")
return response_error
return response_data
# =============
# ADEME IMPORT
# =============
def _get_ademe_emission_factor_values(self, lines):
existing_factors = dict(self.env['esg.emission.factor']._read_group(
domain=[('database_id', '=', self.id)],
groupby=['code'],
aggregates=['id:recordset'],
))
gasses = {
'CO2f': self.env.ref('esg.esg_gas_co2'),
'CH4f': self.env.ref('esg.esg_gas_ch4f'),
'CH4b': self.env.ref('esg.esg_gas_ch4b'),
'N2O': self.env.ref('esg.esg_gas_n2o'),
'SF6': self.env.ref('esg.esg_gas_sf6'),
}
hardcoded_units_conversion = {
'kg': [
'kg of active substance', 'kg (live weight)', 'kg net weight', 'kg of spreaded nitrogen',
'kg of suppressed DCO', 'kg of treated leather', 'Kg', 'kg of spreaded nitrgen',
'kgH2', 'kg NTK', 'kg BioGNC'
],
'L': ['liter', 'Liter'],
'Ton': ['ton', 'ton of K2O', 'ton of N', 'ton of P2O5', 'ton of clinker', 'ton of waste', 'tonne'],
'Units': ['unit', 'meal'],
'm': ['m of road'],
'm²': ['m² net floor area', 'm² of ceiling', 'm² of floor', 'm² of wall', 'm2 SHON'],
'm³': ['m3', 'm3 (n)'],
'ml': ['mL'],
'Hours': ['hour', 'heure'],
'kWh': ['kWh (LHV)', 'kWh (PCI)', 'kWh HHV', 'kWh ICV', 'kWh IVC', 'kWh LHV', 'kWh SCV']
}
hardcoded_source_scopes = {
'Achats de biens': 'indirect_others',
'Process et émissions fugitives': 'direct',
'Achats de services': 'indirect_others',
'Combustibles': 'direct',
'Transport de marchandises': 'indirect_others',
'Transport de personnes': 'indirect_others',
'Statistiques territoriales': 'direct',
'UTCF': 'direct',
'Traitement des déchets': 'indirect_others',
'Electricité': 'indirect',
'Réseaux de chaleur / froid': 'direct',
}
def _get_gas_lines(line, gasses, total_emissions):
gas_lines = []
for gas in ['CO2f', 'CH4f', 'CH4b', 'N2O']:
if line.get(gas, 0) > 0:
gas_lines.append({
'gas_id': gasses[gas],
'quantity': line[gas],
'activity_type_id': line.get('Type_poste', False),
})
for i in range(1, 6):
additional_gas = line.get(f"Code_gaz_supplémentaire_{i}", None)
if additional_gas and additional_gas in ['SF6']:
gas_lines.append({
'gas_id': gasses[additional_gas],
'quantity': line.get(f"Valeur_gaz_supplémentaire_{i}", 0),
'activity_type_id': line.get('Type_poste', False),
})
# if the gaz decomposition is equal to the total emissions, that means that the decomposition
# is given with CO2 equivalent value; each line needs to be adapted to have the gaz volume instead
if not float_compare(sum(gas['quantity'] for gas in gas_lines), total_emissions, precision_rounding=0.1):
for gas in gas_lines:
gas['quantity'] = gas['quantity'] / gas['gas_id'].global_warming_potential
return gas_lines
_logger.info("ESG: Processing raw lines")
line_values = []
posts_gas_lines = []
for raw_line in lines:
if not raw_line:
continue
line = raw_line # dict(zip(dict_keys, raw_line))
if line["Statut_de_l'élément"] == "Archivé" or\
line["Type_de_l'élément"] == "Données source":
continue
total_emissions = line['Total_poste_non_décomposé']
if line["Type_Ligne"] == "Poste":
posts_gas_lines += _get_gas_lines(line, gasses, total_emissions)
line_values[-1]['gas_line_ids'] = posts_gas_lines
continue
dates = {}
for raw_date in ['Date_de_création', 'Période_de_validité']:
if raw_date not in line:
dates[raw_date] = False
continue
try:
dates[raw_date] = datetime.datetime.strptime(line[raw_date], '%Y-%m-%d')
except ValueError:
dates[raw_date] = False
posts_gas_lines = []
gas_lines = _get_gas_lines(line, gasses, total_emissions)
normalized_unit = line.get('Unité_anglais', line.get('Unité_français', '')).replace('kgCO2e/', '')
for base_unit, alternate_names in hardcoded_units_conversion.items():
if normalized_unit in alternate_names:
normalized_unit = base_unit
break
factor_values = {
'name': f'{line.get("Nom_base_anglais", "")} {line.get("Nom_attribut_anglais", "")} {line.get("Nom_frontière_anglais", "")}'
if line.get("Nom_base_anglais", '')
else f'{line.get("Nom_base_français", "")} {line.get("Nom_attribut_français", "")} {line.get("Nom_frontière_français", "")}',
'code': line["Identifiant_de_l'élément"],
'database_id': self.id,
'uom_id': normalized_unit,
'esg_uncertainty_value': line.get('Incertitude', 0) / 100,
'compute_method': 'physically',
'valid_from': dates['Date_de_création'],
'valid_to': dates['Période_de_validité'],
'source_id': line['Code_de_la_catégorie'],
'gas_line_ids': gas_lines,
}
if not gas_lines:
factor_values['esg_emissions_value'] = total_emissions
line_values.append(factor_values)
_logger.info("ESG: Checking existing units, emission sources and esg activity type")
# Prepare to process all units at once to avoid multiplying queries and loops
units = [line['uom_id'].strip() for line in line_values]
existing_units = dict(
self.env['uom.uom']._read_group(
domain=[('name', 'in', units)],
groupby=['name'],
aggregates=['id:recordset'],
)
)
# Prepare to process all sources at once to avoid multiplying queries and loops
source_tree_list = []
for line in line_values:
source_tree_list.append([s.strip() for s in line['source_id'].split('>')])
all_sources_name = {item for sublist in source_tree_list for item in sublist}
existing_sources = {(name, parent): record for name, parent, record in self.env['esg.emission.source']._read_group(
domain=[('name', 'in', all_sources_name)],
groupby=['name', 'parent_id'],
aggregates=['id:recordset'],
)}
# Prepare to process all activity types at once to avoid multiplying queries and loops
existing_activity_types = dict(
self.env['esg.activity.type']._read_group(
domain=[],
groupby=['name'],
aggregates=['id:recordset'],
)
)
_logger.info("ESG: Processing link to related records")
# slow loop, should be optimized
for source_tree, unit, line in zip(source_tree_list, units, line_values):
# units
existing_unit = existing_units.get(unit, False)
if not existing_unit:
existing_unit = self.env['uom.uom'].create({'name': unit})
existing_units[unit] = existing_unit
line['uom_id'] = existing_unit.id
# sources
previous_existing_source = self.env['esg.emission.source']
for source_name in source_tree:
existing_source = existing_sources.get((source_name, previous_existing_source), False)
if not existing_source:
existing_source = self.env['esg.emission.source'].create({
'name': source_name,
'parent_id': previous_existing_source.id,
'scope': hardcoded_source_scopes.get(source_name, previous_existing_source.scope) or 'direct',
})
existing_sources[source_name, previous_existing_source] = existing_source
previous_existing_source = existing_source
line['source_id'] = previous_existing_source.id
# activity types + declare gas lines correctly
if line['gas_line_ids']:
for gas_line in line['gas_line_ids']:
gas_line['gas_id'] = gas_line['gas_id'].id
if not gas_line['activity_type_id']:
continue
activity = existing_activity_types.get(gas_line['activity_type_id'], False)
if not activity:
activity = self.env['esg.activity.type'].create({'name': gas_line['activity_type_id']})
existing_activity_types[gas_line['activity_type_id']] = activity
gas_line['activity_type_id'] = activity.id
line['gas_line_ids'] = [Command.create(gas) for gas in line['gas_line_ids']]
new_factor_values = []
write_factor_values = {}
for line_value in line_values:
# Separate data into existing one and new one
line_code = line_value['code']
if line_code in existing_factors:
write_factor_values[existing_factors[line_code]] = line_value
else:
new_factor_values.append(line_value)
return new_factor_values, write_factor_values
def _action_import_ademe_file(self):
ademe_api_url = 'https://data.ademe.fr/data-fair/api/v1/datasets/base-carboner'
ademe_data = self._external_api_call(ademe_api_url)
if not ademe_data.get('updatedAt'):
return ademe_data
ademe_data['updatedAt'] = re.sub(r'\.\d+', '', ademe_data['updatedAt'])
self.latest_version = datetime.datetime.strptime(ademe_data['updatedAt'], "%Y-%m-%dT%H:%M:%SZ")
if self.last_update and self.latest_version <= self.last_update:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'type': 'success',
'message': self.env._('ADEME data is already up to date.'),
},
}
required_calls = math.ceil(ademe_data['count'] / 10000) # the api limits the amount of data lines to 10k
_logger.info("ESG: Fetching ADEME data from their API")
complete_data = []
for i in range(0, required_calls):
data_url = f'{ademe_api_url}/lines?size=10000&format=json&after={10000 * i}'
data = self._external_api_call(data_url)
if not data.get('results', None):
return data
complete_data += data['results']
_logger.info("ESG: Reading ADEME carbon data")
try:
new_values, write_values = self._get_ademe_emission_factor_values(complete_data)
_logger.info("ESG: Creating/Writing records")
self.env['esg.emission.factor'].create(new_values)
for record, values in write_values.items():
record.gas_line_ids = False
record.write(values)
_logger.info("ESG: File imported")
except KeyError as e:
_logger.error(e)
raise ValidationError(self.env._("The file format doesn't seem to be correct."))
return True
# =============
# IPCC IMPORT
# =============
def _get_ipcc_xls_file(self, reset=False):
request_headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate, br, zstd',
'Content-Type': 'application/x-www-form-urlencoded',
}
table_name = 'tmp_e63ckbntq05n963ul2451jk2sp'
if reset:
request_response = requests.request(
'GET',
'https://www.ipcc-nggip.iges.or.jp/EFDB/find_ef.php?reset=',
timeout=(30, 30),
)
request_response.raise_for_status()
request_response = requests.request(
'POST',
'https://www.ipcc-nggip.iges.or.jp/EFDB/find_ef.php',
headers=request_headers,
data={'action': 'apply_filter', 'source_data': 'default'},
timeout=(30, 30),
)
request_response.raise_for_status()
root_node = html.fromstring(request_response.content)
if elements := root_node.xpath('//input[@name="tableName"]'):
if len(elements) == 1:
table_name = elements[0].value
cookie = request_response.headers.get('Set-Cookie', '')
cookie_parts = [part.split('=', 1) for part in cookie.split(';') if '=' in part]
for cookie_name, cookie_value in cookie_parts:
if cookie_name == 'PHPSESSID':
request_headers['Cookie'] = f'PHPSESSID={cookie_value}'
break
request_response = requests.request(
'POST',
'https://www.ipcc-nggip.iges.or.jp/EFDB/find_ef_xls.php',
headers=request_headers,
data={'lang_id': 1, 'tableName': table_name, 'mi_show_fuel': True, 'mi_show_cpool': True},
timeout=(30, 30),
)
request_response.raise_for_status()
if request_response.content:
xls_file = BytesIO(request_response.content)
elif not reset:
xls_file = self._get_ipcc_xls_file(reset=True)
else:
raise ValidationError(self.env._("The IPCC server did not return any file to import the data."))
return xls_file
def _action_import_efdb_from_ipcc(self):
response_error = {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'type': 'warning',
},
}
ipcc_ef_data_list = []
parent_source_names = set()
try:
filename = self._get_ipcc_xls_file()
workbook = load_workbook(filename=filename)
sheet = workbook.active
column_id_per_vals_key = {}
for row in sheet.iter_rows():
ipcc_data = {}
is_header = True
for cell in row:
if cell.row == 1:
if cell.value == 'EF ID':
column_id_per_vals_key[cell.column] = 'code'
elif cell.value == 'IPCC 1996 Source/Sink Category':
column_id_per_vals_key[cell.column] = 'parent_source_id'
elif cell.value == 'IPCC 2006 Source/Sink Category':
column_id_per_vals_key[cell.column] = 'source_id'
elif cell.value == 'Gas':
column_id_per_vals_key[cell.column] = 'gas_id'
elif cell.value == 'Description':
column_id_per_vals_key[cell.column] = 'name'
elif cell.value == 'Region / Regional Conditions':
column_id_per_vals_key[cell.column] = 'region'
elif cell.value == 'Value':
column_id_per_vals_key[cell.column] = 'quantity'
elif cell.value == 'Unit':
column_id_per_vals_key[cell.column] = 'unit'
elif cell.value == 'Technologies / Practices':
column_id_per_vals_key[cell.column] = 'note1'
elif cell.value == 'Parameters / Conditions':
column_id_per_vals_key[cell.column] = 'note2'
elif cell.column in column_id_per_vals_key:
is_header = False
key = column_id_per_vals_key[cell.column]
value = str(cell.value).strip()
if key == 'parent_source_id':
parent_source_names.add(value)
ipcc_data[key] = value
if not is_header:
ipcc_ef_data_list.append(ipcc_data)
except (ValueError, HTTPError, RequestException) as exception:
response_error['params']['message'] = self.env._(
"Server returned an unexpected error: %(error)s",
error=str(exception),
)
return response_error
parent_sources = self.env['esg.emission.source']._load_records([{'xml_id': f'esg.ipcc_emission_source_{i}', 'noupdate': True, 'values': {'name': source_name.strip()}} for i, source_name in enumerate(parent_source_names, 1)])
parent_source_per_name = {es.name: es for es in parent_sources}
source_name_per_parent_source = {
ef_data['source_id']: parent_source_per_name[ef_data['parent_source_id'].strip()]
for ef_data in ipcc_ef_data_list
if ef_data.get('source_id') and parent_source_per_name.get(ef_data.get('parent_source_id', '').strip())
}
source_per_name = {
es.name: es
for es in self.env['esg.emission.source']._load_records([
{'xml_id': f'esg.ipcc_emission_source_{parent.id}_{i}', 'noupdate': True, 'values': {'name': source_name.strip(), 'parent_id': parent.id}}
for i, (source_name, parent) in enumerate(source_name_per_parent_source.items(), 1)
])
}
gas_mapping = {
'METHANE': self.env.ref('esg.esg_gas_ch4'),
'CARBON DIOXIDE': self.env.ref('esg.esg_gas_co2'),
'c-C4F8': self.env.ref('esg.esg_gas_pfc_c-c4f8'),
'C2F6': self.env.ref('esg.esg_gas_pfc_c2f6'),
'C3F8': self.env.ref('esg.esg_gas_pfc_c3f8'),
'C4F6': self.env.ref('esg.esg_gas_pfc_c4f6'),
'C4F8O': self.env.ref('esg.esg_gas_c4f8o'),
'C5F8': self.env.ref('esg.esg_gas_pfc_c5f8'),
'C6F14': self.env.ref('esg.esg_gas_pfc_c6f14'),
'CARBON MONOXIDE': self.env.ref('esg.esg_gas_co'),
'CF4': self.env.ref('esg.esg_gas_pfc_cf4'),
'HFC-125': self.env.ref('esg.esg_gas_hfc_125'),
'HFC-134a': self.env.ref('esg.esg_gas_hfc_134a'),
'HFC-134a\nHFC-152a': self.env.ref('esg.esg_gas_hfc_134a'),
'HFC-143a': self.env.ref('esg.esg_gas_hfc_143a'),
'HFC-152a': self.env.ref('esg.esg_gas_hfc_152a'),
'HFC-23': self.env.ref('esg.esg_gas_hfc_23'),
'HFC-23\nHFC-32\nHFC-125\nHFC-134a\nHFC-143a\nCF4\nC2F6\nC3F8\nC4F8\nC5F8\nC6F14': self.env.ref('esg.esg_gas_hfc_23'),
'HFC-23\nHFC-32\nHFC-125\nHFC-134a\nHFC-152a\nHFC-143a\nHFC-227ea\nHFC-236fa': self.env.ref('esg.esg_gas_hfc_23'),
'HFC-23\nHFC-32\nHFC-41\nHFC-43-10mee\nHFC-125\nHFC-134\nHFC-134a\nHFC-152a\nHFC-143\nHFC-143a\nHFC-227ea\nHFC-236fa\nHFC-245ca\nCF4\nC2F6\nC3F8\nC4F10\nc-C4F8\nC5F12\nC6F14': self.env.ref('esg.esg_gas_hfc_23'),
'HFC-23\nHFC-32\nHFC-41\nHFC-43-10mee\nHFC-125\nHFC-134\nHFC-134a\nHFC-152a\nHFC-143\nHFC-143a\nHFC-227ea\nHFC-236fa\nHFC-245ca\nHFC-152\nHFC-161\nHFC-236cb\nHFC-236ea\nHFC-245fa\nHFC-365mfc': self.env.ref('esg.esg_gas_hfc_23'),
'HFC-32': self.env.ref('esg.esg_gas_hfc_32'),
'HFC-41': self.env.ref('esg.esg_gas_hfc_41'),
'HFE-125\nHFC-43-10mee\nHFC-125\nHFC-134\nHFC-134a\nHFC-152a\nHFC-143\nHFC-143a\nHFC-227ea\nHFC-236fa\nHFC-245ca\nHFC-152\nHFC-161\nHFC-236cb\nHFC-236ea\nHFC-245fa\nHFC-365mfc': self.env.ref('esg.esg_gas_cf3ochf2'),
'HFE-245fa1\nHFE-365mcf3\nHFC-134a\nHFC-152a\nHFC-227ea': self.env.ref('esg.esg_gas_chf2ch2ocf3'),
'HFE-245fa1\nHFE-365mcf3\nHFC-43-10mee\nHFC-134a\nHFC-152a\nHFC-227ea': self.env.ref('esg.esg_gas_chf2ch2ocf3'),
'HFE-365mcf3\nHFC-43-10mee\nC6F14': self.env.ref('esg.esg_gas_cf3cf2ch2och3'),
'HFE-7100': self.env.ref('esg.esg_gas_chf2_c4f9och3'),
'METHANE\nCARBON DIOXIDE\nNITROUS OXIDE': self.env.ref('esg.esg_gas_ch4'),
'METHANE\nNITROUS OXIDE': self.env.ref('esg.esg_gas_ch4'),
"NITROGEN OXIDES (NO+NO2)\nMETHANE\nCARBON MONOXIDE\nCARBON DIOXIDE\nNITROUS OXIDE": self.env.ref('esg.esg_gas_co'),
"NITROGEN OXIDES (NO+NO2)\nMETHANE\nCARBON MONOXIDE\nNITROUS OXIDE": self.env.ref('esg.esg_gas_co'),
'Nitrogen Trifluoride': self.env.ref('esg.esg_gas_nf3'),
"Nitrogen Trifluoride\nHFC-23\nHFC-32\nCF4\nC2F6\nC3F8\nc-C4F8\nSulphur Hexafluoride": self.env.ref('esg.esg_gas_nf3'),
'NITROUS OXIDE': self.env.ref('esg.esg_gas_n2o'),
"SULPHUR DIOXIDE (SO2+SO3)\nNITROGEN OXIDES (NO+NO2)\nNON METHANE VOLATILE ORGANIC COMPOUNDS\nMETHANE\nCARBON MONOXIDE\nCARBON DIOXIDE\nNITROUS OXIDE": self.env.ref('esg.esg_gas_so2'),
"Sulphur Hexafluoride": self.env.ref('esg.esg_gas_sf6'),
}
def parse_float(value):
amount = None
with contextlib.suppress(ValueError, TypeError):
amount = float(value)
return amount
emission_factor_xmlid_list = []
uom_kg = self.env.ref('uom.product_uom_kgm')
uom_unit = self.env.ref('uom.product_uom_unit')
uom_m3 = self.env.ref('uom.product_uom_cubic_meter')
uom_tonne = self.env.ref('uom.product_uom_ton')
uom_ha, uom_lto = self.env['uom.uom']._load_records([
{'xml_id': 'esg.uom_ha', 'noupdate': True, 'values': {'name': 'ha', 'relative_factor': 1}},
{'xml_id': 'esg.uom_lt', 'noupdate': True, 'values': {'name': 'LTO', 'relative_factor': 1}},
])
nb_skipped_records = 0
for ef_data in ipcc_ef_data_list:
code = ef_data['code']
note = ef_data.get('note1', '')
uom = uom_kg
gaz_id = False
source_id = False
name = ef_data['name'].strip()
if not name:
id = int(code)
if 327476 <= id <= 327568:
name = "Combustion factor (Cf) for fires in vegetation types"
elif 327569 <= id <= 327644:
name = "Below-ground biomass (BGB): root-to-shoot ratio"
elif 327645 <= id <= 328060:
name = "Above-ground biomass (AGB): net biomass growth in natural forests"
elif 327427 <= id <= 327475:
name = "Soil organic carbon stocks (SOCREF) in mineral soils"
elif 327386 <= id <= 327426:
name = "Dead wood carbon stock"
elif 327260 <= id <= 327385:
name = "Litter carbon stock"
else:
nb_skipped_records += 1
continue
if source_name := ef_data['source_id'].strip():
source = source_per_name.get(source_name)
if not source:
nb_skipped_records += 1
continue
source_id = source.id
else:
parent_source = parent_source_per_name.get(ef_data['parent_source_id'].strip())
if not parent_source:
nb_skipped_records += 1
continue
source_id = parent_source.id
if gaz := gas_mapping.get(ef_data['gas_id']):
gaz_id = gaz.id
else:
nb_skipped_records += 1
continue
if note2 := ef_data.get('note2', ''):
note += '\n' + note2
value = parse_float(ef_data['quantity'])
if value is None:
nb_skipped_records += 1
continue
if unit := ef_data.get('unit'):
if unit.startswith('%') or unit.lower().startswith('fraction') or unit.lower().startswith('year') or unit in ['per year', 'months', 'parts per billion by volume', 'asse', 'installe', 'equipment']:
nb_skipped_records += 1
continue
elif unit.startswith('g'):
value /= 1000
elif (
unit.lower().startswith('kg')
):
value *= 1
elif unit.startswith('TJ'):
value *= 1.11 * (10 ** -5)
elif unit.lower().startswith('gg'):
value *= 10 ** 9
elif unit.lower().startswith('ton') or unit in ['(kg PFC/tAl)/(AE-Minutes/cellday)', '(kg PFC/tAl)/(mV/day)', 'kg SF6/tonnes magnesium produced or smelted']:
uom = uom_tonne
elif unit == 'm3/m3 beer':
value *= 1.020
uom = uom_m3
elif unit == 'm3/m3 ethanol':
value *= 789
uom = uom_m3
elif unit == 'kg CH4/head/yr':
uom = uom_unit
elif unit == 't dm/ha':
uom = uom_ha
value *= 1000
elif unit == 'kg/LTO':
uom = uom_lto
note += '\nUnit converted in kg: ' + unit
emission_factor_xmlid_list.append({
'xml_id': f'esg.ipcc_emission_factor_{code}',
'noupdate': True,
'values': {
'code': code,
'name': name,
'description': note,
'uom_id': uom.id,
'source_id': source_id,
'region': ef_data['region'],
'database_id': self.id,
'gas_line_ids': [
Command.create({
'gas_id': gaz_id,
'quantity': value,
}),
],
},
})
if nb_skipped_records:
_logger.warning("%s entries from IPCC Database were skipped because of missing information", nb_skipped_records)
self.env['esg.emission.factor']._load_records(emission_factor_xmlid_list)
return True