Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: update ODP schema mapping to v0.2.0 #193

Merged
merged 4 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const mockLDCESession = {
slug: "apply-for-a-lawful-development-certificate",
team: {
slug: "buckinghamshire",
referenceCode: "BKM",
},
},
created_at: "2023-09-01T06:11:24.502169+00:00",
Expand Down Expand Up @@ -4505,6 +4506,7 @@ export const mockLDCPSession = {
slug: "apply-for-a-lawful-development-certificate",
team: {
slug: "buckinghamshire",
referenceCode: "BKM",
},
},
created_at: "2023-09-04T05:59:05.516553+00:00",
Expand Down
1 change: 1 addition & 0 deletions src/export/digitalPlanning/mocks/planningPermission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const mockPlanningPermissionSession = {
slug: "apply-for-planning-permission",
team: {
slug: "lambeth",
referenceCode: "LBH",
},
},
created_at: "2023-08-31T17:33:46.22695+00:00",
Expand Down
1 change: 1 addition & 0 deletions src/export/digitalPlanning/mocks/priorApproval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const mockPriorApprovalSession = {
slug: "apply-for-prior-approval",
team: {
slug: "southwark",
referenceCode: "SWK",
},
},
created_at: "2023-09-02T13:36:24.49483+00:00",
Expand Down
37 changes: 27 additions & 10 deletions src/export/digitalPlanning/model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ import { mockPriorApprovalSession } from "./mocks/priorApproval";
import { DigitalPlanning } from "./model";

