Source code for oasislmf.preparation.summaries

__all__ = [
    'get_summary_mapping',
    'generate_summaryxref_files',
    'get_ri_inuring_priority_output_levels',
    'merge_oed_to_mapping',
    'write_exposure_summary',
    'get_exposure_summary',
    'get_useful_summary_cols',
    'get_xref_df',
    'write_summary_levels',
    'write_mapping_file',
    'calculated_summary_cols'
]

import logging
import pathlib
import shutil

import io
import json
import os

import numpy as np
import pandas as pd

from ..utils.coverages import SUPPORTED_COVERAGE_TYPES
from ..utils.data import (
    factorize_dataframe,
    get_dataframe,
    get_json,
    merge_dataframes,
    set_dataframe_column_dtypes,
    fill_na_with_categoricals
)
from ..utils.defaults import (
    SOURCE_IDX,
    SUMMARY_MAPPING,
    SUMMARY_OUTPUT,
    SUMMARY_TOP_LEVEL_COLS,
    get_default_exposure_profile,
)
from ..utils.exceptions import OasisException
from ..utils.log import oasis_log
from ..utils.path import as_path
from ..utils.status import OASIS_KEYS_STATUS, OASIS_KEYS_STATUS_MODELLED
from oasislmf.pytools.common.data import gul_summary_xref_dtype, fm_summary_xref_dtype
from oasislmf.pytools.converters.csvtobin.utils.common import df_to_ndarray
from oasislmf.utils.fm import SUPPORTED_FM_LEVELS


MAP_SUMMARY_DTYPES = {
    'loc_id': 'int',
    SOURCE_IDX['loc']: 'int',
    SOURCE_IDX['acc']: 'int',
    'item_id': 'int',
    'layer_id': 'int',
    'coverage_id': 'int',
    'peril_id': 'category',
    'agg_id': 'int',
    'output_id': 'int',
    'coverage_type_id': 'int',
    'tiv': 'float',
    'building_id': 'int',
    'risk_id': 'int',
}

logger = logging.getLogger(__name__)


