From 91c4120906d0b51f06fc7e4b7342ab7922f128c1 Mon Sep 17 00:00:00 2001 From: EllieKallmier <61219730+EllieKallmier@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:59:05 +1000 Subject: [PATCH] add templating new entrant LCFs --- src/ispypsa/iasr_table_caching/local_cache.py | 2 + src/ispypsa/templater/new_entrants.py | 267 ++++++++++++-- .../schemas/generators_new_entrant.yaml | 2 +- .../schemas/storage_new_entrant.yaml | 3 +- .../test_local_cache.py | 3 + .../test_create_ispypsa_inputs_template.py | 50 ++- tests/test_templater/test_new_entrants.py | 340 +++++++++++++++--- .../7.5/locational_cost_factors.csv | 65 ++++ .../7.5/technology_specific_lcfs.csv | 65 ++++ 9 files changed, 685 insertions(+), 112 deletions(-) create mode 100644 tests/test_workbook_table_cache/7.5/locational_cost_factors.csv create mode 100644 tests/test_workbook_table_cache/7.5/technology_specific_lcfs.csv diff --git a/src/ispypsa/iasr_table_caching/local_cache.py b/src/ispypsa/iasr_table_caching/local_cache.py index a3f59c94..6c1faea4 100644 --- a/src/ispypsa/iasr_table_caching/local_cache.py +++ b/src/ispypsa/iasr_table_caching/local_cache.py @@ -60,6 +60,8 @@ def _build_required_tables(iasr_workbook_version: str) -> list[str]: "gpg_min_stable_level_new_entrants", "battery_properties", "pumped_hydro_new_entrant_properties", + "technology_specific_lcfs", + "locational_cost_factors", ] + augmentation else: diff --git a/src/ispypsa/templater/new_entrants.py b/src/ispypsa/templater/new_entrants.py index 5be81b68..73bcabf4 100644 --- a/src/ispypsa/templater/new_entrants.py +++ b/src/ispypsa/templater/new_entrants.py @@ -17,7 +17,13 @@ into battery and pumped-hydro (PHES) rows, which take their storage-specific properties from different IASR tables, then recombines them before merging the common properties. - 6. Selects the table's schema columns. + 6. Merges in the two locational cost factors: lcf_build (per geo_id + technology) + and lcf_om (per geo_id alone) — see _merge_lcf_build and _merge_lcf_om. + 7. Selects the table's schema columns. + +Note: lcf_build is taken directly from IASR's precomputed technology_specific_lcfs table, +rather than recomputed from the more granular cost-component IASR tables at this +stage. """ import logging @@ -51,6 +57,8 @@ _GENERATOR_PROPERTY_COLUMNS = [ "fom", "vom", + "lcf_build", + "lcf_om", "lifetime_technical", "lifetime_economic", "heat_rate", @@ -73,6 +81,8 @@ "soc_max", "soc_min", "minimum_stable_level", + "lcf_build", + "lcf_om", "lifetime_technical", "lifetime_economic", "degradation_annual", @@ -120,11 +130,18 @@ _BOTN_CETHANA_DETAILS["full_name"]: _BOTN_CETHANA_DETAILS["name"] } +# Typo(?) in 'Regional build cost zone' for PHES rows in NSA subregion: see Open-ISP/ISPyPSA#131. +_KNOWN_BUILD_COST_ZONE_TYPOS = { + ("NSA", "CSA"), # (geo_id, Regional build cost zone) +} + +# Data scale diff for 'BOTN - Cethana' in LCF table: see first comment on Open-ISP/ISPyPSA#131. +_LCF_COLUMNS_IN_PERCENT = ["BOTN - Cethana"] + # --- public orchestrators --- -# NOTE: partial scope intentional - lcf_* columns added in a later PR! def _template_generators_new_entrant( iasr_tables: dict[str, pd.DataFrame], ) -> pd.DataFrame: @@ -133,8 +150,9 @@ def _template_generators_new_entrant( Keeps only generator rows, renames the carried-over summary columns to schema names, derives geo_id (REZ ID or sub-region) and resource_type (from the VRE resource code in the IASR ID), merges in the per-technology property columns - (see ``_GENERATORS_NEW_ENTRANT_PROPERTY_MAP``), and returns the identity + - property columns. + (see ``_GENERATORS_NEW_ENTRANT_PROPERTY_MAP``) and the two locational cost factors + (``lcf_build`` per geo_id+technology, ``lcf_om`` per geo_id), and returns the + identity + property columns. Args: iasr_tables: IASR tables; uses ``new_entrants_summary`` plus the property @@ -163,10 +181,12 @@ def _template_generators_new_entrant( gens = _set_geo_id(gens) gens = _add_resource_type(gens) gens = _merge_properties(gens, iasr_tables, _GENERATORS_NEW_ENTRANT_PROPERTY_MAP) + _assert_build_cost_zone_matches_geo_id(gens) + gens = _merge_lcf_build(gens, iasr_tables["technology_specific_lcfs"]) + gens = _merge_lcf_om(gens, iasr_tables["locational_cost_factors"]) return gens[_GENERATOR_IDENTITY_COLUMNS + _GENERATOR_PROPERTY_COLUMNS] -# NOTE: partial scope intentional - lcf_* columns added in a later PR! def _template_storage_new_entrant( iasr_tables: dict[str, pd.DataFrame], ) -> pd.DataFrame: @@ -176,7 +196,9 @@ def _template_storage_new_entrant( and derives geo_id (REZ ID or sub-region). Battery and pumped-hydro (PHES) rows draw their storage-specific properties from different IASR tables, so each subset is merged separately and recombined; the shared properties (see - ``_COMMON_NEW_ENTRANT_PROPERTY_MAP``) are then merged onto the combined set. + ``_COMMON_NEW_ENTRANT_PROPERTY_MAP``) and the two locational cost factors + (``lcf_build`` per geo_id+technology, ``lcf_om`` per geo_id) are then merged onto the + combined set. Args: iasr_tables: IASR tables; uses ``new_entrants_summary`` plus the property tables @@ -209,6 +231,9 @@ def _template_storage_new_entrant( ) storage = pd.concat([batteries, phes], ignore_index=True) storage = _merge_properties(storage, iasr_tables, _COMMON_NEW_ENTRANT_PROPERTY_MAP) + _assert_build_cost_zone_matches_geo_id(storage) + storage = _merge_lcf_build(storage, iasr_tables["technology_specific_lcfs"]) + storage = _merge_lcf_om(storage, iasr_tables["locational_cost_factors"]) return storage[_STORAGE_IDENTITY_COLUMNS + _STORAGE_PROPERTY_COLUMNS] @@ -241,7 +266,12 @@ def _merge_properties( property_map ).items(): table = iasr_tables[table_name] - _assert_property_table_attrs(table, table_name, props) + _assert_table_valid( + table, + table_name, + _required_property_columns(props), + f"{sorted(props.keys())}", + ) matched_technology = _fuzzy_map_to_allowed_values( new_entrants["technology"], table[technology_col], @@ -282,6 +312,20 @@ def _group_by_source_key(property_map: dict[str, dict]) -> dict[tuple[str, str], return groups +def _required_property_columns(props: dict[str, dict]) -> set[str]: + """Returns every ``value_col``/``technology_col`` named across a source's properties. + + I/O Example: + props: + fom: {table: fixed_opex_new_entrants, technology_col: Technology, value_col: Base value} + vom: {table: variable_opex_new_entrants, technology_col: Generator, value_col: Base value} + + returns: + {"Technology", "Generator", "Base value"} + """ + return {d[col] for col in ["value_col", "technology_col"] for d in props.values()} + + def _get_property_value_map( table: pd.DataFrame, attrs: dict[str, str | float] ) -> pd.Series: @@ -310,26 +354,25 @@ def _get_property_value_map( return value_map -def _assert_property_table_attrs( - table: pd.DataFrame, table_name: str, attrs: dict[str, dict] +def _assert_table_valid( + table: pd.DataFrame, table_name: str, required_cols: set[str], merge_desc: str ) -> None: - """Asserts a property table has all required columns and isn't empty. + """Asserts a source table has every required column and isn't empty. - Guards against two ways a property table can silently break the downstream - merge: missing required columns, or has no rows. Checks every property sourced - from ``table`` at once (``attrs`` is one ``_group_by_source_key`` group), so a - single table failure is reported once, naming every property it would have fed. + Shared precondition check for every IASR table merged in this module — guards + against two silent-failure modes: a missing column producing a KeyError, and + an empty table merges to an all-NaN column with no warning. Args: - table: the property table to validate, e.g. ``iasr_tables["battery_properties"]``. - table_name: ``table``'s IASR table name, used only to name it in error messages. - attrs: every property sourced from ``table``, keyed by property name — each - value is that property's entry from a property map (e.g. - ``_STORAGE_BATTERY_PROPERTY_MAP["storage_hours"]``). + table: the source table to validate, e.g. ``iasr_tables["battery_properties"]``. + table_name: ``table``'s IASR table name, used to name it in error messages. + required_cols: every column the downstream merge reads from ``table``. + merge_desc: short description of what would be merged, named in the + empty-table error, e.g. ``"properties '['fom']'"`` or ``"'lcf_build'"``. Raises: - ValueError: if ``table`` is missing any property's ``technology_col``/ - ``value_col``, or if ``table`` has no rows. + ValueError: if any of ``required_cols`` is missing from ``table``, or if + ``table`` has no rows. I/O Example: table: @@ -337,27 +380,18 @@ def _assert_property_table_attrs( Wind 2.0 unused_info table_name: "fixed_opex_new_entrants" - attrs: { - fom: {table: fixed_opex_new_entrants, technology_col: Technology, value_col: Base value} - } + required_cols: {"Technology", "Base value"} + merge_desc: "properties '['fom']'" - # No ValueError raised: table has rows, both expected columns (Technology, - # Base value) present. + # No ValueError raised: table has rows, both required columns present. """ - required_cols = { - d[col_name] - for col_name in ["value_col", "technology_col"] - for d in attrs.values() - } missing_cols = required_cols - set(table.columns) if missing_cols: raise ValueError( f"'{table_name}' table missing required columns: {sorted(missing_cols)}" ) if table.empty: - raise ValueError( - f"'{table_name}' table is empty - cannot merge properties '{sorted(attrs.keys())}'" - ) + raise ValueError(f"'{table_name}' table is empty - cannot merge {merge_desc}") def _set_geo_id(new_entrants: pd.DataFrame) -> pd.DataFrame: @@ -370,6 +404,171 @@ def _set_geo_id(new_entrants: pd.DataFrame) -> pd.DataFrame: return new_entrants +# --- locational cost factor (LCF) helpers --- + + +def _assert_build_cost_zone_matches_geo_id(new_entrants: pd.DataFrame) -> None: + """Asserts the LCF lookup key (geo_id) matches the IASR's 'Regional build cost zone'. + + LCFs are keyed on geo_id here, which equals each unit's cost zone. v7.5 (and 7.8) breaks + that rule for three NSA pumped-hydro rows, mislabelling them with the CSA cost zone + (``_KNOWN_BUILD_COST_ZONE_TYPOS``); we accept that known typo and key on geo_id (NSA) + regardless. Any *other* divergence is unexpected — possibly a real cost-zone split + rather than a typo — so this function raises if any other diffs are seen. + + Raises: + ValueError: if geo_id and 'Regional build cost zone' diverge for any + (geo_id, cost zone) pair not in ``_KNOWN_BUILD_COST_ZONE_TYPOS``. + """ + divergent = new_entrants[ + new_entrants["geo_id"] != new_entrants["Regional build cost zone"] + ] + unexpected = ( + set(zip(divergent["geo_id"], divergent["Regional build cost zone"])) + - _KNOWN_BUILD_COST_ZONE_TYPOS + ) + if unexpected: + raise ValueError( + "Unexpected divergence between geo_id and 'Regional build cost zone' in " + f"new_entrants_summary: {sorted(unexpected, key=str)}." + ) + + +def _merge_lcf_build( + new_entrants: pd.DataFrame, technology_specific_lcfs: pd.DataFrame +) -> pd.DataFrame: + """Merges the build/connection locational cost factor (``lcf_build``, %) per unit. + + Looks up each unit's precomputed build/connection locational cost factor (LCF) from + ``technology_specific_lcfs``, merged on (geo_id, technology). Units with no entry for + their (geo_id, technology) pair get NaN (a default is applied downstream — see the + schema ``nan_fill``). + + I/O Example: + new_entrants (BOTN 'technology' already overridden): + name technology geo_id + Q1_WH_Far North QLD Wind Q1 + NQ OCGT Small OCGT (small GT) NQ + BOTN - Cethana - 20h BOTN - Cethana TAS + + technology_specific_lcfs: + Cost zone / REZ ID REZ name / Description Wind BOTN - Cethana + Q1 Far North QLD 1.0860 Not Applicable + TAS Subregional Ref Node 1.0325 100 + + returns (adds lcf_build): + name ... lcf_build + Q1_WH_Far North QLD ... 108.60 + NQ OCGT Small ... 108.01 + BOTN - Cethana - 20h ... 100.0 + """ + lcf_by_geo_id_and_technology = _reshape_technology_specific_lcfs( + technology_specific_lcfs + ) + new_entrants = new_entrants.copy() + new_entrants["lcf_technology"] = _fuzzy_map_to_allowed_values( + new_entrants["technology"], + lcf_by_geo_id_and_technology["lcf_technology"].unique(), + task_desc="merging new entrant 'lcf_build' by technology", + ) + return new_entrants.merge( + lcf_by_geo_id_and_technology, how="left", on=["geo_id", "lcf_technology"] + ).drop(columns="lcf_technology") + + +def _merge_lcf_om( + new_entrants: pd.DataFrame, locational_cost_factors: pd.DataFrame +) -> pd.DataFrame: + """Merges the O&M locational cost factor (``lcf_om``, %) per unit, looked up by geo_id. + + The O&M LCF is a single per-zone factor (technology-independent), already a percentage, + held in the ``O&M costs 3`` column of ``locational_cost_factors``. Units whose geo_id + has no entry get NaN (a 100% default is applied downstream). + + Raises: + ValueError: if 'O&M costs 3' column contains anything ``pd.to_numeric`` can't parse, + e.g. a stray typo in the IASR table. + + I/O Example: + new_entrants: + name geo_id + Q1_WH_Far North QLD Q1 + NQ OCGT Small NQ + + locational_cost_factors (relevant cols): + Cost zone / REZ ID O&M costs 3 + Q1 122.27 + NQ 114.997 + + returns (adds lcf_om): + name geo_id lcf_om + Q1_WH_Far North QLD Q1 122.27 + NQ OCGT Small NQ 114.997 + """ + # "Cost zone / REZ ID" and "O&M costs 3": literal v7.5 IASR workbook column names. + zone_col = "Cost zone / REZ ID" + om_col = "O&M costs 3" + _assert_table_valid( + locational_cost_factors, + "locational_cost_factors", + {zone_col, om_col}, + "'lcf_om'", + ) + om_by_geo_id = pd.to_numeric( + locational_cost_factors.set_index(zone_col)[om_col], errors="raise" + ) + new_entrants = new_entrants.copy() + new_entrants["lcf_om"] = new_entrants["geo_id"].map(om_by_geo_id) + return new_entrants + + +def _reshape_technology_specific_lcfs( + technology_specific_lcfs: pd.DataFrame, +) -> pd.DataFrame: + """Reshapes the wide ``technology_specific_lcfs`` table to long (geo_id, technology, %). + + Each source row is a cost zone (a REZ ID or sub-region — i.e. a geo_id) and each column + after the description is a technology's precomputed LCF. Factors are converted to + percentages (×100), except the bespoke columns already published as percentages + (``_LCF_COLUMNS_IN_PERCENT``). "Not Applicable"/blank cells become NaN and are dropped, + so only fully defined (geo_id, technology) pairs remain. + + I/O Example: + technology_specific_lcfs: + Cost zone / REZ ID REZ name / Description Wind BOTN - Cethana + Q1 Far North QLD 1.0860 Not Applicable + TAS Subregional Ref Node 1.0325 100 + + returns: + geo_id lcf_technology lcf_build + Q1 Wind 108.60 # factor ×100 + TAS Wind 103.25 + TAS BOTN - Cethana 100.0 # already % -> left unscaled + """ + # "Cost zone / REZ ID" / "REZ name / Description": literal v7.5 IASR column names. + zone_col = "Cost zone / REZ ID" + description_col = "REZ name / Description" + _assert_table_valid( + technology_specific_lcfs, + "technology_specific_lcfs", + {zone_col, description_col}, + "'lcf_build'", + ) + technology_cols = technology_specific_lcfs.columns.difference( + [zone_col, description_col] + ) + long = technology_specific_lcfs.melt( + id_vars=zone_col, + value_vars=list(technology_cols), + var_name="lcf_technology", + value_name="lcf_build", + ).rename(columns={zone_col: "geo_id"}) + long["lcf_build"] = pd.to_numeric(long["lcf_build"], errors="coerce") + in_factor_form = ~long["lcf_technology"].isin(_LCF_COLUMNS_IN_PERCENT) + long.loc[in_factor_form, "lcf_build"] *= 100 + return long.dropna(subset="lcf_build").reset_index(drop=True) + + # --- storage-specific helpers --- diff --git a/src/ispypsa/validation/schemas/generators_new_entrant.yaml b/src/ispypsa/validation/schemas/generators_new_entrant.yaml index 6d5ffc24..e115bd17 100644 --- a/src/ispypsa/validation/schemas/generators_new_entrant.yaml +++ b/src/ispypsa/validation/schemas/generators_new_entrant.yaml @@ -18,7 +18,7 @@ description: > - `lead_time_and_project_life` - `gpg_min_stable_level_new_entrants` - `locational_cost_factors` - - `technology_cost_breakdown_ratios` + - `technology_specific_lcfs` If absent: Only existing (and planned) generation will be modelled. diff --git a/src/ispypsa/validation/schemas/storage_new_entrant.yaml b/src/ispypsa/validation/schemas/storage_new_entrant.yaml index 0397210c..f3e866fc 100644 --- a/src/ispypsa/validation/schemas/storage_new_entrant.yaml +++ b/src/ispypsa/validation/schemas/storage_new_entrant.yaml @@ -22,8 +22,7 @@ description: > - `gpg_min_stable_level_new_entrants` - `lead_time_and_project_life` - `locational_cost_factors` - - `locational_cost_pumped_hydro_factors` - - `technology_cost_breakdown_ratios` + - `technology_specific_lcfs` If absent: Only existing or planned storage will be modelled. diff --git a/tests/test_iasr_table_caching/test_local_cache.py b/tests/test_iasr_table_caching/test_local_cache.py index 09a736d5..91bd54d8 100644 --- a/tests/test_iasr_table_caching/test_local_cache.py +++ b/tests/test_iasr_table_caching/test_local_cache.py @@ -38,6 +38,9 @@ def test_build_required_tables_new_format(): # Storage property tables merged into the new entrant storage template assert "battery_properties" in result assert "pumped_hydro_new_entrant_properties" in result + # Locational cost factor tables feed lcf_build / lcf_om in both new entrant templates + assert "technology_specific_lcfs" in result + assert "locational_cost_factors" in result def test_build_required_tables_old_format(): diff --git a/tests/test_templater/test_create_ispypsa_inputs_template.py b/tests/test_templater/test_create_ispypsa_inputs_template.py index bd33415a..1c9c3679 100644 --- a/tests/test_templater/test_create_ispypsa_inputs_template.py +++ b/tests/test_templater/test_create_ispypsa_inputs_template.py @@ -116,6 +116,20 @@ def _new_entrant_property_tables(csv_str_to_df) -> dict[str, pd.DataFrame]: Pumped Hydro (24hrs storage), 24, 76 BOTN - Cethana - 20h, 20, 80 """), + "technology_specific_lcfs": csv_str_to_df(""" + Cost zone / REZ ID, REZ name / Description, Wind, Large scale Solar PV, OCGT (small GT), Pumped Hydro (24hrs storage), BOTN - Cethana + Q1, Far North QLD, 1.05, 1.08, Not Applicable, Not Applicable, Not Applicable + CNSW, Subregional Ref Node, Not Applicable, Not Applicable, 1.04, Not Applicable, Not Applicable + SNW, Subregional Ref Node, Not Applicable, Not Applicable, 1.00, Not Applicable, Not Applicable + TAS, Subregional Ref Node, Not Applicable, Not Applicable, Not Applicable, 1.0469, 100 + """), + "locational_cost_factors": csv_str_to_df(""" + Cost zone / REZ ID, O&M costs 3 + Q1, 122.0 + CNSW, 110.0 + SNW, 100.0 + TAS, 100.0 + """), } @@ -292,12 +306,12 @@ def test_create_ispypsa_inputs_template_new_format(csv_str_to_df): IBR, 10 """) new_entrants_summary = csv_str_to_df(""" - IASR ID / DLT names, Technology Type, Fuel type, Fuel cost mapping, REZ ID, Sub-region - Q1_WH_Far North QLD, Wind, Wind, Wind, Q1, NQ - Q1_SAT_Far North QLD, Large scale Solar PV, Solar, Solar, Q1, NQ - CNSW OCGT Small, OCGT (small GT), Gas, NSW new OCGT, Not Applicable, CNSW - SNW OCGT Small, OCGT (small GT), Gas, NSW new OCGT, Not Applicable, SNW - BOTN - Cethana - 20h, Pumped Hydro (24hrs storage), Water, Hydro, Not Applicable, TAS + IASR ID / DLT names, Technology Type, Fuel type, Fuel cost mapping, REZ ID, Sub-region, Regional build cost zone + Q1_WH_Far North QLD, Wind, Wind, Wind, Q1, NQ, Q1 + Q1_SAT_Far North QLD, Large scale Solar PV, Solar, Solar, Q1, NQ, Q1 + CNSW OCGT Small, OCGT (small GT), Gas, NSW new OCGT, Not Applicable, CNSW, CNSW + SNW OCGT Small, OCGT (small GT), Gas, NSW new OCGT, Not Applicable, SNW, SNW + BOTN - Cethana - 20h, Pumped Hydro (24hrs storage), Water, Hydro, Not Applicable, TAS, TAS """) with ( @@ -488,12 +502,12 @@ def test_create_ispypsa_inputs_template_new_format_nem_regions(csv_str_to_df): IBR, 10 """) new_entrants_summary = csv_str_to_df(""" - IASR ID / DLT names, Technology Type, Fuel type, Fuel cost mapping, REZ ID, Sub-region - Q1_WH_Far North QLD, Wind, Wind, Wind, Q1, NQ - Q1_SAT_Far North QLD, Large scale Solar PV, Solar, Solar, Q1, NQ - CNSW OCGT Small, OCGT (small GT), Gas, NSW new OCGT, Not Applicable, CNSW - SNW OCGT Small, OCGT (small GT), Gas, NSW new OCGT, Not Applicable, SNW - BOTN - Cethana - 20h, Pumped Hydro (24hrs storage), Water, Hydro, Not Applicable, TAS + IASR ID / DLT names, Technology Type, Fuel type, Fuel cost mapping, REZ ID, Sub-region, Regional build cost zone + Q1_WH_Far North QLD, Wind, Wind, Wind, Q1, NQ, Q1 + Q1_SAT_Far North QLD, Large scale Solar PV, Solar, Solar, Q1, NQ, Q1 + CNSW OCGT Small, OCGT (small GT), Gas, NSW new OCGT, Not Applicable, CNSW, CNSW + SNW OCGT Small, OCGT (small GT), Gas, NSW new OCGT, Not Applicable, SNW, SNW + BOTN - Cethana - 20h, Pumped Hydro (24hrs storage), Water, Hydro, Not Applicable, TAS, TAS """) with ( @@ -645,12 +659,12 @@ def test_create_ispypsa_inputs_template_new_format_single_region(csv_str_to_df): IBR, 10 """) new_entrants_summary = csv_str_to_df(""" - IASR ID / DLT names, Technology Type, Fuel type, Fuel cost mapping, REZ ID, Sub-region - Q1_WH_Far North QLD, Wind, Wind, Wind, Q1, NQ - Q1_SAT_Far North QLD, Large scale Solar PV, Solar, Solar, Q1, NQ - CNSW OCGT Small, OCGT (small GT), Gas, NSW new OCGT, Not Applicable, CNSW - SNW OCGT Small, OCGT (small GT), Gas, NSW new OCGT, Not Applicable, SNW - BOTN - Cethana - 20h, Pumped Hydro (24hrs storage), Water, Hydro, Not Applicable, TAS + IASR ID / DLT names, Technology Type, Fuel type, Fuel cost mapping, REZ ID, Sub-region, Regional build cost zone + Q1_WH_Far North QLD, Wind, Wind, Wind, Q1, NQ, Q1 + Q1_SAT_Far North QLD, Large scale Solar PV, Solar, Solar, Q1, NQ, Q1 + CNSW OCGT Small, OCGT (small GT), Gas, NSW new OCGT, Not Applicable, CNSW, CNSW + SNW OCGT Small, OCGT (small GT), Gas, NSW new OCGT, Not Applicable, SNW, SNW + BOTN - Cethana - 20h, Pumped Hydro (24hrs storage), Water, Hydro, Not Applicable, TAS, TAS """) with ( diff --git a/tests/test_templater/test_new_entrants.py b/tests/test_templater/test_new_entrants.py index d5dc79f4..eada5233 100644 --- a/tests/test_templater/test_new_entrants.py +++ b/tests/test_templater/test_new_entrants.py @@ -8,13 +8,18 @@ _STORAGE_PROPERTY_COLUMNS, _add_resource_type, _assert_botn_technology_expected, - _assert_property_table_attrs, + _assert_build_cost_zone_matches_geo_id, + _assert_table_valid, _derive_phes_symmetric_efficiency, _group_by_source_key, + _merge_lcf_build, + _merge_lcf_om, _merge_phes_properties, _merge_properties, _normalise_phes_botn_key, _override_botn_technology, + _required_property_columns, + _reshape_technology_specific_lcfs, _set_geo_id, _template_generators_new_entrant, _template_storage_new_entrant, @@ -28,10 +33,10 @@ def test_template_generators_new_entrant(csv_str_to_df): # and the identity + property columns are produced, one row per generating unit. # Detailed content is covered by the per-helper tests. new_entrants_summary = csv_str_to_df(""" - IASR ID / DLT names, Technology Type, Fuel type, Fuel cost mapping, REZ ID, Sub-region - Q1_WH_Far North QLD, Wind, Wind, Wind, Q1, NQ - NQ OCGT Small, OCGT (small GT), Gas, QLD new OCGT, Not Applicable, NQ - NQ Battery 2hrs, Battery Storage (2hrs storage), Battery, Battery, Not Applicable, NQ + IASR ID / DLT names, Technology Type, Fuel type, Fuel cost mapping, REZ ID, Sub-region, Regional build cost zone + Q1_WH_Far North QLD, Wind, Wind, Wind, Q1, NQ, Q1 + NQ OCGT Small, OCGT (small GT), Gas, QLD new OCGT, Not Applicable, NQ, NQ + NQ Battery 2hrs, Battery Storage (2hrs storage), Battery, Battery, Not Applicable, NQ, NQ """) iasr_tables = { "new_entrants_summary": new_entrants_summary, @@ -60,6 +65,16 @@ def test_template_generators_new_entrant(csv_str_to_df): Wind, 0.0 OCGT (small GT), 50.0 """), + "technology_specific_lcfs": csv_str_to_df(""" + Cost zone / REZ ID, REZ name / Description, Wind, OCGT (small GT) + Q1, Far North QLD, 1.05, Not Applicable + NQ, Subregional Ref Node, Not Applicable, 1.08 + """), + "locational_cost_factors": csv_str_to_df(""" + Cost zone / REZ ID, O&M costs 3 + Q1, 122.0 + NQ, 115.0 + """), } result = _template_generators_new_entrant(iasr_tables) @@ -106,6 +121,16 @@ def _storage_property_tables(csv_str_to_df): Pumped Hydro (24hrs storage), 40.0 BOTN - Cethana, 40.0 """), + "technology_specific_lcfs": csv_str_to_df(""" + Cost zone / REZ ID, REZ name / Description, Battery storage (2hrs storage), Distributed Resources Batteries, BOTN - Cethana + N3, Central-West Orana, 1.04, Not Applicable, Not Applicable + NQ, Subregional Ref Node, Not Applicable, 1.06, 100 + """), + "locational_cost_factors": csv_str_to_df(""" + Cost zone / REZ ID, O&M costs 3 + N3, 119.0 + NQ, 115.0 + """), } @@ -115,12 +140,12 @@ def test_template_storage_new_entrant(csv_str_to_df): # storage unit (battery + PHES) is returned. Detailed content is covered by the # per-helper tests. new_entrants_summary = csv_str_to_df(""" - IASR ID / DLT names, Technology Type, Fuel type, Fuel cost mapping, REZ ID, Sub-region - Q1_WH_Far North QLD, Wind, Wind, Wind, Q1, NQ - NQ OCGT Small, OCGT (small GT), Gas, QLD new OCGT, Not Applicable, NQ - NQ Battery 2hrs, Battery Storage (2hrs storage), Battery, Battery, N3, NQ - NQ Battery - Distributed, Distributed Resources Batteries, Battery, Battery, Not Applicable, NQ - BOTN - Cethana - 20h, Pumped Hydro (24hrs storage), Water, Hydro, Not Applicable, NQ + IASR ID / DLT names, Technology Type, Fuel type, Fuel cost mapping, REZ ID, Sub-region, Regional build cost zone + Q1_WH_Far North QLD, Wind, Wind, Wind, Q1, NQ, Q1 + NQ OCGT Small, OCGT (small GT), Gas, QLD new OCGT, Not Applicable, NQ, NQ + NQ Battery 2hrs, Battery Storage (2hrs storage), Battery, Battery, N3, NQ, N3 + NQ Battery - Distributed, Distributed Resources Batteries, Battery, Battery, Not Applicable, NQ, NQ + BOTN - Cethana - 20h, Pumped Hydro (24hrs storage), Water, Hydro, Not Applicable, NQ, NQ """) iasr_tables = { "new_entrants_summary": new_entrants_summary, @@ -134,38 +159,13 @@ def test_template_storage_new_entrant(csv_str_to_df): assert len(result) == 3 -# --- _assert_property_table_attrs --- - - -def test_assert_property_table_attrs_valid_table(csv_str_to_df): - # Table has both required columns and at least one row - no error raised. - table = csv_str_to_df(""" - Technology, Base value - Wind, 20.0 - """) - attrs = { - "fom": { - "table": "fixed_opex_new_entrants", - "technology_col": "Technology", - "value_col": "Base value", - "scale": 1000.0, - } - } - # should not raise - _assert_property_table_attrs(table, "fixed_opex_new_entrants", attrs) +# --- _required_property_columns --- -def test_assert_property_table_attrs_raises_missing_columns(csv_str_to_df): - # Table is missing technology_col - raised message names the source table, - # and the missing columns - including the 'Storage Hours' column with different - # capitalisation to expected 'Storage hours'. - # Two properties share the source table - both missing columns are reported - # together in one raise. - table = csv_str_to_df(""" - Technology, Storage Hours - Battery (2h), 2 - """) - attrs = { +def test_required_property_columns(): + # Two properties sharing a source - both properties' value_col/technology_col + # are collected into one set. + props = { "storage_hours": { "table": "battery_properties", "technology_col": "Technology", @@ -178,32 +178,58 @@ def test_assert_property_table_attrs_raises_missing_columns(csv_str_to_df): }, } + result = _required_property_columns(props) + + assert result == {"Technology", "Storage hours", "Variable value"} + + +# --- _assert_table_valid --- + + +def test_assert_table_valid_passes(csv_str_to_df): + # Table has both required columns and at least one row - no error raised. + table = csv_str_to_df(""" + Technology, Base value + Wind, 20.0 + """) + # should not raise + _assert_table_valid( + table, "fixed_opex_new_entrants", {"Technology", "Base value"}, "'fom'" + ) + + +def test_assert_table_valid_raises_missing_columns(csv_str_to_df): + # Table is missing a required column -> raise, naming the table and the column. + table = csv_str_to_df(""" + Technology, Base value + Wind, 20.0 + """) + with pytest.raises( ValueError, - match=r"'battery_properties' table missing required columns: " - r"\['Storage hours', 'Variable value'\]", + match=r"'fixed_opex_new_entrants' table missing required columns: " + r"\['Storage hours'\]", ): - _assert_property_table_attrs(table, "battery_properties", attrs) + _assert_table_valid( + table, + "fixed_opex_new_entrants", + {"Technology", "Storage hours"}, + "'fom'", + ) -def test_assert_property_table_attrs_raises_empty_table(): - # Table has both required columns but no rows - raise, naming every property - # sourced from the table. +def test_assert_table_valid_raises_empty_table(): + # Table has both required columns but no rows -> raise, naming what would + # have been merged. table = pd.DataFrame(columns=["Technology", "Base value"]) - attrs = { - "fom": { - "table": "fixed_opex_new_entrants", - "technology_col": "Technology", - "value_col": "Base value", - "scale": 1000.0, - } - } with pytest.raises( ValueError, - match=r"'fixed_opex_new_entrants' table is empty - cannot merge properties '\['fom'\]'", + match=r"'fixed_opex_new_entrants' table is empty - cannot merge 'fom'", ): - _assert_property_table_attrs(table, "fixed_opex_new_entrants", attrs) + _assert_table_valid( + table, "fixed_opex_new_entrants", {"Technology", "Base value"}, "'fom'" + ) # --- _group_by_source_key --- @@ -305,6 +331,28 @@ def test_merge_properties(csv_str_to_df, caplog): assert caplog.messages.count(msg) == 1 +def test_merge_properties_raises_on_invalid_source_table(csv_str_to_df): + # Regression: confirms the source table is actually validated before merging. + # Exact raise behaviour is covered by _assert_table_valid's own tests. + new_entrants = csv_str_to_df(""" + name, technology + SQ CCGT, CCGT + """) + property_map = { + "fom": { + "table": "fixed_opex_new_entrants", + "technology_col": "Technology", + "value_col": "Base value", + } + } + iasr_tables = { + "fixed_opex_new_entrants": pd.DataFrame(columns=["Technology", "Base value"]), + } + + with pytest.raises(ValueError): + _merge_properties(new_entrants, iasr_tables, property_map) + + # --- _merge_phes_properties / _override_botn_technology / _derive_phes_symmetric_efficiency --- @@ -522,3 +570,181 @@ def test_add_resource_type_empty_input(): expected = pd.DataFrame(columns=["name", "technology", "resource_type"]) pd.testing.assert_frame_equal(result, expected) + + +# --- _assert_build_cost_zone_matches_geo_id (LCF) --- + + +def test_assert_build_cost_zone_matches_geo_id(csv_str_to_df): + # geo_id equals 'Regional build cost zone' for every row -> no raise. + # Includes existing 'known typo' (NSA -> CSA) which should be explicitly handled. + new_entrants = csv_str_to_df(""" + geo_id, Regional build cost zone + Q1, Q1 + NQ, NQ + NSA, CSA + """) + _assert_build_cost_zone_matches_geo_id(new_entrants) + + +def test_assert_build_cost_zone_matches_geo_id_raises(csv_str_to_df): + # An unexpected (geo_id, cost zone) split that isn't the known typo -> raise. + new_entrants = csv_str_to_df(""" + geo_id, Regional build cost zone + NSA, CSA + Q1, Q2 + Test, + """) + + with pytest.raises( + ValueError, + match=( + r"Unexpected divergence between geo_id and 'Regional build cost zone' in " + r"new_entrants_summary: \[\('Q1', 'Q2'\), \('Test', nan\)\]" + ), + ): + _assert_build_cost_zone_matches_geo_id(new_entrants) + + +# --- _reshape_technology_specific_lcfs (LCF) --- + + +def test_reshape_technology_specific_lcfs(csv_str_to_df): + # Wide -> long: factors become percentages (x100), the bespoke BOTN column (already a + # percentage) is left unscaled, and "Not Applicable" cells are dropped. + technology_specific_lcfs = csv_str_to_df(""" + Cost zone / REZ ID, REZ name / Description, Wind, BOTN - Cethana + Q1, Far North QLD, 1.05, Not Applicable + TAS, Subregional Ref Node, Not Applicable, 100 + """) + + result = _reshape_technology_specific_lcfs(technology_specific_lcfs) + + expected = csv_str_to_df(""" + geo_id, lcf_technology, lcf_build + Q1, Wind, 105.0 + TAS, BOTN - Cethana, 100.0 + """) + pd.testing.assert_frame_equal( + result.sort_values("geo_id").reset_index(drop=True), + expected.sort_values("geo_id").reset_index(drop=True), + ) + + +def test_reshape_technology_specific_lcfs_raises_on_invalid_table(): + # Regression: confirms the table is actually validated before reshaping. + # Exact raise behaviour is covered by _assert_table_valid's own tests. + technology_specific_lcfs = pd.DataFrame( + columns=["Cost zone / REZ ID", "REZ name / Description", "Wind"] + ) + + with pytest.raises(ValueError): + _reshape_technology_specific_lcfs(technology_specific_lcfs) + + +# --- _merge_lcf_build (LCF) --- + + +def test_merge_lcf_build(csv_str_to_df): + # lcf_build is looked up by (geo_id, technology), with the technology fuzzy-matched to + # the lcf table's column spelling ("OCGT (Small GT)") and factors converted to percentages. + # BOTN's 'technology' arrives already overridden to its own name. + new_entrants = csv_str_to_df(""" + name, technology, geo_id + Q1_WH_Far North QLD, Wind, Q1 + NQ OCGT Small, OCGT (small GT), NQ + BOTN - Cethana - 20h, BOTN - Cethana, TAS + """) + technology_specific_lcfs = csv_str_to_df(""" + Cost zone / REZ ID, REZ name / Description, Wind, OCGT (Small GT), Pumped Hydro (24hrs storage), BOTN - Cethana + Q1, Far North QLD, 1.05, Not Applicable, Not Applicable, Not Applicable + NQ, Subregional Ref Node, Not Applicable, 1.08, Not Applicable, Not Applicable + TAS, Subregional Ref Node, Not Applicable, Not Applicable, 1.0469, 100 + """) + + result = _merge_lcf_build(new_entrants, technology_specific_lcfs) + + expected = csv_str_to_df(""" + name, technology, geo_id, lcf_build + Q1_WH_Far North QLD, Wind, Q1, 105.0 + NQ OCGT Small, OCGT (small GT), NQ, 108.0 + BOTN - Cethana - 20h, BOTN - Cethana, TAS, 100.0 + """) + pd.testing.assert_frame_equal( + result.sort_values("name").reset_index(drop=True), + expected.sort_values("name").reset_index(drop=True), + ) + + +def test_merge_lcf_build_empty(csv_str_to_df): + # No new_entrant rows -> returns empty with the lcf_build column added. + new_entrants = pd.DataFrame(columns=["name", "technology", "geo_id"]) + technology_specific_lcfs = csv_str_to_df(""" + Cost zone / REZ ID, REZ name / Description, Wind + Q1, Far North QLD, 1.05 + """) + + result = _merge_lcf_build(new_entrants, technology_specific_lcfs) + + expected = csv_str_to_df(""" + name, technology, geo_id, lcf_build + """) + pd.testing.assert_frame_equal(result, expected, check_dtype=False) + + +# --- _merge_lcf_om (LCF) --- + + +def test_merge_lcf_om(csv_str_to_df): + # lcf_om is a single per-zone value (technology-independent) looked up by geo_id, + # already a percentage so taken as-is. + new_entrants = csv_str_to_df(""" + name, geo_id + Q1_WH_Far North QLD, Q1 + NQ OCGT Small, NQ + """) + locational_cost_factors = csv_str_to_df(""" + Cost zone / REZ ID, Equipment and installation costs, O&M costs 3 + Q1, 110.0, 122.27 + NQ, 105.0, 114.997 + """) + + result = _merge_lcf_om(new_entrants, locational_cost_factors) + + expected = csv_str_to_df(""" + name, geo_id, lcf_om + Q1_WH_Far North QLD, Q1, 122.27 + NQ OCGT Small, NQ, 114.997 + """) + pd.testing.assert_frame_equal(result, expected) + + +def test_merge_lcf_om_empty(csv_str_to_df): + # No rows -> returns empty with the lcf_om column added. + new_entrants = pd.DataFrame(columns=["name", "geo_id"]) + locational_cost_factors = csv_str_to_df(""" + Cost zone / REZ ID, O&M costs 3 + Q1, 122.27 + """) + + result = _merge_lcf_om(new_entrants, locational_cost_factors) + + expected = csv_str_to_df(""" + name, geo_id, lcf_om + """) + pd.testing.assert_frame_equal(result, expected, check_dtype=False) + + +def test_merge_lcf_om_raises_on_invalid_source_table(csv_str_to_df): + # Regression: confirms the source table is actually validated before merging. + # Exact raise behaviour is covered by _assert_table_valid's own tests. + new_entrants = csv_str_to_df(""" + name, geo_id + Q1_WH_Far North QLD, Q1 + """) + locational_cost_factors = pd.DataFrame( + columns=["Cost zone / REZ ID", "O&M costs 3"] + ) + + with pytest.raises(ValueError): + _merge_lcf_om(new_entrants, locational_cost_factors) diff --git a/tests/test_workbook_table_cache/7.5/locational_cost_factors.csv b/tests/test_workbook_table_cache/7.5/locational_cost_factors.csv new file mode 100644 index 00000000..d25442e7 --- /dev/null +++ b/tests/test_workbook_table_cache/7.5/locational_cost_factors.csv @@ -0,0 +1,65 @@ +Cost zone / REZ ID,REZ name,Equipment and installation costs,Fuel connection costs,Cost of land and development 2,Installation costs,O&M costs 3 +NQ,Subregional Reference Node,99.15912864613021,108.3157714802017,145.9775546367395,117.20818291215403,114.99696417729204 +CQ,Subregional Reference Node,101.80664667542317,117.25131561275197,100.0,132.2503008423586,124.52944748026715 +GG,Subregional Reference Node,99.13013308220367,113.24648050695352,100.0,126.95547533092657,119.91499696417726 +SQ,Subregional Reference Node,100.0,100.0,100.0,100.0,100.0 +NNSW,Subregional Reference Node,102.45373331201533,110.15428375701796,92.84001066003376,117.75585696670778,113.34156886967266 +CNSW,Subregional Reference Node,102.77600127907907,108.39079261542737,72.7813804743715,113.93341553637487,110.43854231006796 +SNSW,Subregional Reference Node,101.82418658565832,101.03029580981597,92.84001066003376,100.24660912453763,100.61766522544782 +SNW,Subregional Reference Node,100.0,100.0,100.0,100.0,100.0 +WNV,Subregional Reference Node,100.0,100.0,100.0,100.0,100.0 +MEL,Subregional Reference Node,100.0,100.0,100.0,100.0,100.0 +SEV,Subregional Reference Node,100.99554884349415,99.93835297183786,100.0,98.86219974715553,99.43431803896921 +NSA,Subregional Reference Node,102.24840603670673,118.1783995092558,13.551237988356595,134.12499999999997,124.56249999999997 +CSA,Subregional Reference Node,100.0,100.0,100.0,100.0,100.0 +SESA,Subregional Reference Node,100.6900306430546,116.83659892281145,100.0,132.99999999999997,123.99999999999997 +TAS,Subregional Reference Node,100.0,100.0,100.0,100.0,100.0 +N0,New South Wales Non-REZ,102.13000000000001,100.0,92.84001066003376,126.62500000000003,119.31250000000003 +N1,North West NSW,103.01249999999999,100.0,35.58674602469574,130.20833333333331,122.2708333333333 +N2,New England,102.69437500000001,100.0,92.84001066003376,120.34375000000001,115.421875 +N3,Central-West Orana,102.55750000000002,100.0,72.7813804743715,127.16666666666667,119.91666666666664 +N4,Broken Hill,103.82499999999999,100.0,17.162654348405436,152.625,139.3125 +N5,South West NSW,102.93999999999998,100.0,123.9140090610287,135.875,126.68749999999997 +N6,Wagga Wagga,103.32000000000001,100.0,123.9140090610287,103.62500000000001,103.8125 +N7,Tumut,101.57624999999999,100.0,92.84001066003376,128.60416666666669,120.88541666666666 +N8,Cooma-Monaro,100.37,100.0,92.84001066003376,126.04166666666667,119.35416666666667 +N9,Hunter-Central Coast,100.31875000000001,100.0,92.84001066003376,109.00000000000003,106.75000000000001 +N10,Hunter Coast,100.0,100.0,100.0,100.0,100.0 +N11,Illawarra Coast,100.0,100.0,100.0,100.0,100.0 +N12,Illawarra,100.38375000000002,100.0,100.0,104.33333333333333,103.33333333333331 +N13,South Cobar,105.06249999999999,100.0,72.7813804743715,177.12500000000003,155.5625 +Q1,Far North QLD,100.9095625,100.0,33.10100413467218,125.65625,121.95312500000001 +Q2,North Qld Clean Energy Hub,103.30375000000001,100.0,33.10100413467218,183.12499999999997,161.56249999999997 +Q3,Northern Qld,100.445875,100.0,145.9775546367395,124.93749999999997,120.71874999999999 +Q4,Isaac,102.69999999999999,100.0,100.0,137.37499999999997,128.1875 +Q5,Barcaldine,105.04,100.0,7.28883638511518,174.0,154.5 +Q6,Fitzroy,100.57249999999999,100.0,100.0,134.45833333333331,125.3958333333333 +Q7,Wide Bay,101.37999999999998,100.0,100.0,111.62500000000001,108.81250000000003 +Q8,Darling Downs,102.11499999999998,100.0,97.42075211655838,119.3125,114.15624999999999 +Q9,Banana,102.76749999999998,100.0,53.5380980507974,152.375,138.6875 +Q10,Collinsville,102.76749999999998,100.0,122.98877731836977,133.83333333333331,126.08333333333333 +S1,South East SA,101.38749999999999,100.0,100.0,129.95833333333334,121.64583333333331 +S2,Riverland,101.82625,100.0,51.525566388440765,134.68749999999997,124.84374999999999 +S3,Mid-North SA,100.9695,100.0,67.68371092562718,119.52083333333331,113.92708333333334 +S4,Yorke Peninsula,101.38499999999999,100.0,51.525566388440765,131.08333333333334,122.20833333333334 +S5,Northern SA,102.595,100.0,13.551237988356595,133.56249999999997,124.28124999999999 +S6,Roxby Downs,104.1925,100.0,13.551237988356595,168.25000000000003,149.125 +S7,Eastern Eyre Peninsula,104.39125,100.0,13.922985200252509,134.49999999999997,125.49999999999997 +S8,Western Eyre Peninsula,104.785,100.0,13.922985200252509,142.125,131.0625 +T1,North East Tasmania,101.30875000000002,100.0,100.0,99.68750000000001,100.34375 +T2,North West Tasmania,100.897375,100.0,100.0,133.5625,124.28124999999999 +T3,Central Highlands,101.6875,100.0,100.0,123.875,116.9375 +T4,North Tasmania Coast,100.0,100.0,100.0,100.0,100.0 +V0,Victoria Non-REZ,101.64999999999999,100.0,100.0,97.75000000000003,98.875 +V1,North West VIC,102.20499999999998,100.0,39.638463124613374,101.62500000000001,101.8125 +V2,Central Highlands VIC,100.64800000000001,100.0,100.0,98.87499999999999,99.43749999999999 +V3,Wimmera Grampians,101.4175,100.0,78.34215409993814,100.25000000000001,100.62500000000001 +V4,Wimmera Southern Mallee,101.6275,100.0,70.02543130112035,99.12500000000001,100.06250000000001 +V5,South West VIC,101.20750000000001,100.0,100.0,104.58333333333336,103.95833333333333 +V6,Gippsland Onshore,101.64999999999999,100.0,100.0,97.75000000000003,98.875 +V7,Central North VIC,101.57124999999998,100.0,78.34215409993814,99.00000000000003,99.75 +V8,Gippsland Offshore,100.0,100.0,100.0,100.0,100.0 +V9,Southern Ocean,100.0,100.0,100.0,100.0,100.0 +DN1,Dubbo,102.85,100.0,72.7813804743715,115.5,111.75 +DN2,Yass,101.8975,100.0,92.84001066003376,101.62500000000001,101.8125 +DN3,Marulan,101.215,100.0,92.84001066003376,117.75000000000004,112.87500000000001 diff --git a/tests/test_workbook_table_cache/7.5/technology_specific_lcfs.csv b/tests/test_workbook_table_cache/7.5/technology_specific_lcfs.csv new file mode 100644 index 00000000..d64777b8 --- /dev/null +++ b/tests/test_workbook_table_cache/7.5/technology_specific_lcfs.csv @@ -0,0 +1,65 @@ +Cost zone / REZ ID,REZ name / Description,OCGT (small GT),OCGT (large GT),CCGT,CCGT with CCS,Biomass,Large scale Solar PV,Solar Thermal (16hrs storage),Battery storage (1hr storage),Battery storage (2hrs storage),Battery storage (4hrs storage),Battery Storage (8hrs storage),Wind,Wind - Offshore (fixed),Wind - offshore (floating),Pumped Hydro (10hrs storage),Pumped Hydro (24hrs storage),Pumped Hydro (48hrs storage),Distributed Resources Solar,Distributed Resources Batteries,Alkaline Electrolyser,BOTN - Cethana +NQ,Subregional Reference Node,1.080074646738433,1.0801421496571129,1.080003934603035,1.0549974707464607,1.0964837766010318,Not Applicable,Not Applicable,1.0467279426648464,1.0406411993937774,1.0355872987830739,1.030577091680457,Not Applicable,Not Applicable,Not Applicable,1.1056898700223396,1.0463322631160885,1.0180236710742092,1.0862019210447542,1.039197143683184,Not Applicable,Not Applicable +CQ,Subregional Reference Node,1.1037044981577715,1.1052110446068442,1.1021263253087428,1.0590735138287448,1.1282942704043837,Not Applicable,Not Applicable,1.0663151224284846,1.0672549166229892,1.068098084219382,1.0688659661280597,Not Applicable,Not Applicable,Not Applicable,1.0252750755232363,1.029239173639732,0.9407437819127237,1.1319255503980865,1.0660536547511592,Not Applicable,Not Applicable +GG,Subregional Reference Node,1.0715593530039886,1.072892860955544,1.0701624455033016,1.0308396766751213,1.0941306517329628,Not Applicable,Not Applicable,1.036797101616533,1.0373063012540336,1.0377845742815417,1.0381986865838335,Not Applicable,Not Applicable,Not Applicable,1.0319854589481032,1.0464951117964456,1.0356048609387305,1.0967949998272892,1.0361698251871119,Not Applicable,Not Applicable +SQ,Subregional Reference Node,0.9999999999999991,0.9999999999999984,0.9999999999999989,1,0.9999999999999987,Not Applicable,Not Applicable,0.9999999999999993,0.9999999999999988,0.9999999999999996,0.9999999999999984,Not Applicable,Not Applicable,Not Applicable,0.9450582116249739,1.02295337609453,1.0390465663527215,0.9999999999999987,1,Not Applicable,Not Applicable +NNSW,Subregional Reference Node,1.0606978948745625,1.0615921835468933,1.0597610886230162,1.038035129238213,1.0727537928383777,Not Applicable,Not Applicable,1.0439658530202747,1.0456459983128652,1.047079405443278,1.0484589143972032,Not Applicable,Not Applicable,Not Applicable,1.0665480320721672,1.0601190499981938,0.986125928742748,1.0768394661650393,1.0451752419068687,Not Applicable,Not Applicable +CNSW,Subregional Reference Node,1.0360263997239365,1.0370747524421584,1.034928204647207,1.0188382155013134,1.0439382238363697,Not Applicable,Not Applicable,1.0291921617251922,1.033606011757405,1.0373167408133235,1.040945776959116,Not Applicable,Not Applicable,Not Applicable,1.066408577509983,1.0462950614902629,1.0563787605125103,1.0528853755703729,1.033614027280581,Not Applicable,Not Applicable +SNSW,Subregional Reference Node,1.0066586716299049,1.006738462269631,1.0065750874490618,1.0087070025215,1.0050344431973244,Not Applicable,Not Applicable,1.0107123910022353,1.0119230922282383,1.0129321589320557,1.0139283837273243,Not Applicable,Not Applicable,Not Applicable,1.1618156207465422,1.1222048314163848,1.2147552008810354,1.0072033607623767,1.0121240715485404,Not Applicable,Not Applicable +SNW,Subregional Reference Node,0.9999999999999991,0.9999999999999984,0.9999999999999989,1,0.9999999999999987,Not Applicable,Not Applicable,0.9999999999999993,0.9999999999999988,0.9999999999999996,0.9999999999999984,Not Applicable,Not Applicable,Not Applicable,1.046898289560425,1.0614079424087675,1.0505176915510523,0.9999999999999987,1,Not Applicable,Not Applicable +WNV,Subregional Reference Node,0.9999999999999991,0.9999999999999984,0.9999999999999989,1,0.9999999999999987,Not Applicable,Not Applicable,0.9999999999999993,0.9999999999999988,0.9999999999999996,0.9999999999999984,Not Applicable,Not Applicable,Not Applicable,0.9996347224921116,0.9688622104060545,1.02271550996436,0.9999999999999987,1,Not Applicable,Not Applicable +MEL,Subregional Reference Node,0.9999999999999991,0.9999999999999984,0.9999999999999989,1,0.9999999999999987,Not Applicable,Not Applicable,0.9999999999999993,0.9999999999999988,0.9999999999999996,0.9999999999999984,Not Applicable,Not Applicable,Not Applicable,1.0238282716653104,1.0383379245136526,1.0274476736559377,0.9999999999999987,1,Not Applicable,Not Applicable +SEV,Subregional Reference Node,1.0030823372299034,1.0030013528670807,1.0031671718657909,1.0061686821484304,1.0013046882501562,Not Applicable,Not Applicable,1.005952758127571,1.0060425823826016,1.0061136338021444,1.0061878911854427,Not Applicable,Not Applicable,Not Applicable,1.0320452723393245,1.046554925187667,1.0356646743299518,1.001341596273194,1.0061439103434022,Not Applicable,Not Applicable +NSA,Subregional Reference Node,1.0436847709505888,1.046708415094939,1.0405173721714862,0.9945305795652397,1.066225530307256,Not Applicable,Not Applicable,1.0249120306299286,1.0379389516073725,1.0488887082114031,1.0595995305890507,Not Applicable,Not Applicable,Not Applicable,1.0542320101718636,1.1054097073610798,1.156679285910914,1.0925671500125023,1.0380077434889965,Not Applicable,Not Applicable +CSA,Subregional Reference Node,0.9999999999999991,0.9999999999999984,0.9999999999999989,1,0.9999999999999987,Not Applicable,Not Applicable,0.9999999999999993,0.9999999999999988,0.9999999999999996,0.9999999999999984,Not Applicable,Not Applicable,Not Applicable,0.9879183014297466,1.0245907352492698,0.9915377034203737,0.9999999999999987,1,Not Applicable,Not Applicable +SESA,Subregional Reference Node,1.0986414188997737,1.1001680189954688,1.0970422389840042,1.051395495288243,1.1248989361243171,Not Applicable,Not Applicable,1.0587867772777617,1.0596139062299132,1.0603664241461972,1.0610413048424774,Not Applicable,Not Applicable,Not Applicable,1.02711090195003,1.0416205547983721,1.0307303039406572,1.1284341357154015,1.0583202451444367,Not Applicable,Not Applicable +TAS,Subregional Reference Node,0.9999999999999991,0.9999999999999984,0.9999999999999989,1,0.9999999999999987,Not Applicable,Not Applicable,0.9999999999999993,0.9999999999999988,0.9999999999999996,0.9999999999999984,Not Applicable,Not Applicable,Not Applicable,1.0324559788117074,1.0469656316600495,1.0360753808023344,0.9999999999999987,1,Not Applicable,100 +N0,New South Wales Non-REZ,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1084754777320933,1.0773326613361927,1.0557794518892147,1.0576227826661257,1.0592099852319763,1.060722159381486,1.0856154766341992,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1531622048128911,Not Applicable +N1,North West NSW,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.0945852650454235,1.0730507332244708,1.0367654871789627,1.0467353769482388,1.055120845861926,1.0633176767329664,1.0876023188122328,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1246069149620905,Not Applicable +N2,New England,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.087967223015112,1.0662496729284792,1.0500361658781343,1.0518028309427037,1.0533130007961784,1.0547632809092637,1.0725140705664127,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1190810656776358,Not Applicable +N3,Central-West Orana,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1015853725955547,1.0749657805523651,1.048889485950713,1.053577528574627,1.0575429004712271,1.061395189285376,1.0852041827753864,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1394673981154053,Not Applicable +N4,Broken Hill,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1733467854802277,1.1268666302872452,1.0691827724921905,1.0823104876862135,1.093376505342234,1.1041673433978507,1.1497590475211479,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.2389930264955937,Not Applicable +N5,South West NSW,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1655550994685049,1.1160395704765538,1.0942998388149734,1.0921355726997612,1.0903887036997983,1.0886026776038922,1.124038209539486,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.2371193504398115,Not Applicable +N6,Wagga Wagga,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.0460079296571847,1.0408908399245393,1.0451028305047245,1.042252640806074,1.0398706851424806,1.0375259924187075,1.0390519678205075,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.0536688700259167,Not Applicable +N7,Tumut,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1128095972289482,1.0781006148534322,1.054646310153296,1.0564701542746526,1.0580458577252414,1.0595415675604982,1.0870979102206153,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.162636690523657,Not Applicable +N8,Cooma-Monaro,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.0963119557195145,1.0631693036761696,1.0410580483804428,1.0426838430204999,1.0440914279138078,1.0454244495049427,1.071643113121144,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1439021653943149,Not Applicable +N9,Hunter-Central Coast,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.0317136852792632,1.021632378770578,1.0130880599224559,1.0143221337873882,1.0153701393774417,1.0163838329925974,1.0249604431999637,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.04611494871533,Not Applicable +N10,Hunter Coast,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1,1,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable +N11,Illawarra Coast,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1,1,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable +N12,Illawarra,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.0185243710691811,1.013248456287584,1.010014249769159,1.0101568954180498,1.010283605208021,1.0104002723765417,1.0144795016293247,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.0261118964030547,Not Applicable +N13,South Cobar,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.3042866304571894,1.2137972829822201,1.1493182855813682,1.15542805459899,1.1606716194176503,1.1656853250878443,1.238628956778976,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.4340533798843138,Not Applicable +Q1,Far North QLD,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.064097148566794,1.0459759840615634,1.0115619790912305,1.0215295358221217,1.0299084227430924,1.0381037541221598,1.0598224068002091,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.0892007996752746,Not Applicable +Q2,North Qld Clean Energy Hub,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.2945123608309443,1.2021464778865907,1.1232731255593742,1.1348195563266896,1.1446224121923587,1.1541071280804927,1.2328818030455257,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.4264338858726138,Not Applicable +Q3,Northern Qld,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1226526252660771,1.078995180873921,1.0693048578319877,1.0635424702602725,1.0587718892956413,1.0540274015530982,1.0821361916192132,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1861395036608937,Not Applicable +Q4,Isaac,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1563207547169794,1.1098485542222072,1.081598891966758,1.082758409468437,1.0837932505237946,1.0847411574074055,1.1205936592809467,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.223164264597191,Not Applicable +Q5,Barcaldine,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.2552955677651578,1.183970327252966,1.10780279024106,1.1229252642795315,1.1356968681188606,1.1481251919231672,1.214002562133481,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.3565390012961502,Not Applicable +Q6,Fitzroy,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1332720125786147,1.0873856622199054,1.0602259048938127,1.061072401024362,1.0618440885962281,1.0625346311728376,1.097692480501865,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1993029995072675,Not Applicable +Q7,Wide Bay,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.0516792452830175,1.0380816373240922,1.0296090858725753,1.0300324543189354,1.0304057318168207,1.0307522499999981,1.0413109632037316,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.0712289356984477,Not Applicable +Q8,Darling Downs,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.083389104971635,1.0611068259234346,1.0463701950784248,1.0474107463108615,1.0483130880324922,1.0491661253919533,1.0667506170336145,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.115389985294432,Not Applicable +Q9,Banana,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1870074139910156,1.130882391633942,1.0806525612469784,1.0886053251872068,1.095347021266326,1.1018806709353308,1.1505089479403354,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.2670733448651001,Not Applicable +Q10,Collinsville,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1563505657776918,1.1095452677538524,1.0891340086713026,1.0870318600436184,1.0853333814182482,1.0835987751206837,1.117048922479871,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.2240034400849429,Not Applicable +S1,South East SA,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1209040880503127,1.0824216428922169,1.0593261542012917,1.0601654035160561,1.0609209817419927,1.0616064737654303,1.0911966799411126,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1762677075634391,Not Applicable +S2,Riverland,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1137951319179837,1.080656219946163,1.0435556669742045,1.0512779708839026,1.0577953506101285,1.0641422877571167,1.0953161738604662,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1607642344107774,Not Applicable +S3,Mid-North SA,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.060859018762932,1.0432674637240384,1.0212651408636892,1.0263029641163999,1.030549268460229,1.034690334122028,1.0520020059317068,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.085697448464046,Not Applicable +S4,Yorke Peninsula,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.0976968614777323,1.0687524371828676,1.0342708862724501,1.0418603750228839,1.048260750191092,1.0544989721706972,1.0824139824396457,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1386356467044458,Not Applicable +S5,Northern SA,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.092406361125483,1.0706866334474472,1.0267152842040748,1.039769710543336,1.05074074019406,1.0614743449978419,1.0884066271599022,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1223833316523861,Not Applicable +S6,Roxby Downs,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.232345040370766,1.1660537784874196,1.0953358797719417,1.1093609896131036,1.121206012567601,1.132732456108953,1.193935905377405,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.3264459702333173,Not Applicable +S7,Eastern Eyre Peninsula,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1063219727548583,1.086093733737855,1.0424993616067875,1.05573197578914,1.066850332295795,1.0777306662407855,1.1036874676083288,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.134131898962535,Not Applicable +S8,Western Eyre Peninsula,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1373243312454244,1.1073659455161797,1.0579169170084493,1.0713678509967812,1.082682381083583,1.093741071333378,1.1271846250779962,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.179132129930754,Not Applicable +T1,North East Tasmania,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.0062287735849045,1.0087293064941287,1.0097396883656504,1.0098845566860455,1.0100025890451958,1.0101220393518504,1.0083614758826889,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.0026111881005173,Not Applicable +T2,North West Tasmania,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1317304245283002,1.087575989887153,1.0613198940443205,1.0621838283845502,1.0629677843460037,1.0636728812499983,1.0975439122087685,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1952641768292682,Not Applicable +T3,Central Highlands,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.0996462264150928,1.0699008804355166,1.0518334487534617,1.0525698193521582,1.05322732714756,1.0538292824074056,1.0767725882688635,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.1424311714708053,Not Applicable +T4,North Tasmania Coast,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1,1,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable +V0,Victorian Non-REZ,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.0008490566037724,1.0065220691896637,1.0092767313019384,1.0094173588039854,1.0095275366656686,1.0096438888888875,1.005496737215592,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,0.9926607538802661,Not Applicable +V1,North West VIC,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,0.9844462998818553,0.9995217903963844,0.9864488161355192,0.9951158069454361,1.0023596267957533,1.009489419097149,1.0052133362904734,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,0.9618490832544526,Not Applicable +V2,Central Highlands VIC,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,0.9994226415094326,1.0019783609875312,1.0032527645429357,1.0033025290697664,1.0033407752170005,1.0033820166666652,1.0015026536486118,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,0.9957351441241684,Not Applicable +V3,Wimmera Grampians,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,0.9967078230754354,1.0035628352081136,0.9995021767866688,1.002677088496651,1.0053288548637944,1.0079408202036908,1.0053734151254843,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,0.9865099590117694,Not Applicable +V4,Wimmera Southern Mallee,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,0.9889436403591234,0.9995587708687104,0.9947185630477116,0.999046202782856,1.0026590336130257,1.0062194919076135,1.0017649831201154,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,0.9732114970438861,Not Applicable +V5,South West VIC,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.0241305031446528,1.0198218565773516,1.016867206832871,1.0171111468715381,1.0173214456749473,1.0175215385802454,1.0209565427304834,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.030314085981769,Not Applicable +V6,Gippsland Onshore,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.0008490566037724,1.0065220691896637,1.0092767313019384,1.0094173588039854,1.0095275366656686,1.0096438888888875,1.005496737215592,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,0.9926607538802661,Not Applicable +V7,Central North VIC,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,0.9928611249622278,1.0016575455513181,0.9986836103046741,1.0018480758305377,1.0044884552828304,1.0070920678888757,1.0030577981118622,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,0.9798669896843487,Not Applicable +V8,Gippsland Offshore,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1,1,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable +V9,Southern Ocean,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1,1,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable +DN1,Dubbo,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.0592158757402088,Not Applicable,Not Applicable,1.0367636546820467,1.0405140318690123,1.0441790519396967,1.0555509135811991,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable +DN2,Yass,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.0128198173547347,Not Applicable,Not Applicable,1.0147665845764247,1.015811317168522,1.01683998808519,1.0160251576753128,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable +DN3,Marulan,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,1.0698056664113387,Not Applicable,Not Applicable,1.0357954570847305,1.0371108682011472,1.0383717519740787,1.0550520942557164,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable,Not Applicable