Climate Risk and Emissions by Emmi¶
Emmi employs a top-down approach to calculate company emissions, transition risk, physical climate risk, and asset hazard analysis across five asset classes. The analysis runs entirely within your Snowflake account, without data ever leaving your environment. It can be run through a guided user interface, or directly via Snowflake SQL so it can be incorporated into production-grade data pipelines.
Installation¶
Climate Risk and Emissions by Emmi is available as a Snowflake listing. Installing it takes a couple of minutes and happens entirely in Snowsight.
Before you start. You need the ACCOUNTADMIN role, or a role with the IMPORT SHARE and CREATE DATABASE privileges.
- Sign in to Snowsight, then select Catalog and Apps from the navigation menu.
- Locate Climate Risk and Emissions by Emmi (it may appear under Recently shared with you, or you can search for it by name) and select its tile.
- Select the Security tab to review the privileges the app requests.
- Select Get.
- Enter a name for the app and choose the warehouse to install it with.
- Select Get again, then choose Open to launch the app or Done to finish.
When you first open the app, Snowsight prompts you to grant the privileges it requests, including CREATE COMPUTE POOL and BIND SERVICE ENDPOINT (which let the app create the compute pool and service that host its user interface) and CREATE WAREHOUSE (a small warehouse the interface uses to run queries). Grant each when prompted. After granting, the interface may take 1-2 minutes to appear the first time you open it while the compute pool starts up; later visits load much faster.
Once installed, open the app and follow the steps in Using the App.
Trial listing¶
If you installed the app from the limited trial listing, the trial lasts 30 days from installation and the app applies two limits:
- Each run can process at most 25 input records. A record is one holdings row for Emissions and Climate Risk, or one asset for Geospatial Risk.
- Across the whole trial, your completed runs can process at most 100 input records in total. Runs that fail or are cancelled do not count towards this total.
The web UI shows how much of the allowance you have used and checks both limits before a run is submitted. A run that exceeds a limit fails straight away with an error naming the limit, whether it was submitted from the web UI or via SQL. The limits do not apply once you move to the full listing; to do so, contact Emmi using the Contact us link in the app.
Asset classes¶
The app analyses five asset classes. The class a holding is run under decides what its identifier means, which columns are required, and how its numbers are produced. Class names are matched case-sensitively.
Public Equity:identifieris an ISIN. Also requiresyear. Looked up from Emmi's pre-computed company universe.Fixed Income:identifieris an ISIN. Also requiresyear. Looked up from Emmi's pre-computed company universe.Property:identifieris the asset name. Also requiresyearandcountry. Calculated in your account from the property inputs.Infrastructure:identifieris the asset name. Also requiresyear,country,industry, andindustry_classification_system. Calculated in your account from the infrastructure inputs.Private Equity:identifieris the company name. Also requiresyear,industry,industry_classification_system, andcountry. Estimated in your account by Emmi's emissions models.
Financial values must already be in US dollars (USD) for every asset class. They are not currency-converted for you, so a portfolio submitted in local currency will produce USD-labelled numbers that are not comparable.
Choosing the asset class¶
In the interface, the asset class is chosen in the Mapping step: either one class for the whole portfolio, or a column in your data that names the class for each row.
On the SQL path those are the asset_class and asset_class_column params. asset_class and asset_class_column are mutually exclusive: pass one or the other, never both.
asset_class: one class for the entire run, e.g.'Public Equity'.asset_class_column: the name of a column in your input whose value names the class row by row, for a mixed portfolio. Every value in that column must be one of the five names above; unknown, blank or missing values fail the run before anything is computed, and the error lists every distinct offending value at once.
If you pass neither, the default is Private Equity: the whole portfolio is run as private equity, exactly as every run submitted before this release was, so existing pipelines need no change.
Reported emissions¶
By default the app uses its own estimate for a company even when a reported figure exists for the requested year, so a portfolio is measured on one consistent basis.
The optional prefer_reported param (default off, mirroring the interface's toggle) relaxes that for the looked-up classes, Public Equity and Fixed Income: when the requested year's record is entirely estimated, the previous year's record is used instead if it carries at least one reported scope. It has no effect on Property, Infrastructure or Private Equity, which are computed in your account rather than looked up.
Emissions¶
Emmi uses the reported data of public companies to train machine learning models to calculate emissions.
Input¶
To calculate emissions, the app requires a view of your portfolio companies and their financials, one row per company per year. All financial values must be in US dollars (USD); they are not currency-converted for you.
Required:
identifier(text): unique company identifier (one row per company per year).year(integer): fiscal year of the financials.industry(text): industry value in your chosen classification system.industry_classification_system(text): the classification system the industry value belongs to. One offactset,gics_l4,rbics_l3,rbics_l4, oranzsic.country(text): country of operation, e.g.United States. Case-insensitive; common aliases likeUS/UKare accepted.
At least one of these is required for every row (USD):
revenue: total revenue.total_assets: total assets.
Recommended (USD); providing these improves the accuracy and data-quality score of the estimate:
gppe: gross property, plant and equipment.evic: enterprise value including cash.market_cap: market capitalisation.cap_ex: capital expenditure.
Optional:
name(text): a readable company name, carried through to the results.scope1,scope2,scope3(tCO2e): reported emissions for each scope, used directly when provided.ebitda(USD): earnings before interest, tax, depreciation and amortisation.enterprise_value(USD): enterprise value.employees(count): number of employees.total_debt(USD): total debt.net_debt(USD): net debt.
Note: if you use the interface, your columns can be selected directly and do not need to match the names above. A single industry_classification_system can also be chosen once and applied to the whole view.
Per-class requirements. The tiers above describe the company contract in full, which is what Private Equity uses. The other classes require less of it: Public Equity and Fixed Income need only identifier (an ISIN) and year, because everything else is looked up. See Asset classes for the per-class list, or read required_by_class from SRC.GET_INPUT_CONTRACT().
Two further column groups are available to every class and are optional throughout:
- Holdings context, carried through to the results untouched:
manager_name,fund_name,asset_weight,ownership,security_name,entity_id,security_id. - Property and infrastructure inputs, used by those two classes:
state,net_lettable_area,building_type,energy_rating,energy_rating_system,market_value,book_value,net_operating_income,operating_cash_flow,gearing_ratio,asset_life_remaining,discount_rate. The monetary ones (market_value,book_value,net_operating_income,operating_cash_flow) are USD, like every other financial value.
Output¶
For each company and year, the app returns estimated Scope 1, 2, and 3 emissions, combined totals, and a set of carbon-intensity metrics. Reported emissions you supply are used directly and labelled as such.
identifier(text): company identifier from your input.name(text): company name from your input, if provided.year(integer): fiscal year.scope1,scope2,scope3(tCO2e): estimated (or reported) emissions for each scope.scope12(tCO2e): Scope 1 and 2 combined.scope123(tCO2e): Scope 1, 2, and 3 combined.scope1_model_name,scope2_model_name,scope3_model_name(text): the model used to produce each scope's estimate.scope1_pcaf,scope2_pcaf,scope3_pcaf(integer, 1 to 5): PCAF data-quality score for each scope (1 is the highest quality).scope1_emission_reporting_status,scope2_emission_reporting_status,scope3_emission_reporting_status(text): whether each scope was reported or estimated.waci_s1,waci_s12,waci_s123(tCO2e per USD revenue): weighted average carbon intensity at each scope grouping.evic_carbon_intensity_s12,evic_carbon_intensity_s123(tCO2e per USD EVIC): carbon intensity relative to enterprise value including cash.emissions_used(text): which emissions basis fed the derived metrics.
The results also include the financial fields carried through from your input (such as revenue, total_assets, evic, and market_cap), so the output is self-contained.
Multi-asset-class columns¶
DATA.EMISSIONS additionally carries the asset-class discriminator, the row key, and the per-holding attributes:
ASSET_CLASS(text): the class this row was run under, one of the five display names.INPUT_ROW(integer): the 0-based position of the holding in your input, in submission order.DATA_YEAR(integer): the year of the record actually used, which can differ fromYEAR. Empty when nothing resolved.MANAGER_NAME,FUND_NAME,SECURITY_NAME,ASSET_WEIGHT,OWNERSHIP: holdings context carried through from your input.FISCAL_END_DATE,COUNTRY,FACTSET_INDUSTRY: resolved entity attributes.DEBT_TYPE(text): instrument type, forFixed Incomeholdings.BUILDING_TYPE(text): building type, forPropertyassets.
YEAR is always populated and holds the year you requested. DATA_YEAR holds the year of the record actually used: for a looked-up holding they differ whenever the requested year had no record, or prefer_reported selected the prior year. The interface's Year column shows DATA_YEAR.
Results accumulate across runs and are read back filtered by TASK_ID. Because results are holdings-grain (the same identifier can legitimately appear more than once in one portfolio, under different funds or managers), the documented key is (TASK_ID, INPUT_ROW, YEAR) rather than one built on the identifier.
Rows written by earlier versions of the app predate ASSET_CLASS and INPUT_ROW, so those columns are empty for them. They are read back (and downloaded) as Private Equity via a COALESCE at the read boundary, which is what they were. Nothing is rewritten; no backfill runs on upgrade.
Climate Risk¶
Emmi combines a company's emissions with its financials to measure the value at risk from the low-carbon transition and from the physical effects of climate change, across a range of forward-looking scenarios and analysis years.
Input¶
Climate risk uses the same company view as the emissions analysis, together with the emissions estimates the app produces. You can run emissions first, or let the app run it for you as part of the climate risk job.
No new columns are needed, but the requirements tighten: each company-year must also provide all of the following, in USD, for its risk metrics to be calculated.
total_assets(USD): used as the enterprise value in the risk calculation.ebitda(USD): earnings before interest, tax, depreciation and amortisation.net_debt(USD): net debt.
Climate risk is not a separate task type. It is requested by adding 'risk' to the run's outputs list on the emissions task (alongside 'emissions', or on its own). A risk run always writes both DATA.EMISSIONS and DATA.CLIMATE_RISK, tied together by the run's TASK_ID.
You also choose two run options:
- Scenarios: one or more climate pathways from the 14 supported scenario ids, drawn from the IPCC, IPCC AR6, NGFS, and IPR scenario families, ranging from orderly net-zero transitions to high-warming pathways:
ipcc_net_zeroipcc_two_degreeipcc_four_degreengfs_below_two_degreengfs_current_policiesngfs_delayed_transitionngfs_fragmented_worldngfs_net_zerongfs_low_demandngfs_ndcipr_fpsipcc_ar6_sustainable_devipcc_ar6_gradualipcc_ar6_bau- Analysis years: the future years to assess. On the engineer path this is the
risk_yearsparam, which defaults to[2030, 2035, 2040, 2045, 2050].
Output¶
Results are written to DATA.CLIMATE_RISK, keyed by (TASK_ID, INPUT_ROW, YEAR, RISK_YEAR, TRANSITION_RISK_SCENARIO): holdings-grain, for the same reason DATA.EMISSIONS is. The table also carries ASSET_CLASS and INPUT_ROW, matching the emissions rows from the same run; the per-holding attribute columns (DEBT_TYPE, BUILDING_TYPE, and the rest) live on DATA.EMISSIONS only. Rows written before this release read back as Private Equity, exactly as for emissions.
For each company, scenario, and analysis year, the app returns the transition and physical value at risk, their combined total, and the budget metrics that show how the company tracks against the scenario's pathway.
identifier(text): identifier from your input, the same value asidentifierinDATA.EMISSIONS.year(integer): fiscal year of the financials.risk_year(integer): future year the metrics are assessed for.transition_risk_scenario(text): climate scenario the metrics are calculated under.transition_risk(USD): transition value at risk, the cost of the company's emissions under the scenario's carbon price.total_physical_risk(USD): physical value at risk, expected losses from acute and chronic climate impacts.baseline_physical_risk(USD): present-day physical value at risk, for comparison.total_climate_risk(USD): transition and physical value at risk combined.budget(tCO2e): the company's share of the scenario's remaining carbon budget.reduction(tCO2e): emissions reduction implied by the scenario pathway.overspend(tCO2e): emissions in excess of the scenario's carbon budget.temp_alignment(°C): temperature alignment, the warming outcome implied if the wider economy emitted at the company's rate.emissions_used(text): which emissions basis fed the risk metrics.
Geospatial Risk¶
Emmi assesses the exposure of your physical assets to climate hazards by their location and estimates expected damages from each asset's value. Upload a file of assets in the app's Geospatial Risk section and the app returns physical risk metrics for every asset. Your asset data never leaves your Snowflake account; the hazard maps ship with the app as shared data content.
Pilot coverage: riverine flooding under the rcp4p5 scenario at analysis year 2050. The remaining acute hazards (coastal flooding, tropical cyclone, wildfire), scenarios, and years (2030/2080) follow as data updates without an app upgrade.
Input¶
In the interface: a CSV or Excel file (first worksheet only), one row per asset. A downloadable template is available in the Geospatial Risk section. Column headers are matched case-insensitively and can be remapped in the UI before validation.
From SQL: a view of your assets with the same columns, bound to the app's input_assets reference (see Via SQL). Column names are matched case-insensitively; Owner Identifier and Name may be left out of the view.
Identifier(text, required): unique asset identifier (one row per asset; duplicates are rejected).Longitude(degrees, required): between -180 and 180.Latitude(degrees, required): between -90 and 90.Asset Value(number, required): value of the asset (>= 0), used to calculate expected damages. Use one consistent currency per file.Owner Identifier(text, optional): identifier linking the asset to its owning entity; carried through to outputs for your own owner-level analysis.Name(text, optional): a readable name for the asset.
The file is validated before any run starts; every problem is reported with the affected column and row numbers. On the SQL path a view missing a required column fails the run with an error naming the column.
Output¶
Results are written to DATA.GEOSPATIAL_RESULTS (one row per asset, hazard, scenario, and year) and can be viewed and downloaded as CSV in the app.
owner_identifier,identifier,name(text): carried through from your input unchanged.latitude,longitude(degrees): the queried coordinates.hazard(text): the hazard assessed (riverine_floodsin the pilot).scenario(text): the climate scenario the metrics are calculated under (rcp4p5).year(integer): future year the metrics are assessed for (2050).data_available(boolean):falsewhen no hazard data exists at the location (e.g. open ocean); the metric fields are then empty rather than the asset being dropped.aal(share of asset value): average annual loss, the expected yearly loss from the hazard.probability(number): probability of the hazard occurring.intensity(number): intensity of the hazard.depth_meters(metres): expected flood depth (flood hazards; empty otherwise).wind_speed_mps(m/s): maximum sustained wind speed (cyclones; empty otherwise).fire_danger_days(days): number of high fire danger days (wildfire; empty otherwise).asset_value(your currency): as submitted.expected_damages(your currency):aal * asset_value.map_version(text): version of the Emmi hazard maps used (e.g.v1.3).
Using the App¶
Via the user interface¶
Open the app in Snowsight and work through the steps in the left sidebar.
- Input. Upload a CSV or Excel file of your portfolio holdings (first worksheet
only). The parsed rows are previewed here, and you can replace the file at any
time. The interface works from the uploaded file; binding a view to the
input_portfolioreference is only needed for the SQL path (see Via SQL and the Engineer path). Geospatial Risk has its own upload in the Geospatial Risk section. - Mapping. Choose the asset class (one class for the whole portfolio, or the column that names it per row; see Asset classes), then match your columns to the fields that class needs (columns whose names already match are selected for you) and choose which industry classification system your industry values use. A downloadable CSV template lists every column the app understands but leaves the looked-up and real-asset columns blank in its example rows: the examples are private-equity shaped companies, and show the file format rather than acting as a per-class sample. Then click Check input: this checks every row and reports any problems (missing required fields, non-numeric financials, unrecognised industries, or coordinates outside the valid range). The check must pass before you can run.
- Run. Choose the analysis you want and click run. Each analysis runs entirely in your account and can take a few minutes for a large portfolio.
- Emissions estimates Scope 1, 2, and 3 for every company.
- Climate Risk uses your emissions estimates and financials to measure risk.
Selecting it adds
'risk'to the run's outputs (it is not a separate task type), so the run writes bothDATA.EMISSIONSandDATA.CLIMATE_RISK. Choose one or more of the supported climate scenarios from the scenario picker. - Geospatial Risk measures physical hazard exposure for assets you
upload as a file in the Geospatial Risk section (see above); results land in
DATA.GEOSPATIAL_RESULTS. - Results. Review headline figures and a per-entity breakdown for the analysis
you ran, and download the full results as CSV. A mixed-class run adds a filter so
each class can be reviewed on its own, and Download CSV returns one combined
file covering every class: a column a row's own class does not produce is left
empty for that row. The combined file is a superset of the equivalent Emmi
portfolio-processing export; it keeps the per-analysis-year
…PhysicalRiskand…TotalClimateRiskcolumns that the export drops. - Exclusions. If a run could not use every row (an ISIN that resolves to
nothing or to more than one company, for instance), the app records why. The
reasons are stored on the run in
DATA.TASKS.EXCLUSIONSand offered as a separate Download exclusions button (exclusions.csv), which appears only for runs that actually excluded something. The file has one row per identifier and stage, with repeated fields and reasons merged into a single cell. It, too, is a superset of the portfolio-processing equivalent: the app also records identifiers that could not be resolved at all, which that export does not.
Via SQL¶
You can also run the app without the UI:
-- grant permissions to the app
GRANT EXECUTE TASK ON ACCOUNT TO APPLICATION <app>;
GRANT EXECUTE MANAGED TASK ON ACCOUNT TO APPLICATION <app>;
-- bind your portfolio view to the app's input_portfolio reference (the UI
-- does not need this; it works from an uploaded file):
CALL <app>.SRC.UPDATE_REFERENCE(
'input_portfolio', 'ADD',
SYSTEM$REFERENCE('VIEW', '<your_db>.<your_schema>.<portfolio_view>', 'PERSISTENT', 'SELECT')
);
-- for geospatial risk from SQL, bind a view of your assets to the app's
-- input_assets reference (again, the UI uploads a file instead):
CALL <app>.SRC.UPDATE_REFERENCE(
'input_assets', 'ADD',
SYSTEM$REFERENCE('VIEW', '<your_db>.<your_schema>.<assets_view>', 'PERSISTENT', 'SELECT')
);
-- (no INIT step: granting EXECUTE MANAGED TASK above fires the app's grant
-- callback, which provisions the serverless run tasks automatically)
-- submit a run (returns a job id). Risk is not a separate task type: it is an
-- output of the emissions task, requested via the params `outputs` list plus
-- `scenarios` (and, optionally, `risk_years`).
-- emissions only:
CALL <app>.SRC.SUBMIT_TASK('emissions', {});
-- emissions + climate risk (writes both DATA.EMISSIONS and DATA.CLIMATE_RISK):
CALL <app>.SRC.SUBMIT_TASK('emissions', OBJECT_CONSTRUCT('outputs', ARRAY_CONSTRUCT('emissions','risk'), 'scenarios', ARRAY_CONSTRUCT('ipcc_net_zero','ngfs_net_zero'), 'risk_years', ARRAY_CONSTRUCT(2030, 2050)));
-- geospatial risk for every asset in the view bound to input_assets:
CALL <app>.SRC.SUBMIT_TASK('geospatial', {});
-- check progress:
SELECT * FROM <app>.DATA.TASKS ORDER BY TASK_ID DESC;
-- read results once the job is COMPLETED:
SELECT * FROM <app>.DATA.EMISSIONS;
SELECT * FROM <app>.DATA.CLIMATE_RISK;
SELECT * FROM <app>.DATA.GEOSPATIAL_RESULTS;
Engineer path (pure SQL)¶
For engineers building the analysis into a data pipeline, the sanctioned interface is pure SQL against your own view. The UI is for the CSV-upload workflow; the recipe below is the supported way to run the app from code.
The source parameter defaults to reference: the app reads the input_portfolio view you bind below without you passing anything extra.
The app has two input references, one per kind of input. input_portfolio is a
view of holdings for the emissions task (steps 1 to 5). input_assets is a
view of physical assets for the geospatial task (step 6). Each is
bound once and stays bound; rebinding replaces the previous view.
- One-time reference bind. Bind your portfolio view to the app's
input_portfolioreference (the UI does not use the reference; it works from an uploaded file). You need the privileges to grant the reference.
CALL <app>.SRC.UPDATE_REFERENCE(
'input_portfolio', 'ADD',
SYSTEM$REFERENCE('VIEW', '<db>.<schema>.<view>', 'PERSISTENT', 'SELECT')
);
- Discover the column contract. List the columns the input expects so you
can build a
column_mapif your view uses different names. The returned object also carriesasset_classes(the five display names in presentation order),identifier_meaning(whatidentifieris for each), andrequired_by_class(the columns each class requires); build your per-class checks off those keys rather than hard-coding a column list.
- Submit a run. Because
sourcedefaults toreference, nosourcekey is passed. Optionally supplycolumn_mapand/orclassification_system.
To add climate risk, extend the same emissions task with an outputs list
containing 'risk', a scenarios list (any of the 14 supported ids listed
under Climate Risk), and the optional engineer-only
risk_years param (defaults to [2030, 2035, 2040, 2045, 2050]).
outputs selects whether the run computes emissions, risk, or both; a risk run
always writes both DATA.EMISSIONS and DATA.CLIMATE_RISK. Risk additionally
requires total_assets, ebitda, and net_debt on the input view.
CALL <app>.SRC.SUBMIT_TASK('emissions', OBJECT_CONSTRUCT('outputs', ARRAY_CONSTRUCT('emissions','risk'), 'scenarios', ARRAY_CONSTRUCT('ipcc_net_zero','ngfs_net_zero'), 'risk_years', ARRAY_CONSTRUCT(2030, 2035, 2040, 2045, 2050)));
- Choose the asset class. Asset class is a param on the same
emissionstask; there is no per-class task type, and nothing about the app's requested privileges changes when you use one. Passasset_classfor a single-class portfolio:
Or asset_class_column for a mixed portfolio, naming the column in your view
that holds each row's class (the two params cannot be combined):
Both combine with everything above (outputs, scenarios, risk_years,
column_map, classification_system) and with the optional
prefer_reported flag:
CALL <app>.SRC.SUBMIT_TASK('emissions', OBJECT_CONSTRUCT('asset_class', 'Fixed Income', 'prefer_reported', TRUE, 'outputs', ARRAY_CONSTRUCT('emissions','risk'), 'scenarios', ARRAY_CONSTRUCT('ipcc_net_zero')));
Omitting both params runs the whole portfolio as Private Equity, so a
pipeline written before this release keeps working untouched.
- Monitor and collect. Poll the task table, then read the results once the
job is
COMPLETED. Filter byASSET_CLASSto separate the classes of a mixed run, and readEXCLUSIONSfor anything the run could not use.
SELECT * FROM <app>.DATA.TASKS ORDER BY CREATED_DATETIME DESC;
SELECT TASK_ID, EXCLUSIONS FROM <app>.DATA.TASKS WHERE EXCLUSIONS IS NOT NULL;
SELECT * FROM <app>.DATA.EMISSIONS;
SELECT * FROM <app>.DATA.CLIMATE_RISK;
- Geospatial risk from SQL. Bind a view of your physical assets to the
input_assetsreference, then submit ageospatialtask with no params. The view needsidentifier,latitude,longitudeandasset_value;owner_identifierandnameare optional and carried through to the results when present (see Geospatial Risk for the column meanings). Every row in the view is run each time; results land inDATA.GEOSPATIAL_RESULTSunder the run'sTASK_ID, withSUBMISSION_IDset toinput_assetsso they can be told apart from runs submitted through the interface.
CALL <app>.SRC.UPDATE_REFERENCE(
'input_assets', 'ADD',
SYSTEM$REFERENCE('VIEW', '<db>.<schema>.<assets_view>', 'PERSISTENT', 'SELECT')
);
CALL <app>.SRC.SUBMIT_TASK('geospatial', {});
SELECT * FROM <app>.DATA.GEOSPATIAL_RESULTS WHERE TASK_ID = <task_id>;
Passing a submission_id param instead selects the interface's upload path;
engineers do not need it.