From 053e7ca775a74369d7668df7e57d1d078ffaf023 Mon Sep 17 00:00:00 2001
From: Jessica McInchak <jessica.mcinchak@gmail.com>
Date: Mon, 11 Mar 2024 10:32:09 +0100
Subject: [PATCH 1/2] populate fee explanations

---
 src/export/digitalPlanning/model.test.ts      |  1 +
 src/export/digitalPlanning/model.ts           | 45 ++++++++++++
 src/export/digitalPlanning/schema/schema.json | 72 ++++++++++++++++++-
 src/export/digitalPlanning/schema/types.d.ts  | 24 ++++++-
 4 files changed, 138 insertions(+), 4 deletions(-)

diff --git a/src/export/digitalPlanning/model.test.ts b/src/export/digitalPlanning/model.test.ts
index 03e5a03b..47bfa2e3 100644
--- a/src/export/digitalPlanning/model.test.ts
+++ b/src/export/digitalPlanning/model.test.ts
@@ -101,6 +101,7 @@ describe("DigitalPlanning", () => {
         });
 
         const payload = instance.getPayload();
+        console.log(payload.metadata);
 
         expect(payload).toEqual(instance.payload);
       });
diff --git a/src/export/digitalPlanning/model.ts b/src/export/digitalPlanning/model.ts
index c04cf225..476b8bec 100644
--- a/src/export/digitalPlanning/model.ts
+++ b/src/export/digitalPlanning/model.ts
@@ -7,8 +7,10 @@ import { Passport } from "../../models";
 import { getResultData } from "../../models/result";
 import {
   Breadcrumbs,
+  ComponentType,
   EnhancedGISResponse,
   FlowGraph,
+  Node,
   Passport as IPassport,
   SessionMetadata,
   Value,
@@ -16,11 +18,13 @@ import {
 import {
   extractFileDescriptionForPassportKey,
   formatProposalDetails,
+  parsePolicyRefs,
 } from "../bops";
 import jsonSchema from "./schema/schema.json";
 import {
   ApplicationType,
   DigitalPlanningApplication as Payload,
+  FeeExplanation,
   File,
   FileType,
   GeoJSON,
@@ -814,6 +818,46 @@ export class DigitalPlanning {
     return requestedFiles;
   }
 
+  private getFeeExplanations(): FeeExplanation {
+    const explanations: FeeExplanation = {
+      calculated: [],
+      payable: [],
+    };
+
+    const fns = ["application.fee.calculated", "application.fee.payable"];
+    const calculateNodes = Object.entries(this.flow)
+      .filter(
+        ([nodeId, node]: [string, Node]) =>
+          node?.type === ComponentType.Calculate &&
+          fns.includes(node.data?.output as string) &&
+          Object.keys(this.breadcrumbs).includes(nodeId),
+      )
+      .map(([_nodeId, node]: [string, Node]) => node);
+
+    if (!calculateNodes) {
+      return explanations;
+    }
+
+    calculateNodes.forEach((node: Node) => {
+      const suffix = (node.data?.output as string).split(".").pop() as string;
+      explanations[suffix].push({
+        ...(node.data?.info && { description: node.data.info }),
+        ...(node.data?.policyRef && {
+          policyRefs: parsePolicyRefs(node.data.policyRef as string),
+        }),
+      });
+    });
+
+    if (
+      explanations.calculated.length > 0 &&
+      explanations.payable.length === 0
+    ) {
+      explanations["payable"] = explanations["calculated"];
+    }
+
+    return explanations;
+  }
+
   private getMetadata(): Payload["metadata"] {
     return {
       id: this.sessionId,
@@ -824,6 +868,7 @@ export class DigitalPlanning {
         flowId: this.metadata.flow.id,
         url: `https://www.editor.planx.uk/${this.metadata.flow.team.slug}/${this.metadata.flow.slug}/preview`,
         files: this.getRequestedFiles(),
+        fee: this.getFeeExplanations(),
       },
       schema: `https://theopensystemslab.github.io/digital-planning-data-schemas/${jsonSchema["$id"]}/schema.json`,
     } as PlanXMetadata;
diff --git a/src/export/digitalPlanning/schema/schema.json b/src/export/digitalPlanning/schema/schema.json
index c7285f2f..5add0434 100644
--- a/src/export/digitalPlanning/schema/schema.json
+++ b/src/export/digitalPlanning/schema/schema.json
@@ -1,5 +1,5 @@
 {
-  "$id": "v0.4.0",
+  "$id": "@next",
   "$schema": "http://json-schema.org/draft-07/schema#",
   "additionalProperties": false,
   "definitions": {
@@ -1344,6 +1344,33 @@
       },
       "type": "object"
     },
+    "CalculateMetadata": {
+      "$id": "#CalculateMetadata",
+      "additionalProperties": false,
+      "description": "Metadata associated with PlanX Calculate components used to determine fees throughout a service",
+      "properties": {
+        "description": {
+          "type": "string"
+        },
+        "policyRefs": {
+          "items": {
+            "additionalProperties": false,
+            "properties": {
+              "text": {
+                "type": "string"
+              },
+              "url": {
+                "$ref": "#/definitions/URL"
+              }
+            },
+            "required": ["text"],
+            "type": "object"
+          },
+          "type": "array"
+        }
+      },
+      "type": "object"
+    },
     "Date": {
       "format": "date",
       "type": "string"
@@ -1439,6 +1466,27 @@
       "required": ["features", "type"],
       "type": "object"
     },
+    "FeeExplanation": {
+      "$id": "#FeeExplanation",
+      "additionalProperties": false,
+      "description": "An explanation, including policy references, of the calculated and payable fees associated with this application",
+      "properties": {
+        "calculated": {
+          "items": {
+            "$ref": "#/definitions/CalculateMetadata"
+          },
+          "type": "array"
+        },
+        "payable": {
+          "items": {
+            "$ref": "#/definitions/CalculateMetadata"
+          },
+          "type": "array"
+        }
+      },
+      "required": ["calculated", "payable"],
+      "type": "object"
+    },
     "File": {
       "$id": "#File",
       "additionalProperties": false,
@@ -2693,6 +2741,21 @@
           "required": ["value", "description"],
           "type": "object"
         },
+        {
+          "additionalProperties": false,
+          "properties": {
+            "description": {
+              "const": "Viability Appraisal",
+              "type": "string"
+            },
+            "value": {
+              "const": "viabilityAppraisal",
+              "type": "string"
+            }
+          },
+          "required": ["value", "description"],
+          "type": "object"
+        },
         {
           "additionalProperties": false,
           "properties": {
@@ -3723,6 +3786,9 @@
         "service": {
           "additionalProperties": false,
           "properties": {
+            "fee": {
+              "$ref": "#/definitions/FeeExplanation"
+            },
             "files": {
               "$ref": "#/definitions/RequestedFiles"
             },
@@ -3733,7 +3799,7 @@
               "$ref": "#/definitions/URL"
             }
           },
-          "required": ["flowId", "url", "files"],
+          "required": ["flowId", "url", "files", "fee"],
           "type": "object"
         },
         "source": {
@@ -17681,7 +17747,7 @@
     "RequestedFiles": {
       "$id": "#RequestedFiles",
       "additionalProperties": false,
-      "description": "File types requested by this service. Schema[\"files\"] will be a subset of this list based on the user's journey through the service.",
+      "description": "File types requested by this service. Schema[\"files\"] will be a subset of this list based on the user's journey through the service",
       "properties": {
         "optional": {
           "items": {
diff --git a/src/export/digitalPlanning/schema/types.d.ts b/src/export/digitalPlanning/schema/types.d.ts
index e10749c3..36c18639 100644
--- a/src/export/digitalPlanning/schema/types.d.ts
+++ b/src/export/digitalPlanning/schema/types.d.ts
@@ -4386,6 +4386,10 @@ export type FileType =
       description: "Ventilation or extraction statement";
       value: "ventilationStatement";
     }
+  | {
+      description: "Viability Appraisal";
+      value: "viabilityAppraisal";
+    }
   | {
       description: "Visualisations";
       value: "visualisations";
@@ -5293,6 +5297,7 @@ export interface PlanXMetadata {
   organisation: string;
   schema: URL;
   service: {
+    fee: FeeExplanation;
     files: RequestedFiles;
     flowId: UUID;
     url: URL;
@@ -5301,7 +5306,24 @@ export interface PlanXMetadata {
   submittedAt: DateTime;
 }
 /**
- * File types requested by this service. Schema["files"] will be a subset of this list based on the user's journey through the service.
+ * An explanation, including policy references, of the calculated and payable fees associated with this application
+ */
+export interface FeeExplanation {
+  calculated: CalculateMetadata[];
+  payable: CalculateMetadata[];
+}
+/**
+ * Metadata associated with PlanX Calculate components used to determine fees throughout a service
+ */
+export interface CalculateMetadata {
+  description?: string;
+  policyRefs?: {
+    text: string;
+    url?: URL;
+  }[];
+}
+/**
+ * File types requested by this service. Schema["files"] will be a subset of this list based on the user's journey through the service
  */
 export interface RequestedFiles {
   optional: FileType[];

From 4eee5ff9d3c513b5d270026b367a1cf9a4df9374 Mon Sep 17 00:00:00 2001
From: Jessica McInchak <jessica.mcinchak@gmail.com>
Date: Fri, 15 Mar 2024 17:44:59 +0100
Subject: [PATCH 2/2] add schema v0.4.1

---
 src/export/digitalPlanning/model.test.ts      |   1 -
 src/export/digitalPlanning/schema/schema.json | 138 +++++++++++++++++-
 src/export/digitalPlanning/schema/types.d.ts  |  49 +++++++
 3 files changed, 186 insertions(+), 2 deletions(-)

diff --git a/src/export/digitalPlanning/model.test.ts b/src/export/digitalPlanning/model.test.ts
index 47bfa2e3..03e5a03b 100644
--- a/src/export/digitalPlanning/model.test.ts
+++ b/src/export/digitalPlanning/model.test.ts
@@ -101,7 +101,6 @@ describe("DigitalPlanning", () => {
         });
 
         const payload = instance.getPayload();
-        console.log(payload.metadata);
 
         expect(payload).toEqual(instance.payload);
       });
diff --git a/src/export/digitalPlanning/schema/schema.json b/src/export/digitalPlanning/schema/schema.json
index 5add0434..b0025726 100644
--- a/src/export/digitalPlanning/schema/schema.json
+++ b/src/export/digitalPlanning/schema/schema.json
@@ -1,5 +1,5 @@
 {
-  "$id": "@next",
+  "$id": "v0.4.1",
   "$schema": "http://json-schema.org/draft-07/schema#",
   "additionalProperties": false,
   "definitions": {
@@ -203,6 +203,9 @@
       "additionalProperties": false,
       "description": "Information about this planning application",
       "properties": {
+        "CIL": {
+          "$ref": "#/definitions/CommunityInfrastructureLevy"
+        },
         "declaration": {
           "$ref": "#/definitions/ApplicationDeclaration"
         },
@@ -1316,6 +1319,9 @@
           "required": ["area"],
           "type": "object"
         },
+        "materials": {
+          "$ref": "#/definitions/Materials"
+        },
         "new": {
           "additionalProperties": false,
           "properties": {
@@ -1371,6 +1377,26 @@
       },
       "type": "object"
     },
+    "CommunityInfrastructureLevy": {
+      "$id": "#CommunityInfrastructureLevy",
+      "additionalProperties": false,
+      "description": "Details about the Community Infrastructure Levy planning charge, if applicable",
+      "properties": {
+        "result": {
+          "enum": [
+            "exempt.annexe",
+            "exempt.extension",
+            "exempt.selfBuild",
+            "liable",
+            "relief.charity",
+            "relief.socialHousing"
+          ],
+          "type": "string"
+        }
+      },
+      "required": ["result"],
+      "type": "object"
+    },
     "Date": {
       "format": "date",
       "type": "string"
@@ -1511,6 +1537,21 @@
     "FileType": {
       "$id": "#FileType",
       "anyOf": [
+        {
+          "additionalProperties": false,
+          "properties": {
+            "description": {
+              "const": "Details of impact on access, roads, and rights of way",
+              "type": "string"
+            },
+            "value": {
+              "const": "accessRoadsRightsOfWayDetails",
+              "type": "string"
+            }
+          },
+          "required": ["value", "description"],
+          "type": "object"
+        },
         {
           "additionalProperties": false,
           "properties": {
@@ -1826,6 +1867,21 @@
           "required": ["value", "description"],
           "type": "object"
         },
+        {
+          "additionalProperties": false,
+          "properties": {
+            "description": {
+              "const": "External materials details",
+              "type": "string"
+            },
+            "value": {
+              "const": "externalMaterialsDetails",
+              "type": "string"
+            }
+          },
+          "required": ["value", "description"],
+          "type": "object"
+        },
         {
           "additionalProperties": false,
           "properties": {
@@ -2591,6 +2647,36 @@
           "required": ["value", "description"],
           "type": "object"
         },
+        {
+          "additionalProperties": false,
+          "properties": {
+            "description": {
+              "const": "Location of trees and hedges",
+              "type": "string"
+            },
+            "value": {
+              "const": "treeAndHedgeLocation",
+              "type": "string"
+            }
+          },
+          "required": ["value", "description"],
+          "type": "object"
+        },
+        {
+          "additionalProperties": false,
+          "properties": {
+            "description": {
+              "const": "Removed or pruned trees and hedges",
+              "type": "string"
+            },
+            "value": {
+              "const": "treeAndHedgeRemovedOrPruned",
+              "type": "string"
+            }
+          },
+          "required": ["value", "description"],
+          "type": "object"
+        },
         {
           "additionalProperties": false,
           "properties": {
@@ -2915,6 +3001,9 @@
           "required": ["area"],
           "type": "object"
         },
+        "materials": {
+          "$ref": "#/definitions/Materials"
+        },
         "new": {
           "additionalProperties": false,
           "properties": {
@@ -3466,6 +3555,9 @@
           "required": ["site", "area"],
           "type": "object"
         },
+        "details": {
+          "$ref": "#/definitions/PropertyDetails"
+        },
         "localAuthorityDistrict": {
           "description": "Current and historic UK Local Authority Districts that contain this address sourced from planning.data.gov.uk/dataset/local-authority-district",
           "items": {
@@ -3557,6 +3649,36 @@
       ],
       "type": "object"
     },
+    "Materials": {
+      "additionalProperties": false,
+      "properties": {
+        "boundary": {
+          "type": "string"
+        },
+        "door": {
+          "type": "string"
+        },
+        "lighting": {
+          "type": "string"
+        },
+        "other": {
+          "type": "string"
+        },
+        "roof": {
+          "type": "string"
+        },
+        "surface": {
+          "type": "string"
+        },
+        "wall": {
+          "type": "string"
+        },
+        "window": {
+          "type": "string"
+        }
+      },
+      "type": "object"
+    },
     "Metadata": {
       "$id": "#DigitalPlanningMetadata",
       "anyOf": [
@@ -10628,6 +10750,17 @@
       ],
       "description": "Information about the site where the works will happen"
     },
+    "PropertyDetails": {
+      "$id": "#PropertyDetails",
+      "additionalProperties": false,
+      "description": "Details about the property as it currently exists",
+      "properties": {
+        "materials": {
+          "$ref": "#/definitions/Materials"
+        }
+      },
+      "type": "object"
+    },
     "PropertyType": {
       "$id": "#PropertyType",
       "anyOf": [
@@ -18347,6 +18480,9 @@
           "required": ["site", "area"],
           "type": "object"
         },
+        "details": {
+          "$ref": "#/definitions/PropertyDetails"
+        },
         "localAuthorityDistrict": {
           "description": "Current and historic UK Local Authority Districts that contain this address sourced from planning.data.gov.uk/dataset/local-authority-district",
           "items": {
diff --git a/src/export/digitalPlanning/schema/types.d.ts b/src/export/digitalPlanning/schema/types.d.ts
index 36c18639..c90b9a83 100644
--- a/src/export/digitalPlanning/schema/types.d.ts
+++ b/src/export/digitalPlanning/schema/types.d.ts
@@ -4058,6 +4058,10 @@ export type ProjectType =
  * Types of planning documents and drawings
  */
 export type FileType =
+  | {
+      description: "Details of impact on access, roads, and rights of way";
+      value: "accessRoadsRightsOfWayDetails";
+    }
   | {
       description: "Affordable housing statement";
       value: "affordableHousingStatement";
@@ -4142,6 +4146,10 @@ export type FileType =
       description: "Environmental Impact Assessment (EIA)";
       value: "environmentalImpactAssessment";
     }
+  | {
+      description: "External materials details";
+      value: "externalMaterialsDetails";
+    }
   | {
       description: "Fire safety report";
       value: "fireSafetyReport";
@@ -4346,6 +4354,14 @@ export type FileType =
       description: "Travel plan";
       value: "travelPlan";
     }
+  | {
+      description: "Location of trees and hedges";
+      value: "treeAndHedgeLocation";
+    }
+  | {
+      description: "Removed or pruned trees and hedges";
+      value: "treeAndHedgeRemovedOrPruned";
+    }
   | {
       description: "Tree canopy calculator";
       value: "treeCanopyCalculator";
@@ -4668,11 +4684,24 @@ export interface Agent {
  * Information about this planning application
  */
 export interface Application {
+  CIL?: CommunityInfrastructureLevy;
   declaration: ApplicationDeclaration;
   fee: ApplicationFee;
   preApp?: PreApplication;
   type: ApplicationType;
 }
+/**
+ * Details about the Community Infrastructure Levy planning charge, if applicable
+ */
+export interface CommunityInfrastructureLevy {
+  result:
+    | "exempt.annexe"
+    | "exempt.extension"
+    | "exempt.selfBuild"
+    | "liable"
+    | "relief.charity"
+    | "relief.socialHousing";
+}
 /**
  * Declarations about the accuracy of this application and any personal connections to the receiving authority
  */
@@ -4731,6 +4760,7 @@ export interface UKProperty {
     area: Area;
     site: GeoJSON;
   };
+  details?: PropertyDetails;
   /**
    * Current and historic UK Local Authority Districts that contain this address sourced from planning.data.gov.uk/dataset/local-authority-district
    */
@@ -5012,6 +5042,22 @@ export interface Feature3CGeometry2CGeoJsonProperties3E {
    */
   type: "Feature";
 }
+/**
+ * Details about the property as it currently exists
+ */
+export interface PropertyDetails {
+  materials?: Materials;
+}
+export interface Materials {
+  boundary?: string;
+  door?: string;
+  lighting?: string;
+  other?: string;
+  roof?: string;
+  surface?: string;
+  wall?: string;
+  window?: string;
+}
 /**
  * Property details for sites within the Greater London Authority (GLA) area
  */
@@ -5025,6 +5071,7 @@ export interface LondonProperty {
     area: Area;
     site: GeoJSON;
   };
+  details?: PropertyDetails;
   /**
    * Current and historic UK Local Authority Districts that contain this address sourced from planning.data.gov.uk/dataset/local-authority-district
    */
@@ -5087,6 +5134,7 @@ export interface BaseDetails {
   extend?: {
     area: Area;
   };
+  materials?: Materials;
   new?: {
     area: Area;
     count?: {
@@ -5103,6 +5151,7 @@ export interface LondonDetails {
   extend?: {
     area: Area;
   };
+  materials?: Materials;
   new?: {
     area: Area;
     count?: {