[docs] def get_useful_summary_cols(oed_hierarchy): return [ oed_hierarchy['accnum']['ProfileElementName'], oed_hierarchy['locnum']['ProfileElementName'], 'loc_id', oed_hierarchy['polnum']['ProfileElementName'], oed_hierarchy['portnum']['ProfileElementName'], SOURCE_IDX['loc'], SOURCE_IDX['acc'], 'item_id', 'layer_id', 'coverage_id', 'peril_id', 'agg_id', 'output_id', 'coverage_type_id', 'tiv', 'building_id', 'risk_id', 'intensity_adjustment', 'return_period' ]
def is_property_damage(summary_df): property_damage_cov_id = [SUPPORTED_COVERAGE_TYPES['buildings']['id'], SUPPORTED_COVERAGE_TYPES['contents']['id']] summary_df['is_property_damage'] = summary_df['coverage_type_id'].isin(property_damage_cov_id) return summary_df
[docs] calculated_summary_cols = {'is_property_damage': is_property_damage}
[docs] def get_xref_df(il_inputs_df): top_level_layers_df = il_inputs_df.loc[il_inputs_df['level_id'] == il_inputs_df['level_id'].max(), ['top_agg_id'] + SUMMARY_TOP_LEVEL_COLS].drop_duplicates() bottom_level_layers_df = il_inputs_df[il_inputs_df['level_id'] == 0].copy() bottom_level_layers_df.drop(columns=SUMMARY_TOP_LEVEL_COLS, inplace=True) return (merge_dataframes(bottom_level_layers_df, top_level_layers_df, join_on=['top_agg_id']) .drop_duplicates(subset=['gul_input_id', 'layer_id'], keep='first') .sort_values(['gul_input_id', 'layer_id'], kind='stable') )
@oasis_log
[docs] def get_summary_mapping(inputs_df, oed_hierarchy, is_fm_summary=False): """Create a DataFrame with linking information between Ktools `OasisFiles` And the Exposure data Args: inputs_df (pandas.DataFrame): datafame from gul_inputs.get_gul_input_items(..) / il_inputs.get_il_input_items(..) oed_hierarchy (dict): OED profile hierarchy, used to resolve the acc/loc/pol/port column names to keep is_fm_summary (bool): Indicates whether an FM summary mapping is required Returns: pandas.DataFrame: Subset of columns from gul_inputs_df / il_inputs_df """ # Case GUL+FM (based on il_inputs_df) if is_fm_summary: summary_mapping = inputs_df.rename(columns={'gul_input_id': 'agg_id'}) # GUL Only else: summary_mapping = inputs_df.copy(deep=True) summary_mapping['layer_id'] = 1 summary_mapping['agg_id'] = summary_mapping['item_id'] summary_mapping.drop( [c for c in summary_mapping.columns if c not in get_useful_summary_cols(oed_hierarchy)], axis=1, inplace=True ) acc_num = oed_hierarchy['accnum']['ProfileElementName'] loc_num = oed_hierarchy['locnum']['ProfileElementName'] policy_num = oed_hierarchy['polnum']['ProfileElementName'] portfolio_num = oed_hierarchy['portnum']['ProfileElementName'] dtypes = { **{t: 'str' for t in [portfolio_num, policy_num, acc_num, loc_num, 'peril_id']}, **{t: 'uint8' for t in ['coverage_type_id']}, **{t: 'uint32' for t in [SOURCE_IDX['loc'], SOURCE_IDX['acc'], 'loc_id', 'item_id', 'layer_id', 'coverage_id', 'agg_id', 'output_id', 'building_id', 'risk_id']}, **{t: 'float64' for t in ['tiv']} } summary_mapping = set_dataframe_column_dtypes(summary_mapping, dtypes) return summary_mapping
[docs] def merge_oed_to_mapping(summary_map_df, exposure_df, oed_column_join, oed_column_info): """Create a factorized col (summary ids) based on a list of oed column names {'Col_A': 0, 'Col_B': 1, 'Col_C': 2} Args: summary_map_df (pandas.DataFrame): dataframe return from get_summary_mapping exposure_df (pandas.DataFrame): Summary map file path oed_column_join (list): column to join on oed_column_info (dict): Dictionary of columns to pick from exposure_df and their default value Returns: pandas.DataFrame: New DataFrame of summary_map_df + exposure_df merged on exposure index """ column_set = set(oed_column_info) columns_found = [c for c in column_set if c in exposure_df.columns and c not in summary_map_df.columns] columns_missing = list(set(column_set) - set(columns_found)) new_summary_map_df = merge_dataframes(summary_map_df, exposure_df.loc[:, columns_found + oed_column_join], join_on=oed_column_join, how='inner') for col, default in oed_column_info.items(): if col in columns_missing: new_summary_map_df[col] = default fill_na_with_categoricals(new_summary_map_df, oed_column_info) return new_summary_map_df
def group_by_oed(oed_col_group, summary_map_df, exposure_df, sort_by, accounts_df=None): """Adds list of OED fields from `column_set` to summary map file Args: oed_col_group (list): OED column names the rows are grouped by to form the summaries summary_map_df (pandas.DataFrame): dataframe return from get_summary_mapping exposure_df (pandas.DataFrame): DataFrame loaded from location.csv sort_by (str): column the grouped rows are ordered by accounts_df (pandas.DataFrame): DataFrame loaded from accounts.csv Returns: tuple: a 3-tuple ``(summary_ids, summary_id_values, summary_tiv)``: - summary_ids (numpy.ndarray): integer summary id (1..n) for each row, e.g. ``array([1, 2, 1, 2, 1, 2, 1, 2, 1, 2, ...])`` - summary_id_values (numpy.ndarray): distinct values used to factorize the rows into summaries, e.g. ``array(['Layer1', 'Layer2'], dtype=object)`` - summary_tiv (pandas.DataFrame): total TIV aggregated per summary group """ oed_cols = oed_col_group # All required columns exposure_cols = [c for c in oed_cols if c not in summary_map_df.columns and c not in calculated_summary_cols] # columns which are in locations / Accounts file mapped_cols = [c for c in oed_cols + [SOURCE_IDX['loc'], SOURCE_IDX['acc'], sort_by] if c in summary_map_df.columns] # Columns already in summary_map_df to_calculate_column = [c for c in oed_cols if c in calculated_summary_cols] tiv_cols = ['tiv', 'loc_id', 'building_id', 'coverage_type_id'] # Extract mapped_cols from summary_map_df summary_group_df = summary_map_df.loc[:, list(set(tiv_cols).union(mapped_cols))] # Search Loc / Acc files and merge in remaing if exposure_cols is not []: # Location file columns exposure_cols_loc = [c for c in exposure_cols if c in exposure_df.columns] exposure_col_df = exposure_df.loc[:, exposure_cols_loc + [SOURCE_IDX['loc']]] summary_group_df = merge_dataframes(summary_group_df, exposure_col_df, join_on=SOURCE_IDX['loc'], how='left') # Account file columns if isinstance(accounts_df, pd.DataFrame): accounts_cols = [c for c in exposure_cols if c in set(accounts_df.columns) - set(exposure_df.columns)] if accounts_cols: accounts_col_df = accounts_df.loc[:, accounts_cols + [SOURCE_IDX['acc']]] summary_group_df = merge_dataframes(summary_group_df, accounts_col_df, join_on=SOURCE_IDX['acc'], how='left') for col in to_calculate_column: summary_group_df = calculated_summary_cols[col](summary_group_df) fill_na_with_categoricals(summary_group_df, 0) summary_group_df.sort_values(by=[sort_by], inplace=True, kind='stable') summary_ids = factorize_dataframe(summary_group_df, by_col_labels=oed_cols) summary_tiv = summary_group_df.drop_duplicates(['loc_id', 'building_id', 'coverage_type_id'] + oed_col_group, keep='first').groupby(oed_col_group, observed=True).agg({'tiv': "sum"}) return summary_ids[0], summary_ids[1], summary_tiv @oasis_log
[docs] def write_summary_levels(exposure_df, accounts_df, exposure_data, target_dir): """Json file with list Available / Recommended columns for use in the summary reporting Available: Columns which exists in input files and has at least one non-zero / NaN value Recommended: Columns which are available + also in the list of `useful` groupings SUMMARY_LEVEL_LOC { 'GUL': { 'available': ['AccNumber', 'LocNumber', 'istenant', 'buildingid', 'countrycode', 'latitude', 'longitude', 'streetaddress', 'postalcode', 'occupancycode', 'constructioncode', 'locperilscovered', 'BuildingTIV', 'ContentsTIV', 'BITIV', 'PortNumber'], 'IL': { ... etc ... } } """ # Manage internal columns, (Non-OED exposure input) int_excluded_cols = ['loc_id', SOURCE_IDX['loc']] desc_non_oed = 'Not an OED field' int_oasis_cols = { 'coverage_type_id': 'Oasis coverage type', 'peril_id': 'OED peril code', 'coverage_id': 'Oasis coverage identifier', } # GUL perspective (loc columns only) l_col_list = (exposure_df.drop(columns=['peril_group_id'], errors='ignore') .replace(0, np.nan) .dropna(how='any', axis=1) .columns.to_list()) l_col_info = exposure_data.get_input_fields('Loc') gul_avail = {k: l_col_info[k.lower()]["Type & Description"] if k.lower() in l_col_info else desc_non_oed for k in set([c for c in l_col_list]).difference(int_excluded_cols)} # IL perspective (join of acc + loc col with no dups) il_avail = {} if accounts_df is not None: a_col_list = accounts_df.loc[:, ~accounts_df.isnull().all()].columns.to_list() a_col_info = exposure_data.get_input_fields('Acc') a_avail = set([c for c in a_col_list]) il_avail = {k: a_col_info[k.lower()]["Type & Description"] if k.lower() in a_col_info else desc_non_oed for k in a_avail.difference(gul_avail.keys())} # Write JSON gul_summary_lvl = {'GUL': {'available': {**gul_avail, **il_avail, **int_oasis_cols}}} il_summary_lvl = {'IL': {'available': {**gul_avail, **il_avail, **int_oasis_cols}}} if il_avail else {} with io.open(os.path.join(target_dir, 'exposure_summary_levels.json'), 'w', encoding='utf-8') as f: f.write(json.dumps({**gul_summary_lvl, **il_summary_lvl}, sort_keys=True, ensure_ascii=False, indent=4))
@oasis_log
[docs] def write_mapping_file(sum_inputs_df, target_dir, is_fm_summary=False): """Writes a summary map file, used to build summarycalc xref files. Args: sum_inputs_df (pandas.DataFrame): dataframe return from get_summary_mapping target_dir (str): directory the summary map file is written to is_fm_summary (bool): Indicates whether an FM summary mapping is required Returns: str: Summary xref file path """ target_dir = as_path( target_dir, 'Target IL input files directory', is_dir=True, preexists=False ) # Set chunk size for writing the CSV files - default is max 20K, min 1K chunksize = min(2 * 10 ** 5, max(len(sum_inputs_df), 1000)) if is_fm_summary: sum_mapping_fp = os.path.join(target_dir, SUMMARY_MAPPING['fm_map_fn']) else: sum_mapping_fp = os.path.join(target_dir, SUMMARY_MAPPING['gul_map_fn']) try: sum_inputs_df.to_csv( path_or_buf=sum_mapping_fp, encoding='utf-8', mode=('w' if os.path.exists(sum_mapping_fp) else 'a'), chunksize=chunksize, index=False ) except (IOError, OSError) as e: raise OasisException("Exception raised in 'write_mapping_file'", e) return sum_mapping_fp
def get_column_selection(summary_set): """Given a analysis_settings summary definition, return either 1. the set of OED columns requested to group by 2. If no information key 'oed_fields', then group all outputs into a single summary_set Args: summary_set (dict): summary group dictionary from the `analysis_settings.json` Returns: list: List of selected OED columns to create summary groups from """ if "oed_fields" not in summary_set: return [] if not summary_set["oed_fields"]: return [] # Use OED column list set in analysis_settings file elif isinstance(summary_set['oed_fields'], list) and len(summary_set['oed_fields']) > 0: return [c for c in summary_set['oed_fields']] elif isinstance(summary_set['oed_fields'], str) and len(summary_set['oed_fields']) > 0: return [summary_set['oed_fields']] else: raise OasisException( 'Error processing settings file: "oed_fields" ' 'is expected to be a list of strings, not {}'.format(type(summary_set['oed_fields'])) ) def get_ri_settings(run_dir): """Return the contents of ri_layers.json Example: { "1": { "inuring_priority": 1, "risk_level": "LOC", "directory": " ... /runs/ProgOasis-20190501145127/RI_1" } } Args: run_dir (str): The file path of the model run directory Returns: dict: metadata for the Reinsurance layers """ return get_json(src_fp=os.path.join(run_dir, 'ri_layers.json'))
[docs] def get_ri_inuring_priority_output_levels(run_dir): """Load the mapping from OED InuringPriority to RI output level from the input directory. The mapping is created during input generation and records, for each OED InuringPriority value, the index of the last RI layer (output level) that belongs to that priority. When an InuringPriority spans multiple risk levels (e.g. LOC and ACC), its output level is the highest RI layer index among those risk levels. Args: run_dir (str): Directory containing ``ri_inuring_priority_output_levels.json`` Returns: dict: mapping ``{inuring_priority: output_level}`` with integer keys/values """ raw = get_json(src_fp=os.path.join(run_dir, 'ri_inuring_priority_output_levels.json')) return {int(k): int(v) for k, v in raw.items()}
def write_df_to_csv_file(df, target_dir, filename): """Write a generated summary xref dataframe to disk in csv format. Args: df (pandas.DataFrame): The dataframe output of get_df( .. ) target_dir (str): Abs directory to write a summary_xref file filename (str): Name of output file """ target_dir = as_path(target_dir, 'Input files directory', is_dir=True, preexists=False) pathlib.Path(target_dir).mkdir(parents=True, exist_ok=True) chunksize = min(2 * 10 ** 5, max(len(df), 1000)) csv_fp = os.path.join(target_dir, filename) try: df.to_csv( path_or_buf=csv_fp, encoding='utf-8', mode=('w'), chunksize=chunksize, index=False ) except (IOError, OSError) as e: raise OasisException("Exception raised in 'write_df_to_csv_file'", e) return csv_fp def write_df_to_parquet_file(df, target_dir, filename): """Write a generated summary xref dataframe to disk in parquet format. Args: df (pandas.DataFrame): The dataframe output of get_df( .. ) target_dir (str): Abs directory to write a summary_xref file filename (str): Name of output file """ target_dir = as_path( target_dir, 'Output files directory', is_dir=True, preexists=False ) parquet_fp = os.path.join(target_dir, filename) try: df.to_parquet(path=parquet_fp, engine='pyarrow') except (IOError, OSError) as e: raise OasisException( "Exception raised in 'write_df_to_parquet_file'", e ) return parquet_fp def get_summary_xref_df( map_df, exposure_df, accounts_df, summaries_info_dict, summaries_type, id_set_index='output_id' ): """Create a Dataframe for either gul / il / ri based on a section from the analysis settings Args: map_df (pandas.DataFrame): Summary Map dataframe (GUL / IL) exposure_df (pandas.DataFrame): Location OED data accounts_df (pandas.DataFrame): Accounts OED data id_set_index (str): column of map_df the summary xref ids are taken from summaries_info_dict (list): list of dictionary definition for a summary group from the analysis_settings file, e.g.:: [{ "id": 1, "oed_fields": [], ... }, ... ] summaries_type (str): Text label to use as key in summary description either ['gul', 'il', 'ri'] Returns: summaryxref_df (pandas.DataFrame): Dataframe containing abstracted summary data for ktools summary_desc (dictionary): dictionary of dataFrames listing what summary_ids map to """ summaryxref_df = pd.DataFrame() summary_desc = {} all_cols = set(map_df.columns.to_list() + exposure_df.columns.to_list() + list(calculated_summary_cols.keys())) if isinstance(accounts_df, pd.DataFrame): all_cols.update(accounts_df.columns.to_list()) # Extract the summary id index column depending on id_set_index map_df.sort_values(id_set_index, inplace=True, kind='stable') ids_set_df = map_df.loc[:, [id_set_index]].rename(columns={'output_id': "output"}) # For each granularity build a set grouping for summary_set in summaries_info_dict: summary_set_df = ids_set_df cols_group_by = get_column_selection(summary_set) file_extension = 'csv' if summary_set.get('ord_output', {}).get('parquet_format'): file_extension = 'parquet' desc_key = '{}_S{}_summary-info.{}'.format( summaries_type, summary_set['id'], file_extension ) # an empty intersection means no selected columns from the input data if not set(cols_group_by).intersection(all_cols): # is the intersection empty because the columns don't exist? if set(cols_group_by).difference(all_cols): err_msg = 'Input error: Summary set columns missing from the input files: {}'.format( set(cols_group_by).difference(all_cols)) raise OasisException(err_msg) # Fall back to setting all in single group summary_set_df['summary_id'] = 1 summary_desc[desc_key] = pd.DataFrame(data=['All-Risks'], columns=['_not_set_']) summary_desc[desc_key].insert(loc=0, column='summary_id', value=1) summary_desc[desc_key].insert(loc=len(summary_desc[desc_key].columns), column='tiv', value=map_df.drop_duplicates(['building_id', 'loc_id', 'coverage_type_id'], keep='first').tiv.sum()) else: ( summary_set_df['summary_id'], set_values, tiv_values ) = group_by_oed(cols_group_by, map_df, exposure_df, id_set_index, accounts_df) # Build description file summary_desc_df = pd.DataFrame(data=list(set_values), columns=cols_group_by) summary_desc_df.insert(loc=0, column='summary_id', value=range(1, len(set_values) + 1)) summary_desc[desc_key] = pd.merge(summary_desc_df, tiv_values, left_on=cols_group_by, right_on=cols_group_by) # Appends summary set to '__summaryxref.csv' summary_set_df['summaryset_id'] = summary_set['id'] summaryxref_df = pd.concat([summaryxref_df, summary_set_df.drop_duplicates()], sort=True, ignore_index=True) dtypes = { t: 'uint32' for t in ['coverage_id', 'summary_id', 'summaryset_id'] } summaryxref_df = set_dataframe_column_dtypes(summaryxref_df, dtypes) return summaryxref_df, summary_desc @oasis_log
[docs] def generate_summaryxref_files( location_df, account_df, model_run_fp, analysis_settings, il=False, ri=False, rl=False, intermediary_csv=False ): """Top level function for creating the summaryxref files from the manager.py Args: location_df (pandas.DataFrame): Source locations, joined to the summary map to build the summary groupings and their description files account_df (pandas.DataFrame): Source accounts, joined the same way. Required for the il, ri and rl summary levels model_run_fp (str): Model run directory file path analysis_settings (dict): Model analysis settings file il (bool): Boolean to indicate the insured loss level mode - false if the source accounts file path not provided to Oasis files gen. ri (bool): Boolean to indicate the RI loss level mode - false if the source accounts file path not provided to Oasis files gen. rl (bool): Boolean to indicate the RL loss level mode - false if the source accounts file path not provided to Oasis files gen. intermediary_csv (bool): If True, also write a csv copy of each summaryxref file alongside the binary """ # Boolean checks for summary generation types (gul / il / ri) gul_summaries = all([ analysis_settings.get('gul_output'), analysis_settings.get('gul_summaries'), ]) il_summaries = all([ analysis_settings.get('il_output'), analysis_settings.get('il_summaries'), il, ]) ri_summaries = all([ analysis_settings.get('ri_output'), analysis_settings.get('ri_summaries'), ri, ]) rl_summaries = all([ analysis_settings.get('rl_output'), analysis_settings.get('rl_summaries'), rl, ]) # Verify account file + il_map file il_map_fp = os.path.join(model_run_fp, 'input', SUMMARY_MAPPING['fm_map_fn']) if il_summaries or ri_summaries or rl_summaries: if account_df is None: raise OasisException('No account file found.') if not os.path.exists(il_map_fp): raise OasisException('No summary map file found.') # Load il_map if present if os.path.exists(il_map_fp): il_map_df = get_dataframe( src_fp=il_map_fp, lowercase_cols=False, col_dtypes=MAP_SUMMARY_DTYPES, empty_data_error_msg='No summary map file found.', ) il_map_df = il_map_df[list(set(il_map_df).intersection(MAP_SUMMARY_DTYPES))] if gul_summaries: gul_map_df = il_map_df gul_map_df['item_id'] = gul_map_df['agg_id'] elif gul_summaries: gul_map_fp = os.path.join(model_run_fp, 'input', SUMMARY_MAPPING['gul_map_fn']) gul_map_df = get_dataframe( src_fp=gul_map_fp, lowercase_cols=False, col_dtypes=MAP_SUMMARY_DTYPES, empty_data_error_msg='No summary map file found.', ) gul_map_df = gul_map_df[list(set(gul_map_df).intersection(MAP_SUMMARY_DTYPES))] if gul_summaries: # Load GUL summary map id_set_index = 'item_id' gul_summaryxref_df, gul_summary_desc = get_summary_xref_df( gul_map_df, location_df, account_df, analysis_settings['gul_summaries'], 'gul', id_set_index ) # Write Xref file df_to_ndarray(gul_summaryxref_df, gul_summary_xref_dtype).tofile(os.path.join(model_run_fp, 'input', f"{SUMMARY_OUTPUT['gul']}.bin")) if intermediary_csv: write_df_to_csv_file(gul_summaryxref_df, os.path.join(model_run_fp, 'input'), f"{SUMMARY_OUTPUT['gul']}.csv") # Write summary_id description files for desc_key in gul_summary_desc: if desc_key.split('.')[-1] == 'parquet': write_df_to_parquet_file(gul_summary_desc[desc_key], os.path.join(model_run_fp, 'output'), desc_key) else: write_df_to_csv_file(gul_summary_desc[desc_key], os.path.join(model_run_fp, 'output'), desc_key) if il_summaries: # Load FM summary map il_map_fp = os.path.join(model_run_fp, 'input', SUMMARY_MAPPING['fm_map_fn']) il_map_df = get_dataframe( src_fp=il_map_fp, lowercase_cols=False, col_dtypes=MAP_SUMMARY_DTYPES, empty_data_error_msg='No summary map file found.', ) il_map_df = il_map_df[list(set(il_map_df).intersection(MAP_SUMMARY_DTYPES))] il_summaryxref_df, il_summary_desc = get_summary_xref_df( il_map_df, location_df, account_df, analysis_settings['il_summaries'], 'il' ) # Write Xref file df_to_ndarray(il_summaryxref_df, fm_summary_xref_dtype).tofile(os.path.join(model_run_fp, 'input', f"{SUMMARY_OUTPUT['il']}.bin")) if intermediary_csv: write_df_to_csv_file(il_summaryxref_df, os.path.join(model_run_fp, 'input'), f"{SUMMARY_OUTPUT['il']}.csv") # Write summary_id description files for desc_key in il_summary_desc: if desc_key.split('.')[-1] == 'parquet': write_df_to_parquet_file(il_summary_desc[desc_key], os.path.join(model_run_fp, 'output'), desc_key) else: write_df_to_csv_file(il_summary_desc[desc_key], os.path.join(model_run_fp, 'output'), desc_key) if ri_summaries or rl_summaries: if ('il_summaries' not in analysis_settings) or (not il_summaries): il_map_fp = os.path.join(model_run_fp, 'input', SUMMARY_MAPPING['fm_map_fn']) il_map_df = get_dataframe( src_fp=il_map_fp, lowercase_cols=False, col_dtypes=MAP_SUMMARY_DTYPES, empty_data_error_msg='No summary map file found.', ) il_map_df = il_map_df[list(set(il_map_df).intersection(MAP_SUMMARY_DTYPES))] ri_summaryxref_df, ri_summary_desc = get_summary_xref_df( il_map_df, location_df, account_df, analysis_settings['ri_summaries'], 'ri' ) # Write Xref file for each inuring priority where output has been requested. # analysis_settings['ri_inuring_priorities'] contains OED InuringPriority values; we use the # mapping file (written during input generation) to convert each to the RI output level (last # RI layer index for that OED priority). ri_settings = get_ri_settings(os.path.join(model_run_fp, 'input')) ri_layers = {int(x) for x in ri_settings} inuring_priority_to_output_level = get_ri_inuring_priority_output_levels( os.path.join(model_run_fp, 'input') ) valid_oed_priorities = set(inuring_priority_to_output_level.keys()) max_oed_priority = max(valid_oed_priorities) ri_inuring_priorities_oed = set(int(p) for p in analysis_settings.get('ri_inuring_priorities', [])) ri_inuring_priorities_oed.add(max_oed_priority) # final priority always gets output if not ri_inuring_priorities_oed.issubset(valid_oed_priorities): ri_missing = ri_inuring_priorities_oed.difference(valid_oed_priorities) ri_missing = [str(p) for p in sorted(ri_missing)] missing_str = ', '.join(ri_missing[:-1]) missing_str += ' and ' * (len(ri_missing) > 1) + ri_missing[-1] missing_str = ('priority ' if len(ri_missing) == 1 else 'priorities ') + missing_str raise OasisException(f'Requested outputs for inuring {missing_str} lie outside of scope.') # Convert OED priorities to the RI output levels that should receive summary xref files. # For gross RL output every RI layer is needed; for net RI output only the last layer of # each requested OED priority is needed. if rl_summaries: ri_output_levels = ri_layers else: ri_output_levels = {inuring_priority_to_output_level[p] for p in ri_inuring_priorities_oed} for output_level in ri_output_levels: summary_ri_fp = os.path.join( model_run_fp, 'input', os.path.basename(ri_settings[str(output_level)]['directory'])) df_to_ndarray(ri_summaryxref_df, fm_summary_xref_dtype).tofile(os.path.join(summary_ri_fp, f"{SUMMARY_OUTPUT['il']}.bin")) if intermediary_csv: write_df_to_csv_file(ri_summaryxref_df, summary_ri_fp, f"{SUMMARY_OUTPUT['il']}.csv") # Write summary_id description files output_dir = os.path.join(model_run_fp, 'output') for desc_key in ri_summary_desc: if desc_key.split('.')[-1] == 'parquet': write_df_to_parquet_file(ri_summary_desc[desc_key], output_dir, desc_key) else: write_df_to_csv_file(ri_summary_desc[desc_key], output_dir, desc_key) # Copy the inuring-priority-to-output-level mapping into the output directory so it is # available alongside the results for downstream consumers. pathlib.Path(output_dir).mkdir(parents=True, exist_ok=True) shutil.copyfile( os.path.join(model_run_fp, 'input', 'ri_inuring_priority_output_levels.json'), os.path.join(output_dir, 'ri_inuring_priority_output_levels.json'), )
def get_exposure_summary_field(df, exposure_summary, field_name, field_value, status): """Populate exposure_summary dictionary with the values below grouped by field and status - tiv - number_of_locations - number_of_buildings - number_of_risks Args: df (pandas.DataFrame): dataframe from gul_inputs.get_gul_input_items(..) exposure_summary (dict): input exposure_summary dictionary field_name (str): Name of OED field to add to exposure_summary field_value (str): OED field vlaue to add to exposure_summary status (str): status returned by lookup ('all', 'success', 'fail' or 'nomatch') Returns: dict: populated exposure_summary dictionary """ dedupe_cols_tiv = ['loc_id', 'peril_id'] useful_cols = ['tiv', 'loc_id', 'peril_id', 'coverage_type_id', 'number_of_buildings', 'number_of_risks'] df_field = df.loc[df[field_name] == field_value, useful_cols] for coverage_type in SUPPORTED_COVERAGE_TYPES: df_cov = df_field.loc[df_field['coverage_type_id'] == SUPPORTED_COVERAGE_TYPES[coverage_type]['id']] df_cov = df_cov.drop_duplicates(subset=dedupe_cols_tiv) tiv_sum = float(df_cov['tiv'].sum()) exposure_summary[field_name][field_value][status]['tiv_by_coverage'][coverage_type] = tiv_sum exposure_summary[field_name][field_value][status]['tiv'] += tiv_sum df_num = df_cov.drop_duplicates(subset='loc_id') exposure_summary[field_name][field_value][status]['number_of_locations_by_coverage'][coverage_type] = len(df_num) exposure_summary[field_name][field_value][status]['number_of_buildings_by_coverage'][coverage_type] = int(df_num['number_of_buildings'].sum()) exposure_summary[field_name][field_value][status]['number_of_risks_by_coverage'][coverage_type] = int(df_num['number_of_risks'].sum()) num_df = df_field.drop_duplicates(subset='loc_id') exposure_summary[field_name][field_value][status]['number_of_locations'] = len(num_df['loc_id']) exposure_summary[field_name][field_value][status]['number_of_buildings'] = int(num_df['number_of_buildings'].sum()) exposure_summary[field_name][field_value][status]['number_of_risks'] = int(num_df['number_of_risks'].sum()) return exposure_summary @oasis_log def get_exposure_totals(df): """Return dictionary with total TIVs and number of locations Args: df (pandas.DataFrame): dataframe `df_summary_peril` from `get_exposure_summary` Returns: dict: totals section for exposure_summary dictionary """ dedupe_cols = ['loc_id', 'coverage_type_id'] within_scope_tiv = df[df.status.isin(OASIS_KEYS_STATUS_MODELLED)].drop_duplicates(subset=dedupe_cols)['tiv'].sum() within_scope_num = len(df[df.status.isin(OASIS_KEYS_STATUS_MODELLED)]['loc_id'].unique()) within_scope_num_buildings = int(df[df.status.isin(OASIS_KEYS_STATUS_MODELLED)].drop_duplicates(subset='loc_id')['number_of_buildings'].sum()) within_scope_num_risks = int(df[df.status.isin(OASIS_KEYS_STATUS_MODELLED)].drop_duplicates(subset='loc_id')['number_of_risks'].sum()) outside_scope_tiv = df[~df.status.isin(OASIS_KEYS_STATUS_MODELLED)].drop_duplicates(subset=dedupe_cols)['tiv'].sum() outside_scope_num = len(df[~df.status.isin(OASIS_KEYS_STATUS_MODELLED)]['loc_id'].unique()) outside_scope_num_buildings = int(df[~df.status.isin(OASIS_KEYS_STATUS_MODELLED)].drop_duplicates(subset='loc_id')['number_of_buildings'].sum()) outside_scope_num_risks = int(df[~df.status.isin(OASIS_KEYS_STATUS_MODELLED)].drop_duplicates(subset='loc_id')['number_of_risks'].sum()) portfolio_tiv = df.drop_duplicates(subset=dedupe_cols)['tiv'].sum() portfolio_num = len(df['loc_id'].unique()) portfolio_num_buildings = int(df.drop_duplicates(subset='loc_id')['number_of_buildings'].sum()) portfolio_num_risks = int(df.drop_duplicates(subset='loc_id')['number_of_risks'].sum()) return { "modelled": { "tiv": within_scope_tiv, "number_of_locations": within_scope_num, "number_of_buildings": within_scope_num_buildings, "number_of_risks": within_scope_num_risks, }, "not-modelled": { "tiv": outside_scope_tiv, "number_of_locations": outside_scope_num, "number_of_buildings": outside_scope_num_buildings, "number_of_risks": outside_scope_num_risks, }, "portfolio": { "tiv": portfolio_tiv, "number_of_locations": portfolio_num, "number_of_buildings": portfolio_num_buildings, "number_of_risks": portfolio_num_risks, } } def convert_col_name(col_name): """Convert a column from OED format to exposure summary report format. For example `CountryCode` will be converted to `country_code`. Args: col_name (str): original OED column name Returns: str: exposure summary report field name """ col_list = [col_name[0].lower()] for i, c in enumerate(col_name[1:]): if c.isupper() and not col_name[i].isupper(): col_list += ['_'] col_list += c.lower() return ''.join(col_list) @oasis_log
[docs] def get_exposure_summary( exposure_df, keys_df, exposure_profile=get_default_exposure_profile(), additional_fields=[] ): """Create exposure summary as dictionary of TIVs and number of locations grouped by peril and validity respectively. returns a python dict(). Args: exposure_df (pandas.DataFrame): source exposure dataframe keys_df (pandas.DataFrame): dataFrame holding keys data (success and errors) exposure_profile (dict): profile defining exposure file additional_fields (list): extra exposure columns to group the summary by, on top of loc_id Returns: dict: Exposure summary dictionary """ # get location tivs by coveragetype df_summary = [] exposure_fields = ['loc_id'] exposure_col_names = ['loc_id'] for field_name in additional_fields: if field_name in exposure_df.columns: exposure_fields += [field_name] exposure_col_names += [convert_col_name(field_name)] else: logger.warn(f'exposure summary field not found: {field_name}') for field_name in exposure_profile: if 'FMTermType' in exposure_profile[field_name].keys(): if exposure_profile[field_name]['FMTermType'] == 'TIV' and exposure_profile[field_name]['ProfileElementName'] in exposure_df.columns: cov_name = exposure_profile[field_name]['ProfileElementName'] coverage_type_id = exposure_profile[field_name]['CoverageTypeID'] fields = exposure_fields + [cov_name] column_names = exposure_col_names + ['tiv'] tmp_df = exposure_df[fields].copy() tmp_df.columns = column_names tmp_df['coverage_type_id'] = coverage_type_id # Add number_of_buildings column if 'NumberOfBuildings' in exposure_df: tmp_df['number_of_buildings'] = exposure_df['NumberOfBuildings'] else: tmp_df['number_of_buildings'] = 1 # Add number_of_risks column if 'IsAggregate' in exposure_df: tmp_df['number_of_risks'] = tmp_df['number_of_buildings'] tmp_df.loc[exposure_df['IsAggregate'] == 0, 'number_of_risks'] = 1 else: tmp_df['number_of_risks'] = 1 df_summary.append(tmp_df) df_summary = pd.concat(df_summary) # fix 0 number_of_buildings and risks df_summary = df_summary.replace({'number_of_buildings': 0, 'number_of_risks': 0}, 1) # get all perils peril_list = keys_df['peril_id'].drop_duplicates().to_list() # Initialise sub categories oed_categories = {'peril_id': peril_list} for field_name in exposure_col_names[1:]: # ignore 'loc_id' field_list = df_summary[field_name].drop_duplicates().to_list() oed_categories[field_name] = field_list df_summary_peril = [] for peril_id in peril_list: tmp_df = df_summary.copy() tmp_df['peril_id'] = peril_id df_summary_peril.append(tmp_df) df_summary_peril = pd.concat(df_summary_peril) df_summary_peril = df_summary_peril.merge(keys_df, how='left', on=['loc_id', 'coverage_type_id', 'peril_id']) no_return = OASIS_KEYS_STATUS['noreturn']['id'] df_summary_peril['status'] = df_summary_peril['status'].fillna(no_return) # Compile summary of exposure data exposure_summary = {} # Create totals section exposure_summary['total'] = get_exposure_totals(df_summary_peril) for field_name, field_list in oed_categories.items(): exposure_summary[field_name] = {} for value in field_list: exposure_summary[field_name][value] = {} # Create dictionary structure for all and each validity status for status in ['all'] + list(OASIS_KEYS_STATUS.keys()): exposure_summary[field_name][value][status] = {} exposure_summary[field_name][value][status]['tiv'] = 0.0 exposure_summary[field_name][value][status]['tiv_by_coverage'] = {} exposure_summary[field_name][value][status]['number_of_locations'] = 0 exposure_summary[field_name][value][status]['number_of_locations_by_coverage'] = {} exposure_summary[field_name][value][status]['number_of_buildings'] = 0 exposure_summary[field_name][value][status]['number_of_buildings_by_coverage'] = {} exposure_summary[field_name][value][status]['number_of_risks'] = 0 exposure_summary[field_name][value][status]['number_of_risks_by_coverage'] = {} for status in ['all'] + list(OASIS_KEYS_STATUS.keys()): if status != 'all': df_summary_peril_status = df_summary_peril[df_summary_peril['status'] == status] else: df_summary_peril_status = df_summary_peril.copy() # fill perils exposure_summary for field_name, field_list in oed_categories.items(): for field_value in field_list: exposure_summary = get_exposure_summary_field( df_summary_peril_status, exposure_summary, field_name, field_value, status ) return exposure_summary
@oasis_log def write_gul_errors_map( target_dir, exposure_df, keys_errors_df, exposure_profile, ): """Create csv file to help map keys errors back to original exposures. Args: target_dir (str): directory on disk to write csv file exposure_df (pandas.DataFrame): source exposure dataframe keys_errors_df (pandas.DataFrame): keys errors dataframe exposure_profile (dict): profile defining exposure file """ cols = ['loc_id', 'PortNumber', 'AccNumber', 'LocNumber', 'peril_id', 'coverage_type_id', 'tiv', 'status', 'message'] gul_error_map_fp = os.path.join(target_dir, 'gul_errors_map.csv') exposure_id_cols = ['loc_id', 'PortNumber', 'AccNumber', 'LocNumber'] keys_error_cols = ['loc_id', 'peril_id', 'coverage_type_id', 'status', 'message'] cov_level_id = SUPPORTED_FM_LEVELS['site coverage']['id'] tiv_maps = {term_info['CoverageTypeID']: term_info['ProfileElementName'] for term_info in exposure_profile.values() if (term_info.get('FMTermType') == 'TIV' and term_info.get('FMLevel') == cov_level_id and term_info['ProfileElementName'] in exposure_df.columns)} exposure_cols = list(set(exposure_id_cols + list(tiv_maps.values())).intersection(exposure_df.columns)) keys_errors_df.columns = keys_error_cols gul_inputs_errors_df = exposure_df[exposure_cols].merge(keys_errors_df[keys_error_cols], on=['loc_id']) gul_inputs_errors_df['tiv'] = 0.0 for cov_type in tiv_maps: tiv_field = tiv_maps[cov_type] gul_inputs_errors_df['tiv'] = np.where( gul_inputs_errors_df['coverage_type_id'] == cov_type, gul_inputs_errors_df[tiv_field], gul_inputs_errors_df['tiv'] ) gul_inputs_errors_df['tiv'] = gul_inputs_errors_df['tiv'].fillna(0.0) gul_inputs_errors_df[list(set(cols).intersection(gul_inputs_errors_df.columns))].to_csv(gul_error_map_fp, index=False) @oasis_log
[docs] def write_exposure_summary( target_dir, exposure_df, keys_fp, keys_errors_fp, exposure_profile, additional_fields=[] ): """Create exposure summary as dictionary of TIVs and number of locations grouped by peril and validity respectively. Writes dictionary as json file to disk. Args: target_dir (str): directory on disk to write exposure summary file exposure_df (pandas.DataFrame): source exposure dataframe keys_fp (str): file path to keys file keys_errors_fp (str): file path to keys errors file exposure_profile (dict): profile defining exposure file additional_fields (list[str]): list of additional OED fields to add to exposure summary file Returns: str: Exposure summary file path """ keys_success_df = keys_errors_df = None # get keys success if keys_fp: try: keys_success_df = get_dataframe(src_fp=keys_fp, lowercase_cols=False)[['LocID', 'PerilID', 'CoverageTypeID']] except OasisException: # Assume empty file on read error. keys_success_df = pd.DataFrame(columns=['locid']) else: keys_success_df['status'] = OASIS_KEYS_STATUS['success']['id'] keys_success_df.columns = ['loc_id', 'peril_id', 'coverage_type_id', 'status'] # get keys errors if keys_errors_fp: try: keys_errors_df = get_dataframe(src_fp=keys_errors_fp, lowercase_cols=False)[['LocID', 'PerilID', 'CoverageTypeID', 'Status', 'Message']] except OasisException: # Assume empty file on read error. keys_errors_df = pd.DataFrame(columns=['locid']) else: keys_errors_df.columns = ['loc_id', 'peril_id', 'coverage_type_id', 'status', 'message'] if not keys_errors_df.empty: write_gul_errors_map(target_dir, exposure_df, keys_errors_df, exposure_profile) # concatinate keys responses & run df_keys = pd.concat([keys_success_df, keys_errors_df]) exposure_summary = get_exposure_summary(exposure_df, df_keys, exposure_profile, additional_fields) # write exposure summary as json fileV fp = os.path.join(target_dir, 'exposure_summary_report.json') with io.open(fp, 'w', encoding='utf-8') as f: f.write(json.dumps(exposure_summary, ensure_ascii=False, indent=4)) return fp