diff --git a/PiCyberPreAnalysis/LICENSE b/PiCyberPreAnalysis/LICENSE
new file mode 100644
index 0000000..1cbb673
--- /dev/null
+++ b/PiCyberPreAnalysis/LICENSE
@@ -0,0 +1,25 @@
+BSD 2-Clause License
+
+Copyright (c) 2017, Oasis Loss Modelling Framework
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/PiCyberPreAnalysis/README.md b/PiCyberPreAnalysis/README.md
new file mode 100644
index 0000000..d8d7620
--- /dev/null
+++ b/PiCyberPreAnalysis/README.md
@@ -0,0 +1,16 @@
+
+
+# PiCyber Pre-Analysis
+
+A copy of the [PiCyber](../PiCyber) model with an exposure pre-analysis hook, to check that pre-analysis works for account only portfolios (no location file), as used by cyber and liability models.
+
+The hook, `src/exposure_modification/exposure_pre_analysis_revenue.py`, multiplies each account's `AnnualRevenue` by `revenue_scale_factor` (set in `tests/test_1/exposure_pre_analysis.json`). The lookup maps `AnnualRevenue` into revenue bands, so the scaled accounts can be assigned a different area peril and vulnerability.
+
+After a run, the modified account file is in `runs/losses-/input/account.csv`.
+
+## Running the model
+
+```
+cd tests/test_1
+oasislmf model run --config oasislmf.json
+```
diff --git a/PiCyberPreAnalysis/keys_data/AnnualRevenueMap.csv b/PiCyberPreAnalysis/keys_data/AnnualRevenueMap.csv
new file mode 100644
index 0000000..a6ab10d
--- /dev/null
+++ b/PiCyberPreAnalysis/keys_data/AnnualRevenueMap.csv
@@ -0,0 +1,6 @@
+0
+100000
+1000000
+10000000
+50000000
+100000000
diff --git a/PiCyberPreAnalysis/keys_data/area_peril_dict.csv b/PiCyberPreAnalysis/keys_data/area_peril_dict.csv
new file mode 100644
index 0000000..5553911
--- /dev/null
+++ b/PiCyberPreAnalysis/keys_data/area_peril_dict.csv
@@ -0,0 +1,7 @@
+area_peril_id,revenuecategory,countrycode,peril_id
+1,1,GB,CSB
+2,2,GB,CSB
+3,3,GB,CSB
+4,4,GB,CSB
+5,5,GB,CSB
+6,6,GB,CSB
diff --git a/PiCyberPreAnalysis/keys_data/lookup_config.json b/PiCyberPreAnalysis/keys_data/lookup_config.json
new file mode 100644
index 0000000..dc0a598
--- /dev/null
+++ b/PiCyberPreAnalysis/keys_data/lookup_config.json
@@ -0,0 +1,73 @@
+{
+ "model": {
+ "supplier_id": "OasisLMF",
+ "model_id": "PiCyberPreAnalysis",
+ "model_version": "0.0.1"
+ },
+ "builtin_lookup_type": "new_lookup",
+ "keys_data_path": "./",
+ "step_definition": {
+ "coverage_type": {
+ "type": "prepare",
+ "parameters": {
+ "coverage_type": {
+ "default": 10
+ }
+ }
+
+ },
+ "annual_revenue_map": {
+ "type": "interval_to_index",
+ "columns": [
+ "annualrevenue"
+ ],
+ "parameters": {
+ "sorted_array": "%%KEYS_DATA_PATH%%/AnnualRevenueMap.csv",
+ "value_column_name": "annualrevenue",
+ "index_column_name": "revenuecategory"
+ }
+ },
+ "area_peril": {
+ "type": "merge",
+ "columns": [
+ "revenuecategory",
+ "countrycode"
+ ],
+ "parameters": {
+ "file_path": "%%KEYS_DATA_PATH%%/area_peril_dict.csv",
+ "id_columns": ["area_peril_id"],
+ "usecols": [
+ "revenuecategory",
+ "countrycode",
+ "area_peril_id",
+ "peril_id"
+ ]
+
+ }
+ },
+ "vulnerability": {
+ "type": "merge",
+ "columns": [
+ "revenuecategory",
+ "countrycode"
+ ],
+ "parameters": {
+ "file_path": "%%KEYS_DATA_PATH%%/vulnerability_dict.csv",
+ "id_columns": ["vulnerability_id"],
+ "usecols": [
+ "revenuecategory",
+ "countrycode",
+ "vulnerability_id",
+ "peril_id"
+ ]
+
+ }
+ }
+ },
+ "strategy": [
+ "coverage_type",
+ "annual_revenue_map",
+ "area_peril",
+ "vulnerability"
+ ]
+}
diff --git a/PiCyberPreAnalysis/keys_data/vulnerability_dict.csv b/PiCyberPreAnalysis/keys_data/vulnerability_dict.csv
new file mode 100644
index 0000000..d39c39e
--- /dev/null
+++ b/PiCyberPreAnalysis/keys_data/vulnerability_dict.csv
@@ -0,0 +1,7 @@
+vulnerability_id,revenuecategory,countrycode,peril_id
+1,1,GB,CSB
+2,2,GB,CSB
+3,3,GB,CSB
+4,4,GB,CSB
+5,5,GB,CSB
+6,6,GB,CSB
diff --git a/PiCyberPreAnalysis/model_data/damage_bin_dict.bin b/PiCyberPreAnalysis/model_data/damage_bin_dict.bin
new file mode 100644
index 0000000..272416f
Binary files /dev/null and b/PiCyberPreAnalysis/model_data/damage_bin_dict.bin differ
diff --git a/PiCyberPreAnalysis/model_data/damage_bin_dict.csv b/PiCyberPreAnalysis/model_data/damage_bin_dict.csv
new file mode 100644
index 0000000..fa4ec88
--- /dev/null
+++ b/PiCyberPreAnalysis/model_data/damage_bin_dict.csv
@@ -0,0 +1,3 @@
+bin_index,bin_from,bin_to,interpolation,damage_type
+1,0,0.5,0.25,0
+2,0.5,1,0.75,0
diff --git a/PiCyberPreAnalysis/model_data/events.bin b/PiCyberPreAnalysis/model_data/events.bin
new file mode 100644
index 0000000..8956160
Binary files /dev/null and b/PiCyberPreAnalysis/model_data/events.bin differ
diff --git a/PiCyberPreAnalysis/model_data/events.csv b/PiCyberPreAnalysis/model_data/events.csv
new file mode 100644
index 0000000..360ec03
--- /dev/null
+++ b/PiCyberPreAnalysis/model_data/events.csv
@@ -0,0 +1,13 @@
+event_id
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
\ No newline at end of file
diff --git a/PiCyberPreAnalysis/model_data/footprint.bin.z b/PiCyberPreAnalysis/model_data/footprint.bin.z
new file mode 100644
index 0000000..1f01928
Binary files /dev/null and b/PiCyberPreAnalysis/model_data/footprint.bin.z differ
diff --git a/PiCyberPreAnalysis/model_data/footprint.csv b/PiCyberPreAnalysis/model_data/footprint.csv
new file mode 100644
index 0000000..8878124
--- /dev/null
+++ b/PiCyberPreAnalysis/model_data/footprint.csv
@@ -0,0 +1,73 @@
+event_id,areaperil_id,intensity_bin_id,probability
+1,1,1,1
+1,2,2,1
+1,3,1,1
+1,4,2,1
+1,5,1,1
+1,6,2,1
+2,1,1,1
+2,2,2,1
+2,3,1,1
+2,4,2,1
+2,5,1,1
+2,6,2,1
+3,1,1,1
+3,2,2,1
+3,3,1,1
+3,4,2,1
+3,5,1,1
+3,6,2,1
+4,1,1,1
+4,2,2,1
+4,3,1,1
+4,4,2,1
+4,5,1,1
+4,6,2,1
+5,1,1,1
+5,2,2,1
+5,3,1,1
+5,4,2,1
+5,5,1,1
+5,6,2,1
+6,1,1,1
+6,2,2,1
+6,3,1,1
+6,4,2,1
+6,5,1,1
+6,6,2,1
+7,1,1,1
+7,2,2,1
+7,3,1,1
+7,4,2,1
+7,5,1,1
+7,6,2,1
+8,1,1,1
+8,2,2,1
+8,3,1,1
+8,4,2,1
+8,5,1,1
+8,6,2,1
+9,1,1,1
+9,2,2,1
+9,3,1,1
+9,4,2,1
+9,5,1,1
+9,6,2,1
+10,1,1,1
+10,2,2,1
+10,3,1,1
+10,4,2,1
+10,5,1,1
+10,6,2,1
+11,1,1,1
+11,2,2,1
+11,3,1,1
+11,4,2,1
+11,5,1,1
+11,6,2,1
+12,1,1,1
+12,2,2,1
+12,3,1,1
+12,4,2,1
+12,5,1,1
+12,6,2,1
diff --git a/PiCyberPreAnalysis/model_data/footprint.idx.z b/PiCyberPreAnalysis/model_data/footprint.idx.z
new file mode 100644
index 0000000..38b4735
Binary files /dev/null and b/PiCyberPreAnalysis/model_data/footprint.idx.z differ
diff --git a/PiCyberPreAnalysis/model_data/model_to_bin.sh b/PiCyberPreAnalysis/model_data/model_to_bin.sh
new file mode 100644
index 0000000..1dd34ff
--- /dev/null
+++ b/PiCyberPreAnalysis/model_data/model_to_bin.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+
+# damage bin dict
+csvtobin damagebin -i damage_bin_dict.csv -o damage_bin_dict.bin
+
+# events
+csvtobin eve -i events.csv -o events.bin
+
+# footprint
+csvtobin footprint -z -m2 -n -i footprint.csv -o footprint.bin.z -x footprint.idx.z
+
+# occurrence
+csvtobin occurrence -P10 -i occurrence.csv -o occurrence.bin
+
+# return periods
+csvtobin returnperiods -i returnperiods.csv -o returnperiods.bin
+
+# vulnerability
+csvtobin vulnerability -d2 -N -i vulnerability.csv -o vulnerability.bin
+
+
+
+
diff --git a/PiCyberPreAnalysis/model_data/occurrence.bin b/PiCyberPreAnalysis/model_data/occurrence.bin
new file mode 100644
index 0000000..cc55a37
Binary files /dev/null and b/PiCyberPreAnalysis/model_data/occurrence.bin differ
diff --git a/PiCyberPreAnalysis/model_data/occurrence.csv b/PiCyberPreAnalysis/model_data/occurrence.csv
new file mode 100644
index 0000000..8ce7ece
--- /dev/null
+++ b/PiCyberPreAnalysis/model_data/occurrence.csv
@@ -0,0 +1,4 @@
+event_id,period_no,occ_year,occ_month,occ_day
+1,1,2026,1,1
+2,4,2026,2,2
+3,10,2026,3,3
\ No newline at end of file
diff --git a/PiCyberPreAnalysis/model_data/returnperiods.bin b/PiCyberPreAnalysis/model_data/returnperiods.bin
new file mode 100644
index 0000000..2c77133
Binary files /dev/null and b/PiCyberPreAnalysis/model_data/returnperiods.bin differ
diff --git a/PiCyberPreAnalysis/model_data/returnperiods.csv b/PiCyberPreAnalysis/model_data/returnperiods.csv
new file mode 100644
index 0000000..179e053
--- /dev/null
+++ b/PiCyberPreAnalysis/model_data/returnperiods.csv
@@ -0,0 +1,5 @@
+return_period
+10
+5
+2
+1
\ No newline at end of file
diff --git a/PiCyberPreAnalysis/model_data/vulnerability.bin b/PiCyberPreAnalysis/model_data/vulnerability.bin
new file mode 100644
index 0000000..9acd4d4
Binary files /dev/null and b/PiCyberPreAnalysis/model_data/vulnerability.bin differ
diff --git a/PiCyberPreAnalysis/model_data/vulnerability.csv b/PiCyberPreAnalysis/model_data/vulnerability.csv
new file mode 100644
index 0000000..1b589d8
--- /dev/null
+++ b/PiCyberPreAnalysis/model_data/vulnerability.csv
@@ -0,0 +1,13 @@
+vulnerability_id,intensity_bin_id,damage_bin_id,probability
+1,1,1,1
+1,2,2,1
+2,1,1,1
+2,2,2,1
+3,1,1,1
+3,2,2,1
+4,1,1,1
+4,2,2,1
+5,1,1,1
+5,2,2,1
+6,1,1,1
+6,2,2,1
diff --git a/PiCyberPreAnalysis/src/exposure_modification/__init__.py b/PiCyberPreAnalysis/src/exposure_modification/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/PiCyberPreAnalysis/src/exposure_modification/exposure_pre_analysis_revenue.py b/PiCyberPreAnalysis/src/exposure_modification/exposure_pre_analysis_revenue.py
new file mode 100644
index 0000000..371e601
--- /dev/null
+++ b/PiCyberPreAnalysis/src/exposure_modification/exposure_pre_analysis_revenue.py
@@ -0,0 +1,26 @@
+import logging
+
+
+class ExposurePreAnalysis:
+ """
+ Example of custom module called by oasislmf/computation/hooks/pre_analysis.py
+ for an account only (cyber) portfolio - there is no location file.
+
+ The account's AnnualRevenue is multiplied by "revenue_scale_factor" (from
+ exposure_pre_analysis.json), e.g. to bring revenues up to date. The lookup maps
+ AnnualRevenue into revenue bands, so the scaled accounts can move to a different
+ area peril / vulnerability.
+ """
+
+ def __init__(self, exposure_data, exposure_pre_analysis_setting, **kwargs):
+ self.exposure_data = exposure_data
+ self.exposure_pre_analysis_setting = exposure_pre_analysis_setting
+
+ def run(self):
+ scale_factor = self.exposure_pre_analysis_setting.get('revenue_scale_factor', 1)
+ account_df = self.exposure_data.account.dataframe
+
+ logging.info(f'Scaling AnnualRevenue by {scale_factor}')
+ account_df['AnnualRevenue'] = account_df['AnnualRevenue'] * scale_factor
+
+ self.exposure_data.account.dataframe = account_df
diff --git a/PiCyberPreAnalysis/tests/test_1/account.csv b/PiCyberPreAnalysis/tests/test_1/account.csv
new file mode 100644
index 0000000..9932f1f
--- /dev/null
+++ b/PiCyberPreAnalysis/tests/test_1/account.csv
@@ -0,0 +1,5 @@
+PortNumber,AccNumber,AccCurrency,PolNumber,PolInceptionDate,PolExpiryDate,LOB,PolPerilsCovered,LayerParticipation,LayerLimit,LayerAttachment,OEDVersion,CountryCode,AreaCode,IndustryScheme1,IndustryCode1,IndustryVintage1,IndustryDescription,AnnualRevenue,AnnualRevenueCurrency,PolDed,DedCurrency,PolCoverage,InsuredName,URL
+PiCyber,1,GBP,P1,2026-01-01,2026-12-31,,CSB,1,10000000,0,5.0.0,GB,,NAICS,72111,2022,Laser Quest,50000,GBP,10000,GBP,CSB,A People,a.com
+PiCyber,2,GBP,P2,2026-01-01,2026-12-31,,CSB,1,10000000,0,5.0.0,GB,,NAICS,72111,2022,Laser Quest,1000000,GBP,10000,GBP,CSB,Bee Keepers,b.com
+PiCyber,3,GBP,P3,2026-01-01,2026-12-31,,CSB,1,10000000,0,5.0.0,GB,,NAICS,72111,2022,Laser Quest,10000000,GBP,10000,GBP,CSB,C Shanty,c.com
+PiCyber,4,GBP,P4,2026-01-01,2026-12-31,,CSB,1,10000000,0,5.0.0,GB,,NAICS,72111,2022,Laser Quest,50000000,GBP,10000,GBP,CSB,Big D,d.com
diff --git a/PiCyberPreAnalysis/tests/test_1/analysis_settings.json b/PiCyberPreAnalysis/tests/test_1/analysis_settings.json
new file mode 100644
index 0000000..ae79687
--- /dev/null
+++ b/PiCyberPreAnalysis/tests/test_1/analysis_settings.json
@@ -0,0 +1,18 @@
+{
+ "version": "3",
+ "model_name_id": "PiCyberPreAnalysis",
+ "model_supplier_id": "OasisLMF",
+ "gul_output": true,
+ "number_of_samples": 10,
+ "model_settings": {},
+ "gul_summaries": [
+ {
+ "id": 1,
+ "ord_output": {
+ "elt_moment": true,
+ "alt_period": true
+ }
+ }
+ ]
+}
+
diff --git a/PiCyberPreAnalysis/tests/test_1/exposure_pre_analysis.json b/PiCyberPreAnalysis/tests/test_1/exposure_pre_analysis.json
new file mode 100644
index 0000000..b856adb
--- /dev/null
+++ b/PiCyberPreAnalysis/tests/test_1/exposure_pre_analysis.json
@@ -0,0 +1,3 @@
+{
+ "revenue_scale_factor": 10
+}
diff --git a/PiCyberPreAnalysis/tests/test_1/oasislmf.json b/PiCyberPreAnalysis/tests/test_1/oasislmf.json
new file mode 100644
index 0000000..199fc11
--- /dev/null
+++ b/PiCyberPreAnalysis/tests/test_1/oasislmf.json
@@ -0,0 +1,9 @@
+{
+ "analysis_settings_json": "analysis_settings.json",
+ "lookup_config_json": "../../keys_data/lookup_config.json",
+ "lookup_data_dir": "../../keys_data/",
+ "model_data_dir": "../../model_data/",
+ "oed_accounts_csv": "./account.csv",
+ "exposure_pre_analysis_module": "../../src/exposure_modification/exposure_pre_analysis_revenue.py",
+ "exposure_pre_analysis_setting_json": "exposure_pre_analysis.json"
+}