diff --git a/Changelog.md b/Changelog.md
index d80611e28feeaea646af29a2bfa6cf9e705e344b..a11547fd38b10def70c136784078fb320cca5327 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -2,6 +2,11 @@
 
 All notable changes to this project will be documented in this file.
 
+## [0.64] - 2024-10-25
+- Make EOSC questions readonly
+- Fix invalid html with nested paragraphs in survey navigation component
+- General refactoring and improvements
+
 ## [0.63] - 2024-10-08
 - Minor text change to the survey landing page
 - Prevent publishing a survey year which doesn't have a mapped publishing function
diff --git a/compendium-frontend/src/survey/SurveyComponent.tsx b/compendium-frontend/src/survey/SurveyComponent.tsx
index 99c5d788bc0aea88a0cc1f3e9440c88404883757..04e95caae7c928948430c367fb48ffcf3b1b28fd 100644
--- a/compendium-frontend/src/survey/SurveyComponent.tsx
+++ b/compendium-frontend/src/survey/SurveyComponent.tsx
@@ -45,6 +45,27 @@ function fixTitleCss(_, options) {
     }
 }
 
+function strikeThroughOrHide(question: Question) {
+    const hasVisibleIf = !!question.visibleIf;
+
+    const selector = '[data-name="' + question.name + '"]';
+    const container = document.querySelector(selector) as HTMLElement;
+    const header = container?.querySelector('h5');
+    if (hasVisibleIf) {
+        container.style.display = "none";
+        return;
+    }
+    if (header) {
+        header.style.textDecoration = "line-through";
+    }
+    // hide the question content
+    const questionContent = document.querySelector(selector)?.querySelector('.sv-question__content') as HTMLElement;
+    if (questionContent) {
+        questionContent.style.display = "none";
+    }
+
+}
+
 function setVerifyButton(question: Question, state: VerificationStatus, surveyModel) {
 
     surveyModel.verificationStatus.set(question.name, state);
@@ -85,9 +106,14 @@ function SurveyComponent({ surveyModel }) {
 
     const alwaysSetVerify = useCallback((_, options) => {
         const status = surveyModel.verificationStatus.get(options.question.name);
-        if (status) {
+        const readonly = options.question?.readOnly;
+
+        if (status && !readonly) {
             setVerifyButton(options.question, status, surveyModel);
         }
+        else if (readonly) {
+            strikeThroughOrHide(options.question);
+        }
     }, [surveyModel])
 
     const updateFromUnverified = useCallback((_, options) => {
diff --git a/compendium-frontend/src/survey/SurveyNavigationComponent.tsx b/compendium-frontend/src/survey/SurveyNavigationComponent.tsx
index 52e39b8e077600e171c8d7f1013b5a52f8b283cd..94c1db26647997ece293c6e74396de553d79a12b 100644
--- a/compendium-frontend/src/survey/SurveyNavigationComponent.tsx
+++ b/compendium-frontend/src/survey/SurveyNavigationComponent.tsx
@@ -72,25 +72,24 @@ function SurveyNavigationComponent({ surveyModel, surveyActions, year, nren, chi
         );
     };
 
-    const _year = parseInt(year);
     return (
         <Container>
             <Row className="survey-content">
                 <h2><span className="survey-title">{year} Compendium Survey </span><span className="survey-title-nren"> {nren} </span><span> - {responseStatus}</span></h2>
 
 
-                <p style={{ marginTop: '1rem', textAlign: 'justify' }}>
+                <div style={{ marginTop: '1rem', textAlign: 'justify' }}>
                     <p>
-                    To get started, click “{startEditing}” to end read-only mode. Different people from your NREN (Compendium administrators) can contribute to the survey if needed, but agreement should be reached internally before completing the survey as the administration team will treat responses as a single source of truth from the NREN.
-                    You can start editing only when nobody else from your NREN is currently working on the survey.</p>
+                        To get started, click “{startEditing}” to end read-only mode. Different people from your NREN (Compendium administrators) can contribute to the survey if needed, but agreement should be reached internally before completing the survey as the administration team will treat responses as a single source of truth from the NREN.
+                        You can start editing only when nobody else from your NREN is currently working on the survey.</p>
                     <p><b>In a small change, the survey now asks about this calendar year, i.e. {year}</b> (or the current financial year if your budget or staffing data does not match the calendar year). For network questions, please provide data from the 12 months preceding you answering the question.
-                    Where available, the survey questions are pre-filled with answers from the previous survey.
-                    You can edit the pre-filled answer to provide new information, or press the “no change from previous year” button.</p>
+                        Where available, the survey questions are pre-filled with answers from the previous survey.
+                        You can edit the pre-filled answer to provide new information, or press the “no change from previous year” button.</p>
                     <p>Press the “{save}“ or “{saveAndStopEdit}“ button to save all answers in the survey. When you reach the last section of the survey (Services), you will find a “{completeSurvey}“ button which saves all answers in the survey and lets the Compendium team know that your answers are ready to be published.
-                    As long as the survey remains open, any Compendium administrator from your NREN can add answers or amend existing ones, even after using the “{completeSurvey}“ button.</p>
+                        As long as the survey remains open, any Compendium administrator from your NREN can add answers or amend existing ones, even after using the “{completeSurvey}“ button.</p>
                     <p>Some fields require specific data, such as numerical data, valid http-addresses, and in some questions, the answer has to add up to 100%. If an answer does not fulfil the set criteria, the question will turn pink and an error message will appear. Fields can be left blank if you prefer not to answer a question.
-                    If you notice any errors after the survey was closed, please contact us for correcting those.</p>
-                </p>
+                        If you notice any errors after the survey was closed, please contact us for correcting those.</p>
+                </div>
                 <p>Thank you for taking the time to fill in the {year} Compendium Survey. Any questions or requests can be sent to <a href={`mailto:Partner-Relations@geant.org`}><span>Partner-Relations@geant.org</span></a></p>
                 {editing && <><br /><b>Remember to click “{saveAndStopEdit}” before leaving the page.</b></>}
             </Row>
diff --git a/compendium-survey-creator/models/survey_model.json b/compendium-survey-creator/models/survey_model.json
index 41512ae6ce1ea28db9a4353e5ae1fbcef6ee904e..0348979daea9197123cfa0534fbda61c12e4f583 100644
--- a/compendium-survey-creator/models/survey_model.json
+++ b/compendium-survey-creator/models/survey_model.json
@@ -593,6 +593,7 @@
               "type": "radiogroup",
               "name": "service_portfolio_eosc_portal",
               "title": "Are any of the services in your service portfolio listed on the EOSC portal?",
+              "readOnly": true,
               "choices": [
                 "Yes",
                 "No"
@@ -603,6 +604,7 @@
               "type": "matrixdynamic",
               "name": "services_on_eosc_portal_list",
               "visibleIf": "{service_portfolio_eosc_portal} = 'Yes'",
+              "readOnly": true,
               "indent": 1,
               "title": "Can you list them?",
               "columns": [
diff --git a/compendium_v2/__init__.py b/compendium_v2/__init__.py
index a491fc12729811d9148e7aee5ce3720001da7d57..ca40cec905127c1228571fc65cd2cbf324180546 100644
--- a/compendium_v2/__init__.py
+++ b/compendium_v2/__init__.py
@@ -5,7 +5,7 @@ import logging
 import os
 import sys
 
-import pkg_resources
+import importlib.metadata as importlib_metadata
 import sentry_sdk
 from sentry_sdk.integrations.flask import FlaskIntegration
 
@@ -26,7 +26,7 @@ if sentry_dsn:
     sentry_sdk.init(
         dsn=sentry_dsn,
         integrations=[FlaskIntegration()],
-        release=pkg_resources.get_distribution('compendium-v2').version)
+        release=importlib_metadata.distribution('compendium-v2').version)
 
 environment.setup_logging()
 
diff --git a/compendium_v2/db/presentation_models.py b/compendium_v2/db/presentation_models.py
index 5297ed4ed6c555265b453bf01d79d0690fa51037..513860e1165d87035ab52fc8c2ad69f1fe33e677 100644
--- a/compendium_v2/db/presentation_models.py
+++ b/compendium_v2/db/presentation_models.py
@@ -18,7 +18,6 @@ from compendium_v2.db.presentation_model_enums import CarryMechanism, Commercial
 
 logger = logging.getLogger(__name__)
 
-
 str128 = Annotated[str, 128]
 str128_pk = Annotated[str, mapped_column(String(128), primary_key=True)]
 str256_pk = Annotated[str, mapped_column(String(256), primary_key=True)]
@@ -48,13 +47,16 @@ RemoteCampus = TypedDict(
 # See https://github.com/pallets-eco/flask-sqlalchemy/issues/1140
 # mypy: disable-error-code="name-defined"
 
+class PresentationModel(db.Model):
+    __abstract__ = True
+
 
-class PreviewYear(db.Model):
+class PreviewYear(PresentationModel):
     __tablename__ = 'preview_year'
     year: Mapped[int_pk]
 
 
-class NREN(db.Model):
+class NREN(PresentationModel):
     __tablename__ = 'nren'
     id: Mapped[int_pk]
     name: Mapped[str128]
@@ -64,7 +66,7 @@ class NREN(db.Model):
         return f'<NREN {self.id} | {self.name}>'
 
 
-class BudgetEntry(db.Model):
+class BudgetEntry(PresentationModel):
     __tablename__ = 'budgets'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -72,7 +74,7 @@ class BudgetEntry(db.Model):
     budget: Mapped[Decimal]
 
 
-class FundingSource(db.Model):
+class FundingSource(PresentationModel):
     __tablename__ = 'funding_source'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -84,7 +86,7 @@ class FundingSource(db.Model):
     other: Mapped[Decimal]
 
 
-class ChargingStructure(db.Model):
+class ChargingStructure(PresentationModel):
     __tablename__ = 'charging_structure'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -92,7 +94,7 @@ class ChargingStructure(db.Model):
     fee_type: Mapped[Optional[FeeType]]
 
 
-class NrenStaff(db.Model):
+class NrenStaff(PresentationModel):
     __tablename__ = 'nren_staff'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -103,7 +105,7 @@ class NrenStaff(db.Model):
     non_technical_fte: Mapped[Decimal]
 
 
-class ParentOrganization(db.Model):
+class ParentOrganization(PresentationModel):
     __tablename__ = 'parent_organization'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -111,7 +113,7 @@ class ParentOrganization(db.Model):
     organization: Mapped[str128]
 
 
-class SubOrganization(db.Model):
+class SubOrganization(PresentationModel):
     __tablename__ = 'sub_organization'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -120,7 +122,7 @@ class SubOrganization(db.Model):
     role: Mapped[str128_pk]
 
 
-class ECProject(db.Model):
+class ECProject(PresentationModel):
     __tablename__ = 'ec_project'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -128,7 +130,7 @@ class ECProject(db.Model):
     project: Mapped[str256_pk]
 
 
-class Policy(db.Model):
+class Policy(PresentationModel):
     __tablename__ = 'policy'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -143,7 +145,7 @@ class Policy(db.Model):
     gender_equality: Mapped[str]
 
 
-class TrafficVolume(db.Model):
+class TrafficVolume(PresentationModel):
     __tablename__ = 'traffic_volume'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -154,7 +156,7 @@ class TrafficVolume(db.Model):
     from_external: Mapped[Decimal]
 
 
-class InstitutionURLs(db.Model):
+class InstitutionURLs(PresentationModel):
     __tablename__ = 'institution_urls'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -162,7 +164,7 @@ class InstitutionURLs(db.Model):
     urls: Mapped[json_str_list]
 
 
-class CentralProcurement(db.Model):
+class CentralProcurement(PresentationModel):
     __tablename__ = 'central_procurement'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -171,7 +173,7 @@ class CentralProcurement(db.Model):
     amount: Mapped[Optional[Decimal]]
 
 
-class ServiceManagement(db.Model):
+class ServiceManagement(PresentationModel):
     __tablename__ = 'service_management'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -180,7 +182,7 @@ class ServiceManagement(db.Model):
     service_level_targets: Mapped[Optional[bool]]
 
 
-class ServiceUserTypes(db.Model):
+class ServiceUserTypes(PresentationModel):
     __tablename__ = 'service_user_types'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -189,7 +191,7 @@ class ServiceUserTypes(db.Model):
     service_category: Mapped[ServiceCategory] = mapped_column(primary_key=True)
 
 
-class EOSCListings(db.Model):
+class EOSCListings(PresentationModel):
     __tablename__ = 'eosc_listings'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -197,7 +199,7 @@ class EOSCListings(db.Model):
     service_names: Mapped[json_str_list]
 
 
-class Standards(db.Model):
+class Standards(PresentationModel):
     __tablename__ = 'standards'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -209,7 +211,7 @@ class Standards(db.Model):
     crisis_management_procedure: Mapped[Optional[bool]]
 
 
-class CrisisExercises(db.Model):
+class CrisisExercises(PresentationModel):
     __tablename__ = 'crisis_exercises'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -217,7 +219,7 @@ class CrisisExercises(db.Model):
     exercise_descriptions: Mapped[json_str_list]
 
 
-class SecurityControls(db.Model):
+class SecurityControls(PresentationModel):
     __tablename__ = 'security_controls'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -225,7 +227,7 @@ class SecurityControls(db.Model):
     security_control_descriptions: Mapped[json_str_list]
 
 
-class ConnectedProportion(db.Model):
+class ConnectedProportion(PresentationModel):
     __tablename__ = 'connected_proportion'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -237,7 +239,7 @@ class ConnectedProportion(db.Model):
     users_served: Mapped[Optional[int]]
 
 
-class ConnectivityLevel(db.Model):
+class ConnectivityLevel(PresentationModel):
     __tablename__ = 'connectivity_level'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -248,7 +250,7 @@ class ConnectivityLevel(db.Model):
     highest_speed_proportion: Mapped[Optional[Decimal]]
 
 
-class ConnectionCarrier(db.Model):
+class ConnectionCarrier(PresentationModel):
     __tablename__ = 'connection_carrier'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -257,7 +259,7 @@ class ConnectionCarrier(db.Model):
     carry_mechanism: Mapped[CarryMechanism]
 
 
-class ConnectivityLoad(db.Model):
+class ConnectivityLoad(PresentationModel):
     __tablename__ = 'connectivity_load'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -269,7 +271,7 @@ class ConnectivityLoad(db.Model):
     peak_load_to_institutions: Mapped[Optional[int]]
 
 
-class ConnectivityGrowth(db.Model):
+class ConnectivityGrowth(PresentationModel):
     __tablename__ = 'connectivity_growth'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -278,7 +280,7 @@ class ConnectivityGrowth(db.Model):
     growth: Mapped[Decimal]
 
 
-class CommercialConnectivity(db.Model):
+class CommercialConnectivity(PresentationModel):
     __tablename__ = 'commercial_connectivity'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -290,7 +292,7 @@ class CommercialConnectivity(db.Model):
     university_spin_off: Mapped[Optional[CommercialConnectivityCoverage]]
 
 
-class CommercialChargingLevel(db.Model):
+class CommercialChargingLevel(PresentationModel):
     __tablename__ = 'commercial_charging_level'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -300,7 +302,7 @@ class CommercialChargingLevel(db.Model):
     direct_peering: Mapped[Optional[CommercialCharges]]
 
 
-class RemoteCampuses(db.Model):
+class RemoteCampuses(PresentationModel):
     __tablename__ = 'remote_campuses'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -309,7 +311,7 @@ class RemoteCampuses(db.Model):
     connections: Mapped[List[RemoteCampus]] = mapped_column(JSON)
 
 
-class DarkFibreLease(db.Model):
+class DarkFibreLease(PresentationModel):
     __tablename__ = 'dark_fibre_lease'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -320,7 +322,7 @@ class DarkFibreLease(db.Model):
     iru_duration: Mapped[Optional[Decimal]]
 
 
-class DarkFibreInstalled(db.Model):
+class DarkFibreInstalled(PresentationModel):
     __tablename__ = 'dark_fibre_installed'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -329,7 +331,7 @@ class DarkFibreInstalled(db.Model):
     fibre_length_in_country: Mapped[Optional[int]]
 
 
-class FibreLight(db.Model):
+class FibreLight(PresentationModel):
     __tablename__ = 'fibre_light'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -337,7 +339,7 @@ class FibreLight(db.Model):
     light_description: Mapped[str]
 
 
-class NetworkMapUrls(db.Model):
+class NetworkMapUrls(PresentationModel):
     __tablename__ = 'network_map_urls'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -345,7 +347,7 @@ class NetworkMapUrls(db.Model):
     urls: Mapped[json_str_list]
 
 
-class MonitoringTools(db.Model):
+class MonitoringTools(PresentationModel):
     __tablename__ = 'monitoring_tools'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -354,7 +356,7 @@ class MonitoringTools(db.Model):
     netflow_processing_description: Mapped[str]
 
 
-class PassiveMonitoring(db.Model):
+class PassiveMonitoring(PresentationModel):
     __tablename__ = 'passive_monitoring'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -363,7 +365,7 @@ class PassiveMonitoring(db.Model):
     method: Mapped[Optional[MonitoringMethod]]
 
 
-class TrafficStatistics(db.Model):
+class TrafficStatistics(PresentationModel):
     __tablename__ = 'traffic_statistics'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -372,7 +374,7 @@ class TrafficStatistics(db.Model):
     urls: Mapped[json_str_list]
 
 
-class SiemVendors(db.Model):
+class SiemVendors(PresentationModel):
     __tablename__ = 'siem_vendors'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -380,7 +382,7 @@ class SiemVendors(db.Model):
     vendor_names: Mapped[json_str_list]
 
 
-class CertificateProviders(db.Model):
+class CertificateProviders(PresentationModel):
     __tablename__ = 'certificate_providers'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -388,7 +390,7 @@ class CertificateProviders(db.Model):
     provider_names: Mapped[json_str_list]
 
 
-class WeatherMap(db.Model):
+class WeatherMap(PresentationModel):
     __tablename__ = 'weather_map'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -397,7 +399,7 @@ class WeatherMap(db.Model):
     url: Mapped[str]
 
 
-class PertTeam(db.Model):
+class PertTeam(PresentationModel):
     __tablename__ = 'pert_team'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -405,7 +407,7 @@ class PertTeam(db.Model):
     pert_team: Mapped[YesNoPlanned]
 
 
-class AlienWave(db.Model):
+class AlienWave(PresentationModel):
     __tablename__ = 'alien_wave'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -415,7 +417,7 @@ class AlienWave(db.Model):
     alien_wave_internal: Mapped[Optional[bool]]
 
 
-class Capacity(db.Model):
+class Capacity(PresentationModel):
     __tablename__ = 'capacity'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -424,7 +426,7 @@ class Capacity(db.Model):
     typical_backbone_capacity: Mapped[Optional[Decimal]]
 
 
-class ExternalConnections(db.Model):
+class ExternalConnections(PresentationModel):
     __tablename__ = 'external_connections'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -432,7 +434,7 @@ class ExternalConnections(db.Model):
     connections: Mapped[List[ExternalConnection]] = mapped_column(JSON)
 
 
-class NonREPeers(db.Model):
+class NonREPeers(PresentationModel):
     __tablename__ = 'non_r_and_e_peers'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -440,7 +442,7 @@ class NonREPeers(db.Model):
     nr_of_non_r_and_e_peers: Mapped[int]
 
 
-class TrafficRatio(db.Model):
+class TrafficRatio(PresentationModel):
     __tablename__ = 'traffic_ratio'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -449,7 +451,7 @@ class TrafficRatio(db.Model):
     commodity_percentage: Mapped[Decimal]
 
 
-class OpsAutomation(db.Model):
+class OpsAutomation(PresentationModel):
     __tablename__ = 'ops_automation'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -458,7 +460,7 @@ class OpsAutomation(db.Model):
     ops_automation_specifics: Mapped[str]
 
 
-class NetworkFunctionVirtualisation(db.Model):
+class NetworkFunctionVirtualisation(PresentationModel):
     __tablename__ = 'network_function_virtualisation'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -467,7 +469,7 @@ class NetworkFunctionVirtualisation(db.Model):
     nfv_specifics: Mapped[json_str_list]
 
 
-class NetworkAutomation(db.Model):
+class NetworkAutomation(PresentationModel):
     __tablename__ = 'network_automation'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
@@ -476,7 +478,7 @@ class NetworkAutomation(db.Model):
     network_automation_specifics: Mapped[json_str_list]
 
 
-class Service(db.Model):
+class Service(PresentationModel):
     __tablename__ = 'service'
     name_key: Mapped[str128_pk]
     name: Mapped[str128]
@@ -484,7 +486,7 @@ class Service(db.Model):
     description: Mapped[str]
 
 
-class NRENService(db.Model):
+class NRENService(PresentationModel):
     __tablename__ = 'nren_service'
     nren_id: Mapped[int_pk_fkNREN]
     nren: Mapped[NREN] = relationship(lazy='joined')
diff --git a/compendium_v2/migrations/surveymodels/survey_model_2024.json b/compendium_v2/migrations/surveymodels/survey_model_2024.json
index 4a45973c77a8c6308531c7ca3efa3433981ea9b9..0348979daea9197123cfa0534fbda61c12e4f583 100644
--- a/compendium_v2/migrations/surveymodels/survey_model_2024.json
+++ b/compendium_v2/migrations/surveymodels/survey_model_2024.json
@@ -1,2772 +1,2774 @@
 {
-    "title": "Compendium",
-    "logoPosition": "right",
-    "pages": [
-      {
-        "name": "organization",
-        "elements": [
-          {
-            "type": "panel",
-            "name": "panel1",
-            "elements": [
-              {
-                "type": "text",
-                "name": "budget",
-                "title": "What is your NREN's budget for {surveyyear} in million euro?",
-                "description": "Note that this question has changed slightly and now asks for the year we are still in. If your budget is not per calendar year, please provide figures for the budget that covers the largest part of {surveyyear} including GEANT subsidy. When responding to this question, please include all parts of your organisation serving your users, bearing in mind the services you deliver, as referenced later in the survey.",
-                "validators": [
-                  {
-                    "type": "numeric",
-                    "minValue": 0
-                  }
-                ]
-              },
-              {
-                "type": "multipletext",
-                "name": "income_sources",
-                "title": "Estimate (in % of income) the sources of your NREN-related income for {surveyyear}.",
-                "description": "European Funding should include GÉANT funding.",
-                "validators": [
-                  {
-                    "type": "expression",
-                    "text": "The percentages should add up to 100",
-                    "expression": "(sum({income_sources.client_institutions}, {income_sources.gov_public_bodies}, {income_sources.european_funding}, {income_sources.commercial}, {income_sources.other}) = 100) or ({income_sources} empty)"
-                  }
-                ],
-                "items": [
-                  {
-                    "name": "client_institutions",
-                    "title": "Client institutions (universities/schools/research institutes/commercial/other)",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0,
-                        "maxValue": 100
-                      }
-                    ]
-                  },
-                  {
-                    "name": "gov_public_bodies",
-                    "title": "Government/public bodies",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0,
-                        "maxValue": 100
-                      }
-                    ]
-                  },
-                  {
-                    "name": "european_funding",
-                    "title": "European funding",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0,
-                        "maxValue": 100
-                      }
-                    ]
-                  },
-                  {
-                    "name": "commercial",
-                    "title": "Commercial services (e.g. domain reg, security)",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0,
-                        "maxValue": 100
-                      }
-                    ]
-                  },
-                  {
-                    "name": "other",
-                    "title": "Other",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0,
-                        "maxValue": 100
-                      }
-                    ]
-                  }
-                ]
-              },
-              {
-                "type": "dropdown",
-                "name": "charging_mechanism",
-                "title": "How do you charge your client institutions?",
-                "choices": [
-                  {
-                    "value": "no_charge",
-                    "text": "We do not charge them directly"
-                  },
-                  {
-                    "value": "flat_fee",
-                    "text": "We charge a flat fee, based on bandwidth"
-                  },
-                  {
-                    "value": "usage_based_fee",
-                    "text": "We charge a usage-based fee"
-                  },
-                  {
-                    "value": "combination",
-                    "text": "We use a combination of flat fee and usage-based fee"
-                  },
-                  {
-                    "value": "other",
-                    "text": "Other"
-                  }
-                ]
-              }
-            ],
-            "title": "Budget, Income and Billing"
-          },
-          {
-            "type": "panel",
-            "name": "staff",
-            "elements": [
-              {
-                "type": "multipletext",
-                "name": "staff_employment_type",
-                "title": "What is the number of staff engaged in the NREN activities? (In Full Time Equivalent (FTEs))",
-                "description": "When responding to this question, please include all parts of your organisation serving your users, bearing in mind the services you deliver, as referenced later in the survey.",
-                "items": [
-                  {
-                    "name": "permanent_fte",
-                    "title": "Permanent staff",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0
-                      }
-                    ]
-                  },
-                  {
-                    "name": "subcontracted_fte",
-                    "title": "Subcontracted staff",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0
-                      }
-                    ]
-                  }
-                ]
-              },
-              {
-                "type": "multipletext",
-                "name": "staff_roles",
-                "title": "How much FTE is devoted to roles in the following functional areas?",
-                "items": [
-                  {
-                    "name": "technical_fte",
-                    "title": "Technical roles",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0
-                      }
-                    ]
-                  },
-                  {
-                    "name": "nontechnical_fte",
-                    "title": "Non-technical roles",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0
-                      }
-                    ]
-                  }
-                ]
-              },
-              {
-                "type": "radiogroup",
-                "name": "parent_organization",
-                "title": "Is your NREN part of a larger organisation (e.g. ministry, university)?",
-                "choices": [
-                  "Yes",
-                  "No"
-                ],
-                "showClearButton": true
-              },
-              {
-                "type": "text",
-                "name": "parent_organization_name",
-                "visibleIf": "{parent_organization} = 'Yes'",
-                "indent": 1,
-                "title": "What is the name of this larger organisation?"
-              },
-              {
-                "type": "radiogroup",
-                "name": "suborganizations",
-                "title": "Does your NREN have sub-organisations?",
-                "choices": [
-                  "Yes",
-                  "No"
-                ],
-                "showClearButton": true
-              },
-              {
-                "type": "matrixdynamic",
-                "name": "suborganization_details",
-                "visibleIf": "{suborganizations} = 'Yes'",
-                "indent": 1,
-                "title": "Please fill in the details:",
-                "columns": [
-                  {
-                    "name": "suborganization_name",
-                    "title": "Name of your sub-organisation?",
-                    "cellType": "text",
-                    "isRequired": true
-                  },
-                  {
-                    "name": "suborganization_role",
-                    "title": "Role of your sub-organisation?",
-                    "cellType": "dropdown",
-                    "choices": [
-                      {
-                        "value": "idc",
-                        "text": "IDC federation"
-                      },
-                      {
-                        "value": "hpc",
-                        "text": "HPC centre"
-                      }
-                    ],
-                    "showOtherItem": true
-                  }
-                ],
-                "rowCount": 1,
-                "maxRowCount": 30
-              },
-              {
-                "type": "radiogroup",
-                "name": "ec_projects",
-                "title": "Other than GÉANT, is your NREN involved in other EC projects?",
-                "choices": [
-                  "Yes",
-                  "No"
-                ],
-                "showClearButton": true
-              },
-              {
-                "type": "matrixdynamic",
-                "name": "ec_project_names",
-                "visibleIf": "{ec_projects} = 'Yes'",
-                "indent": 1,
-                "title": "Please list the name of the projects",
-                "description": "Use one line per project.",
-                "columns": [
-                  {
-                    "name": "ec_project_name",
-                    "title": "Project name",
-                    "cellType": "text"
-                  }
-                ],
-                "rowCount": 1
-              }
-            ],
-            "title": "Staff and Projects"
-          },
-          {
-            "type": "panel",
-            "name": "basic_information",
-            "elements": [
-              {
-                "type": "text",
-                "name": "full_name_english",
-                "title": "Full name of the organisation (in English):"
-              },
-              {
-                "type": "text",
-                "name": "full_name_national_languages",
-                "title": "Full name of the organisation in the national language(s):",
-                "description": "Please use the Latin alphabet."
-              },
-              {
-                "type": "text",
-                "name": "abbreviation_english",
-                "title": "Abbreviation in English (if applicable):",
-                "description": "Please always provide this information."
-              },
-              {
-                "type": "text",
-                "name": "abbreviation_national_languages",
-                "title": "Abbreviation in the national language(s) (if applicable):",
-                "description": "Please always provide this information. Please use the Latin alphabet."
-              },
-              {
-                "type": "text",
-                "name": "street_name_and_number",
-                "title": "Number and street name:"
-              },
-              {
-                "type": "text",
-                "name": "city",
-                "title": "Postal town (county):"
-              },
-              {
-                "type": "text",
-                "name": "postal_code",
-                "title": "Postal Code:"
-              },
-              {
-                "type": "text",
-                "name": "country",
-                "title": "Country (in English):"
-              },
-              {
-                "type": "text",
-                "name": "phone_number",
-                "title": "Phone number:",
-                "inputType": "tel"
-              },
-              {
-                "type": "text",
-                "name": "email_address",
-                "title": "General email address:",
-                "validators": [
-                  {
-                    "type": "email"
-                  }
-                ],
-                "inputType": "email"
-              },
-              {
-                "type": "text",
-                "name": "website",
-                "title": "Website:",
-                "validators": [
-                  {
-                    "type": "expression",
-                    "text": "Please provide a single valid website url including http:// or https://",
-                    "expression": "validateWebsiteUrl({website})"
-                  }
-                ],
-                "inputType": "url"
-              }
-            ],
-            "title": "Basic Information"
-          },
-          {
-            "type": "comment",
-            "name": "organization_comments",
-            "title": "Comments regarding this section:"
-          }
-        ],
-        "title": "Organisation"
-      },
-      {
-        "name": "standards_and_policies",
-        "elements": [
-          {
-            "type": "panel",
-            "name": "policy",
-            "elements": [
-              {
-                "type": "radiogroup",
-                "name": "corporate_strategy",
-                "title": "Have you made any updates to your corporate strategy over the last year?",
-                "choices": [
-                  "Yes",
-                  "No"
-                ],
-                "showClearButton": true
-              },
-              {
-                "type": "text",
-                "name": "corporate_strategy_url",
-                "title": "Please provide the URL for your latest (corporate) strategic plan?",
-                "validators": [
-                  {
-                    "type": "expression",
-                    "text": "Please provide a single valid website url including http:// or https://",
-                    "expression": "validateWebsiteUrl({corporate_strategy_url})"
-                  }
-                ],
-                "inputType": "url"
-              },
-              {
-                "type": "matrixdropdown",
-                "name": "policies",
-                "title": "Does your NREN have the following policies?",
-                "hideCheckboxLabels": true,
-                "columns": [
-                  {
-                    "name": "available",
-                    "title": "Policy available",
-                    "cellType": "checkbox",
-                    "choices": [
-                      {
-                        "value": "yes",
-                        "text": "Yes"
-                      }
-                    ]
-                  },
-                  {
-                    "name": "url",
-                    "title": "Please provide the URL",
-                    "cellType": "text",
-                    "visibleIf": "{row.available} = ['yes']",
-                    "validators": [
-                      {
-                        "type": "expression",
-                        "text": "Please provide a single valid website url including http:// or https://",
-                        "expression": "validateWebsiteUrl({row.url})"
-                      }
-                    ],
-                    "inputType": "url"
-                  }
-                ],
-                "cellType": "checkbox",
-                "rows": [
-                  {
-                    "value": "environmental_policy",
-                    "text": "Environmental Policy"
-                  },
-                  {
-                    "value": "equal_opportunity_policy",
-                    "text": "Equality Opportunity Policy"
-                  },
-                  {
-                    "value": "gender_equality_policy",
-                    "text": "Gender Equality Plan"
-                  },
-                  {
-                    "value": "connectivity_policy",
-                    "text": "Connectivity Policy"
-                  },
-                  {
-                    "value": "acceptable_use_policy",
-                    "text": "Acceptable Use Policy (AUP)"
-                  },
-                  {
-                    "value": "privacy_notice",
-                    "text": "Organisational Privacy Policy"
-                  },
-                  {
-                    "value": "data_protection_contact",
-                    "text": "Dedicated contact for data protection, privacy or GDPR queries"
-                  }
-                ],
-                "rowTitleWidth": "20%"
-              },
-              {
-                "type": "radiogroup",
-                "name": "central_software_procurement",
-                "title": "Do you centrally procure software for your customers?",
-                "choices": [
-                  "Yes",
-                  "No"
-                ],
-                "showClearButton": true
-              },
-              {
-                "type": "text",
-                "name": "central_procurement_amount",
-                "visibleIf": "{central_software_procurement} = 'yes'",
-                "indent": 1,
-                "title": "What is the total amount (in Euro) that you procured in {surveyyear} or {previousyear}/{surveyyear} on behalf of your customers?",
-                "validators": [
-                  {
-                    "type": "numeric",
-                    "minValue": 0
-                  },
-                  {
-                    "type": "regex",
-                    "text": "Please round to a whole number",
-                    "regex": "^[0-9]*$"
-                  }
-                ]
-              },
-              {
-                "type": "radiogroup",
-                "name": "formal_service_management_framework",
-                "title": "Does your NREN operate a formal service management framework for all of your services?",
-                "choices": [
-                  "Yes",
-                  "No"
-                ],
-                "showClearButton": true
-              },
-              {
-                "type": "radiogroup",
-                "name": "service_level_targets",
-                "title": "Are Service Level Targets available for your NREN services?",
-                "choices": [
-                  "Yes",
-                  "No"
-                ],
-                "showClearButton": true
-              },
-              {
-                "type": "matrixdropdown",
-                "name": "service_matrix",
-                "title": "Which service types are available for which user types?",
-                "columns": [
-                  {
-                    "name": "service_types",
-                    "title": "Service types",
-                    "cellType": "checkbox",
-                    "showInMultipleColumns": true,
-                    "choices": [
-                      {
-                        "value": "network_services",
-                        "text": "Network services"
-                      },
-                      {
-                        "value": "isp_support",
-                        "text": "ISP support"
-                      },
-                      {
-                        "value": "security",
-                        "text": "Security"
-                      },
-                      {
-                        "value": "identity",
-                        "text": "Identity/T&I"
-                      },
-                      {
-                        "value": "collaboration",
-                        "text": "Collaboration"
-                      },
-                      {
-                        "value": "multimedia",
-                        "text": "Multimedia"
-                      },
-                      {
-                        "value": "storage_and_hosting",
-                        "text": "Storage and Hosting"
-                      },
-                      {
-                        "value": "professional_services",
-                        "text": "Professional services"
-                      }
-                    ]
-                  }
-                ],
-                "rows": [
-                  {
-                    "value": "universities",
-                    "text": "Universities & Other (ISCED 6-8)"
-                  },
-                  {
-                    "value": "further_education",
-                    "text": "Further education (ISCED 4-5)"
-                  },
-                  {
-                    "value": "secondary_schools",
-                    "text": "Secondary schools (ISCED 2-3)"
-                  },
-                  {
-                    "value": "primary_schools",
-                    "text": "Primary schools (ISCED 1)"
-                  },
-                  {
-                    "value": "institutes",
-                    "text": "Research Institutes"
-                  },
-                  {
-                    "value": "cultural",
-                    "text": "Libraries, Museums, Archives, Cultural institutions"
-                  },
-                  {
-                    "value": "hospitals",
-                    "text": "Non-university public Hospitals"
-                  },
-                  {
-                    "value": "government",
-                    "text": "Government departments (national, regional, local)"
-                  },
-                  {
-                    "value": "iros",
-                    "text": "International (virtual) research organisations"
-                  },
-                  {
-                    "value": "for_profit_orgs",
-                    "text": "For-profit organisations"
-                  }
-                ]
-              },
-              {
-                "type": "radiogroup",
-                "name": "service_portfolio_eosc_portal",
-                "title": "Are any of the services in your service portfolio listed on the EOSC portal?",
-                "choices": [
-                  "Yes",
-                  "No"
-                ],
-                "showClearButton": true
-              },
-              {
-                "type": "matrixdynamic",
-                "name": "services_on_eosc_portal_list",
-                "visibleIf": "{service_portfolio_eosc_portal} = 'Yes'",
-                "indent": 1,
-                "title": "Can you list them?",
-                "columns": [
-                  {
-                    "name": "service_name",
-                    "title": "Service name",
-                    "cellType": "text"
-                  }
-                ],
-                "rowCount": 1,
-                "maxRowCount": 50
-              }
-            ],
-            "title": "Policy & Portfolio"
-          },
-          {
-            "type": "panel",
-            "name": "standards",
-            "elements": [
-              {
-                "type": "radiogroup",
-                "name": "audits",
-                "title": "Do you have external or internal audits of your information security management systems e.g. risk management and policies?",
-                "choices": [
-                  "Yes",
-                  "No"
-                ],
-                "showClearButton": true
-              },
-              {
-                "type": "text",
-                "name": "audit_specifics",
-                "visibleIf": "{audits} = 'yes'",
-                "indent": 1,
-                "title": "Please specify (for example a certified security auditor on ISO 27001 is performing the audits):"
-              },
-              {
-                "type": "radiogroup",
-                "name": "business_continuity_plans",
-                "title": "Do you have Business Continuity plans in place to ensure business continuation and operations?",
-                "choices": [
-                  "Yes",
-                  "No"
-                ],
-                "showClearButton": true
-              },
-              {
-                "type": "text",
-                "name": "business_continuity_plans_specifics",
-                "visibleIf": "{business_continuity_plans} = 'yes'",
-                "indent": 1,
-                "title": "Please specify if you comply with any international standard and if you test the continuity plans regularly."
-              },
-              {
-                "type": "radiogroup",
-                "name": "crisis_management_procedure",
-                "title": "Does your NREN have a formal crisis management procedure?",
-                "choices": [
-                  "Yes",
-                  "No"
-                ],
-                "showClearButton": true
-              },
-              {
-                "type": "checkbox",
-                "name": "crisis_exercises",
-                "title": "Does your NREN run or participate in operational security measures and other exercises including crisis awareness to train and test employees?",
-                "description": "Multiple answers possible, in the last 12 months",
-                "choices": [
-                  {
-                    "value": "geant_workshops",
-                    "text": "We participate in GÉANT crisis workshops such as CLAW"
-                  },
-                  {
-                    "value": "national_excercises",
-                    "text": "We participated in national crisis exercises "
-                  },
-                  {
-                    "value": "tabletop_exercises",
-                    "text": "We run our own tabletop exercises"
-                  },
-                  {
-                    "value": "simulation_excercises",
-                    "text": "We run our own simulation exercises"
-                  },
-                  {
-                    "value": "other_excercises",
-                    "text": " We have done/participated in other exercises or trainings"
-                  },
-                  {
-                    "value": "real_crisis",
-                    "text": "We had a real crisis"
-                  },
-                  {
-                    "value": "internal_security_programme",
-                    "text": "We run an internal security awareness programme"
-                  },
-                  {
-                    "value": "none",
-                    "text": "No, we have not done any crisis exercises or trainings"
-                  }
-                ]
-              },
-              {
-                "type": "checkbox",
-                "name": "security_controls",
-                "title": "Do you utilise security controls such as anti-virus, integrity checkers and systemic firewalls to protect your assets?",
-                "choices": [
-                  {
-                    "value": "anti_virus",
-                    "text": "Anti Virus"
-                  },
-                  {
-                    "value": "anti_spam",
-                    "text": "Anti-Spam"
-                  },
-                  {
-                    "value": "firewall",
-                    "text": "Firewall"
-                  },
-                  {
-                    "value": "ddos_mitigation",
-                    "text": "DDoS mitigation"
-                  },
-                  {
-                    "value": "monitoring",
-                    "text": "Network monitoring"
-                  },
-                  {
-                    "value": "ips_ids",
-                    "text": "IPS/IDS"
-                  },
-                  {
-                    "value": "acl",
-                    "text": "ACL"
-                  },
-                  {
-                    "value": "segmentation",
-                    "text": "Network segmentation"
-                  },
-                  {
-                    "value": "integrity_checking",
-                    "text": "Integrity checking"
-                  }
-                ],
-                "showOtherItem": true
-              }
-            ],
-            "title": "Standards"
-          },
-          {
-            "type": "comment",
-            "name": "policy_comments",
-            "title": "Comments regarding this section:"
-          }
-        ],
-        "title": "Standards & Policies"
-      },
-      {
-        "name": "connected_users",
-        "elements": [
-          {
-            "type": "panel",
-            "name": "panel2",
-            "elements": [
-              {
-                "type": "matrixdynamic",
-                "name": "connected_sites_lists",
-                "title": "Please provide the URLs of the webpages listing the institutions or organisations that are connected to the NREN, if available:",
-                "description": "Many NRENs have one or more pages on their website listing user institutions. Please provide these links here if you have them.",
-                "columns": [
-                  {
-                    "name": "connected_sites_url",
-                    "title": "Url:",
-                    "cellType": "text",
-                    "validators": [
-                      {
-                        "type": "expression",
-                        "text": "Please provide a single valid website url including http:// or https://",
-                        "expression": "validateWebsiteUrl({row.connected_sites_url})"
-                      }
-                    ],
-                    "inputType": "url"
-                  }
-                ],
-                "rowCount": 1,
-                "maxRowCount": 50
-              },
-              {
-                "type": "matrixdropdown",
-                "name": "connectivity_proportions",
-                "title": "Please give an estimate of the proportion of the institutions in each category for which your NREN provides IP connectivity: ",
-                "description": "For the questions in this section, please use the ISCED 2011 classification system (the UNESCO scheme for International Standard Classification of Education) as follows: * Level 8 - Doctorate or equivalent level * Level 7 - Masters or equivalent level * Level 6 - Bachelors or equivalent level * Level 5 - Short-cycle tertiary education * Level 4 - Post-secondary non-tertiary education. This can include, for example, short vocational training programmes * Levels 2 and 3: Secondary education * Level 1: Primary or basic education * Level 0: Pre-primary education",
-                "columns": [
-                  {
-                    "name": "covered",
-                    "title": "Does your remit cover connectivity to this institution type:",
-                    "cellType": "dropdown",
-                    "choices": [
-                      {
-                        "value": "yes_incl_other",
-                        "text": "Yes - including transit to other networks"
-                      },
-                      {
-                        "value": "yes_national_nren",
-                        "text": "Yes - national NREN access"
-                      },
-                      {
-                        "value": "sometimes",
-                        "text": "In some circumstances"
-                      },
-                      {
-                        "value": "no_policy",
-                        "text": "No - not eligible for policy reasons"
-                      },
-                      {
-                        "value": "no_financial",
-                        "text": "No - financial restrictions (NREN is unable to charge/cover costs)"
-                      },
-                      {
-                        "value": "no_other",
-                        "text": "No - other reason"
-                      },
-                      {
-                        "value": "unsure",
-                        "text": "Unsure/unclear"
-                      }
-                    ]
-                  },
-                  {
-                    "name": "nr_connected",
-                    "title": "Number of institutions connected in this category (actual number):",
-                    "cellType": "text",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0
-                      },
-                      {
-                        "type": "regex",
-                        "text": "Please use a whole number",
-                        "regex": "^[0-9]*$"
-                      }
-                    ]
-                  },
-                  {
-                    "name": "market_share_percentage",
-                    "title": "% market share of institutions connected in this category:",
-                    "cellType": "text",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0,
-                        "maxValue": 100
-                      }
-                    ]
-                  },
-                  {
-                    "name": "nr_of_users",
-                    "title": "Number of users served in this category (actual number):",
-                    "cellType": "text",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0
-                      },
-                      {
-                        "type": "regex",
-                        "text": "Please use a whole number",
-                        "regex": "^[0-9]*$"
-                      }
-                    ]
-                  }
-                ],
-                "rows": [
-                  {
-                    "value": "universities",
-                    "text": "Universities & Other (ISCED 6-8)"
-                  },
-                  {
-                    "value": "further_education",
-                    "text": "Further education (ISCED 4-5)"
-                  },
-                  {
-                    "value": "secondary_schools",
-                    "text": "Secondary schools (ISCED 2-3)"
-                  },
-                  {
-                    "value": "primary_schools",
-                    "text": "Primary schools (ISCED 1)"
-                  },
-                  {
-                    "value": "institutes",
-                    "text": "Research Institutes"
-                  },
-                  {
-                    "value": "cultural",
-                    "text": "Libraries, Museums, Archives, Cultural institutions"
-                  },
-                  {
-                    "value": "hospitals",
-                    "text": "Non-university public Hospitals"
-                  },
-                  {
-                    "value": "government",
-                    "text": "Government departments (national, regional, local)"
-                  },
-                  {
-                    "value": "iros",
-                    "text": "International (virtual) research organisations"
-                  },
-                  {
-                    "value": "for_profit_orgs",
-                    "text": "For-profit organisations"
-                  }
-                ]
-              },
-              {
-                "type": "matrixdropdown",
-                "name": "connectivity_level",
-                "title": "Level of IP connectivity by Institution type:",
-                "description": "This table explores the average level of connectivity for each type of institution to the NREN. Please enter the typical and the highest capacity at which institutions in this category are connected ( in Mbit/s). As a minimum, please provide this information for Universities and Research Institutes.",
-                "columns": [
-                  {
-                    "name": "typical_speed",
-                    "title": "Typical link speed (Mbit/s):",
-                    "cellType": "text",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0
-                      },
-                      {
-                        "type": "regex",
-                        "text": "Please use a whole number",
-                        "regex": "^[0-9]*$"
-                      }
-                    ]
-                  },
-                  {
-                    "name": "highest_speed",
-                    "title": "Highest speed link (Mbit/s):",
-                    "cellType": "text",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0
-                      },
-                      {
-                        "type": "regex",
-                        "text": "Please use a whole number",
-                        "regex": "^[0-9]*$"
-                      }
-                    ]
-                  },
-                  {
-                    "name": "highest_speed_connection_percentage",
-                    "title": "Proportionally how many institutions in this category are connected at the highest capacity? (%):",
-                    "cellType": "text",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0,
-                        "maxValue": 100
-                      }
-                    ]
-                  }
-                ],
-                "rows": [
-                  {
-                    "value": "universities",
-                    "text": "Universities & Other (ISCED 6-8)"
-                  },
-                  {
-                    "value": "further_education",
-                    "text": "Further education (ISCED 4-5)"
-                  },
-                  {
-                    "value": "secondary_schools",
-                    "text": "Secondary schools (ISCED 2-3)"
-                  },
-                  {
-                    "value": "primary_schools",
-                    "text": "Primary schools (ISCED 1)"
-                  },
-                  {
-                    "value": "institutes",
-                    "text": "Research Institutes"
-                  },
-                  {
-                    "value": "cultural",
-                    "text": "Libraries, Museums, Archives, Cultural institutions"
-                  },
-                  {
-                    "value": "hospitals",
-                    "text": "Non-university public Hospitals"
-                  },
-                  {
-                    "value": "government",
-                    "text": "Government departments (national, regional, local)"
-                  },
-                  {
-                    "value": "iros",
-                    "text": "International (virtual) research organisations"
-                  },
-                  {
-                    "value": "for_profit_orgs",
-                    "text": "For-profit organisations"
-                  }
-                ]
-              },
-              {
-                "type": "matrixdropdown",
-                "name": "traffic_carriers",
-                "title": "How is the traffic carried?",
-                "columns": [
-                  {
-                    "name": "carry_mechanism",
-                    "title": "Carry mechanism",
-                    "cellType": "dropdown",
-                    "choices": [
-                      {
-                        "value": "nren_local_loops",
-                        "text": "NREN provides the local loops"
-                      },
-                      {
-                        "value": "regional_nren_backbone",
-                        "text": "Traffic carried to the backbone by regional NREN"
-                      },
-                      {
-                        "value": "commercial_provider_backbone",
-                        "text": "Traffic carried to the backbone by commercial providers"
-                      },
-                      {
-                        "value": "man",
-                        "text": "MAN"
-                      },
-                      {
-                        "value": "other",
-                        "text": "Other"
-                      }
-                    ]
-                  }
-                ],
-                "rows": [
-                  {
-                    "value": "universities",
-                    "text": "Universities & Other (ISCED 6-8)"
-                  },
-                  {
-                    "value": "further_education",
-                    "text": "Further education (ISCED 4-5)"
-                  },
-                  {
-                    "value": "secondary_schools",
-                    "text": "Secondary schools (ISCED 2-3)"
-                  },
-                  {
-                    "value": "primary_schools",
-                    "text": "Primary schools (ISCED 1)"
-                  },
-                  {
-                    "value": "institutes",
-                    "text": "Research Institutes"
-                  },
-                  {
-                    "value": "cultural",
-                    "text": "Libraries, Museums, Archives, Cultural institutions"
-                  },
-                  {
-                    "value": "hospitals",
-                    "text": "Non-university public Hospitals"
-                  },
-                  {
-                    "value": "government",
-                    "text": "Government departments (national, regional, local)"
-                  },
-                  {
-                    "value": "iros",
-                    "text": "International (virtual) research organisations"
-                  },
-                  {
-                    "value": "for_profit_orgs",
-                    "text": "For-profit organisations"
-                  }
-                ]
-              },
-              {
-                "type": "matrixdropdown",
-                "name": "traffic_load",
-                "title": "What are the traffic loads in Mbit/s?",
-                "columns": [
-                  {
-                    "name": "average_from_institutions_to_network",
-                    "title": "Average load from institutions to the network",
-                    "cellType": "text",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0
-                      },
-                      {
-                        "type": "regex",
-                        "text": "Please round to a whole number",
-                        "regex": "^[0-9]*$"
-                      }
-                    ]
-                  },
-                  {
-                    "name": "average_to_institutions_from_network",
-                    "title": "Average load to institutions from the network",
-                    "cellType": "text",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0
-                      },
-                      {
-                        "type": "regex",
-                        "text": "Please round to a whole number",
-                        "regex": "^[0-9]*$"
-                      }
-                    ]
-                  },
-                  {
-                    "name": "peak_from_institutions_to_network",
-                    "title": "Peak load from institutions to the network",
-                    "cellType": "text",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0
-                      },
-                      {
-                        "type": "regex",
-                        "text": "Please round to a whole number",
-                        "regex": "^[0-9]*$"
-                      }
-                    ]
-                  },
-                  {
-                    "name": "peak_to_institutions_from_network",
-                    "title": "Peak load to institutions from the network",
-                    "cellType": "text",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0
-                      },
-                      {
-                        "type": "regex",
-                        "text": "Please round to a whole number",
-                        "regex": "^[0-9]*$"
-                      }
-                    ]
-                  }
-                ],
-                "rows": [
-                  {
-                    "value": "universities",
-                    "text": "Universities & Other (ISCED 6-8)"
-                  },
-                  {
-                    "value": "further_education",
-                    "text": "Further education (ISCED 4-5)"
-                  },
-                  {
-                    "value": "secondary_schools",
-                    "text": "Secondary schools (ISCED 2-3)"
-                  },
-                  {
-                    "value": "primary_schools",
-                    "text": "Primary schools (ISCED 1)"
-                  },
-                  {
-                    "value": "institutes",
-                    "text": "Research Institutes"
-                  },
-                  {
-                    "value": "cultural",
-                    "text": "Libraries, Museums, Archives, Cultural institutions"
-                  },
-                  {
-                    "value": "hospitals",
-                    "text": "Non-university public Hospitals"
-                  },
-                  {
-                    "value": "government",
-                    "text": "Government departments (national, regional, local)"
-                  },
-                  {
-                    "value": "iros",
-                    "text": "International (virtual) research organisations"
-                  },
-                  {
-                    "value": "for_profit_orgs",
-                    "text": "For-profit organisations"
-                  }
-                ]
-              },
-              {
-                "type": "matrixdropdown",
-                "name": "traffic_growth",
-                "title": "What do you expect the traffic growth to be in the next 3 years?",
-                "columns": [
-                  {
-                    "name": "growth_rate",
-                    "title": "% growth",
-                    "cellType": "text",
-                    "validators": [
-                      {
-                        "type": "numeric"
-                      }
-                    ]
-                  }
-                ],
-                "rows": [
-                  {
-                    "value": "universities",
-                    "text": "Universities & Other (ISCED 6-8)"
-                  },
-                  {
-                    "value": "further_education",
-                    "text": "Further education (ISCED 4-5)"
-                  },
-                  {
-                    "value": "secondary_schools",
-                    "text": "Secondary schools (ISCED 2-3)"
-                  },
-                  {
-                    "value": "primary_schools",
-                    "text": "Primary schools (ISCED 1)"
-                  },
-                  {
-                    "value": "institutes",
-                    "text": "Research Institutes"
-                  },
-                  {
-                    "value": "cultural",
-                    "text": "Libraries, Museums, Archives, Cultural institutions"
-                  },
-                  {
-                    "value": "hospitals",
-                    "text": "Non-university public Hospitals"
-                  },
-                  {
-                    "value": "government",
-                    "text": "Government departments (national, regional, local)"
-                  },
-                  {
-                    "value": "iros",
-                    "text": "International (virtual) research organisations"
-                  },
-                  {
-                    "value": "for_profit_orgs",
-                    "text": "For-profit organisations"
-                  }
-                ]
-              }
-            ],
-            "title": "CONNECTED USERS"
-          },
-          {
-            "type": "panel",
-            "name": "connected_users_commercial",
-            "elements": [
-              {
-                "type": "matrixdropdown",
-                "name": "commercial_organizations",
-                "title": "What types of commercial organisations do you connect?",
-                "columns": [
-                  {
-                    "name": "connection",
-                    "title": "Connection",
-                    "cellType": "dropdown",
-                    "choices": [
-                      {
-                        "value": "yes_incl_other",
-                        "text": "Yes - Including transit to other networks"
-                      },
-                      {
-                        "value": "yes_national_nren",
-                        "text": "Yes - National NREN access only"
-                      },
-                      {
-                        "value": "yes_if_sponsored",
-                        "text": "Yes - only if sponsored by a connected institution"
-                      },
-                      {
-                        "value": "no_but_direct_peering",
-                        "text": "No - but we offer a direct or IX peering"
-                      },
-                      {
-                        "value": "no_policy",
-                        "text": "No - not eligible for policy reasons"
-                      },
-                      {
-                        "value": "no_financial",
-                        "text": "No - financial restrictions (NREN is unable to charge/recover costs)"
-                      },
-                      {
-                        "value": "no_other",
-                        "text": "No - other reason / unsure"
-                      }
-                    ]
-                  }
-                ],
-                "rows": [
-                  {
-                    "value": "commercial_r_e",
-                    "text": "Commercial R&E traffic only"
-                  },
-                  {
-                    "value": "commercial_general",
-                    "text": "Commercial general"
-                  },
-                  {
-                    "value": "commercial_collaboration",
-                    "text": "Commercial for collaboration only (project/time limited)"
-                  },
-                  {
-                    "value": "commercial_service_provider",
-                    "text": "Commercial Service Provider"
-                  },
-                  {
-                    "value": "university_spin_off",
-                    "text": "University Spin Off/Incubator"
-                  }
-                ]
-              },
-              {
-                "type": "matrixdropdown",
-                "name": "commercial_charging_levels",
-                "title": "What are the typical charging levels for the following types of commercial connections? Please tick all that apply:",
-                "columns": [
-                  {
-                    "name": "charging_level",
-                    "title": "Charging level",
-                    "cellType": "dropdown",
-                    "choices": [
-                      {
-                        "value": "higher_than_r_e_charges",
-                        "text": "Charges typically higher than for R&E users"
-                      },
-                      {
-                        "value": "same_as_r_e_charges",
-                        "text": "Same charging model as for R&E users"
-                      },
-                      {
-                        "value": "no_charges_if_r_e_requested",
-                        "text": "No charges applied if requested by R&E users"
-                      },
-                      {
-                        "value": "lower_than_r_e_charges",
-                        "text": "Charges typically lower than for R&E users"
-                      }
-                    ]
-                  }
-                ],
-                "rows": [
-                  {
-                    "value": "collaboration",
-                    "text": "Connection to your network for collaboration with R&E users"
-                  },
-                  {
-                    "value": "services",
-                    "text": "Connection to your network for supplying services for R&E"
-                  },
-                  {
-                    "value": "peering",
-                    "text": "Direct peering (e.g. direct peering or cloud peering)"
-                  }
-                ]
-              },
-              {
-                "type": "radiogroup",
-                "name": "remote_campuses",
-                "title": "Do you provide connectivity to any remote campuses in other countries?",
-                "choices": [
-                  "Yes",
-                  "No"
-                ],
-                "showClearButton": true
-              },
-              {
-                "type": "matrixdynamic",
-                "name": "remote_campuses_specifics",
-                "visibleIf": "{remote_campuses} = 'Yes'",
-                "indent": 1,
-                "title": "Please specify:",
-                "columns": [
-                  {
-                    "name": "country",
-                    "title": "Which Country",
-                    "cellType": "text",
-                    "isRequired": true
-                  },
-                  {
-                    "name": "connected",
-                    "title": "Connected to local R&E network",
-                    "cellType": "radiogroup",
-                    "choices": [
-                      "Yes",
-                      "No"
-                    ]
-                  }
-                ],
-                "rowCount": 0,
-                "maxRowCount": 50
-              },
-              {
-                "type": "comment",
-                "name": "connected_users_comments",
-                "title": "Comments regarding this section:"
-              }
-            ],
-            "title": "CONNECTED USERS - COMMERCIAL"
-          }
-        ],
-        "title": "Connected Users"
-      },
-      {
-        "name": "network",
-        "elements": [
-          {
-            "type": "panel",
-            "name": "connectivity",
-            "elements": [
-              {
-                "type": "radiogroup",
-                "name": "dark_fibre_lease",
-                "title": "Does your NREN have an IRU or lease of dark fibre?",
-                "description": "An Indefeasible Right of Use (IRU) is essentially a long-term lease of a portion of the capacity of a cable. Please do not include fibre that you have installed and own yourself. This is covered in a later question.",
-                "choices": [
-                  "Yes",
-                  "No"
-                ],
-                "showClearButton": true
-              },
-              {
-                "type": "text",
-                "name": "dark_fibre_lease_kilometers_inside_country",
-                "visibleIf": "{dark_fibre_lease} = 'Yes'",
-                "indent": 1,
-                "title": "Please state the number of kilometres of such fibre in your country:",
-                "description": "Please include only the fibre inside your country for this answer. The distance is the number of kilometers of your fibre pairs or if you are using bidirectional traffic on a single fibre please treat this as a fibre pair.",
-                "validators": [
-                  {
-                    "type": "numeric",
-                    "minValue": 0
-                  },
-                  {
-                    "type": "regex",
-                    "text": "Please round to a whole number",
-                    "regex": "^[0-9]*$"
-                  }
-                ]
-              },
-              {
-                "type": "text",
-                "name": "dark_fibre_lease_kilometers_outside_country",
-                "visibleIf": "{dark_fibre_lease} = 'Yes'",
-                "indent": 1,
-                "title": "Please state the number of kilometers of such fibre that is outside your country:",
-                "validators": [
-                  {
-                    "type": "numeric",
-                    "minValue": 0
-                  },
-                  {
-                    "type": "regex",
-                    "text": "Please round to a whole number",
-                    "regex": "^[0-9]*$"
-                  }
-                ]
-              },
-              {
-                "type": "text",
-                "name": "dark_fibre_lease_duration",
-                "visibleIf": "{dark_fibre_lease} = 'Yes'",
-                "indent": 1,
-                "title": "What is the average duration, in years, of your IRU?",
-                "validators": [
-                  {
-                    "type": "numeric",
-                    "minValue": 0
-                  }
-                ]
-              },
-              {
-                "type": "radiogroup",
-                "name": "dark_fibre_nren",
-                "title": "Has your NREN physically laid any dark fibre cables in your network?",
-                "description": "Please include only cables that you laid yourself. If this cable was installed and is owned by a third party, this is included in a previous question.",
-                "choices": [
-                  "Yes",
-                  "No"
-                ],
-                "showClearButton": true
-              },
-              {
-                "type": "text",
-                "name": "dark_fibre_nren_kilometers_inside_country",
-                "visibleIf": "{dark_fibre_nren} = 'Yes'",
-                "indent": 1,
-                "title": "Please state the number of kilometers of such fibre in your network:",
-                "description": "Please include only the fibre inside your country for this answer. The distance is the number of kilometers of your fibre pairs, or If you are using bi-directional traffic on a single fibre please treat this as a fibre pair.",
-                "validators": [
-                  {
-                    "type": "numeric",
-                    "minValue": 0
-                  },
-                  {
-                    "type": "regex",
-                    "text": "Please round to a whole number",
-                    "regex": "^[0-9]*$"
-                  }
-                ]
-              },
-              {
-                "type": "radiogroup",
-                "name": "fibre_light",
-                "title": "How do you light your fibre network:",
-                "choices": [
-                  {
-                    "value": "nren_owns_and_operates",
-                    "text": "NREN owns and operates equipment"
-                  },
-                  {
-                    "value": "nren_owns_outsourced_operation",
-                    "text": "NREN owns equipment and operation is outsourced"
-                  },
-                  {
-                    "value": "outsourced_ownership_and_operation",
-                    "text": "Ownership and management are out-sourced (turn-key model)"
-                  }
-                ],
-                "showOtherItem": true,
-                "showClearButton": true
-              },
-              {
-                "type": "matrixdynamic",
-                "name": "network_map_urls",
-                "title": "Please provide a network map for layers 1, 2 and 3 of your network:",
-                "columns": [
-                  {
-                    "name": "network_map_url",
-                    "title": "Url:",
-                    "cellType": "text",
-                    "validators": [
-                      {
-                        "type": "expression",
-                        "text": "Please provide a single valid website url including http:// or https://",
-                        "expression": "validateWebsiteUrl({row.network_map_url})"
-                      }
-                    ],
-                    "inputType": "url"
-                  }
-                ],
-                "rowCount": 1,
-                "maxRowCount": 50
-              }
-            ],
-            "title": "Connectivity"
-          },
-          {
-            "type": "panel",
-            "name": "performance",
-            "elements": [
-              {
-                "type": "checkbox",
-                "name": "monitoring_tools",
-                "title": "Which tools do you offer your client institutions for monitoring or troubleshooting the network?",
-                "choices": [
-                  {
-                    "value": "looking_glass",
-                    "text": "Looking Glass"
-                  },
-                  {
-                    "value": "status_dashboard",
-                    "text": "Network or Services Status Dashboard"
-                  },
-                  {
-                    "value": "historical_traffic_volumes",
-                    "text": "Historical traffic volume information"
-                  },
-                  {
-                    "value": "netflow_analysis",
-                    "text": "Netflow analysis tool"
-                  }
-                ],
-                "showOtherItem": true
-              },
-              {
-                "type": "text",
-                "name": "netflow_vendors",
-                "title": "If you process NetFlow, please, indicate the system name and vendor:"
-              },
-              {
-                "type": "radiogroup",
-                "name": "passive_monitoring",
-                "title": "Do you passively monitor international traffic?",
-                "choices": [
-                  "Yes",
-                  "No"
-                ],
-                "showClearButton": true
-              },
-              {
-                "type": "radiogroup",
-                "name": "passive_monitoring_tech",
-                "visibleIf": "{passive_monitoring} = 'Yes'",
-                "indent": 1,
-                "title": "Do you use:",
-                "choices": [
-                  {
-                    "value": "span_ports",
-                    "text": "SPAN ports"
-                  },
-                  {
-                    "value": "taps",
-                    "text": "Passive optical TAPS"
-                  },
-                  {
-                    "value": "both",
-                    "text": "Both"
-                  }
-                ],
-                "showClearButton": true
-              },
-              {
-                "type": "radiogroup",
-                "name": "traffic_statistics",
-                "title": "Do you have traffic statistics on your website?",
-                "choices": [
-                  "Yes",
-                  "No"
-                ],
-                "showClearButton": true
-              },
-              {
-                "type": "matrixdynamic",
-                "name": "traffic_statistics_urls",
-                "visibleIf": "{traffic_statistics} = 'Yes'",
-                "indent": 1,
-                "title": "Please give the URL(s):",
-                "columns": [
-                  {
-                    "name": "traffic_statistics_url",
-                    "title": "Url:",
-                    "cellType": "text",
-                    "validators": [
-                      {
-                        "type": "expression",
-                        "text": "Please provide a single valid website url including http:// or https://",
-                        "expression": "validateWebsiteUrl({row.traffic_statistics_url})"
-                      }
-                    ],
-                    "inputType": "url"
-                  }
-                ],
-                "rowCount": 1,
-                "maxRowCount": 50
-              },
-              {
-                "type": "checkbox",
-                "name": "siem_soc_vendor",
-                "title": "If you use a SIEM/SOC system, please indicate the name of the vendor you use here:",
-                "choices": [
-                  "Splunk",
-                  "IBM Qradar",
-                  "Exabeam",
-                  "LogRythm",
-                  "Securonix"
-                ],
-                "showOtherItem": true
-              },
-              {
-                "type": "checkbox",
-                "name": "certificate_service",
-                "title": "Which certificate service do you use?",
-                "choices": [
-                  "TCS",
-                  "Digicert",
-                  {
-                    "value": "Sectigo",
-                    "text": "Sectigo (outside of TCS)"
-                  },
-                  "Let's Encrypt",
-                  "Entrust Datacard"
-                ],
-                "showOtherItem": true
-              },
-              {
-                "type": "radiogroup",
-                "name": "network_weather",
-                "title": "Do you have an online weather map of your network?",
-                "choices": [
-                  "Yes",
-                  "No"
-                ],
-                "showClearButton": true
-              },
-              {
-                "type": "text",
-                "name": "network_weather_url",
-                "visibleIf": "{network_weather} = 'Yes'",
-                "indent": 1,
-                "title": "Please give the URL:",
-                "validators": [
-                  {
-                    "type": "expression",
-                    "text": "Please provide a single valid website url including http:// or https://",
-                    "expression": "validateWebsiteUrl({network_weather_url})"
-                  }
-                ],
-                "inputType": "url"
-              },
-              {
-                "type": "radiogroup",
-                "name": "pert_team",
-                "title": "Do you run a PERT team?",
-                "choices": [
-                  "Yes",
-                  "No",
-                  "Planned"
-                ],
-                "showClearButton": true
-              }
-            ],
-            "title": "PERFORMANCE MONITORING AND MANAGEMENT"
-          },
-          {
-            "type": "panel",
-            "name": "alienwave",
-            "elements": [
-              {
-                "type": "radiogroup",
-                "name": "alienwave_services",
-                "title": "Does your NREN make use of alien wavelength/lightpath services provided by third parties?",
-                "description": "This does not include alien waves used internally inside your network e.g. coloured optics on routers as they are covered in a later question.",
-                "choices": [
-                  "Yes",
-                  "No",
-                  "Planned"
-                ],
-                "showClearButton": true
-              },
-              {
-                "type": "text",
-                "name": "alienwave_services_number",
-                "visibleIf": "{alienwave_services} = 'yes'",
-                "indent": 1,
-                "title": "Please state the number of individual alien wavelength services:",
-                "validators": [
-                  {
-                    "type": "numeric",
-                    "minValue": 0
-                  },
-                  {
-                    "type": "regex",
-                    "text": "Please round to a whole number",
-                    "regex": "^[0-9]*$"
-                  }
-                ]
-              },
-              {
-                "type": "radiogroup",
-                "name": "alienwave_internal",
-                "title": "Are you using alien waves internally in your network?",
-                "description": "This includes for example alien waves used between two equipment vendors e.g. coloured optics on routers carried over DWDM equipment.",
-                "choices": [
-                  "Yes",
-                  "No"
-                ],
-                "showClearButton": true
-              }
-            ],
-            "title": "Alienwave",
-            "description": "Pure optical connectivity services provided by the NREN without a known framing, such as foreign, alien wavelength."
-          },
-          {
-            "type": "panel",
-            "name": "capacity",
-            "elements": [
-              {
-                "type": "text",
-                "name": "max_capacity",
-                "title": "What is the capacity (in Gbit/s) of the largest link in your network used for internet traffic (either shared or dedicated)?",
-                "description": "Please provide the sum of aggregated links, but don't include backup capacity.",
-                "validators": [
-                  {
-                    "type": "numeric",
-                    "minValue": 0
-                  }
-                ]
-              },
-              {
-                "type": "text",
-                "name": "typical_capacity",
-                "title": "What is the current typical core usable backbone IP capacity of your network in Gbit/s?",
-                "description": "Note this refers to circuit capacity not traffic e.g. 2 x 10GE LAG aggregated links.",
-                "validators": [
-                  {
-                    "type": "numeric",
-                    "minValue": 0
-                  }
-                ]
-              },
-              {
-                "type": "matrixdynamic",
-                "name": "external_connections",
-                "title": "Please provide a list of the operational external IP connections (usable links excluding backup links): ",
-                "description": "This should include links to your regional backbone (GÉANT, APAN, RedCLARA etc, to other research locations, to the commercial Internet, peerings to Internet exchanges, cross-border dark fibre links and any other links you may have. Note that we are interested in the capacity for production purposes, not in any additional links that may be there for the purpose of giving resilience. Some of your capacity to your regional backbone may be used for transiting to intercontinental services; please include these too. Cross-border fibre links means those links that have been commissioned or established by the NREN from a point on the network that is near the border to another point near the border on the network of a neighbouring NREN, for example.",
-                "columns": [
-                  {
-                    "name": "link_name",
-                    "title": "Link name",
-                    "cellType": "text"
-                  },
-                  {
-                    "name": "capacity",
-                    "title": "Capacity (Gbit/s):",
-                    "cellType": "text",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0
-                      }
-                    ]
-                  },
-                  {
-                    "name": "from_organization",
-                    "title": "The link is from (organisation):",
-                    "cellType": "text"
-                  },
-                  {
-                    "name": "to_organization",
-                    "title": "To (organisation):",
-                    "cellType": "text"
-                  },
-                  {
-                    "name": "interconnection_method",
-                    "title": "Interconnection method:",
-                    "cellType": "dropdown",
-                    "choices": [
-                      {
-                        "value": "internet_exchange",
-                        "text": "Internet exchange points"
-                      },
-                      {
-                        "value": "open_exchange",
-                        "text": "Open Exchange points"
-                      },
-                      {
-                        "value": "direct",
-                        "text": "Directly connected R&E peers"
-                      },
-                      {
-                        "value": "geant",
-                        "text": "GEANT"
-                      },
-                      {
-                        "value": "other",
-                        "text": "Other"
-                      }
-                    ]
-                  }
-                ],
-                "rowCount": 1,
-                "maxRowCount": 50
-              },
-              {
-                "type": "text",
-                "name": "non_r_and_e_peers",
-                "title": "Please state how many non-R&E networks you are peering with:",
-                "description": "This should include all direct IP-peerings to commercial networks e.g. Google.",
-                "validators": [
-                  {
-                    "type": "numeric",
-                    "minValue": 0
-                  },
-                  {
-                    "type": "regex",
-                    "text": "Please round to a whole number",
-                    "regex": "^[0-9]*$"
-                  }
-                ]
-              },
-              {
-                "type": "multipletext",
-                "name": "traffic_estimate",
-                "title": "Please supply an estimate of the total amount of traffic in Terabytes in the last 12 months for the following:",
-                "items": [
-                  {
-                    "name": "from_customers",
-                    "title": "Traffic from NREN customers (sources that are part of the remit of the NREN's domain)",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0
-                      }
-                    ]
-                  },
-                  {
-                    "name": "to_customers",
-                    "title": "Traffic to NREN customers (sources that are part of the remit of the NREN's domain)",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0
-                      }
-                    ]
-                  },
-                  {
-                    "name": "from_external",
-                    "title": "Traffic from external networks (sources that are outside the NREN's domain, such as, GÉANT, General/Commercial Internet, Internet exchanges, peerings, other NRENs etc)",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0
-                      }
-                    ]
-                  },
-                  {
-                    "name": "to_external",
-                    "title": "Traffic to external networks (sources that are outside the NREN's domain, such as, GÉANT, General/Commercial Internet, Internet exchanges, peerings, other NRENs etc)",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0
-                      }
-                    ]
-                  }
-                ]
-              },
-              {
-                "type": "multipletext",
-                "name": "commodity_vs_r_e",
-                "title": "What is the ratio of commodity vs R&E traffic in your network?",
-                "validators": [
-                  {
-                    "type": "expression",
-                    "text": "The percentages should add up to 100",
-                    "expression": "(sum({commodity_vs_r_e.r_e}, {commodity_vs_r_e.commodity}) = 100) or ({commodity_vs_r_e} empty)"
-                  }
-                ],
-                "items": [
-                  {
-                    "name": "r_e",
-                    "title": "R&E percentage",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0,
-                        "maxValue": 100
-                      }
-                    ]
-                  },
-                  {
-                    "name": "commodity",
-                    "title": "Commodity percentage",
-                    "validators": [
-                      {
-                        "type": "numeric",
-                        "minValue": 0,
-                        "maxValue": 100
-                      }
-                    ]
-                  }
-                ]
-              }
-            ],
-            "title": "CAPACITY"
-          },
-          {
-            "type": "panel",
-            "name": "sdn",
-            "elements": [
-              {
-                "type": "radiogroup",
-                "name": "operational_process_automation",
-                "title": "Are you automating your operational processes?",
-                "choices": [
-                  "Yes",
-                  "No",
-                  "Planned"
-                ],
-                "showClearButton": true
-              },
-              {
-                "type": "text",
-                "name": "operational_process_automation_tools",
-                "visibleIf": "{operational_process_automation} = 'yes'",
-                "indent": 1,
-                "title": "Please specify which processes and the name/s of the automation software and tools you use for it:"
-              },
-              {
-                "type": "radiogroup",
-                "name": "nfv",
-                "title": "Do you use any kind of NFV?",
-                "choices": [
-                  "Yes",
-                  "No",
-                  "Planned"
-                ],
-                "showClearButton": true
-              },
-              {
-                "type": "checkbox",
-                "name": "nfv_types",
-                "visibleIf": "{nfv} = 'yes' or {nfv} = 'planned'",
-                "indent": 1,
-                "title": "What kind of NFV do/will you use:",
-                "choices": [
-                  {
-                    "value": "routers",
-                    "text": "Routers/switches"
-                  },
-                  {
-                    "value": "firewalls",
-                    "text": "Firewalls"
-                  },
-                  {
-                    "value": "load_balancers",
-                    "text": "Load balancers"
-                  },
-                  {
-                    "value": "vpn_concentrators",
-                    "text": "VPN Concentrator Services"
-                  }
-                ],
-                "showOtherItem": true
-              },
-              {
-                "type": "radiogroup",
-                "name": "network_automation",
-                "title": "Do you use automation on your network?",
-                "choices": [
-                  "Yes",
-                  "No",
-                  "Planned"
-                ],
-                "showClearButton": true
-              },
-              {
-                "type": "checkbox",
-                "name": "network_automation_tasks",
-                "visibleIf": "{network_automation} = 'yes' or {network_automation} = 'planned'",
-                "indent": 1,
-                "title": "What kind of task do you use it for?",
-                "choices": [
-                  {
-                    "value": "provisioning",
-                    "text": "Device Provisioning"
-                  },
-                  {
-                    "value": "data_collection",
-                    "text": "Data Collection"
-                  },
-                  {
-                    "value": "config_management",
-                    "text": "Configuration Management"
-                  },
-                  {
-                    "value": "compliance",
-                    "text": "Compliance"
-                  },
-                  {
-                    "value": "reporting",
-                    "text": "Reporting"
-                  },
-                  {
-                    "value": "troubleshooting",
-                    "text": "Troubleshooting"
-                  }
-                ]
-              }
-            ],
-            "title": "SOFTWARE-DEFINED NETWORKING (SDN) AND NETWORK FUNCTION VIRTUALISATION (NFV)"
-          },
-          {
-            "type": "comment",
-            "name": "network_comments",
-            "title": "Comments regarding this section:"
-          }
-        ],
-        "title": "Network"
-      },
-      {
-        "name": "services",
-        "elements": [
-          {
-            "type": "html",
-            "name": "services_hover_explanation",
-            "html": "Descriptions of the individual services are shown if you hover over the service name."
-          },
-          {
-            "type": "matrixdropdown",
-            "name": "services_network",
-            "state": "collapsed",
-            "title": "Network services",
-            "hideCheckboxLabels": true,
-            "columns": [
-              {
-                "name": "offered",
-                "title": "Service offered",
-                "cellType": "checkbox",
-                "choices": [
-                  {
-                    "value": "yes",
-                    "text": "Yes"
-                  }
-                ]
-              },
-              {
-                "name": "name",
-                "title": "Service name",
-                "cellType": "text",
-                "width": "20%",
-                "visibleIf": "{row.offered} = ['yes']"
-              },
-              {
-                "name": "description",
-                "title": "Official description",
-                "cellType": "comment",
-                "visibleIf": "{row.offered} = ['yes']",
-                "rows": 2
-              },
-              {
-                "name": "additional_information",
-                "title": "Additional information",
-                "cellType": "comment",
-                "visibleIf": "{row.offered} = ['yes']",
-                "rows": 2,
-                "placeholder": "(e.g. software used, third party service, links, etc.)"
-              }
-            ],
-            "cellType": "checkbox",
-            "rows": [
-              {
-                "value": "connectivity",
-                "text": "IP Connectivity",
-                "customDescription": "Basic IP connectivity services inc R&E and commodity internet"
-              },
-              {
-                "value": "home-vpn",
-                "text": "Remote access VPN server",
-                "customDescription": "Remote access VPN for end users (e.g. eduVPN) or site-to-site VPN with the possibility of encryption"
-              },
-              {
-                "value": "ipv6",
-                "text": "IPv6",
-                "customDescription": "The new version of the internet protocol (IP) that will eventually replace IPv4"
-              },
-              {
-                "value": "dedicated-optical-connections",
-                "text": "Dedicated optical connections",
-                "customDescription": "Provision of dedicated optical connections to users, e.g. Layer 1 optical channels or Open Lightpath exchanges."
-              },
-              {
-                "value": "managed-router",
-                "text": "Managed router service",
-                "customDescription": "Remote router support for institutions"
-              },
-              {
-                "value": "multicast",
-                "text": "Multicast",
-                "customDescription": "Extension to the IP protocol which allows individual packets to be sent to multiple hosts on the internet"
-              },
-              {
-                "value": "netflow",
-                "text": "Netflow tool",
-                "customDescription": "Network protocol of collecting IP traffic and monitoring network traffic"
-              },
-              {
-                "value": "user-monitoring",
-                "text": "Network troubleshooting",
-                "customDescription": "Providing a support service to identify, diagnose and resolve problems and issues within a computer network, using network monitoring system tools like Looking Glass."
-              },
-              {
-                "value": "network-monitoring",
-                "text": "Network monitoring",
-                "customDescription": "Network Monitoring systems is software/hardware tools that can track various aspects of a network and its operation. These systems can detect devices and other network elements that comprise or touch the network, as well as provide status updates."
-              },
-              {
-                "value": "pert",
-                "text": "PERT",
-                "customDescription": "Team supporting resolution of end-to-end performance problems for networked applications"
-              },
-              {
-                "value": "point-to-point-circuit-vpn",
-                "text": "Virtual circuits/network VPNs",
-                "customDescription": "Virtual point to point circuits or VPNs to create virtual networks between geographically distinct sites"
-              },
-              {
-                "value": "quality-of-service",
-                "text": "Quality of Service",
-                "customDescription": "Preferential service to specific applications or classes of applications"
-              }
-            ],
-            "rowTitleWidth": "20%"
-          },
-          {
-            "type": "matrixdropdown",
-            "name": "services_isp",
-            "state": "collapsed",
-            "title": "ISP services",
-            "hideCheckboxLabels": true,
-            "columns": [
-              {
-                "name": "offered",
-                "title": "Service offered",
-                "cellType": "checkbox",
-                "choices": [
-                  {
-                    "value": "yes",
-                    "text": "Yes"
-                  }
-                ]
-              },
-              {
-                "name": "name",
-                "title": "Service name",
-                "cellType": "text",
-                "width": "20%",
-                "visibleIf": "{row.offered} = ['yes']"
-              },
-              {
-                "name": "description",
-                "title": "Official description",
-                "cellType": "comment",
-                "visibleIf": "{row.offered} = ['yes']",
-                "rows": 2
-              },
-              {
-                "name": "additional_information",
-                "title": "Additional information",
-                "cellType": "comment",
-                "visibleIf": "{row.offered} = ['yes']",
-                "rows": 2,
-                "placeholder": "(e.g. software used, third party service, links, etc.)"
-              }
-            ],
-            "cellType": "checkbox",
-            "rows": [
-              {
-                "value": "domain-registration",
-                "text": "Domain name registration",
-                "customDescription": "Adminstration/registration of top and second level domain names"
-              },
-              {
-                "value": "ip-address-allocation",
-                "text": "IP address allocation",
-                "customDescription": "Allocating addresses for users according to the RIPE policies"
-              },
-              {
-                "value": "ix-operation",
-                "text": "National IX operation",
-                "customDescription": "Operating an IX with national importance"
-              },
-              {
-                "value": "nameserver",
-                "text": "Nameserver services",
-                "customDescription": "Operation of nameservers and maintenance of DNS information on behalf of users"
-              },
-              {
-                "value": "timeserver-ntp",
-                "text": "NTP service",
-                "customDescription": "Allows the synchronization of computer clocks over the internet"
-              }
-            ],
-            "rowTitleWidth": "20%"
-          },
-          {
-            "type": "matrixdropdown",
-            "name": "services_security",
-            "state": "collapsed",
-            "title": "Security services",
-            "hideCheckboxLabels": true,
-            "columns": [
-              {
-                "name": "offered",
-                "title": "Service offered",
-                "cellType": "checkbox",
-                "choices": [
-                  {
-                    "value": "yes",
-                    "text": "Yes"
-                  }
-                ]
-              },
-              {
-                "name": "name",
-                "title": "Service name",
-                "cellType": "text",
-                "width": "20%",
-                "visibleIf": "{row.offered} = ['yes']"
-              },
-              {
-                "name": "description",
-                "title": "Official description",
-                "cellType": "comment",
-                "visibleIf": "{row.offered} = ['yes']",
-                "rows": 2
-              },
-              {
-                "name": "additional_information",
-                "title": "Additional information",
-                "cellType": "comment",
-                "visibleIf": "{row.offered} = ['yes']",
-                "rows": 2,
-                "placeholder": "(e.g. software used, third party service, links, etc.)"
-              }
-            ],
-            "cellType": "checkbox",
-            "rows": [
-              {
-                "value": "anti-spam",
-                "text": "Anti-spam solution",
-                "customDescription": "Anti-Spam solutions for detecting and eliminating viruses and spam mails"
-              },
-              {
-                "value": "csirt",
-                "text": "CERT/CSIRT",
-                "customDescription": "A single point of contact for users to deal with computer security incidents and prevention"
-              },
-              {
-                "value": "ddos-prevention",
-                "text": "DDoS mitigation",
-                "customDescription": "Tools and techniques for mitigating Distributed Denial of Service attacks"
-              },
-              {
-                "value": "external-security-awareness",
-                "text": "External security awareness programme",
-                "customDescription": "An external security awareness programme is a service offering to users  (consisting for example in training, workshops, toolkits, campaigns, awareness games, …) aimed at helping member institutions to effectively manage human risk"
-              },
-              {
-                "value": "firewall-on-demand",
-                "text": "Firewall-on-Demand",
-                "customDescription": "Provision of a dynamic firewall services to mitigate against DDoS attacks"
-              },
-              {
-                "value": "intrusion",
-                "text": "Intrusion detection",
-                "customDescription": "System for detecting and preventing Intrusions (IDS/IPS)"
-              },
-              {
-                "value": "pgp-key",
-                "text": "PGP key server",
-                "customDescription": "Operation of PGP key server"
-              },
-              {
-                "value": "security-audit",
-                "text": "Security auditing",
-                "customDescription": "Carrying out vulnerability assessments and security reviews of user systems and resources on their behalf"
-              },
-              {
-                "value": "vulnerability-testing",
-                "text": "Vulnerability scanning",
-                "customDescription": "Vulnerability service that allows users to scan their own IP networks fo security holes"
-              },
-              {
-                "value": "web-filtering",
-                "text": "Web filtering",
-                "customDescription": "Centralised web content filtering service for protection against access to inappropriate content"
-              }
-            ],
-            "rowTitleWidth": "20%"
-          },
-          {
-            "type": "matrixdropdown",
-            "name": "services_identity",
-            "state": "collapsed",
-            "title": "Identity services",
-            "hideCheckboxLabels": true,
-            "columns": [
-              {
-                "name": "offered",
-                "title": "Service offered",
-                "cellType": "checkbox",
-                "choices": [
-                  {
-                    "value": "yes",
-                    "text": "Yes"
-                  }
-                ]
-              },
-              {
-                "name": "name",
-                "title": "Service name",
-                "cellType": "text",
-                "width": "20%",
-                "visibleIf": "{row.offered} = ['yes']"
-              },
-              {
-                "name": "description",
-                "title": "Official description",
-                "cellType": "comment",
-                "visibleIf": "{row.offered} = ['yes']",
-                "rows": 2
-              },
-              {
-                "name": "additional_information",
-                "title": "Additional information",
-                "cellType": "comment",
-                "visibleIf": "{row.offered} = ['yes']",
-                "rows": 2,
-                "placeholder": "(e.g. software used, third party service, links, etc.)"
-              }
-            ],
-            "cellType": "checkbox",
-            "rows": [
-              {
-                "value": "aai",
-                "text": "Hosted campus AAI",
-                "customDescription": "Hosting of an Identity Provider service on behalf of connected Institutions to authenticate users"
-              },
-              {
-                "value": "eduroam-wifi",
-                "text": "eduroam",
-                "customDescription": "Inter-WLAN service to facilitate easy and secure Internet access for roaming educationals users"
-              },
-              {
-                "value": "interfederation",
-                "text": "Interfederation",
-                "customDescription": "Participation in an interfederation (i.e. eduGAIN, KALMAR)"
-              }
-            ],
-            "rowTitleWidth": "20%"
-          },
-          {
-            "type": "matrixdropdown",
-            "name": "services_hosting",
-            "state": "collapsed",
-            "title": "Storage & hosting services",
-            "hideCheckboxLabels": true,
-            "columns": [
-              {
-                "name": "offered",
-                "title": "Service offered",
-                "cellType": "checkbox",
-                "choices": [
-                  {
-                    "value": "yes",
-                    "text": "Yes"
-                  }
-                ]
-              },
-              {
-                "name": "name",
-                "title": "Service name",
-                "cellType": "text",
-                "width": "20%",
-                "visibleIf": "{row.offered} = ['yes']"
-              },
-              {
-                "name": "description",
-                "title": "Official description",
-                "cellType": "comment",
-                "visibleIf": "{row.offered} = ['yes']",
-                "rows": 2
-              },
-              {
-                "name": "additional_information",
-                "title": "Additional information",
-                "cellType": "comment",
-                "visibleIf": "{row.offered} = ['yes']",
-                "rows": 2,
-                "placeholder": "(e.g. software used, third party service, links, etc.)"
-              }
-            ],
-            "cellType": "checkbox",
-            "rows": [
-              {
-                "value": "cloud-service-end-user",
-                "text": "Cloud storage (end user)",
-                "customDescription": "Browser-based virtual storage service for individuals"
-              },
-              {
-                "value": "content-delivery-hosting",
-                "text": "Content delivery hosting",
-                "customDescription": "Hosting of contenct delivery servers, e.g. Akamai"
-              },
-              {
-                "value": "disaster-recovery",
-                "text": "Disaster recovery",
-                "customDescription": "Off site backup services"
-              },
-              {
-                "value": "dns-server",
-                "text": "DNS hosting",
-                "customDescription": "Hosting of primary and secondary DNS servers"
-              },
-              {
-                "value": "email-services",
-                "text": "Email server hosting",
-                "customDescription": "NREN hosted email servers."
-              },
-              {
-                "value": "filesender",
-                "text": "FileSender",
-                "customDescription": "Web-based application that allows authenticated userds to securely and easily send arbitrarily large files"
-              },
-              {
-                "value": "saas",
-                "text": "SaaS",
-                "customDescription": "Software as a service e.g. Google Apps for Education"
-              },
-              {
-                "value": "storage-co-location",
-                "text": "Housing/co-location",
-                "customDescription": "Hosting of user equipment in a managed data centre"
-              },
-              {
-                "value": "virtual-machines-iaas",
-                "text": "Virtual machines/IaaS",
-                "customDescription": "Access to virtual computing resources"
-              }
-            ],
-            "rowTitleWidth": "20%"
-          },
-          {
-            "type": "matrixdropdown",
-            "name": "services_multimedia",
-            "state": "collapsed",
-            "title": "Multimedia services",
-            "hideCheckboxLabels": true,
-            "columns": [
-              {
-                "name": "offered",
-                "title": "Service offered",
-                "cellType": "checkbox",
-                "choices": [
-                  {
-                    "value": "yes",
-                    "text": "Yes"
-                  }
-                ]
-              },
-              {
-                "name": "name",
-                "title": "Service name",
-                "cellType": "text",
-                "width": "20%",
-                "visibleIf": "{row.offered} = ['yes']"
-              },
-              {
-                "name": "description",
-                "title": "Official description",
-                "cellType": "comment",
-                "visibleIf": "{row.offered} = ['yes']",
-                "rows": 2
-              },
-              {
-                "name": "additional_information",
-                "title": "Additional information",
-                "cellType": "comment",
-                "visibleIf": "{row.offered} = ['yes']",
-                "rows": 2,
-                "placeholder": "(e.g. software used, third party service, links, etc.)"
-              }
-            ],
-            "cellType": "checkbox",
-            "rows": [
-              {
-                "value": "internet-radio-tv",
-                "text": "TV/radio streaming",
-                "customDescription": "Internet and radio streaming services"
-              },
-              {
-                "value": "videoconferencing",
-                "text": "Event recording/streaming",
-                "customDescription": "Provision of equipment and/or software to support event streaming/recording"
-              },
-              {
-                "value": "video-portal",
-                "text": "Provision of content portal",
-                "customDescription": "Multi-media content portal"
-              },
-              {
-                "value": "web-conferencing",
-                "text": "Web/desktop conferencing",
-                "customDescription": "Video conferencing service to desktops and hand-held devices using software"
-              }
-            ],
-            "rowTitleWidth": "20%"
-          },
-          {
-            "type": "matrixdropdown",
-            "name": "services_collaboration",
-            "state": "collapsed",
-            "title": "Collaboration services",
-            "hideCheckboxLabels": true,
-            "columns": [
-              {
-                "name": "offered",
-                "title": "Service offered",
-                "cellType": "checkbox",
-                "choices": [
-                  {
-                    "value": "yes",
-                    "text": "Yes"
-                  }
-                ]
-              },
-              {
-                "name": "name",
-                "title": "Service name",
-                "cellType": "text",
-                "width": "20%",
-                "visibleIf": "{row.offered} = ['yes']"
-              },
-              {
-                "name": "description",
-                "title": "Official description",
-                "cellType": "comment",
-                "visibleIf": "{row.offered} = ['yes']",
-                "rows": 2
-              },
-              {
-                "name": "additional_information",
-                "title": "Additional information",
-                "cellType": "comment",
-                "visibleIf": "{row.offered} = ['yes']",
-                "rows": 2,
-                "placeholder": "(e.g. software used, third party service, links, etc.)"
-              }
-            ],
-            "cellType": "checkbox",
-            "rows": [
-              {
-                "value": "e-portfolio",
-                "text": "E-portfolio service",
-                "customDescription": "Functions to create user professional and career portfolios"
-              },
-              {
-                "value": "identifier-reg",
-                "text": "Identifier Registry",
-                "customDescription": "Registering of unique and automatically-processable identifiers in the form of text or numeric strings"
-              },
-              {
-                "value": "journal-library-access",
-                "text": "Journal access",
-                "customDescription": "Access to academic journals"
-              },
-              {
-                "value": "mailing-lists",
-                "text": "Mailing lists",
-                "customDescription": "Service for operation of electronic discussion lists"
-              },
-              {
-                "value": "project-collaboration-toolkit",
-                "text": "Project collaboration",
-                "customDescription": "Packaged services or virtual project groups e.g. mailing lists, storage, web meetings, wiki."
-              },
-              {
-                "value": "scheduling-tool",
-                "text": "Scheduling tool",
-                "customDescription": "Provision of tools to users for scheduling appointments or classes"
-              },
-              {
-                "value": "survey-tool",
-                "text": "Survey/polling tool",
-                "customDescription": "Provision of applications for creating surveys or polls"
-              },
-              {
-                "value": "virtual-learning-environment",
-                "text": "VLE",
-                "customDescription": "Online e-learning education system that provides virtual access to resources used in teaching"
-              },
-              {
-                "value": "voip",
-                "text": "VoIP",
-                "customDescription": "Service to deliver voice communications and multimedia sessions over Internet Protocal (IP) networks"
-              },
-              {
-                "value": "web-email-hosting",
-                "text": "Web hosting",
-                "customDescription": "Service to provide space on central web servers for users to publish their website"
-              }
-            ],
-            "rowTitleWidth": "20%"
-          },
-          {
-            "type": "matrixdropdown",
-            "name": "services_professional",
-            "state": "collapsed",
-            "title": "Professional services",
-            "hideCheckboxLabels": true,
-            "columns": [
-              {
-                "name": "offered",
-                "title": "Service offered",
-                "cellType": "checkbox",
-                "choices": [
-                  {
-                    "value": "yes",
-                    "text": "Yes"
-                  }
-                ]
-              },
-              {
-                "name": "name",
-                "title": "Service name",
-                "cellType": "text",
-                "width": "20%",
-                "visibleIf": "{row.offered} = ['yes']"
-              },
-              {
-                "name": "description",
-                "title": "Official description",
-                "cellType": "comment",
-                "visibleIf": "{row.offered} = ['yes']",
-                "rows": 2
-              },
-              {
-                "name": "additional_information",
-                "title": "Additional information",
-                "cellType": "comment",
-                "visibleIf": "{row.offered} = ['yes']",
-                "rows": 2,
-                "placeholder": "(e.g. software used, third party service, links, etc.)"
-              }
-            ],
-            "cellType": "checkbox",
-            "rows": [
-              {
-                "value": "consultancy",
-                "text": "Consultancy/training",
-                "customDescription": "Training and consultancy services provided by the NREN"
-              },
-              {
-                "value": "dissemination",
-                "text": "Dissemination",
-                "customDescription": "Dissemination of information to users e.g newsletters and magazines"
-              },
-              {
-                "value": "procurement",
-                "text": "Procurement brokerage",
-                "customDescription": "Procurement support for users to purchase services (e.g. cloud services), software/software licenses (e.g. VLE or office software) or hardware either directly or via procurement frameworks."
-              },
-              {
-                "value": "user-conference",
-                "text": "User conferences",
-                "customDescription": "Hosting of regular user conferences"
-              },
-              {
-                "value": "user-portal",
-                "text": "User portals",
-                "customDescription": "User portals for service management and monitoring"
-              }
-            ],
-            "rowTitleWidth": "20%"
-          },
-          {
-            "type": "comment",
-            "name": "service_comments",
-            "title": "Comments regarding this section:"
-          }
-        ],
-        "title": "Services"
-      }
-    ],
-    "showQuestionNumbers": "onPage",
-    "checkErrorsMode": "onValueChanged"
-  }
\ No newline at end of file
+  "title": "Compendium",
+  "logoPosition": "right",
+  "pages": [
+    {
+      "name": "organization",
+      "elements": [
+        {
+          "type": "panel",
+          "name": "panel1",
+          "elements": [
+            {
+              "type": "text",
+              "name": "budget",
+              "title": "What is your NREN's budget for {surveyyear} in million euro?",
+              "description": "Note that this question has changed slightly and now asks for the year we are still in. If your budget is not per calendar year, please provide figures for the budget that covers the largest part of {surveyyear} including GEANT subsidy. When responding to this question, please include all parts of your organisation serving your users, bearing in mind the services you deliver, as referenced later in the survey.",
+              "validators": [
+                {
+                  "type": "numeric",
+                  "minValue": 0
+                }
+              ]
+            },
+            {
+              "type": "multipletext",
+              "name": "income_sources",
+              "title": "Estimate (in % of income) the sources of your NREN-related income for {surveyyear}.",
+              "description": "European Funding should include GÉANT funding.",
+              "validators": [
+                {
+                  "type": "expression",
+                  "text": "The percentages should add up to 100",
+                  "expression": "(sum({income_sources.client_institutions}, {income_sources.gov_public_bodies}, {income_sources.european_funding}, {income_sources.commercial}, {income_sources.other}) = 100) or ({income_sources} empty)"
+                }
+              ],
+              "items": [
+                {
+                  "name": "client_institutions",
+                  "title": "Client institutions (universities/schools/research institutes/commercial/other)",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0,
+                      "maxValue": 100
+                    }
+                  ]
+                },
+                {
+                  "name": "gov_public_bodies",
+                  "title": "Government/public bodies",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0,
+                      "maxValue": 100
+                    }
+                  ]
+                },
+                {
+                  "name": "european_funding",
+                  "title": "European funding",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0,
+                      "maxValue": 100
+                    }
+                  ]
+                },
+                {
+                  "name": "commercial",
+                  "title": "Commercial services (e.g. domain reg, security)",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0,
+                      "maxValue": 100
+                    }
+                  ]
+                },
+                {
+                  "name": "other",
+                  "title": "Other",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0,
+                      "maxValue": 100
+                    }
+                  ]
+                }
+              ]
+            },
+            {
+              "type": "dropdown",
+              "name": "charging_mechanism",
+              "title": "How do you charge your client institutions?",
+              "choices": [
+                {
+                  "value": "no_charge",
+                  "text": "We do not charge them directly"
+                },
+                {
+                  "value": "flat_fee",
+                  "text": "We charge a flat fee, based on bandwidth"
+                },
+                {
+                  "value": "usage_based_fee",
+                  "text": "We charge a usage-based fee"
+                },
+                {
+                  "value": "combination",
+                  "text": "We use a combination of flat fee and usage-based fee"
+                },
+                {
+                  "value": "other",
+                  "text": "Other"
+                }
+              ]
+            }
+          ],
+          "title": "Budget, Income and Billing"
+        },
+        {
+          "type": "panel",
+          "name": "staff",
+          "elements": [
+            {
+              "type": "multipletext",
+              "name": "staff_employment_type",
+              "title": "What is the number of staff engaged in the NREN activities? (In Full Time Equivalent (FTEs))",
+              "description": "When responding to this question, please include all parts of your organisation serving your users, bearing in mind the services you deliver, as referenced later in the survey.",
+              "items": [
+                {
+                  "name": "permanent_fte",
+                  "title": "Permanent staff",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    }
+                  ]
+                },
+                {
+                  "name": "subcontracted_fte",
+                  "title": "Subcontracted staff",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    }
+                  ]
+                }
+              ]
+            },
+            {
+              "type": "multipletext",
+              "name": "staff_roles",
+              "title": "How much FTE is devoted to roles in the following functional areas?",
+              "items": [
+                {
+                  "name": "technical_fte",
+                  "title": "Technical roles",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    }
+                  ]
+                },
+                {
+                  "name": "nontechnical_fte",
+                  "title": "Non-technical roles",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    }
+                  ]
+                }
+              ]
+            },
+            {
+              "type": "radiogroup",
+              "name": "parent_organization",
+              "title": "Is your NREN part of a larger organisation (e.g. ministry, university)?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "text",
+              "name": "parent_organization_name",
+              "visibleIf": "{parent_organization} = 'Yes'",
+              "indent": 1,
+              "title": "What is the name of this larger organisation?"
+            },
+            {
+              "type": "radiogroup",
+              "name": "suborganizations",
+              "title": "Does your NREN have sub-organisations?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "matrixdynamic",
+              "name": "suborganization_details",
+              "visibleIf": "{suborganizations} = 'Yes'",
+              "indent": 1,
+              "title": "Please fill in the details:",
+              "columns": [
+                {
+                  "name": "suborganization_name",
+                  "title": "Name of your sub-organisation?",
+                  "cellType": "text",
+                  "isRequired": true
+                },
+                {
+                  "name": "suborganization_role",
+                  "title": "Role of your sub-organisation?",
+                  "cellType": "dropdown",
+                  "choices": [
+                    {
+                      "value": "idc",
+                      "text": "IDC federation"
+                    },
+                    {
+                      "value": "hpc",
+                      "text": "HPC centre"
+                    }
+                  ],
+                  "showOtherItem": true
+                }
+              ],
+              "rowCount": 1,
+              "maxRowCount": 30
+            },
+            {
+              "type": "radiogroup",
+              "name": "ec_projects",
+              "title": "Other than GÉANT, is your NREN involved in other EC projects?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "matrixdynamic",
+              "name": "ec_project_names",
+              "visibleIf": "{ec_projects} = 'Yes'",
+              "indent": 1,
+              "title": "Please list the name of the projects",
+              "description": "Use one line per project.",
+              "columns": [
+                {
+                  "name": "ec_project_name",
+                  "title": "Project name",
+                  "cellType": "text"
+                }
+              ],
+              "rowCount": 1
+            }
+          ],
+          "title": "Staff and Projects"
+        },
+        {
+          "type": "panel",
+          "name": "basic_information",
+          "elements": [
+            {
+              "type": "text",
+              "name": "full_name_english",
+              "title": "Full name of the organisation (in English):"
+            },
+            {
+              "type": "text",
+              "name": "full_name_national_languages",
+              "title": "Full name of the organisation in the national language(s):",
+              "description": "Please use the Latin alphabet."
+            },
+            {
+              "type": "text",
+              "name": "abbreviation_english",
+              "title": "Abbreviation in English (if applicable):",
+              "description": "Please always provide this information."
+            },
+            {
+              "type": "text",
+              "name": "abbreviation_national_languages",
+              "title": "Abbreviation in the national language(s) (if applicable):",
+              "description": "Please always provide this information. Please use the Latin alphabet."
+            },
+            {
+              "type": "text",
+              "name": "street_name_and_number",
+              "title": "Number and street name:"
+            },
+            {
+              "type": "text",
+              "name": "city",
+              "title": "Postal town (county):"
+            },
+            {
+              "type": "text",
+              "name": "postal_code",
+              "title": "Postal Code:"
+            },
+            {
+              "type": "text",
+              "name": "country",
+              "title": "Country (in English):"
+            },
+            {
+              "type": "text",
+              "name": "phone_number",
+              "title": "Phone number:",
+              "inputType": "tel"
+            },
+            {
+              "type": "text",
+              "name": "email_address",
+              "title": "General email address:",
+              "validators": [
+                {
+                  "type": "email"
+                }
+              ],
+              "inputType": "email"
+            },
+            {
+              "type": "text",
+              "name": "website",
+              "title": "Website:",
+              "validators": [
+                {
+                  "type": "expression",
+                  "text": "Please provide a single valid website url including http:// or https://",
+                  "expression": "validateWebsiteUrl({website})"
+                }
+              ],
+              "inputType": "url"
+            }
+          ],
+          "title": "Basic Information"
+        },
+        {
+          "type": "comment",
+          "name": "organization_comments",
+          "title": "Comments regarding this section:"
+        }
+      ],
+      "title": "Organisation"
+    },
+    {
+      "name": "standards_and_policies",
+      "elements": [
+        {
+          "type": "panel",
+          "name": "policy",
+          "elements": [
+            {
+              "type": "radiogroup",
+              "name": "corporate_strategy",
+              "title": "Have you made any updates to your corporate strategy over the last year?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "text",
+              "name": "corporate_strategy_url",
+              "title": "Please provide the URL for your latest (corporate) strategic plan?",
+              "validators": [
+                {
+                  "type": "expression",
+                  "text": "Please provide a single valid website url including http:// or https://",
+                  "expression": "validateWebsiteUrl({corporate_strategy_url})"
+                }
+              ],
+              "inputType": "url"
+            },
+            {
+              "type": "matrixdropdown",
+              "name": "policies",
+              "title": "Does your NREN have the following policies?",
+              "hideCheckboxLabels": true,
+              "columns": [
+                {
+                  "name": "available",
+                  "title": "Policy available",
+                  "cellType": "checkbox",
+                  "choices": [
+                    {
+                      "value": "yes",
+                      "text": "Yes"
+                    }
+                  ]
+                },
+                {
+                  "name": "url",
+                  "title": "Please provide the URL",
+                  "cellType": "text",
+                  "visibleIf": "{row.available} = ['yes']",
+                  "validators": [
+                    {
+                      "type": "expression",
+                      "text": "Please provide a single valid website url including http:// or https://",
+                      "expression": "validateWebsiteUrl({row.url})"
+                    }
+                  ],
+                  "inputType": "url"
+                }
+              ],
+              "cellType": "checkbox",
+              "rows": [
+                {
+                  "value": "environmental_policy",
+                  "text": "Environmental Policy"
+                },
+                {
+                  "value": "equal_opportunity_policy",
+                  "text": "Equality Opportunity Policy"
+                },
+                {
+                  "value": "gender_equality_policy",
+                  "text": "Gender Equality Plan"
+                },
+                {
+                  "value": "connectivity_policy",
+                  "text": "Connectivity Policy"
+                },
+                {
+                  "value": "acceptable_use_policy",
+                  "text": "Acceptable Use Policy (AUP)"
+                },
+                {
+                  "value": "privacy_notice",
+                  "text": "Organisational Privacy Policy"
+                },
+                {
+                  "value": "data_protection_contact",
+                  "text": "Dedicated contact for data protection, privacy or GDPR queries"
+                }
+              ],
+              "rowTitleWidth": "20%"
+            },
+            {
+              "type": "radiogroup",
+              "name": "central_software_procurement",
+              "title": "Do you centrally procure software for your customers?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "text",
+              "name": "central_procurement_amount",
+              "visibleIf": "{central_software_procurement} = 'yes'",
+              "indent": 1,
+              "title": "What is the total amount (in Euro) that you procured in {surveyyear} or {previousyear}/{surveyyear} on behalf of your customers?",
+              "validators": [
+                {
+                  "type": "numeric",
+                  "minValue": 0
+                },
+                {
+                  "type": "regex",
+                  "text": "Please round to a whole number",
+                  "regex": "^[0-9]*$"
+                }
+              ]
+            },
+            {
+              "type": "radiogroup",
+              "name": "formal_service_management_framework",
+              "title": "Does your NREN operate a formal service management framework for all of your services?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "radiogroup",
+              "name": "service_level_targets",
+              "title": "Are Service Level Targets available for your NREN services?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "matrixdropdown",
+              "name": "service_matrix",
+              "title": "Which service types are available for which user types?",
+              "columns": [
+                {
+                  "name": "service_types",
+                  "title": "Service types",
+                  "cellType": "checkbox",
+                  "showInMultipleColumns": true,
+                  "choices": [
+                    {
+                      "value": "network_services",
+                      "text": "Network services"
+                    },
+                    {
+                      "value": "isp_support",
+                      "text": "ISP support"
+                    },
+                    {
+                      "value": "security",
+                      "text": "Security"
+                    },
+                    {
+                      "value": "identity",
+                      "text": "Identity/T&I"
+                    },
+                    {
+                      "value": "collaboration",
+                      "text": "Collaboration"
+                    },
+                    {
+                      "value": "multimedia",
+                      "text": "Multimedia"
+                    },
+                    {
+                      "value": "storage_and_hosting",
+                      "text": "Storage and Hosting"
+                    },
+                    {
+                      "value": "professional_services",
+                      "text": "Professional services"
+                    }
+                  ]
+                }
+              ],
+              "rows": [
+                {
+                  "value": "universities",
+                  "text": "Universities & Other (ISCED 6-8)"
+                },
+                {
+                  "value": "further_education",
+                  "text": "Further education (ISCED 4-5)"
+                },
+                {
+                  "value": "secondary_schools",
+                  "text": "Secondary schools (ISCED 2-3)"
+                },
+                {
+                  "value": "primary_schools",
+                  "text": "Primary schools (ISCED 1)"
+                },
+                {
+                  "value": "institutes",
+                  "text": "Research Institutes"
+                },
+                {
+                  "value": "cultural",
+                  "text": "Libraries, Museums, Archives, Cultural institutions"
+                },
+                {
+                  "value": "hospitals",
+                  "text": "Non-university public Hospitals"
+                },
+                {
+                  "value": "government",
+                  "text": "Government departments (national, regional, local)"
+                },
+                {
+                  "value": "iros",
+                  "text": "International (virtual) research organisations"
+                },
+                {
+                  "value": "for_profit_orgs",
+                  "text": "For-profit organisations"
+                }
+              ]
+            },
+            {
+              "type": "radiogroup",
+              "name": "service_portfolio_eosc_portal",
+              "title": "Are any of the services in your service portfolio listed on the EOSC portal?",
+              "readOnly": true,
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "matrixdynamic",
+              "name": "services_on_eosc_portal_list",
+              "visibleIf": "{service_portfolio_eosc_portal} = 'Yes'",
+              "readOnly": true,
+              "indent": 1,
+              "title": "Can you list them?",
+              "columns": [
+                {
+                  "name": "service_name",
+                  "title": "Service name",
+                  "cellType": "text"
+                }
+              ],
+              "rowCount": 1,
+              "maxRowCount": 50
+            }
+          ],
+          "title": "Policy & Portfolio"
+        },
+        {
+          "type": "panel",
+          "name": "standards",
+          "elements": [
+            {
+              "type": "radiogroup",
+              "name": "audits",
+              "title": "Do you have external or internal audits of your information security management systems e.g. risk management and policies?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "text",
+              "name": "audit_specifics",
+              "visibleIf": "{audits} = 'yes'",
+              "indent": 1,
+              "title": "Please specify (for example a certified security auditor on ISO 27001 is performing the audits):"
+            },
+            {
+              "type": "radiogroup",
+              "name": "business_continuity_plans",
+              "title": "Do you have Business Continuity plans in place to ensure business continuation and operations?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "text",
+              "name": "business_continuity_plans_specifics",
+              "visibleIf": "{business_continuity_plans} = 'yes'",
+              "indent": 1,
+              "title": "Please specify if you comply with any international standard and if you test the continuity plans regularly."
+            },
+            {
+              "type": "radiogroup",
+              "name": "crisis_management_procedure",
+              "title": "Does your NREN have a formal crisis management procedure?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "checkbox",
+              "name": "crisis_exercises",
+              "title": "Does your NREN run or participate in operational security measures and other exercises including crisis awareness to train and test employees?",
+              "description": "Multiple answers possible, in the last 12 months",
+              "choices": [
+                {
+                  "value": "geant_workshops",
+                  "text": "We participate in GÉANT crisis workshops such as CLAW"
+                },
+                {
+                  "value": "national_excercises",
+                  "text": "We participated in national crisis exercises "
+                },
+                {
+                  "value": "tabletop_exercises",
+                  "text": "We run our own tabletop exercises"
+                },
+                {
+                  "value": "simulation_excercises",
+                  "text": "We run our own simulation exercises"
+                },
+                {
+                  "value": "other_excercises",
+                  "text": " We have done/participated in other exercises or trainings"
+                },
+                {
+                  "value": "real_crisis",
+                  "text": "We had a real crisis"
+                },
+                {
+                  "value": "internal_security_programme",
+                  "text": "We run an internal security awareness programme"
+                },
+                {
+                  "value": "none",
+                  "text": "No, we have not done any crisis exercises or trainings"
+                }
+              ]
+            },
+            {
+              "type": "checkbox",
+              "name": "security_controls",
+              "title": "Do you utilise security controls such as anti-virus, integrity checkers and systemic firewalls to protect your assets?",
+              "choices": [
+                {
+                  "value": "anti_virus",
+                  "text": "Anti Virus"
+                },
+                {
+                  "value": "anti_spam",
+                  "text": "Anti-Spam"
+                },
+                {
+                  "value": "firewall",
+                  "text": "Firewall"
+                },
+                {
+                  "value": "ddos_mitigation",
+                  "text": "DDoS mitigation"
+                },
+                {
+                  "value": "monitoring",
+                  "text": "Network monitoring"
+                },
+                {
+                  "value": "ips_ids",
+                  "text": "IPS/IDS"
+                },
+                {
+                  "value": "acl",
+                  "text": "ACL"
+                },
+                {
+                  "value": "segmentation",
+                  "text": "Network segmentation"
+                },
+                {
+                  "value": "integrity_checking",
+                  "text": "Integrity checking"
+                }
+              ],
+              "showOtherItem": true
+            }
+          ],
+          "title": "Standards"
+        },
+        {
+          "type": "comment",
+          "name": "policy_comments",
+          "title": "Comments regarding this section:"
+        }
+      ],
+      "title": "Standards & Policies"
+    },
+    {
+      "name": "connected_users",
+      "elements": [
+        {
+          "type": "panel",
+          "name": "panel2",
+          "elements": [
+            {
+              "type": "matrixdynamic",
+              "name": "connected_sites_lists",
+              "title": "Please provide the URLs of the webpages listing the institutions or organisations that are connected to the NREN, if available:",
+              "description": "Many NRENs have one or more pages on their website listing user institutions. Please provide these links here if you have them.",
+              "columns": [
+                {
+                  "name": "connected_sites_url",
+                  "title": "Url:",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "expression",
+                      "text": "Please provide a single valid website url including http:// or https://",
+                      "expression": "validateWebsiteUrl({row.connected_sites_url})"
+                    }
+                  ],
+                  "inputType": "url"
+                }
+              ],
+              "rowCount": 1,
+              "maxRowCount": 50
+            },
+            {
+              "type": "matrixdropdown",
+              "name": "connectivity_proportions",
+              "title": "Please give an estimate of the proportion of the institutions in each category for which your NREN provides IP connectivity: ",
+              "description": "For the questions in this section, please use the ISCED 2011 classification system (the UNESCO scheme for International Standard Classification of Education) as follows: * Level 8 - Doctorate or equivalent level * Level 7 - Masters or equivalent level * Level 6 - Bachelors or equivalent level * Level 5 - Short-cycle tertiary education * Level 4 - Post-secondary non-tertiary education. This can include, for example, short vocational training programmes * Levels 2 and 3: Secondary education * Level 1: Primary or basic education * Level 0: Pre-primary education",
+              "columns": [
+                {
+                  "name": "covered",
+                  "title": "Does your remit cover connectivity to this institution type:",
+                  "cellType": "dropdown",
+                  "choices": [
+                    {
+                      "value": "yes_incl_other",
+                      "text": "Yes - including transit to other networks"
+                    },
+                    {
+                      "value": "yes_national_nren",
+                      "text": "Yes - national NREN access"
+                    },
+                    {
+                      "value": "sometimes",
+                      "text": "In some circumstances"
+                    },
+                    {
+                      "value": "no_policy",
+                      "text": "No - not eligible for policy reasons"
+                    },
+                    {
+                      "value": "no_financial",
+                      "text": "No - financial restrictions (NREN is unable to charge/cover costs)"
+                    },
+                    {
+                      "value": "no_other",
+                      "text": "No - other reason"
+                    },
+                    {
+                      "value": "unsure",
+                      "text": "Unsure/unclear"
+                    }
+                  ]
+                },
+                {
+                  "name": "nr_connected",
+                  "title": "Number of institutions connected in this category (actual number):",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    },
+                    {
+                      "type": "regex",
+                      "text": "Please use a whole number",
+                      "regex": "^[0-9]*$"
+                    }
+                  ]
+                },
+                {
+                  "name": "market_share_percentage",
+                  "title": "% market share of institutions connected in this category:",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0,
+                      "maxValue": 100
+                    }
+                  ]
+                },
+                {
+                  "name": "nr_of_users",
+                  "title": "Number of users served in this category (actual number):",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    },
+                    {
+                      "type": "regex",
+                      "text": "Please use a whole number",
+                      "regex": "^[0-9]*$"
+                    }
+                  ]
+                }
+              ],
+              "rows": [
+                {
+                  "value": "universities",
+                  "text": "Universities & Other (ISCED 6-8)"
+                },
+                {
+                  "value": "further_education",
+                  "text": "Further education (ISCED 4-5)"
+                },
+                {
+                  "value": "secondary_schools",
+                  "text": "Secondary schools (ISCED 2-3)"
+                },
+                {
+                  "value": "primary_schools",
+                  "text": "Primary schools (ISCED 1)"
+                },
+                {
+                  "value": "institutes",
+                  "text": "Research Institutes"
+                },
+                {
+                  "value": "cultural",
+                  "text": "Libraries, Museums, Archives, Cultural institutions"
+                },
+                {
+                  "value": "hospitals",
+                  "text": "Non-university public Hospitals"
+                },
+                {
+                  "value": "government",
+                  "text": "Government departments (national, regional, local)"
+                },
+                {
+                  "value": "iros",
+                  "text": "International (virtual) research organisations"
+                },
+                {
+                  "value": "for_profit_orgs",
+                  "text": "For-profit organisations"
+                }
+              ]
+            },
+            {
+              "type": "matrixdropdown",
+              "name": "connectivity_level",
+              "title": "Level of IP connectivity by Institution type:",
+              "description": "This table explores the average level of connectivity for each type of institution to the NREN. Please enter the typical and the highest capacity at which institutions in this category are connected ( in Mbit/s). As a minimum, please provide this information for Universities and Research Institutes.",
+              "columns": [
+                {
+                  "name": "typical_speed",
+                  "title": "Typical link speed (Mbit/s):",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    },
+                    {
+                      "type": "regex",
+                      "text": "Please use a whole number",
+                      "regex": "^[0-9]*$"
+                    }
+                  ]
+                },
+                {
+                  "name": "highest_speed",
+                  "title": "Highest speed link (Mbit/s):",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    },
+                    {
+                      "type": "regex",
+                      "text": "Please use a whole number",
+                      "regex": "^[0-9]*$"
+                    }
+                  ]
+                },
+                {
+                  "name": "highest_speed_connection_percentage",
+                  "title": "Proportionally how many institutions in this category are connected at the highest capacity? (%):",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0,
+                      "maxValue": 100
+                    }
+                  ]
+                }
+              ],
+              "rows": [
+                {
+                  "value": "universities",
+                  "text": "Universities & Other (ISCED 6-8)"
+                },
+                {
+                  "value": "further_education",
+                  "text": "Further education (ISCED 4-5)"
+                },
+                {
+                  "value": "secondary_schools",
+                  "text": "Secondary schools (ISCED 2-3)"
+                },
+                {
+                  "value": "primary_schools",
+                  "text": "Primary schools (ISCED 1)"
+                },
+                {
+                  "value": "institutes",
+                  "text": "Research Institutes"
+                },
+                {
+                  "value": "cultural",
+                  "text": "Libraries, Museums, Archives, Cultural institutions"
+                },
+                {
+                  "value": "hospitals",
+                  "text": "Non-university public Hospitals"
+                },
+                {
+                  "value": "government",
+                  "text": "Government departments (national, regional, local)"
+                },
+                {
+                  "value": "iros",
+                  "text": "International (virtual) research organisations"
+                },
+                {
+                  "value": "for_profit_orgs",
+                  "text": "For-profit organisations"
+                }
+              ]
+            },
+            {
+              "type": "matrixdropdown",
+              "name": "traffic_carriers",
+              "title": "How is the traffic carried?",
+              "columns": [
+                {
+                  "name": "carry_mechanism",
+                  "title": "Carry mechanism",
+                  "cellType": "dropdown",
+                  "choices": [
+                    {
+                      "value": "nren_local_loops",
+                      "text": "NREN provides the local loops"
+                    },
+                    {
+                      "value": "regional_nren_backbone",
+                      "text": "Traffic carried to the backbone by regional NREN"
+                    },
+                    {
+                      "value": "commercial_provider_backbone",
+                      "text": "Traffic carried to the backbone by commercial providers"
+                    },
+                    {
+                      "value": "man",
+                      "text": "MAN"
+                    },
+                    {
+                      "value": "other",
+                      "text": "Other"
+                    }
+                  ]
+                }
+              ],
+              "rows": [
+                {
+                  "value": "universities",
+                  "text": "Universities & Other (ISCED 6-8)"
+                },
+                {
+                  "value": "further_education",
+                  "text": "Further education (ISCED 4-5)"
+                },
+                {
+                  "value": "secondary_schools",
+                  "text": "Secondary schools (ISCED 2-3)"
+                },
+                {
+                  "value": "primary_schools",
+                  "text": "Primary schools (ISCED 1)"
+                },
+                {
+                  "value": "institutes",
+                  "text": "Research Institutes"
+                },
+                {
+                  "value": "cultural",
+                  "text": "Libraries, Museums, Archives, Cultural institutions"
+                },
+                {
+                  "value": "hospitals",
+                  "text": "Non-university public Hospitals"
+                },
+                {
+                  "value": "government",
+                  "text": "Government departments (national, regional, local)"
+                },
+                {
+                  "value": "iros",
+                  "text": "International (virtual) research organisations"
+                },
+                {
+                  "value": "for_profit_orgs",
+                  "text": "For-profit organisations"
+                }
+              ]
+            },
+            {
+              "type": "matrixdropdown",
+              "name": "traffic_load",
+              "title": "What are the traffic loads in Mbit/s?",
+              "columns": [
+                {
+                  "name": "average_from_institutions_to_network",
+                  "title": "Average load from institutions to the network",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    },
+                    {
+                      "type": "regex",
+                      "text": "Please round to a whole number",
+                      "regex": "^[0-9]*$"
+                    }
+                  ]
+                },
+                {
+                  "name": "average_to_institutions_from_network",
+                  "title": "Average load to institutions from the network",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    },
+                    {
+                      "type": "regex",
+                      "text": "Please round to a whole number",
+                      "regex": "^[0-9]*$"
+                    }
+                  ]
+                },
+                {
+                  "name": "peak_from_institutions_to_network",
+                  "title": "Peak load from institutions to the network",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    },
+                    {
+                      "type": "regex",
+                      "text": "Please round to a whole number",
+                      "regex": "^[0-9]*$"
+                    }
+                  ]
+                },
+                {
+                  "name": "peak_to_institutions_from_network",
+                  "title": "Peak load to institutions from the network",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    },
+                    {
+                      "type": "regex",
+                      "text": "Please round to a whole number",
+                      "regex": "^[0-9]*$"
+                    }
+                  ]
+                }
+              ],
+              "rows": [
+                {
+                  "value": "universities",
+                  "text": "Universities & Other (ISCED 6-8)"
+                },
+                {
+                  "value": "further_education",
+                  "text": "Further education (ISCED 4-5)"
+                },
+                {
+                  "value": "secondary_schools",
+                  "text": "Secondary schools (ISCED 2-3)"
+                },
+                {
+                  "value": "primary_schools",
+                  "text": "Primary schools (ISCED 1)"
+                },
+                {
+                  "value": "institutes",
+                  "text": "Research Institutes"
+                },
+                {
+                  "value": "cultural",
+                  "text": "Libraries, Museums, Archives, Cultural institutions"
+                },
+                {
+                  "value": "hospitals",
+                  "text": "Non-university public Hospitals"
+                },
+                {
+                  "value": "government",
+                  "text": "Government departments (national, regional, local)"
+                },
+                {
+                  "value": "iros",
+                  "text": "International (virtual) research organisations"
+                },
+                {
+                  "value": "for_profit_orgs",
+                  "text": "For-profit organisations"
+                }
+              ]
+            },
+            {
+              "type": "matrixdropdown",
+              "name": "traffic_growth",
+              "title": "What do you expect the traffic growth to be in the next 3 years?",
+              "columns": [
+                {
+                  "name": "growth_rate",
+                  "title": "% growth",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric"
+                    }
+                  ]
+                }
+              ],
+              "rows": [
+                {
+                  "value": "universities",
+                  "text": "Universities & Other (ISCED 6-8)"
+                },
+                {
+                  "value": "further_education",
+                  "text": "Further education (ISCED 4-5)"
+                },
+                {
+                  "value": "secondary_schools",
+                  "text": "Secondary schools (ISCED 2-3)"
+                },
+                {
+                  "value": "primary_schools",
+                  "text": "Primary schools (ISCED 1)"
+                },
+                {
+                  "value": "institutes",
+                  "text": "Research Institutes"
+                },
+                {
+                  "value": "cultural",
+                  "text": "Libraries, Museums, Archives, Cultural institutions"
+                },
+                {
+                  "value": "hospitals",
+                  "text": "Non-university public Hospitals"
+                },
+                {
+                  "value": "government",
+                  "text": "Government departments (national, regional, local)"
+                },
+                {
+                  "value": "iros",
+                  "text": "International (virtual) research organisations"
+                },
+                {
+                  "value": "for_profit_orgs",
+                  "text": "For-profit organisations"
+                }
+              ]
+            }
+          ],
+          "title": "CONNECTED USERS"
+        },
+        {
+          "type": "panel",
+          "name": "connected_users_commercial",
+          "elements": [
+            {
+              "type": "matrixdropdown",
+              "name": "commercial_organizations",
+              "title": "What types of commercial organisations do you connect?",
+              "columns": [
+                {
+                  "name": "connection",
+                  "title": "Connection",
+                  "cellType": "dropdown",
+                  "choices": [
+                    {
+                      "value": "yes_incl_other",
+                      "text": "Yes - Including transit to other networks"
+                    },
+                    {
+                      "value": "yes_national_nren",
+                      "text": "Yes - National NREN access only"
+                    },
+                    {
+                      "value": "yes_if_sponsored",
+                      "text": "Yes - only if sponsored by a connected institution"
+                    },
+                    {
+                      "value": "no_but_direct_peering",
+                      "text": "No - but we offer a direct or IX peering"
+                    },
+                    {
+                      "value": "no_policy",
+                      "text": "No - not eligible for policy reasons"
+                    },
+                    {
+                      "value": "no_financial",
+                      "text": "No - financial restrictions (NREN is unable to charge/recover costs)"
+                    },
+                    {
+                      "value": "no_other",
+                      "text": "No - other reason / unsure"
+                    }
+                  ]
+                }
+              ],
+              "rows": [
+                {
+                  "value": "commercial_r_e",
+                  "text": "Commercial R&E traffic only"
+                },
+                {
+                  "value": "commercial_general",
+                  "text": "Commercial general"
+                },
+                {
+                  "value": "commercial_collaboration",
+                  "text": "Commercial for collaboration only (project/time limited)"
+                },
+                {
+                  "value": "commercial_service_provider",
+                  "text": "Commercial Service Provider"
+                },
+                {
+                  "value": "university_spin_off",
+                  "text": "University Spin Off/Incubator"
+                }
+              ]
+            },
+            {
+              "type": "matrixdropdown",
+              "name": "commercial_charging_levels",
+              "title": "What are the typical charging levels for the following types of commercial connections? Please tick all that apply:",
+              "columns": [
+                {
+                  "name": "charging_level",
+                  "title": "Charging level",
+                  "cellType": "dropdown",
+                  "choices": [
+                    {
+                      "value": "higher_than_r_e_charges",
+                      "text": "Charges typically higher than for R&E users"
+                    },
+                    {
+                      "value": "same_as_r_e_charges",
+                      "text": "Same charging model as for R&E users"
+                    },
+                    {
+                      "value": "no_charges_if_r_e_requested",
+                      "text": "No charges applied if requested by R&E users"
+                    },
+                    {
+                      "value": "lower_than_r_e_charges",
+                      "text": "Charges typically lower than for R&E users"
+                    }
+                  ]
+                }
+              ],
+              "rows": [
+                {
+                  "value": "collaboration",
+                  "text": "Connection to your network for collaboration with R&E users"
+                },
+                {
+                  "value": "services",
+                  "text": "Connection to your network for supplying services for R&E"
+                },
+                {
+                  "value": "peering",
+                  "text": "Direct peering (e.g. direct peering or cloud peering)"
+                }
+              ]
+            },
+            {
+              "type": "radiogroup",
+              "name": "remote_campuses",
+              "title": "Do you provide connectivity to any remote campuses in other countries?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "matrixdynamic",
+              "name": "remote_campuses_specifics",
+              "visibleIf": "{remote_campuses} = 'Yes'",
+              "indent": 1,
+              "title": "Please specify:",
+              "columns": [
+                {
+                  "name": "country",
+                  "title": "Which Country",
+                  "cellType": "text",
+                  "isRequired": true
+                },
+                {
+                  "name": "connected",
+                  "title": "Connected to local R&E network",
+                  "cellType": "radiogroup",
+                  "choices": [
+                    "Yes",
+                    "No"
+                  ]
+                }
+              ],
+              "rowCount": 0,
+              "maxRowCount": 50
+            },
+            {
+              "type": "comment",
+              "name": "connected_users_comments",
+              "title": "Comments regarding this section:"
+            }
+          ],
+          "title": "CONNECTED USERS - COMMERCIAL"
+        }
+      ],
+      "title": "Connected Users"
+    },
+    {
+      "name": "network",
+      "elements": [
+        {
+          "type": "panel",
+          "name": "connectivity",
+          "elements": [
+            {
+              "type": "radiogroup",
+              "name": "dark_fibre_lease",
+              "title": "Does your NREN have an IRU or lease of dark fibre?",
+              "description": "An Indefeasible Right of Use (IRU) is essentially a long-term lease of a portion of the capacity of a cable. Please do not include fibre that you have installed and own yourself. This is covered in a later question.",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "text",
+              "name": "dark_fibre_lease_kilometers_inside_country",
+              "visibleIf": "{dark_fibre_lease} = 'Yes'",
+              "indent": 1,
+              "title": "Please state the number of kilometres of such fibre in your country:",
+              "description": "Please include only the fibre inside your country for this answer. The distance is the number of kilometers of your fibre pairs or if you are using bidirectional traffic on a single fibre please treat this as a fibre pair.",
+              "validators": [
+                {
+                  "type": "numeric",
+                  "minValue": 0
+                },
+                {
+                  "type": "regex",
+                  "text": "Please round to a whole number",
+                  "regex": "^[0-9]*$"
+                }
+              ]
+            },
+            {
+              "type": "text",
+              "name": "dark_fibre_lease_kilometers_outside_country",
+              "visibleIf": "{dark_fibre_lease} = 'Yes'",
+              "indent": 1,
+              "title": "Please state the number of kilometers of such fibre that is outside your country:",
+              "validators": [
+                {
+                  "type": "numeric",
+                  "minValue": 0
+                },
+                {
+                  "type": "regex",
+                  "text": "Please round to a whole number",
+                  "regex": "^[0-9]*$"
+                }
+              ]
+            },
+            {
+              "type": "text",
+              "name": "dark_fibre_lease_duration",
+              "visibleIf": "{dark_fibre_lease} = 'Yes'",
+              "indent": 1,
+              "title": "What is the average duration, in years, of your IRU?",
+              "validators": [
+                {
+                  "type": "numeric",
+                  "minValue": 0
+                }
+              ]
+            },
+            {
+              "type": "radiogroup",
+              "name": "dark_fibre_nren",
+              "title": "Has your NREN physically laid any dark fibre cables in your network?",
+              "description": "Please include only cables that you laid yourself. If this cable was installed and is owned by a third party, this is included in a previous question.",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "text",
+              "name": "dark_fibre_nren_kilometers_inside_country",
+              "visibleIf": "{dark_fibre_nren} = 'Yes'",
+              "indent": 1,
+              "title": "Please state the number of kilometers of such fibre in your network:",
+              "description": "Please include only the fibre inside your country for this answer. The distance is the number of kilometers of your fibre pairs, or If you are using bi-directional traffic on a single fibre please treat this as a fibre pair.",
+              "validators": [
+                {
+                  "type": "numeric",
+                  "minValue": 0
+                },
+                {
+                  "type": "regex",
+                  "text": "Please round to a whole number",
+                  "regex": "^[0-9]*$"
+                }
+              ]
+            },
+            {
+              "type": "radiogroup",
+              "name": "fibre_light",
+              "title": "How do you light your fibre network:",
+              "choices": [
+                {
+                  "value": "nren_owns_and_operates",
+                  "text": "NREN owns and operates equipment"
+                },
+                {
+                  "value": "nren_owns_outsourced_operation",
+                  "text": "NREN owns equipment and operation is outsourced"
+                },
+                {
+                  "value": "outsourced_ownership_and_operation",
+                  "text": "Ownership and management are out-sourced (turn-key model)"
+                }
+              ],
+              "showOtherItem": true,
+              "showClearButton": true
+            },
+            {
+              "type": "matrixdynamic",
+              "name": "network_map_urls",
+              "title": "Please provide a network map for layers 1, 2 and 3 of your network:",
+              "columns": [
+                {
+                  "name": "network_map_url",
+                  "title": "Url:",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "expression",
+                      "text": "Please provide a single valid website url including http:// or https://",
+                      "expression": "validateWebsiteUrl({row.network_map_url})"
+                    }
+                  ],
+                  "inputType": "url"
+                }
+              ],
+              "rowCount": 1,
+              "maxRowCount": 50
+            }
+          ],
+          "title": "Connectivity"
+        },
+        {
+          "type": "panel",
+          "name": "performance",
+          "elements": [
+            {
+              "type": "checkbox",
+              "name": "monitoring_tools",
+              "title": "Which tools do you offer your client institutions for monitoring or troubleshooting the network?",
+              "choices": [
+                {
+                  "value": "looking_glass",
+                  "text": "Looking Glass"
+                },
+                {
+                  "value": "status_dashboard",
+                  "text": "Network or Services Status Dashboard"
+                },
+                {
+                  "value": "historical_traffic_volumes",
+                  "text": "Historical traffic volume information"
+                },
+                {
+                  "value": "netflow_analysis",
+                  "text": "Netflow analysis tool"
+                }
+              ],
+              "showOtherItem": true
+            },
+            {
+              "type": "text",
+              "name": "netflow_vendors",
+              "title": "If you process NetFlow, please, indicate the system name and vendor:"
+            },
+            {
+              "type": "radiogroup",
+              "name": "passive_monitoring",
+              "title": "Do you passively monitor international traffic?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "radiogroup",
+              "name": "passive_monitoring_tech",
+              "visibleIf": "{passive_monitoring} = 'Yes'",
+              "indent": 1,
+              "title": "Do you use:",
+              "choices": [
+                {
+                  "value": "span_ports",
+                  "text": "SPAN ports"
+                },
+                {
+                  "value": "taps",
+                  "text": "Passive optical TAPS"
+                },
+                {
+                  "value": "both",
+                  "text": "Both"
+                }
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "radiogroup",
+              "name": "traffic_statistics",
+              "title": "Do you have traffic statistics on your website?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "matrixdynamic",
+              "name": "traffic_statistics_urls",
+              "visibleIf": "{traffic_statistics} = 'Yes'",
+              "indent": 1,
+              "title": "Please give the URL(s):",
+              "columns": [
+                {
+                  "name": "traffic_statistics_url",
+                  "title": "Url:",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "expression",
+                      "text": "Please provide a single valid website url including http:// or https://",
+                      "expression": "validateWebsiteUrl({row.traffic_statistics_url})"
+                    }
+                  ],
+                  "inputType": "url"
+                }
+              ],
+              "rowCount": 1,
+              "maxRowCount": 50
+            },
+            {
+              "type": "checkbox",
+              "name": "siem_soc_vendor",
+              "title": "If you use a SIEM/SOC system, please indicate the name of the vendor you use here:",
+              "choices": [
+                "Splunk",
+                "IBM Qradar",
+                "Exabeam",
+                "LogRythm",
+                "Securonix"
+              ],
+              "showOtherItem": true
+            },
+            {
+              "type": "checkbox",
+              "name": "certificate_service",
+              "title": "Which certificate service do you use?",
+              "choices": [
+                "TCS",
+                "Digicert",
+                {
+                  "value": "Sectigo",
+                  "text": "Sectigo (outside of TCS)"
+                },
+                "Let's Encrypt",
+                "Entrust Datacard"
+              ],
+              "showOtherItem": true
+            },
+            {
+              "type": "radiogroup",
+              "name": "network_weather",
+              "title": "Do you have an online weather map of your network?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "text",
+              "name": "network_weather_url",
+              "visibleIf": "{network_weather} = 'Yes'",
+              "indent": 1,
+              "title": "Please give the URL:",
+              "validators": [
+                {
+                  "type": "expression",
+                  "text": "Please provide a single valid website url including http:// or https://",
+                  "expression": "validateWebsiteUrl({network_weather_url})"
+                }
+              ],
+              "inputType": "url"
+            },
+            {
+              "type": "radiogroup",
+              "name": "pert_team",
+              "title": "Do you run a PERT team?",
+              "choices": [
+                "Yes",
+                "No",
+                "Planned"
+              ],
+              "showClearButton": true
+            }
+          ],
+          "title": "PERFORMANCE MONITORING AND MANAGEMENT"
+        },
+        {
+          "type": "panel",
+          "name": "alienwave",
+          "elements": [
+            {
+              "type": "radiogroup",
+              "name": "alienwave_services",
+              "title": "Does your NREN make use of alien wavelength/lightpath services provided by third parties?",
+              "description": "This does not include alien waves used internally inside your network e.g. coloured optics on routers as they are covered in a later question.",
+              "choices": [
+                "Yes",
+                "No",
+                "Planned"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "text",
+              "name": "alienwave_services_number",
+              "visibleIf": "{alienwave_services} = 'yes'",
+              "indent": 1,
+              "title": "Please state the number of individual alien wavelength services:",
+              "validators": [
+                {
+                  "type": "numeric",
+                  "minValue": 0
+                },
+                {
+                  "type": "regex",
+                  "text": "Please round to a whole number",
+                  "regex": "^[0-9]*$"
+                }
+              ]
+            },
+            {
+              "type": "radiogroup",
+              "name": "alienwave_internal",
+              "title": "Are you using alien waves internally in your network?",
+              "description": "This includes for example alien waves used between two equipment vendors e.g. coloured optics on routers carried over DWDM equipment.",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            }
+          ],
+          "title": "Alienwave",
+          "description": "Pure optical connectivity services provided by the NREN without a known framing, such as foreign, alien wavelength."
+        },
+        {
+          "type": "panel",
+          "name": "capacity",
+          "elements": [
+            {
+              "type": "text",
+              "name": "max_capacity",
+              "title": "What is the capacity (in Gbit/s) of the largest link in your network used for internet traffic (either shared or dedicated)?",
+              "description": "Please provide the sum of aggregated links, but don't include backup capacity.",
+              "validators": [
+                {
+                  "type": "numeric",
+                  "minValue": 0
+                }
+              ]
+            },
+            {
+              "type": "text",
+              "name": "typical_capacity",
+              "title": "What is the current typical core usable backbone IP capacity of your network in Gbit/s?",
+              "description": "Note this refers to circuit capacity not traffic e.g. 2 x 10GE LAG aggregated links.",
+              "validators": [
+                {
+                  "type": "numeric",
+                  "minValue": 0
+                }
+              ]
+            },
+            {
+              "type": "matrixdynamic",
+              "name": "external_connections",
+              "title": "Please provide a list of the operational external IP connections (usable links excluding backup links): ",
+              "description": "This should include links to your regional backbone (GÉANT, APAN, RedCLARA etc, to other research locations, to the commercial Internet, peerings to Internet exchanges, cross-border dark fibre links and any other links you may have. Note that we are interested in the capacity for production purposes, not in any additional links that may be there for the purpose of giving resilience. Some of your capacity to your regional backbone may be used for transiting to intercontinental services; please include these too. Cross-border fibre links means those links that have been commissioned or established by the NREN from a point on the network that is near the border to another point near the border on the network of a neighbouring NREN, for example.",
+              "columns": [
+                {
+                  "name": "link_name",
+                  "title": "Link name",
+                  "cellType": "text"
+                },
+                {
+                  "name": "capacity",
+                  "title": "Capacity (Gbit/s):",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    }
+                  ]
+                },
+                {
+                  "name": "from_organization",
+                  "title": "The link is from (organisation):",
+                  "cellType": "text"
+                },
+                {
+                  "name": "to_organization",
+                  "title": "To (organisation):",
+                  "cellType": "text"
+                },
+                {
+                  "name": "interconnection_method",
+                  "title": "Interconnection method:",
+                  "cellType": "dropdown",
+                  "choices": [
+                    {
+                      "value": "internet_exchange",
+                      "text": "Internet exchange points"
+                    },
+                    {
+                      "value": "open_exchange",
+                      "text": "Open Exchange points"
+                    },
+                    {
+                      "value": "direct",
+                      "text": "Directly connected R&E peers"
+                    },
+                    {
+                      "value": "geant",
+                      "text": "GEANT"
+                    },
+                    {
+                      "value": "other",
+                      "text": "Other"
+                    }
+                  ]
+                }
+              ],
+              "rowCount": 1,
+              "maxRowCount": 50
+            },
+            {
+              "type": "text",
+              "name": "non_r_and_e_peers",
+              "title": "Please state how many non-R&E networks you are peering with:",
+              "description": "This should include all direct IP-peerings to commercial networks e.g. Google.",
+              "validators": [
+                {
+                  "type": "numeric",
+                  "minValue": 0
+                },
+                {
+                  "type": "regex",
+                  "text": "Please round to a whole number",
+                  "regex": "^[0-9]*$"
+                }
+              ]
+            },
+            {
+              "type": "multipletext",
+              "name": "traffic_estimate",
+              "title": "Please supply an estimate of the total amount of traffic in Terabytes in the last 12 months for the following:",
+              "items": [
+                {
+                  "name": "from_customers",
+                  "title": "Traffic from NREN customers (sources that are part of the remit of the NREN's domain)",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    }
+                  ]
+                },
+                {
+                  "name": "to_customers",
+                  "title": "Traffic to NREN customers (sources that are part of the remit of the NREN's domain)",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    }
+                  ]
+                },
+                {
+                  "name": "from_external",
+                  "title": "Traffic from external networks (sources that are outside the NREN's domain, such as, GÉANT, General/Commercial Internet, Internet exchanges, peerings, other NRENs etc)",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    }
+                  ]
+                },
+                {
+                  "name": "to_external",
+                  "title": "Traffic to external networks (sources that are outside the NREN's domain, such as, GÉANT, General/Commercial Internet, Internet exchanges, peerings, other NRENs etc)",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    }
+                  ]
+                }
+              ]
+            },
+            {
+              "type": "multipletext",
+              "name": "commodity_vs_r_e",
+              "title": "What is the ratio of commodity vs R&E traffic in your network?",
+              "validators": [
+                {
+                  "type": "expression",
+                  "text": "The percentages should add up to 100",
+                  "expression": "(sum({commodity_vs_r_e.r_e}, {commodity_vs_r_e.commodity}) = 100) or ({commodity_vs_r_e} empty)"
+                }
+              ],
+              "items": [
+                {
+                  "name": "r_e",
+                  "title": "R&E percentage",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0,
+                      "maxValue": 100
+                    }
+                  ]
+                },
+                {
+                  "name": "commodity",
+                  "title": "Commodity percentage",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0,
+                      "maxValue": 100
+                    }
+                  ]
+                }
+              ]
+            }
+          ],
+          "title": "CAPACITY"
+        },
+        {
+          "type": "panel",
+          "name": "sdn",
+          "elements": [
+            {
+              "type": "radiogroup",
+              "name": "operational_process_automation",
+              "title": "Are you automating your operational processes?",
+              "choices": [
+                "Yes",
+                "No",
+                "Planned"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "text",
+              "name": "operational_process_automation_tools",
+              "visibleIf": "{operational_process_automation} = 'yes'",
+              "indent": 1,
+              "title": "Please specify which processes and the name/s of the automation software and tools you use for it:"
+            },
+            {
+              "type": "radiogroup",
+              "name": "nfv",
+              "title": "Do you use any kind of NFV?",
+              "choices": [
+                "Yes",
+                "No",
+                "Planned"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "checkbox",
+              "name": "nfv_types",
+              "visibleIf": "{nfv} = 'yes' or {nfv} = 'planned'",
+              "indent": 1,
+              "title": "What kind of NFV do/will you use:",
+              "choices": [
+                {
+                  "value": "routers",
+                  "text": "Routers/switches"
+                },
+                {
+                  "value": "firewalls",
+                  "text": "Firewalls"
+                },
+                {
+                  "value": "load_balancers",
+                  "text": "Load balancers"
+                },
+                {
+                  "value": "vpn_concentrators",
+                  "text": "VPN Concentrator Services"
+                }
+              ],
+              "showOtherItem": true
+            },
+            {
+              "type": "radiogroup",
+              "name": "network_automation",
+              "title": "Do you use automation on your network?",
+              "choices": [
+                "Yes",
+                "No",
+                "Planned"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "checkbox",
+              "name": "network_automation_tasks",
+              "visibleIf": "{network_automation} = 'yes' or {network_automation} = 'planned'",
+              "indent": 1,
+              "title": "What kind of task do you use it for?",
+              "choices": [
+                {
+                  "value": "provisioning",
+                  "text": "Device Provisioning"
+                },
+                {
+                  "value": "data_collection",
+                  "text": "Data Collection"
+                },
+                {
+                  "value": "config_management",
+                  "text": "Configuration Management"
+                },
+                {
+                  "value": "compliance",
+                  "text": "Compliance"
+                },
+                {
+                  "value": "reporting",
+                  "text": "Reporting"
+                },
+                {
+                  "value": "troubleshooting",
+                  "text": "Troubleshooting"
+                }
+              ]
+            }
+          ],
+          "title": "SOFTWARE-DEFINED NETWORKING (SDN) AND NETWORK FUNCTION VIRTUALISATION (NFV)"
+        },
+        {
+          "type": "comment",
+          "name": "network_comments",
+          "title": "Comments regarding this section:"
+        }
+      ],
+      "title": "Network"
+    },
+    {
+      "name": "services",
+      "elements": [
+        {
+          "type": "html",
+          "name": "services_hover_explanation",
+          "html": "Descriptions of the individual services are shown if you hover over the service name."
+        },
+        {
+          "type": "matrixdropdown",
+          "name": "services_network",
+          "state": "collapsed",
+          "title": "Network services",
+          "hideCheckboxLabels": true,
+          "columns": [
+            {
+              "name": "offered",
+              "title": "Service offered",
+              "cellType": "checkbox",
+              "choices": [
+                {
+                  "value": "yes",
+                  "text": "Yes"
+                }
+              ]
+            },
+            {
+              "name": "name",
+              "title": "Service name",
+              "cellType": "text",
+              "width": "20%",
+              "visibleIf": "{row.offered} = ['yes']"
+            },
+            {
+              "name": "description",
+              "title": "Official description",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2
+            },
+            {
+              "name": "additional_information",
+              "title": "Additional information",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2,
+              "placeholder": "(e.g. software used, third party service, links, etc.)"
+            }
+          ],
+          "cellType": "checkbox",
+          "rows": [
+            {
+              "value": "connectivity",
+              "text": "IP Connectivity",
+              "customDescription": "Basic IP connectivity services inc R&E and commodity internet"
+            },
+            {
+              "value": "home-vpn",
+              "text": "Remote access VPN server",
+              "customDescription": "Remote access VPN for end users (e.g. eduVPN) or site-to-site VPN with the possibility of encryption"
+            },
+            {
+              "value": "ipv6",
+              "text": "IPv6",
+              "customDescription": "The new version of the internet protocol (IP) that will eventually replace IPv4"
+            },
+            {
+              "value": "dedicated-optical-connections",
+              "text": "Dedicated optical connections",
+              "customDescription": "Provision of dedicated optical connections to users, e.g. Layer 1 optical channels or Open Lightpath exchanges."
+            },
+            {
+              "value": "managed-router",
+              "text": "Managed router service",
+              "customDescription": "Remote router support for institutions"
+            },
+            {
+              "value": "multicast",
+              "text": "Multicast",
+              "customDescription": "Extension to the IP protocol which allows individual packets to be sent to multiple hosts on the internet"
+            },
+            {
+              "value": "netflow",
+              "text": "Netflow tool",
+              "customDescription": "Network protocol of collecting IP traffic and monitoring network traffic"
+            },
+            {
+              "value": "user-monitoring",
+              "text": "Network troubleshooting",
+              "customDescription": "Providing a support service to identify, diagnose and resolve problems and issues within a computer network, using network monitoring system tools like Looking Glass."
+            },
+            {
+              "value": "network-monitoring",
+              "text": "Network monitoring",
+              "customDescription": "Network Monitoring systems is software/hardware tools that can track various aspects of a network and its operation. These systems can detect devices and other network elements that comprise or touch the network, as well as provide status updates."
+            },
+            {
+              "value": "pert",
+              "text": "PERT",
+              "customDescription": "Team supporting resolution of end-to-end performance problems for networked applications"
+            },
+            {
+              "value": "point-to-point-circuit-vpn",
+              "text": "Virtual circuits/network VPNs",
+              "customDescription": "Virtual point to point circuits or VPNs to create virtual networks between geographically distinct sites"
+            },
+            {
+              "value": "quality-of-service",
+              "text": "Quality of Service",
+              "customDescription": "Preferential service to specific applications or classes of applications"
+            }
+          ],
+          "rowTitleWidth": "20%"
+        },
+        {
+          "type": "matrixdropdown",
+          "name": "services_isp",
+          "state": "collapsed",
+          "title": "ISP services",
+          "hideCheckboxLabels": true,
+          "columns": [
+            {
+              "name": "offered",
+              "title": "Service offered",
+              "cellType": "checkbox",
+              "choices": [
+                {
+                  "value": "yes",
+                  "text": "Yes"
+                }
+              ]
+            },
+            {
+              "name": "name",
+              "title": "Service name",
+              "cellType": "text",
+              "width": "20%",
+              "visibleIf": "{row.offered} = ['yes']"
+            },
+            {
+              "name": "description",
+              "title": "Official description",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2
+            },
+            {
+              "name": "additional_information",
+              "title": "Additional information",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2,
+              "placeholder": "(e.g. software used, third party service, links, etc.)"
+            }
+          ],
+          "cellType": "checkbox",
+          "rows": [
+            {
+              "value": "domain-registration",
+              "text": "Domain name registration",
+              "customDescription": "Adminstration/registration of top and second level domain names"
+            },
+            {
+              "value": "ip-address-allocation",
+              "text": "IP address allocation",
+              "customDescription": "Allocating addresses for users according to the RIPE policies"
+            },
+            {
+              "value": "ix-operation",
+              "text": "National IX operation",
+              "customDescription": "Operating an IX with national importance"
+            },
+            {
+              "value": "nameserver",
+              "text": "Nameserver services",
+              "customDescription": "Operation of nameservers and maintenance of DNS information on behalf of users"
+            },
+            {
+              "value": "timeserver-ntp",
+              "text": "NTP service",
+              "customDescription": "Allows the synchronization of computer clocks over the internet"
+            }
+          ],
+          "rowTitleWidth": "20%"
+        },
+        {
+          "type": "matrixdropdown",
+          "name": "services_security",
+          "state": "collapsed",
+          "title": "Security services",
+          "hideCheckboxLabels": true,
+          "columns": [
+            {
+              "name": "offered",
+              "title": "Service offered",
+              "cellType": "checkbox",
+              "choices": [
+                {
+                  "value": "yes",
+                  "text": "Yes"
+                }
+              ]
+            },
+            {
+              "name": "name",
+              "title": "Service name",
+              "cellType": "text",
+              "width": "20%",
+              "visibleIf": "{row.offered} = ['yes']"
+            },
+            {
+              "name": "description",
+              "title": "Official description",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2
+            },
+            {
+              "name": "additional_information",
+              "title": "Additional information",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2,
+              "placeholder": "(e.g. software used, third party service, links, etc.)"
+            }
+          ],
+          "cellType": "checkbox",
+          "rows": [
+            {
+              "value": "anti-spam",
+              "text": "Anti-spam solution",
+              "customDescription": "Anti-Spam solutions for detecting and eliminating viruses and spam mails"
+            },
+            {
+              "value": "csirt",
+              "text": "CERT/CSIRT",
+              "customDescription": "A single point of contact for users to deal with computer security incidents and prevention"
+            },
+            {
+              "value": "ddos-prevention",
+              "text": "DDoS mitigation",
+              "customDescription": "Tools and techniques for mitigating Distributed Denial of Service attacks"
+            },
+            {
+              "value": "external-security-awareness",
+              "text": "External security awareness programme",
+              "customDescription": "An external security awareness programme is a service offering to users  (consisting for example in training, workshops, toolkits, campaigns, awareness games, …) aimed at helping member institutions to effectively manage human risk"
+            },
+            {
+              "value": "firewall-on-demand",
+              "text": "Firewall-on-Demand",
+              "customDescription": "Provision of a dynamic firewall services to mitigate against DDoS attacks"
+            },
+            {
+              "value": "intrusion",
+              "text": "Intrusion detection",
+              "customDescription": "System for detecting and preventing Intrusions (IDS/IPS)"
+            },
+            {
+              "value": "pgp-key",
+              "text": "PGP key server",
+              "customDescription": "Operation of PGP key server"
+            },
+            {
+              "value": "security-audit",
+              "text": "Security auditing",
+              "customDescription": "Carrying out vulnerability assessments and security reviews of user systems and resources on their behalf"
+            },
+            {
+              "value": "vulnerability-testing",
+              "text": "Vulnerability scanning",
+              "customDescription": "Vulnerability service that allows users to scan their own IP networks fo security holes"
+            },
+            {
+              "value": "web-filtering",
+              "text": "Web filtering",
+              "customDescription": "Centralised web content filtering service for protection against access to inappropriate content"
+            }
+          ],
+          "rowTitleWidth": "20%"
+        },
+        {
+          "type": "matrixdropdown",
+          "name": "services_identity",
+          "state": "collapsed",
+          "title": "Identity services",
+          "hideCheckboxLabels": true,
+          "columns": [
+            {
+              "name": "offered",
+              "title": "Service offered",
+              "cellType": "checkbox",
+              "choices": [
+                {
+                  "value": "yes",
+                  "text": "Yes"
+                }
+              ]
+            },
+            {
+              "name": "name",
+              "title": "Service name",
+              "cellType": "text",
+              "width": "20%",
+              "visibleIf": "{row.offered} = ['yes']"
+            },
+            {
+              "name": "description",
+              "title": "Official description",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2
+            },
+            {
+              "name": "additional_information",
+              "title": "Additional information",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2,
+              "placeholder": "(e.g. software used, third party service, links, etc.)"
+            }
+          ],
+          "cellType": "checkbox",
+          "rows": [
+            {
+              "value": "aai",
+              "text": "Hosted campus AAI",
+              "customDescription": "Hosting of an Identity Provider service on behalf of connected Institutions to authenticate users"
+            },
+            {
+              "value": "eduroam-wifi",
+              "text": "eduroam",
+              "customDescription": "Inter-WLAN service to facilitate easy and secure Internet access for roaming educationals users"
+            },
+            {
+              "value": "interfederation",
+              "text": "Interfederation",
+              "customDescription": "Participation in an interfederation (i.e. eduGAIN, KALMAR)"
+            }
+          ],
+          "rowTitleWidth": "20%"
+        },
+        {
+          "type": "matrixdropdown",
+          "name": "services_hosting",
+          "state": "collapsed",
+          "title": "Storage & hosting services",
+          "hideCheckboxLabels": true,
+          "columns": [
+            {
+              "name": "offered",
+              "title": "Service offered",
+              "cellType": "checkbox",
+              "choices": [
+                {
+                  "value": "yes",
+                  "text": "Yes"
+                }
+              ]
+            },
+            {
+              "name": "name",
+              "title": "Service name",
+              "cellType": "text",
+              "width": "20%",
+              "visibleIf": "{row.offered} = ['yes']"
+            },
+            {
+              "name": "description",
+              "title": "Official description",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2
+            },
+            {
+              "name": "additional_information",
+              "title": "Additional information",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2,
+              "placeholder": "(e.g. software used, third party service, links, etc.)"
+            }
+          ],
+          "cellType": "checkbox",
+          "rows": [
+            {
+              "value": "cloud-service-end-user",
+              "text": "Cloud storage (end user)",
+              "customDescription": "Browser-based virtual storage service for individuals"
+            },
+            {
+              "value": "content-delivery-hosting",
+              "text": "Content delivery hosting",
+              "customDescription": "Hosting of contenct delivery servers, e.g. Akamai"
+            },
+            {
+              "value": "disaster-recovery",
+              "text": "Disaster recovery",
+              "customDescription": "Off site backup services"
+            },
+            {
+              "value": "dns-server",
+              "text": "DNS hosting",
+              "customDescription": "Hosting of primary and secondary DNS servers"
+            },
+            {
+              "value": "email-services",
+              "text": "Email server hosting",
+              "customDescription": "NREN hosted email servers."
+            },
+            {
+              "value": "filesender",
+              "text": "FileSender",
+              "customDescription": "Web-based application that allows authenticated userds to securely and easily send arbitrarily large files"
+            },
+            {
+              "value": "saas",
+              "text": "SaaS",
+              "customDescription": "Software as a service e.g. Google Apps for Education"
+            },
+            {
+              "value": "storage-co-location",
+              "text": "Housing/co-location",
+              "customDescription": "Hosting of user equipment in a managed data centre"
+            },
+            {
+              "value": "virtual-machines-iaas",
+              "text": "Virtual machines/IaaS",
+              "customDescription": "Access to virtual computing resources"
+            }
+          ],
+          "rowTitleWidth": "20%"
+        },
+        {
+          "type": "matrixdropdown",
+          "name": "services_multimedia",
+          "state": "collapsed",
+          "title": "Multimedia services",
+          "hideCheckboxLabels": true,
+          "columns": [
+            {
+              "name": "offered",
+              "title": "Service offered",
+              "cellType": "checkbox",
+              "choices": [
+                {
+                  "value": "yes",
+                  "text": "Yes"
+                }
+              ]
+            },
+            {
+              "name": "name",
+              "title": "Service name",
+              "cellType": "text",
+              "width": "20%",
+              "visibleIf": "{row.offered} = ['yes']"
+            },
+            {
+              "name": "description",
+              "title": "Official description",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2
+            },
+            {
+              "name": "additional_information",
+              "title": "Additional information",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2,
+              "placeholder": "(e.g. software used, third party service, links, etc.)"
+            }
+          ],
+          "cellType": "checkbox",
+          "rows": [
+            {
+              "value": "internet-radio-tv",
+              "text": "TV/radio streaming",
+              "customDescription": "Internet and radio streaming services"
+            },
+            {
+              "value": "videoconferencing",
+              "text": "Event recording/streaming",
+              "customDescription": "Provision of equipment and/or software to support event streaming/recording"
+            },
+            {
+              "value": "video-portal",
+              "text": "Provision of content portal",
+              "customDescription": "Multi-media content portal"
+            },
+            {
+              "value": "web-conferencing",
+              "text": "Web/desktop conferencing",
+              "customDescription": "Video conferencing service to desktops and hand-held devices using software"
+            }
+          ],
+          "rowTitleWidth": "20%"
+        },
+        {
+          "type": "matrixdropdown",
+          "name": "services_collaboration",
+          "state": "collapsed",
+          "title": "Collaboration services",
+          "hideCheckboxLabels": true,
+          "columns": [
+            {
+              "name": "offered",
+              "title": "Service offered",
+              "cellType": "checkbox",
+              "choices": [
+                {
+                  "value": "yes",
+                  "text": "Yes"
+                }
+              ]
+            },
+            {
+              "name": "name",
+              "title": "Service name",
+              "cellType": "text",
+              "width": "20%",
+              "visibleIf": "{row.offered} = ['yes']"
+            },
+            {
+              "name": "description",
+              "title": "Official description",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2
+            },
+            {
+              "name": "additional_information",
+              "title": "Additional information",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2,
+              "placeholder": "(e.g. software used, third party service, links, etc.)"
+            }
+          ],
+          "cellType": "checkbox",
+          "rows": [
+            {
+              "value": "e-portfolio",
+              "text": "E-portfolio service",
+              "customDescription": "Functions to create user professional and career portfolios"
+            },
+            {
+              "value": "identifier-reg",
+              "text": "Identifier Registry",
+              "customDescription": "Registering of unique and automatically-processable identifiers in the form of text or numeric strings"
+            },
+            {
+              "value": "journal-library-access",
+              "text": "Journal access",
+              "customDescription": "Access to academic journals"
+            },
+            {
+              "value": "mailing-lists",
+              "text": "Mailing lists",
+              "customDescription": "Service for operation of electronic discussion lists"
+            },
+            {
+              "value": "project-collaboration-toolkit",
+              "text": "Project collaboration",
+              "customDescription": "Packaged services or virtual project groups e.g. mailing lists, storage, web meetings, wiki."
+            },
+            {
+              "value": "scheduling-tool",
+              "text": "Scheduling tool",
+              "customDescription": "Provision of tools to users for scheduling appointments or classes"
+            },
+            {
+              "value": "survey-tool",
+              "text": "Survey/polling tool",
+              "customDescription": "Provision of applications for creating surveys or polls"
+            },
+            {
+              "value": "virtual-learning-environment",
+              "text": "VLE",
+              "customDescription": "Online e-learning education system that provides virtual access to resources used in teaching"
+            },
+            {
+              "value": "voip",
+              "text": "VoIP",
+              "customDescription": "Service to deliver voice communications and multimedia sessions over Internet Protocal (IP) networks"
+            },
+            {
+              "value": "web-email-hosting",
+              "text": "Web hosting",
+              "customDescription": "Service to provide space on central web servers for users to publish their website"
+            }
+          ],
+          "rowTitleWidth": "20%"
+        },
+        {
+          "type": "matrixdropdown",
+          "name": "services_professional",
+          "state": "collapsed",
+          "title": "Professional services",
+          "hideCheckboxLabels": true,
+          "columns": [
+            {
+              "name": "offered",
+              "title": "Service offered",
+              "cellType": "checkbox",
+              "choices": [
+                {
+                  "value": "yes",
+                  "text": "Yes"
+                }
+              ]
+            },
+            {
+              "name": "name",
+              "title": "Service name",
+              "cellType": "text",
+              "width": "20%",
+              "visibleIf": "{row.offered} = ['yes']"
+            },
+            {
+              "name": "description",
+              "title": "Official description",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2
+            },
+            {
+              "name": "additional_information",
+              "title": "Additional information",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2,
+              "placeholder": "(e.g. software used, third party service, links, etc.)"
+            }
+          ],
+          "cellType": "checkbox",
+          "rows": [
+            {
+              "value": "consultancy",
+              "text": "Consultancy/training",
+              "customDescription": "Training and consultancy services provided by the NREN"
+            },
+            {
+              "value": "dissemination",
+              "text": "Dissemination",
+              "customDescription": "Dissemination of information to users e.g newsletters and magazines"
+            },
+            {
+              "value": "procurement",
+              "text": "Procurement brokerage",
+              "customDescription": "Procurement support for users to purchase services (e.g. cloud services), software/software licenses (e.g. VLE or office software) or hardware either directly or via procurement frameworks."
+            },
+            {
+              "value": "user-conference",
+              "text": "User conferences",
+              "customDescription": "Hosting of regular user conferences"
+            },
+            {
+              "value": "user-portal",
+              "text": "User portals",
+              "customDescription": "User portals for service management and monitoring"
+            }
+          ],
+          "rowTitleWidth": "20%"
+        },
+        {
+          "type": "comment",
+          "name": "service_comments",
+          "title": "Comments regarding this section:"
+        }
+      ],
+      "title": "Services"
+    }
+  ],
+  "showQuestionNumbers": "onPage",
+  "checkErrorsMode": "onValueChanged"
+}
\ No newline at end of file
diff --git a/compendium_v2/migrations/versions/aeb82357e76a_readonly_eosc_survey_questions.py b/compendium_v2/migrations/versions/aeb82357e76a_readonly_eosc_survey_questions.py
new file mode 100644
index 0000000000000000000000000000000000000000..9c84215e60deb513be1e7b765ac4f7c810253070
--- /dev/null
+++ b/compendium_v2/migrations/versions/aeb82357e76a_readonly_eosc_survey_questions.py
@@ -0,0 +1,2808 @@
+# flake8: noqa
+"""Readonly EOSC survey questions
+
+Revision ID: aeb82357e76a
+Revises: c91b6d6e6523
+Create Date: 2024-10-23 12:50:22.492113
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = 'aeb82357e76a'
+down_revision = 'c91b6d6e6523'
+branch_labels = None
+depends_on = None
+
+survey_model = """
+{
+  "title": "Compendium",
+  "logoPosition": "right",
+  "pages": [
+    {
+      "name": "organization",
+      "elements": [
+        {
+          "type": "panel",
+          "name": "panel1",
+          "elements": [
+            {
+              "type": "text",
+              "name": "budget",
+              "title": "What is your NREN's budget for {surveyyear} in million euro?",
+              "description": "Note that this question has changed slightly and now asks for the year we are still in. If your budget is not per calendar year, please provide figures for the budget that covers the largest part of {surveyyear} including GEANT subsidy. When responding to this question, please include all parts of your organisation serving your users, bearing in mind the services you deliver, as referenced later in the survey.",
+              "validators": [
+                {
+                  "type": "numeric",
+                  "minValue": 0
+                }
+              ]
+            },
+            {
+              "type": "multipletext",
+              "name": "income_sources",
+              "title": "Estimate (in % of income) the sources of your NREN-related income for {surveyyear}.",
+              "description": "European Funding should include GÉANT funding.",
+              "validators": [
+                {
+                  "type": "expression",
+                  "text": "The percentages should add up to 100",
+                  "expression": "(sum({income_sources.client_institutions}, {income_sources.gov_public_bodies}, {income_sources.european_funding}, {income_sources.commercial}, {income_sources.other}) = 100) or ({income_sources} empty)"
+                }
+              ],
+              "items": [
+                {
+                  "name": "client_institutions",
+                  "title": "Client institutions (universities/schools/research institutes/commercial/other)",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0,
+                      "maxValue": 100
+                    }
+                  ]
+                },
+                {
+                  "name": "gov_public_bodies",
+                  "title": "Government/public bodies",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0,
+                      "maxValue": 100
+                    }
+                  ]
+                },
+                {
+                  "name": "european_funding",
+                  "title": "European funding",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0,
+                      "maxValue": 100
+                    }
+                  ]
+                },
+                {
+                  "name": "commercial",
+                  "title": "Commercial services (e.g. domain reg, security)",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0,
+                      "maxValue": 100
+                    }
+                  ]
+                },
+                {
+                  "name": "other",
+                  "title": "Other",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0,
+                      "maxValue": 100
+                    }
+                  ]
+                }
+              ]
+            },
+            {
+              "type": "dropdown",
+              "name": "charging_mechanism",
+              "title": "How do you charge your client institutions?",
+              "choices": [
+                {
+                  "value": "no_charge",
+                  "text": "We do not charge them directly"
+                },
+                {
+                  "value": "flat_fee",
+                  "text": "We charge a flat fee, based on bandwidth"
+                },
+                {
+                  "value": "usage_based_fee",
+                  "text": "We charge a usage-based fee"
+                },
+                {
+                  "value": "combination",
+                  "text": "We use a combination of flat fee and usage-based fee"
+                },
+                {
+                  "value": "other",
+                  "text": "Other"
+                }
+              ]
+            }
+          ],
+          "title": "Budget, Income and Billing"
+        },
+        {
+          "type": "panel",
+          "name": "staff",
+          "elements": [
+            {
+              "type": "multipletext",
+              "name": "staff_employment_type",
+              "title": "What is the number of staff engaged in the NREN activities? (In Full Time Equivalent (FTEs))",
+              "description": "When responding to this question, please include all parts of your organisation serving your users, bearing in mind the services you deliver, as referenced later in the survey.",
+              "items": [
+                {
+                  "name": "permanent_fte",
+                  "title": "Permanent staff",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    }
+                  ]
+                },
+                {
+                  "name": "subcontracted_fte",
+                  "title": "Subcontracted staff",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    }
+                  ]
+                }
+              ]
+            },
+            {
+              "type": "multipletext",
+              "name": "staff_roles",
+              "title": "How much FTE is devoted to roles in the following functional areas?",
+              "items": [
+                {
+                  "name": "technical_fte",
+                  "title": "Technical roles",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    }
+                  ]
+                },
+                {
+                  "name": "nontechnical_fte",
+                  "title": "Non-technical roles",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    }
+                  ]
+                }
+              ]
+            },
+            {
+              "type": "radiogroup",
+              "name": "parent_organization",
+              "title": "Is your NREN part of a larger organisation (e.g. ministry, university)?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "text",
+              "name": "parent_organization_name",
+              "visibleIf": "{parent_organization} = 'Yes'",
+              "indent": 1,
+              "title": "What is the name of this larger organisation?"
+            },
+            {
+              "type": "radiogroup",
+              "name": "suborganizations",
+              "title": "Does your NREN have sub-organisations?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "matrixdynamic",
+              "name": "suborganization_details",
+              "visibleIf": "{suborganizations} = 'Yes'",
+              "indent": 1,
+              "title": "Please fill in the details:",
+              "columns": [
+                {
+                  "name": "suborganization_name",
+                  "title": "Name of your sub-organisation?",
+                  "cellType": "text",
+                  "isRequired": true
+                },
+                {
+                  "name": "suborganization_role",
+                  "title": "Role of your sub-organisation?",
+                  "cellType": "dropdown",
+                  "choices": [
+                    {
+                      "value": "idc",
+                      "text": "IDC federation"
+                    },
+                    {
+                      "value": "hpc",
+                      "text": "HPC centre"
+                    }
+                  ],
+                  "showOtherItem": true
+                }
+              ],
+              "rowCount": 1,
+              "maxRowCount": 30
+            },
+            {
+              "type": "radiogroup",
+              "name": "ec_projects",
+              "title": "Other than GÉANT, is your NREN involved in other EC projects?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "matrixdynamic",
+              "name": "ec_project_names",
+              "visibleIf": "{ec_projects} = 'Yes'",
+              "indent": 1,
+              "title": "Please list the name of the projects",
+              "description": "Use one line per project.",
+              "columns": [
+                {
+                  "name": "ec_project_name",
+                  "title": "Project name",
+                  "cellType": "text"
+                }
+              ],
+              "rowCount": 1
+            }
+          ],
+          "title": "Staff and Projects"
+        },
+        {
+          "type": "panel",
+          "name": "basic_information",
+          "elements": [
+            {
+              "type": "text",
+              "name": "full_name_english",
+              "title": "Full name of the organisation (in English):"
+            },
+            {
+              "type": "text",
+              "name": "full_name_national_languages",
+              "title": "Full name of the organisation in the national language(s):",
+              "description": "Please use the Latin alphabet."
+            },
+            {
+              "type": "text",
+              "name": "abbreviation_english",
+              "title": "Abbreviation in English (if applicable):",
+              "description": "Please always provide this information."
+            },
+            {
+              "type": "text",
+              "name": "abbreviation_national_languages",
+              "title": "Abbreviation in the national language(s) (if applicable):",
+              "description": "Please always provide this information. Please use the Latin alphabet."
+            },
+            {
+              "type": "text",
+              "name": "street_name_and_number",
+              "title": "Number and street name:"
+            },
+            {
+              "type": "text",
+              "name": "city",
+              "title": "Postal town (county):"
+            },
+            {
+              "type": "text",
+              "name": "postal_code",
+              "title": "Postal Code:"
+            },
+            {
+              "type": "text",
+              "name": "country",
+              "title": "Country (in English):"
+            },
+            {
+              "type": "text",
+              "name": "phone_number",
+              "title": "Phone number:",
+              "inputType": "tel"
+            },
+            {
+              "type": "text",
+              "name": "email_address",
+              "title": "General email address:",
+              "validators": [
+                {
+                  "type": "email"
+                }
+              ],
+              "inputType": "email"
+            },
+            {
+              "type": "text",
+              "name": "website",
+              "title": "Website:",
+              "validators": [
+                {
+                  "type": "expression",
+                  "text": "Please provide a single valid website url including http:// or https://",
+                  "expression": "validateWebsiteUrl({website})"
+                }
+              ],
+              "inputType": "url"
+            }
+          ],
+          "title": "Basic Information"
+        },
+        {
+          "type": "comment",
+          "name": "organization_comments",
+          "title": "Comments regarding this section:"
+        }
+      ],
+      "title": "Organisation"
+    },
+    {
+      "name": "standards_and_policies",
+      "elements": [
+        {
+          "type": "panel",
+          "name": "policy",
+          "elements": [
+            {
+              "type": "radiogroup",
+              "name": "corporate_strategy",
+              "title": "Have you made any updates to your corporate strategy over the last year?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "text",
+              "name": "corporate_strategy_url",
+              "title": "Please provide the URL for your latest (corporate) strategic plan?",
+              "validators": [
+                {
+                  "type": "expression",
+                  "text": "Please provide a single valid website url including http:// or https://",
+                  "expression": "validateWebsiteUrl({corporate_strategy_url})"
+                }
+              ],
+              "inputType": "url"
+            },
+            {
+              "type": "matrixdropdown",
+              "name": "policies",
+              "title": "Does your NREN have the following policies?",
+              "hideCheckboxLabels": true,
+              "columns": [
+                {
+                  "name": "available",
+                  "title": "Policy available",
+                  "cellType": "checkbox",
+                  "choices": [
+                    {
+                      "value": "yes",
+                      "text": "Yes"
+                    }
+                  ]
+                },
+                {
+                  "name": "url",
+                  "title": "Please provide the URL",
+                  "cellType": "text",
+                  "visibleIf": "{row.available} = ['yes']",
+                  "validators": [
+                    {
+                      "type": "expression",
+                      "text": "Please provide a single valid website url including http:// or https://",
+                      "expression": "validateWebsiteUrl({row.url})"
+                    }
+                  ],
+                  "inputType": "url"
+                }
+              ],
+              "cellType": "checkbox",
+              "rows": [
+                {
+                  "value": "environmental_policy",
+                  "text": "Environmental Policy"
+                },
+                {
+                  "value": "equal_opportunity_policy",
+                  "text": "Equality Opportunity Policy"
+                },
+                {
+                  "value": "gender_equality_policy",
+                  "text": "Gender Equality Plan"
+                },
+                {
+                  "value": "connectivity_policy",
+                  "text": "Connectivity Policy"
+                },
+                {
+                  "value": "acceptable_use_policy",
+                  "text": "Acceptable Use Policy (AUP)"
+                },
+                {
+                  "value": "privacy_notice",
+                  "text": "Organisational Privacy Policy"
+                },
+                {
+                  "value": "data_protection_contact",
+                  "text": "Dedicated contact for data protection, privacy or GDPR queries"
+                }
+              ],
+              "rowTitleWidth": "20%"
+            },
+            {
+              "type": "radiogroup",
+              "name": "central_software_procurement",
+              "title": "Do you centrally procure software for your customers?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "text",
+              "name": "central_procurement_amount",
+              "visibleIf": "{central_software_procurement} = 'yes'",
+              "indent": 1,
+              "title": "What is the total amount (in Euro) that you procured in {surveyyear} or {previousyear}/{surveyyear} on behalf of your customers?",
+              "validators": [
+                {
+                  "type": "numeric",
+                  "minValue": 0
+                },
+                {
+                  "type": "regex",
+                  "text": "Please round to a whole number",
+                  "regex": "^[0-9]*$"
+                }
+              ]
+            },
+            {
+              "type": "radiogroup",
+              "name": "formal_service_management_framework",
+              "title": "Does your NREN operate a formal service management framework for all of your services?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "radiogroup",
+              "name": "service_level_targets",
+              "title": "Are Service Level Targets available for your NREN services?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "matrixdropdown",
+              "name": "service_matrix",
+              "title": "Which service types are available for which user types?",
+              "columns": [
+                {
+                  "name": "service_types",
+                  "title": "Service types",
+                  "cellType": "checkbox",
+                  "showInMultipleColumns": true,
+                  "choices": [
+                    {
+                      "value": "network_services",
+                      "text": "Network services"
+                    },
+                    {
+                      "value": "isp_support",
+                      "text": "ISP support"
+                    },
+                    {
+                      "value": "security",
+                      "text": "Security"
+                    },
+                    {
+                      "value": "identity",
+                      "text": "Identity/T&I"
+                    },
+                    {
+                      "value": "collaboration",
+                      "text": "Collaboration"
+                    },
+                    {
+                      "value": "multimedia",
+                      "text": "Multimedia"
+                    },
+                    {
+                      "value": "storage_and_hosting",
+                      "text": "Storage and Hosting"
+                    },
+                    {
+                      "value": "professional_services",
+                      "text": "Professional services"
+                    }
+                  ]
+                }
+              ],
+              "rows": [
+                {
+                  "value": "universities",
+                  "text": "Universities & Other (ISCED 6-8)"
+                },
+                {
+                  "value": "further_education",
+                  "text": "Further education (ISCED 4-5)"
+                },
+                {
+                  "value": "secondary_schools",
+                  "text": "Secondary schools (ISCED 2-3)"
+                },
+                {
+                  "value": "primary_schools",
+                  "text": "Primary schools (ISCED 1)"
+                },
+                {
+                  "value": "institutes",
+                  "text": "Research Institutes"
+                },
+                {
+                  "value": "cultural",
+                  "text": "Libraries, Museums, Archives, Cultural institutions"
+                },
+                {
+                  "value": "hospitals",
+                  "text": "Non-university public Hospitals"
+                },
+                {
+                  "value": "government",
+                  "text": "Government departments (national, regional, local)"
+                },
+                {
+                  "value": "iros",
+                  "text": "International (virtual) research organisations"
+                },
+                {
+                  "value": "for_profit_orgs",
+                  "text": "For-profit organisations"
+                }
+              ]
+            },
+            {
+              "type": "radiogroup",
+              "name": "service_portfolio_eosc_portal",
+              "title": "Are any of the services in your service portfolio listed on the EOSC portal?",
+              "readOnly": true,
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "matrixdynamic",
+              "name": "services_on_eosc_portal_list",
+              "visibleIf": "{service_portfolio_eosc_portal} = 'Yes'",
+              "readOnly": true,
+              "indent": 1,
+              "title": "Can you list them?",
+              "columns": [
+                {
+                  "name": "service_name",
+                  "title": "Service name",
+                  "cellType": "text"
+                }
+              ],
+              "rowCount": 1,
+              "maxRowCount": 50
+            }
+          ],
+          "title": "Policy & Portfolio"
+        },
+        {
+          "type": "panel",
+          "name": "standards",
+          "elements": [
+            {
+              "type": "radiogroup",
+              "name": "audits",
+              "title": "Do you have external or internal audits of your information security management systems e.g. risk management and policies?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "text",
+              "name": "audit_specifics",
+              "visibleIf": "{audits} = 'yes'",
+              "indent": 1,
+              "title": "Please specify (for example a certified security auditor on ISO 27001 is performing the audits):"
+            },
+            {
+              "type": "radiogroup",
+              "name": "business_continuity_plans",
+              "title": "Do you have Business Continuity plans in place to ensure business continuation and operations?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "text",
+              "name": "business_continuity_plans_specifics",
+              "visibleIf": "{business_continuity_plans} = 'yes'",
+              "indent": 1,
+              "title": "Please specify if you comply with any international standard and if you test the continuity plans regularly."
+            },
+            {
+              "type": "radiogroup",
+              "name": "crisis_management_procedure",
+              "title": "Does your NREN have a formal crisis management procedure?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "checkbox",
+              "name": "crisis_exercises",
+              "title": "Does your NREN run or participate in operational security measures and other exercises including crisis awareness to train and test employees?",
+              "description": "Multiple answers possible, in the last 12 months",
+              "choices": [
+                {
+                  "value": "geant_workshops",
+                  "text": "We participate in GÉANT crisis workshops such as CLAW"
+                },
+                {
+                  "value": "national_excercises",
+                  "text": "We participated in national crisis exercises "
+                },
+                {
+                  "value": "tabletop_exercises",
+                  "text": "We run our own tabletop exercises"
+                },
+                {
+                  "value": "simulation_excercises",
+                  "text": "We run our own simulation exercises"
+                },
+                {
+                  "value": "other_excercises",
+                  "text": " We have done/participated in other exercises or trainings"
+                },
+                {
+                  "value": "real_crisis",
+                  "text": "We had a real crisis"
+                },
+                {
+                  "value": "internal_security_programme",
+                  "text": "We run an internal security awareness programme"
+                },
+                {
+                  "value": "none",
+                  "text": "No, we have not done any crisis exercises or trainings"
+                }
+              ]
+            },
+            {
+              "type": "checkbox",
+              "name": "security_controls",
+              "title": "Do you utilise security controls such as anti-virus, integrity checkers and systemic firewalls to protect your assets?",
+              "choices": [
+                {
+                  "value": "anti_virus",
+                  "text": "Anti Virus"
+                },
+                {
+                  "value": "anti_spam",
+                  "text": "Anti-Spam"
+                },
+                {
+                  "value": "firewall",
+                  "text": "Firewall"
+                },
+                {
+                  "value": "ddos_mitigation",
+                  "text": "DDoS mitigation"
+                },
+                {
+                  "value": "monitoring",
+                  "text": "Network monitoring"
+                },
+                {
+                  "value": "ips_ids",
+                  "text": "IPS/IDS"
+                },
+                {
+                  "value": "acl",
+                  "text": "ACL"
+                },
+                {
+                  "value": "segmentation",
+                  "text": "Network segmentation"
+                },
+                {
+                  "value": "integrity_checking",
+                  "text": "Integrity checking"
+                }
+              ],
+              "showOtherItem": true
+            }
+          ],
+          "title": "Standards"
+        },
+        {
+          "type": "comment",
+          "name": "policy_comments",
+          "title": "Comments regarding this section:"
+        }
+      ],
+      "title": "Standards & Policies"
+    },
+    {
+      "name": "connected_users",
+      "elements": [
+        {
+          "type": "panel",
+          "name": "panel2",
+          "elements": [
+            {
+              "type": "matrixdynamic",
+              "name": "connected_sites_lists",
+              "title": "Please provide the URLs of the webpages listing the institutions or organisations that are connected to the NREN, if available:",
+              "description": "Many NRENs have one or more pages on their website listing user institutions. Please provide these links here if you have them.",
+              "columns": [
+                {
+                  "name": "connected_sites_url",
+                  "title": "Url:",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "expression",
+                      "text": "Please provide a single valid website url including http:// or https://",
+                      "expression": "validateWebsiteUrl({row.connected_sites_url})"
+                    }
+                  ],
+                  "inputType": "url"
+                }
+              ],
+              "rowCount": 1,
+              "maxRowCount": 50
+            },
+            {
+              "type": "matrixdropdown",
+              "name": "connectivity_proportions",
+              "title": "Please give an estimate of the proportion of the institutions in each category for which your NREN provides IP connectivity: ",
+              "description": "For the questions in this section, please use the ISCED 2011 classification system (the UNESCO scheme for International Standard Classification of Education) as follows: * Level 8 - Doctorate or equivalent level * Level 7 - Masters or equivalent level * Level 6 - Bachelors or equivalent level * Level 5 - Short-cycle tertiary education * Level 4 - Post-secondary non-tertiary education. This can include, for example, short vocational training programmes * Levels 2 and 3: Secondary education * Level 1: Primary or basic education * Level 0: Pre-primary education",
+              "columns": [
+                {
+                  "name": "covered",
+                  "title": "Does your remit cover connectivity to this institution type:",
+                  "cellType": "dropdown",
+                  "choices": [
+                    {
+                      "value": "yes_incl_other",
+                      "text": "Yes - including transit to other networks"
+                    },
+                    {
+                      "value": "yes_national_nren",
+                      "text": "Yes - national NREN access"
+                    },
+                    {
+                      "value": "sometimes",
+                      "text": "In some circumstances"
+                    },
+                    {
+                      "value": "no_policy",
+                      "text": "No - not eligible for policy reasons"
+                    },
+                    {
+                      "value": "no_financial",
+                      "text": "No - financial restrictions (NREN is unable to charge/cover costs)"
+                    },
+                    {
+                      "value": "no_other",
+                      "text": "No - other reason"
+                    },
+                    {
+                      "value": "unsure",
+                      "text": "Unsure/unclear"
+                    }
+                  ]
+                },
+                {
+                  "name": "nr_connected",
+                  "title": "Number of institutions connected in this category (actual number):",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    },
+                    {
+                      "type": "regex",
+                      "text": "Please use a whole number",
+                      "regex": "^[0-9]*$"
+                    }
+                  ]
+                },
+                {
+                  "name": "market_share_percentage",
+                  "title": "% market share of institutions connected in this category:",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0,
+                      "maxValue": 100
+                    }
+                  ]
+                },
+                {
+                  "name": "nr_of_users",
+                  "title": "Number of users served in this category (actual number):",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    },
+                    {
+                      "type": "regex",
+                      "text": "Please use a whole number",
+                      "regex": "^[0-9]*$"
+                    }
+                  ]
+                }
+              ],
+              "rows": [
+                {
+                  "value": "universities",
+                  "text": "Universities & Other (ISCED 6-8)"
+                },
+                {
+                  "value": "further_education",
+                  "text": "Further education (ISCED 4-5)"
+                },
+                {
+                  "value": "secondary_schools",
+                  "text": "Secondary schools (ISCED 2-3)"
+                },
+                {
+                  "value": "primary_schools",
+                  "text": "Primary schools (ISCED 1)"
+                },
+                {
+                  "value": "institutes",
+                  "text": "Research Institutes"
+                },
+                {
+                  "value": "cultural",
+                  "text": "Libraries, Museums, Archives, Cultural institutions"
+                },
+                {
+                  "value": "hospitals",
+                  "text": "Non-university public Hospitals"
+                },
+                {
+                  "value": "government",
+                  "text": "Government departments (national, regional, local)"
+                },
+                {
+                  "value": "iros",
+                  "text": "International (virtual) research organisations"
+                },
+                {
+                  "value": "for_profit_orgs",
+                  "text": "For-profit organisations"
+                }
+              ]
+            },
+            {
+              "type": "matrixdropdown",
+              "name": "connectivity_level",
+              "title": "Level of IP connectivity by Institution type:",
+              "description": "This table explores the average level of connectivity for each type of institution to the NREN. Please enter the typical and the highest capacity at which institutions in this category are connected ( in Mbit/s). As a minimum, please provide this information for Universities and Research Institutes.",
+              "columns": [
+                {
+                  "name": "typical_speed",
+                  "title": "Typical link speed (Mbit/s):",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    },
+                    {
+                      "type": "regex",
+                      "text": "Please use a whole number",
+                      "regex": "^[0-9]*$"
+                    }
+                  ]
+                },
+                {
+                  "name": "highest_speed",
+                  "title": "Highest speed link (Mbit/s):",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    },
+                    {
+                      "type": "regex",
+                      "text": "Please use a whole number",
+                      "regex": "^[0-9]*$"
+                    }
+                  ]
+                },
+                {
+                  "name": "highest_speed_connection_percentage",
+                  "title": "Proportionally how many institutions in this category are connected at the highest capacity? (%):",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0,
+                      "maxValue": 100
+                    }
+                  ]
+                }
+              ],
+              "rows": [
+                {
+                  "value": "universities",
+                  "text": "Universities & Other (ISCED 6-8)"
+                },
+                {
+                  "value": "further_education",
+                  "text": "Further education (ISCED 4-5)"
+                },
+                {
+                  "value": "secondary_schools",
+                  "text": "Secondary schools (ISCED 2-3)"
+                },
+                {
+                  "value": "primary_schools",
+                  "text": "Primary schools (ISCED 1)"
+                },
+                {
+                  "value": "institutes",
+                  "text": "Research Institutes"
+                },
+                {
+                  "value": "cultural",
+                  "text": "Libraries, Museums, Archives, Cultural institutions"
+                },
+                {
+                  "value": "hospitals",
+                  "text": "Non-university public Hospitals"
+                },
+                {
+                  "value": "government",
+                  "text": "Government departments (national, regional, local)"
+                },
+                {
+                  "value": "iros",
+                  "text": "International (virtual) research organisations"
+                },
+                {
+                  "value": "for_profit_orgs",
+                  "text": "For-profit organisations"
+                }
+              ]
+            },
+            {
+              "type": "matrixdropdown",
+              "name": "traffic_carriers",
+              "title": "How is the traffic carried?",
+              "columns": [
+                {
+                  "name": "carry_mechanism",
+                  "title": "Carry mechanism",
+                  "cellType": "dropdown",
+                  "choices": [
+                    {
+                      "value": "nren_local_loops",
+                      "text": "NREN provides the local loops"
+                    },
+                    {
+                      "value": "regional_nren_backbone",
+                      "text": "Traffic carried to the backbone by regional NREN"
+                    },
+                    {
+                      "value": "commercial_provider_backbone",
+                      "text": "Traffic carried to the backbone by commercial providers"
+                    },
+                    {
+                      "value": "man",
+                      "text": "MAN"
+                    },
+                    {
+                      "value": "other",
+                      "text": "Other"
+                    }
+                  ]
+                }
+              ],
+              "rows": [
+                {
+                  "value": "universities",
+                  "text": "Universities & Other (ISCED 6-8)"
+                },
+                {
+                  "value": "further_education",
+                  "text": "Further education (ISCED 4-5)"
+                },
+                {
+                  "value": "secondary_schools",
+                  "text": "Secondary schools (ISCED 2-3)"
+                },
+                {
+                  "value": "primary_schools",
+                  "text": "Primary schools (ISCED 1)"
+                },
+                {
+                  "value": "institutes",
+                  "text": "Research Institutes"
+                },
+                {
+                  "value": "cultural",
+                  "text": "Libraries, Museums, Archives, Cultural institutions"
+                },
+                {
+                  "value": "hospitals",
+                  "text": "Non-university public Hospitals"
+                },
+                {
+                  "value": "government",
+                  "text": "Government departments (national, regional, local)"
+                },
+                {
+                  "value": "iros",
+                  "text": "International (virtual) research organisations"
+                },
+                {
+                  "value": "for_profit_orgs",
+                  "text": "For-profit organisations"
+                }
+              ]
+            },
+            {
+              "type": "matrixdropdown",
+              "name": "traffic_load",
+              "title": "What are the traffic loads in Mbit/s?",
+              "columns": [
+                {
+                  "name": "average_from_institutions_to_network",
+                  "title": "Average load from institutions to the network",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    },
+                    {
+                      "type": "regex",
+                      "text": "Please round to a whole number",
+                      "regex": "^[0-9]*$"
+                    }
+                  ]
+                },
+                {
+                  "name": "average_to_institutions_from_network",
+                  "title": "Average load to institutions from the network",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    },
+                    {
+                      "type": "regex",
+                      "text": "Please round to a whole number",
+                      "regex": "^[0-9]*$"
+                    }
+                  ]
+                },
+                {
+                  "name": "peak_from_institutions_to_network",
+                  "title": "Peak load from institutions to the network",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    },
+                    {
+                      "type": "regex",
+                      "text": "Please round to a whole number",
+                      "regex": "^[0-9]*$"
+                    }
+                  ]
+                },
+                {
+                  "name": "peak_to_institutions_from_network",
+                  "title": "Peak load to institutions from the network",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    },
+                    {
+                      "type": "regex",
+                      "text": "Please round to a whole number",
+                      "regex": "^[0-9]*$"
+                    }
+                  ]
+                }
+              ],
+              "rows": [
+                {
+                  "value": "universities",
+                  "text": "Universities & Other (ISCED 6-8)"
+                },
+                {
+                  "value": "further_education",
+                  "text": "Further education (ISCED 4-5)"
+                },
+                {
+                  "value": "secondary_schools",
+                  "text": "Secondary schools (ISCED 2-3)"
+                },
+                {
+                  "value": "primary_schools",
+                  "text": "Primary schools (ISCED 1)"
+                },
+                {
+                  "value": "institutes",
+                  "text": "Research Institutes"
+                },
+                {
+                  "value": "cultural",
+                  "text": "Libraries, Museums, Archives, Cultural institutions"
+                },
+                {
+                  "value": "hospitals",
+                  "text": "Non-university public Hospitals"
+                },
+                {
+                  "value": "government",
+                  "text": "Government departments (national, regional, local)"
+                },
+                {
+                  "value": "iros",
+                  "text": "International (virtual) research organisations"
+                },
+                {
+                  "value": "for_profit_orgs",
+                  "text": "For-profit organisations"
+                }
+              ]
+            },
+            {
+              "type": "matrixdropdown",
+              "name": "traffic_growth",
+              "title": "What do you expect the traffic growth to be in the next 3 years?",
+              "columns": [
+                {
+                  "name": "growth_rate",
+                  "title": "% growth",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric"
+                    }
+                  ]
+                }
+              ],
+              "rows": [
+                {
+                  "value": "universities",
+                  "text": "Universities & Other (ISCED 6-8)"
+                },
+                {
+                  "value": "further_education",
+                  "text": "Further education (ISCED 4-5)"
+                },
+                {
+                  "value": "secondary_schools",
+                  "text": "Secondary schools (ISCED 2-3)"
+                },
+                {
+                  "value": "primary_schools",
+                  "text": "Primary schools (ISCED 1)"
+                },
+                {
+                  "value": "institutes",
+                  "text": "Research Institutes"
+                },
+                {
+                  "value": "cultural",
+                  "text": "Libraries, Museums, Archives, Cultural institutions"
+                },
+                {
+                  "value": "hospitals",
+                  "text": "Non-university public Hospitals"
+                },
+                {
+                  "value": "government",
+                  "text": "Government departments (national, regional, local)"
+                },
+                {
+                  "value": "iros",
+                  "text": "International (virtual) research organisations"
+                },
+                {
+                  "value": "for_profit_orgs",
+                  "text": "For-profit organisations"
+                }
+              ]
+            }
+          ],
+          "title": "CONNECTED USERS"
+        },
+        {
+          "type": "panel",
+          "name": "connected_users_commercial",
+          "elements": [
+            {
+              "type": "matrixdropdown",
+              "name": "commercial_organizations",
+              "title": "What types of commercial organisations do you connect?",
+              "columns": [
+                {
+                  "name": "connection",
+                  "title": "Connection",
+                  "cellType": "dropdown",
+                  "choices": [
+                    {
+                      "value": "yes_incl_other",
+                      "text": "Yes - Including transit to other networks"
+                    },
+                    {
+                      "value": "yes_national_nren",
+                      "text": "Yes - National NREN access only"
+                    },
+                    {
+                      "value": "yes_if_sponsored",
+                      "text": "Yes - only if sponsored by a connected institution"
+                    },
+                    {
+                      "value": "no_but_direct_peering",
+                      "text": "No - but we offer a direct or IX peering"
+                    },
+                    {
+                      "value": "no_policy",
+                      "text": "No - not eligible for policy reasons"
+                    },
+                    {
+                      "value": "no_financial",
+                      "text": "No - financial restrictions (NREN is unable to charge/recover costs)"
+                    },
+                    {
+                      "value": "no_other",
+                      "text": "No - other reason / unsure"
+                    }
+                  ]
+                }
+              ],
+              "rows": [
+                {
+                  "value": "commercial_r_e",
+                  "text": "Commercial R&E traffic only"
+                },
+                {
+                  "value": "commercial_general",
+                  "text": "Commercial general"
+                },
+                {
+                  "value": "commercial_collaboration",
+                  "text": "Commercial for collaboration only (project/time limited)"
+                },
+                {
+                  "value": "commercial_service_provider",
+                  "text": "Commercial Service Provider"
+                },
+                {
+                  "value": "university_spin_off",
+                  "text": "University Spin Off/Incubator"
+                }
+              ]
+            },
+            {
+              "type": "matrixdropdown",
+              "name": "commercial_charging_levels",
+              "title": "What are the typical charging levels for the following types of commercial connections? Please tick all that apply:",
+              "columns": [
+                {
+                  "name": "charging_level",
+                  "title": "Charging level",
+                  "cellType": "dropdown",
+                  "choices": [
+                    {
+                      "value": "higher_than_r_e_charges",
+                      "text": "Charges typically higher than for R&E users"
+                    },
+                    {
+                      "value": "same_as_r_e_charges",
+                      "text": "Same charging model as for R&E users"
+                    },
+                    {
+                      "value": "no_charges_if_r_e_requested",
+                      "text": "No charges applied if requested by R&E users"
+                    },
+                    {
+                      "value": "lower_than_r_e_charges",
+                      "text": "Charges typically lower than for R&E users"
+                    }
+                  ]
+                }
+              ],
+              "rows": [
+                {
+                  "value": "collaboration",
+                  "text": "Connection to your network for collaboration with R&E users"
+                },
+                {
+                  "value": "services",
+                  "text": "Connection to your network for supplying services for R&E"
+                },
+                {
+                  "value": "peering",
+                  "text": "Direct peering (e.g. direct peering or cloud peering)"
+                }
+              ]
+            },
+            {
+              "type": "radiogroup",
+              "name": "remote_campuses",
+              "title": "Do you provide connectivity to any remote campuses in other countries?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "matrixdynamic",
+              "name": "remote_campuses_specifics",
+              "visibleIf": "{remote_campuses} = 'Yes'",
+              "indent": 1,
+              "title": "Please specify:",
+              "columns": [
+                {
+                  "name": "country",
+                  "title": "Which Country",
+                  "cellType": "text",
+                  "isRequired": true
+                },
+                {
+                  "name": "connected",
+                  "title": "Connected to local R&E network",
+                  "cellType": "radiogroup",
+                  "choices": [
+                    "Yes",
+                    "No"
+                  ]
+                }
+              ],
+              "rowCount": 0,
+              "maxRowCount": 50
+            },
+            {
+              "type": "comment",
+              "name": "connected_users_comments",
+              "title": "Comments regarding this section:"
+            }
+          ],
+          "title": "CONNECTED USERS - COMMERCIAL"
+        }
+      ],
+      "title": "Connected Users"
+    },
+    {
+      "name": "network",
+      "elements": [
+        {
+          "type": "panel",
+          "name": "connectivity",
+          "elements": [
+            {
+              "type": "radiogroup",
+              "name": "dark_fibre_lease",
+              "title": "Does your NREN have an IRU or lease of dark fibre?",
+              "description": "An Indefeasible Right of Use (IRU) is essentially a long-term lease of a portion of the capacity of a cable. Please do not include fibre that you have installed and own yourself. This is covered in a later question.",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "text",
+              "name": "dark_fibre_lease_kilometers_inside_country",
+              "visibleIf": "{dark_fibre_lease} = 'Yes'",
+              "indent": 1,
+              "title": "Please state the number of kilometres of such fibre in your country:",
+              "description": "Please include only the fibre inside your country for this answer. The distance is the number of kilometers of your fibre pairs or if you are using bidirectional traffic on a single fibre please treat this as a fibre pair.",
+              "validators": [
+                {
+                  "type": "numeric",
+                  "minValue": 0
+                },
+                {
+                  "type": "regex",
+                  "text": "Please round to a whole number",
+                  "regex": "^[0-9]*$"
+                }
+              ]
+            },
+            {
+              "type": "text",
+              "name": "dark_fibre_lease_kilometers_outside_country",
+              "visibleIf": "{dark_fibre_lease} = 'Yes'",
+              "indent": 1,
+              "title": "Please state the number of kilometers of such fibre that is outside your country:",
+              "validators": [
+                {
+                  "type": "numeric",
+                  "minValue": 0
+                },
+                {
+                  "type": "regex",
+                  "text": "Please round to a whole number",
+                  "regex": "^[0-9]*$"
+                }
+              ]
+            },
+            {
+              "type": "text",
+              "name": "dark_fibre_lease_duration",
+              "visibleIf": "{dark_fibre_lease} = 'Yes'",
+              "indent": 1,
+              "title": "What is the average duration, in years, of your IRU?",
+              "validators": [
+                {
+                  "type": "numeric",
+                  "minValue": 0
+                }
+              ]
+            },
+            {
+              "type": "radiogroup",
+              "name": "dark_fibre_nren",
+              "title": "Has your NREN physically laid any dark fibre cables in your network?",
+              "description": "Please include only cables that you laid yourself. If this cable was installed and is owned by a third party, this is included in a previous question.",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "text",
+              "name": "dark_fibre_nren_kilometers_inside_country",
+              "visibleIf": "{dark_fibre_nren} = 'Yes'",
+              "indent": 1,
+              "title": "Please state the number of kilometers of such fibre in your network:",
+              "description": "Please include only the fibre inside your country for this answer. The distance is the number of kilometers of your fibre pairs, or If you are using bi-directional traffic on a single fibre please treat this as a fibre pair.",
+              "validators": [
+                {
+                  "type": "numeric",
+                  "minValue": 0
+                },
+                {
+                  "type": "regex",
+                  "text": "Please round to a whole number",
+                  "regex": "^[0-9]*$"
+                }
+              ]
+            },
+            {
+              "type": "radiogroup",
+              "name": "fibre_light",
+              "title": "How do you light your fibre network:",
+              "choices": [
+                {
+                  "value": "nren_owns_and_operates",
+                  "text": "NREN owns and operates equipment"
+                },
+                {
+                  "value": "nren_owns_outsourced_operation",
+                  "text": "NREN owns equipment and operation is outsourced"
+                },
+                {
+                  "value": "outsourced_ownership_and_operation",
+                  "text": "Ownership and management are out-sourced (turn-key model)"
+                }
+              ],
+              "showOtherItem": true,
+              "showClearButton": true
+            },
+            {
+              "type": "matrixdynamic",
+              "name": "network_map_urls",
+              "title": "Please provide a network map for layers 1, 2 and 3 of your network:",
+              "columns": [
+                {
+                  "name": "network_map_url",
+                  "title": "Url:",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "expression",
+                      "text": "Please provide a single valid website url including http:// or https://",
+                      "expression": "validateWebsiteUrl({row.network_map_url})"
+                    }
+                  ],
+                  "inputType": "url"
+                }
+              ],
+              "rowCount": 1,
+              "maxRowCount": 50
+            }
+          ],
+          "title": "Connectivity"
+        },
+        {
+          "type": "panel",
+          "name": "performance",
+          "elements": [
+            {
+              "type": "checkbox",
+              "name": "monitoring_tools",
+              "title": "Which tools do you offer your client institutions for monitoring or troubleshooting the network?",
+              "choices": [
+                {
+                  "value": "looking_glass",
+                  "text": "Looking Glass"
+                },
+                {
+                  "value": "status_dashboard",
+                  "text": "Network or Services Status Dashboard"
+                },
+                {
+                  "value": "historical_traffic_volumes",
+                  "text": "Historical traffic volume information"
+                },
+                {
+                  "value": "netflow_analysis",
+                  "text": "Netflow analysis tool"
+                }
+              ],
+              "showOtherItem": true
+            },
+            {
+              "type": "text",
+              "name": "netflow_vendors",
+              "title": "If you process NetFlow, please, indicate the system name and vendor:"
+            },
+            {
+              "type": "radiogroup",
+              "name": "passive_monitoring",
+              "title": "Do you passively monitor international traffic?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "radiogroup",
+              "name": "passive_monitoring_tech",
+              "visibleIf": "{passive_monitoring} = 'Yes'",
+              "indent": 1,
+              "title": "Do you use:",
+              "choices": [
+                {
+                  "value": "span_ports",
+                  "text": "SPAN ports"
+                },
+                {
+                  "value": "taps",
+                  "text": "Passive optical TAPS"
+                },
+                {
+                  "value": "both",
+                  "text": "Both"
+                }
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "radiogroup",
+              "name": "traffic_statistics",
+              "title": "Do you have traffic statistics on your website?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "matrixdynamic",
+              "name": "traffic_statistics_urls",
+              "visibleIf": "{traffic_statistics} = 'Yes'",
+              "indent": 1,
+              "title": "Please give the URL(s):",
+              "columns": [
+                {
+                  "name": "traffic_statistics_url",
+                  "title": "Url:",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "expression",
+                      "text": "Please provide a single valid website url including http:// or https://",
+                      "expression": "validateWebsiteUrl({row.traffic_statistics_url})"
+                    }
+                  ],
+                  "inputType": "url"
+                }
+              ],
+              "rowCount": 1,
+              "maxRowCount": 50
+            },
+            {
+              "type": "checkbox",
+              "name": "siem_soc_vendor",
+              "title": "If you use a SIEM/SOC system, please indicate the name of the vendor you use here:",
+              "choices": [
+                "Splunk",
+                "IBM Qradar",
+                "Exabeam",
+                "LogRythm",
+                "Securonix"
+              ],
+              "showOtherItem": true
+            },
+            {
+              "type": "checkbox",
+              "name": "certificate_service",
+              "title": "Which certificate service do you use?",
+              "choices": [
+                "TCS",
+                "Digicert",
+                {
+                  "value": "Sectigo",
+                  "text": "Sectigo (outside of TCS)"
+                },
+                "Let's Encrypt",
+                "Entrust Datacard"
+              ],
+              "showOtherItem": true
+            },
+            {
+              "type": "radiogroup",
+              "name": "network_weather",
+              "title": "Do you have an online weather map of your network?",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "text",
+              "name": "network_weather_url",
+              "visibleIf": "{network_weather} = 'Yes'",
+              "indent": 1,
+              "title": "Please give the URL:",
+              "validators": [
+                {
+                  "type": "expression",
+                  "text": "Please provide a single valid website url including http:// or https://",
+                  "expression": "validateWebsiteUrl({network_weather_url})"
+                }
+              ],
+              "inputType": "url"
+            },
+            {
+              "type": "radiogroup",
+              "name": "pert_team",
+              "title": "Do you run a PERT team?",
+              "choices": [
+                "Yes",
+                "No",
+                "Planned"
+              ],
+              "showClearButton": true
+            }
+          ],
+          "title": "PERFORMANCE MONITORING AND MANAGEMENT"
+        },
+        {
+          "type": "panel",
+          "name": "alienwave",
+          "elements": [
+            {
+              "type": "radiogroup",
+              "name": "alienwave_services",
+              "title": "Does your NREN make use of alien wavelength/lightpath services provided by third parties?",
+              "description": "This does not include alien waves used internally inside your network e.g. coloured optics on routers as they are covered in a later question.",
+              "choices": [
+                "Yes",
+                "No",
+                "Planned"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "text",
+              "name": "alienwave_services_number",
+              "visibleIf": "{alienwave_services} = 'yes'",
+              "indent": 1,
+              "title": "Please state the number of individual alien wavelength services:",
+              "validators": [
+                {
+                  "type": "numeric",
+                  "minValue": 0
+                },
+                {
+                  "type": "regex",
+                  "text": "Please round to a whole number",
+                  "regex": "^[0-9]*$"
+                }
+              ]
+            },
+            {
+              "type": "radiogroup",
+              "name": "alienwave_internal",
+              "title": "Are you using alien waves internally in your network?",
+              "description": "This includes for example alien waves used between two equipment vendors e.g. coloured optics on routers carried over DWDM equipment.",
+              "choices": [
+                "Yes",
+                "No"
+              ],
+              "showClearButton": true
+            }
+          ],
+          "title": "Alienwave",
+          "description": "Pure optical connectivity services provided by the NREN without a known framing, such as foreign, alien wavelength."
+        },
+        {
+          "type": "panel",
+          "name": "capacity",
+          "elements": [
+            {
+              "type": "text",
+              "name": "max_capacity",
+              "title": "What is the capacity (in Gbit/s) of the largest link in your network used for internet traffic (either shared or dedicated)?",
+              "description": "Please provide the sum of aggregated links, but don't include backup capacity.",
+              "validators": [
+                {
+                  "type": "numeric",
+                  "minValue": 0
+                }
+              ]
+            },
+            {
+              "type": "text",
+              "name": "typical_capacity",
+              "title": "What is the current typical core usable backbone IP capacity of your network in Gbit/s?",
+              "description": "Note this refers to circuit capacity not traffic e.g. 2 x 10GE LAG aggregated links.",
+              "validators": [
+                {
+                  "type": "numeric",
+                  "minValue": 0
+                }
+              ]
+            },
+            {
+              "type": "matrixdynamic",
+              "name": "external_connections",
+              "title": "Please provide a list of the operational external IP connections (usable links excluding backup links): ",
+              "description": "This should include links to your regional backbone (GÉANT, APAN, RedCLARA etc, to other research locations, to the commercial Internet, peerings to Internet exchanges, cross-border dark fibre links and any other links you may have. Note that we are interested in the capacity for production purposes, not in any additional links that may be there for the purpose of giving resilience. Some of your capacity to your regional backbone may be used for transiting to intercontinental services; please include these too. Cross-border fibre links means those links that have been commissioned or established by the NREN from a point on the network that is near the border to another point near the border on the network of a neighbouring NREN, for example.",
+              "columns": [
+                {
+                  "name": "link_name",
+                  "title": "Link name",
+                  "cellType": "text"
+                },
+                {
+                  "name": "capacity",
+                  "title": "Capacity (Gbit/s):",
+                  "cellType": "text",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    }
+                  ]
+                },
+                {
+                  "name": "from_organization",
+                  "title": "The link is from (organisation):",
+                  "cellType": "text"
+                },
+                {
+                  "name": "to_organization",
+                  "title": "To (organisation):",
+                  "cellType": "text"
+                },
+                {
+                  "name": "interconnection_method",
+                  "title": "Interconnection method:",
+                  "cellType": "dropdown",
+                  "choices": [
+                    {
+                      "value": "internet_exchange",
+                      "text": "Internet exchange points"
+                    },
+                    {
+                      "value": "open_exchange",
+                      "text": "Open Exchange points"
+                    },
+                    {
+                      "value": "direct",
+                      "text": "Directly connected R&E peers"
+                    },
+                    {
+                      "value": "geant",
+                      "text": "GEANT"
+                    },
+                    {
+                      "value": "other",
+                      "text": "Other"
+                    }
+                  ]
+                }
+              ],
+              "rowCount": 1,
+              "maxRowCount": 50
+            },
+            {
+              "type": "text",
+              "name": "non_r_and_e_peers",
+              "title": "Please state how many non-R&E networks you are peering with:",
+              "description": "This should include all direct IP-peerings to commercial networks e.g. Google.",
+              "validators": [
+                {
+                  "type": "numeric",
+                  "minValue": 0
+                },
+                {
+                  "type": "regex",
+                  "text": "Please round to a whole number",
+                  "regex": "^[0-9]*$"
+                }
+              ]
+            },
+            {
+              "type": "multipletext",
+              "name": "traffic_estimate",
+              "title": "Please supply an estimate of the total amount of traffic in Terabytes in the last 12 months for the following:",
+              "items": [
+                {
+                  "name": "from_customers",
+                  "title": "Traffic from NREN customers (sources that are part of the remit of the NREN's domain)",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    }
+                  ]
+                },
+                {
+                  "name": "to_customers",
+                  "title": "Traffic to NREN customers (sources that are part of the remit of the NREN's domain)",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    }
+                  ]
+                },
+                {
+                  "name": "from_external",
+                  "title": "Traffic from external networks (sources that are outside the NREN's domain, such as, GÉANT, General/Commercial Internet, Internet exchanges, peerings, other NRENs etc)",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    }
+                  ]
+                },
+                {
+                  "name": "to_external",
+                  "title": "Traffic to external networks (sources that are outside the NREN's domain, such as, GÉANT, General/Commercial Internet, Internet exchanges, peerings, other NRENs etc)",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0
+                    }
+                  ]
+                }
+              ]
+            },
+            {
+              "type": "multipletext",
+              "name": "commodity_vs_r_e",
+              "title": "What is the ratio of commodity vs R&E traffic in your network?",
+              "validators": [
+                {
+                  "type": "expression",
+                  "text": "The percentages should add up to 100",
+                  "expression": "(sum({commodity_vs_r_e.r_e}, {commodity_vs_r_e.commodity}) = 100) or ({commodity_vs_r_e} empty)"
+                }
+              ],
+              "items": [
+                {
+                  "name": "r_e",
+                  "title": "R&E percentage",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0,
+                      "maxValue": 100
+                    }
+                  ]
+                },
+                {
+                  "name": "commodity",
+                  "title": "Commodity percentage",
+                  "validators": [
+                    {
+                      "type": "numeric",
+                      "minValue": 0,
+                      "maxValue": 100
+                    }
+                  ]
+                }
+              ]
+            }
+          ],
+          "title": "CAPACITY"
+        },
+        {
+          "type": "panel",
+          "name": "sdn",
+          "elements": [
+            {
+              "type": "radiogroup",
+              "name": "operational_process_automation",
+              "title": "Are you automating your operational processes?",
+              "choices": [
+                "Yes",
+                "No",
+                "Planned"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "text",
+              "name": "operational_process_automation_tools",
+              "visibleIf": "{operational_process_automation} = 'yes'",
+              "indent": 1,
+              "title": "Please specify which processes and the name/s of the automation software and tools you use for it:"
+            },
+            {
+              "type": "radiogroup",
+              "name": "nfv",
+              "title": "Do you use any kind of NFV?",
+              "choices": [
+                "Yes",
+                "No",
+                "Planned"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "checkbox",
+              "name": "nfv_types",
+              "visibleIf": "{nfv} = 'yes' or {nfv} = 'planned'",
+              "indent": 1,
+              "title": "What kind of NFV do/will you use:",
+              "choices": [
+                {
+                  "value": "routers",
+                  "text": "Routers/switches"
+                },
+                {
+                  "value": "firewalls",
+                  "text": "Firewalls"
+                },
+                {
+                  "value": "load_balancers",
+                  "text": "Load balancers"
+                },
+                {
+                  "value": "vpn_concentrators",
+                  "text": "VPN Concentrator Services"
+                }
+              ],
+              "showOtherItem": true
+            },
+            {
+              "type": "radiogroup",
+              "name": "network_automation",
+              "title": "Do you use automation on your network?",
+              "choices": [
+                "Yes",
+                "No",
+                "Planned"
+              ],
+              "showClearButton": true
+            },
+            {
+              "type": "checkbox",
+              "name": "network_automation_tasks",
+              "visibleIf": "{network_automation} = 'yes' or {network_automation} = 'planned'",
+              "indent": 1,
+              "title": "What kind of task do you use it for?",
+              "choices": [
+                {
+                  "value": "provisioning",
+                  "text": "Device Provisioning"
+                },
+                {
+                  "value": "data_collection",
+                  "text": "Data Collection"
+                },
+                {
+                  "value": "config_management",
+                  "text": "Configuration Management"
+                },
+                {
+                  "value": "compliance",
+                  "text": "Compliance"
+                },
+                {
+                  "value": "reporting",
+                  "text": "Reporting"
+                },
+                {
+                  "value": "troubleshooting",
+                  "text": "Troubleshooting"
+                }
+              ]
+            }
+          ],
+          "title": "SOFTWARE-DEFINED NETWORKING (SDN) AND NETWORK FUNCTION VIRTUALISATION (NFV)"
+        },
+        {
+          "type": "comment",
+          "name": "network_comments",
+          "title": "Comments regarding this section:"
+        }
+      ],
+      "title": "Network"
+    },
+    {
+      "name": "services",
+      "elements": [
+        {
+          "type": "html",
+          "name": "services_hover_explanation",
+          "html": "Descriptions of the individual services are shown if you hover over the service name."
+        },
+        {
+          "type": "matrixdropdown",
+          "name": "services_network",
+          "state": "collapsed",
+          "title": "Network services",
+          "hideCheckboxLabels": true,
+          "columns": [
+            {
+              "name": "offered",
+              "title": "Service offered",
+              "cellType": "checkbox",
+              "choices": [
+                {
+                  "value": "yes",
+                  "text": "Yes"
+                }
+              ]
+            },
+            {
+              "name": "name",
+              "title": "Service name",
+              "cellType": "text",
+              "width": "20%",
+              "visibleIf": "{row.offered} = ['yes']"
+            },
+            {
+              "name": "description",
+              "title": "Official description",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2
+            },
+            {
+              "name": "additional_information",
+              "title": "Additional information",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2,
+              "placeholder": "(e.g. software used, third party service, links, etc.)"
+            }
+          ],
+          "cellType": "checkbox",
+          "rows": [
+            {
+              "value": "connectivity",
+              "text": "IP Connectivity",
+              "customDescription": "Basic IP connectivity services inc R&E and commodity internet"
+            },
+            {
+              "value": "home-vpn",
+              "text": "Remote access VPN server",
+              "customDescription": "Remote access VPN for end users (e.g. eduVPN) or site-to-site VPN with the possibility of encryption"
+            },
+            {
+              "value": "ipv6",
+              "text": "IPv6",
+              "customDescription": "The new version of the internet protocol (IP) that will eventually replace IPv4"
+            },
+            {
+              "value": "dedicated-optical-connections",
+              "text": "Dedicated optical connections",
+              "customDescription": "Provision of dedicated optical connections to users, e.g. Layer 1 optical channels or Open Lightpath exchanges."
+            },
+            {
+              "value": "managed-router",
+              "text": "Managed router service",
+              "customDescription": "Remote router support for institutions"
+            },
+            {
+              "value": "multicast",
+              "text": "Multicast",
+              "customDescription": "Extension to the IP protocol which allows individual packets to be sent to multiple hosts on the internet"
+            },
+            {
+              "value": "netflow",
+              "text": "Netflow tool",
+              "customDescription": "Network protocol of collecting IP traffic and monitoring network traffic"
+            },
+            {
+              "value": "user-monitoring",
+              "text": "Network troubleshooting",
+              "customDescription": "Providing a support service to identify, diagnose and resolve problems and issues within a computer network, using network monitoring system tools like Looking Glass."
+            },
+            {
+              "value": "network-monitoring",
+              "text": "Network monitoring",
+              "customDescription": "Network Monitoring systems is software/hardware tools that can track various aspects of a network and its operation. These systems can detect devices and other network elements that comprise or touch the network, as well as provide status updates."
+            },
+            {
+              "value": "pert",
+              "text": "PERT",
+              "customDescription": "Team supporting resolution of end-to-end performance problems for networked applications"
+            },
+            {
+              "value": "point-to-point-circuit-vpn",
+              "text": "Virtual circuits/network VPNs",
+              "customDescription": "Virtual point to point circuits or VPNs to create virtual networks between geographically distinct sites"
+            },
+            {
+              "value": "quality-of-service",
+              "text": "Quality of Service",
+              "customDescription": "Preferential service to specific applications or classes of applications"
+            }
+          ],
+          "rowTitleWidth": "20%"
+        },
+        {
+          "type": "matrixdropdown",
+          "name": "services_isp",
+          "state": "collapsed",
+          "title": "ISP services",
+          "hideCheckboxLabels": true,
+          "columns": [
+            {
+              "name": "offered",
+              "title": "Service offered",
+              "cellType": "checkbox",
+              "choices": [
+                {
+                  "value": "yes",
+                  "text": "Yes"
+                }
+              ]
+            },
+            {
+              "name": "name",
+              "title": "Service name",
+              "cellType": "text",
+              "width": "20%",
+              "visibleIf": "{row.offered} = ['yes']"
+            },
+            {
+              "name": "description",
+              "title": "Official description",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2
+            },
+            {
+              "name": "additional_information",
+              "title": "Additional information",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2,
+              "placeholder": "(e.g. software used, third party service, links, etc.)"
+            }
+          ],
+          "cellType": "checkbox",
+          "rows": [
+            {
+              "value": "domain-registration",
+              "text": "Domain name registration",
+              "customDescription": "Adminstration/registration of top and second level domain names"
+            },
+            {
+              "value": "ip-address-allocation",
+              "text": "IP address allocation",
+              "customDescription": "Allocating addresses for users according to the RIPE policies"
+            },
+            {
+              "value": "ix-operation",
+              "text": "National IX operation",
+              "customDescription": "Operating an IX with national importance"
+            },
+            {
+              "value": "nameserver",
+              "text": "Nameserver services",
+              "customDescription": "Operation of nameservers and maintenance of DNS information on behalf of users"
+            },
+            {
+              "value": "timeserver-ntp",
+              "text": "NTP service",
+              "customDescription": "Allows the synchronization of computer clocks over the internet"
+            }
+          ],
+          "rowTitleWidth": "20%"
+        },
+        {
+          "type": "matrixdropdown",
+          "name": "services_security",
+          "state": "collapsed",
+          "title": "Security services",
+          "hideCheckboxLabels": true,
+          "columns": [
+            {
+              "name": "offered",
+              "title": "Service offered",
+              "cellType": "checkbox",
+              "choices": [
+                {
+                  "value": "yes",
+                  "text": "Yes"
+                }
+              ]
+            },
+            {
+              "name": "name",
+              "title": "Service name",
+              "cellType": "text",
+              "width": "20%",
+              "visibleIf": "{row.offered} = ['yes']"
+            },
+            {
+              "name": "description",
+              "title": "Official description",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2
+            },
+            {
+              "name": "additional_information",
+              "title": "Additional information",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2,
+              "placeholder": "(e.g. software used, third party service, links, etc.)"
+            }
+          ],
+          "cellType": "checkbox",
+          "rows": [
+            {
+              "value": "anti-spam",
+              "text": "Anti-spam solution",
+              "customDescription": "Anti-Spam solutions for detecting and eliminating viruses and spam mails"
+            },
+            {
+              "value": "csirt",
+              "text": "CERT/CSIRT",
+              "customDescription": "A single point of contact for users to deal with computer security incidents and prevention"
+            },
+            {
+              "value": "ddos-prevention",
+              "text": "DDoS mitigation",
+              "customDescription": "Tools and techniques for mitigating Distributed Denial of Service attacks"
+            },
+            {
+              "value": "external-security-awareness",
+              "text": "External security awareness programme",
+              "customDescription": "An external security awareness programme is a service offering to users  (consisting for example in training, workshops, toolkits, campaigns, awareness games, …) aimed at helping member institutions to effectively manage human risk"
+            },
+            {
+              "value": "firewall-on-demand",
+              "text": "Firewall-on-Demand",
+              "customDescription": "Provision of a dynamic firewall services to mitigate against DDoS attacks"
+            },
+            {
+              "value": "intrusion",
+              "text": "Intrusion detection",
+              "customDescription": "System for detecting and preventing Intrusions (IDS/IPS)"
+            },
+            {
+              "value": "pgp-key",
+              "text": "PGP key server",
+              "customDescription": "Operation of PGP key server"
+            },
+            {
+              "value": "security-audit",
+              "text": "Security auditing",
+              "customDescription": "Carrying out vulnerability assessments and security reviews of user systems and resources on their behalf"
+            },
+            {
+              "value": "vulnerability-testing",
+              "text": "Vulnerability scanning",
+              "customDescription": "Vulnerability service that allows users to scan their own IP networks fo security holes"
+            },
+            {
+              "value": "web-filtering",
+              "text": "Web filtering",
+              "customDescription": "Centralised web content filtering service for protection against access to inappropriate content"
+            }
+          ],
+          "rowTitleWidth": "20%"
+        },
+        {
+          "type": "matrixdropdown",
+          "name": "services_identity",
+          "state": "collapsed",
+          "title": "Identity services",
+          "hideCheckboxLabels": true,
+          "columns": [
+            {
+              "name": "offered",
+              "title": "Service offered",
+              "cellType": "checkbox",
+              "choices": [
+                {
+                  "value": "yes",
+                  "text": "Yes"
+                }
+              ]
+            },
+            {
+              "name": "name",
+              "title": "Service name",
+              "cellType": "text",
+              "width": "20%",
+              "visibleIf": "{row.offered} = ['yes']"
+            },
+            {
+              "name": "description",
+              "title": "Official description",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2
+            },
+            {
+              "name": "additional_information",
+              "title": "Additional information",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2,
+              "placeholder": "(e.g. software used, third party service, links, etc.)"
+            }
+          ],
+          "cellType": "checkbox",
+          "rows": [
+            {
+              "value": "aai",
+              "text": "Hosted campus AAI",
+              "customDescription": "Hosting of an Identity Provider service on behalf of connected Institutions to authenticate users"
+            },
+            {
+              "value": "eduroam-wifi",
+              "text": "eduroam",
+              "customDescription": "Inter-WLAN service to facilitate easy and secure Internet access for roaming educationals users"
+            },
+            {
+              "value": "interfederation",
+              "text": "Interfederation",
+              "customDescription": "Participation in an interfederation (i.e. eduGAIN, KALMAR)"
+            }
+          ],
+          "rowTitleWidth": "20%"
+        },
+        {
+          "type": "matrixdropdown",
+          "name": "services_hosting",
+          "state": "collapsed",
+          "title": "Storage & hosting services",
+          "hideCheckboxLabels": true,
+          "columns": [
+            {
+              "name": "offered",
+              "title": "Service offered",
+              "cellType": "checkbox",
+              "choices": [
+                {
+                  "value": "yes",
+                  "text": "Yes"
+                }
+              ]
+            },
+            {
+              "name": "name",
+              "title": "Service name",
+              "cellType": "text",
+              "width": "20%",
+              "visibleIf": "{row.offered} = ['yes']"
+            },
+            {
+              "name": "description",
+              "title": "Official description",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2
+            },
+            {
+              "name": "additional_information",
+              "title": "Additional information",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2,
+              "placeholder": "(e.g. software used, third party service, links, etc.)"
+            }
+          ],
+          "cellType": "checkbox",
+          "rows": [
+            {
+              "value": "cloud-service-end-user",
+              "text": "Cloud storage (end user)",
+              "customDescription": "Browser-based virtual storage service for individuals"
+            },
+            {
+              "value": "content-delivery-hosting",
+              "text": "Content delivery hosting",
+              "customDescription": "Hosting of contenct delivery servers, e.g. Akamai"
+            },
+            {
+              "value": "disaster-recovery",
+              "text": "Disaster recovery",
+              "customDescription": "Off site backup services"
+            },
+            {
+              "value": "dns-server",
+              "text": "DNS hosting",
+              "customDescription": "Hosting of primary and secondary DNS servers"
+            },
+            {
+              "value": "email-services",
+              "text": "Email server hosting",
+              "customDescription": "NREN hosted email servers."
+            },
+            {
+              "value": "filesender",
+              "text": "FileSender",
+              "customDescription": "Web-based application that allows authenticated userds to securely and easily send arbitrarily large files"
+            },
+            {
+              "value": "saas",
+              "text": "SaaS",
+              "customDescription": "Software as a service e.g. Google Apps for Education"
+            },
+            {
+              "value": "storage-co-location",
+              "text": "Housing/co-location",
+              "customDescription": "Hosting of user equipment in a managed data centre"
+            },
+            {
+              "value": "virtual-machines-iaas",
+              "text": "Virtual machines/IaaS",
+              "customDescription": "Access to virtual computing resources"
+            }
+          ],
+          "rowTitleWidth": "20%"
+        },
+        {
+          "type": "matrixdropdown",
+          "name": "services_multimedia",
+          "state": "collapsed",
+          "title": "Multimedia services",
+          "hideCheckboxLabels": true,
+          "columns": [
+            {
+              "name": "offered",
+              "title": "Service offered",
+              "cellType": "checkbox",
+              "choices": [
+                {
+                  "value": "yes",
+                  "text": "Yes"
+                }
+              ]
+            },
+            {
+              "name": "name",
+              "title": "Service name",
+              "cellType": "text",
+              "width": "20%",
+              "visibleIf": "{row.offered} = ['yes']"
+            },
+            {
+              "name": "description",
+              "title": "Official description",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2
+            },
+            {
+              "name": "additional_information",
+              "title": "Additional information",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2,
+              "placeholder": "(e.g. software used, third party service, links, etc.)"
+            }
+          ],
+          "cellType": "checkbox",
+          "rows": [
+            {
+              "value": "internet-radio-tv",
+              "text": "TV/radio streaming",
+              "customDescription": "Internet and radio streaming services"
+            },
+            {
+              "value": "videoconferencing",
+              "text": "Event recording/streaming",
+              "customDescription": "Provision of equipment and/or software to support event streaming/recording"
+            },
+            {
+              "value": "video-portal",
+              "text": "Provision of content portal",
+              "customDescription": "Multi-media content portal"
+            },
+            {
+              "value": "web-conferencing",
+              "text": "Web/desktop conferencing",
+              "customDescription": "Video conferencing service to desktops and hand-held devices using software"
+            }
+          ],
+          "rowTitleWidth": "20%"
+        },
+        {
+          "type": "matrixdropdown",
+          "name": "services_collaboration",
+          "state": "collapsed",
+          "title": "Collaboration services",
+          "hideCheckboxLabels": true,
+          "columns": [
+            {
+              "name": "offered",
+              "title": "Service offered",
+              "cellType": "checkbox",
+              "choices": [
+                {
+                  "value": "yes",
+                  "text": "Yes"
+                }
+              ]
+            },
+            {
+              "name": "name",
+              "title": "Service name",
+              "cellType": "text",
+              "width": "20%",
+              "visibleIf": "{row.offered} = ['yes']"
+            },
+            {
+              "name": "description",
+              "title": "Official description",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2
+            },
+            {
+              "name": "additional_information",
+              "title": "Additional information",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2,
+              "placeholder": "(e.g. software used, third party service, links, etc.)"
+            }
+          ],
+          "cellType": "checkbox",
+          "rows": [
+            {
+              "value": "e-portfolio",
+              "text": "E-portfolio service",
+              "customDescription": "Functions to create user professional and career portfolios"
+            },
+            {
+              "value": "identifier-reg",
+              "text": "Identifier Registry",
+              "customDescription": "Registering of unique and automatically-processable identifiers in the form of text or numeric strings"
+            },
+            {
+              "value": "journal-library-access",
+              "text": "Journal access",
+              "customDescription": "Access to academic journals"
+            },
+            {
+              "value": "mailing-lists",
+              "text": "Mailing lists",
+              "customDescription": "Service for operation of electronic discussion lists"
+            },
+            {
+              "value": "project-collaboration-toolkit",
+              "text": "Project collaboration",
+              "customDescription": "Packaged services or virtual project groups e.g. mailing lists, storage, web meetings, wiki."
+            },
+            {
+              "value": "scheduling-tool",
+              "text": "Scheduling tool",
+              "customDescription": "Provision of tools to users for scheduling appointments or classes"
+            },
+            {
+              "value": "survey-tool",
+              "text": "Survey/polling tool",
+              "customDescription": "Provision of applications for creating surveys or polls"
+            },
+            {
+              "value": "virtual-learning-environment",
+              "text": "VLE",
+              "customDescription": "Online e-learning education system that provides virtual access to resources used in teaching"
+            },
+            {
+              "value": "voip",
+              "text": "VoIP",
+              "customDescription": "Service to deliver voice communications and multimedia sessions over Internet Protocal (IP) networks"
+            },
+            {
+              "value": "web-email-hosting",
+              "text": "Web hosting",
+              "customDescription": "Service to provide space on central web servers for users to publish their website"
+            }
+          ],
+          "rowTitleWidth": "20%"
+        },
+        {
+          "type": "matrixdropdown",
+          "name": "services_professional",
+          "state": "collapsed",
+          "title": "Professional services",
+          "hideCheckboxLabels": true,
+          "columns": [
+            {
+              "name": "offered",
+              "title": "Service offered",
+              "cellType": "checkbox",
+              "choices": [
+                {
+                  "value": "yes",
+                  "text": "Yes"
+                }
+              ]
+            },
+            {
+              "name": "name",
+              "title": "Service name",
+              "cellType": "text",
+              "width": "20%",
+              "visibleIf": "{row.offered} = ['yes']"
+            },
+            {
+              "name": "description",
+              "title": "Official description",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2
+            },
+            {
+              "name": "additional_information",
+              "title": "Additional information",
+              "cellType": "comment",
+              "visibleIf": "{row.offered} = ['yes']",
+              "rows": 2,
+              "placeholder": "(e.g. software used, third party service, links, etc.)"
+            }
+          ],
+          "cellType": "checkbox",
+          "rows": [
+            {
+              "value": "consultancy",
+              "text": "Consultancy/training",
+              "customDescription": "Training and consultancy services provided by the NREN"
+            },
+            {
+              "value": "dissemination",
+              "text": "Dissemination",
+              "customDescription": "Dissemination of information to users e.g newsletters and magazines"
+            },
+            {
+              "value": "procurement",
+              "text": "Procurement brokerage",
+              "customDescription": "Procurement support for users to purchase services (e.g. cloud services), software/software licenses (e.g. VLE or office software) or hardware either directly or via procurement frameworks."
+            },
+            {
+              "value": "user-conference",
+              "text": "User conferences",
+              "customDescription": "Hosting of regular user conferences"
+            },
+            {
+              "value": "user-portal",
+              "text": "User portals",
+              "customDescription": "User portals for service management and monitoring"
+            }
+          ],
+          "rowTitleWidth": "20%"
+        },
+        {
+          "type": "comment",
+          "name": "service_comments",
+          "title": "Comments regarding this section:"
+        }
+      ],
+      "title": "Services"
+    }
+  ],
+  "showQuestionNumbers": "onPage",
+  "checkErrorsMode": "onValueChanged"
+}
+"""
+
+year = 2024
+
+def upgrade():
+    survey = survey_model.replace('\n', '')
+    op.execute(sa.text(
+        f"UPDATE survey SET survey = :new_survey ::json WHERE year IN ({year})"
+    ).bindparams(new_survey=survey))
+
+
+def downgrade():
+    # we dont supply a downgrade; create a new migration to add changes
+    pass
+
diff --git a/compendium_v2/publishers/excel_parser.py b/compendium_v2/publishers/excel_parser.py
index 9da7c6a465dcb909002f7e0b910d3c16297c5e56..c47c1e799dd1c949b840450f57adacdd30e2b03d 100644
--- a/compendium_v2/publishers/excel_parser.py
+++ b/compendium_v2/publishers/excel_parser.py
@@ -1,52 +1,42 @@
 import logging
-
 import openpyxl
 
+from pathlib import Path
+
 from compendium_v2.conversion import mapping
 from compendium_v2.db.presentation_model_enums import CarryMechanism, ConnectivityCoverage, MonitoringMethod, \
     UserCategory, FeeType, YesNoPlanned
 from compendium_v2.environment import setup_logging
-from compendium_v2.resources import get_resource_file_path
 
 setup_logging()
 
 logger = logging.getLogger(__name__)
 
-EXCEL_FILE_ORGANISATION = get_resource_file_path("2021_Organisation_DataSeries.xlsx")
-EXCEL_FILE_USERS = get_resource_file_path("2022_Connected_Users_DataSeries.xlsx")
-EXCEL_FILE_NETWORKS = get_resource_file_path("2022_Networks_DataSeries.xlsx")
-EXCEL_FILE_NREN_SERVICES = get_resource_file_path("NREN-Services-prefills_2023_Recovered.xlsx")
+EXCEL_FILE_ORGANISATION = openpyxl.load_workbook(str(Path(__file__).resolve(
+).parent.parent / "resources" / "2021_Organisation_DataSeries.xlsx"), data_only=True, read_only=True)
+EXCEL_FILE_USERS = openpyxl.load_workbook(str(Path(__file__).resolve(
+).parent.parent / "resources" / "2022_Connected_Users_DataSeries.xlsx"), data_only=True, read_only=True)
+EXCEL_FILE_NETWORKS = openpyxl.load_workbook(str(Path(__file__).resolve(
+).parent.parent / "resources" / "2022_Networks_DataSeries.xlsx"), data_only=True, read_only=True)
+EXCEL_FILE_NREN_SERVICES = openpyxl.load_workbook(str(Path(__file__).resolve(
+).parent.parent / "resources" / "NREN-Services-prefills_2023_Recovered.xlsx"), data_only=True, read_only=True)
 
 
 def fetch_budget_excel_data():
-    # load the xlsx file
-    sheet_name = "1. Budget"
-    wb = openpyxl.load_workbook(EXCEL_FILE_ORGANISATION, data_only=True, read_only=True)
-
-    # select the active worksheet
-    ws = wb[sheet_name]
-    # iterate over the rows in the worksheet
-    for row in range(14, 58):
-        for col in range(3, 9):
-            # extract the data from the row
-            nren = ws.cell(row=row, column=2).value
-            budget = ws.cell(row=row, column=col).value
-            year = ws.cell(row=13, column=col).value
-
+    ws = EXCEL_FILE_ORGANISATION["1. Budget"]
+    for row in ws.iter_rows(min_row=14, max_row=58, min_col=1, max_col=8):
+        nren = row[1].value
+        for i, year in zip(range(3, 8), reversed(range(2016, 2021))):
+            budget = row[i].value
             if budget is not None:
                 budget = round(budget / 1000000, 2)
-
+                assert isinstance(nren, str) and isinstance(budget, float) and isinstance(year, int)
                 yield nren.upper(), budget, year
 
 
 def fetch_funding_excel_data():
-    # load the xlsx file
-    wb = openpyxl.load_workbook(EXCEL_FILE_ORGANISATION, data_only=True, read_only=True)
-
-    # select the active worksheet
-    sheet_name = "2. Income Sources"
-    ws = wb[sheet_name]
-    ws2 = wb["Income Sources table"]
+    ws = EXCEL_FILE_ORGANISATION["2. Income Sources"]
+    ws2 = EXCEL_FILE_ORGANISATION["Income Sources table"]
 
     def hard_number_convert(s, source_name, nren, year):
         if s is None:
@@ -60,15 +50,16 @@ def fetch_funding_excel_data():
 
     # iterate over the rows in the worksheet
     def create_points_for_year_until_2017(start_row, end_row, year, col_nren, col_start):
-        for row in range(start_row, end_row):
+        for row in ws.iter_rows(min_row=start_row, max_row=end_row, min_col=col_nren, max_col=col_start+5):
+            offset = col_start - col_nren
             # extract the data from the row
-            nren = ws.cell(row=row, column=col_nren).value
-            client_institution = ws.cell(row=row, column=col_start).value
-            commercial = ws.cell(row=row, column=col_start + 1).value
-            geant_subsidy = ws.cell(row=row, column=col_start + 2).value
-            gov_public_bodies = ws.cell(row=row, column=col_start + 3).value
-            other_european_funding = ws.cell(row=row, column=col_start + 4).value
-            other = ws.cell(row=row, column=col_start + 5).value
+            nren = row[0].value
+            client_institution = row[offset].value
+            commercial = row[offset+1].value
+            geant_subsidy = row[offset+2].value
+            gov_public_bodies = row[offset+3].value
+            other_european_funding = row[offset+4].value
+            other = row[offset+5].value
 
             client_institution = hard_number_convert(client_institution, "client institution", nren, year)
             commercial = hard_number_convert(commercial, "commercial", nren, year)
@@ -84,14 +75,15 @@ def fetch_funding_excel_data():
                 yield (nren.upper(), year, client_institution, european_funding, gov_public_bodies, commercial, other)
 
     def create_points_for_year_from_2018(sheet, start_row, end_row, year, nren_col, col_start):
-        for row in range(start_row, end_row):
+        for row in sheet.iter_rows(min_row=start_row, max_row=end_row, min_col=nren_col, max_col=col_start+4):
+            offset = col_start - nren_col
             # extract the data from the row
-            nren = sheet.cell(row=row, column=nren_col).value
-            client_institution = sheet.cell(row=row, column=col_start).value
-            european_funding = sheet.cell(row=row, column=col_start + 1).value
-            gov_public_bodies = sheet.cell(row=row, column=col_start + 2).value
-            commercial = sheet.cell(row=row, column=col_start + 3).value
-            other = sheet.cell(row=row, column=col_start + 4).value
+            nren = row[0].value
+            client_institution = row[offset].value
+            european_funding = row[offset+1].value
+            gov_public_bodies = row[offset+2].value
+            commercial = row[offset+3].value
+            other = row[offset+4].value
 
             client_institution = hard_number_convert(client_institution, "client institution", nren, year)
             european_funding = hard_number_convert(european_funding, "european funding", nren, year)
@@ -123,12 +115,7 @@ def fetch_funding_excel_data():
 
 
 def fetch_charging_structure_excel_data():
-    # load the xlsx file
-    wb = openpyxl.load_workbook(EXCEL_FILE_ORGANISATION, data_only=True, read_only=True)
-
-    # select the active worksheet
-    sheet_name = "3. Charging mechanism"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_ORGANISATION["3. Charging mechanism"]
 
     # iterate over the rows in the worksheet
     def create_points_for_2021(start_row, end_row, year, col_start):
@@ -190,12 +177,7 @@ def fetch_charging_structure_excel_data():
 
 
 def fetch_staffing_excel_data():
-    # load the xlsx file
-    wb = openpyxl.load_workbook(EXCEL_FILE_ORGANISATION, data_only=True, read_only=True)
-
-    # select the active worksheet
-    sheet_name = "4. Staff"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_ORGANISATION["4. Staff"]
 
     start_row = 18
     end_row = 61
@@ -208,42 +190,30 @@ def fetch_staffing_excel_data():
             return 0
 
     def create_points_for_year(year, nren_column, start_column):
-        for row in range(start_row, end_row):
+        offset = start_column - nren_column
+        for row in ws.iter_rows(min_row=start_row, max_row=end_row, min_col=nren_column, max_col=start_column + 1):
+
             # extract the data from the row
-            nren = ws.cell(row=row, column=nren_column).value
-            permanent = ws.cell(row=row, column=start_column).value
+            nren = row[0].value
+            if not nren:
+                continue
+            permanent = row[offset].value
             permanent = convert_number(permanent, nren, year, "permanent ftes")
-            subcontracted = ws.cell(row=row, column=start_column + 1).value
+            subcontracted = row[offset+1].value
             subcontracted = convert_number(subcontracted, nren, year, "subcontractor ftes")
             if permanent + subcontracted > 0:
                 yield nren.upper(), year, permanent, subcontracted
 
-    # For 2016
-    yield from create_points_for_year(2016, 53, 55)
-
-    # For 2017
-    yield from create_points_for_year(2017, 43, 46)
-
-    # For 2018
-    yield from create_points_for_year(2018, 33, 36)
-
-    # For 2019
-    yield from create_points_for_year(2019, 23, 26)
-
-    # For 2020
-    yield from create_points_for_year(2020, 13, 16)
-
-    # For 2021
     yield from create_points_for_year(2021, 2, 5)
+    yield from create_points_for_year(2020, 13, 16)
+    yield from create_points_for_year(2019, 23, 26)
+    yield from create_points_for_year(2018, 33, 36)
+    yield from create_points_for_year(2017, 43, 46)
+    yield from create_points_for_year(2016, 53, 55)
 
 
 def fetch_staff_function_excel_data():
-    # load the xlsx file
-    wb = openpyxl.load_workbook(EXCEL_FILE_ORGANISATION, data_only=True, read_only=True)
-
-    # select the active worksheet
-    sheet_name = "5. Staff by Function"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_ORGANISATION["5. Staff by Function"]
 
     start_row = 14
     end_row = 58
@@ -260,17 +230,18 @@ def fetch_staff_function_excel_data():
         return convert_number(value, nren, year, description)
 
     def create_points_for_year_until_2019(year, nren_column, start_column):
-        for row in range(start_row, end_row):
+        offset = abs(start_column - nren_column)
+        for row in ws.iter_rows(min_row=start_row, max_row=end_row, min_col=nren_column, max_col=start_column + 5):
             # extract the data from the row
-            nren = ws.cell(row=row, column=nren_column).value
+            nren = row[0].value
             if nren is None:
                 continue
-            admin = read_cell_number(row, start_column, nren, year, "admin and finance ftes")
-            communication = read_cell_number(row, start_column + 1, nren, year, "communication ftes")
-            infosec = read_cell_number(row, start_column + 2, nren, year, "infosec ftes")
-            it = read_cell_number(row, start_column + 3, nren, year, "it and software dev ftes")
-            noc = read_cell_number(row, start_column + 4, nren, year, "NOC and engineering ftes")
-            others = read_cell_number(row, start_column + 5, nren, year, "other ftes")
+            admin = convert_number(row[offset].value, nren, year, "admin and finance ftes")
+            communication = convert_number(row[offset+1].value, nren, year, "communication ftes")
+            infosec = convert_number(row[offset+2].value, nren, year, "infosec ftes")
+            it = convert_number(row[offset+3].value, nren, year, "it and software dev ftes")
+            noc = convert_number(row[offset+4].value, nren, year, "NOC and engineering ftes")
+            others = convert_number(row[offset+5].value, nren, year, "other ftes")
 
             technical = infosec + it + noc
             non_technical = admin + communication + others
@@ -279,13 +250,13 @@ def fetch_staff_function_excel_data():
                 yield nren.upper(), year, technical, non_technical
 
     def create_points_for_year(year, nren_column, start_column):
-        for row in range(start_row, end_row):
-            # extract the data from the row
-            nren = ws.cell(row=row, column=nren_column).value
+        offset = abs(start_column - nren_column)
+        for row in ws.iter_rows(min_row=start_row, max_row=end_row, min_col=nren_column, max_col=start_column + 1):
+            nren = row[0].value
             if nren is None:
                 continue
-            technical = read_cell_number(row, start_column, nren, year, "technical ftes")
-            non_technical = read_cell_number(row, start_column + 1, nren, year, "non-technical ftes")
+            technical = convert_number(row[offset].value, nren, year, "technical ftes")
+            non_technical = convert_number(row[offset+1].value, nren, year, "non-technical ftes")
             if technical + non_technical > 0:
                 yield nren.upper(), year, technical, non_technical
 
@@ -306,25 +277,19 @@ def fetch_staff_function_excel_data():
 
 
 def fetch_ecproject_excel_data():
-    # load the xlsx file
-    wb = openpyxl.load_workbook(EXCEL_FILE_ORGANISATION, data_only=True, read_only=True)
-
-    # select the active worksheet
-    sheet_name = "7. EC Projects"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_ORGANISATION["7. EC Projects"]
 
     start_row = 6
 
     def create_points_for_year(year, start_column, end_row):
-        for row in range(start_row, end_row):
+        for row in ws.iter_rows(min_row=start_row, max_row=end_row, min_col=start_column, max_col=start_column + 1):
             # extract the data from the row
-            nren = ws.cell(row=row, column=start_column).value
+            nren = row[0].value
             if nren is None:
                 continue
-            project = ws.cell(row=row, column=start_column + 1).value
-            if project is None:
-                continue
-            yield nren.upper(), year, project
+            project = row[1].value
+            if project is not None:
+                yield nren.upper(), year, project
 
     yield from create_points_for_year(2017, 13, 165)
 
@@ -338,12 +303,7 @@ def fetch_ecproject_excel_data():
 
 
 def fetch_organization_excel_data():
-    # load the xlsx file
-    wb = openpyxl.load_workbook(EXCEL_FILE_ORGANISATION, data_only=True, read_only=True)
-
-    # select the active worksheet
-    sheet_name = "Organization"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_ORGANISATION["Organization"]
 
     # iterate over the rows in the worksheet
     for row in range(5, 48):
@@ -356,12 +316,7 @@ def fetch_organization_excel_data():
 
 
 def fetch_traffic_excel_data():
-    # load the xlsx file
-    wb = openpyxl.load_workbook(EXCEL_FILE_NETWORKS, data_only=True, read_only=True)
-
-    # select the active worksheet
-    sheet_name = "Estimated_Traffic TByte"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_NETWORKS["Estimated_Traffic TByte"]
 
     rows = list(ws.rows)
 
@@ -400,8 +355,7 @@ def fetch_traffic_excel_data():
 
 
 def fetch_nren_services_excel_data():
-    wb = openpyxl.load_workbook(EXCEL_FILE_NREN_SERVICES, data_only=True, read_only=True)
-    ws = wb["Sheet1"]
+    ws = EXCEL_FILE_NREN_SERVICES["Sheet1"]
     rows = list(ws.rows)
 
     titles = rows[0]
@@ -447,6 +401,8 @@ def fetch_nren_services_excel_data():
 def get_category(excel_cat):
     if not excel_cat:
         return None
+    if "hospital" in excel_cat.lower():
+        return UserCategory.hospitals
     if "universit" in excel_cat.lower():
         return UserCategory.universities
     if "research ins" in excel_cat.lower():
@@ -457,8 +413,6 @@ def get_category(excel_cat):
         return UserCategory.iros
     if "cultural" in excel_cat.lower() or "librar" in excel_cat.lower():
         return UserCategory.cultural
-    if "hospital" in excel_cat.lower():
-        return UserCategory.hospitals
     if "primary" in excel_cat.lower():
         return UserCategory.primary_schools
     if "secondary" in excel_cat.lower():
@@ -471,10 +425,7 @@ def get_category(excel_cat):
 
 
 def fetch_remit_excel_data():
-    wb = openpyxl.load_workbook(EXCEL_FILE_USERS, data_only=True, read_only=True)
-    sheet_name = "Connectivity Remit"
-    ws = wb[sheet_name]
-    rows = list(ws.rows)
+    ws = EXCEL_FILE_USERS["Connectivity Remit"]
 
     def get_remit(excel_remit):
         if not excel_remit:
@@ -497,32 +448,40 @@ def fetch_remit_excel_data():
 
     result = {}
 
-    def create_points_for_year(year, start_column):
-        for i in range(8, 51):
-            nren_name = rows[i][start_column].value
+    def create_points_for_year(year, start_column, cut=False):
+        category_row = [c.value for c in list(ws.iter_rows(
+            min_row=8, max_row=8, min_col=start_column+2, max_col=start_column + 21 + (2 if cut else 0)))[0]]
+        if cut:
+            category_row = category_row[:4] + category_row[6:]
+        categories = [get_category(cat) for cat in category_row[::2]]
+
+        for row in ws.iter_rows(min_row=9, max_row=52, min_col=start_column+1,
+                                max_col=start_column + 21 + (2 if cut else 0)):
+            if cut:
+                row = row[:4] + row[6:]
+
+            nren_name = row[0].value
             if not nren_name:
                 continue
             nren_name = nren_name.upper()
-            for col in range(start_column + 2, start_column + 21, 2):
-                c = col
-                if year == 2021 and col > 30:
-                    c += 2
-                category = get_category(rows[7][c].value)
-                remit = get_remit(rows[i][c].value)
-                if category and remit:
+            every_second_value = [c.value for c in row][::2]
+            remit = [get_remit(val) for val in every_second_value[1:]]
+
+            values = dict(zip(categories, remit))
+
+            for category, remit in values.items():
+                if remit:
                     result[(nren_name, year, category)] = remit
 
-    create_points_for_year(2019, 72)
-    create_points_for_year(2020, 50)
-    create_points_for_year(2021, 26)
     create_points_for_year(2022, 3)
+    create_points_for_year(2021, 26, cut=True)
+    create_points_for_year(2020, 50)
+    create_points_for_year(2019, 72)
     return result
 
 
 def fetch_nr_connected_excel_data():
-    wb = openpyxl.load_workbook(EXCEL_FILE_USERS, data_only=True, read_only=True)
-    sheet_name = "Connected Institutions"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_USERS["Connected Institutions"]
     rows = list(ws.rows)
     result = {}
 
@@ -546,9 +505,7 @@ def fetch_nr_connected_excel_data():
 
 
 def fetch_market_share_excel_data():
-    wb = openpyxl.load_workbook(EXCEL_FILE_USERS, data_only=True, read_only=True)
-    sheet_name = "Table Market Share"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_USERS["Table Market Share"]
     rows = list(ws.rows)
     result = {}
 
@@ -574,9 +531,7 @@ def fetch_market_share_excel_data():
 
 
 def fetch_users_served_excel_data():
-    wb = openpyxl.load_workbook(EXCEL_FILE_USERS, data_only=True, read_only=True)
-    sheet_name = "Users"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_USERS["Users"]
     rows = list(ws.rows)
     result = {}
 
@@ -600,9 +555,7 @@ def fetch_users_served_excel_data():
 
 
 def fetch_typical_speed_excel_data():
-    wb = openpyxl.load_workbook(EXCEL_FILE_USERS, data_only=True, read_only=True)
-    sheet_name = "Table _Typical IP Link capacity"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_USERS["Table _Typical IP Link capacity"]
     rows = list(ws.rows)
     result = {}
 
@@ -628,9 +581,7 @@ def fetch_typical_speed_excel_data():
 
 
 def fetch_highest_speed_excel_data():
-    wb = openpyxl.load_workbook(EXCEL_FILE_USERS, data_only=True, read_only=True)
-    sheet_name = "Table _Highest IP Link capacity"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_USERS["Table _Highest IP Link capacity"]
     rows = list(ws.rows)
     result = {}
 
@@ -656,9 +607,7 @@ def fetch_highest_speed_excel_data():
 
 
 def fetch_highest_speed_proportion_excel_data():
-    wb = openpyxl.load_workbook(EXCEL_FILE_USERS, data_only=True, read_only=True)
-    sheet_name = "Aver High cap conn Share"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_USERS["Aver High cap conn Share"]
     rows = list(ws.rows)
     result = {}
 
@@ -681,9 +630,7 @@ def fetch_highest_speed_proportion_excel_data():
 
 
 def fetch_carriers_excel_data():
-    wb = openpyxl.load_workbook(EXCEL_FILE_USERS, data_only=True, read_only=True)
-    sheet_name = "Traffic carriers"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_USERS["Traffic carriers"]
     rows = list(ws.rows)
 
     def get_carrier(excel_carrier):
@@ -723,9 +670,7 @@ def fetch_carriers_excel_data():
 
 
 def fetch_growth_excel_data():
-    wb = openpyxl.load_workbook(EXCEL_FILE_NETWORKS, data_only=True, read_only=True)
-    sheet_name = "Table Traffic Growth % "
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_NETWORKS["Table Traffic Growth % "]
     rows = list(ws.rows)
     result = {}
 
@@ -749,9 +694,7 @@ def fetch_growth_excel_data():
 
 
 def fetch_average_traffic_excel_data():
-    wb = openpyxl.load_workbook(EXCEL_FILE_USERS, data_only=True, read_only=True)
-    sheet_name = "Average Traffic"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_USERS["Average Traffic"]
     rows = list(ws.rows)
     result = {}
 
@@ -776,9 +719,7 @@ def fetch_average_traffic_excel_data():
 
 
 def fetch_peak_traffic_excel_data():
-    wb = openpyxl.load_workbook(EXCEL_FILE_USERS, data_only=True, read_only=True)
-    sheet_name = "Peak traffic"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_USERS["Peak traffic"]
     rows = list(ws.rows)
     result = {}
 
@@ -803,9 +744,7 @@ def fetch_peak_traffic_excel_data():
 
 
 def fetch_remote_campuses_excel_data():
-    wb = openpyxl.load_workbook(EXCEL_FILE_USERS, data_only=True, read_only=True)
-    sheet_name = "Foreign Campuses"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_USERS["Foreign Campuses"]
     rows = list(ws.rows)
 
     def create_points_for_year(year, start_column):
@@ -831,9 +770,7 @@ def fetch_remote_campuses_excel_data():
 
 
 def fetch_dark_fibre_iru_excel_data():
-    wb = openpyxl.load_workbook(EXCEL_FILE_NETWORKS, data_only=True, read_only=True)
-    sheet_name = "Dark Fibre"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_NETWORKS["Dark Fibre"]
     rows = list(ws.rows)
 
     def parse_int(excel_value):
@@ -871,9 +808,7 @@ def fetch_dark_fibre_iru_excel_data():
 
 
 def fetch_dark_fibre_installed_excel_data():
-    wb = openpyxl.load_workbook(EXCEL_FILE_NETWORKS, data_only=True, read_only=True)
-    sheet_name = "Dark Fibre"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_NETWORKS["Dark Fibre"]
     rows = list(ws.rows)
 
     def parse_int(excel_value):
@@ -905,9 +840,7 @@ def fetch_dark_fibre_installed_excel_data():
 
 
 def fetch_iru_duration_excel_data():
-    wb = openpyxl.load_workbook(EXCEL_FILE_NETWORKS, data_only=True, read_only=True)
-    sheet_name = "IRU duration"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_NETWORKS["IRU duration"]
     rows = list(ws.rows)
     result = {}
 
@@ -938,9 +871,7 @@ def fetch_iru_duration_excel_data():
 
 
 def fetch_passive_monitoring_excel_data():
-    wb = openpyxl.load_workbook(EXCEL_FILE_NETWORKS, data_only=True, read_only=True)
-    sheet_name = "Traffic monitoring"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_NETWORKS["Traffic monitoring"]
     rows = list(ws.rows)
 
     def create_points_for_year(year, start_column):
@@ -966,9 +897,7 @@ def fetch_passive_monitoring_excel_data():
 
 
 def fetch_largest_link_capacity_excel_data():
-    wb = openpyxl.load_workbook(EXCEL_FILE_NETWORKS, data_only=True, read_only=True)
-    sheet_name = "Largest IP Trunk capacity"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_NETWORKS["Largest IP Trunk capacity"]
     rows = list(ws.rows)
     result = {}
 
@@ -993,9 +922,7 @@ def fetch_largest_link_capacity_excel_data():
 
 
 def fetch_typical_backbone_capacity_excel_data():
-    wb = openpyxl.load_workbook(EXCEL_FILE_NETWORKS, data_only=True, read_only=True)
-    sheet_name = "Typical IP Trunk capacity"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_NETWORKS["Typical IP Trunk capacity"]
     rows = list(ws.rows)
     result = {}
 
@@ -1020,9 +947,7 @@ def fetch_typical_backbone_capacity_excel_data():
 
 
 def fetch_non_r_e_peers_excel_data():
-    wb = openpyxl.load_workbook(EXCEL_FILE_NETWORKS, data_only=True, read_only=True)
-    sheet_name = "Peering-Non R& Network"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_NETWORKS["Peering-Non R& Network"]
     rows = list(ws.rows)
 
     def create_points_for_year(year, start_column):
@@ -1045,9 +970,7 @@ def fetch_non_r_e_peers_excel_data():
 
 
 def fetch_ops_automation_excel_data():
-    wb = openpyxl.load_workbook(EXCEL_FILE_NETWORKS, data_only=True, read_only=True)
-    sheet_name = "Automation"
-    ws = wb[sheet_name]
+    ws = EXCEL_FILE_NETWORKS["Automation"]
     rows = list(ws.rows)
 
     def create_points_for_year(year, start_column):
diff --git a/compendium_v2/publishers/survey_publisher.py b/compendium_v2/publishers/survey_publisher.py
index a2a0cdff0fb719aca5dee317c8955d5a63792e40..8acd5d885b2f8761dc2d53e4aae3aa3767f6243d 100644
--- a/compendium_v2/publishers/survey_publisher.py
+++ b/compendium_v2/publishers/survey_publisher.py
@@ -9,546 +9,27 @@ Usage:
     Used in publish_survey API in compendium_v2/routes/survey.py
 """
 
-from decimal import Decimal
-from typing import List
+from typing import Sequence, Dict, Any
+from sqlalchemy import select, delete
 
-from sqlalchemy import delete, select
 
 from compendium_v2.db import db
-from compendium_v2.db.presentation_models import BudgetEntry, ChargingStructure, ECProject, ExternalConnections, \
-    InstitutionURLs, NrenStaff, ParentOrganization, Policy, SubOrganization, TrafficVolume, ExternalConnection, \
-    FundingSource, CentralProcurement, ServiceManagement, ServiceUserTypes, EOSCListings, \
-    Standards, CrisisExercises, SecurityControls, ConnectedProportion, ConnectivityLevel, \
-    ConnectionCarrier, ConnectivityLoad, ConnectivityGrowth, CommercialConnectivity, \
-    CommercialChargingLevel, RemoteCampuses, DarkFibreLease, DarkFibreInstalled, FibreLight, \
-    NetworkMapUrls, MonitoringTools, PassiveMonitoring, TrafficStatistics, SiemVendors, \
-    CertificateProviders, WeatherMap, PertTeam, AlienWave, Capacity, NonREPeers, TrafficRatio, \
-    OpsAutomation, NetworkFunctionVirtualisation, NetworkAutomation, NRENService
-from compendium_v2.db.presentation_model_enums import CarryMechanism, CommercialCharges, YesNoPlanned, \
-    CommercialConnectivityCoverage, ConnectivityCoverage, FeeType, MonitoringMethod, ServiceCategory, UserCategory
-from compendium_v2.db.survey_models import ResponseStatus, SurveyResponse
-
-
-def int_or_none(answers_dict, key):
-    if key in answers_dict:
-        value = answers_dict[key]
-        if isinstance(value, str):
-            value = value.replace(",", ".")
-        return int(answers_dict[key])
-    return None
-
-
-def decimal_or_none(answers_dict, key):
-    if key in answers_dict:
-        value = answers_dict[key]
-        if isinstance(value, str):
-            value = value.replace(",", ".")
-        return Decimal(value)
-    return None
-
-
-def decimal_or_zero(answers_dict, key):
-    value = answers_dict.get(key, 0)
-    if isinstance(value, str):
-        value = value.replace(",", ".")
-    return Decimal(value)
-
-
-def bool_or_none(answer, key=None):
-    if key:
-        answer = answer.get(key)
-    if answer:
-        return answer == "Yes"
-    return None
-
-
-def _map_2023(nren, answers) -> None:
-    year = 2023
-
-    for table_class in [BudgetEntry, ChargingStructure, ECProject, ExternalConnections,
-                        InstitutionURLs, NrenStaff, ParentOrganization, Policy, SubOrganization, TrafficVolume,
-                        FundingSource, CentralProcurement, ServiceManagement, ServiceUserTypes, EOSCListings,
-                        Standards, CrisisExercises, SecurityControls, ConnectedProportion, ConnectivityLevel,
-                        ConnectionCarrier, ConnectivityLoad, ConnectivityGrowth, CommercialConnectivity,
-                        CommercialChargingLevel, RemoteCampuses, DarkFibreLease, DarkFibreInstalled, FibreLight,
-                        NetworkMapUrls, MonitoringTools, PassiveMonitoring, TrafficStatistics, SiemVendors,
-                        CertificateProviders, WeatherMap, PertTeam, AlienWave, Capacity, NonREPeers, TrafficRatio,
-                        OpsAutomation, NetworkFunctionVirtualisation, NetworkAutomation, NRENService]:
-        db.session.execute(delete(table_class).where(table_class.year == year,  # type: ignore
-                           table_class.nren_id == nren.id))  # type: ignore
-
-    answers = answers["data"]
-    budget = answers.get("budget")
-    if budget:
-        # replace comma with dot for decimal, surveyjs allows commas for decimals (common in EU countries)
-        budget = budget.replace(",", ".")
-        db.session.add(BudgetEntry(nren_id=nren.id, nren=nren, year=year, budget=Decimal(budget)))
-
-    funding_source = answers.get("income_sources")
-    if funding_source:
-        db.session.add(FundingSource(
-            nren_id=nren.id, nren=nren, year=year,
-            client_institutions=decimal_or_zero(funding_source, "client_institutions"),
-            european_funding=decimal_or_zero(funding_source, "european_funding"),
-            gov_public_bodies=decimal_or_zero(funding_source, "gov_public_bodies"),
-            commercial=decimal_or_zero(funding_source, "commercial"),
-            other=decimal_or_zero(funding_source, "other")
-        ))
-
-    charging = answers.get("charging_mechanism")
-    if charging:
-        db.session.add(ChargingStructure(nren_id=nren.id, nren=nren, year=year, fee_type=FeeType[charging]))
-
-    staff_roles = answers.get("staff_roles", {})
-    staff_employment_type = answers.get("staff_employment_type", {})
-    if staff_roles or staff_employment_type:
-        db.session.add(NrenStaff(
-            nren_id=nren.id, nren=nren, year=year,
-            permanent_fte=decimal_or_zero(staff_employment_type, "permanent_fte"),
-            subcontracted_fte=decimal_or_zero(staff_employment_type, "subcontracted_fte"),
-            technical_fte=decimal_or_zero(staff_roles, "technical_fte"),
-            non_technical_fte=decimal_or_zero(staff_roles, "nontechnical_fte")
-        ))
-
-    has_parent = answers.get("parent_organization") == "Yes"
-    parent = answers.get("parent_organization_name")
-    if has_parent and parent:
-        db.session.add(ParentOrganization(nren_id=nren.id, nren=nren, year=year, organization=parent))
-
-    has_subs = answers.get("suborganizations") == 'Yes'
-    subs = answers.get("suborganization_details")
-    if has_subs and subs:
-        for sub in subs:
-            role = sub.get("suborganization_role", "")
-            if role == "other":
-                role = sub.get("suborganization_role-Comment", "")
-            db.session.add(SubOrganization(
-                nren_id=nren.id, nren=nren, year=year,
-                organization=sub.get("suborganization_name"),
-                role=role
-            ))
-    has_ec_projects = answers.get("ec_projects") == "Yes"
-    ec_projects = answers.get("ec_project_names")
-    if has_ec_projects and ec_projects:
-        for ec_project in ec_projects:
-            if ec_project:
-                db.session.add(
-                    ECProject(nren_id=nren.id, nren=nren, year=year, project=ec_project.get("ec_project_name"))
-                )
-
-    strategy = answers.get("corporate_strategy_url", "")
-    policies = answers.get("policies", {})
-    if strategy or policies:
-        db.session.add(Policy(
-            nren_id=nren.id, nren=nren, year=year,
-            strategic_plan=strategy,
-            environmental=policies.get("environmental_policy", {}).get("url", ""),
-            equal_opportunity=policies.get("equal_opportunity_policy", {}).get("url", ""),
-            connectivity=policies.get("connectivity_policy", {}).get("url", ""),
-            acceptable_use=policies.get("acceptable_use_policy", {}).get("url", ""),
-            privacy_notice=policies.get("privacy_notice", {}).get("url", ""),
-            data_protection=policies.get("data_protection_contact", {}).get("url", ""),
-            gender_equality=policies.get("gender_equality_policy", {}).get("url", "")
-        ))
-
-    traffic_estimate = answers.get("traffic_estimate")
-    if traffic_estimate:
-        db.session.add(TrafficVolume(
-            nren_id=nren.id, nren=nren, year=year,
-            to_customers=decimal_or_zero(traffic_estimate, "to_customers"),
-            from_customers=decimal_or_zero(traffic_estimate, "from_customers"),
-            to_external=decimal_or_zero(traffic_estimate, "to_external"),
-            from_external=decimal_or_zero(traffic_estimate, "from_external")
-        ))
-
-    institution_urls = answers.get("connected_sites_lists")
-    if institution_urls:
-        urls = [i.get("connected_sites_url", "") for i in institution_urls if i.get("connected_sites_url", "") != ""]
-        if urls:
-            db.session.add(InstitutionURLs(
-                nren_id=nren.id, nren=nren, year=year,
-                urls=urls
-            ))
-
-    central_procurement = answers.get("central_software_procurement") == "Yes"
-    if central_procurement:
-        central_procurement_amount = decimal_or_none(answers, "central_procurement_amount")
-    else:
-        central_procurement_amount = None
-    db.session.add(CentralProcurement(
-        nren_id=nren.id, nren=nren, year=year,
-        central_procurement=central_procurement,
-        amount=central_procurement_amount
-    ))
-
-    formal_service_management_framework = answers.get("formal_service_management_framework")
-    service_level_targets = answers.get("service_level_targets")
-    if formal_service_management_framework or service_level_targets:
-        db.session.add(ServiceManagement(
-            nren_id=nren.id, nren=nren, year=year,
-            service_management_framework=bool_or_none(formal_service_management_framework),
-            service_level_targets=bool_or_none(service_level_targets)
-        ))
-
-    service_type_matrix = answers.get("service_matrix", {})
-    for user_type, service_types in service_type_matrix.items():
-        user_type = UserCategory[user_type]
-        service_types = service_types["service_types"]
-        for service_type in service_types:
-            service_type = ServiceCategory[service_type]
-            db.session.add(ServiceUserTypes(
-                nren_id=nren.id, nren=nren, year=year,
-                user_category=user_type,
-                service_category=service_type
-            ))
-
-    has_eosc_listings = answers.get("service_portfolio_eosc_portal") == "Yes"
-    services_on_eosc_portal_list = answers.get("services_on_eosc_portal_list", [])
-    eosc_list = [i.get("service_name") for i in services_on_eosc_portal_list]
-    eosc_list = [i for i in eosc_list if i]
-    if has_eosc_listings and eosc_list:
-        db.session.add(EOSCListings(
-            nren_id=nren.id, nren=nren, year=year,
-            service_names=eosc_list
-        ))
-
-    audits = answers.get("audits")
-    business_continuity_plans = answers.get("business_continuity_plans")
-    crisis_management_procedure = answers.get("crisis_management_procedure")
-    if audits or business_continuity_plans or crisis_management_procedure:
-        db.session.add(Standards(
-            nren_id=nren.id, nren=nren, year=year,
-            audits=bool_or_none(audits),
-            audit_specifics=answers.get("audit_specifics", ""),
-            business_continuity_plans=bool_or_none(business_continuity_plans),
-            business_continuity_plans_specifics=answers.get("business_continuity_plans_specifics", ""),
-            crisis_management_procedure=bool_or_none(crisis_management_procedure)
-        ))
-
-    crisis_exercises = answers.get("crisis_exercises")
-    if crisis_exercises:
-        db.session.add(CrisisExercises(
-            nren_id=nren.id, nren=nren, year=year,
-            exercise_descriptions=crisis_exercises
-        ))
-
-    security_controls = answers.get("security_controls")
-    if security_controls:
-        if "other" in security_controls:
-            security_controls.remove("other")
-            security_controls.append(answers.get("security_controls-Comment", "other"))
-        db.session.add(SecurityControls(
-            nren_id=nren.id, nren=nren, year=year,
-            security_control_descriptions=security_controls
-        ))
 
-    connectivity_proportions = answers.get("connectivity_proportions", {})
-    for user_type, connectivity_proportion in connectivity_proportions.items():
-        user_type = UserCategory[user_type]
-        coverage = connectivity_proportion.get("covered")
-        coverage = ConnectivityCoverage[coverage] if coverage else None
-        number_connected = int_or_none(connectivity_proportion, "nr_connected")
-        market_share = decimal_or_none(connectivity_proportion, "market_share_percentage")
-        users_served = int_or_none(connectivity_proportion, "nr_of_users")
-        db.session.add(ConnectedProportion(
-            nren_id=nren.id, nren=nren, year=year,
-            user_category=user_type,
-            coverage=coverage,
-            number_connected=number_connected,
-            market_share=market_share,
-            users_served=users_served
-        ))
-
-    connectivity_levels = answers.get("connectivity_level", {})
-    for user_type, connectivity_level in connectivity_levels.items():
-        user_type = UserCategory[user_type]
-        db.session.add(ConnectivityLevel(
-            nren_id=nren.id, nren=nren, year=year,
-            user_category=user_type,
-            typical_speed=int_or_none(connectivity_level, "typical_speed"),
-            highest_speed=int_or_none(connectivity_level, "highest_speed"),
-            highest_speed_proportion=decimal_or_none(connectivity_level, "highest_speed_connection_percentage")
-        ))
-
-    traffic_carriers = answers.get("traffic_carriers", {})
-    for user_type, traffic_carrier in traffic_carriers.items():
-        user_type = UserCategory[user_type]
-        traffic_carrier = traffic_carrier.get("carry_mechanism")
-        if traffic_carrier:
-            db.session.add(ConnectionCarrier(
-                nren_id=nren.id, nren=nren, year=year,
-                user_category=user_type,
-                carry_mechanism=CarryMechanism[traffic_carrier]
-            ))
-
-    traffic_loads = answers.get("traffic_load", {})
-    for user_type, traffic_load in traffic_loads.items():
-        user_type = UserCategory[user_type]
-        db.session.add(ConnectivityLoad(
-            nren_id=nren.id, nren=nren, year=year,
-            user_category=user_type,
-            average_load_from_institutions=int_or_none(traffic_load, "average_from_institutions_to_network"),
-            average_load_to_institutions=int_or_none(traffic_load, "average_to_institutions_from_network"),
-            peak_load_from_institutions=int_or_none(traffic_load, "peak_from_institutions_to_network"),
-            peak_load_to_institutions=int_or_none(traffic_load, "peak_to_institutions_from_network")
-        ))
-
-    traffic_growths = answers.get("traffic_growth", {})
-    for user_type, traffic_growth in traffic_growths.items():
-        user_type = UserCategory[user_type]
-        db.session.add(ConnectivityGrowth(
-            nren_id=nren.id, nren=nren, year=year,
-            user_category=user_type,
-            growth=decimal_or_zero(traffic_growth, "growth_rate")
-        ))
-
-    commercial_organizations = answers.get("commercial_organizations")
-    if commercial_organizations:
-        c1 = commercial_organizations.get("commercial_r_e", {}).get("connection")
-        c2 = commercial_organizations.get("commercial_general", {}).get("connection")
-        c3 = commercial_organizations.get("commercial_collaboration", {}).get("connection")
-        c4 = commercial_organizations.get("commercial_service_provider", {}).get("connection")
-        c5 = commercial_organizations.get("university_spin_off", {}).get("connection")
-        db.session.add(CommercialConnectivity(
-            nren_id=nren.id, nren=nren, year=year,
-            commercial_r_and_e=CommercialConnectivityCoverage[c1] if c1 else None,
-            commercial_general=CommercialConnectivityCoverage[c2] if c2 else None,
-            commercial_collaboration=CommercialConnectivityCoverage[c3] if c3 else None,
-            commercial_service_provider=CommercialConnectivityCoverage[c4] if c4 else None,
-            university_spin_off=CommercialConnectivityCoverage[c5] if c5 else None,
-        ))
-
-    commercial_charging_levels = answers.get("commercial_charging_levels")
-    if commercial_charging_levels:
-        c1 = commercial_charging_levels.get("collaboration", {}).get("charging_level")
-        c2 = commercial_charging_levels.get("services", {}).get("charging_level")
-        c3 = commercial_charging_levels.get("peering", {}).get("charging_level")
-        db.session.add(CommercialChargingLevel(
-            nren_id=nren.id, nren=nren, year=year,
-            collaboration=CommercialCharges[c1] if c1 else None,
-            service_supplier=CommercialCharges[c2] if c2 else None,
-            direct_peering=CommercialCharges[c3] if c3 else None,
-        ))
-
-    remote_campuses = answers.get("remote_campuses")
-    if remote_campuses:
-        remote_campuses = remote_campuses == "Yes"
-
-        if remote_campuses:
-            remote_campuses_specifics = answers.get("remote_campuses_specifics", [])
-            remote_campuses_specifics = [
-                {"country": i.get("country", ""), "local_r_and_e_connection": bool_or_none(i, "connected")}
-                for i in remote_campuses_specifics
-            ]
-        else:
-            remote_campuses_specifics = []
-        db.session.add(RemoteCampuses(
-            nren_id=nren.id, nren=nren, year=year,
-            remote_campus_connectivity=remote_campuses,
-            connections=remote_campuses_specifics
-        ))
-
-    dark_fibre_lease = answers.get("dark_fibre_lease") == "Yes"
-    if dark_fibre_lease:
-        db.session.add(DarkFibreLease(
-            nren_id=nren.id, nren=nren, year=year,
-            iru_or_lease=dark_fibre_lease,
-            fibre_length_in_country=int_or_none(answers, "dark_fibre_lease_kilometers_inside_country"),
-            fibre_length_outside_country=int_or_none(answers, "dark_fibre_lease_kilometers_outside_country"),
-            iru_duration=decimal_or_none(answers, "dark_fibre_lease_duration")
-        ))
-
-    dark_fibre_nren = answers.get("dark_fibre_nren") == "Yes"
-    if dark_fibre_nren:
-        db.session.add(DarkFibreInstalled(
-            nren_id=nren.id, nren=nren, year=year,
-            installed=dark_fibre_nren,
-            fibre_length_in_country=int_or_none(answers, "dark_fibre_nren_kilometers_inside_country")
-        ))
-
-    fibre_light = answers.get("fibre_light")
-    if fibre_light:
-        if fibre_light == "other":
-            fibre_light = answers.get("fibre_light-Comment", "other")
-        db.session.add(FibreLight(
-            nren_id=nren.id, nren=nren, year=year,
-            light_description=fibre_light
-        ))
-
-    network_map_urls = answers.get("network_map_urls", [])
-    urls = [i.get("network_map_url", "") for i in network_map_urls if i.get("network_map_url", "") != ""]
-    if urls:
-        db.session.add(NetworkMapUrls(
-            nren_id=nren.id, nren=nren, year=year,
-            urls=urls
-        ))
-
-    monitoring_tools = answers.get("monitoring_tools", [])
-    netflow_vendors = answers.get("netflow_vendors", "")
-    if monitoring_tools or netflow_vendors:
-        if "other" in monitoring_tools:
-            monitoring_tools.remove("other")
-            monitoring_tools.append(answers.get("monitoring_tools-Comment", "other"))
-        db.session.add(MonitoringTools(
-            nren_id=nren.id, nren=nren, year=year,
-            tool_descriptions=monitoring_tools,
-            netflow_processing_description=netflow_vendors
-        ))
-
-    passive_monitoring = answers.get("passive_monitoring")
-    if passive_monitoring:
-        passive_monitoring = passive_monitoring == "Yes"
-        passive_monitoring_tech = answers.get("passive_monitoring_tech")
-        db.session.add(PassiveMonitoring(
-            nren_id=nren.id, nren=nren, year=year,
-            monitoring=passive_monitoring,
-            method=MonitoringMethod[passive_monitoring_tech] if passive_monitoring_tech else None
-        ))
-
-    traffic_statistics = answers.get("traffic_statistics")
-    if traffic_statistics:
-        traffic_statistics = traffic_statistics == "Yes"
-        urls = answers.get("traffic_statistics_urls", [])
-        urls = [i.get("traffic_statistics_url", "") for i in urls if i.get("traffic_statistics_url")]
-        db.session.add(TrafficStatistics(
-            nren_id=nren.id, nren=nren, year=year,
-            traffic_statistics=traffic_statistics,
-            urls=urls
-        ))
-
-    siem_soc_vendor = answers.get("siem_soc_vendor")
-    if siem_soc_vendor:
-        if "other" in siem_soc_vendor:
-            siem_soc_vendor.remove("other")
-            siem_soc_vendor.append(answers.get("siem_soc_vendor-Comment", "other"))
-        db.session.add(SiemVendors(
-            nren_id=nren.id, nren=nren, year=year,
-            vendor_names=siem_soc_vendor
-        ))
-
-    certificate_service = answers.get("certificate_service")
-    if certificate_service:
-        if "other" in certificate_service:
-            certificate_service.remove("other")
-            certificate_service.append(answers.get("certificate_service-Comment", "other"))
-        db.session.add(CertificateProviders(
-            nren_id=nren.id, nren=nren, year=year,
-            provider_names=certificate_service
-        ))
-
-    network_weather = answers.get("network_weather")
-    if network_weather:
-        network_weather = network_weather == "Yes"
-        db.session.add(WeatherMap(
-            nren_id=nren.id, nren=nren, year=year,
-            weather_map=network_weather,
-            url=answers.get("network_weather_url", "")
-        ))
-
-    pert_team = answers.get("pert_team")
-    if pert_team:
-        pert_team = YesNoPlanned[pert_team.lower()]
-        db.session.add(PertTeam(
-            nren_id=nren.id, nren=nren, year=year,
-            pert_team=pert_team
-        ))
-
-    alienwave_services = answers.get("alienwave_services")
-    alienwave_internal = answers.get("alienwave_internal")
-    if alienwave_services or alienwave_internal:
-        alienwave_services = YesNoPlanned[alienwave_services.lower()] if alienwave_services else None
-        alienwave_internal = alienwave_internal == "Yes" if alienwave_internal else None
-        db.session.add(AlienWave(
-            nren_id=nren.id, nren=nren, year=year,
-            alien_wave_third_pary=alienwave_services,
-            nr_of_alien_wave_third_party_services=int_or_none(answers, "alienwave_services_number"),
-            alien_wave_internal=alienwave_internal
-        ))
-
-    max_capacity = answers.get("max_capacity")
-    typical_capacity = answers.get("typical_capacity")
-    if max_capacity or typical_capacity:
-        db.session.add(Capacity(
-            nren_id=nren.id, nren=nren, year=year,
-            largest_link_capacity=decimal_or_none(answers, "max_capacity"),
-            typical_backbone_capacity=decimal_or_none(answers, "typical_capacity")
-        ))
-
-    external_connections = answers.get("external_connections")
-    if external_connections:
-        connections: List[ExternalConnection] = []
-        for connection in external_connections:
-            connections.append({
-                'link_name': connection.get('link_name', ''),
-                'capacity': connection.get('capacity'),
-                'from_organization': connection.get('from_organization', ''),
-                'to_organization': connection.get('to_organization', ''),
-                'interconnection_method': connection.get('interconnection_method')
-            })
-        db.session.add(ExternalConnections(
-            nren_id=nren.id, nren=nren, year=year,
-            connections=connections
-        ))
-
-    non_r_and_e_peers = answers.get("non_r_and_e_peers")
-    if non_r_and_e_peers:
-        db.session.add(NonREPeers(
-            nren_id=nren.id, nren=nren, year=year,
-            nr_of_non_r_and_e_peers=int(non_r_and_e_peers)
-        ))
-
-    commodity_vs_r_e = answers.get("commodity_vs_r_e")
-    if commodity_vs_r_e:
-        db.session.add(TrafficRatio(
-            nren_id=nren.id, nren=nren, year=year,
-            r_and_e_percentage=decimal_or_zero(commodity_vs_r_e, "r_e"),
-            commodity_percentage=decimal_or_zero(commodity_vs_r_e, "commodity"),
-        ))
-
-    operational_process_automation = answers.get("operational_process_automation")
-    if operational_process_automation:
-        db.session.add(OpsAutomation(
-            nren_id=nren.id, nren=nren, year=year,
-            ops_automation=YesNoPlanned[operational_process_automation.lower()],
-            ops_automation_specifics=answers.get("operational_process_automation_tools", "")
-        ))
-
-    nfv = answers.get("nfv")
-    if nfv:
-        nfv_types = answers.get("nfv_types", [])
-        if "other" in nfv_types:
-            nfv_types.remove("other")
-            nfv_types.append(answers.get("nfv_types-Comment", "other"))
-        db.session.add(NetworkFunctionVirtualisation(
-            nren_id=nren.id, nren=nren, year=year,
-            nfv=YesNoPlanned[nfv.lower()],
-            nfv_specifics=nfv_types
-        ))
+from compendium_v2.db.survey_models import ResponseStatus, SurveyResponse
+from compendium_v2.publishers.year.map_2023 import map_2023
+from compendium_v2.db.presentation_models import PresentationModel
 
-    network_automation = answers.get("network_automation")
-    if network_automation:
-        network_automation_tasks = answers.get("network_automation_tasks", [])
-        db.session.add(NetworkAutomation(
-            nren_id=nren.id, nren=nren, year=year,
-            network_automation=YesNoPlanned[network_automation.lower()],
-            network_automation_specifics=network_automation_tasks
-        ))
 
-    all_services = {}
-    for question_name in ["services_collaboration", "services_hosting", "services_identity", "services_isp",
-                          "services_multimedia", "services_network", "services_professional", "services_security"]:
-        all_services.update(answers.get(question_name, {}))
+def save_data(year: int, data: Dict[PresentationModel, Sequence[Dict[str, Any]]]):
+    table_classes = list(data.keys())
+    if not all(issubclass(table, db.Model) for table in table_classes):
+        raise ValueError("All tables must be subclasses of db.Model")
 
-    for service_key, answers in all_services.items():
-        offered = answers.get("offered")
-        if offered == ["yes"]:
-            db.session.add(NRENService(
-                nren_id=nren.id, nren=nren, year=year,
-                service_key=service_key,
-                product_name=answers.get("name", ""),
-                additional_information=answers.get("additional_information", ""),
-                official_description=answers.get("description", "")
-            ))
+    for table_class in table_classes:
+        db.session.execute(delete(table_class).where(table_class.year == year))
+    for model, rows in data.items():
+        db.session.bulk_insert_mappings(model, rows)  # type: ignore
+        db.session.commit()
 
 
 def publish(year):
@@ -558,7 +39,7 @@ def publish(year):
     ).unique()
 
     question_mapping = {
-        2023: _map_2023
+        2023: map_2023
     }
 
     mapping_function = question_mapping.get(year)
@@ -566,5 +47,6 @@ def publish(year):
     if not mapping_function:
         raise ValueError(f"No publishing function found for the {year} survey.")
 
-    for response in responses:
-        mapping_function(response.nren, response.answers)
+    data = mapping_function(responses)
+
+    save_data(year, data)
diff --git a/compendium_v2/publishers/survey_publisher_legacy_excel.py b/compendium_v2/publishers/survey_publisher_legacy_excel.py
index 4b7fca892e22e664b99450edaa26b9d28ade8455..30e32ba98651769692b854f936a686dcf1bfec60 100644
--- a/compendium_v2/publishers/survey_publisher_legacy_excel.py
+++ b/compendium_v2/publishers/survey_publisher_legacy_excel.py
@@ -8,20 +8,21 @@ Registered as click cli command when installing compendium-v2.
 
 """
 from __future__ import annotations
-
+import itertools
 import json
 import logging
 import math
-
 import click
+
 from sqlalchemy import select, delete
+from pathlib import Path
+from collections import defaultdict
 
 import compendium_v2
 from compendium_v2.config import load
 from compendium_v2.db import db, presentation_models
 from compendium_v2.environment import setup_logging
 from compendium_v2.publishers import helpers, excel_parser
-from compendium_v2.resources import get_resource_file_path
 from compendium_v2.survey_db import model as survey_model
 
 setup_logging()
@@ -34,6 +35,7 @@ def db_budget_migration(nren_dict):
     data = db.session.scalars(select(survey_model.Nrens))
     db.session.execute(delete(presentation_models.BudgetEntry).where(
         presentation_models.BudgetEntry.year < 2022))
+    inserts = []
     for nren in data:
         for budget in nren.budgets:
             abbrev = nren.abbreviation.upper()
@@ -51,13 +53,13 @@ def db_budget_migration(nren_dict):
                 logger.warning(f'{abbrev} unknown. Skipping.')
                 continue
 
-            budget_entry = presentation_models.BudgetEntry(
-                nren=nren_dict[abbrev],
-                nren_id=nren_dict[abbrev].id,
-                budget=float(budget.budget),
-                year=year
-            )
-            db.session.merge(budget_entry)
+            budget_entry = {
+                'nren': nren_dict[abbrev],
+                'nren_id': nren_dict[abbrev].id,
+                'budget': float(budget.budget),
+                'year': year
+            }
+            inserts.append(budget_entry)
 
     # Import the data from excel sheet to database
     exceldata = excel_parser.fetch_budget_excel_data()
@@ -71,13 +73,14 @@ def db_budget_migration(nren_dict):
             logger.warning(f'{nren} has budget set to >400M EUR for {year}. ({budget})')
             continue
 
-        budget_entry = presentation_models.BudgetEntry(
-            nren=nren_dict[abbrev],
-            nren_id=nren_dict[abbrev].id,
-            budget=budget,
-            year=year
-        )
-        db.session.merge(budget_entry)
+        budget_entry = {
+            'nren': nren_dict[abbrev],
+            'nren_id': nren_dict[abbrev].id,
+            'budget': budget,
+            'year': year
+        }
+        inserts.append(budget_entry)
+    db.session.bulk_insert_mappings(presentation_models.BudgetEntry, inserts)
     db.session.commit()
 
 
@@ -86,7 +89,7 @@ def db_funding_migration(nren_dict):
     data = excel_parser.fetch_funding_excel_data()
     db.session.execute(delete(presentation_models.FundingSource).where(
         presentation_models.FundingSource.year < 2022))
-
+    inserts = []
     for (abbrev, year, client_institution,
             european_funding,
             gov_public_bodies,
@@ -101,17 +104,17 @@ def db_funding_migration(nren_dict):
         if abbrev not in nren_dict:
             logger.warning(f'{abbrev} unknown. Skipping.')
             continue
-
-        budget_entry = presentation_models.FundingSource(
-            nren=nren_dict[abbrev],
-            nren_id=nren_dict[abbrev].id,
-            year=year,
-            client_institutions=client_institution,
-            european_funding=european_funding,
-            gov_public_bodies=gov_public_bodies,
-            commercial=commercial,
-            other=other)
-        db.session.merge(budget_entry)
+        inserts.append({
+            'nren': nren_dict[abbrev],
+            'nren_id': nren_dict[abbrev].id,
+            'year': year,
+            'client_institutions': client_institution,
+            'european_funding': european_funding,
+            'gov_public_bodies': gov_public_bodies,
+            'commercial': commercial,
+            'other': other
+        })
+    db.session.bulk_insert_mappings(presentation_models.FundingSource, inserts)
     db.session.commit()
 
 
@@ -121,43 +124,44 @@ def db_charging_structure_migration(nren_dict):
     db.session.execute(delete(presentation_models.ChargingStructure).where(
         presentation_models.ChargingStructure.year < 2022))
 
+    inserts = []
     for (abbrev, year, charging_structure) in data:
         if abbrev not in nren_dict:
             logger.warning(f'{abbrev} unknown. Skipping.')
             continue
 
-        charging_structure_entry = presentation_models.ChargingStructure(
-            nren=nren_dict[abbrev],
-            nren_id=nren_dict[abbrev].id,
-            year=year,
-            fee_type=charging_structure
-        )
-        db.session.merge(charging_structure_entry)
+        inserts.append({
+            'nren': nren_dict[abbrev],
+            'nren_id': nren_dict[abbrev].id,
+            'year': year,
+            'fee_type': charging_structure
+        })
+    db.session.bulk_insert_mappings(presentation_models.ChargingStructure, inserts)
     db.session.commit()
 
 
 def db_staffing_migration(nren_dict):
-    staff_data = excel_parser.fetch_staffing_excel_data()
-
     db.session.execute(delete(presentation_models.NrenStaff).where(
         presentation_models.NrenStaff.year < 2022))
+    staff_data = list(excel_parser.fetch_staffing_excel_data())
 
     nren_staff_map = {}
+    inserts = []
     for (abbrev, year, permanent_fte, subcontracted_fte) in staff_data:
         if abbrev not in nren_dict:
             logger.warning(f'{abbrev} unknown. Skipping staff data.')
             continue
 
         nren = nren_dict[abbrev]
-        nren_staff_map[(nren.id, year)] = presentation_models.NrenStaff(
-            nren=nren,
-            nren_id=nren.id,
-            year=year,
-            permanent_fte=permanent_fte,
-            subcontracted_fte=subcontracted_fte,
-            technical_fte=0,
-            non_technical_fte=0
-        )
+        nren_staff_map[(nren.id, year)] = {
+            'nren': nren,
+            'nren_id': nren.id,
+            'year': year,
+            'permanent_fte': permanent_fte,
+            'subcontracted_fte': subcontracted_fte,
+            'technical_fte': 0,
+            'non_technical_fte': 0
+        }
 
     function_data = excel_parser.fetch_staff_function_excel_data()
     for (abbrev, year, technical_fte, non_technical_fte) in function_data:
@@ -167,80 +171,98 @@ def db_staffing_migration(nren_dict):
 
         nren = nren_dict[abbrev]
         if (nren.id, year) in nren_staff_map:
-            nren_staff_map[(nren.id, year)].technical_fte = technical_fte
-            nren_staff_map[(nren.id, year)].non_technical_fte = non_technical_fte
+            nren_staff_map[(nren.id, year)]['technical_fte'] = technical_fte
+            nren_staff_map[(nren.id, year)]['non_technical_fte'] = non_technical_fte
         else:
-            nren_staff_map[(nren.id, year)] = presentation_models.NrenStaff(
-                nren=nren,
-                nren_id=nren.id,
-                year=year,
-                permanent_fte=0,
-                subcontracted_fte=0,
-                technical_fte=technical_fte,
-                non_technical_fte=non_technical_fte
-            )
+            nren_staff_map[(nren.id, year)] = {
+                'nren': nren,
+                'nren_id': nren.id,
+                'year': year,
+                'permanent_fte': 0,
+                'subcontracted_fte': 0,
+                'technical_fte': technical_fte,
+                'non_technical_fte': non_technical_fte
+            }
 
     for nren_staff_model in nren_staff_map.values():
-        employed = nren_staff_model.permanent_fte + nren_staff_model.subcontracted_fte
-        technical = nren_staff_model.technical_fte + nren_staff_model.non_technical_fte
+        employed = nren_staff_model['permanent_fte'] + nren_staff_model['subcontracted_fte']
+        technical = nren_staff_model['technical_fte'] + nren_staff_model['non_technical_fte']
         if not math.isclose(employed, technical, abs_tol=0.01) and employed != 0 and technical != 0:
-            logger.warning(f'{nren_staff_model.nren.name} in {nren_staff_model.year}:'
+            logger.warning(f'{nren_staff_model["nren"].name} in {nren_staff_model["year"]}:'
                            f' FTE do not equal across employed/technical categories ({employed} != {technical})')
-
-        db.session.merge(nren_staff_model)
-
+        del nren_staff_model['nren']
+        inserts.append(nren_staff_model)
+    db.session.bulk_insert_mappings(presentation_models.NrenStaff, inserts)
     db.session.commit()
 
 
 def db_ecprojects_migration(nren_dict):
+    db.session.execute(delete(presentation_models.ECProject).where(
+        presentation_models.ECProject.year < 2022))
     ecproject_data = excel_parser.fetch_ecproject_excel_data()
+    inserts = []
     for (abbrev, year, project) in ecproject_data:
         if abbrev not in nren_dict:
             logger.warning(f'{abbrev} unknown. Skipping.')
             continue
 
         nren = nren_dict[abbrev]
-        ecproject_entry = presentation_models.ECProject(nren=nren, nren_id=nren.id, year=year, project=project)
-        db.session.merge(ecproject_entry)
+        inserts.append({
+            'nren_id': nren.id,
+            'year': year,
+            'project': project
+        })
+    db.session.bulk_insert_mappings(presentation_models.ECProject, inserts)
     db.session.commit()
 
 
 def db_organizations_migration(nren_dict):
+    db.session.execute(delete(presentation_models.ParentOrganization).where(
+        presentation_models.ParentOrganization.year < 2022))
     organization_data = excel_parser.fetch_organization_excel_data()
+    inserts = []
     for (abbrev, year, org) in organization_data:
         if abbrev not in nren_dict:
             logger.warning(f'{abbrev} unknown. Skipping.')
             continue
 
         nren = nren_dict[abbrev]
-        org_entry = presentation_models.ParentOrganization(nren=nren, nren_id=nren.id, year=year, organization=org)
-        db.session.merge(org_entry)
+        inserts.append({
+            'nren_id': nren.id,
+            'year': year,
+            'organization': org
+        })
+    db.session.bulk_insert_mappings(presentation_models.ParentOrganization, inserts)
     db.session.commit()
 
 
 def db_traffic_volume_migration(nren_dict):
+    db.session.execute(delete(presentation_models.TrafficVolume).where(
+        presentation_models.TrafficVolume.year < 2023))
     traffic_data = excel_parser.fetch_traffic_excel_data()
+    inserts = []
     for (abbrev, year, from_external, to_external, from_customers, to_customers) in traffic_data:
         if abbrev not in nren_dict:
             logger.warning(f'{abbrev} unknown. Skipping.')
             continue
 
         nren = nren_dict[abbrev]
-        traffic_entry = presentation_models.TrafficVolume(
-            nren=nren,
-            nren_id=nren.id,
-            year=year,
-            from_customers=from_customers,
-            to_customers=to_customers,
-            from_external=from_external,
-            to_external=to_external
-        )
-        db.session.merge(traffic_entry)
+        inserts.append({
+            'nren_id': nren.id,
+            'year': year,
+            'from_customers': from_customers,
+            'to_customers': to_customers,
+            'from_external': from_external,
+            'to_external': to_external
+        })
+
+    db.session.bulk_insert_mappings(presentation_models.TrafficVolume, inserts)
     db.session.commit()
 
 
 def db_services_migration():
-    with open(get_resource_file_path('nren_services.json')) as f:
+    nren_services = Path(__file__).parent.parent / 'resources' / 'nren_services.json'
+    with open(nren_services) as f:
         services = json.load(f)
         for name_key, record in services.items():
             service = presentation_models.Service(
@@ -282,72 +304,76 @@ def db_nren_services_migration(nren_dict):
 
 
 def db_connected_proportion_migration(nren_dict):
+    db.session.execute(delete(presentation_models.ConnectedProportion).where(
+        presentation_models.ConnectedProportion.year < 2023))
     remit = excel_parser.fetch_remit_excel_data()
     nr_connected = excel_parser.fetch_nr_connected_excel_data()
     market_share = excel_parser.fetch_market_share_excel_data()
     users_served = excel_parser.fetch_users_served_excel_data()
 
-    all_entry_keys = set()
-    all_entry_keys.update(remit.keys())
-    all_entry_keys.update(nr_connected.keys())
-    all_entry_keys.update(market_share.keys())
-    all_entry_keys.update(users_served.keys())
+    data_by_nren = defaultdict(dict)
 
-    for key in all_entry_keys:
+    for key in itertools.chain(remit.keys(), nr_connected.keys(), market_share.keys(), users_served.keys()):
         (abbrev, year, user_category) = key
         if abbrev not in nren_dict:
             logger.warning(f'{abbrev} unknown. Skipping.')
             continue
 
         nren = nren_dict[abbrev]
-        connected_proportion = presentation_models.ConnectedProportion(
-            nren=nren,
-            nren_id=nren.id,
-            year=year,
-            user_category=user_category,
-            coverage=remit.get(key),
-            number_connected=nr_connected.get(key),
-            market_share=market_share.get(key),
-            users_served=users_served.get(key)
-        )
-        db.session.merge(connected_proportion)
-
+        to_add = (nren.id, year, user_category)
+        data_by_nren[to_add].update({
+            'nren_id': nren.id,
+            'year': year,
+            'user_category': user_category,
+            'coverage': remit.get(key),
+            'number_connected': nr_connected.get(key, data_by_nren[to_add].get('number_connected')),
+            'market_share': market_share.get(key, data_by_nren[to_add].get('market_share')),
+            'users_served': users_served.get(key, data_by_nren[to_add].get('users_served'))
+        })
+
+    inserts = list(data_by_nren.values())
+
+    db.session.bulk_insert_mappings(presentation_models.ConnectedProportion, inserts)
     db.session.commit()
 
 
 def db_connectivity_level_migration(nren_dict):
+    db.session.execute(delete(presentation_models.ConnectivityLevel).where(
+        presentation_models.ConnectivityLevel.year < 2023))
     typical_speed = excel_parser.fetch_typical_speed_excel_data()
     highest_speed = excel_parser.fetch_highest_speed_excel_data()
     highest_speed_proportion = excel_parser.fetch_highest_speed_proportion_excel_data()
+    data_by_nren = defaultdict(dict)
 
-    all_entry_keys = set()
-    all_entry_keys.update(typical_speed.keys())
-    all_entry_keys.update(highest_speed.keys())
-    all_entry_keys.update(highest_speed_proportion.keys())
-
-    for key in all_entry_keys:
+    for key in itertools.chain(typical_speed.keys(), highest_speed.keys(), highest_speed_proportion.keys()):
         (abbrev, year, user_category) = key
         if abbrev not in nren_dict:
             logger.warning(f'{abbrev} unknown. Skipping.')
             continue
 
         nren = nren_dict[abbrev]
-        connected_proportion = presentation_models.ConnectivityLevel(
-            nren=nren,
-            nren_id=nren.id,
-            year=year,
-            user_category=user_category,
-            typical_speed=typical_speed.get(key),
-            highest_speed=highest_speed.get(key),
-            highest_speed_proportion=highest_speed_proportion.get(key)
-        )
-        db.session.merge(connected_proportion)
-
+        to_add = (nren.id, year, user_category)
+        data_by_nren[to_add].update({
+            'nren_id': nren.id,
+            'year': year,
+            'user_category': user_category,
+            'typical_speed': typical_speed.get(key, data_by_nren[to_add].get('typical_speed')),
+            'highest_speed': highest_speed.get(key, data_by_nren[to_add].get('highest_speed')),
+            'highest_speed_proportion': highest_speed_proportion.get(
+                key, data_by_nren[to_add].get('highest_speed_proportion'))
+        })
+
+    inserts = list(data_by_nren.values())
+
+    db.session.bulk_insert_mappings(presentation_models.ConnectivityLevel, inserts)
     db.session.commit()
 
 
 def db_connection_carrier_migration(nren_dict):
+    db.session.execute(delete(presentation_models.ConnectionCarrier).where(
+        presentation_models.ConnectionCarrier.year < 2023))
     carriers = excel_parser.fetch_carriers_excel_data()
+    inserts = []
     for key, carry_mechanism in carriers.items():
         (abbrev, year, user_category) = key
         if abbrev not in nren_dict:
@@ -355,20 +381,21 @@ def db_connection_carrier_migration(nren_dict):
             continue
 
         nren = nren_dict[abbrev]
-        connection_carrier = presentation_models.ConnectionCarrier(
-            nren=nren,
-            nren_id=nren.id,
-            year=year,
-            user_category=user_category,
-            carry_mechanism=carry_mechanism
-        )
-        db.session.merge(connection_carrier)
-
+        inserts.append({
+            'nren_id': nren.id,
+            'year': year,
+            'user_category': user_category,
+            'carry_mechanism': carry_mechanism
+        })
+    db.session.bulk_insert_mappings(presentation_models.ConnectionCarrier, inserts)
     db.session.commit()
 
 
 def db_connectivity_growth_migration(nren_dict):
+    db.session.execute(delete(presentation_models.ConnectivityGrowth).where(
+        presentation_models.ConnectivityGrowth.year < 2023))
     growth = excel_parser.fetch_growth_excel_data()
+    inserts = []
     for key, growth_percent in growth.items():
         (abbrev, year, user_category) = key
         if abbrev not in nren_dict:
@@ -376,26 +403,26 @@ def db_connectivity_growth_migration(nren_dict):
             continue
 
         nren = nren_dict[abbrev]
-        connectivity_growth = presentation_models.ConnectivityGrowth(
-            nren=nren,
-            nren_id=nren.id,
-            year=year,
-            user_category=user_category,
-            growth=growth_percent
-        )
-        db.session.merge(connectivity_growth)
-
+        inserts.append({
+            'nren_id': nren.id,
+            'year': year,
+            'user_category': user_category,
+            'growth': growth_percent
+        })
+    db.session.bulk_insert_mappings(presentation_models.ConnectivityGrowth, inserts)
     db.session.commit()
 
 
 def db_connectivity_load_migration(nren_dict):
+    db.session.execute(delete(presentation_models.ConnectivityLoad).where(
+        presentation_models.ConnectivityLoad.year < 2023))
     average = excel_parser.fetch_average_traffic_excel_data()
     peak = excel_parser.fetch_peak_traffic_excel_data()
 
     all_entry_keys = set()
     all_entry_keys.update(average.keys())
     all_entry_keys.update(peak.keys())
-
+    inserts = []
     for key in all_entry_keys:
         (abbrev, year, user_category) = key
         if abbrev not in nren_dict:
@@ -403,23 +430,24 @@ def db_connectivity_load_migration(nren_dict):
             continue
 
         nren = nren_dict[abbrev]
-        connectivity_load = presentation_models.ConnectivityLoad(
-            nren=nren,
-            nren_id=nren.id,
-            year=year,
-            user_category=user_category,
-            average_load_from_institutions=average.get(key, (None, None))[0],
-            average_load_to_institutions=average.get(key, (None, None))[1],
-            peak_load_from_institutions=peak.get(key, (None, None))[0],
-            peak_load_to_institutions=peak.get(key, (None, None))[1]
-        )
-        db.session.merge(connectivity_load)
-
+        inserts.append({
+            'nren_id': nren.id,
+            'year': year,
+            'user_category': user_category,
+            'average_load_from_institutions': average.get(key, (None, None))[0],
+            'average_load_to_institutions': average.get(key, (None, None))[1],
+            'peak_load_from_institutions': peak.get(key, (None, None))[0],
+            'peak_load_to_institutions': peak.get(key, (None, None))[1]
+        })
+    db.session.bulk_insert_mappings(presentation_models.ConnectivityLoad, inserts)
     db.session.commit()
 
 
 def db_remote_campuses_migration(nren_dict):
+    db.session.execute(delete(presentation_models.RemoteCampuses).where(
+        presentation_models.RemoteCampuses.year < 2023))
     campuses = excel_parser.fetch_remote_campuses_excel_data()
+    inserts = []
     for (abbrev, year, connectivity, country, connected_to_r_e) in campuses:
         if abbrev not in nren_dict:
             logger.warning(f'{abbrev} unknown. Skipping.')
@@ -430,134 +458,147 @@ def db_remote_campuses_migration(nren_dict):
             connections.append({'country': country, 'local_r_and_e_connection': connected_to_r_e})
 
         nren = nren_dict[abbrev]
-        new_entry = presentation_models.RemoteCampuses(
-            nren=nren,
-            nren_id=nren.id,
-            year=year,
-            remote_campus_connectivity=connectivity,
-            connections=connections
-        )
-        db.session.merge(new_entry)
-
+        inserts.append({
+            'nren_id': nren.id,
+            'year': year,
+            'remote_campus_connectivity': connectivity,
+            'connections': connections
+        })
+    db.session.bulk_insert_mappings(presentation_models.RemoteCampuses, inserts)
     db.session.commit()
 
 
 def db_dark_fibre_lease_migration(nren_dict):
+    db.session.execute(delete(presentation_models.DarkFibreLease).where(
+        presentation_models.DarkFibreLease.year < 2023))
     data_rows = excel_parser.fetch_dark_fibre_iru_excel_data()
     iru_duration = excel_parser.fetch_iru_duration_excel_data()
-
+    inserts = []
     for (abbrev, year, iru, length_in_country, length_out_country) in data_rows:
         if abbrev not in nren_dict:
             logger.warning(f'{abbrev} unknown. Skipping.')
             continue
 
         nren = nren_dict[abbrev]
-        new_entry = presentation_models.DarkFibreLease(
-            nren=nren,
-            nren_id=nren.id,
-            year=year,
-            iru_or_lease=iru,
-            fibre_length_in_country=length_in_country,
-            fibre_length_outside_country=length_out_country,
-            iru_duration=iru_duration.get((abbrev, year))
-        )
-        db.session.merge(new_entry)
+        inserts.append({
+            'nren_id': nren.id,
+            'year': year,
+            'iru_or_lease': iru,
+            'fibre_length_in_country': length_in_country,
+            'fibre_length_outside_country': length_out_country,
+            'iru_duration': iru_duration.get((abbrev, year))
+        })
+    db.session.bulk_insert_mappings(presentation_models.DarkFibreLease, inserts)
     db.session.commit()
 
 
 def db_dark_fibre_installed_migration(nren_dict):
+    db.session.execute(delete(presentation_models.DarkFibreInstalled).where(
+        presentation_models.DarkFibreInstalled.year < 2023))
     data_rows = excel_parser.fetch_dark_fibre_installed_excel_data()
+    inserts = []
     for (abbrev, year, installed, length) in data_rows:
         if abbrev not in nren_dict:
             logger.warning(f'{abbrev} unknown. Skipping.')
             continue
 
         nren = nren_dict[abbrev]
-        new_entry = presentation_models.DarkFibreInstalled(
-            nren=nren,
-            nren_id=nren.id,
-            year=year,
-            installed=installed,
-            fibre_length_in_country=length
-        )
-        db.session.merge(new_entry)
+        inserts.append({
+            'nren_id': nren.id,
+            'year': year,
+            'installed': installed,
+            'fibre_length_in_country': length
+        })
+    db.session.bulk_insert_mappings(presentation_models.DarkFibreInstalled, inserts)
     db.session.commit()
 
 
 def db_passive_monitoring_migration(nren_dict):
+    db.session.execute(delete(presentation_models.PassiveMonitoring).where(
+        presentation_models.PassiveMonitoring.year < 2023))
     data_rows = excel_parser.fetch_passive_monitoring_excel_data()
+    inserts = []
     for (abbrev, year, monitoring, method) in data_rows:
         if abbrev not in nren_dict:
             logger.warning(f'{abbrev} unknown. Skipping.')
             continue
 
         nren = nren_dict[abbrev]
-        new_entry = presentation_models.PassiveMonitoring(
-            nren=nren,
-            nren_id=nren.id,
-            year=year,
-            monitoring=monitoring,
-            method=method
-        )
-        db.session.merge(new_entry)
+        inserts.append({
+            'nren_id': nren.id,
+            'year': year,
+            'monitoring': monitoring,
+            'method': method
+        })
+    db.session.bulk_insert_mappings(presentation_models.PassiveMonitoring, inserts)
     db.session.commit()
 
 
 def db_capacity_migration(nren_dict):
+    db.session.execute(delete(presentation_models.Capacity).where(
+        presentation_models.Capacity.year < 2023))
     largest_data_rows = excel_parser.fetch_largest_link_capacity_excel_data()
     typical_data_rows = excel_parser.fetch_typical_backbone_capacity_excel_data()
 
-    for abbrev, year in largest_data_rows.keys() | typical_data_rows.keys():
+    by_nren = defaultdict(dict)
+
+    for key in itertools.chain(largest_data_rows.keys(), typical_data_rows.keys()):
+        (abbrev, year) = key
         if abbrev not in nren_dict:
             logger.warning(f'{abbrev} unknown. Skipping.')
             continue
 
         nren = nren_dict[abbrev]
-        new_entry = presentation_models.Capacity(
-            nren=nren,
-            nren_id=nren.id,
-            year=year,
-            largest_link_capacity=largest_data_rows.get((abbrev, year)),
-            typical_backbone_capacity=typical_data_rows.get((abbrev, year))
-        )
-        db.session.merge(new_entry)
+        to_add = (nren.id, year)
+        by_nren[to_add].update({
+            'nren_id': nren.id,
+            'year': year,
+            'largest_link_capacity': largest_data_rows.get(key, by_nren[to_add].get('largest_link_capacity')),
+            'typical_backbone_capacity': typical_data_rows.get(key, by_nren[to_add].get('typical_backbone_capacity'))
+        })
+    inserts = list(by_nren.values())
+    db.session.bulk_insert_mappings(presentation_models.Capacity, inserts)
     db.session.commit()
 
 
 def db_non_r_e_peers_migration(nren_dict):
+    db.session.execute(delete(presentation_models.NonREPeers).where(
+        presentation_models.NonREPeers.year < 2023))
     data_rows = excel_parser.fetch_non_r_e_peers_excel_data()
+    inserts = []
     for (abbrev, year, nr_of_non_r_and_e_peers) in data_rows:
         if abbrev not in nren_dict:
             logger.warning(f'{abbrev} unknown. Skipping.')
             continue
 
         nren = nren_dict[abbrev]
-        new_entry = presentation_models.NonREPeers(
-            nren=nren,
-            nren_id=nren.id,
-            year=year,
-            nr_of_non_r_and_e_peers=nr_of_non_r_and_e_peers
-        )
-        db.session.merge(new_entry)
+        inserts.append({
+            'nren_id': nren.id,
+            'year': year,
+            'nr_of_non_r_and_e_peers': nr_of_non_r_and_e_peers
+        })
+    db.session.bulk_insert_mappings(presentation_models.NonREPeers, inserts)
     db.session.commit()
 
 
 def db_ops_automation_migration(nren_dict):
+    db.session.execute(delete(presentation_models.OpsAutomation).where(
+        presentation_models.OpsAutomation.year < 2023))
     data_rows = excel_parser.fetch_ops_automation_excel_data()
+    inserts = []
     for (abbrev, year, automation, specifics) in data_rows:
         if abbrev not in nren_dict:
             logger.warning(f'{abbrev} unknown. Skipping.')
             continue
 
         nren = nren_dict[abbrev]
-        new_entry = presentation_models.OpsAutomation(
-            nren=nren,
-            nren_id=nren.id,
-            year=year,
-            ops_automation=automation,
-            ops_automation_specifics=specifics
-        )
-        db.session.merge(new_entry)
+        inserts.append({
+            'nren_id': nren.id,
+            'year': year,
+            'ops_automation': automation,
+            'ops_automation_specifics': specifics
+        })
+    db.session.bulk_insert_mappings(presentation_models.OpsAutomation, inserts)
     db.session.commit()
 
 
diff --git a/compendium_v2/publishers/utils.py b/compendium_v2/publishers/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..04b1f66d1447522d64b8e1708e111125b850b46a
--- /dev/null
+++ b/compendium_v2/publishers/utils.py
@@ -0,0 +1,34 @@
+from decimal import Decimal
+
+
+def int_or_none(answers_dict, key):
+    if key in answers_dict:
+        value = answers_dict[key]
+        if isinstance(value, str):
+            value = value.replace(",", ".")
+        return int(answers_dict[key])
+    return None
+
+
+def decimal_or_none(answers_dict, key):
+    if key in answers_dict:
+        value = answers_dict[key]
+        if isinstance(value, str):
+            value = value.replace(",", ".")
+        return Decimal(value)
+    return None
+
+
+def decimal_or_zero(answers_dict, key):
+    value = answers_dict.get(key, 0)
+    if isinstance(value, str):
+        value = value.replace(",", ".")
+    return Decimal(value)
+
+
+def bool_or_none(answer, key=None):
+    if key:
+        answer = answer.get(key)
+    if answer:
+        return answer == "Yes"
+    return None
diff --git a/compendium_v2/publishers/year/__init__.py b/compendium_v2/publishers/year/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/compendium_v2/publishers/year/map_2023.py b/compendium_v2/publishers/year/map_2023.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a17ef5f4308b951ca817664aab3204c635b2d93
--- /dev/null
+++ b/compendium_v2/publishers/year/map_2023.py
@@ -0,0 +1,826 @@
+from decimal import Decimal
+from typing import List, Dict, Any, Type
+from collections import defaultdict
+from compendium_v2.db.presentation_models import BudgetEntry, ChargingStructure, ECProject, ExternalConnections, \
+    InstitutionURLs, NrenStaff, ParentOrganization, Policy, SubOrganization, TrafficVolume, ExternalConnection, \
+    FundingSource, CentralProcurement, ServiceManagement, ServiceUserTypes, EOSCListings, \
+    Standards, CrisisExercises, SecurityControls, ConnectedProportion, ConnectivityLevel, \
+    ConnectionCarrier, ConnectivityLoad, ConnectivityGrowth, CommercialConnectivity, \
+    CommercialChargingLevel, RemoteCampuses, DarkFibreLease, DarkFibreInstalled, FibreLight, \
+    NetworkMapUrls, MonitoringTools, PassiveMonitoring, TrafficStatistics, SiemVendors, \
+    CertificateProviders, WeatherMap, PertTeam, AlienWave, Capacity, NonREPeers, TrafficRatio, \
+    OpsAutomation, NetworkFunctionVirtualisation, NetworkAutomation, NRENService, NREN, PresentationModel
+from compendium_v2.db.presentation_model_enums import CarryMechanism, CommercialCharges, YesNoPlanned, \
+    CommercialConnectivityCoverage, ConnectivityCoverage, FeeType, MonitoringMethod, ServiceCategory, UserCategory
+from compendium_v2.db.survey_models import SurveyResponse
+from compendium_v2.publishers.utils import decimal_or_zero, decimal_or_none, bool_or_none, int_or_none
+
+
+def map_budget_entry(year, nren, answers):
+    budget = answers.get("budget")
+    if budget:
+        # replace comma with dot for decimal, surveyjs allows commas for decimals (common in EU countries)
+        budget = budget.replace(",", ".")
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'budget': Decimal(budget)
+        }
+
+
+def map_fundingsource_entry(year, nren, answers):
+    funding_source = answers.get("income_sources")
+    if funding_source:
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'client_institutions': decimal_or_zero(funding_source, "client_institutions"),
+            'european_funding': decimal_or_zero(funding_source, "european_funding"),
+            'gov_public_bodies': decimal_or_zero(funding_source, "gov_public_bodies"),
+            'commercial': decimal_or_zero(funding_source, "commercial"),
+            'other': decimal_or_zero(funding_source, "other")
+        }
+
+
+def map_charging_structure_entry(year, nren, answers):
+    charging = answers.get("charging_mechanism")
+    if charging:
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'fee_type': FeeType[charging]
+        }
+
+
+def map_nren_staff(year, nren, answers):
+    staff_roles = answers.get("staff_roles", {})
+    staff_employment_type = answers.get("staff_employment_type", {})
+    if staff_roles or staff_employment_type:
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'permanent_fte': decimal_or_zero(staff_employment_type, "permanent_fte"),
+            'subcontracted_fte': decimal_or_zero(staff_employment_type, "subcontracted_fte"),
+            'technical_fte': decimal_or_zero(staff_roles, "technical_fte"),
+            'non_technical_fte': decimal_or_zero(staff_roles, "nontechnical_fte")
+        }
+
+
+def map_parent_orgs(nren: NREN, year: int, answers: Dict[str, Any]):
+    has_parent = answers.get("parent_organization") == "Yes"
+    parent = answers.get("parent_organization_name")
+    if has_parent and parent:
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'organization': parent
+        }
+
+
+def map_sub_orgs(nren: NREN, year: int, answers: Dict[str, Any]):
+    has_subs = answers.get("suborganizations") == 'Yes'
+    subs = answers.get("suborganization_details")
+    if has_subs and subs:
+        for sub in subs:
+            role = sub.get("suborganization_role", "")
+            if role == "other":
+                role = sub.get("suborganization_role-Comment", "")
+            yield {
+                'nren_id': nren.id,
+                'year': year,
+                'organization': sub.get("suborganization_name"),
+                'role': role
+            }
+
+
+def map_ec_projects(nren: NREN, year: int, answers: Dict[str, Any]):
+    has_ec_projects = answers.get("ec_projects") == "Yes"
+    ec_projects = answers.get("ec_project_names")
+    if has_ec_projects and ec_projects:
+        for ec_project in ec_projects:
+            if ec_project:
+                yield {
+                    'nren_id': nren.id,
+                    'year': year,
+                    'project': ec_project.get("ec_project_name")
+                }
+
+
+def map_policy(nren: NREN, year: int, answers: Dict[str, Any]):
+    strategy = answers.get("corporate_strategy_url", "")
+    policies = answers.get("policies", {})
+    if strategy or policies:
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'strategic_plan': strategy,
+            'environmental': policies.get("environmental_policy", {}).get("url", ""),
+            'equal_opportunity': policies.get("equal_opportunity_policy", {}).get("url", ""),
+            'connectivity': policies.get("connectivity_policy", {}).get("url", ""),
+            'acceptable_use': policies.get("acceptable_use_policy", {}).get("url", ""),
+            'privacy_notice': policies.get("privacy_notice", {}).get("url", ""),
+            'data_protection': policies.get("data_protection_contact", {}).get("url", ""),
+            'gender_equality': policies.get("gender_equality_policy", {}).get("url", "")
+        }
+
+
+def map_traffic_volume(nren: NREN, year: int, answers: Dict[str, Any]):
+    traffic_estimate = answers.get("traffic_estimate")
+    if traffic_estimate:
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'to_customers': decimal_or_zero(traffic_estimate, "to_customers"),
+            'from_customers': decimal_or_zero(traffic_estimate, "from_customers"),
+            'to_external': decimal_or_zero(traffic_estimate, "to_external"),
+            'from_external': decimal_or_zero(traffic_estimate, "from_external")
+        }
+
+
+def map_institution_urls(nren: NREN, year: int, answers: Dict[str, Any]):
+    institution_urls = answers.get("connected_sites_lists")
+    if institution_urls:
+        urls = [i.get("connected_sites_url", "")
+                for i in institution_urls if i.get("connected_sites_url", "")]
+        if urls:
+            return {
+                'nren_id': nren.id,
+                'year': year,
+                'urls': urls
+            }
+
+
+def map_central_procurement(nren: NREN, year: int, answers: Dict[str, Any]):
+    central_procurement = answers.get("central_software_procurement") == "Yes"
+    if central_procurement:
+        central_procurement_amount = decimal_or_none(answers, "central_procurement_amount")
+    else:
+        central_procurement_amount = None
+    return {
+        'nren_id': nren.id,
+        'year': year,
+        'central_procurement': central_procurement,
+        'amount': central_procurement_amount
+    }
+
+
+def map_service_management(nren: NREN, year: int, answers: Dict[str, Any]):
+    formal_service_management_framework = answers.get("formal_service_management_framework")
+    service_level_targets = answers.get("service_level_targets")
+    if formal_service_management_framework or service_level_targets:
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'service_management_framework': bool_or_none(formal_service_management_framework),
+            'service_level_targets': bool_or_none(service_level_targets)
+        }
+
+
+def map_service_user_types(nren: NREN, year: int, answers: Dict[str, Any]):
+    service_type_matrix = answers.get("service_matrix", {})
+    for user_type, service_types in service_type_matrix.items():
+        user_type_enum = UserCategory[user_type]
+        service_types_list = service_types["service_types"]
+        for service_type in service_types_list:
+            service_type_enum = ServiceCategory[service_type]
+            yield {
+                'nren_id': nren.id,
+                'year': year,
+                'user_category': user_type_enum,
+                'service_category': service_type_enum
+            }
+
+
+def map_eosc_listings(nren: NREN, year: int, answers: Dict[str, Any]):
+    has_eosc_listings = answers.get("service_portfolio_eosc_portal") == "Yes"
+    services_on_eosc_portal_list = answers.get("services_on_eosc_portal_list", [])
+    eosc_list = [i.get("service_name") for i in services_on_eosc_portal_list if i.get("service_name")]
+    if has_eosc_listings and eosc_list:
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'service_names': eosc_list
+        }
+
+
+def map_standards(nren: NREN, year: int, answers: Dict[str, Any]):
+    audits = answers.get("audits")
+    business_continuity_plans = answers.get("business_continuity_plans")
+    crisis_management_procedure = answers.get("crisis_management_procedure")
+    if audits or business_continuity_plans or crisis_management_procedure:
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'audits': bool_or_none(audits),
+            'audit_specifics': answers.get("audit_specifics", ""),
+            'business_continuity_plans': bool_or_none(business_continuity_plans),
+            'business_continuity_plans_specifics': answers.get("business_continuity_plans_specifics", ""),
+            'crisis_management_procedure': bool_or_none(crisis_management_procedure)
+        }
+
+
+def map_crisis_exercises(nren: NREN, year: int, answers: Dict[str, Any]):
+    crisis_exercises = answers.get("crisis_exercises")
+    if crisis_exercises:
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'exercise_descriptions': crisis_exercises
+        }
+
+
+def map_security_controls(nren: NREN, year: int, answers: Dict[str, Any]):
+    security_controls = answers.get("security_controls")
+    if security_controls:
+        if "other" in security_controls:
+            security_controls.remove("other")
+            security_controls.append(answers.get("security_controls-Comment", "other"))
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'security_control_descriptions': security_controls
+        }
+
+
+def map_connected_proportion(nren: NREN, year: int, answers: Dict[str, Any]):
+    connectivity_proportions = answers.get("connectivity_proportions", {})
+    for user_type, connectivity_proportion in connectivity_proportions.items():
+        user_type_enum = UserCategory[user_type]
+        coverage = connectivity_proportion.get("covered")
+        coverage_enum = ConnectivityCoverage[coverage] if coverage else None
+        number_connected = int_or_none(connectivity_proportion, "nr_connected")
+        market_share = decimal_or_none(connectivity_proportion, "market_share_percentage")
+        users_served = int_or_none(connectivity_proportion, "nr_of_users")
+        yield {
+            'nren_id': nren.id,
+            'year': year,
+            'user_category': user_type_enum,
+            'coverage': coverage_enum,
+            'number_connected': number_connected,
+            'market_share': market_share,
+            'users_served': users_served
+        }
+
+
+def map_connectivity_levels(nren: NREN, year: int, answers: Dict[str, Any]):
+    connectivity_levels = answers.get("connectivity_level", {})
+    for user_type, connectivity_level in connectivity_levels.items():
+        user_type_enum = UserCategory[user_type]
+        typical_speed = int_or_none(connectivity_level, "typical_speed")
+        highest_speed = int_or_none(connectivity_level, "highest_speed")
+        highest_speed_proportion = decimal_or_none(connectivity_level, "highest_speed_connection_percentage")
+        yield {
+            'nren_id': nren.id,
+            'year': year,
+            'user_category': user_type_enum,
+            'typical_speed': typical_speed,
+            'highest_speed': highest_speed,
+            'highest_speed_proportion': highest_speed_proportion
+        }
+
+
+def map_connection_carriers(nren: NREN, year: int, answers: Dict[str, Any]):
+    traffic_carriers = answers.get("traffic_carriers", {})
+    for user_type, traffic_carrier in traffic_carriers.items():
+        user_type_enum = UserCategory[user_type]
+        carry_mechanism = traffic_carrier.get("carry_mechanism")
+        if carry_mechanism:
+            yield {
+                'nren_id': nren.id,
+                'year': year,
+                'user_category': user_type_enum,
+                'carry_mechanism': CarryMechanism[carry_mechanism]
+            }
+
+
+def map_connectivity_loads(nren: NREN, year: int, answers: Dict[str, Any]):
+    traffic_loads = answers.get("traffic_load", {})
+    for user_type, traffic_load in traffic_loads.items():
+        user_type_enum = UserCategory[user_type]
+        yield {
+            'nren_id': nren.id,
+            'year': year,
+            'user_category': user_type_enum,
+            'average_load_from_institutions': int_or_none(traffic_load, "average_from_institutions_to_network"),
+            'average_load_to_institutions': int_or_none(traffic_load, "average_to_institutions_from_network"),
+            'peak_load_from_institutions': int_or_none(traffic_load, "peak_from_institutions_to_network"),
+            'peak_load_to_institutions': int_or_none(traffic_load, "peak_to_institutions_from_network")
+        }
+
+
+def map_connectivity_growth(nren: NREN, year: int, answers: Dict[str, Any]):
+    traffic_growths = answers.get("traffic_growth", {})
+    for user_type, traffic_growth in traffic_growths.items():
+        user_type_enum = UserCategory[user_type]
+        yield {
+            'nren_id': nren.id,
+            'year': year,
+            'user_category': user_type_enum,
+            'growth': decimal_or_zero(traffic_growth, "growth_rate")
+        }
+
+
+def map_commercial_connectivity(nren: NREN, year: int, answers: Dict[str, Any]):
+    commercial_organizations = answers.get("commercial_organizations")
+    if commercial_organizations:
+        c1 = commercial_organizations.get("commercial_r_e", {}).get("connection")
+        c2 = commercial_organizations.get("commercial_general", {}).get("connection")
+        c3 = commercial_organizations.get("commercial_collaboration", {}).get("connection")
+        c4 = commercial_organizations.get("commercial_service_provider", {}).get("connection")
+        c5 = commercial_organizations.get("university_spin_off", {}).get("connection")
+        yield {
+            'nren_id': nren.id,
+            'year': year,
+            'commercial_r_and_e': CommercialConnectivityCoverage[c1] if c1 else None,
+            'commercial_general': CommercialConnectivityCoverage[c2] if c2 else None,
+            'commercial_collaboration': CommercialConnectivityCoverage[c3] if c3 else None,
+            'commercial_service_provider': CommercialConnectivityCoverage[c4] if c4 else None,
+            'university_spin_off': CommercialConnectivityCoverage[c5] if c5 else None,
+        }
+
+
+def map_commercial_charging_levels(nren: NREN, year: int, answers: Dict[str, Any]):
+    commercial_charging_levels = answers.get("commercial_charging_levels")
+    if commercial_charging_levels:
+        c1 = commercial_charging_levels.get("collaboration", {}).get("charging_level")
+        c2 = commercial_charging_levels.get("services", {}).get("charging_level")
+        c3 = commercial_charging_levels.get("peering", {}).get("charging_level")
+        yield {
+            'nren_id': nren.id,
+            'year': year,
+            'collaboration': CommercialCharges[c1] if c1 else None,
+            'service_supplier': CommercialCharges[c2] if c2 else None,
+            'direct_peering': CommercialCharges[c3] if c3 else None,
+        }
+
+
+def map_remote_campuses(nren: NREN, year: int, answers: Dict[str, Any]):
+    remote_campuses = answers.get("remote_campuses")
+    if remote_campuses:
+        remote_campuses = remote_campuses == "Yes"
+        remote_campuses_specifics = []
+        if remote_campuses:
+            remote_campuses_specifics = [
+                {"country": i.get("country", ""), "local_r_and_e_connection": bool_or_none(i, "connected")}
+                for i in answers.get("remote_campuses_specifics", [])
+            ]
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'remote_campus_connectivity': remote_campuses,
+            'connections': remote_campuses_specifics
+        }
+
+
+def map_dark_fibre_lease(nren: NREN, year: int, answers: Dict[str, Any]):
+    dark_fibre_lease = answers.get("dark_fibre_lease") == "Yes"
+    if dark_fibre_lease:
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'iru_or_lease': dark_fibre_lease,
+            'fibre_length_in_country': int_or_none(answers, "dark_fibre_lease_kilometers_inside_country"),
+            'fibre_length_outside_country': int_or_none(answers, "dark_fibre_lease_kilometers_outside_country"),
+            'iru_duration': decimal_or_none(answers, "dark_fibre_lease_duration")
+        }
+
+
+def map_dark_fibre_installed(nren: NREN, year: int, answers: Dict[str, Any]):
+    dark_fibre_nren = answers.get("dark_fibre_nren") == "Yes"
+    if dark_fibre_nren:
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'installed': dark_fibre_nren,
+            'fibre_length_in_country': int_or_none(answers, "dark_fibre_nren_kilometers_inside_country")
+        }
+
+
+def map_fibre_light(nren: NREN, year: int, answers: Dict[str, Any]):
+    fibre_light = answers.get("fibre_light")
+    if fibre_light:
+        if fibre_light == "other":
+            fibre_light = answers.get("fibre_light-Comment", "other")
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'light_description': fibre_light
+        }
+
+
+def map_monitoring_tools(nren: NREN, year: int, answers: Dict[str, Any]):
+    monitoring_tools = answers.get("monitoring_tools", [])
+    netflow_vendors = answers.get("netflow_vendors", "")
+    if monitoring_tools or netflow_vendors:
+        if "other" in monitoring_tools:
+            monitoring_tools.remove("other")
+            monitoring_tools.append(answers.get("monitoring_tools-Comment", "other"))
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'tool_descriptions': monitoring_tools,
+            'netflow_processing_description': netflow_vendors
+        }
+
+
+def map_passive_monitoring(nren: NREN, year: int, answers: Dict[str, Any]):
+    passive_monitoring = answers.get("passive_monitoring")
+    if passive_monitoring:
+        passive_monitoring = passive_monitoring == "Yes"
+        passive_monitoring_tech = answers.get("passive_monitoring_tech")
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'monitoring': passive_monitoring,
+            'method': MonitoringMethod[passive_monitoring_tech] if passive_monitoring_tech else None
+        }
+
+
+def map_traffic_statistics(nren: NREN, year: int, answers: Dict[str, Any]):
+    traffic_statistics = answers.get("traffic_statistics")
+    if traffic_statistics:
+        traffic_statistics = traffic_statistics == "Yes"
+        urls = answers.get("traffic_statistics_urls", [])
+        urls = [i.get("traffic_statistics_url", "") for i in urls if i.get("traffic_statistics_url")]
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'traffic_statistics': traffic_statistics,
+            'urls': urls
+        }
+
+
+def map_siem_vendors(nren: NREN, year: int, answers: Dict[str, Any]):
+    siem_soc_vendor = answers.get("siem_soc_vendor")
+    if siem_soc_vendor:
+        if "other" in siem_soc_vendor:
+            siem_soc_vendor.remove("other")
+            siem_soc_vendor.append(answers.get("siem_soc_vendor-Comment", "other"))
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'vendor_names': siem_soc_vendor
+        }
+
+
+def map_network_map_urls(nren: NREN, year: int, answers: Dict[str, Any]):
+    network_map_urls = answers.get("network_map_urls", [])
+    urls = [i.get("network_map_url", "") for i in network_map_urls if i.get("network_map_url", "")]
+    if urls:
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'urls': urls
+        }
+
+
+def map_certificate_providers(nren: NREN, year: int, answers: Dict[str, Any]):
+    certificate_service = answers.get("certificate_service")
+    if certificate_service:
+        if "other" in certificate_service:
+            certificate_service.remove("other")
+            certificate_service.append(answers.get("certificate_service-Comment", "other"))
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'provider_names': certificate_service
+        }
+
+
+def map_weathermap_entry(nren: NREN, year: int, answers: Dict[str, Any]):
+    network_weather = answers.get("network_weather")
+    if network_weather:
+        network_weather = network_weather == "Yes"
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'weather_map': network_weather,
+            'url': answers.get("network_weather_url", "")
+        }
+
+
+def map_pert_team(nren: NREN, year: int, answers: Dict[str, Any]):
+    pert_team = answers.get("pert_team")
+    if pert_team:
+        pert_team = YesNoPlanned[pert_team.lower()]
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'pert_team': pert_team
+        }
+
+
+def map_alienwave_entry(nren: NREN, year: int, answers: Dict[str, Any]):
+    alienwave_services = answers.get("alienwave_services")
+    alienwave_internal = answers.get("alienwave_internal")
+    if alienwave_services or alienwave_internal:
+        alienwave_services = YesNoPlanned[alienwave_services.lower()] if alienwave_services else None
+        alienwave_internal = alienwave_internal == "Yes" if alienwave_internal else None
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'alien_wave_third_pary': alienwave_services,
+            'nr_of_alien_wave_third_party_services': int_or_none(answers, "alienwave_services_number"),
+            'alien_wave_internal': alienwave_internal
+        }
+
+
+def map_capacity_entry(nren: NREN, year: int, answers: Dict[str, Any]):
+    max_capacity = answers.get("max_capacity")
+    typical_capacity = answers.get("typical_capacity")
+    if max_capacity or typical_capacity:
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'largest_link_capacity': decimal_or_none(answers, "max_capacity"),
+            'typical_backbone_capacity': decimal_or_none(answers, "typical_capacity")
+        }
+
+
+def map_external_connections(nren: NREN, year: int, answers: Dict[str, Any]):
+    external_connections = answers.get("external_connections")
+    if external_connections:
+        connections: List[ExternalConnection] = [{
+            'link_name': connection.get('link_name', ''),
+            'capacity': connection.get('capacity'),
+            'from_organization': connection.get('from_organization', ''),
+            'to_organization': connection.get('to_organization', ''),
+            'interconnection_method': connection.get('interconnection_method')
+        } for connection in external_connections]
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'connections': connections
+        }
+
+
+def map_non_re_peers(nren: NREN, year: int, answers: Dict[str, Any]):
+    non_r_and_e_peers = answers.get("non_r_and_e_peers")
+    if non_r_and_e_peers:
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'nr_of_non_r_and_e_peers': int(non_r_and_e_peers)
+        }
+
+
+def map_traffic_ratio(nren: NREN, year: int, answers: Dict[str, Any]):
+    commodity_vs_r_e = answers.get("commodity_vs_r_e")
+    if commodity_vs_r_e:
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'r_and_e_percentage': decimal_or_zero(commodity_vs_r_e, "r_e"),
+            'commodity_percentage': decimal_or_zero(commodity_vs_r_e, "commodity"),
+        }
+
+
+def map_ops_automation(nren: NREN, year: int, answers: Dict[str, Any]):
+    operational_process_automation = answers.get("operational_process_automation")
+    if operational_process_automation:
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'ops_automation': YesNoPlanned[operational_process_automation.lower()],
+            'ops_automation_specifics': answers.get("operational_process_automation_tools", "")
+        }
+
+
+def map_network_function_virtualisation(nren: NREN, year: int, answers: Dict[str, Any]):
+    nfv = answers.get("nfv")
+    if nfv:
+        nfv_types = answers.get("nfv_types", [])
+        if "other" in nfv_types:
+            nfv_types.remove("other")
+            nfv_types.append(answers.get("nfv_types-Comment", "other"))
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'nfv': YesNoPlanned[nfv.lower()],
+            'nfv_specifics': nfv_types
+        }
+
+
+def map_network_automation(nren: NREN, year: int, answers: Dict[str, Any]):
+    network_automation = answers.get("network_automation")
+    if network_automation:
+        network_automation_tasks = answers.get("network_automation_tasks", [])
+        return {
+            'nren_id': nren.id,
+            'year': year,
+            'network_automation': YesNoPlanned[network_automation.lower()],
+            'network_automation_specifics': network_automation_tasks
+        }
+
+
+def map_nren_services(nren: NREN, year: int, answers: Dict[str, Any]):
+    all_services = {}
+    for question_name in [
+        "services_collaboration", "services_hosting", "services_identity", "services_isp",
+        "services_multimedia", "services_network", "services_professional", "services_security"
+    ]:
+        all_services.update(answers.get(question_name, {}))
+
+    for service_key, service_answers in all_services.items():
+        offered = service_answers.get("offered")
+        if offered == ["yes"]:
+            yield {
+                'nren_id': nren.id,
+                'year': year,
+                'service_key': service_key,
+                'product_name': service_answers.get("name", ""),
+                'additional_information': service_answers.get("additional_information", ""),
+                'official_description': service_answers.get("description", "")
+            }
+
+
+def map_2023(responses: List[SurveyResponse]) -> Dict[Type[PresentationModel], List[Dict]]:
+    year = 2023
+
+    result = defaultdict(list)  # {model: [data]}
+
+    for response in responses:
+        nren = response.nren
+        answers = response.answers['data']
+
+        budget = map_budget_entry(year, nren, answers)
+        if budget:
+            result[BudgetEntry].append(budget)
+
+        funding = map_fundingsource_entry(year, nren, answers)
+        if funding:
+            result[FundingSource].append(funding)
+
+        charging = map_charging_structure_entry(year, nren, answers)
+        if charging:
+            result[ChargingStructure].append(charging)
+
+        nren_staff = map_nren_staff(year, nren, answers)
+        if nren_staff:
+            result[NrenStaff].append(nren_staff)
+
+        parent = map_parent_orgs(nren, year, answers)
+        if parent:
+            result[ParentOrganization].append(parent)
+
+        subs = map_sub_orgs(nren, year, answers)
+        for sub in subs:
+            result[SubOrganization].append(sub)
+
+        ec_projects = map_ec_projects(nren, year, answers)
+        for ec_project in ec_projects:
+            result[ECProject].append(ec_project)
+
+        policy = map_policy(nren, year, answers)
+        if policy:
+            result[Policy].append(policy)
+
+        traffic_volume = map_traffic_volume(nren, year, answers)
+        if traffic_volume:
+            result[TrafficVolume].append(traffic_volume)
+
+        institution_urls = map_institution_urls(nren, year, answers)
+        if institution_urls:
+            result[InstitutionURLs].append(institution_urls)
+
+        central_procurement = map_central_procurement(nren, year, answers)
+        if central_procurement:
+            result[CentralProcurement].append(central_procurement)
+
+        service_management = map_service_management(nren, year, answers)
+        if service_management:
+            result[ServiceManagement].append(service_management)
+
+        service_user_types = map_service_user_types(nren, year, answers)
+        for service_user_type in service_user_types:
+            result[ServiceUserTypes].append(service_user_type)
+
+        eosc_listings = map_eosc_listings(nren, year, answers)
+        if eosc_listings:
+            result[EOSCListings].append(eosc_listings)
+
+        standards = map_standards(nren, year, answers)
+        if standards:
+            result[Standards].append(standards)
+
+        crisis_exercises = map_crisis_exercises(nren, year, answers)
+        if crisis_exercises:
+            result[CrisisExercises].append(crisis_exercises)
+
+        security_controls = map_security_controls(nren, year, answers)
+        if security_controls:
+            result[SecurityControls].append(security_controls)
+
+        connected_proportions = map_connected_proportion(nren, year, answers)
+        for connected_proportion in connected_proportions:
+            result[ConnectedProportion].append(connected_proportion)
+
+        connectivity_levels = map_connectivity_levels(nren, year, answers)
+        for connectivity_level in connectivity_levels:
+            result[ConnectivityLevel].append(connectivity_level)
+
+        connection_carriers = map_connection_carriers(nren, year, answers)
+        for connection_carrier in connection_carriers:
+            result[ConnectionCarrier].append(connection_carrier)
+
+        connectivity_loads = map_connectivity_loads(nren, year, answers)
+        for connectivity_load in connectivity_loads:
+            result[ConnectivityLoad].append(connectivity_load)
+
+        connectivity_growths = map_connectivity_growth(nren, year, answers)
+        for connectivity_growth in connectivity_growths:
+            result[ConnectivityGrowth].append(connectivity_growth)
+
+        commercial_connectivities = map_commercial_connectivity(nren, year, answers)
+        for commercial_connectivity in commercial_connectivities:
+            result[CommercialConnectivity].append(commercial_connectivity)
+
+        commercial_charging_levels = map_commercial_charging_levels(nren, year, answers)
+        for commercial_charging_level in commercial_charging_levels:
+            result[CommercialChargingLevel].append(commercial_charging_level)
+
+        remote_campuses = map_remote_campuses(nren, year, answers)
+        if remote_campuses:
+            result[RemoteCampuses].append(remote_campuses)
+
+        dark_fibre_lease = map_dark_fibre_lease(nren, year, answers)
+        if dark_fibre_lease:
+            result[DarkFibreLease].append(dark_fibre_lease)
+
+        dark_fibre_installed = map_dark_fibre_installed(nren, year, answers)
+        if dark_fibre_installed:
+            result[DarkFibreInstalled].append(dark_fibre_installed)
+
+        fibre_light = map_fibre_light(nren, year, answers)
+        if fibre_light:
+            result[FibreLight].append(fibre_light)
+
+        network_map_urls = map_network_map_urls(nren, year, answers)
+        if network_map_urls:
+            result[NetworkMapUrls].append(network_map_urls)
+
+        monitoring_tools = map_monitoring_tools(nren, year, answers)
+        if monitoring_tools:
+            result[MonitoringTools].append(monitoring_tools)
+
+        passive_monitoring = map_passive_monitoring(nren, year, answers)
+        if passive_monitoring:
+            result[PassiveMonitoring].append(passive_monitoring)
+
+        traffic_statistics = map_traffic_statistics(nren, year, answers)
+        if traffic_statistics:
+            result[TrafficStatistics].append(traffic_statistics)
+
+        siem_vendors = map_siem_vendors(nren, year, answers)
+        if siem_vendors:
+            result[SiemVendors].append(siem_vendors)
+
+        certificate_providers = map_certificate_providers(nren, year, answers)
+        if certificate_providers:
+            result[CertificateProviders].append(certificate_providers)
+
+        weathermap = map_weathermap_entry(nren, year, answers)
+        if weathermap:
+            result[WeatherMap].append(weathermap)
+
+        pert_team = map_pert_team(nren, year, answers)
+        if pert_team:
+            result[PertTeam].append(pert_team)
+
+        alienwave = map_alienwave_entry(nren, year, answers)
+        if alienwave:
+            result[AlienWave].append(alienwave)
+
+        capacity = map_capacity_entry(nren, year, answers)
+        if capacity:
+            result[Capacity].append(capacity)
+
+        external_connections = map_external_connections(nren, year, answers)
+        if external_connections:
+            result[ExternalConnections].append(external_connections)
+
+        non_re_peers = map_non_re_peers(nren, year, answers)
+        if non_re_peers:
+            result[NonREPeers].append(non_re_peers)
+
+        traffic_ratio = map_traffic_ratio(nren, year, answers)
+        if traffic_ratio:
+            result[TrafficRatio].append(traffic_ratio)
+
+        ops_automation = map_ops_automation(nren, year, answers)
+        if ops_automation:
+            result[OpsAutomation].append(ops_automation)
+
+        nfv = map_network_function_virtualisation(nren, year, answers)
+        if nfv:
+            result[NetworkFunctionVirtualisation].append(nfv)
+
+        network_automation = map_network_automation(nren, year, answers)
+        if network_automation:
+            result[NetworkAutomation].append(network_automation)
+
+        nren_services = map_nren_services(nren, year, answers)
+        for nren_service in nren_services:
+            result[NRENService].append(nren_service)
+
+    return result
diff --git a/compendium_v2/publishers/year/map_2024.py b/compendium_v2/publishers/year/map_2024.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/compendium_v2/resources/__init__.py b/compendium_v2/resources/__init__.py
deleted file mode 100644
index e2499aeb09afcf692f1532633362b5e0d8dd3647..0000000000000000000000000000000000000000
--- a/compendium_v2/resources/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from pathlib import Path
-
-
-def get_resource_file_path(filename: str) -> Path:
-    resource_dir = Path(__file__).resolve().parent
-    return resource_dir / filename
diff --git a/compendium_v2/routes/default.py b/compendium_v2/routes/default.py
index b58658f692434b99250f7fb8b7fea3a67f276b8a..d61a89b1fdedd90ef36ec5857626103322193cd9 100644
--- a/compendium_v2/routes/default.py
+++ b/compendium_v2/routes/default.py
@@ -1,4 +1,4 @@
-import pkg_resources
+import importlib.metadata as importlib_metadata
 from flask import Blueprint, jsonify, render_template, Response
 from compendium_v2.routes import common
 
@@ -74,6 +74,6 @@ def version():
     version_params = {
         'api': API_VERSION,
         'module':
-            pkg_resources.get_distribution('compendium-v2').version
+            importlib_metadata.distribution('compendium-v2').version
     }
     return jsonify(version_params)
diff --git a/compendium_v2/static/bundle.js b/compendium_v2/static/bundle.js
index 8a74ec2450efe8c218f815d86ffa1f6daef7f239..d9d1a7278faadf551ed3a92a725dad60f7cf287a 100644
--- a/compendium_v2/static/bundle.js
+++ b/compendium_v2/static/bundle.js
@@ -169,4 +169,4 @@ to {
   > * {
     pointer-events: auto;
   }
-`,jO=({reverseOrder:e,position:n="top-center",toastOptions:r,gutter:o,children:i,containerStyle:s,containerClassName:a})=>{let{toasts:l,handlers:u}=(e=>{let{toasts:n,pausedAt:r}=((e={})=>{let[n,r]=(0,t.useState)(cO);(0,t.useEffect)((()=>(uO.push(r),()=>{let e=uO.indexOf(r);e>-1&&uO.splice(e,1)})),[n]);let o=n.toasts.map((t=>{var n,r;return{...e,...e[t.type],...t,duration:t.duration||(null==(n=e[t.type])?void 0:n.duration)||(null==e?void 0:e.duration)||dO[t.type],style:{...e.style,...null==(r=e[t.type])?void 0:r.style,...t.style}}}));return{...n,toasts:o}})(e);(0,t.useEffect)((()=>{if(r)return;let e=Date.now(),t=n.map((t=>{if(t.duration===1/0)return;let n=(t.duration||0)+t.pauseDuration-(e-t.createdAt);if(!(n<0))return setTimeout((()=>fO.dismiss(t.id)),n);t.visible&&fO.dismiss(t.id)}));return()=>{t.forEach((e=>e&&clearTimeout(e)))}}),[n,r]);let o=(0,t.useCallback)((()=>{r&&pO({type:6,time:Date.now()})}),[r]),i=(0,t.useCallback)(((e,t)=>{let{reverseOrder:r=!1,gutter:o=8,defaultPosition:i}=t||{},s=n.filter((t=>(t.position||i)===(e.position||i)&&t.height)),a=s.findIndex((t=>t.id===e.id)),l=s.filter(((e,t)=>t<a&&e.visible)).length;return s.filter((e=>e.visible)).slice(...r?[l+1]:[0,l]).reduce(((e,t)=>e+(t.height||0)+o),0)}),[n]);return{toasts:n,handlers:{updateHeight:mO,startPause:gO,endPause:o,calculateOffset:i}}})(r);return t.createElement("div",{style:{position:"fixed",zIndex:9999,top:16,left:16,right:16,bottom:16,pointerEvents:"none",...s},className:a,onMouseEnter:u.startPause,onMouseLeave:u.endPause},l.map((r=>{let s=r.position||n,a=((e,t)=>{let n=e.includes("top"),r=n?{top:0}:{bottom:0},o=e.includes("center")?{justifyContent:"center"}:e.includes("right")?{justifyContent:"flex-end"}:{};return{left:0,right:0,display:"flex",position:"absolute",transition:iO()?void 0:"all 230ms cubic-bezier(.21,1.02,.73,1)",transform:`translateY(${t*(n?1:-1)}px)`,...r,...o}})(s,u.calculateOffset(r,{reverseOrder:e,gutter:o,defaultPosition:n}));return t.createElement(MO,{id:r.id,key:r.id,onHeightUpdate:u.updateHeight,className:r.visible?LO:"",style:a},"custom"===r.type?rO(r.message,r):i?i(r):t.createElement(NO,{toast:r,position:s}))})))},FO=fO,BO=function(e){return e.Unverified="unverified",e.Verified="verified",e.Edited="edited",e}({}),qO=function(e){return e.not_started="not started",e.started="started",e.completed="completed",e}({}),HO=function(e){return e.closed="closed",e.open="open",e.preview="preview",e.published="published",e}({});function zO(){return QO.apply(this,arguments)}function QO(){return(QO=Fu(qu().mark((function e(){var t,n;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,fetch("/api/survey/list");case 3:return t=e.sent,e.next=6,t.json();case 6:return n=e.sent,e.abrupt("return",n);case 10:return e.prev=10,e.t0=e.catch(0),e.abrupt("return",[]);case 13:case"end":return e.stop()}}),e,null,[[0,10]])})))).apply(this,arguments)}function UO(){return WO.apply(this,arguments)}function WO(){return(WO=Fu(qu().mark((function e(){var t,n,r;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,fetch("/api/survey/active/year");case 3:return t=e.sent,e.next=6,t.json();case 6:if(!("year"in(n=e.sent))){e.next=12;break}return r=n.year,e.abrupt("return",r.toString());case 12:return console.log("Invalid response format: Failed fetching active survey year."),e.abrupt("return","");case 14:e.next=20;break;case 16:return e.prev=16,e.t0=e.catch(0),console.error("Failed fetching active survey year:",e.t0),e.abrupt("return","");case 20:case"end":return e.stop()}}),e,null,[[0,16]])})))).apply(this,arguments)}const $O=t.forwardRef((({bsPrefix:e,variant:t,animation:n="border",size:r,as:o="div",className:i,...s},a)=>{const l=`${e=kt(e,"spinner")}-${n}`;return(0,_t.jsx)(o,{ref:a,...s,className:Tt()(i,l,r&&`${l}-${r}`,t&&`text-${t}`)})}));$O.displayName="Spinner";const GO=$O;function JO(e){var n=e.text,r=e.helpText,o=e.onClick,i=e.enabled,s=Cn((0,t.useState)(!1),2),a=s[0],l=s[1],u=function(){var e=Fu(qu().mark((function e(){return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!a){e.next=2;break}return e.abrupt("return");case 2:return l(!0),e.prev=3,e.next=6,o();case 6:return e.prev=6,l(!1),e.finish(6);case 9:case"end":return e.stop()}}),e,null,[[3,,6,9]])})));return function(){return e.apply(this,arguments)}}();return t.createElement(Ru,{onClick:u,disabled:!i,style:{pointerEvents:"auto",marginLeft:".5rem"},title:r},a&&t.createElement(GO,{as:"span",animation:"border",size:"sm",role:"status","aria-hidden":"true"}),n)}const YO=function(){var e=Cn((0,t.useState)([]),2),n=e[0],r=e[1],o=(0,t.useRef)(!1);function i(e,t,n){return s.apply(this,arguments)}function s(){return(s=Fu(qu().mark((function e(t,n,o){var i,s;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,fetch(t,{method:"POST"});case 3:return i=e.sent,e.next=6,i.json();case 6:s=e.sent,i.ok?(FO(o),zO().then((function(e){r(e)}))):FO(n+s.message),e.next=13;break;case 10:e.prev=10,e.t0=e.catch(0),FO(n+e.t0.message);case 13:case"end":return e.stop()}}),e,null,[[0,10]])})))).apply(this,arguments)}function a(){return(a=Fu(qu().mark((function e(){return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,i("/api/survey/new","Failed creating new survey: ","Created new survey");case 2:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function l(e,t){return u.apply(this,arguments)}function u(){return(u=Fu(qu().mark((function e(t,n){return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!o.current){e.next=3;break}return FO("Wait for status update to be finished..."),e.abrupt("return");case 3:return o.current=!0,e.next=6,i("/api/survey/"+n+"/"+t,"Error while updating "+t+" survey status to "+n+": ",t+" survey status updated to "+n);case 6:o.current=!1;case 7:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function c(){return(c=Fu(qu().mark((function e(t,n){return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,i("/api/response/unlock/"+t+"/"+n,"Error while unlocking "+n+" "+t+" survey response: ",n+" "+t+" survey response unlocked");case 2:case"end":return e.stop()}}),e)})))).apply(this,arguments)}(0,t.useEffect)((function(){zO().then((function(e){r(e)}))}),[]);var p=n.length>0&&n.every((function(e){return e.status==HO.published})),d=Ge(),h=window.location.origin+"/data?preview";return t.createElement("div",null,t.createElement(jO,null),t.createElement(Ru,{onClick:function(){return a.apply(this,arguments)},disabled:!p,style:{pointerEvents:"auto"},title:"Create a new survey for the next year. Only possible if all current surveys are published."},"start new survey"),t.createElement(BS,null,n.map((function(e){return t.createElement(BS.Item,{eventKey:e.year.toString(),key:e.year},t.createElement(BS.Header,null,e.year," - ",e.status),t.createElement(BS.Body,null,t.createElement("div",{style:{marginLeft:".5rem"}},t.createElement(Ru,{style:{marginLeft:".5rem"},onClick:function(){return d("/survey/admin/inspect/".concat(e.year))},title:"Open the survey for inspection with all questions visible and any visibleIf logic added to the title."},"Inspect Survey"),t.createElement(Ru,{style:{marginLeft:".5rem"},onClick:function(){return d("/survey/admin/try/".concat(e.year))},title:"Open the survey exactly as the nrens will see it, but without any nren data."},"Try Survey"),t.createElement(JO,{text:"Mark as open",helpText:"Allow the NRENs to respond to this survey. Only 1 survey may be open at a time, and (pre)-published surveys cannot be opened anymore.",enabled:e.status==HO.closed,onClick:function(){return l(e.year,"open")}}),t.createElement(JO,{text:"Mark as closed",helpText:"Do not allow the NRENs to respond to this survey anymore. Only surveys with status open can be closed.",enabled:e.status==HO.open,onClick:function(){return l(e.year,"close")}}),t.createElement(JO,{text:"Preview results",helpText:"Publish all completed survey responses to the compendium website for preview by admins. This is only possible if the survey is closed or previewed already.",enabled:e.status==HO.closed||e.status==HO.preview,onClick:function(){return l(e.year,"preview")}}),t.createElement(JO,{text:"Publish results",helpText:"Publish or re-publish all completed survey responses to the compendium website. This is only possible if the survey is in preview or published already.",enabled:e.status==HO.preview||e.status==HO.published,onClick:function(){return l(e.year,"publish")}}),e.status==HO.preview&&t.createElement("span",null,"  Preview link: ",t.createElement("a",{href:h},h))),t.createElement(Wx,null,t.createElement("tbody",null,e.responses.map((function(n){return t.createElement("tr",{key:n.nren},t.createElement("td",null,n.nren),t.createElement("td",null,n.status),t.createElement("td",null,n.lock_description),t.createElement("td",null,t.createElement(Ru,{onClick:function(){return d("/survey/response/".concat(e.year,"/").concat(n.nren))},style:{pointerEvents:"auto"},title:"Open the responses of the NREN."},"open"),t.createElement(Ru,{onClick:function(){return function(e,t){return c.apply(this,arguments)}(e.year,n.nren)},disabled:""==n.lock_description,style:{pointerEvents:"auto"},title:"Remove the lock from the survey so that another person can open the survey for editing. WARNING: The person that currently has the lock will not be abe to save their changes anymore once someone else starts editing!"},"remove lock")))}))))))}))))};function KO(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function XO(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?KO(Object(n),!0).forEach((function(t){Pn(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):KO(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function ZO(){return(ZO=Fu(qu().mark((function e(){var t,n;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,fetch("/api/user/list");case 3:return t=e.sent,e.next=6,t.json();case 6:return n=e.sent,e.abrupt("return",n);case 10:return e.prev=10,e.t0=e.catch(0),e.abrupt("return",[]);case 13:case"end":return e.stop()}}),e,null,[[0,10]])})))).apply(this,arguments)}function eT(){return(eT=Fu(qu().mark((function e(){var t,n;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,fetch("/api/nren/list");case 3:return t=e.sent,e.next=6,t.json();case 6:return n=e.sent,e.abrupt("return",n);case 10:return e.prev=10,e.t0=e.catch(0),e.abrupt("return",[]);case 13:case"end":return e.stop()}}),e,null,[[0,10]])})))).apply(this,arguments)}var tT=function(){var e=Fu(qu().mark((function e(t,n){var r,o,i,s;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return r=XO({id:t},n),o={method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)},e.next=4,fetch("/api/user/".concat(t),o);case 4:return i=e.sent,e.next=7,i.json();case 7:if(s=e.sent,i.ok){e.next=10;break}throw new Error(s.message);case 10:return e.abrupt("return",s.user);case 11:case"end":return e.stop()}}),e)})));return function(t,n){return e.apply(this,arguments)}}(),nT=function(e,t){return e.permissions.active&&!t.permissions.active?-1:!e.permissions.active&&t.permissions.active?1:e.permissions.active&&t.permissions.active?"admin"===e.role&&"admin"!==t.role?1:"admin"!==e.role&&"admin"===t.role?-1:e.name.localeCompare(t.name):e.name.localeCompare(t.name)};const rT=function(){var e=Cn((0,t.useState)([]),2),n=e[0],r=e[1],o=Cn((0,t.useState)([]),2),i=o[0],s=o[1],a=(0,t.useContext)(Qu).user,l=Cn((0,t.useState)({idx:-1,asc:!0}),2),u=l[0],c=l[1],p=Cn((0,t.useState)([]),2),d=p[0],h=p[1];(0,t.useEffect)((function(){(function(){return ZO.apply(this,arguments)})().then((function(e){r(e),h(e.sort(nT))})),function(){return eT.apply(this,arguments)}().then((function(e){s(e.sort((function(e,t){return e.name.localeCompare(t.name)})))}))}),[]),(0,t.useEffect)((function(){h(bn(n.sort(nT)))}),[n]);for(var f=function(e,t){var o=n.findIndex((function(e){return e.id===t.id})),i=bn(n),s=e.target.name,a={};a[s]="active"===s?e.target.checked:e.target.value,tT(t.id,a).then((function(e){i[o]=e,r(i)})).catch((function(e){alert(e.message)}))},m=function(e){var t;if(e===u.idx||(5===e||0===e)&&-1===u.idx)return 5!==e&&0!==e||(e=-1),c({idx:e,asc:!u.asc}),void h(bn(d.reverse()));0===e?(t=nT,c({idx:-1,asc:!0})):1===e?(t=function(e,t){return e.permissions.active&&!t.permissions.active?-1:!e.permissions.active&&t.permissions.active?1:0},c({idx:e,asc:!0})):2===e?(t=function(e,t){return e.role.localeCompare(t.role)},c({idx:e,asc:!0})):3===e?(t=function(e,t){return e.email.localeCompare(t.email)},c({idx:e,asc:!0})):4===e?(t=function(e,t){return e.name.localeCompare(t.name)},c({idx:e,asc:!0})):5===e?(t=nT,c({idx:-1,asc:!0})):6===e?(t=function(e,t){return 0===e.nrens.length&&0===t.nrens.length?0:0===e.nrens.length?-1:0===t.nrens.length?1:e.nrens[0].localeCompare(t.nrens[0])},c({idx:e,asc:!0})):(t=nT,c({idx:e,asc:!0})),h(n.sort(t))},g={},y=0;y<=6;y++)g[y]=u.idx===y?{"aria-sort":u.asc?"ascending":"descending"}:null;return t.createElement(Lt,{style:{maxWidth:"90vw"}},t.createElement(Ft,null,t.createElement("h1",null," User Management Page"),t.createElement(Wx,null,t.createElement("thead",null,t.createElement("tr",null,t.createElement("th",jC({},g[0],{onClick:function(){return m(0)},className:"pt-3 sortable",style:{border:"1px solid #ddd"}}),"Id"),t.createElement("th",jC({},g[1],{onClick:function(){return m(1)},className:"pt-3 sortable",style:{border:"1px solid #ddd"}}),"Active"),t.createElement("th",jC({},g[2],{onClick:function(){return m(2)},className:"pt-3 sortable",style:{border:"1px solid #ddd"}}),"Role"),t.createElement("th",jC({},g[3],{onClick:function(){return m(3)},className:"pt-3 sortable",style:{border:"1px solid #ddd"}}),"Email"),t.createElement("th",jC({},g[4],{onClick:function(){return m(4)},className:"pt-3 sortable",style:{border:"1px solid #ddd"}}),"Full Name"),t.createElement("th",jC({},g[5],{onClick:function(){return m(5)},className:"pt-3 sortable",style:{border:"1px solid #ddd"}}),"OIDC Sub"),t.createElement("th",jC({},g[6],{onClick:function(){return m(6)},className:"pt-3 sortable",style:{border:"1px solid #ddd"}}),"NREN"))),t.createElement("tbody",null,d.map((function(e){return t.createElement("tr",{key:e.id},t.createElement("td",{style:{border:"1px dotted #ddd"}},e.id),t.createElement("td",{style:{border:"1px dotted #ddd"}},e.id==a.id?"Active":t.createElement("input",{type:"checkbox",name:"active",checked:e.permissions.active,onChange:function(t){return f(t,e)}})),t.createElement("td",{style:{border:"1px dotted #ddd"}},e.id==a.id?e.role.charAt(0).toUpperCase()+e.role.slice(1):t.createElement("select",{name:"role",defaultValue:e.role,onChange:function(t){return f(t,e)}},t.createElement("option",{value:"admin"},"Admin"),t.createElement("option",{value:"user"},"User"),t.createElement("option",{value:"observer"},"Observer"))),t.createElement("td",{style:{border:"1px dotted #ddd"}},e.email),t.createElement("td",{style:{border:"1px dotted #ddd"}},e.name),t.createElement("td",{style:{border:"1px dotted #ddd"}},e.oidc_sub),t.createElement("td",{style:{border:"1px dotted #ddd"}},t.createElement("select",{name:"nren",multiple:!1,value:e.nrens.length>0?(n=e.nrens[0],null===(r=i.find((function(e){return e.id==n||e.name==n})))||void 0===r?void 0:r.id):"",onChange:function(t){return f(t,e)}},t.createElement("option",{value:""},"Select NREN"),i.map((function(e){return t.createElement("option",{key:"nren_"+e.id,value:e.id},e.name)})))));var n,r}))))))};var oT=o(522),iT=o(755);function sT(e,t){if(0==t.column.indexValue&&"item"in t.row){var n,r,o=t.row.item;void 0!==o.customDescription&&(null===(n=t.htmlElement.parentElement)||void 0===n||n.children[0].children[0].setAttribute("description",o.customDescription),null===(r=t.htmlElement.parentElement)||void 0===r||r.children[0].children[0].classList.add("survey-tooltip"))}}function aT(e){var t=e[0];if(void 0===t||null==t||""==t)return!0;try{return!!new URL(t)}catch(e){return!1}}function lT(e,t){t.question.hideCheckboxLabels&&(t.cssClasses.root+=" hidden-checkbox-labels")}function uT(e,t){var n,r='[data-name="'+t.question.name+'"]',o=null===(n=document.querySelector(r))||void 0===n?void 0:n.querySelector("h5");o&&!o.classList.contains("sv-header-flex")&&t.question.updateElementCss()}function cT(e,t,n){var r;n.verificationStatus.set(e.name,t);var o=document.createElement("button");o.type="button",o.className="sv-action-bar-item verification",o.innerHTML=t,t==BO.Unverified?(o.innerHTML="No change from previous year",o.className+=" verification-required",o.onclick=function(){"display"!=n.mode&&(e.validate(),cT(e,BO.Verified,n))}):(o.innerHTML="Answer updated",o.className+=" verification-ok");var i='[data-name="'+e.name+'"]',s=null===(r=document.querySelector(i))||void 0===r?void 0:r.querySelector("h5"),a=null==s?void 0:s.querySelector(".verification");a?a.replaceWith(o):null==s||s.appendChild(o)}const pT=function(e){var n=e.surveyModel,r=(0,t.useCallback)((function(e,t){var r=n.verificationStatus.get(t.question.name);r&&cT(t.question,r,n)}),[n]),o=(0,t.useCallback)((function(e,t){n.verificationStatus.get(t.question.name)==BO.Unverified&&cT(t.question,BO.Edited,n)}),[n]);return oT.FunctionFactory.Instance.hasFunction("validateWebsiteUrl")||oT.FunctionFactory.Instance.register("validateWebsiteUrl",aT),n.css.question.title.includes("sv-header-flex")||(n.css.question.title="sv-title sv-question__title sv-header-flex",n.css.question.titleOnError="sv-question__title--error sv-error-color-fix"),n.onAfterRenderQuestion.hasFunc(r)||(n.onAfterRenderQuestion.add(r),n.onAfterRenderQuestion.add(uT)),n.onValueChanged.hasFunc(o)||n.onValueChanged.add(o),n.onUpdateQuestionCssClasses.hasFunc(lT)||n.onUpdateQuestionCssClasses.add(lT),n.onMatrixAfterCellRender.hasFunc(sT)||n.onMatrixAfterCellRender.add(sT),t.createElement(iT.Survey,{model:n})};function dT(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function hT(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?dT(Object(n),!0).forEach((function(t){Pn(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):dT(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}const fT=function(e){var n=e.surveyModel,r=e.pageNoSetter,o=Cn((0,t.useState)([]),2),i=o[0],s=o[1],a=function(e){return!(null===e.value||void 0===e.value||""===e.value||"checkbox"===e.getType()&&0==e.value.length||"multipletext"===e.getType()&&(1===Object.keys(e.value).length&&void 0===Object.values(e.value)[0]||0===Object.keys(e.value).length))};(0,t.useEffect)((function(){var e=function(e){if(e&&e.pages){var t=[];e.pages.forEach((function(n){var r=n.questions.filter((function(e){return e.startWithNewLine})),o=r.length,i=r.filter(a).length,s=o-i,l=i/o;t.push({completionPercentage:100*l,unansweredPercentage:s/o*100,totalPages:e.pages.length,pageTitle:n.title})})),s(t)}};n.onValueChanged.add((function(t){e(t)})),e(n)}),[n]);var l={height:"0.5rem",transition:"width 0.3s ease"};return t.createElement(Lt,{className:"survey-progress"},t.createElement(Ft,null,i.map((function(e,o){return t.createElement(qt,{xs:12,md:!0,key:o,onClick:function(){return r(o)},style:{cursor:"pointer",margin:"0.5rem"}},t.createElement("div",null,t.createElement("span",{style:{whiteSpace:"nowrap",fontSize:"1.5rem",marginRight:"0.25rem",fontWeight:"bold",color:"#2db394"}},o+1),t.createElement("span",{style:hT({whiteSpace:"nowrap"},n.currentPageNo==o&&{fontWeight:"bold"})},e.pageTitle),t.createElement("div",{style:{display:"flex",flexWrap:"wrap"}},t.createElement("div",{style:hT(hT({},l),{},{width:"".concat(e.completionPercentage,"%"),backgroundColor:"#262261"})}),t.createElement("div",{style:hT(hT({},l),{},{width:"".concat(e.unansweredPercentage,"%"),backgroundColor:"#cdcdcd"})}))))}))))},mT=function(e){var n=e.surveyModel,r=e.surveyActions,o=e.year,i=e.nren,s=e.children,a=Cn((0,t.useState)(0),2),l=a[0],u=a[1],c=Cn((0,t.useState)(!1),2),p=c[0],d=c[1],h=Cn((0,t.useState)(""),2),f=h[0],m=h[1],g=Cn((0,t.useState)(""),2),y=g[0],v=g[1],b=(0,t.useContext)(Qu).user,C=(0,t.useCallback)((function(){d("edit"==n.mode),m(n.lockedBy),u(n.currentPageNo),v(n.status)}),[n]);(0,t.useEffect)((function(){C()}),[C]);var w=function(e){u(e),n.currentPageNo=e},x=function(){w(n.currentPageNo+1)},E=function(){var e=Fu(qu().mark((function e(t){return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,r[t]();case 2:C();case 3:case"end":return e.stop()}}),e)})));return function(t){return e.apply(this,arguments)}}(),P=function(e,t){return S(e,(function(){return E(t)}))},S=function(e,n){return t.createElement("button",{className:"sv-btn sv-btn--navigation",onClick:n},e)},O="Save and stop editing",T="Save progress",_="Start editing",V="Complete Survey",R=function(){return t.createElement("div",{className:"survey-edit-buttons-block"},!p&&!f&&n.editAllowed&&P(_,"startEdit"),!p&&f&&f==b.name&&P("Discard any unsaved changes and release your lock","releaseLock"),p&&y==qO.started&&P(T,"save"),p&&y==qO.started&&P(O,"saveAndStopEdit"),p&&l===n.visiblePages.length-1&&P(V,"complete"),l!==n.visiblePages.length-1&&S("Next Section",x))};return parseInt(o),t.createElement(Lt,null,t.createElement(Ft,{className:"survey-content"},t.createElement("h2",null,t.createElement("span",{className:"survey-title"},o," Compendium Survey "),t.createElement("span",{className:"survey-title-nren"}," ",i," "),t.createElement("span",null," - ",y)),t.createElement("p",{style:{marginTop:"1rem",textAlign:"justify"}},t.createElement("p",null,"To get started, click “",_,"” to end read-only mode. Different people from your NREN (Compendium administrators) can contribute to the survey if needed, but agreement should be reached internally before completing the survey as the administration team will treat responses as a single source of truth from the NREN. You can start editing only when nobody else from your NREN is currently working on the survey."),t.createElement("p",null,t.createElement("b",null,"In a small change, the survey now asks about this calendar year, i.e. ",o)," (or the current financial year if your budget or staffing data does not match the calendar year). For network questions, please provide data from the 12 months preceding you answering the question. Where available, the survey questions are pre-filled with answers from the previous survey. You can edit the pre-filled answer to provide new information, or press the “no change from previous year” button."),t.createElement("p",null,"Press the “",T,"“ or “",O,"“ button to save all answers in the survey. When you reach the last section of the survey (Services), you will find a “",V,"“ button which saves all answers in the survey and lets the Compendium team know that your answers are ready to be published. As long as the survey remains open, any Compendium administrator from your NREN can add answers or amend existing ones, even after using the “",V,"“ button."),t.createElement("p",null,"Some fields require specific data, such as numerical data, valid http-addresses, and in some questions, the answer has to add up to 100%. If an answer does not fulfil the set criteria, the question will turn pink and an error message will appear. Fields can be left blank if you prefer not to answer a question. If you notice any errors after the survey was closed, please contact us for correcting those.")),t.createElement("p",null,"Thank you for taking the time to fill in the ",o," Compendium Survey. Any questions or requests can be sent to ",t.createElement("a",{href:"mailto:Partner-Relations@geant.org"},t.createElement("span",null,"Partner-Relations@geant.org"))),p&&t.createElement(t.Fragment,null,t.createElement("br",null),t.createElement("b",null,"Remember to click “",O,"” before leaving the page."))),t.createElement(Ft,null,R()),t.createElement(Ft,{className:"survey-content"},!p&&t.createElement("div",{className:"survey-edit-explainer"},!f&&n.editAllowed&&"The survey is in read-only mode; click the “Start editing“ button to begin editing the answers.",!f&&!n.editAllowed&&"The survey is in read-only mode and can not be edited by you.",f&&f!=b.name&&"The survey is in read-only mode and currently being edited by: "+f+". To start editing the survey, ask them to complete their edits.",f&&f==b.name&&'The survey is in read-only mode because you started editing in another tab, browser or device. To start editing the survey, either complete those edits or click the "Discard any unsaved changes" button.')),t.createElement(Ft,null,t.createElement(fT,{surveyModel:n,pageNoSetter:w}),s),t.createElement(Ft,null,R()))},gT=function(e){var n=e.when,r=e.onPageExit;return function(e){let{router:n,basename:r}=rt(tt.UseBlocker),o=ot(nt.UseBlocker),[i,s]=t.useState(""),a=t.useCallback((t=>{if("/"===r)return e();let{currentLocation:n,nextLocation:o,historyAction:i}=t;return e((je({},n,{pathname:A(n.pathname,r)||n.pathname}),je({},o,{pathname:A(o.pathname,r)||o.pathname})))}),[r,e]);t.useEffect((()=>{let e=String(++st);return s(e),()=>n.deleteBlocker(e)}),[n]),t.useEffect((()=>{""!==i&&n.getBlocker(i,a)}),[n,i,a]),i&&o.blockers.has(i)&&o.blockers.get(i)}((function(){if(n()){var t=window.confirm(e.message);return t&&r(),!t}return!1})),t.createElement("div",null)};oT.Serializer.addProperty("itemvalue","customDescription:text"),oT.Serializer.addProperty("question","hideCheckboxLabels:boolean");const yT=function(e){var n=e.loadFrom,r=Cn((0,t.useState)(),2),o=r[0],i=r[1],s=function(){let{matches:e}=t.useContext(ze),n=e[e.length-1];return n?n.params:{}}(),a=s.year,l=s.nren,u=Cn((0,t.useState)("loading survey..."),2),c=u[0],p=u[1],d=An().trackPageView,h=(0,t.useCallback)((function(e){return e.preventDefault(),e.returnValue=""}),[]),f=(0,t.useCallback)((function(){window.navigator.sendBeacon("/api/response/unlock/"+a+"/"+l)}),[]),m=(0,t.useCallback)((function(){window.navigator.sendBeacon("/api/response/unlock/"+a+"/"+l),removeEventListener("beforeunload",h,{capture:!0}),removeEventListener("pagehide",f)}),[]);if((0,t.useEffect)((function(){function e(){return(e=Fu(qu().mark((function e(){var t,r,o,s;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,fetch(n+a+(l?"/"+l:""));case 2:return t=e.sent,e.next=5,t.json();case 5:if(r=e.sent,t.ok){e.next=12;break}if(!("message"in r)){e.next=11;break}throw new Error(r.message);case 11:throw new Error("Request failed with status ".concat(t.status));case 12:for(s in(o=new oT.Model(r.model)).setVariable("surveyyear",a),o.setVariable("previousyear",parseInt(a)-1),o.showNavigationButtons=!1,o.requiredText="",o.verificationStatus=new Map,r.verification_status)o.verificationStatus.set(s,r.verification_status[s]);o.data=r.data,o.clearIncorrectValues(!0),o.currentPageNo=r.page,o.mode=r.mode,o.lockedBy=r.locked_by,o.status=r.status,o.editAllowed=r.edit_allowed,i(o);case 27:case"end":return e.stop()}}),e)})))).apply(this,arguments)}(function(){return e.apply(this,arguments)})().catch((function(e){return p("Error when loading survey: "+e.message)})).then((function(){d({documentTitle:"Survey for ".concat(l," (").concat(a,")")})}))}),[]),!o)return t.createElement(t.Fragment,null,c);var g,y,v,b,C,w=function(){var e=Fu(qu().mark((function e(t,n){var r,i,s;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(l){e.next=2;break}return e.abrupt("return","Saving not available in inpect/try mode");case 2:return r={lock_uuid:t.lockUUID,new_state:n,data:t.data,page:t.currentPageNo,verification_status:Object.fromEntries(t.verificationStatus)},e.prev=3,e.next=6,fetch("/api/response/save/"+a+"/"+l,{method:"POST",headers:{"Content-Type":"application/json; charset=utf-8"},body:JSON.stringify(r)});case 6:return i=e.sent,e.next=9,i.json();case 9:if(s=e.sent,i.ok){e.next=12;break}return e.abrupt("return",s.message);case 12:o.mode=s.mode,o.lockedBy=s.locked_by,o.status=s.status,e.next=20;break;case 17:return e.prev=17,e.t0=e.catch(3),e.abrupt("return","Unknown Error: "+e.t0.message);case 20:case"end":return e.stop()}}),e,null,[[3,17]])})));return function(t,n){return e.apply(this,arguments)}}(),x=function(e){var t="",n=function(e,n){e.verificationStatus.get(n.name)==BO.Unverified&&(""==t&&(t=n.name),n.error='Please verify that last years data is correct by editing the answer or pressing the "No change from previous year" button!')};o.onValidateQuestion.add(n);var r=e();return o.onValidateQuestion.remove(n),r||FO("Validation failed!"),r},E={save:(C=Fu(qu().mark((function e(){var t;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,w(o,"editing");case 2:t=e.sent,FO(t?"Failed saving survey: "+t:"Survey saved!");case 4:case"end":return e.stop()}}),e)}))),function(){return C.apply(this,arguments)}),complete:(b=Fu(qu().mark((function e(){var t;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!x(o.validate.bind(o,!0,!0))){e.next=6;break}return e.next=4,w(o,"completed");case 4:(t=e.sent)?FO("Failed completing survey: "+t):(FO("Survey completed!"),removeEventListener("beforeunload",h,{capture:!0}),removeEventListener("pagehide",f));case 6:case"end":return e.stop()}}),e)}))),function(){return b.apply(this,arguments)}),saveAndStopEdit:(v=Fu(qu().mark((function e(){var t;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,w(o,"readonly");case 2:(t=e.sent)?FO("Failed saving survey: "+t):(FO("Survey saved!"),removeEventListener("beforeunload",h,{capture:!0}),removeEventListener("pagehide",f));case 4:case"end":return e.stop()}}),e)}))),function(){return v.apply(this,arguments)}),startEdit:(y=Fu(qu().mark((function e(){var t,n,r;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,fetch("/api/response/lock/"+a+"/"+l,{method:"POST"});case 2:return t=e.sent,e.next=5,t.json();case 5:if(n=e.sent,t.ok){e.next=9;break}return FO("Failed starting edit: "+n.message),e.abrupt("return");case 9:for(r in addEventListener("pagehide",f),addEventListener("beforeunload",h,{capture:!0}),n.verification_status)o.verificationStatus.set(r,n.verification_status[r]);o.data=n.data,o.clearIncorrectValues(!0),o.mode=n.mode,o.lockedBy=n.locked_by,o.lockUUID=n.lock_uuid,o.status=n.status;case 18:case"end":return e.stop()}}),e)}))),function(){return y.apply(this,arguments)}),releaseLock:(g=Fu(qu().mark((function e(){var t,n;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,fetch("/api/response/unlock/"+a+"/"+l,{method:"POST"});case 2:return t=e.sent,e.next=5,t.json();case 5:if(n=e.sent,t.ok){e.next=9;break}return FO("Failed releasing lock: "+n.message),e.abrupt("return");case 9:o.mode=n.mode,o.lockedBy=n.locked_by,o.status=n.status;case 12:case"end":return e.stop()}}),e)}))),function(){return g.apply(this,arguments)}),validatePage:function(){x(o.validatePage.bind(o))&&FO("Page validation successful!")}};return t.createElement(Lt,{className:"survey-container"},t.createElement(jO,null),t.createElement(gT,{message:"Are you sure you want to leave this page? Information you've entered may not be saved.",when:function(){return"edit"==o.mode&&!!l},onPageExit:m}),t.createElement(mT,{surveyModel:o,surveyActions:E,year:a,nren:l},t.createElement(pT,{surveyModel:o})))},vT=function(){var e=An().trackPageView,n=(0,t.useContext)(Qu).user,r=Ge(),o=!!n.id,i=!!o&&!!n.nrens.length,s=i?n.nrens[0]:"",a=!!o&&n.permissions.admin,l=!!o&&"observer"===n.role,u=Cn((0,t.useState)(null),2),c=u[0],p=u[1];(0,t.useEffect)((function(){var t=function(){var e=Fu(qu().mark((function e(){var t;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,UO();case 2:t=e.sent,p(t);case 4:case"end":return e.stop()}}),e)})));return function(){return e.apply(this,arguments)}}();t(),e({documentTitle:"GEANT Survey Landing Page"})}),[e]);var d=function(){var e=Cn((0,t.useState)(),2),n=e[0],r=e[1];return(0,t.useEffect)((function(){zO().then((function(e){r(e[0])}))}),[]),t.createElement(Wx,{striped:!0,bordered:!0,responsive:!0},t.createElement("thead",null,t.createElement("tr",null,t.createElement("th",null,"(N)REN"),t.createElement("th",null,"Link"),t.createElement("th",null,"Survey Status"))),t.createElement("tbody",null,n&&n.responses.map((function(e){return t.createElement("tr",{key:e.nren},t.createElement("td",null,e.nren),t.createElement("td",null,t.createElement(Et,{to:"/survey/response/".concat(n.year,"/").concat(e.nren)},t.createElement("span",null,"Navigate to survey"))),t.createElement("td",null,e.status))}))))};return t.createElement(Lt,{className:"py-5 grey-container"},t.createElement(Ft,null,t.createElement("div",{className:"center-text"},t.createElement("h1",{className:"geant-header"},"THE GÉANT COMPENDIUM OF NRENS SURVEY"),t.createElement("div",{className:"wordwrap pt-4",style:{maxWidth:"75rem"}},t.createElement("p",{style:{textAlign:"left"}},"Hello,",t.createElement("br",null),"Welcome to the GÉANT Compendium Survey. (N)REN Compendium administrators can login via Single Sign On (SSO) ",t.createElement("a",{href:"/login"},"here"),", which will complete their registration to fill in the latest Compendium survey. This will send a notification to the Compendium administration team and they will assign you to your (N)REN.",t.createElement("br",null),"Once this step has been completed, you will receive an email from the administration team. We aim to get back to you the same working day, but sometimes may take a little longer.",t.createElement("br",null),"If you are not sure whether you are a Compendium Administrator for your (N)REN, please contact your GÉANT Partner Relations relationship manager.",t.createElement("br",null),"Thank you."),t.createElement("span",null,"Current registration status:"),t.createElement("br",null),t.createElement("br",null),a?t.createElement("ul",null,t.createElement("li",null,t.createElement("span",null,"You are logged in as a Compendium Administrator")),t.createElement("li",null,t.createElement("span",null,"Click ",t.createElement(Et,{to:"/survey/admin/surveys"},"here")," to access the survey management page.")),t.createElement("li",null,t.createElement("span",null,"Click ",t.createElement(Et,{to:"/survey/admin/users"},"here")," to access the user management page.")),t.createElement("li",null,t.createElement("span",null,"Click ",t.createElement("a",{href:"#",onClick:function(){fetch("/api/data-download").then((function(e){if(!e.ok)throw new Error("Network response was not ok");return e.json()})).then((function(e){var t=function(e){var t=Py.book_new();e.forEach((function(e){var n=Py.json_to_sheet(e.data);e.meta&&function(e,t,n){for(var r,o=Py.decode_range(null!==(r=e["!ref"])&&void 0!==r?r:""),i=-1,s=o.s.c;s<=o.e.c;s++){var a=e[Py.encode_cell({r:o.s.r,c:s})];if(a&&"string"==typeof a.v&&a.v===t){i=s;break}}if(-1!==i)for(var l=o.s.r+1;l<=o.e.r;++l){var u=Py.encode_cell({r:l,c:i});e[u]&&"n"===e[u].t&&(e[u].z=n)}else console.error("Column '".concat(t,"' not found."))}(n,e.meta.columnName,e.meta.format),Py.book_append_sheet(t,n,e.name)}));for(var n=fy(t,{bookType:"xlsx",type:"binary"}),r=new ArrayBuffer(n.length),o=new Uint8Array(r),i=0;i<n.length;i++)o[i]=255&n.charCodeAt(i);return new Blob([r],{type:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8"})}(e),n=document.createElement("a");n.href=URL.createObjectURL(t),n.download="data.xlsx",document.body.appendChild(n),n.click(),document.body.removeChild(n)})).catch((function(e){console.error("Error fetching data:",e)}))}},"here")," to do the full data download."))):t.createElement("ul",null,c&&!a&&!l&&i&&function(){try{return r("/survey/response/".concat(c,"/").concat(s)),t.createElement("li",null,"Redirecting to survey...")}catch(e){return console.error("Error navigating:",e),null}}(),o?t.createElement("li",null,t.createElement("span",null,"You are logged in")):t.createElement("li",null,t.createElement("span",null,"You are not logged in")),o&&!l&&!i&&t.createElement("li",null,t.createElement("span",null,"Your access to the survey has not yet been approved")),o&&!l&&!i&&t.createElement("li",null,t.createElement("span",null,"Once you have been approved, you will immediately be directed to the relevant survey upon visiting this page")),o&&l&&t.createElement("li",null,t.createElement("span",null,"You have read-only access to the following surveys:"))),o&&l&&t.createElement(d,null)))))};var bT,CT=(bT=[{path:"/budget",element:t.createElement(sx,null)},{path:"/funding",element:t.createElement(Qx,null)},{path:"/data/employment",element:t.createElement(eE,null)},{path:"/data/roles",element:t.createElement(eE,{roles:!0})},{path:"/employee-count",element:t.createElement(nE,null)},{path:"/charging",element:t.createElement(Gx,null)},{path:"/suborganisations",element:t.createElement(rE,null)},{path:"/parentorganisation",element:t.createElement(oE,null)},{path:"/ec-projects",element:t.createElement(iE,null)},{path:"/policy",element:t.createElement(uE,null)},{path:"/traffic-volume",element:t.createElement(nP,null)},{path:"/data",element:t.createElement(ux,null)},{path:"/institutions-urls",element:t.createElement(dE,null)},{path:"/connected-proportion",element:t.createElement(TE,{connectivity_category:fE.ConnectedProportion.toString()})},{path:"/connectivity-level",element:t.createElement(TE,{connectivity_category:fE.ConnectivityLevel.toString()})},{path:"/connectivity-growth",element:t.createElement(TE,{connectivity_category:fE.ConnectivityGrowth.toString()})},{path:"/connection-carrier",element:t.createElement(TE,{connectivity_category:fE.ConnectionCarrier.toString()})},{path:"/connectivity-load",element:t.createElement(TE,{connectivity_category:fE.ConnectivityLoad.toString()})},{path:"/commercial-charging-level",element:t.createElement(TE,{connectivity_category:fE.CommercialChargingLevel.toString()})},{path:"/commercial-connectivity",element:t.createElement(TE,{connectivity_category:fE.CommercialConnectivity.toString()})},{path:"/network-services",element:t.createElement(vE,{category:hE.network_services})},{path:"/isp-support-services",element:t.createElement(vE,{category:hE.isp_support})},{path:"/security-services",element:t.createElement(vE,{category:hE.security})},{path:"/identity-services",element:t.createElement(vE,{category:hE.identity})},{path:"/collaboration-services",element:t.createElement(vE,{category:hE.collaboration})},{path:"/multimedia-services",element:t.createElement(vE,{category:hE.multimedia})},{path:"/storage-and-hosting-services",element:t.createElement(vE,{category:hE.storage_and_hosting})},{path:"/professional-services",element:t.createElement(vE,{category:hE.professional_services})},{path:"/fibre-light",element:t.createElement(_E,null)},{path:"/monitoring-tools",element:t.createElement(VE,null)},{path:"/pert-team",element:t.createElement(RE,null)},{path:"/passive-monitoring",element:t.createElement(IE,null)},{path:"/alien-wave",element:t.createElement(kE,null)},{path:"/alien-wave-internal",element:t.createElement(AE,null)},{path:"/ops-automation",element:t.createElement(DE,null)},{path:"/network-automation",element:t.createElement(NE,null)},{path:"/traffic-stats",element:t.createElement(ME,null)},{path:"/weather-map",element:t.createElement(LE,null)},{path:"/network-map",element:t.createElement(jE,null)},{path:"/nfv",element:t.createElement(FE,null)},{path:"/certificate-provider",element:t.createElement(BE,null)},{path:"/siem-vendors",element:t.createElement(qE,null)},{path:"/capacity-largest-link",element:t.createElement(zE,null)},{path:"/capacity-core-ip",element:t.createElement(UE,null)},{path:"/non-rne-peers",element:t.createElement($E,null)},{path:"/iru-duration",element:t.createElement(JE,null)},{path:"/iru-duration",element:t.createElement(JE,null)},{path:"/audits",element:t.createElement(YE,null)},{path:"/business-continuity",element:t.createElement(KE,null)},{path:"/crisis-management",element:t.createElement(ZE,null)},{path:"/crisis-exercise",element:t.createElement(XE,null)},{path:"/security-control",element:t.createElement(eP,null)},{path:"survey/admin/surveys",element:t.createElement(YO,null)},{path:"survey/admin/users",element:t.createElement(rT,null)},{path:"survey/admin/inspect/:year",element:t.createElement(yT,{loadFrom:"/api/response/inspect/"})},{path:"survey/admin/try/:year",element:t.createElement(yT,{loadFrom:"/api/response/try/"})},{path:"survey/response/:year/:nren",element:t.createElement(yT,{loadFrom:"/api/response/load/"})},{path:"survey/*",element:t.createElement(vT,null)},{path:"*",element:t.createElement(Dn,null)}],function(t){const n=t.window?t.window:"undefined"!=typeof window?window:void 0,r=void 0!==n&&void 0!==n.document&&void 0!==n.document.createElement,o=!r;let i;if(u(t.routes.length>0,"You must provide a non-empty routes array to createRouter"),t.mapRouteProperties)i=t.mapRouteProperties;else if(t.detectErrorBoundary){let e=t.detectErrorBoundary;i=t=>({hasErrorBoundary:e(t)})}else i=te;let s,l,p,h={},f=y(t.routes,i,void 0,h),g=t.basename||"/",C=t.unstable_dataStrategy||pe,w=t.unstable_patchRoutesOnMiss,x=a({v7_fetcherPersist:!1,v7_normalizeFormMethod:!1,v7_partialHydration:!1,v7_prependBasename:!1,v7_relativeSplatPath:!1,unstable_skipActionErrorRevalidation:!1},t.future),E=null,P=new Set,S=null,O=null,T=null,_=null!=t.hydrationData,V=v(f,t.history.location,g),R=null;if(null==V&&!w){let e=xe(404,{pathname:t.history.location.pathname}),{matches:n,route:r}=we(f);V=n,R={[r.id]:e}}if(V&&w&&ct(V,f,t.history.location.pathname).active&&(V=null),V)if(V.some((e=>e.route.lazy)))l=!1;else if(V.some((e=>e.route.loader)))if(x.v7_partialHydration){let e=t.hydrationData?t.hydrationData.loaderData:null,n=t.hydrationData?t.hydrationData.errors:null,r=t=>!t.route.loader||("function"!=typeof t.route.loader||!0!==t.route.loader.hydrate)&&(e&&void 0!==e[t.route.id]||n&&void 0!==n[t.route.id]);if(n){let e=V.findIndex((e=>void 0!==n[e.route.id]));l=V.slice(0,e+1).every(r)}else l=V.every(r)}else l=null!=t.hydrationData;else l=!0;else l=!1,V=[];let I,k={historyAction:t.history.action,location:t.history.location,matches:V,initialized:l,navigation:K,restoreScrollPosition:null==t.hydrationData&&null,preventScrollReset:!1,revalidation:"idle",loaderData:t.hydrationData&&t.hydrationData.loaderData||{},actionData:t.hydrationData&&t.hydrationData.actionData||null,errors:t.hydrationData&&t.hydrationData.errors||R,fetchers:new Map,blockers:new Map},D=e.Pop,N=!1,M=!1,L=new Map,j=null,F=!1,B=!1,q=[],Q=[],U=new Map,W=0,$=-1,G=new Map,se=new Set,ae=new Map,me=new Map,ge=new Set,Pe=new Map,ke=new Map,je=new Map,Fe=!1;function Be(e,t){void 0===t&&(t={}),k=a({},k,e);let n=[],r=[];x.v7_fetcherPersist&&k.fetchers.forEach(((e,t)=>{"idle"===e.state&&(ge.has(t)?r.push(t):n.push(t))})),[...P].forEach((e=>e(k,{deletedFetchers:r,unstable_viewTransitionOpts:t.viewTransitionOpts,unstable_flushSync:!0===t.flushSync}))),x.v7_fetcherPersist&&(n.forEach((e=>k.fetchers.delete(e))),r.forEach((e=>Ke(e))))}function qe(n,r,o){var i,l;let u,{flushSync:c}=void 0===o?{}:o,p=null!=k.actionData&&null!=k.navigation.formMethod&&Ve(k.navigation.formMethod)&&"loading"===k.navigation.state&&!0!==(null==(i=n.state)?void 0:i._isRedirect);u=r.actionData?Object.keys(r.actionData).length>0?r.actionData:null:p?k.actionData:null;let d=r.loaderData?ve(k.loaderData,r.loaderData,r.matches||[],r.errors):k.loaderData,h=k.blockers;h.size>0&&(h=new Map(h),h.forEach(((e,t)=>h.set(t,Z))));let m,g=!0===N||null!=k.navigation.formMethod&&Ve(k.navigation.formMethod)&&!0!==(null==(l=n.state)?void 0:l._isRedirect);if(s&&(f=s,s=void 0),F||D===e.Pop||(D===e.Push?t.history.push(n,n.state):D===e.Replace&&t.history.replace(n,n.state)),D===e.Pop){let e=L.get(k.location.pathname);e&&e.has(n.pathname)?m={currentLocation:k.location,nextLocation:n}:L.has(n.pathname)&&(m={currentLocation:n,nextLocation:k.location})}else if(M){let e=L.get(k.location.pathname);e?e.add(n.pathname):(e=new Set([n.pathname]),L.set(k.location.pathname,e)),m={currentLocation:k.location,nextLocation:n}}Be(a({},r,{actionData:u,loaderData:d,historyAction:D,location:n,initialized:!0,navigation:K,revalidation:"idle",restoreScrollPosition:ut(n,r.matches||k.matches),preventScrollReset:g,blockers:h}),{viewTransitionOpts:m,flushSync:!0===c}),D=e.Pop,N=!1,M=!1,F=!1,B=!1,q=[],Q=[]}async function He(n,r,o){I&&I.abort(),I=null,D=n,F=!0===(o&&o.startUninterruptedRevalidation),function(e,t){if(S&&T){let n=lt(e,t);S[n]=T()}}(k.location,k.matches),N=!0===(o&&o.preventScrollReset),M=!0===(o&&o.enableViewTransition);let i=s||f,l=o&&o.overrideNavigation,u=v(i,r,g),c=!0===(o&&o.flushSync),p=ct(u,i,r.pathname);if(p.active&&p.matches&&(u=p.matches),!u){let{error:e,notFoundMatches:t,route:n}=it(r.pathname);return void qe(r,{matches:t,loaderData:{},errors:{[n.id]:e}},{flushSync:c})}if(k.initialized&&!B&&function(e,t){return e.pathname===t.pathname&&e.search===t.search&&(""===e.hash?""!==t.hash:e.hash===t.hash||""!==t.hash)}(k.location,r)&&!(o&&o.submission&&Ve(o.submission.formMethod)))return void qe(r,{matches:u},{flushSync:c});I=new AbortController;let d,h=fe(t.history,r,I.signal,o&&o.submission);if(o&&o.pendingError)d=[Ce(u).route.id,{type:m.error,error:o.pendingError}];else if(o&&o.submission&&Ve(o.submission.formMethod)){let n=await async function(t,n,r,o,i,s){void 0===s&&(s={}),$e();let a,l=function(e,t){return{state:"submitting",location:e,formMethod:t.formMethod,formAction:t.formAction,formEncType:t.formEncType,formData:t.formData,json:t.json,text:t.text}}(n,r);if(Be({navigation:l},{flushSync:!0===s.flushSync}),i){let e=await pt(o,n.pathname,t.signal);if("aborted"===e.type)return{shortCircuited:!0};if("error"===e.type){let{error:t,notFoundMatches:r,route:o}=st(n.pathname,e);return{matches:r,pendingActionResult:[o.id,{type:m.error,error:t}]}}if(!e.matches){let{notFoundMatches:e,error:t,route:r}=it(n.pathname);return{matches:e,pendingActionResult:[r.id,{type:m.error,error:t}]}}o=e.matches}let u=Ae(o,n);if(u.route.action||u.route.lazy){if(a=(await Ue("action",t,[u],o))[0],t.signal.aborted)return{shortCircuited:!0}}else a={type:m.error,error:xe(405,{method:t.method,pathname:n.pathname,routeId:u.route.id})};if(Te(a)){let e;return e=s&&null!=s.replace?s.replace:he(a.response.headers.get("Location"),new URL(t.url),g)===k.location.pathname+k.location.search,await Qe(t,a,{submission:r,replace:e}),{shortCircuited:!0}}if(Se(a))throw xe(400,{type:"defer-action"});if(Oe(a)){let t=Ce(o,u.route.id);return!0!==(s&&s.replace)&&(D=e.Push),{matches:o,pendingActionResult:[t.route.id,a]}}return{matches:o,pendingActionResult:[u.route.id,a]}}(h,r,o.submission,u,p.active,{replace:o.replace,flushSync:c});if(n.shortCircuited)return;if(n.pendingActionResult){let[e,t]=n.pendingActionResult;if(Oe(t)&&z(t.error)&&404===t.error.status)return I=null,void qe(r,{matches:n.matches,loaderData:{},errors:{[e]:t.error}})}u=n.matches||u,d=n.pendingActionResult,l=Ne(r,o.submission),c=!1,p.active=!1,h=fe(t.history,h.url,h.signal)}let{shortCircuited:y,matches:b,loaderData:C,errors:w}=await async function(e,n,r,o,i,l,u,c,p,d,h){let m=i||Ne(n,l),y=l||u||De(m),v=!(F||x.v7_partialHydration&&p);if(o){if(v){let e=ze(h);Be(a({navigation:m},void 0!==e?{actionData:e}:{}),{flushSync:d})}let t=await pt(r,n.pathname,e.signal);if("aborted"===t.type)return{shortCircuited:!0};if("error"===t.type){let{error:e,notFoundMatches:r,route:o}=st(n.pathname,t);return{matches:r,loaderData:{},errors:{[o.id]:e}}}if(!t.matches){let{error:e,notFoundMatches:t,route:r}=it(n.pathname);return{matches:t,loaderData:{},errors:{[r.id]:e}}}r=t.matches}let b=s||f,[C,w]=ie(t.history,k,r,y,n,x.v7_partialHydration&&!0===p,x.unstable_skipActionErrorRevalidation,B,q,Q,ge,ae,se,b,g,h);if(at((e=>!(r&&r.some((t=>t.route.id===e)))||C&&C.some((t=>t.route.id===e)))),$=++W,0===C.length&&0===w.length){let e=et();return qe(n,a({matches:r,loaderData:{},errors:h&&Oe(h[1])?{[h[0]]:h[1].error}:null},be(h),e?{fetchers:new Map(k.fetchers)}:{}),{flushSync:d}),{shortCircuited:!0}}if(v){let e={};if(!o){e.navigation=m;let t=ze(h);void 0!==t&&(e.actionData=t)}w.length>0&&(e.fetchers=function(e){return e.forEach((e=>{let t=k.fetchers.get(e.key),n=Me(void 0,t?t.data:void 0);k.fetchers.set(e.key,n)})),new Map(k.fetchers)}(w)),Be(e,{flushSync:d})}w.forEach((e=>{U.has(e.key)&&Xe(e.key),e.controller&&U.set(e.key,e.controller)}));let E=()=>w.forEach((e=>Xe(e.key)));I&&I.signal.addEventListener("abort",E);let{loaderResults:P,fetcherResults:S}=await We(k.matches,r,C,w,e);if(e.signal.aborted)return{shortCircuited:!0};I&&I.signal.removeEventListener("abort",E),w.forEach((e=>U.delete(e.key)));let O=Ee([...P,...S]);if(O){if(O.idx>=C.length){let e=w[O.idx-C.length].key;se.add(e)}return await Qe(e,O.result,{replace:c}),{shortCircuited:!0}}let{loaderData:T,errors:_}=ye(k,r,C,P,h,w,S,Pe);Pe.forEach(((e,t)=>{e.subscribe((n=>{(n||e.done)&&Pe.delete(t)}))})),x.v7_partialHydration&&p&&k.errors&&Object.entries(k.errors).filter((e=>{let[t]=e;return!C.some((e=>e.route.id===t))})).forEach((e=>{let[t,n]=e;_=Object.assign(_||{},{[t]:n})}));let V=et(),R=tt($),A=V||R||w.length>0;return a({matches:r,loaderData:T,errors:_},A?{fetchers:new Map(k.fetchers)}:{})}(h,r,u,p.active,l,o&&o.submission,o&&o.fetcherSubmission,o&&o.replace,o&&!0===o.initialHydration,c,d);y||(I=null,qe(r,a({matches:b||u},be(d),{loaderData:C,errors:w})))}function ze(e){return e&&!Oe(e[1])?{[e[0]]:e[1].data}:k.actionData?0===Object.keys(k.actionData).length?null:k.actionData:void 0}async function Qe(o,i,s){let{submission:l,fetcherSubmission:c,replace:p}=void 0===s?{}:s;i.response.headers.has("X-Remix-Revalidate")&&(B=!0);let h=i.response.headers.get("Location");u(h,"Expected a Location header on the redirect Response"),h=he(h,new URL(o.url),g);let f=d(k.location,h,{_isRedirect:!0});if(r){let e=!1;if(i.response.headers.has("X-Remix-Reload-Document"))e=!0;else if(ee.test(h)){const r=t.history.createURL(h);e=r.origin!==n.location.origin||null==A(r.pathname,g)}if(e)return void(p?n.location.replace(h):n.location.assign(h))}I=null;let m=!0===p?e.Replace:e.Push,{formMethod:y,formAction:v,formEncType:b}=k.navigation;!l&&!c&&y&&v&&b&&(l=De(k.navigation));let C=l||c;if(Y.has(i.response.status)&&C&&Ve(C.formMethod))await He(m,f,{submission:a({},C,{formAction:h}),preventScrollReset:N});else{let e=Ne(f,l);await He(m,f,{overrideNavigation:e,fetcherSubmission:c,preventScrollReset:N})}}async function Ue(e,t,n,r){try{let o=await async function(e,t,n,r,o,i,s,l){let c=r.reduce(((e,t)=>e.add(t.route.id)),new Set),p=new Set,d=await e({matches:o.map((e=>{let r=c.has(e.route.id);return a({},e,{shouldLoad:r,resolve:o=>(p.add(e.route.id),r?async function(e,t,n,r,o,i,s){let a,l,c=r=>{let o,a=new Promise(((e,t)=>o=t));l=()=>o(),t.signal.addEventListener("abort",l);let u,c=o=>"function"!=typeof r?Promise.reject(new Error('You cannot call the handler for a route which defines a boolean "'+e+'" [routeId: '+n.route.id+"]")):r({request:t,params:n.params,context:s},...void 0!==o?[o]:[]);return u=i?i((e=>c(e))):(async()=>{try{return{type:"data",result:await c()}}catch(e){return{type:"error",result:e}}})(),Promise.race([u,a])};try{let i=n.route[e];if(n.route.lazy)if(i){let e,[t]=await Promise.all([c(i).catch((t=>{e=t})),ce(n.route,o,r)]);if(void 0!==e)throw e;a=t}else{if(await ce(n.route,o,r),i=n.route[e],!i){if("action"===e){let e=new URL(t.url),r=e.pathname+e.search;throw xe(405,{method:t.method,pathname:r,routeId:n.route.id})}return{type:m.data,result:void 0}}a=await c(i)}else{if(!i){let e=new URL(t.url);throw xe(404,{pathname:e.pathname+e.search})}a=await c(i)}u(void 0!==a.result,"You defined "+("action"===e?"an action":"a loader")+' for route "'+n.route.id+"\" but didn't return anything from your `"+e+"` function. Please return a value or `null`.")}catch(e){return{type:m.error,result:e}}finally{l&&t.signal.removeEventListener("abort",l)}return a}(t,n,e,i,s,o,l):Promise.resolve({type:m.data,result:void 0}))})})),request:n,params:o[0].params,context:l});return o.forEach((e=>u(p.has(e.route.id),'`match.resolve()` was not called for route id "'+e.route.id+'". You must call `match.resolve()` on every match passed to `dataStrategy` to ensure all routes are properly loaded.'))),d.filter(((e,t)=>c.has(o[t].route.id)))}(C,e,t,n,r,h,i);return await Promise.all(o.map(((e,o)=>{if(function(e){return _e(e.result)&&J.has(e.result.status)}(e)){let i=e.result;return{type:m.redirect,response:de(i,t,n[o].route.id,r,g,x.v7_relativeSplatPath)}}return async function(e){let{result:t,type:n,status:r}=e;if(_e(t)){let e;try{let n=t.headers.get("Content-Type");e=n&&/\bapplication\/json\b/.test(n)?null==t.body?null:await t.json():await t.text()}catch(e){return{type:m.error,error:e}}return n===m.error?{type:m.error,error:new H(t.status,t.statusText,e),statusCode:t.status,headers:t.headers}:{type:m.data,data:e,statusCode:t.status,headers:t.headers}}return n===m.error?{type:m.error,error:t,statusCode:z(t)?t.status:r}:function(e){let t=e;return t&&"object"==typeof t&&"object"==typeof t.data&&"function"==typeof t.subscribe&&"function"==typeof t.cancel&&"function"==typeof t.resolveData}(t)?{type:m.deferred,deferredData:t,statusCode:null==(o=t.init)?void 0:o.status,headers:(null==(i=t.init)?void 0:i.headers)&&new Headers(t.init.headers)}:{type:m.data,data:t,statusCode:r};var o,i}(e)})))}catch(e){return n.map((()=>({type:m.error,error:e})))}}async function We(e,n,r,o,i){let[s,...a]=await Promise.all([r.length?Ue("loader",i,r,n):[],...o.map((e=>e.matches&&e.match&&e.controller?Ue("loader",fe(t.history,e.path,e.controller.signal),[e.match],e.matches).then((e=>e[0])):Promise.resolve({type:m.error,error:xe(404,{pathname:e.path})})))]);return await Promise.all([Re(e,r,s,s.map((()=>i.signal)),!1,k.loaderData),Re(e,o.map((e=>e.match)),a,o.map((e=>e.controller?e.controller.signal:null)),!0)]),{loaderResults:s,fetcherResults:a}}function $e(){B=!0,q.push(...at()),ae.forEach(((e,t)=>{U.has(t)&&(Q.push(t),Xe(t))}))}function Ge(e,t,n){void 0===n&&(n={}),k.fetchers.set(e,t),Be({fetchers:new Map(k.fetchers)},{flushSync:!0===(n&&n.flushSync)})}function Je(e,t,n,r){void 0===r&&(r={});let o=Ce(k.matches,t);Ke(e),Be({errors:{[o.route.id]:n},fetchers:new Map(k.fetchers)},{flushSync:!0===(r&&r.flushSync)})}function Ye(e){return x.v7_fetcherPersist&&(me.set(e,(me.get(e)||0)+1),ge.has(e)&&ge.delete(e)),k.fetchers.get(e)||X}function Ke(e){let t=k.fetchers.get(e);!U.has(e)||t&&"loading"===t.state&&G.has(e)||Xe(e),ae.delete(e),G.delete(e),se.delete(e),ge.delete(e),k.fetchers.delete(e)}function Xe(e){let t=U.get(e);u(t,"Expected fetch controller: "+e),t.abort(),U.delete(e)}function Ze(e){for(let t of e){let e=Le(Ye(t).data);k.fetchers.set(t,e)}}function et(){let e=[],t=!1;for(let n of se){let r=k.fetchers.get(n);u(r,"Expected fetcher: "+n),"loading"===r.state&&(se.delete(n),e.push(n),t=!0)}return Ze(e),t}function tt(e){let t=[];for(let[n,r]of G)if(r<e){let e=k.fetchers.get(n);u(e,"Expected fetcher: "+n),"loading"===e.state&&(Xe(n),G.delete(n),t.push(n))}return Ze(t),t.length>0}function nt(e){k.blockers.delete(e),ke.delete(e)}function rt(e,t){let n=k.blockers.get(e)||Z;u("unblocked"===n.state&&"blocked"===t.state||"blocked"===n.state&&"blocked"===t.state||"blocked"===n.state&&"proceeding"===t.state||"blocked"===n.state&&"unblocked"===t.state||"proceeding"===n.state&&"unblocked"===t.state,"Invalid blocker state transition: "+n.state+" -> "+t.state);let r=new Map(k.blockers);r.set(e,t),Be({blockers:r})}function ot(e){let{currentLocation:t,nextLocation:n,historyAction:r}=e;if(0===ke.size)return;ke.size>1&&c(!1,"A router only supports one blocker at a time");let o=Array.from(ke.entries()),[i,s]=o[o.length-1],a=k.blockers.get(i);return a&&"proceeding"===a.state?void 0:s({currentLocation:t,nextLocation:n,historyAction:r})?i:void 0}function it(e){let t=xe(404,{pathname:e}),n=s||f,{matches:r,route:o}=we(n);return at(),{notFoundMatches:r,route:o,error:t}}function st(e,t){let n=t.partialMatches,r=n[n.length-1].route;return{notFoundMatches:n,route:r,error:xe(400,{type:"route-discovery",routeId:r.id,pathname:e,message:null!=t.error&&"message"in t.error?t.error:String(t.error)})}}function at(e){let t=[];return Pe.forEach(((n,r)=>{e&&!e(r)||(n.cancel(),t.push(r),Pe.delete(r))})),t}function lt(e,t){if(O){return O(e,t.map((e=>function(e,t){let{route:n,pathname:r,params:o}=e;return{id:n.id,pathname:r,params:o,data:t[n.id],handle:n.handle}}(e,k.loaderData))))||e.key}return e.key}function ut(e,t){if(S){let n=lt(e,t),r=S[n];if("number"==typeof r)return r}return null}function ct(e,t,n){if(w){if(!e)return{active:!0,matches:b(t,n,g,!0)||[]};{let r=e[e.length-1].route;if(r.path&&("*"===r.path||r.path.endsWith("/*")))return{active:!0,matches:b(t,n,g,!0)}}}return{active:!1,matches:null}}async function pt(e,t,n){let r=e,o=r.length>0?r[r.length-1].route:null;for(;;){let e=null==s,a=s||f;try{await le(w,t,r,a,h,i,je,n)}catch(e){return{type:"error",error:e,partialMatches:r}}finally{e&&(f=[...f])}if(n.aborted)return{type:"aborted"};let l=v(a,t,g),u=!1;if(l){let e=l[l.length-1].route;if(e.index)return{type:"success",matches:l};if(e.path&&e.path.length>0){if("*"!==e.path)return{type:"success",matches:l};u=!0}}let c=b(a,t,g,!0);if(!c||r.map((e=>e.route.id)).join("-")===c.map((e=>e.route.id)).join("-"))return{type:"success",matches:u?l:null};if(r=c,o=r[r.length-1].route,"*"===o.path)return{type:"success",matches:r}}}return p={get basename(){return g},get future(){return x},get state(){return k},get routes(){return f},get window(){return n},initialize:function(){if(E=t.history.listen((e=>{let{action:n,location:r,delta:o}=e;if(Fe)return void(Fe=!1);c(0===ke.size||null!=o,"You are trying to use a blocker on a POP navigation to a location that was not created by @remix-run/router. This will fail silently in production. This can happen if you are navigating outside the router via `window.history.pushState`/`window.location.hash` instead of using router navigation APIs.  This can also happen if you are using createHashRouter and the user manually changes the URL.");let i=ot({currentLocation:k.location,nextLocation:r,historyAction:n});return i&&null!=o?(Fe=!0,t.history.go(-1*o),void rt(i,{state:"blocked",location:r,proceed(){rt(i,{state:"proceeding",proceed:void 0,reset:void 0,location:r}),t.history.go(o)},reset(){let e=new Map(k.blockers);e.set(i,Z),Be({blockers:e})}})):He(n,r)})),r){!function(e,t){try{let n=e.sessionStorage.getItem(ne);if(n){let e=JSON.parse(n);for(let[n,r]of Object.entries(e||{}))r&&Array.isArray(r)&&t.set(n,new Set(r||[]))}}catch(e){}}(n,L);let e=()=>function(e,t){if(t.size>0){let n={};for(let[e,r]of t)n[e]=[...r];try{e.sessionStorage.setItem(ne,JSON.stringify(n))}catch(e){c(!1,"Failed to save applied view transitions in sessionStorage ("+e+").")}}}(n,L);n.addEventListener("pagehide",e),j=()=>n.removeEventListener("pagehide",e)}return k.initialized||He(e.Pop,k.location,{initialHydration:!0}),p},subscribe:function(e){return P.add(e),()=>P.delete(e)},enableScrollRestoration:function(e,t,n){if(S=e,T=t,O=n||null,!_&&k.navigation===K){_=!0;let e=ut(k.location,k.matches);null!=e&&Be({restoreScrollPosition:e})}return()=>{S=null,T=null,O=null}},navigate:async function n(r,o){if("number"==typeof r)return void t.history.go(r);let i=re(k.location,k.matches,g,x.v7_prependBasename,r,x.v7_relativeSplatPath,null==o?void 0:o.fromRouteId,null==o?void 0:o.relative),{path:s,submission:l,error:u}=oe(x.v7_normalizeFormMethod,!1,i,o),c=k.location,p=d(k.location,s,o&&o.state);p=a({},p,t.history.encodeLocation(p));let h=o&&null!=o.replace?o.replace:void 0,f=e.Push;!0===h?f=e.Replace:!1===h||null!=l&&Ve(l.formMethod)&&l.formAction===k.location.pathname+k.location.search&&(f=e.Replace);let m=o&&"preventScrollReset"in o?!0===o.preventScrollReset:void 0,y=!0===(o&&o.unstable_flushSync),v=ot({currentLocation:c,nextLocation:p,historyAction:f});if(!v)return await He(f,p,{submission:l,pendingError:u,preventScrollReset:m,replace:o&&o.replace,enableViewTransition:o&&o.unstable_viewTransition,flushSync:y});rt(v,{state:"blocked",location:p,proceed(){rt(v,{state:"proceeding",proceed:void 0,reset:void 0,location:p}),n(r,o)},reset(){let e=new Map(k.blockers);e.set(v,Z),Be({blockers:e})}})},fetch:function(e,n,r,i){if(o)throw new Error("router.fetch() was called during the server render, but it shouldn't be. You are likely calling a useFetcher() method in the body of your component. Try moving it to a useEffect or a callback.");U.has(e)&&Xe(e);let a=!0===(i&&i.unstable_flushSync),l=s||f,c=re(k.location,k.matches,g,x.v7_prependBasename,r,x.v7_relativeSplatPath,n,null==i?void 0:i.relative),p=v(l,c,g),d=ct(p,l,c);if(d.active&&d.matches&&(p=d.matches),!p)return void Je(e,n,xe(404,{pathname:c}),{flushSync:a});let{path:h,submission:m,error:y}=oe(x.v7_normalizeFormMethod,!0,c,i);if(y)return void Je(e,n,y,{flushSync:a});let b=Ae(p,h);N=!0===(i&&i.preventScrollReset),m&&Ve(m.formMethod)?async function(e,n,r,o,i,a,l,c){function p(t){if(!t.route.action&&!t.route.lazy){let t=xe(405,{method:c.formMethod,pathname:r,routeId:n});return Je(e,n,t,{flushSync:l}),!0}return!1}if($e(),ae.delete(e),!a&&p(o))return;let d=k.fetchers.get(e);Ge(e,function(e,t){return{state:"submitting",formMethod:e.formMethod,formAction:e.formAction,formEncType:e.formEncType,formData:e.formData,json:e.json,text:e.text,data:t?t.data:void 0}}(c,d),{flushSync:l});let h=new AbortController,m=fe(t.history,r,h.signal,c);if(a){let t=await pt(i,r,m.signal);if("aborted"===t.type)return;if("error"===t.type){let{error:o}=st(r,t);return void Je(e,n,o,{flushSync:l})}if(!t.matches)return void Je(e,n,xe(404,{pathname:r}),{flushSync:l});if(p(o=Ae(i=t.matches,r)))return}U.set(e,h);let y=W,b=(await Ue("action",m,[o],i))[0];if(m.signal.aborted)return void(U.get(e)===h&&U.delete(e));if(x.v7_fetcherPersist&&ge.has(e)){if(Te(b)||Oe(b))return void Ge(e,Le(void 0))}else{if(Te(b))return U.delete(e),$>y?void Ge(e,Le(void 0)):(se.add(e),Ge(e,Me(c)),Qe(m,b,{fetcherSubmission:c}));if(Oe(b))return void Je(e,n,b.error)}if(Se(b))throw xe(400,{type:"defer-action"});let C=k.navigation.location||k.location,w=fe(t.history,C,h.signal),E=s||f,P="idle"!==k.navigation.state?v(E,k.navigation.location,g):k.matches;u(P,"Didn't find any matches after fetcher action");let S=++W;G.set(e,S);let O=Me(c,b.data);k.fetchers.set(e,O);let[T,_]=ie(t.history,k,P,c,C,!1,x.unstable_skipActionErrorRevalidation,B,q,Q,ge,ae,se,E,g,[o.route.id,b]);_.filter((t=>t.key!==e)).forEach((e=>{let t=e.key,n=k.fetchers.get(t),r=Me(void 0,n?n.data:void 0);k.fetchers.set(t,r),U.has(t)&&Xe(t),e.controller&&U.set(t,e.controller)})),Be({fetchers:new Map(k.fetchers)});let V=()=>_.forEach((e=>Xe(e.key)));h.signal.addEventListener("abort",V);let{loaderResults:R,fetcherResults:A}=await We(k.matches,P,T,_,w);if(h.signal.aborted)return;h.signal.removeEventListener("abort",V),G.delete(e),U.delete(e),_.forEach((e=>U.delete(e.key)));let N=Ee([...R,...A]);if(N){if(N.idx>=T.length){let e=_[N.idx-T.length].key;se.add(e)}return Qe(w,N.result)}let{loaderData:M,errors:L}=ye(k,k.matches,T,R,void 0,_,A,Pe);if(k.fetchers.has(e)){let t=Le(b.data);k.fetchers.set(e,t)}tt(S),"loading"===k.navigation.state&&S>$?(u(D,"Expected pending action"),I&&I.abort(),qe(k.navigation.location,{matches:P,loaderData:M,errors:L,fetchers:new Map(k.fetchers)})):(Be({errors:L,loaderData:ve(k.loaderData,M,P,L),fetchers:new Map(k.fetchers)}),B=!1)}(e,n,h,b,p,d.active,a,m):(ae.set(e,{routeId:n,path:h}),async function(e,n,r,o,i,s,a,l){let c=k.fetchers.get(e);Ge(e,Me(l,c?c.data:void 0),{flushSync:a});let p=new AbortController,d=fe(t.history,r,p.signal);if(s){let t=await pt(i,r,d.signal);if("aborted"===t.type)return;if("error"===t.type){let{error:o}=st(r,t);return void Je(e,n,o,{flushSync:a})}if(!t.matches)return void Je(e,n,xe(404,{pathname:r}),{flushSync:a});o=Ae(i=t.matches,r)}U.set(e,p);let h=W,f=(await Ue("loader",d,[o],i))[0];if(Se(f)&&(f=await Ie(f,d.signal,!0)||f),U.get(e)===p&&U.delete(e),!d.signal.aborted){if(!ge.has(e))return Te(f)?$>h?void Ge(e,Le(void 0)):(se.add(e),void await Qe(d,f)):void(Oe(f)?Je(e,n,f.error):(u(!Se(f),"Unhandled fetcher deferred data"),Ge(e,Le(f.data))));Ge(e,Le(void 0))}}(e,n,h,b,p,d.active,a,m))},revalidate:function(){$e(),Be({revalidation:"loading"}),"submitting"!==k.navigation.state&&("idle"!==k.navigation.state?He(D||k.historyAction,k.navigation.location,{overrideNavigation:k.navigation}):He(k.historyAction,k.location,{startUninterruptedRevalidation:!0}))},createHref:e=>t.history.createHref(e),encodeLocation:e=>t.history.encodeLocation(e),getFetcher:Ye,deleteFetcher:function(e){if(x.v7_fetcherPersist){let t=(me.get(e)||0)-1;t<=0?(me.delete(e),ge.add(e)):me.set(e,t)}else Ke(e);Be({fetchers:new Map(k.fetchers)})},dispose:function(){E&&E(),j&&j(),P.clear(),I&&I.abort(),k.fetchers.forEach(((e,t)=>Ke(t))),k.blockers.forEach(((e,t)=>nt(t)))},getBlocker:function(e,t){let n=k.blockers.get(e)||Z;return ke.get(e)!==t&&ke.set(e,t),n},deleteBlocker:nt,patchRoutes:function(e,t){let n=null==s;ue(e,t,s||f,h,i),n&&(f=[...f],Be({}))},_internalFetchControllers:U,_internalActiveDeferreds:Pe,_internalSetRoutes:function(e){h={},s=y(e,i,void 0,h)}},p}({basename:void 0,future:ut({},void 0,{v7_prependBasename:!0}),history:function(t){return void 0===t&&(t={}),function(t,n,r,o){void 0===o&&(o={});let{window:i=document.defaultView,v5Compat:s=!1}=o,c=i.history,f=e.Pop,m=null,g=y();function y(){return(c.state||{idx:null}).idx}function v(){f=e.Pop;let t=y(),n=null==t?null:t-g;g=t,m&&m({action:f,location:C.location,delta:n})}function b(e){let t="null"!==i.location.origin?i.location.origin:i.location.href,n="string"==typeof e?e:h(e);return n=n.replace(/ $/,"%20"),u(t,"No window.location.(origin|href) available to create URL for href: "+n),new URL(n,t)}null==g&&(g=0,c.replaceState(a({},c.state,{idx:g}),""));let C={get action(){return f},get location(){return t(i,c)},listen(e){if(m)throw new Error("A history only accepts one active listener");return i.addEventListener(l,v),m=e,()=>{i.removeEventListener(l,v),m=null}},createHref:e=>n(i,e),createURL:b,encodeLocation(e){let t=b(e);return{pathname:t.pathname,search:t.search,hash:t.hash}},push:function(t,n){f=e.Push;let o=d(C.location,t,n);r&&r(o,t),g=y()+1;let a=p(o,g),l=C.createHref(o);try{c.pushState(a,"",l)}catch(e){if(e instanceof DOMException&&"DataCloneError"===e.name)throw e;i.location.assign(l)}s&&m&&m({action:f,location:C.location,delta:1})},replace:function(t,n){f=e.Replace;let o=d(C.location,t,n);r&&r(o,t),g=y();let i=p(o,g),a=C.createHref(o);c.replaceState(i,"",a),s&&m&&m({action:f,location:C.location,delta:0})},go:e=>c.go(e)};return C}((function(e,t){let{pathname:n,search:r,hash:o}=e.location;return d("",{pathname:n,search:r,hash:o},t.state&&t.state.usr||null,t.state&&t.state.key||"default")}),(function(e,t){return"string"==typeof t?t:h(t)}),null,t)}({window:void 0}),hydrationData:function(){var e;let t=null==(e=window)?void 0:e.__staticRouterHydrationData;return t&&t.errors&&(t=ut({},t,{errors:dt(t.errors)})),t}(),routes:bT,mapRouteProperties:function(e){let n={hasErrorBoundary:null!=e.ErrorBoundary||null!=e.errorElement};return e.Component&&Object.assign(n,{element:t.createElement(e.Component),Component:void 0}),e.HydrateFallback&&Object.assign(n,{hydrateFallbackElement:t.createElement(e.HydrateFallback),HydrateFallback:void 0}),e.ErrorBoundary&&Object.assign(n,{errorElement:t.createElement(e.ErrorBoundary),ErrorBoundary:void 0}),n},unstable_dataStrategy:void 0,unstable_patchRoutesOnMiss:void 0,window:void 0}).initialize());const wT=function(){return t.createElement("div",{className:"app"},t.createElement(lE,null,t.createElement(Mn,null),t.createElement(bt,{router:CT}),t.createElement(vS,null)),t.createElement(Fn,null))};var xT=document.getElementById("root");(0,r.H)(xT).render(t.createElement(t.StrictMode,null,t.createElement(wT,null)))})()})();
\ No newline at end of file
+`,jO=({reverseOrder:e,position:n="top-center",toastOptions:r,gutter:o,children:i,containerStyle:s,containerClassName:a})=>{let{toasts:l,handlers:u}=(e=>{let{toasts:n,pausedAt:r}=((e={})=>{let[n,r]=(0,t.useState)(cO);(0,t.useEffect)((()=>(uO.push(r),()=>{let e=uO.indexOf(r);e>-1&&uO.splice(e,1)})),[n]);let o=n.toasts.map((t=>{var n,r;return{...e,...e[t.type],...t,duration:t.duration||(null==(n=e[t.type])?void 0:n.duration)||(null==e?void 0:e.duration)||dO[t.type],style:{...e.style,...null==(r=e[t.type])?void 0:r.style,...t.style}}}));return{...n,toasts:o}})(e);(0,t.useEffect)((()=>{if(r)return;let e=Date.now(),t=n.map((t=>{if(t.duration===1/0)return;let n=(t.duration||0)+t.pauseDuration-(e-t.createdAt);if(!(n<0))return setTimeout((()=>fO.dismiss(t.id)),n);t.visible&&fO.dismiss(t.id)}));return()=>{t.forEach((e=>e&&clearTimeout(e)))}}),[n,r]);let o=(0,t.useCallback)((()=>{r&&pO({type:6,time:Date.now()})}),[r]),i=(0,t.useCallback)(((e,t)=>{let{reverseOrder:r=!1,gutter:o=8,defaultPosition:i}=t||{},s=n.filter((t=>(t.position||i)===(e.position||i)&&t.height)),a=s.findIndex((t=>t.id===e.id)),l=s.filter(((e,t)=>t<a&&e.visible)).length;return s.filter((e=>e.visible)).slice(...r?[l+1]:[0,l]).reduce(((e,t)=>e+(t.height||0)+o),0)}),[n]);return{toasts:n,handlers:{updateHeight:mO,startPause:gO,endPause:o,calculateOffset:i}}})(r);return t.createElement("div",{style:{position:"fixed",zIndex:9999,top:16,left:16,right:16,bottom:16,pointerEvents:"none",...s},className:a,onMouseEnter:u.startPause,onMouseLeave:u.endPause},l.map((r=>{let s=r.position||n,a=((e,t)=>{let n=e.includes("top"),r=n?{top:0}:{bottom:0},o=e.includes("center")?{justifyContent:"center"}:e.includes("right")?{justifyContent:"flex-end"}:{};return{left:0,right:0,display:"flex",position:"absolute",transition:iO()?void 0:"all 230ms cubic-bezier(.21,1.02,.73,1)",transform:`translateY(${t*(n?1:-1)}px)`,...r,...o}})(s,u.calculateOffset(r,{reverseOrder:e,gutter:o,defaultPosition:n}));return t.createElement(MO,{id:r.id,key:r.id,onHeightUpdate:u.updateHeight,className:r.visible?LO:"",style:a},"custom"===r.type?rO(r.message,r):i?i(r):t.createElement(NO,{toast:r,position:s}))})))},FO=fO,BO=function(e){return e.Unverified="unverified",e.Verified="verified",e.Edited="edited",e}({}),qO=function(e){return e.not_started="not started",e.started="started",e.completed="completed",e}({}),HO=function(e){return e.closed="closed",e.open="open",e.preview="preview",e.published="published",e}({});function zO(){return QO.apply(this,arguments)}function QO(){return(QO=Fu(qu().mark((function e(){var t,n;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,fetch("/api/survey/list");case 3:return t=e.sent,e.next=6,t.json();case 6:return n=e.sent,e.abrupt("return",n);case 10:return e.prev=10,e.t0=e.catch(0),e.abrupt("return",[]);case 13:case"end":return e.stop()}}),e,null,[[0,10]])})))).apply(this,arguments)}function UO(){return WO.apply(this,arguments)}function WO(){return(WO=Fu(qu().mark((function e(){var t,n,r;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,fetch("/api/survey/active/year");case 3:return t=e.sent,e.next=6,t.json();case 6:if(!("year"in(n=e.sent))){e.next=12;break}return r=n.year,e.abrupt("return",r.toString());case 12:return console.log("Invalid response format: Failed fetching active survey year."),e.abrupt("return","");case 14:e.next=20;break;case 16:return e.prev=16,e.t0=e.catch(0),console.error("Failed fetching active survey year:",e.t0),e.abrupt("return","");case 20:case"end":return e.stop()}}),e,null,[[0,16]])})))).apply(this,arguments)}const $O=t.forwardRef((({bsPrefix:e,variant:t,animation:n="border",size:r,as:o="div",className:i,...s},a)=>{const l=`${e=kt(e,"spinner")}-${n}`;return(0,_t.jsx)(o,{ref:a,...s,className:Tt()(i,l,r&&`${l}-${r}`,t&&`text-${t}`)})}));$O.displayName="Spinner";const GO=$O;function JO(e){var n=e.text,r=e.helpText,o=e.onClick,i=e.enabled,s=Cn((0,t.useState)(!1),2),a=s[0],l=s[1],u=function(){var e=Fu(qu().mark((function e(){return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!a){e.next=2;break}return e.abrupt("return");case 2:return l(!0),e.prev=3,e.next=6,o();case 6:return e.prev=6,l(!1),e.finish(6);case 9:case"end":return e.stop()}}),e,null,[[3,,6,9]])})));return function(){return e.apply(this,arguments)}}();return t.createElement(Ru,{onClick:u,disabled:!i,style:{pointerEvents:"auto",marginLeft:".5rem"},title:r},a&&t.createElement(GO,{as:"span",animation:"border",size:"sm",role:"status","aria-hidden":"true"}),n)}const YO=function(){var e=Cn((0,t.useState)([]),2),n=e[0],r=e[1],o=(0,t.useRef)(!1);function i(e,t,n){return s.apply(this,arguments)}function s(){return(s=Fu(qu().mark((function e(t,n,o){var i,s;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,fetch(t,{method:"POST"});case 3:return i=e.sent,e.next=6,i.json();case 6:s=e.sent,i.ok?(FO(o),zO().then((function(e){r(e)}))):FO(n+s.message),e.next=13;break;case 10:e.prev=10,e.t0=e.catch(0),FO(n+e.t0.message);case 13:case"end":return e.stop()}}),e,null,[[0,10]])})))).apply(this,arguments)}function a(){return(a=Fu(qu().mark((function e(){return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,i("/api/survey/new","Failed creating new survey: ","Created new survey");case 2:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function l(e,t){return u.apply(this,arguments)}function u(){return(u=Fu(qu().mark((function e(t,n){return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!o.current){e.next=3;break}return FO("Wait for status update to be finished..."),e.abrupt("return");case 3:return o.current=!0,e.next=6,i("/api/survey/"+n+"/"+t,"Error while updating "+t+" survey status to "+n+": ",t+" survey status updated to "+n);case 6:o.current=!1;case 7:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function c(){return(c=Fu(qu().mark((function e(t,n){return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,i("/api/response/unlock/"+t+"/"+n,"Error while unlocking "+n+" "+t+" survey response: ",n+" "+t+" survey response unlocked");case 2:case"end":return e.stop()}}),e)})))).apply(this,arguments)}(0,t.useEffect)((function(){zO().then((function(e){r(e)}))}),[]);var p=n.length>0&&n.every((function(e){return e.status==HO.published})),d=Ge(),h=window.location.origin+"/data?preview";return t.createElement("div",null,t.createElement(jO,null),t.createElement(Ru,{onClick:function(){return a.apply(this,arguments)},disabled:!p,style:{pointerEvents:"auto"},title:"Create a new survey for the next year. Only possible if all current surveys are published."},"start new survey"),t.createElement(BS,null,n.map((function(e){return t.createElement(BS.Item,{eventKey:e.year.toString(),key:e.year},t.createElement(BS.Header,null,e.year," - ",e.status),t.createElement(BS.Body,null,t.createElement("div",{style:{marginLeft:".5rem"}},t.createElement(Ru,{style:{marginLeft:".5rem"},onClick:function(){return d("/survey/admin/inspect/".concat(e.year))},title:"Open the survey for inspection with all questions visible and any visibleIf logic added to the title."},"Inspect Survey"),t.createElement(Ru,{style:{marginLeft:".5rem"},onClick:function(){return d("/survey/admin/try/".concat(e.year))},title:"Open the survey exactly as the nrens will see it, but without any nren data."},"Try Survey"),t.createElement(JO,{text:"Mark as open",helpText:"Allow the NRENs to respond to this survey. Only 1 survey may be open at a time, and (pre)-published surveys cannot be opened anymore.",enabled:e.status==HO.closed,onClick:function(){return l(e.year,"open")}}),t.createElement(JO,{text:"Mark as closed",helpText:"Do not allow the NRENs to respond to this survey anymore. Only surveys with status open can be closed.",enabled:e.status==HO.open,onClick:function(){return l(e.year,"close")}}),t.createElement(JO,{text:"Preview results",helpText:"Publish all completed survey responses to the compendium website for preview by admins. This is only possible if the survey is closed or previewed already.",enabled:e.status==HO.closed||e.status==HO.preview,onClick:function(){return l(e.year,"preview")}}),t.createElement(JO,{text:"Publish results",helpText:"Publish or re-publish all completed survey responses to the compendium website. This is only possible if the survey is in preview or published already.",enabled:e.status==HO.preview||e.status==HO.published,onClick:function(){return l(e.year,"publish")}}),e.status==HO.preview&&t.createElement("span",null,"  Preview link: ",t.createElement("a",{href:h},h))),t.createElement(Wx,null,t.createElement("tbody",null,e.responses.map((function(n){return t.createElement("tr",{key:n.nren},t.createElement("td",null,n.nren),t.createElement("td",null,n.status),t.createElement("td",null,n.lock_description),t.createElement("td",null,t.createElement(Ru,{onClick:function(){return d("/survey/response/".concat(e.year,"/").concat(n.nren))},style:{pointerEvents:"auto"},title:"Open the responses of the NREN."},"open"),t.createElement(Ru,{onClick:function(){return function(e,t){return c.apply(this,arguments)}(e.year,n.nren)},disabled:""==n.lock_description,style:{pointerEvents:"auto"},title:"Remove the lock from the survey so that another person can open the survey for editing. WARNING: The person that currently has the lock will not be abe to save their changes anymore once someone else starts editing!"},"remove lock")))}))))))}))))};function KO(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function XO(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?KO(Object(n),!0).forEach((function(t){Pn(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):KO(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function ZO(){return(ZO=Fu(qu().mark((function e(){var t,n;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,fetch("/api/user/list");case 3:return t=e.sent,e.next=6,t.json();case 6:return n=e.sent,e.abrupt("return",n);case 10:return e.prev=10,e.t0=e.catch(0),e.abrupt("return",[]);case 13:case"end":return e.stop()}}),e,null,[[0,10]])})))).apply(this,arguments)}function eT(){return(eT=Fu(qu().mark((function e(){var t,n;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,fetch("/api/nren/list");case 3:return t=e.sent,e.next=6,t.json();case 6:return n=e.sent,e.abrupt("return",n);case 10:return e.prev=10,e.t0=e.catch(0),e.abrupt("return",[]);case 13:case"end":return e.stop()}}),e,null,[[0,10]])})))).apply(this,arguments)}var tT=function(){var e=Fu(qu().mark((function e(t,n){var r,o,i,s;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return r=XO({id:t},n),o={method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)},e.next=4,fetch("/api/user/".concat(t),o);case 4:return i=e.sent,e.next=7,i.json();case 7:if(s=e.sent,i.ok){e.next=10;break}throw new Error(s.message);case 10:return e.abrupt("return",s.user);case 11:case"end":return e.stop()}}),e)})));return function(t,n){return e.apply(this,arguments)}}(),nT=function(e,t){return e.permissions.active&&!t.permissions.active?-1:!e.permissions.active&&t.permissions.active?1:e.permissions.active&&t.permissions.active?"admin"===e.role&&"admin"!==t.role?1:"admin"!==e.role&&"admin"===t.role?-1:e.name.localeCompare(t.name):e.name.localeCompare(t.name)};const rT=function(){var e=Cn((0,t.useState)([]),2),n=e[0],r=e[1],o=Cn((0,t.useState)([]),2),i=o[0],s=o[1],a=(0,t.useContext)(Qu).user,l=Cn((0,t.useState)({idx:-1,asc:!0}),2),u=l[0],c=l[1],p=Cn((0,t.useState)([]),2),d=p[0],h=p[1];(0,t.useEffect)((function(){(function(){return ZO.apply(this,arguments)})().then((function(e){r(e),h(e.sort(nT))})),function(){return eT.apply(this,arguments)}().then((function(e){s(e.sort((function(e,t){return e.name.localeCompare(t.name)})))}))}),[]),(0,t.useEffect)((function(){h(bn(n.sort(nT)))}),[n]);for(var f=function(e,t){var o=n.findIndex((function(e){return e.id===t.id})),i=bn(n),s=e.target.name,a={};a[s]="active"===s?e.target.checked:e.target.value,tT(t.id,a).then((function(e){i[o]=e,r(i)})).catch((function(e){alert(e.message)}))},m=function(e){var t;if(e===u.idx||(5===e||0===e)&&-1===u.idx)return 5!==e&&0!==e||(e=-1),c({idx:e,asc:!u.asc}),void h(bn(d.reverse()));0===e?(t=nT,c({idx:-1,asc:!0})):1===e?(t=function(e,t){return e.permissions.active&&!t.permissions.active?-1:!e.permissions.active&&t.permissions.active?1:0},c({idx:e,asc:!0})):2===e?(t=function(e,t){return e.role.localeCompare(t.role)},c({idx:e,asc:!0})):3===e?(t=function(e,t){return e.email.localeCompare(t.email)},c({idx:e,asc:!0})):4===e?(t=function(e,t){return e.name.localeCompare(t.name)},c({idx:e,asc:!0})):5===e?(t=nT,c({idx:-1,asc:!0})):6===e?(t=function(e,t){return 0===e.nrens.length&&0===t.nrens.length?0:0===e.nrens.length?-1:0===t.nrens.length?1:e.nrens[0].localeCompare(t.nrens[0])},c({idx:e,asc:!0})):(t=nT,c({idx:e,asc:!0})),h(n.sort(t))},g={},y=0;y<=6;y++)g[y]=u.idx===y?{"aria-sort":u.asc?"ascending":"descending"}:null;return t.createElement(Lt,{style:{maxWidth:"90vw"}},t.createElement(Ft,null,t.createElement("h1",null," User Management Page"),t.createElement(Wx,null,t.createElement("thead",null,t.createElement("tr",null,t.createElement("th",jC({},g[0],{onClick:function(){return m(0)},className:"pt-3 sortable",style:{border:"1px solid #ddd"}}),"Id"),t.createElement("th",jC({},g[1],{onClick:function(){return m(1)},className:"pt-3 sortable",style:{border:"1px solid #ddd"}}),"Active"),t.createElement("th",jC({},g[2],{onClick:function(){return m(2)},className:"pt-3 sortable",style:{border:"1px solid #ddd"}}),"Role"),t.createElement("th",jC({},g[3],{onClick:function(){return m(3)},className:"pt-3 sortable",style:{border:"1px solid #ddd"}}),"Email"),t.createElement("th",jC({},g[4],{onClick:function(){return m(4)},className:"pt-3 sortable",style:{border:"1px solid #ddd"}}),"Full Name"),t.createElement("th",jC({},g[5],{onClick:function(){return m(5)},className:"pt-3 sortable",style:{border:"1px solid #ddd"}}),"OIDC Sub"),t.createElement("th",jC({},g[6],{onClick:function(){return m(6)},className:"pt-3 sortable",style:{border:"1px solid #ddd"}}),"NREN"))),t.createElement("tbody",null,d.map((function(e){return t.createElement("tr",{key:e.id},t.createElement("td",{style:{border:"1px dotted #ddd"}},e.id),t.createElement("td",{style:{border:"1px dotted #ddd"}},e.id==a.id?"Active":t.createElement("input",{type:"checkbox",name:"active",checked:e.permissions.active,onChange:function(t){return f(t,e)}})),t.createElement("td",{style:{border:"1px dotted #ddd"}},e.id==a.id?e.role.charAt(0).toUpperCase()+e.role.slice(1):t.createElement("select",{name:"role",defaultValue:e.role,onChange:function(t){return f(t,e)}},t.createElement("option",{value:"admin"},"Admin"),t.createElement("option",{value:"user"},"User"),t.createElement("option",{value:"observer"},"Observer"))),t.createElement("td",{style:{border:"1px dotted #ddd"}},e.email),t.createElement("td",{style:{border:"1px dotted #ddd"}},e.name),t.createElement("td",{style:{border:"1px dotted #ddd"}},e.oidc_sub),t.createElement("td",{style:{border:"1px dotted #ddd"}},t.createElement("select",{name:"nren",multiple:!1,value:e.nrens.length>0?(n=e.nrens[0],null===(r=i.find((function(e){return e.id==n||e.name==n})))||void 0===r?void 0:r.id):"",onChange:function(t){return f(t,e)}},t.createElement("option",{value:""},"Select NREN"),i.map((function(e){return t.createElement("option",{key:"nren_"+e.id,value:e.id},e.name)})))));var n,r}))))))};var oT=o(522),iT=o(755);function sT(e,t){if(0==t.column.indexValue&&"item"in t.row){var n,r,o=t.row.item;void 0!==o.customDescription&&(null===(n=t.htmlElement.parentElement)||void 0===n||n.children[0].children[0].setAttribute("description",o.customDescription),null===(r=t.htmlElement.parentElement)||void 0===r||r.children[0].children[0].classList.add("survey-tooltip"))}}function aT(e){var t=e[0];if(void 0===t||null==t||""==t)return!0;try{return!!new URL(t)}catch(e){return!1}}function lT(e,t){t.question.hideCheckboxLabels&&(t.cssClasses.root+=" hidden-checkbox-labels")}function uT(e,t){var n,r='[data-name="'+t.question.name+'"]',o=null===(n=document.querySelector(r))||void 0===n?void 0:n.querySelector("h5");o&&!o.classList.contains("sv-header-flex")&&t.question.updateElementCss()}function cT(e,t,n){var r;n.verificationStatus.set(e.name,t);var o=document.createElement("button");o.type="button",o.className="sv-action-bar-item verification",o.innerHTML=t,t==BO.Unverified?(o.innerHTML="No change from previous year",o.className+=" verification-required",o.onclick=function(){"display"!=n.mode&&(e.validate(),cT(e,BO.Verified,n))}):(o.innerHTML="Answer updated",o.className+=" verification-ok");var i='[data-name="'+e.name+'"]',s=null===(r=document.querySelector(i))||void 0===r?void 0:r.querySelector("h5"),a=null==s?void 0:s.querySelector(".verification");a?a.replaceWith(o):null==s||s.appendChild(o)}const pT=function(e){var n=e.surveyModel,r=(0,t.useCallback)((function(e,t){var r,o=n.verificationStatus.get(t.question.name),i=null===(r=t.question)||void 0===r?void 0:r.readOnly;o&&!i?cT(t.question,o,n):i&&function(e){var t,n=!!e.visibleIf,r='[data-name="'+e.name+'"]',o=document.querySelector(r),i=null==o?void 0:o.querySelector("h5");if(n)o.style.display="none";else{i&&(i.style.textDecoration="line-through");var s=null===(t=document.querySelector(r))||void 0===t?void 0:t.querySelector(".sv-question__content");s&&(s.style.display="none")}}(t.question)}),[n]),o=(0,t.useCallback)((function(e,t){n.verificationStatus.get(t.question.name)==BO.Unverified&&cT(t.question,BO.Edited,n)}),[n]);return oT.FunctionFactory.Instance.hasFunction("validateWebsiteUrl")||oT.FunctionFactory.Instance.register("validateWebsiteUrl",aT),n.css.question.title.includes("sv-header-flex")||(n.css.question.title="sv-title sv-question__title sv-header-flex",n.css.question.titleOnError="sv-question__title--error sv-error-color-fix"),n.onAfterRenderQuestion.hasFunc(r)||(n.onAfterRenderQuestion.add(r),n.onAfterRenderQuestion.add(uT)),n.onValueChanged.hasFunc(o)||n.onValueChanged.add(o),n.onUpdateQuestionCssClasses.hasFunc(lT)||n.onUpdateQuestionCssClasses.add(lT),n.onMatrixAfterCellRender.hasFunc(sT)||n.onMatrixAfterCellRender.add(sT),t.createElement(iT.Survey,{model:n})};function dT(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function hT(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?dT(Object(n),!0).forEach((function(t){Pn(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):dT(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}const fT=function(e){var n=e.surveyModel,r=e.pageNoSetter,o=Cn((0,t.useState)([]),2),i=o[0],s=o[1],a=function(e){return!(null===e.value||void 0===e.value||""===e.value||"checkbox"===e.getType()&&0==e.value.length||"multipletext"===e.getType()&&(1===Object.keys(e.value).length&&void 0===Object.values(e.value)[0]||0===Object.keys(e.value).length))};(0,t.useEffect)((function(){var e=function(e){if(e&&e.pages){var t=[];e.pages.forEach((function(n){var r=n.questions.filter((function(e){return e.startWithNewLine})),o=r.length,i=r.filter(a).length,s=o-i,l=i/o;t.push({completionPercentage:100*l,unansweredPercentage:s/o*100,totalPages:e.pages.length,pageTitle:n.title})})),s(t)}};n.onValueChanged.add((function(t){e(t)})),e(n)}),[n]);var l={height:"0.5rem",transition:"width 0.3s ease"};return t.createElement(Lt,{className:"survey-progress"},t.createElement(Ft,null,i.map((function(e,o){return t.createElement(qt,{xs:12,md:!0,key:o,onClick:function(){return r(o)},style:{cursor:"pointer",margin:"0.5rem"}},t.createElement("div",null,t.createElement("span",{style:{whiteSpace:"nowrap",fontSize:"1.5rem",marginRight:"0.25rem",fontWeight:"bold",color:"#2db394"}},o+1),t.createElement("span",{style:hT({whiteSpace:"nowrap"},n.currentPageNo==o&&{fontWeight:"bold"})},e.pageTitle),t.createElement("div",{style:{display:"flex",flexWrap:"wrap"}},t.createElement("div",{style:hT(hT({},l),{},{width:"".concat(e.completionPercentage,"%"),backgroundColor:"#262261"})}),t.createElement("div",{style:hT(hT({},l),{},{width:"".concat(e.unansweredPercentage,"%"),backgroundColor:"#cdcdcd"})}))))}))))},mT=function(e){var n=e.surveyModel,r=e.surveyActions,o=e.year,i=e.nren,s=e.children,a=Cn((0,t.useState)(0),2),l=a[0],u=a[1],c=Cn((0,t.useState)(!1),2),p=c[0],d=c[1],h=Cn((0,t.useState)(""),2),f=h[0],m=h[1],g=Cn((0,t.useState)(""),2),y=g[0],v=g[1],b=(0,t.useContext)(Qu).user,C=(0,t.useCallback)((function(){d("edit"==n.mode),m(n.lockedBy),u(n.currentPageNo),v(n.status)}),[n]);(0,t.useEffect)((function(){C()}),[C]);var w=function(e){u(e),n.currentPageNo=e},x=function(){w(n.currentPageNo+1)},E=function(){var e=Fu(qu().mark((function e(t){return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,r[t]();case 2:C();case 3:case"end":return e.stop()}}),e)})));return function(t){return e.apply(this,arguments)}}(),P=function(e,t){return S(e,(function(){return E(t)}))},S=function(e,n){return t.createElement("button",{className:"sv-btn sv-btn--navigation",onClick:n},e)},O="Save and stop editing",T="Save progress",_="Start editing",V="Complete Survey",R=function(){return t.createElement("div",{className:"survey-edit-buttons-block"},!p&&!f&&n.editAllowed&&P(_,"startEdit"),!p&&f&&f==b.name&&P("Discard any unsaved changes and release your lock","releaseLock"),p&&y==qO.started&&P(T,"save"),p&&y==qO.started&&P(O,"saveAndStopEdit"),p&&l===n.visiblePages.length-1&&P(V,"complete"),l!==n.visiblePages.length-1&&S("Next Section",x))};return t.createElement(Lt,null,t.createElement(Ft,{className:"survey-content"},t.createElement("h2",null,t.createElement("span",{className:"survey-title"},o," Compendium Survey "),t.createElement("span",{className:"survey-title-nren"}," ",i," "),t.createElement("span",null," - ",y)),t.createElement("div",{style:{marginTop:"1rem",textAlign:"justify"}},t.createElement("p",null,"To get started, click “",_,"” to end read-only mode. Different people from your NREN (Compendium administrators) can contribute to the survey if needed, but agreement should be reached internally before completing the survey as the administration team will treat responses as a single source of truth from the NREN. You can start editing only when nobody else from your NREN is currently working on the survey."),t.createElement("p",null,t.createElement("b",null,"In a small change, the survey now asks about this calendar year, i.e. ",o)," (or the current financial year if your budget or staffing data does not match the calendar year). For network questions, please provide data from the 12 months preceding you answering the question. Where available, the survey questions are pre-filled with answers from the previous survey. You can edit the pre-filled answer to provide new information, or press the “no change from previous year” button."),t.createElement("p",null,"Press the “",T,"“ or “",O,"“ button to save all answers in the survey. When you reach the last section of the survey (Services), you will find a “",V,"“ button which saves all answers in the survey and lets the Compendium team know that your answers are ready to be published. As long as the survey remains open, any Compendium administrator from your NREN can add answers or amend existing ones, even after using the “",V,"“ button."),t.createElement("p",null,"Some fields require specific data, such as numerical data, valid http-addresses, and in some questions, the answer has to add up to 100%. If an answer does not fulfil the set criteria, the question will turn pink and an error message will appear. Fields can be left blank if you prefer not to answer a question. If you notice any errors after the survey was closed, please contact us for correcting those.")),t.createElement("p",null,"Thank you for taking the time to fill in the ",o," Compendium Survey. Any questions or requests can be sent to ",t.createElement("a",{href:"mailto:Partner-Relations@geant.org"},t.createElement("span",null,"Partner-Relations@geant.org"))),p&&t.createElement(t.Fragment,null,t.createElement("br",null),t.createElement("b",null,"Remember to click “",O,"” before leaving the page."))),t.createElement(Ft,null,R()),t.createElement(Ft,{className:"survey-content"},!p&&t.createElement("div",{className:"survey-edit-explainer"},!f&&n.editAllowed&&"The survey is in read-only mode; click the “Start editing“ button to begin editing the answers.",!f&&!n.editAllowed&&"The survey is in read-only mode and can not be edited by you.",f&&f!=b.name&&"The survey is in read-only mode and currently being edited by: "+f+". To start editing the survey, ask them to complete their edits.",f&&f==b.name&&'The survey is in read-only mode because you started editing in another tab, browser or device. To start editing the survey, either complete those edits or click the "Discard any unsaved changes" button.')),t.createElement(Ft,null,t.createElement(fT,{surveyModel:n,pageNoSetter:w}),s),t.createElement(Ft,null,R()))},gT=function(e){var n=e.when,r=e.onPageExit;return function(e){let{router:n,basename:r}=rt(tt.UseBlocker),o=ot(nt.UseBlocker),[i,s]=t.useState(""),a=t.useCallback((t=>{if("/"===r)return e();let{currentLocation:n,nextLocation:o,historyAction:i}=t;return e((je({},n,{pathname:A(n.pathname,r)||n.pathname}),je({},o,{pathname:A(o.pathname,r)||o.pathname})))}),[r,e]);t.useEffect((()=>{let e=String(++st);return s(e),()=>n.deleteBlocker(e)}),[n]),t.useEffect((()=>{""!==i&&n.getBlocker(i,a)}),[n,i,a]),i&&o.blockers.has(i)&&o.blockers.get(i)}((function(){if(n()){var t=window.confirm(e.message);return t&&r(),!t}return!1})),t.createElement("div",null)};oT.Serializer.addProperty("itemvalue","customDescription:text"),oT.Serializer.addProperty("question","hideCheckboxLabels:boolean");const yT=function(e){var n=e.loadFrom,r=Cn((0,t.useState)(),2),o=r[0],i=r[1],s=function(){let{matches:e}=t.useContext(ze),n=e[e.length-1];return n?n.params:{}}(),a=s.year,l=s.nren,u=Cn((0,t.useState)("loading survey..."),2),c=u[0],p=u[1],d=An().trackPageView,h=(0,t.useCallback)((function(e){return e.preventDefault(),e.returnValue=""}),[]),f=(0,t.useCallback)((function(){window.navigator.sendBeacon("/api/response/unlock/"+a+"/"+l)}),[]),m=(0,t.useCallback)((function(){window.navigator.sendBeacon("/api/response/unlock/"+a+"/"+l),removeEventListener("beforeunload",h,{capture:!0}),removeEventListener("pagehide",f)}),[]);if((0,t.useEffect)((function(){function e(){return(e=Fu(qu().mark((function e(){var t,r,o,s;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,fetch(n+a+(l?"/"+l:""));case 2:return t=e.sent,e.next=5,t.json();case 5:if(r=e.sent,t.ok){e.next=12;break}if(!("message"in r)){e.next=11;break}throw new Error(r.message);case 11:throw new Error("Request failed with status ".concat(t.status));case 12:for(s in(o=new oT.Model(r.model)).setVariable("surveyyear",a),o.setVariable("previousyear",parseInt(a)-1),o.showNavigationButtons=!1,o.requiredText="",o.verificationStatus=new Map,r.verification_status)o.verificationStatus.set(s,r.verification_status[s]);o.data=r.data,o.clearIncorrectValues(!0),o.currentPageNo=r.page,o.mode=r.mode,o.lockedBy=r.locked_by,o.status=r.status,o.editAllowed=r.edit_allowed,i(o);case 27:case"end":return e.stop()}}),e)})))).apply(this,arguments)}(function(){return e.apply(this,arguments)})().catch((function(e){return p("Error when loading survey: "+e.message)})).then((function(){d({documentTitle:"Survey for ".concat(l," (").concat(a,")")})}))}),[]),!o)return t.createElement(t.Fragment,null,c);var g,y,v,b,C,w=function(){var e=Fu(qu().mark((function e(t,n){var r,i,s;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(l){e.next=2;break}return e.abrupt("return","Saving not available in inpect/try mode");case 2:return r={lock_uuid:t.lockUUID,new_state:n,data:t.data,page:t.currentPageNo,verification_status:Object.fromEntries(t.verificationStatus)},e.prev=3,e.next=6,fetch("/api/response/save/"+a+"/"+l,{method:"POST",headers:{"Content-Type":"application/json; charset=utf-8"},body:JSON.stringify(r)});case 6:return i=e.sent,e.next=9,i.json();case 9:if(s=e.sent,i.ok){e.next=12;break}return e.abrupt("return",s.message);case 12:o.mode=s.mode,o.lockedBy=s.locked_by,o.status=s.status,e.next=20;break;case 17:return e.prev=17,e.t0=e.catch(3),e.abrupt("return","Unknown Error: "+e.t0.message);case 20:case"end":return e.stop()}}),e,null,[[3,17]])})));return function(t,n){return e.apply(this,arguments)}}(),x=function(e){var t="",n=function(e,n){e.verificationStatus.get(n.name)==BO.Unverified&&(""==t&&(t=n.name),n.error='Please verify that last years data is correct by editing the answer or pressing the "No change from previous year" button!')};o.onValidateQuestion.add(n);var r=e();return o.onValidateQuestion.remove(n),r||FO("Validation failed!"),r},E={save:(C=Fu(qu().mark((function e(){var t;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,w(o,"editing");case 2:t=e.sent,FO(t?"Failed saving survey: "+t:"Survey saved!");case 4:case"end":return e.stop()}}),e)}))),function(){return C.apply(this,arguments)}),complete:(b=Fu(qu().mark((function e(){var t;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!x(o.validate.bind(o,!0,!0))){e.next=6;break}return e.next=4,w(o,"completed");case 4:(t=e.sent)?FO("Failed completing survey: "+t):(FO("Survey completed!"),removeEventListener("beforeunload",h,{capture:!0}),removeEventListener("pagehide",f));case 6:case"end":return e.stop()}}),e)}))),function(){return b.apply(this,arguments)}),saveAndStopEdit:(v=Fu(qu().mark((function e(){var t;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,w(o,"readonly");case 2:(t=e.sent)?FO("Failed saving survey: "+t):(FO("Survey saved!"),removeEventListener("beforeunload",h,{capture:!0}),removeEventListener("pagehide",f));case 4:case"end":return e.stop()}}),e)}))),function(){return v.apply(this,arguments)}),startEdit:(y=Fu(qu().mark((function e(){var t,n,r;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,fetch("/api/response/lock/"+a+"/"+l,{method:"POST"});case 2:return t=e.sent,e.next=5,t.json();case 5:if(n=e.sent,t.ok){e.next=9;break}return FO("Failed starting edit: "+n.message),e.abrupt("return");case 9:for(r in addEventListener("pagehide",f),addEventListener("beforeunload",h,{capture:!0}),n.verification_status)o.verificationStatus.set(r,n.verification_status[r]);o.data=n.data,o.clearIncorrectValues(!0),o.mode=n.mode,o.lockedBy=n.locked_by,o.lockUUID=n.lock_uuid,o.status=n.status;case 18:case"end":return e.stop()}}),e)}))),function(){return y.apply(this,arguments)}),releaseLock:(g=Fu(qu().mark((function e(){var t,n;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,fetch("/api/response/unlock/"+a+"/"+l,{method:"POST"});case 2:return t=e.sent,e.next=5,t.json();case 5:if(n=e.sent,t.ok){e.next=9;break}return FO("Failed releasing lock: "+n.message),e.abrupt("return");case 9:o.mode=n.mode,o.lockedBy=n.locked_by,o.status=n.status;case 12:case"end":return e.stop()}}),e)}))),function(){return g.apply(this,arguments)}),validatePage:function(){x(o.validatePage.bind(o))&&FO("Page validation successful!")}};return t.createElement(Lt,{className:"survey-container"},t.createElement(jO,null),t.createElement(gT,{message:"Are you sure you want to leave this page? Information you've entered may not be saved.",when:function(){return"edit"==o.mode&&!!l},onPageExit:m}),t.createElement(mT,{surveyModel:o,surveyActions:E,year:a,nren:l},t.createElement(pT,{surveyModel:o})))},vT=function(){var e=An().trackPageView,n=(0,t.useContext)(Qu).user,r=Ge(),o=!!n.id,i=!!o&&!!n.nrens.length,s=i?n.nrens[0]:"",a=!!o&&n.permissions.admin,l=!!o&&"observer"===n.role,u=Cn((0,t.useState)(null),2),c=u[0],p=u[1];(0,t.useEffect)((function(){var t=function(){var e=Fu(qu().mark((function e(){var t;return qu().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,UO();case 2:t=e.sent,p(t);case 4:case"end":return e.stop()}}),e)})));return function(){return e.apply(this,arguments)}}();t(),e({documentTitle:"GEANT Survey Landing Page"})}),[e]);var d=function(){var e=Cn((0,t.useState)(),2),n=e[0],r=e[1];return(0,t.useEffect)((function(){zO().then((function(e){r(e[0])}))}),[]),t.createElement(Wx,{striped:!0,bordered:!0,responsive:!0},t.createElement("thead",null,t.createElement("tr",null,t.createElement("th",null,"(N)REN"),t.createElement("th",null,"Link"),t.createElement("th",null,"Survey Status"))),t.createElement("tbody",null,n&&n.responses.map((function(e){return t.createElement("tr",{key:e.nren},t.createElement("td",null,e.nren),t.createElement("td",null,t.createElement(Et,{to:"/survey/response/".concat(n.year,"/").concat(e.nren)},t.createElement("span",null,"Navigate to survey"))),t.createElement("td",null,e.status))}))))};return t.createElement(Lt,{className:"py-5 grey-container"},t.createElement(Ft,null,t.createElement("div",{className:"center-text"},t.createElement("h1",{className:"geant-header"},"THE GÉANT COMPENDIUM OF NRENS SURVEY"),t.createElement("div",{className:"wordwrap pt-4",style:{maxWidth:"75rem"}},t.createElement("p",{style:{textAlign:"left"}},"Hello,",t.createElement("br",null),"Welcome to the GÉANT Compendium Survey. (N)REN Compendium administrators can login via Single Sign On (SSO) ",t.createElement("a",{href:"/login"},"here"),", which will complete their registration to fill in the latest Compendium survey. This will send a notification to the Compendium administration team and they will assign you to your (N)REN.",t.createElement("br",null),"Once this step has been completed, you will receive an email from the administration team. We aim to get back to you the same working day, but sometimes may take a little longer.",t.createElement("br",null),"If you are not sure whether you are a Compendium Administrator for your (N)REN, please contact your GÉANT Partner Relations relationship manager.",t.createElement("br",null),"Thank you."),t.createElement("span",null,"Current registration status:"),t.createElement("br",null),t.createElement("br",null),a?t.createElement("ul",null,t.createElement("li",null,t.createElement("span",null,"You are logged in as a Compendium Administrator")),t.createElement("li",null,t.createElement("span",null,"Click ",t.createElement(Et,{to:"/survey/admin/surveys"},"here")," to access the survey management page.")),t.createElement("li",null,t.createElement("span",null,"Click ",t.createElement(Et,{to:"/survey/admin/users"},"here")," to access the user management page.")),t.createElement("li",null,t.createElement("span",null,"Click ",t.createElement("a",{href:"#",onClick:function(){fetch("/api/data-download").then((function(e){if(!e.ok)throw new Error("Network response was not ok");return e.json()})).then((function(e){var t=function(e){var t=Py.book_new();e.forEach((function(e){var n=Py.json_to_sheet(e.data);e.meta&&function(e,t,n){for(var r,o=Py.decode_range(null!==(r=e["!ref"])&&void 0!==r?r:""),i=-1,s=o.s.c;s<=o.e.c;s++){var a=e[Py.encode_cell({r:o.s.r,c:s})];if(a&&"string"==typeof a.v&&a.v===t){i=s;break}}if(-1!==i)for(var l=o.s.r+1;l<=o.e.r;++l){var u=Py.encode_cell({r:l,c:i});e[u]&&"n"===e[u].t&&(e[u].z=n)}else console.error("Column '".concat(t,"' not found."))}(n,e.meta.columnName,e.meta.format),Py.book_append_sheet(t,n,e.name)}));for(var n=fy(t,{bookType:"xlsx",type:"binary"}),r=new ArrayBuffer(n.length),o=new Uint8Array(r),i=0;i<n.length;i++)o[i]=255&n.charCodeAt(i);return new Blob([r],{type:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8"})}(e),n=document.createElement("a");n.href=URL.createObjectURL(t),n.download="data.xlsx",document.body.appendChild(n),n.click(),document.body.removeChild(n)})).catch((function(e){console.error("Error fetching data:",e)}))}},"here")," to do the full data download."))):t.createElement("ul",null,c&&!a&&!l&&i&&function(){try{return r("/survey/response/".concat(c,"/").concat(s)),t.createElement("li",null,"Redirecting to survey...")}catch(e){return console.error("Error navigating:",e),null}}(),o?t.createElement("li",null,t.createElement("span",null,"You are logged in")):t.createElement("li",null,t.createElement("span",null,"You are not logged in")),o&&!l&&!i&&t.createElement("li",null,t.createElement("span",null,"Your access to the survey has not yet been approved")),o&&!l&&!i&&t.createElement("li",null,t.createElement("span",null,"Once you have been approved, you will immediately be directed to the relevant survey upon visiting this page")),o&&l&&t.createElement("li",null,t.createElement("span",null,"You have read-only access to the following surveys:"))),o&&l&&t.createElement(d,null)))))};var bT,CT=(bT=[{path:"/budget",element:t.createElement(sx,null)},{path:"/funding",element:t.createElement(Qx,null)},{path:"/data/employment",element:t.createElement(eE,null)},{path:"/data/roles",element:t.createElement(eE,{roles:!0})},{path:"/employee-count",element:t.createElement(nE,null)},{path:"/charging",element:t.createElement(Gx,null)},{path:"/suborganisations",element:t.createElement(rE,null)},{path:"/parentorganisation",element:t.createElement(oE,null)},{path:"/ec-projects",element:t.createElement(iE,null)},{path:"/policy",element:t.createElement(uE,null)},{path:"/traffic-volume",element:t.createElement(nP,null)},{path:"/data",element:t.createElement(ux,null)},{path:"/institutions-urls",element:t.createElement(dE,null)},{path:"/connected-proportion",element:t.createElement(TE,{connectivity_category:fE.ConnectedProportion.toString()})},{path:"/connectivity-level",element:t.createElement(TE,{connectivity_category:fE.ConnectivityLevel.toString()})},{path:"/connectivity-growth",element:t.createElement(TE,{connectivity_category:fE.ConnectivityGrowth.toString()})},{path:"/connection-carrier",element:t.createElement(TE,{connectivity_category:fE.ConnectionCarrier.toString()})},{path:"/connectivity-load",element:t.createElement(TE,{connectivity_category:fE.ConnectivityLoad.toString()})},{path:"/commercial-charging-level",element:t.createElement(TE,{connectivity_category:fE.CommercialChargingLevel.toString()})},{path:"/commercial-connectivity",element:t.createElement(TE,{connectivity_category:fE.CommercialConnectivity.toString()})},{path:"/network-services",element:t.createElement(vE,{category:hE.network_services})},{path:"/isp-support-services",element:t.createElement(vE,{category:hE.isp_support})},{path:"/security-services",element:t.createElement(vE,{category:hE.security})},{path:"/identity-services",element:t.createElement(vE,{category:hE.identity})},{path:"/collaboration-services",element:t.createElement(vE,{category:hE.collaboration})},{path:"/multimedia-services",element:t.createElement(vE,{category:hE.multimedia})},{path:"/storage-and-hosting-services",element:t.createElement(vE,{category:hE.storage_and_hosting})},{path:"/professional-services",element:t.createElement(vE,{category:hE.professional_services})},{path:"/fibre-light",element:t.createElement(_E,null)},{path:"/monitoring-tools",element:t.createElement(VE,null)},{path:"/pert-team",element:t.createElement(RE,null)},{path:"/passive-monitoring",element:t.createElement(IE,null)},{path:"/alien-wave",element:t.createElement(kE,null)},{path:"/alien-wave-internal",element:t.createElement(AE,null)},{path:"/ops-automation",element:t.createElement(DE,null)},{path:"/network-automation",element:t.createElement(NE,null)},{path:"/traffic-stats",element:t.createElement(ME,null)},{path:"/weather-map",element:t.createElement(LE,null)},{path:"/network-map",element:t.createElement(jE,null)},{path:"/nfv",element:t.createElement(FE,null)},{path:"/certificate-provider",element:t.createElement(BE,null)},{path:"/siem-vendors",element:t.createElement(qE,null)},{path:"/capacity-largest-link",element:t.createElement(zE,null)},{path:"/capacity-core-ip",element:t.createElement(UE,null)},{path:"/non-rne-peers",element:t.createElement($E,null)},{path:"/iru-duration",element:t.createElement(JE,null)},{path:"/iru-duration",element:t.createElement(JE,null)},{path:"/audits",element:t.createElement(YE,null)},{path:"/business-continuity",element:t.createElement(KE,null)},{path:"/crisis-management",element:t.createElement(ZE,null)},{path:"/crisis-exercise",element:t.createElement(XE,null)},{path:"/security-control",element:t.createElement(eP,null)},{path:"survey/admin/surveys",element:t.createElement(YO,null)},{path:"survey/admin/users",element:t.createElement(rT,null)},{path:"survey/admin/inspect/:year",element:t.createElement(yT,{loadFrom:"/api/response/inspect/"})},{path:"survey/admin/try/:year",element:t.createElement(yT,{loadFrom:"/api/response/try/"})},{path:"survey/response/:year/:nren",element:t.createElement(yT,{loadFrom:"/api/response/load/"})},{path:"survey/*",element:t.createElement(vT,null)},{path:"*",element:t.createElement(Dn,null)}],function(t){const n=t.window?t.window:"undefined"!=typeof window?window:void 0,r=void 0!==n&&void 0!==n.document&&void 0!==n.document.createElement,o=!r;let i;if(u(t.routes.length>0,"You must provide a non-empty routes array to createRouter"),t.mapRouteProperties)i=t.mapRouteProperties;else if(t.detectErrorBoundary){let e=t.detectErrorBoundary;i=t=>({hasErrorBoundary:e(t)})}else i=te;let s,l,p,h={},f=y(t.routes,i,void 0,h),g=t.basename||"/",C=t.unstable_dataStrategy||pe,w=t.unstable_patchRoutesOnMiss,x=a({v7_fetcherPersist:!1,v7_normalizeFormMethod:!1,v7_partialHydration:!1,v7_prependBasename:!1,v7_relativeSplatPath:!1,unstable_skipActionErrorRevalidation:!1},t.future),E=null,P=new Set,S=null,O=null,T=null,_=null!=t.hydrationData,V=v(f,t.history.location,g),R=null;if(null==V&&!w){let e=xe(404,{pathname:t.history.location.pathname}),{matches:n,route:r}=we(f);V=n,R={[r.id]:e}}if(V&&w&&ct(V,f,t.history.location.pathname).active&&(V=null),V)if(V.some((e=>e.route.lazy)))l=!1;else if(V.some((e=>e.route.loader)))if(x.v7_partialHydration){let e=t.hydrationData?t.hydrationData.loaderData:null,n=t.hydrationData?t.hydrationData.errors:null,r=t=>!t.route.loader||("function"!=typeof t.route.loader||!0!==t.route.loader.hydrate)&&(e&&void 0!==e[t.route.id]||n&&void 0!==n[t.route.id]);if(n){let e=V.findIndex((e=>void 0!==n[e.route.id]));l=V.slice(0,e+1).every(r)}else l=V.every(r)}else l=null!=t.hydrationData;else l=!0;else l=!1,V=[];let I,k={historyAction:t.history.action,location:t.history.location,matches:V,initialized:l,navigation:K,restoreScrollPosition:null==t.hydrationData&&null,preventScrollReset:!1,revalidation:"idle",loaderData:t.hydrationData&&t.hydrationData.loaderData||{},actionData:t.hydrationData&&t.hydrationData.actionData||null,errors:t.hydrationData&&t.hydrationData.errors||R,fetchers:new Map,blockers:new Map},D=e.Pop,N=!1,M=!1,L=new Map,j=null,F=!1,B=!1,q=[],Q=[],U=new Map,W=0,$=-1,G=new Map,se=new Set,ae=new Map,me=new Map,ge=new Set,Pe=new Map,ke=new Map,je=new Map,Fe=!1;function Be(e,t){void 0===t&&(t={}),k=a({},k,e);let n=[],r=[];x.v7_fetcherPersist&&k.fetchers.forEach(((e,t)=>{"idle"===e.state&&(ge.has(t)?r.push(t):n.push(t))})),[...P].forEach((e=>e(k,{deletedFetchers:r,unstable_viewTransitionOpts:t.viewTransitionOpts,unstable_flushSync:!0===t.flushSync}))),x.v7_fetcherPersist&&(n.forEach((e=>k.fetchers.delete(e))),r.forEach((e=>Ke(e))))}function qe(n,r,o){var i,l;let u,{flushSync:c}=void 0===o?{}:o,p=null!=k.actionData&&null!=k.navigation.formMethod&&Ve(k.navigation.formMethod)&&"loading"===k.navigation.state&&!0!==(null==(i=n.state)?void 0:i._isRedirect);u=r.actionData?Object.keys(r.actionData).length>0?r.actionData:null:p?k.actionData:null;let d=r.loaderData?ve(k.loaderData,r.loaderData,r.matches||[],r.errors):k.loaderData,h=k.blockers;h.size>0&&(h=new Map(h),h.forEach(((e,t)=>h.set(t,Z))));let m,g=!0===N||null!=k.navigation.formMethod&&Ve(k.navigation.formMethod)&&!0!==(null==(l=n.state)?void 0:l._isRedirect);if(s&&(f=s,s=void 0),F||D===e.Pop||(D===e.Push?t.history.push(n,n.state):D===e.Replace&&t.history.replace(n,n.state)),D===e.Pop){let e=L.get(k.location.pathname);e&&e.has(n.pathname)?m={currentLocation:k.location,nextLocation:n}:L.has(n.pathname)&&(m={currentLocation:n,nextLocation:k.location})}else if(M){let e=L.get(k.location.pathname);e?e.add(n.pathname):(e=new Set([n.pathname]),L.set(k.location.pathname,e)),m={currentLocation:k.location,nextLocation:n}}Be(a({},r,{actionData:u,loaderData:d,historyAction:D,location:n,initialized:!0,navigation:K,revalidation:"idle",restoreScrollPosition:ut(n,r.matches||k.matches),preventScrollReset:g,blockers:h}),{viewTransitionOpts:m,flushSync:!0===c}),D=e.Pop,N=!1,M=!1,F=!1,B=!1,q=[],Q=[]}async function He(n,r,o){I&&I.abort(),I=null,D=n,F=!0===(o&&o.startUninterruptedRevalidation),function(e,t){if(S&&T){let n=lt(e,t);S[n]=T()}}(k.location,k.matches),N=!0===(o&&o.preventScrollReset),M=!0===(o&&o.enableViewTransition);let i=s||f,l=o&&o.overrideNavigation,u=v(i,r,g),c=!0===(o&&o.flushSync),p=ct(u,i,r.pathname);if(p.active&&p.matches&&(u=p.matches),!u){let{error:e,notFoundMatches:t,route:n}=it(r.pathname);return void qe(r,{matches:t,loaderData:{},errors:{[n.id]:e}},{flushSync:c})}if(k.initialized&&!B&&function(e,t){return e.pathname===t.pathname&&e.search===t.search&&(""===e.hash?""!==t.hash:e.hash===t.hash||""!==t.hash)}(k.location,r)&&!(o&&o.submission&&Ve(o.submission.formMethod)))return void qe(r,{matches:u},{flushSync:c});I=new AbortController;let d,h=fe(t.history,r,I.signal,o&&o.submission);if(o&&o.pendingError)d=[Ce(u).route.id,{type:m.error,error:o.pendingError}];else if(o&&o.submission&&Ve(o.submission.formMethod)){let n=await async function(t,n,r,o,i,s){void 0===s&&(s={}),$e();let a,l=function(e,t){return{state:"submitting",location:e,formMethod:t.formMethod,formAction:t.formAction,formEncType:t.formEncType,formData:t.formData,json:t.json,text:t.text}}(n,r);if(Be({navigation:l},{flushSync:!0===s.flushSync}),i){let e=await pt(o,n.pathname,t.signal);if("aborted"===e.type)return{shortCircuited:!0};if("error"===e.type){let{error:t,notFoundMatches:r,route:o}=st(n.pathname,e);return{matches:r,pendingActionResult:[o.id,{type:m.error,error:t}]}}if(!e.matches){let{notFoundMatches:e,error:t,route:r}=it(n.pathname);return{matches:e,pendingActionResult:[r.id,{type:m.error,error:t}]}}o=e.matches}let u=Ae(o,n);if(u.route.action||u.route.lazy){if(a=(await Ue("action",t,[u],o))[0],t.signal.aborted)return{shortCircuited:!0}}else a={type:m.error,error:xe(405,{method:t.method,pathname:n.pathname,routeId:u.route.id})};if(Te(a)){let e;return e=s&&null!=s.replace?s.replace:he(a.response.headers.get("Location"),new URL(t.url),g)===k.location.pathname+k.location.search,await Qe(t,a,{submission:r,replace:e}),{shortCircuited:!0}}if(Se(a))throw xe(400,{type:"defer-action"});if(Oe(a)){let t=Ce(o,u.route.id);return!0!==(s&&s.replace)&&(D=e.Push),{matches:o,pendingActionResult:[t.route.id,a]}}return{matches:o,pendingActionResult:[u.route.id,a]}}(h,r,o.submission,u,p.active,{replace:o.replace,flushSync:c});if(n.shortCircuited)return;if(n.pendingActionResult){let[e,t]=n.pendingActionResult;if(Oe(t)&&z(t.error)&&404===t.error.status)return I=null,void qe(r,{matches:n.matches,loaderData:{},errors:{[e]:t.error}})}u=n.matches||u,d=n.pendingActionResult,l=Ne(r,o.submission),c=!1,p.active=!1,h=fe(t.history,h.url,h.signal)}let{shortCircuited:y,matches:b,loaderData:C,errors:w}=await async function(e,n,r,o,i,l,u,c,p,d,h){let m=i||Ne(n,l),y=l||u||De(m),v=!(F||x.v7_partialHydration&&p);if(o){if(v){let e=ze(h);Be(a({navigation:m},void 0!==e?{actionData:e}:{}),{flushSync:d})}let t=await pt(r,n.pathname,e.signal);if("aborted"===t.type)return{shortCircuited:!0};if("error"===t.type){let{error:e,notFoundMatches:r,route:o}=st(n.pathname,t);return{matches:r,loaderData:{},errors:{[o.id]:e}}}if(!t.matches){let{error:e,notFoundMatches:t,route:r}=it(n.pathname);return{matches:t,loaderData:{},errors:{[r.id]:e}}}r=t.matches}let b=s||f,[C,w]=ie(t.history,k,r,y,n,x.v7_partialHydration&&!0===p,x.unstable_skipActionErrorRevalidation,B,q,Q,ge,ae,se,b,g,h);if(at((e=>!(r&&r.some((t=>t.route.id===e)))||C&&C.some((t=>t.route.id===e)))),$=++W,0===C.length&&0===w.length){let e=et();return qe(n,a({matches:r,loaderData:{},errors:h&&Oe(h[1])?{[h[0]]:h[1].error}:null},be(h),e?{fetchers:new Map(k.fetchers)}:{}),{flushSync:d}),{shortCircuited:!0}}if(v){let e={};if(!o){e.navigation=m;let t=ze(h);void 0!==t&&(e.actionData=t)}w.length>0&&(e.fetchers=function(e){return e.forEach((e=>{let t=k.fetchers.get(e.key),n=Me(void 0,t?t.data:void 0);k.fetchers.set(e.key,n)})),new Map(k.fetchers)}(w)),Be(e,{flushSync:d})}w.forEach((e=>{U.has(e.key)&&Xe(e.key),e.controller&&U.set(e.key,e.controller)}));let E=()=>w.forEach((e=>Xe(e.key)));I&&I.signal.addEventListener("abort",E);let{loaderResults:P,fetcherResults:S}=await We(k.matches,r,C,w,e);if(e.signal.aborted)return{shortCircuited:!0};I&&I.signal.removeEventListener("abort",E),w.forEach((e=>U.delete(e.key)));let O=Ee([...P,...S]);if(O){if(O.idx>=C.length){let e=w[O.idx-C.length].key;se.add(e)}return await Qe(e,O.result,{replace:c}),{shortCircuited:!0}}let{loaderData:T,errors:_}=ye(k,r,C,P,h,w,S,Pe);Pe.forEach(((e,t)=>{e.subscribe((n=>{(n||e.done)&&Pe.delete(t)}))})),x.v7_partialHydration&&p&&k.errors&&Object.entries(k.errors).filter((e=>{let[t]=e;return!C.some((e=>e.route.id===t))})).forEach((e=>{let[t,n]=e;_=Object.assign(_||{},{[t]:n})}));let V=et(),R=tt($),A=V||R||w.length>0;return a({matches:r,loaderData:T,errors:_},A?{fetchers:new Map(k.fetchers)}:{})}(h,r,u,p.active,l,o&&o.submission,o&&o.fetcherSubmission,o&&o.replace,o&&!0===o.initialHydration,c,d);y||(I=null,qe(r,a({matches:b||u},be(d),{loaderData:C,errors:w})))}function ze(e){return e&&!Oe(e[1])?{[e[0]]:e[1].data}:k.actionData?0===Object.keys(k.actionData).length?null:k.actionData:void 0}async function Qe(o,i,s){let{submission:l,fetcherSubmission:c,replace:p}=void 0===s?{}:s;i.response.headers.has("X-Remix-Revalidate")&&(B=!0);let h=i.response.headers.get("Location");u(h,"Expected a Location header on the redirect Response"),h=he(h,new URL(o.url),g);let f=d(k.location,h,{_isRedirect:!0});if(r){let e=!1;if(i.response.headers.has("X-Remix-Reload-Document"))e=!0;else if(ee.test(h)){const r=t.history.createURL(h);e=r.origin!==n.location.origin||null==A(r.pathname,g)}if(e)return void(p?n.location.replace(h):n.location.assign(h))}I=null;let m=!0===p?e.Replace:e.Push,{formMethod:y,formAction:v,formEncType:b}=k.navigation;!l&&!c&&y&&v&&b&&(l=De(k.navigation));let C=l||c;if(Y.has(i.response.status)&&C&&Ve(C.formMethod))await He(m,f,{submission:a({},C,{formAction:h}),preventScrollReset:N});else{let e=Ne(f,l);await He(m,f,{overrideNavigation:e,fetcherSubmission:c,preventScrollReset:N})}}async function Ue(e,t,n,r){try{let o=await async function(e,t,n,r,o,i,s,l){let c=r.reduce(((e,t)=>e.add(t.route.id)),new Set),p=new Set,d=await e({matches:o.map((e=>{let r=c.has(e.route.id);return a({},e,{shouldLoad:r,resolve:o=>(p.add(e.route.id),r?async function(e,t,n,r,o,i,s){let a,l,c=r=>{let o,a=new Promise(((e,t)=>o=t));l=()=>o(),t.signal.addEventListener("abort",l);let u,c=o=>"function"!=typeof r?Promise.reject(new Error('You cannot call the handler for a route which defines a boolean "'+e+'" [routeId: '+n.route.id+"]")):r({request:t,params:n.params,context:s},...void 0!==o?[o]:[]);return u=i?i((e=>c(e))):(async()=>{try{return{type:"data",result:await c()}}catch(e){return{type:"error",result:e}}})(),Promise.race([u,a])};try{let i=n.route[e];if(n.route.lazy)if(i){let e,[t]=await Promise.all([c(i).catch((t=>{e=t})),ce(n.route,o,r)]);if(void 0!==e)throw e;a=t}else{if(await ce(n.route,o,r),i=n.route[e],!i){if("action"===e){let e=new URL(t.url),r=e.pathname+e.search;throw xe(405,{method:t.method,pathname:r,routeId:n.route.id})}return{type:m.data,result:void 0}}a=await c(i)}else{if(!i){let e=new URL(t.url);throw xe(404,{pathname:e.pathname+e.search})}a=await c(i)}u(void 0!==a.result,"You defined "+("action"===e?"an action":"a loader")+' for route "'+n.route.id+"\" but didn't return anything from your `"+e+"` function. Please return a value or `null`.")}catch(e){return{type:m.error,result:e}}finally{l&&t.signal.removeEventListener("abort",l)}return a}(t,n,e,i,s,o,l):Promise.resolve({type:m.data,result:void 0}))})})),request:n,params:o[0].params,context:l});return o.forEach((e=>u(p.has(e.route.id),'`match.resolve()` was not called for route id "'+e.route.id+'". You must call `match.resolve()` on every match passed to `dataStrategy` to ensure all routes are properly loaded.'))),d.filter(((e,t)=>c.has(o[t].route.id)))}(C,e,t,n,r,h,i);return await Promise.all(o.map(((e,o)=>{if(function(e){return _e(e.result)&&J.has(e.result.status)}(e)){let i=e.result;return{type:m.redirect,response:de(i,t,n[o].route.id,r,g,x.v7_relativeSplatPath)}}return async function(e){let{result:t,type:n,status:r}=e;if(_e(t)){let e;try{let n=t.headers.get("Content-Type");e=n&&/\bapplication\/json\b/.test(n)?null==t.body?null:await t.json():await t.text()}catch(e){return{type:m.error,error:e}}return n===m.error?{type:m.error,error:new H(t.status,t.statusText,e),statusCode:t.status,headers:t.headers}:{type:m.data,data:e,statusCode:t.status,headers:t.headers}}return n===m.error?{type:m.error,error:t,statusCode:z(t)?t.status:r}:function(e){let t=e;return t&&"object"==typeof t&&"object"==typeof t.data&&"function"==typeof t.subscribe&&"function"==typeof t.cancel&&"function"==typeof t.resolveData}(t)?{type:m.deferred,deferredData:t,statusCode:null==(o=t.init)?void 0:o.status,headers:(null==(i=t.init)?void 0:i.headers)&&new Headers(t.init.headers)}:{type:m.data,data:t,statusCode:r};var o,i}(e)})))}catch(e){return n.map((()=>({type:m.error,error:e})))}}async function We(e,n,r,o,i){let[s,...a]=await Promise.all([r.length?Ue("loader",i,r,n):[],...o.map((e=>e.matches&&e.match&&e.controller?Ue("loader",fe(t.history,e.path,e.controller.signal),[e.match],e.matches).then((e=>e[0])):Promise.resolve({type:m.error,error:xe(404,{pathname:e.path})})))]);return await Promise.all([Re(e,r,s,s.map((()=>i.signal)),!1,k.loaderData),Re(e,o.map((e=>e.match)),a,o.map((e=>e.controller?e.controller.signal:null)),!0)]),{loaderResults:s,fetcherResults:a}}function $e(){B=!0,q.push(...at()),ae.forEach(((e,t)=>{U.has(t)&&(Q.push(t),Xe(t))}))}function Ge(e,t,n){void 0===n&&(n={}),k.fetchers.set(e,t),Be({fetchers:new Map(k.fetchers)},{flushSync:!0===(n&&n.flushSync)})}function Je(e,t,n,r){void 0===r&&(r={});let o=Ce(k.matches,t);Ke(e),Be({errors:{[o.route.id]:n},fetchers:new Map(k.fetchers)},{flushSync:!0===(r&&r.flushSync)})}function Ye(e){return x.v7_fetcherPersist&&(me.set(e,(me.get(e)||0)+1),ge.has(e)&&ge.delete(e)),k.fetchers.get(e)||X}function Ke(e){let t=k.fetchers.get(e);!U.has(e)||t&&"loading"===t.state&&G.has(e)||Xe(e),ae.delete(e),G.delete(e),se.delete(e),ge.delete(e),k.fetchers.delete(e)}function Xe(e){let t=U.get(e);u(t,"Expected fetch controller: "+e),t.abort(),U.delete(e)}function Ze(e){for(let t of e){let e=Le(Ye(t).data);k.fetchers.set(t,e)}}function et(){let e=[],t=!1;for(let n of se){let r=k.fetchers.get(n);u(r,"Expected fetcher: "+n),"loading"===r.state&&(se.delete(n),e.push(n),t=!0)}return Ze(e),t}function tt(e){let t=[];for(let[n,r]of G)if(r<e){let e=k.fetchers.get(n);u(e,"Expected fetcher: "+n),"loading"===e.state&&(Xe(n),G.delete(n),t.push(n))}return Ze(t),t.length>0}function nt(e){k.blockers.delete(e),ke.delete(e)}function rt(e,t){let n=k.blockers.get(e)||Z;u("unblocked"===n.state&&"blocked"===t.state||"blocked"===n.state&&"blocked"===t.state||"blocked"===n.state&&"proceeding"===t.state||"blocked"===n.state&&"unblocked"===t.state||"proceeding"===n.state&&"unblocked"===t.state,"Invalid blocker state transition: "+n.state+" -> "+t.state);let r=new Map(k.blockers);r.set(e,t),Be({blockers:r})}function ot(e){let{currentLocation:t,nextLocation:n,historyAction:r}=e;if(0===ke.size)return;ke.size>1&&c(!1,"A router only supports one blocker at a time");let o=Array.from(ke.entries()),[i,s]=o[o.length-1],a=k.blockers.get(i);return a&&"proceeding"===a.state?void 0:s({currentLocation:t,nextLocation:n,historyAction:r})?i:void 0}function it(e){let t=xe(404,{pathname:e}),n=s||f,{matches:r,route:o}=we(n);return at(),{notFoundMatches:r,route:o,error:t}}function st(e,t){let n=t.partialMatches,r=n[n.length-1].route;return{notFoundMatches:n,route:r,error:xe(400,{type:"route-discovery",routeId:r.id,pathname:e,message:null!=t.error&&"message"in t.error?t.error:String(t.error)})}}function at(e){let t=[];return Pe.forEach(((n,r)=>{e&&!e(r)||(n.cancel(),t.push(r),Pe.delete(r))})),t}function lt(e,t){if(O){return O(e,t.map((e=>function(e,t){let{route:n,pathname:r,params:o}=e;return{id:n.id,pathname:r,params:o,data:t[n.id],handle:n.handle}}(e,k.loaderData))))||e.key}return e.key}function ut(e,t){if(S){let n=lt(e,t),r=S[n];if("number"==typeof r)return r}return null}function ct(e,t,n){if(w){if(!e)return{active:!0,matches:b(t,n,g,!0)||[]};{let r=e[e.length-1].route;if(r.path&&("*"===r.path||r.path.endsWith("/*")))return{active:!0,matches:b(t,n,g,!0)}}}return{active:!1,matches:null}}async function pt(e,t,n){let r=e,o=r.length>0?r[r.length-1].route:null;for(;;){let e=null==s,a=s||f;try{await le(w,t,r,a,h,i,je,n)}catch(e){return{type:"error",error:e,partialMatches:r}}finally{e&&(f=[...f])}if(n.aborted)return{type:"aborted"};let l=v(a,t,g),u=!1;if(l){let e=l[l.length-1].route;if(e.index)return{type:"success",matches:l};if(e.path&&e.path.length>0){if("*"!==e.path)return{type:"success",matches:l};u=!0}}let c=b(a,t,g,!0);if(!c||r.map((e=>e.route.id)).join("-")===c.map((e=>e.route.id)).join("-"))return{type:"success",matches:u?l:null};if(r=c,o=r[r.length-1].route,"*"===o.path)return{type:"success",matches:r}}}return p={get basename(){return g},get future(){return x},get state(){return k},get routes(){return f},get window(){return n},initialize:function(){if(E=t.history.listen((e=>{let{action:n,location:r,delta:o}=e;if(Fe)return void(Fe=!1);c(0===ke.size||null!=o,"You are trying to use a blocker on a POP navigation to a location that was not created by @remix-run/router. This will fail silently in production. This can happen if you are navigating outside the router via `window.history.pushState`/`window.location.hash` instead of using router navigation APIs.  This can also happen if you are using createHashRouter and the user manually changes the URL.");let i=ot({currentLocation:k.location,nextLocation:r,historyAction:n});return i&&null!=o?(Fe=!0,t.history.go(-1*o),void rt(i,{state:"blocked",location:r,proceed(){rt(i,{state:"proceeding",proceed:void 0,reset:void 0,location:r}),t.history.go(o)},reset(){let e=new Map(k.blockers);e.set(i,Z),Be({blockers:e})}})):He(n,r)})),r){!function(e,t){try{let n=e.sessionStorage.getItem(ne);if(n){let e=JSON.parse(n);for(let[n,r]of Object.entries(e||{}))r&&Array.isArray(r)&&t.set(n,new Set(r||[]))}}catch(e){}}(n,L);let e=()=>function(e,t){if(t.size>0){let n={};for(let[e,r]of t)n[e]=[...r];try{e.sessionStorage.setItem(ne,JSON.stringify(n))}catch(e){c(!1,"Failed to save applied view transitions in sessionStorage ("+e+").")}}}(n,L);n.addEventListener("pagehide",e),j=()=>n.removeEventListener("pagehide",e)}return k.initialized||He(e.Pop,k.location,{initialHydration:!0}),p},subscribe:function(e){return P.add(e),()=>P.delete(e)},enableScrollRestoration:function(e,t,n){if(S=e,T=t,O=n||null,!_&&k.navigation===K){_=!0;let e=ut(k.location,k.matches);null!=e&&Be({restoreScrollPosition:e})}return()=>{S=null,T=null,O=null}},navigate:async function n(r,o){if("number"==typeof r)return void t.history.go(r);let i=re(k.location,k.matches,g,x.v7_prependBasename,r,x.v7_relativeSplatPath,null==o?void 0:o.fromRouteId,null==o?void 0:o.relative),{path:s,submission:l,error:u}=oe(x.v7_normalizeFormMethod,!1,i,o),c=k.location,p=d(k.location,s,o&&o.state);p=a({},p,t.history.encodeLocation(p));let h=o&&null!=o.replace?o.replace:void 0,f=e.Push;!0===h?f=e.Replace:!1===h||null!=l&&Ve(l.formMethod)&&l.formAction===k.location.pathname+k.location.search&&(f=e.Replace);let m=o&&"preventScrollReset"in o?!0===o.preventScrollReset:void 0,y=!0===(o&&o.unstable_flushSync),v=ot({currentLocation:c,nextLocation:p,historyAction:f});if(!v)return await He(f,p,{submission:l,pendingError:u,preventScrollReset:m,replace:o&&o.replace,enableViewTransition:o&&o.unstable_viewTransition,flushSync:y});rt(v,{state:"blocked",location:p,proceed(){rt(v,{state:"proceeding",proceed:void 0,reset:void 0,location:p}),n(r,o)},reset(){let e=new Map(k.blockers);e.set(v,Z),Be({blockers:e})}})},fetch:function(e,n,r,i){if(o)throw new Error("router.fetch() was called during the server render, but it shouldn't be. You are likely calling a useFetcher() method in the body of your component. Try moving it to a useEffect or a callback.");U.has(e)&&Xe(e);let a=!0===(i&&i.unstable_flushSync),l=s||f,c=re(k.location,k.matches,g,x.v7_prependBasename,r,x.v7_relativeSplatPath,n,null==i?void 0:i.relative),p=v(l,c,g),d=ct(p,l,c);if(d.active&&d.matches&&(p=d.matches),!p)return void Je(e,n,xe(404,{pathname:c}),{flushSync:a});let{path:h,submission:m,error:y}=oe(x.v7_normalizeFormMethod,!0,c,i);if(y)return void Je(e,n,y,{flushSync:a});let b=Ae(p,h);N=!0===(i&&i.preventScrollReset),m&&Ve(m.formMethod)?async function(e,n,r,o,i,a,l,c){function p(t){if(!t.route.action&&!t.route.lazy){let t=xe(405,{method:c.formMethod,pathname:r,routeId:n});return Je(e,n,t,{flushSync:l}),!0}return!1}if($e(),ae.delete(e),!a&&p(o))return;let d=k.fetchers.get(e);Ge(e,function(e,t){return{state:"submitting",formMethod:e.formMethod,formAction:e.formAction,formEncType:e.formEncType,formData:e.formData,json:e.json,text:e.text,data:t?t.data:void 0}}(c,d),{flushSync:l});let h=new AbortController,m=fe(t.history,r,h.signal,c);if(a){let t=await pt(i,r,m.signal);if("aborted"===t.type)return;if("error"===t.type){let{error:o}=st(r,t);return void Je(e,n,o,{flushSync:l})}if(!t.matches)return void Je(e,n,xe(404,{pathname:r}),{flushSync:l});if(p(o=Ae(i=t.matches,r)))return}U.set(e,h);let y=W,b=(await Ue("action",m,[o],i))[0];if(m.signal.aborted)return void(U.get(e)===h&&U.delete(e));if(x.v7_fetcherPersist&&ge.has(e)){if(Te(b)||Oe(b))return void Ge(e,Le(void 0))}else{if(Te(b))return U.delete(e),$>y?void Ge(e,Le(void 0)):(se.add(e),Ge(e,Me(c)),Qe(m,b,{fetcherSubmission:c}));if(Oe(b))return void Je(e,n,b.error)}if(Se(b))throw xe(400,{type:"defer-action"});let C=k.navigation.location||k.location,w=fe(t.history,C,h.signal),E=s||f,P="idle"!==k.navigation.state?v(E,k.navigation.location,g):k.matches;u(P,"Didn't find any matches after fetcher action");let S=++W;G.set(e,S);let O=Me(c,b.data);k.fetchers.set(e,O);let[T,_]=ie(t.history,k,P,c,C,!1,x.unstable_skipActionErrorRevalidation,B,q,Q,ge,ae,se,E,g,[o.route.id,b]);_.filter((t=>t.key!==e)).forEach((e=>{let t=e.key,n=k.fetchers.get(t),r=Me(void 0,n?n.data:void 0);k.fetchers.set(t,r),U.has(t)&&Xe(t),e.controller&&U.set(t,e.controller)})),Be({fetchers:new Map(k.fetchers)});let V=()=>_.forEach((e=>Xe(e.key)));h.signal.addEventListener("abort",V);let{loaderResults:R,fetcherResults:A}=await We(k.matches,P,T,_,w);if(h.signal.aborted)return;h.signal.removeEventListener("abort",V),G.delete(e),U.delete(e),_.forEach((e=>U.delete(e.key)));let N=Ee([...R,...A]);if(N){if(N.idx>=T.length){let e=_[N.idx-T.length].key;se.add(e)}return Qe(w,N.result)}let{loaderData:M,errors:L}=ye(k,k.matches,T,R,void 0,_,A,Pe);if(k.fetchers.has(e)){let t=Le(b.data);k.fetchers.set(e,t)}tt(S),"loading"===k.navigation.state&&S>$?(u(D,"Expected pending action"),I&&I.abort(),qe(k.navigation.location,{matches:P,loaderData:M,errors:L,fetchers:new Map(k.fetchers)})):(Be({errors:L,loaderData:ve(k.loaderData,M,P,L),fetchers:new Map(k.fetchers)}),B=!1)}(e,n,h,b,p,d.active,a,m):(ae.set(e,{routeId:n,path:h}),async function(e,n,r,o,i,s,a,l){let c=k.fetchers.get(e);Ge(e,Me(l,c?c.data:void 0),{flushSync:a});let p=new AbortController,d=fe(t.history,r,p.signal);if(s){let t=await pt(i,r,d.signal);if("aborted"===t.type)return;if("error"===t.type){let{error:o}=st(r,t);return void Je(e,n,o,{flushSync:a})}if(!t.matches)return void Je(e,n,xe(404,{pathname:r}),{flushSync:a});o=Ae(i=t.matches,r)}U.set(e,p);let h=W,f=(await Ue("loader",d,[o],i))[0];if(Se(f)&&(f=await Ie(f,d.signal,!0)||f),U.get(e)===p&&U.delete(e),!d.signal.aborted){if(!ge.has(e))return Te(f)?$>h?void Ge(e,Le(void 0)):(se.add(e),void await Qe(d,f)):void(Oe(f)?Je(e,n,f.error):(u(!Se(f),"Unhandled fetcher deferred data"),Ge(e,Le(f.data))));Ge(e,Le(void 0))}}(e,n,h,b,p,d.active,a,m))},revalidate:function(){$e(),Be({revalidation:"loading"}),"submitting"!==k.navigation.state&&("idle"!==k.navigation.state?He(D||k.historyAction,k.navigation.location,{overrideNavigation:k.navigation}):He(k.historyAction,k.location,{startUninterruptedRevalidation:!0}))},createHref:e=>t.history.createHref(e),encodeLocation:e=>t.history.encodeLocation(e),getFetcher:Ye,deleteFetcher:function(e){if(x.v7_fetcherPersist){let t=(me.get(e)||0)-1;t<=0?(me.delete(e),ge.add(e)):me.set(e,t)}else Ke(e);Be({fetchers:new Map(k.fetchers)})},dispose:function(){E&&E(),j&&j(),P.clear(),I&&I.abort(),k.fetchers.forEach(((e,t)=>Ke(t))),k.blockers.forEach(((e,t)=>nt(t)))},getBlocker:function(e,t){let n=k.blockers.get(e)||Z;return ke.get(e)!==t&&ke.set(e,t),n},deleteBlocker:nt,patchRoutes:function(e,t){let n=null==s;ue(e,t,s||f,h,i),n&&(f=[...f],Be({}))},_internalFetchControllers:U,_internalActiveDeferreds:Pe,_internalSetRoutes:function(e){h={},s=y(e,i,void 0,h)}},p}({basename:void 0,future:ut({},void 0,{v7_prependBasename:!0}),history:function(t){return void 0===t&&(t={}),function(t,n,r,o){void 0===o&&(o={});let{window:i=document.defaultView,v5Compat:s=!1}=o,c=i.history,f=e.Pop,m=null,g=y();function y(){return(c.state||{idx:null}).idx}function v(){f=e.Pop;let t=y(),n=null==t?null:t-g;g=t,m&&m({action:f,location:C.location,delta:n})}function b(e){let t="null"!==i.location.origin?i.location.origin:i.location.href,n="string"==typeof e?e:h(e);return n=n.replace(/ $/,"%20"),u(t,"No window.location.(origin|href) available to create URL for href: "+n),new URL(n,t)}null==g&&(g=0,c.replaceState(a({},c.state,{idx:g}),""));let C={get action(){return f},get location(){return t(i,c)},listen(e){if(m)throw new Error("A history only accepts one active listener");return i.addEventListener(l,v),m=e,()=>{i.removeEventListener(l,v),m=null}},createHref:e=>n(i,e),createURL:b,encodeLocation(e){let t=b(e);return{pathname:t.pathname,search:t.search,hash:t.hash}},push:function(t,n){f=e.Push;let o=d(C.location,t,n);r&&r(o,t),g=y()+1;let a=p(o,g),l=C.createHref(o);try{c.pushState(a,"",l)}catch(e){if(e instanceof DOMException&&"DataCloneError"===e.name)throw e;i.location.assign(l)}s&&m&&m({action:f,location:C.location,delta:1})},replace:function(t,n){f=e.Replace;let o=d(C.location,t,n);r&&r(o,t),g=y();let i=p(o,g),a=C.createHref(o);c.replaceState(i,"",a),s&&m&&m({action:f,location:C.location,delta:0})},go:e=>c.go(e)};return C}((function(e,t){let{pathname:n,search:r,hash:o}=e.location;return d("",{pathname:n,search:r,hash:o},t.state&&t.state.usr||null,t.state&&t.state.key||"default")}),(function(e,t){return"string"==typeof t?t:h(t)}),null,t)}({window:void 0}),hydrationData:function(){var e;let t=null==(e=window)?void 0:e.__staticRouterHydrationData;return t&&t.errors&&(t=ut({},t,{errors:dt(t.errors)})),t}(),routes:bT,mapRouteProperties:function(e){let n={hasErrorBoundary:null!=e.ErrorBoundary||null!=e.errorElement};return e.Component&&Object.assign(n,{element:t.createElement(e.Component),Component:void 0}),e.HydrateFallback&&Object.assign(n,{hydrateFallbackElement:t.createElement(e.HydrateFallback),HydrateFallback:void 0}),e.ErrorBoundary&&Object.assign(n,{errorElement:t.createElement(e.ErrorBoundary),ErrorBoundary:void 0}),n},unstable_dataStrategy:void 0,unstable_patchRoutesOnMiss:void 0,window:void 0}).initialize());const wT=function(){return t.createElement("div",{className:"app"},t.createElement(lE,null,t.createElement(Mn,null),t.createElement(bt,{router:CT}),t.createElement(vS,null)),t.createElement(Fn,null))};var xT=document.getElementById("root");(0,r.H)(xT).render(t.createElement(t.StrictMode,null,t.createElement(wT,null)))})()})();
\ No newline at end of file
diff --git a/docs/source/_static/.gitkeep b/docs/source/_static/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/setup.py b/setup.py
index b90ef73c6430c275ebf37561363d2df4c88c2826..643cdc9d16ddf0346d0cc2dce12fa679519f7361 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
 
 setup(
     name='compendium-v2',
-    version="0.63",
+    version="0.64",
     author='GEANT',
     author_email='swd@geant.org',
     description='Flask and React project for displaying '
diff --git a/test/data/2022_Connected_Users_DataSeries.xlsx b/test/data/2022_Connected_Users_DataSeries.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..7b24ba336d55a2bff370dbc8bf0677819a95f6e0
Binary files /dev/null and b/test/data/2022_Connected_Users_DataSeries.xlsx differ
diff --git a/test/data/2022_Networks_DataSeries.xlsx b/test/data/2022_Networks_DataSeries.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..a485a064eaba465554666ee423a6559f64228638
Binary files /dev/null and b/test/data/2022_Networks_DataSeries.xlsx differ
diff --git a/test/data/NREN-Services-prefills_2023_Recovered.xlsx b/test/data/NREN-Services-prefills_2023_Recovered.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..fdc64ed6d40d85dc137be94e356c845167467f90
Binary files /dev/null and b/test/data/NREN-Services-prefills_2023_Recovered.xlsx differ
diff --git a/test/test_send_mail.py b/test/test_send_mail.py
index 1bdeb5a37565dd419f8ce1e0ad366932d3f6db51..eb66a4de1d2774d3e2410e27afcc916a806103ce 100644
--- a/test/test_send_mail.py
+++ b/test/test_send_mail.py
@@ -27,7 +27,7 @@ def decode_message(msg: MIMEMultipart):
 
 
 @contextmanager
-def test_user(app):
+def mock_user(app):
     with app.app_context():
         app.config['MAIL_ENABLE'] = True
         app.config['MAIL_SERVER'] = 'localhost'
@@ -52,7 +52,7 @@ def test_signup_email_admin(app, mocked_admin_user, mocker):
 
     mocker.patch('compendium_v2.email._send_mail', _send_mail)
     mocker.patch('compendium_v2.email.Thread', MockedThread)
-    with test_user(app) as user:
+    with mock_user(app) as user:
         send_admin_signup_notification(user)
 
 
@@ -69,5 +69,5 @@ def test_signup_email_user(app, mocker):
 
     mocker.patch('compendium_v2.email._send_mail', _send_mail)
     mocker.patch('threading.Thread', MockedThread)
-    with test_user(app) as user:
+    with mock_user(app) as user:
         send_user_signup_notification(user, 'testname')
diff --git a/test/test_survey_publisher.py b/test/test_survey_publisher.py
index 140356e22e7db89e5e68a3dc0e3f7b9d6d24999a..5b9eb9d3c4a92d12522a2ee7f73157a9a9be38c6 100644
--- a/test/test_survey_publisher.py
+++ b/test/test_survey_publisher.py
@@ -6,7 +6,8 @@ from sqlalchemy import func, select
 
 from compendium_v2 import db
 from compendium_v2.db import presentation_model_enums as model_enums, presentation_models
-from compendium_v2.publishers.survey_publisher import _map_2023
+from compendium_v2.db.survey_models import Survey, SurveyResponse, ResponseStatus, SurveyStatus
+from compendium_v2.publishers.survey_publisher import publish
 
 
 JSON_FILE = os.path.join(os.path.dirname(__file__), "data", "2023_all_questions_answered.json")
@@ -17,13 +18,13 @@ def test_v2_publisher_empty(app):
 
     with app.app_context():
         nren = presentation_models.NREN(name='name', country='country')
+        db.session.add(nren)
+        survey = Survey(year=2023, survey={}, status=SurveyStatus.open)
+        response = SurveyResponse(survey=survey, nren=nren, answers={"data": data}, status=ResponseStatus.completed)
+        db.session.add(survey)
+        db.session.add(response)
         db.session.commit()
 
-    with app.app_context():
-        _map_2023(nren, {"data": data})
-        db.session.commit()
-
-    with app.app_context():
         budget_count = db.session.scalar(select(func.count(presentation_models.BudgetEntry.year)))
         assert budget_count == 0
         # the main thing is actually that it doesnt crash
@@ -35,13 +36,15 @@ def test_v2_publisher_full(app):
 
     with app.app_context():
         nren = presentation_models.NREN(name='name', country='country')
+        db.session.add(nren)
+        survey = Survey(year=2023, survey={}, status=SurveyStatus.open)
+        response = SurveyResponse(survey=survey, nren=nren, answers={"data": data}, status=ResponseStatus.completed)
+        db.session.add(survey)
+        db.session.add(response)
         db.session.commit()
 
-    with app.app_context():
-        _map_2023(nren, {"data": data})
-        db.session.commit()
+        publish(2023)
 
-    with app.app_context():
         budget = db.session.scalar(select(presentation_models.BudgetEntry.budget))
         assert budget == Decimal("124.76")
 
diff --git a/test/test_survey_publisher_legacy_excel.py b/test/test_survey_publisher_legacy_excel.py
index 5f0ee46dc02808ac8a8c28fc5a241efd266cc107..6791c5d51d6cecd0ed43b7bd05a9345187d7efbf 100644
--- a/test/test_survey_publisher_legacy_excel.py
+++ b/test/test_survey_publisher_legacy_excel.py
@@ -1,16 +1,25 @@
 import os
+import openpyxl
 
 from sqlalchemy import select, func
-
 from compendium_v2 import db
 from compendium_v2.db import presentation_models
 from compendium_v2.publishers.survey_publisher_legacy_excel import _cli
 
-EXCEL_FILE = os.path.join(os.path.dirname(__file__), "data", "2021_Organisation_DataSeries.xlsx")
-
 
 def test_excel_publisher(app_with_survey_db, mocker):
-    mocker.patch('compendium_v2.publishers.excel_parser.EXCEL_FILE_ORGANISATION', EXCEL_FILE)
+    EXCEL_FILE_ORGANISATION = openpyxl.load_workbook(os.path.join(os.path.dirname(
+        __file__), "data", "2021_Organisation_DataSeries.xlsx"), data_only=True, read_only=True)
+    EXCEL_FILE_USERS = openpyxl.load_workbook(os.path.join(os.path.dirname(
+        __file__), "data", "2022_Connected_Users_DataSeries.xlsx"), data_only=True, read_only=True)
+    EXCEL_FILE_NETWORKS = openpyxl.load_workbook(os.path.join(os.path.dirname(
+        __file__), "data", "2022_Networks_DataSeries.xlsx"), data_only=True, read_only=True)
+    EXCEL_FILE_NREN_SERVICES = openpyxl.load_workbook(os.path.join(os.path.dirname(
+        __file__), "data", "NREN-Services-prefills_2023_Recovered.xlsx"), data_only=True, read_only=True)
+    mocker.patch('compendium_v2.publishers.excel_parser.EXCEL_FILE_ORGANISATION', EXCEL_FILE_ORGANISATION)
+    mocker.patch('compendium_v2.publishers.excel_parser.EXCEL_FILE_USERS', EXCEL_FILE_USERS)
+    mocker.patch('compendium_v2.publishers.excel_parser.EXCEL_FILE_NETWORKS', EXCEL_FILE_NETWORKS)
+    mocker.patch('compendium_v2.publishers.excel_parser.EXCEL_FILE_NREN_SERVICES', EXCEL_FILE_NREN_SERVICES)
 
     with app_with_survey_db.app_context():
         nren_names = ['SURF', 'KIFU', 'University of Malta', 'ASNET-AM', 'SIKT', 'LAT', 'RASH', 'AzScienceNet', 'GRNET',
diff --git a/tox.ini b/tox.ini
index d23020ad33673fbca5973da6129da7d076054e82..17ca219ae652bb65270b7be94ed61d5a4b11baad 100644
--- a/tox.ini
+++ b/tox.ini
@@ -20,7 +20,7 @@ deps =
 
 commands =
     coverage erase
-    pytest -n auto --cov compendium_v2 --cov-fail-under=80 --cov-report html --cov-report xml --cov-report term -p no:checkdocs
+    pytest -n auto --cov compendium_v2 --cov-fail-under=80 --cov-report html --cov-report xml --cov-report term -p no:checkdocs --durations=5
     flake8
     mypy {toxinidir}/compendium_v2/ {toxinidir}/test/
     sphinx-build -M html docs/source docs/build -E