From 865da65e20e1aab1f8cce4d6de7fa6aaef0bc663 Mon Sep 17 00:00:00 2001 From: Matt Poole Date: Thu, 2 Apr 2026 10:56:15 +0100 Subject: [PATCH 1/5] plan hard code fallback json --- config/plan-fallback.json | 161 ++++++++++++++++++++++++++++ src/middleware/common.middleware.js | 25 +++++ 2 files changed, 186 insertions(+) create mode 100644 config/plan-fallback.json diff --git a/config/plan-fallback.json b/config/plan-fallback.json new file mode 100644 index 00000000..f7e5613f --- /dev/null +++ b/config/plan-fallback.json @@ -0,0 +1,161 @@ +{ + "specification": "plan", + "name": "plan", + "long-name": "local plan, minerals and waste plan, and supplementary plan", + "long-plural": "local plans, minerals and waste plans, and supplementary plans", + "plural": "plans", + "description": "headline information about local plans, minerals and waste plans, and supplementary plans", + "specification-status": "working-draft", + "specification-reason": "local-plans-2025", + "consideration": "development-plans-and-timetables", + "document-url": "https://digital-land.github.io/specification/specification/plan/", + "date-precision": "YYYY-MM-DD", + "start-date": "", + "end-date": "", + "entry-date": "2026-03-24", + "github-discussion": 26, + "datasets": [ + { + "dataset": "local-plan", + "fields": [ + { "field": "reference" }, + { + "field": "name", + "assertions": [ + { + "reference": "plan-A001", + "text": "match the title of the document at `document-url`." + } + ] + }, + { "field": "datasets" }, + { "field": "period-start-date" }, + { "field": "period-end-date" }, + { "field": "local-planning-authorities" }, + { "field": "documentation-url" }, + { + "field": "document-url", + "assertions": [ + { + "reference": "plan-A002", + "text": "link to the core plan document described by this data." + } + ] + }, + { "field": "required-housing" }, + { "field": "entry-date" }, + { "field": "notes" } + ] + }, + { + "dataset": "supplementary-plan", + "fields": [ + { "field": "reference" }, + { + "field": "name", + "assertions": [ + { + "reference": "plan-A001", + "text": "match the title of the document at `document-url`." + } + ] + }, + { "field": "datasets" }, + { "field": "period-start-date" }, + { "field": "period-end-date" }, + { "field": "local-planning-authorities" }, + { "field": "documentation-url" }, + { + "field": "document-url", + "assertions": [ + { + "reference": "plan-A002", + "text": "link to the core plan document described by this data." + } + ] + }, + { "field": "entry-date" }, + { "field": "notes" } + ] + }, + { + "dataset": "minerals-plan", + "fields": [ + { "field": "reference" }, + { + "field": "name", + "assertions": [ + { + "reference": "plan-A001", + "text": "match the title of the document at `document-url`." + } + ] + }, + { "field": "datasets" }, + { "field": "period-start-date" }, + { "field": "period-end-date" }, + { "field": "mineral-planning-authorities" }, + { "field": "documentation-url" }, + { + "field": "document-url", + "assertions": [ + { + "reference": "plan-A002", + "text": "link to the core plan document described by this data." + } + ] + }, + { "field": "document-count" }, + { "field": "entry-date" }, + { "field": "notes" } + ] + }, + { + "dataset": "waste-plan", + "fields": [ + { "field": "reference" }, + { + "field": "name", + "assertions": [ + { + "reference": "plan-A001", + "text": "match the title of the document at `document-url`." + } + ] + }, + { "field": "datasets" }, + { "field": "period-start-date" }, + { "field": "period-end-date" }, + { "field": "waste-planning-authorities" }, + { "field": "documentation-url" }, + { + "field": "document-url", + "assertions": [ + { + "reference": "plan-A002", + "text": "link to the core plan document described by this data." + } + ] + }, + { "field": "document-count" }, + { "field": "entry-date" }, + { "field": "notes" } + ] + }, + { + "dataset": "plan-timetable", + "fields": [ + { "field": "reference" }, + { "field": "plan" }, + { + "field": "plan-event", + "dataset": "plan-event" + }, + { "field": "event-date" }, + { "field": "actual-date" }, + { "field": "entry-date" }, + { "field": "notes" } + ] + } + ] +} diff --git a/src/middleware/common.middleware.js b/src/middleware/common.middleware.js index b25ac205..454a637f 100644 --- a/src/middleware/common.middleware.js +++ b/src/middleware/common.middleware.js @@ -15,6 +15,7 @@ import { errorTemplateContext, MiddlewareError } from '../utils/errors.js' import { dataRangeParams } from '../routes/schemas.js' import platformApi from '../services/platformApi.js' import config from '../../config/index.js' +import planFallback from '../../config/plan-fallback.json' /** * Middleware. Set `req.handlerName` to a string that will identify @@ -381,6 +382,28 @@ export const fetchDatasetFields = fetchMany({ result: 'datasetFields' }) +/** + * @name checkSpecificationFallback + * @function + * @description Middleware that overrides the specification with a local fallback for plan datasets + * that are not yet in a production-ready format in the specification table. When the fetched + * specification is for 'local-plan', it checks whether the current dataset exists in the + * plan-fallback.json config and, if so, replaces req.specification with the fallback data + * so that pullOutDatasetSpecification can extract the correct dataset-specific fields. + */ +export const checkSpecificationFallback = (req, res, next) => { + const { specification } = req + + if (specification && specification.specification === 'local-plan') { + const fallbackDataset = planFallback.datasets.find(d => d.dataset === req.dataset.dataset) + if (fallbackDataset) { + req.specification = { json: JSON.stringify(planFallback.datasets) } + } + } + + return next() +} + export const pullOutDatasetSpecification = (req, res, next) => { const { specification } = req let collectionSpecifications @@ -494,6 +517,8 @@ export const constructSpecificationTable = (req, res, next) => { */ export const processSpecificationMiddlewares = [ fetchSpecification, + // Certain Specification are not at production level format, so override here + checkSpecificationFallback, pullOutDatasetSpecification, // When specification exists, use field mappings from transform table onlyIf(req => req.specification, replaceUnderscoreInSpecification), From 70f0b04219a28af486cf0600980e928212c6a43e Mon Sep 17 00:00:00 2001 From: Matt Poole Date: Thu, 2 Apr 2026 11:01:36 +0100 Subject: [PATCH 2/5] type check for json --- src/middleware/common.middleware.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/common.middleware.js b/src/middleware/common.middleware.js index 454a637f..4dc70154 100644 --- a/src/middleware/common.middleware.js +++ b/src/middleware/common.middleware.js @@ -15,7 +15,7 @@ import { errorTemplateContext, MiddlewareError } from '../utils/errors.js' import { dataRangeParams } from '../routes/schemas.js' import platformApi from '../services/platformApi.js' import config from '../../config/index.js' -import planFallback from '../../config/plan-fallback.json' +import planFallback from '../../config/plan-fallback.json' with { type: 'json' } /** * Middleware. Set `req.handlerName` to a string that will identify From f2bb74bb88600005d5075c7e349c0aef502136d5 Mon Sep 17 00:00:00 2001 From: Matt Poole Date: Thu, 2 Apr 2026 11:05:26 +0100 Subject: [PATCH 3/5] edit how json loads --- src/middleware/common.middleware.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/middleware/common.middleware.js b/src/middleware/common.middleware.js index 4dc70154..23a7d8b3 100644 --- a/src/middleware/common.middleware.js +++ b/src/middleware/common.middleware.js @@ -15,8 +15,9 @@ import { errorTemplateContext, MiddlewareError } from '../utils/errors.js' import { dataRangeParams } from '../routes/schemas.js' import platformApi from '../services/platformApi.js' import config from '../../config/index.js' -import planFallback from '../../config/plan-fallback.json' with { type: 'json' } +import { readFileSync } from 'node:fs' +const planFallback = JSON.parse(readFileSync(new URL('../../config/plan-fallback.json', import.meta.url), 'utf8')) /** * Middleware. Set `req.handlerName` to a string that will identify * the function that threw the error. From af01a9f62c330184eb064d5e4e552468632b00fc Mon Sep 17 00:00:00 2001 From: Matt Poole Date: Thu, 2 Apr 2026 11:39:54 +0100 Subject: [PATCH 4/5] fallback plan dataset singular field --- config/plan-fallback.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/plan-fallback.json b/config/plan-fallback.json index f7e5613f..c7792c15 100644 --- a/config/plan-fallback.json +++ b/config/plan-fallback.json @@ -28,7 +28,7 @@ } ] }, - { "field": "datasets" }, + { "field": "dataset" }, { "field": "period-start-date" }, { "field": "period-end-date" }, { "field": "local-planning-authorities" }, @@ -60,7 +60,7 @@ } ] }, - { "field": "datasets" }, + { "field": "dataset" }, { "field": "period-start-date" }, { "field": "period-end-date" }, { "field": "local-planning-authorities" }, @@ -91,7 +91,7 @@ } ] }, - { "field": "datasets" }, + { "field": "dataset" }, { "field": "period-start-date" }, { "field": "period-end-date" }, { "field": "mineral-planning-authorities" }, @@ -123,7 +123,7 @@ } ] }, - { "field": "datasets" }, + { "field": "dataset" }, { "field": "period-start-date" }, { "field": "period-end-date" }, { "field": "waste-planning-authorities" }, From 66cd0f79849c825e6e2bfadfb71d546f11c9ffff Mon Sep 17 00:00:00 2001 From: Matt Poole Date: Tue, 7 Apr 2026 10:07:39 +0100 Subject: [PATCH 5/5] pre compute plan fallback --- src/middleware/common.middleware.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/middleware/common.middleware.js b/src/middleware/common.middleware.js index 23a7d8b3..3b83f94b 100644 --- a/src/middleware/common.middleware.js +++ b/src/middleware/common.middleware.js @@ -18,6 +18,7 @@ import config from '../../config/index.js' import { readFileSync } from 'node:fs' const planFallback = JSON.parse(readFileSync(new URL('../../config/plan-fallback.json', import.meta.url), 'utf8')) +const PLAN_FALLBACK_DATASETS_JSON = JSON.stringify(planFallback.datasets) /** * Middleware. Set `req.handlerName` to a string that will identify * the function that threw the error. @@ -398,7 +399,7 @@ export const checkSpecificationFallback = (req, res, next) => { if (specification && specification.specification === 'local-plan') { const fallbackDataset = planFallback.datasets.find(d => d.dataset === req.dataset.dataset) if (fallbackDataset) { - req.specification = { json: JSON.stringify(planFallback.datasets) } + req.specification = { json: PLAN_FALLBACK_DATASETS_JSON } } }