diff --git a/config/plan-fallback.json b/config/plan-fallback.json new file mode 100644 index 00000000..c7792c15 --- /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": "dataset" }, + { "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": "dataset" }, + { "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": "dataset" }, + { "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": "dataset" }, + { "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..3b83f94b 100644 --- a/src/middleware/common.middleware.js +++ b/src/middleware/common.middleware.js @@ -15,7 +15,10 @@ 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 { 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. @@ -381,6 +384,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: PLAN_FALLBACK_DATASETS_JSON } + } + } + + return next() +} + export const pullOutDatasetSpecification = (req, res, next) => { const { specification } = req let collectionSpecifications @@ -494,6 +519,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),