// `getPlanningConstraints` relies on an accurate teamSlug to be available, other vars can be be mocked
const mockMetadataForSession = (teamSlug: string): SessionMetadata => ({
id: "session123",
const mockMetadataForSession = (
teamSlug: string,
referenceCode: string,
): SessionMetadata => ({
id: "c06eebb7-6201-4bc0-9fe7-ec5d7a1c0797",
createdAt: "2023-01-01 00:00:00",
submittedAt: "2023-01-02 00:00:00",
flow: {
Expand All @@ -22,6 +25,7 @@ const mockMetadataForSession = (teamSlug: string): SessionMetadata => ({
team: {
name: teamSlug,
slug: teamSlug,
referenceCode: referenceCode,
},
},
});
Expand All @@ -33,21 +37,30 @@ const mockSessions = [
passport: new Passport({ data: { ...mockLDCPSession.passport } }),
breadcrumbs: mockLDCPSession.breadcrumbs as Breadcrumbs,
flow: mockPublishedLDCFlow,
metadata: mockMetadataForSession(mockLDCPSession.flow.team.slug),
metadata: mockMetadataForSession(
mockLDCPSession.flow.team.slug,
mockLDCPSession.flow.team.referenceCode,
),
},
{
name: "LDC - Existing",
passport: new Passport({ data: { ...mockLDCESession.passport } }),
breadcrumbs: mockLDCESession.breadcrumbs as Breadcrumbs,
flow: mockPublishedLDCFlow,
metadata: mockMetadataForSession(mockLDCESession.flow.team.slug),
metadata: mockMetadataForSession(
mockLDCESession.flow.team.slug,
mockLDCESession.flow.team.referenceCode,
),
},
{
name: "Prior Approval",
passport: new Passport({ data: { ...mockPriorApprovalSession.passport } }),
breadcrumbs: mockPriorApprovalSession.breadcrumbs as Breadcrumbs,
flow: mockPublishedPriorApprovalFlow,
metadata: mockMetadataForSession(mockPriorApprovalSession.flow.team.slug),
metadata: mockMetadataForSession(
mockPriorApprovalSession.flow.team.slug,
mockPriorApprovalSession.flow.team.referenceCode,
),
},
{
name: "Planning Permission",
Expand All @@ -58,25 +71,29 @@ const mockSessions = [
flow: mockPublishedPlanningPermissionFlow,
metadata: mockMetadataForSession(
mockPlanningPermissionSession.flow.team.slug,
mockPlanningPermissionSession.flow.team.referenceCode,
),
},
];

// We don't need to iterate over application types when testing invalid payloads
const mockParams = {
sessionId: "session123",
sessionId: "c06eebb7-6201-4bc0-9fe7-ec5d7a1c0797",
passport: new Passport({ data: { ...mockLDCPSession.passport } }),
breadcrumbs: mockLDCPSession.breadcrumbs,
flow: mockPublishedLDCFlow,
metadata: mockMetadataForSession(mockLDCPSession.flow.team.slug),
metadata: mockMetadataForSession(
mockLDCPSession.flow.team.slug,
mockLDCPSession.flow.team.referenceCode,
),
};

describe("DigitalPlanning", () => {
describe("getPayload", () => {
mockSessions.forEach((mock) => {
it(`should return valid payload (${mock.name})`, () => {
const instance = new DigitalPlanning({
sessionId: "session123",
sessionId: "c06eebb7-6201-4bc0-9fe7-ec5d7a1c0797",
passport: mock.passport,
breadcrumbs: mock.breadcrumbs,
flow: mock.flow,
Expand Down Expand Up @@ -126,7 +143,7 @@ describe("DigitalPlanning", () => {
test("incorrect string format", () => {
const instance = new DigitalPlanning(mockParams);

instance.payload.metadata.service.url =
instance.payload.metadata.schema =
"not a valid URL, but still a string";

expect(() => instance.getPayload()).toThrowError(
Expand All @@ -137,7 +154,7 @@ describe("DigitalPlanning", () => {
test("incorrect datetime format", () => {
const instance = new DigitalPlanning(mockParams);

instance.payload.metadata.session.submittedAt =
instance.payload.metadata.submittedAt =
"not a valid datetime, but still a string";

expect(() => instance.getPayload()).toThrowError(
Expand Down
78 changes: 43 additions & 35 deletions src/export/digitalPlanning/model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Ajv from "ajv";
import addFormats from "ajv-formats";
import capitalize from "lodash.capitalize";
import set from "lodash.set";

import { Passport } from "../../models";
Expand All @@ -25,7 +24,8 @@ import {
FileType,
GeoJSON,
LondonProperty,
PlanningConstraint,
PlanningDesignation,
PlanXMetadata,
ProjectType,
Proposal,
SiteContact,
Expand Down Expand Up @@ -92,7 +92,7 @@ export class DigitalPlanning {
},
proposal: this.getProposal(),
},
result: this.getResult(),
preAssessment: this.getResult(),
responses: this.getResponses(),
files: this.getFiles(),
metadata: this.getMetadata(),
Expand Down Expand Up @@ -126,6 +126,19 @@ export class DigitalPlanning {
)[0]?.properties["description"].const;
}

/**
* For a Planx passport value, find it's corresponding description in the JSON schema Definition for UnionTypes
*/
private findDescriptionFromValueUnionType(
definition: string,
value: string,
): string {
return jsonSchema["definitions"][definition]["anyOf"][0]["anyOf"].filter(
(types: Record<string, string>) =>
types.properties["value"].const === value,
)[0]?.properties["description"].const;
}

/**
* Cast a string to a boolean
*/
Expand Down Expand Up @@ -340,7 +353,7 @@ export class DigitalPlanning {
this.passport.data?.["property.type"]?.[0],
),
},
constraints: this.getPlanningConstraints(),
planning: this.getPlanningConstraints(),
// Only include the 'boundary' key in cases where we have digital data, not an uploaded location plan
...(this.passport.data?.["property.boundary.site"] && {
boundary: this.getBoundary(),
Expand Down Expand Up @@ -371,26 +384,26 @@ export class DigitalPlanning {
}
}

private getPlanningConstraints(): Payload["data"]["property"]["constraints"] {
const data: PlanningConstraint[] = [];
private getPlanningConstraints(): Payload["data"]["property"]["planning"] {
const teamSlug: string = this.metadata.flow.team.slug;
const constraints =
this.passport.generic<EnhancedGISResponse[]>("_constraints");
const constraints = this.passport.data
?._constraints as unknown as EnhancedGISResponse[];
const designations: PlanningDesignation[] = [];

constraints?.forEach((response: EnhancedGISResponse) => {
response.constraints &&
Object.entries(response.constraints)
.filter(([key, _constraint]) => !key.split(".").includes(teamSlug))
.map(([key, constraint]) => {
if (constraint.value) {
data.push({
// Intersecting constraints
designations.push({
value: key,
description: this.findDescriptionFromValue(
"PlanningConstraint",
description: this.findDescriptionFromValueUnionType(
"PlanningDesignation",
key,
),
category: constraint.category,
overlaps: constraint.value,
intersects: constraint.value,
entities:
constraint.data?.map(
(entity) =>
Expand All @@ -403,23 +416,24 @@ export class DigitalPlanning {
: `https://planinng.data.gov.uk/entity/${entity.id}`,
},
) || [],
} as PlanningConstraint);
} as PlanningDesignation);
} else {
data.push({
// Non-intersecting constraints
designations.push({
value: key,
description: this.findDescriptionFromValue(
"PlanningConstraint",
description: this.findDescriptionFromValueUnionType(
"PlanningDesignation",
key,
),
category: constraint.category,
overlaps: constraint.value,
} as PlanningConstraint);
intersects: constraint.value,
} as PlanningDesignation);
}
});
});

return {
planning: data,
sources: constraints?.map((constraint) => constraint.planxRequest) || [],
designations: designations,
};
}

Expand Down Expand Up @@ -496,7 +510,7 @@ export class DigitalPlanning {
}

// @todo getResult() should support flagsets beyond Planning Permission
private getResult(): Payload["result"] {
private getResult(): Payload["preAssessment"] {
// Planning Permission application types won't have a Planning Permission result right now
if (this.passport.data?.["application.type"]?.[0].startsWith("pp")) {
return [];
Expand All @@ -513,7 +527,7 @@ export class DigitalPlanning {
value: title,
description: this.findDescriptionFromValue("ResultFlag", title), // flag.description may be custom text
},
] as Payload["result"];
] as Payload["preAssessment"];
}
}

Expand Down Expand Up @@ -745,21 +759,15 @@ export class DigitalPlanning {

private getMetadata(): Payload["metadata"] {
return {
id: this.sessionId,
organisation: this.metadata.flow.team.referenceCode,
submittedAt: this.metadata.submittedAt,
source: "PlanX",
service: {
flowId: this.metadata.flow.id,
name: capitalize(this.metadata.flow.slug.replaceAll?.("-", " ")),
owner: this.metadata.flow.team.slug,
url: `https://www.editor.planx.uk/${this.metadata.flow.team.slug}/${this.metadata.flow.slug}/preview`,
},
session: {
source: "PlanX",
id: this.sessionId,
createdAt: this.metadata.createdAt,
submittedAt: this.metadata.submittedAt,
},
schema: {
url: `https://theopensystemslab.github.io/digital-planning-data-schemas/${jsonSchema["$id"]}/schema.json`,
},
};
schema: `https://theopensystemslab.github.io/digital-planning-data-schemas/${jsonSchema["$id"]}/schema.json`,
} as PlanXMetadata;
}
}
Loading
Loading