637 lines
30 KiB
Python
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
|