From 229101cb73837c814e4254aea56279f1b44543a8 Mon Sep 17 00:00:00 2001 From: Marjukka Niinioja Date: Thu, 26 Mar 2026 20:12:51 +0200 Subject: [PATCH 01/11] Add guided API journey flow docs page and app components --- src/components/app/ArtifactPanel.astro | 98 +++++++++++++++++++ src/components/app/JourneyIntake.astro | 81 +++++++++++++++ src/components/app/NextStationCard.astro | 61 ++++++++++++ src/components/app/StationChecklist.astro | 93 ++++++++++++++++++ .../docs/getting-started/journey-flow.mdx | 96 ++++++++++++++++++ 5 files changed, 429 insertions(+) create mode 100644 src/components/app/ArtifactPanel.astro create mode 100644 src/components/app/JourneyIntake.astro create mode 100644 src/components/app/NextStationCard.astro create mode 100644 src/components/app/StationChecklist.astro create mode 100644 src/content/docs/getting-started/journey-flow.mdx diff --git a/src/components/app/ArtifactPanel.astro b/src/components/app/ArtifactPanel.astro new file mode 100644 index 0000000..5f19178 --- /dev/null +++ b/src/components/app/ArtifactPanel.astro @@ -0,0 +1,98 @@ +--- +import CanvasCreator from '../CanvasCreator.astro'; +import MaterialIcon from '../MaterialIcon.astro'; + +interface Artifact { + name: string; + description: string; + canvasUrl: string; +} + +interface Props { + artifacts: Artifact[]; + previewCanvasId?: string; +} + +const { artifacts, previewCanvasId } = Astro.props as Props; +--- + +
+
+ +
+

Step 4

+

Required artifacts and canvas links

+
+
+ +
+ {artifacts.map((artifact) => ( +
+

{artifact.name}

+

{artifact.description}

+ Open canvas +
+ ))} +
+ + { + previewCanvasId && ( +
+

Live canvas preview

+ +
+ ) + } +
+ + diff --git a/src/components/app/JourneyIntake.astro b/src/components/app/JourneyIntake.astro new file mode 100644 index 0000000..a0fc2d9 --- /dev/null +++ b/src/components/app/JourneyIntake.astro @@ -0,0 +1,81 @@ +--- +import MaterialIcon from '../MaterialIcon.astro'; + +interface Props { + initiative: string; + context: string; + goals: string[]; + constraints: string[]; +} + +const { initiative, context, goals, constraints } = Astro.props as Props; +--- + +
+
+ +
+

Step 1

+

Initiative & context intake

+
+
+ +

Initiative: {initiative}

+

Context: {context}

+ +
+
+

Primary goals

+
    + {goals.map((goal) =>
  • {goal}
  • )} +
+
+
+

Known constraints

+
    + {constraints.map((constraint) =>
  • {constraint}
  • )} +
+
+
+
+ + diff --git a/src/components/app/NextStationCard.astro b/src/components/app/NextStationCard.astro new file mode 100644 index 0000000..80eb45d --- /dev/null +++ b/src/components/app/NextStationCard.astro @@ -0,0 +1,61 @@ +--- +import MaterialIcon from '../MaterialIcon.astro'; + +interface Props { + step: string; + station: string; + rationale: string; + linkLabel: string; + linkHref: string; +} + +const { step, station, rationale, linkLabel, linkHref } = Astro.props as Props; +--- + +
+
+

{step}

+

{station}

+
+ +

{rationale}

+ + + {linkLabel} + +
+ + diff --git a/src/components/app/StationChecklist.astro b/src/components/app/StationChecklist.astro new file mode 100644 index 0000000..4ac1d34 --- /dev/null +++ b/src/components/app/StationChecklist.astro @@ -0,0 +1,93 @@ +--- +import MaterialIcon from '../MaterialIcon.astro'; + +interface ChecklistItem { + label: string; + done: boolean; +} + +interface Props { + stationName: string; + criteria: ChecklistItem[]; +} + +const { stationName, criteria } = Astro.props as Props; +const completion = criteria.length + ? Math.round((criteria.filter((item) => item.done).length / criteria.length) * 100) + : 0; +--- + +
+
+ +
+

Step 3

+

{stationName} entry criteria checklist

+
+
+ +

Checklist completion: {completion}%

+ + +
+ + diff --git a/src/content/docs/getting-started/journey-flow.mdx b/src/content/docs/getting-started/journey-flow.mdx new file mode 100644 index 0000000..3f0b6ef --- /dev/null +++ b/src/content/docs/getting-started/journey-flow.mdx @@ -0,0 +1,96 @@ +--- +title: API Journey Flow +sidebar: + order: 2 +--- + +import JourneyIntake from '../../../components/app/JourneyIntake.astro'; +import StationChecklist from '../../../components/app/StationChecklist.astro'; +import ArtifactPanel from '../../../components/app/ArtifactPanel.astro'; +import NextStationCard from '../../../components/app/NextStationCard.astro'; + +Use this guided page to move from initiative intake to recommended next stations without leaving the method docs. + + + +## Step 2 — Recommended first station(s) + +
+ + +
+ + + + + +## Step 5 — Completion + next station recommendation + + + + From f26d76f1aa56eb100039439aa6b084ac0552d863 Mon Sep 17 00:00:00 2001 From: Marjukka Niinioja Date: Thu, 26 Mar 2026 20:19:03 +0200 Subject: [PATCH 02/11] Add Node version pin files for local tooling --- .node-version | 1 + .nvmrc | 1 + README.md | 8 +++++++- 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 .node-version create mode 100644 .nvmrc diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..1d9b783 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +22.12.0 diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..1d9b783 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22.12.0 diff --git a/README.md b/README.md index 2035499..6ed765c 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,12 @@ This repository contains the source for the APIOps Cycles documentation site bui - **Node.js 22** or newer - npm +If you use `nvm`, this repo now includes `.nvmrc` (and `.node-version`) pinned to `22.12.0`, so you can switch quickly with: + +```bash +nvm use +``` + Install dependencies once with: ```bash @@ -150,4 +156,4 @@ Please make sure the site builds (`npm run build`) before submitting a pull requ ## License -All content in this repository is provided under the CC BY-SA 4.0 license unless noted otherwise. \ No newline at end of file +All content in this repository is provided under the CC BY-SA 4.0 license unless noted otherwise. From f4b8ef48bf4d540341fbe32f9f4ca65a1d07a691 Mon Sep 17 00:00:00 2001 From: Marjukka Niinioja Date: Thu, 26 Mar 2026 20:19:29 +0200 Subject: [PATCH 03/11] Fix MDX parse error in journey flow layout --- src/content/docs/getting-started/journey-flow.mdx | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/content/docs/getting-started/journey-flow.mdx b/src/content/docs/getting-started/journey-flow.mdx index 3f0b6ef..10906ab 100644 --- a/src/content/docs/getting-started/journey-flow.mdx +++ b/src/content/docs/getting-started/journey-flow.mdx @@ -28,7 +28,7 @@ Use this guided page to move from initiative intake to recommended next stations ## Step 2 — Recommended first station(s) -
+
- - From ad597e1bc64574d0a847948fe1ffbc4f47ff3279 Mon Sep 17 00:00:00 2001 From: Marjukka Niinioja Date: Thu, 26 Mar 2026 20:54:02 +0200 Subject: [PATCH 04/11] Generate and render dynamic journey runtime data --- scripts/generate-method-pages.mjs | 40 + src/components/app/JourneyFlowPage.astro | 124 + .../docs/getting-started/journey-flow.mdx | 82 +- src/data/generated/journey-runtime.json | 2214 +++++++++++++++++ 4 files changed, 2381 insertions(+), 79 deletions(-) create mode 100644 src/components/app/JourneyFlowPage.astro create mode 100644 src/data/generated/journey-runtime.json diff --git a/scripts/generate-method-pages.mjs b/scripts/generate-method-pages.mjs index b6acae7..8890a91 100644 --- a/scripts/generate-method-pages.mjs +++ b/scripts/generate-method-pages.mjs @@ -13,6 +13,7 @@ const snippetDir = path.join(rootDir, 'node_modules/apiops-cycles-method-data/sr const defaultLocale = 'en'; const defaultLocaleDir = path.join(dataDir, defaultLocale); const docsDir = path.join(rootDir, 'src/content/docs'); +const generatedDataDir = path.join(rootDir, 'src/data/generated'); const cacheFile = path.join(__dirname, '.method-checksums.json'); const baseLabels = { @@ -562,6 +563,7 @@ async function generate() { requiredEntryChecksByStationId: stationCriteria, criteriaLabelsById: criteriaMap, resourcesById, + stationStateById, } = deriveStationRuntimeState(stationsData, stationCriteriaData, criteriaData, resourcesData); const resourceMap = {}; @@ -606,6 +608,44 @@ async function generate() { stationMap[station.id] = station; } + const journeyRuntime = { + stationIdsInOrder, + stationStateById, + criteriaLabelsById: criteriaMap, + stationMetaById: Object.fromEntries( + orderedStations.map((station) => [ + station.id, + { + id: station.id, + title: translate(station.title, baseLabels), + slug: station.slug, + stationType: stationsData['core-stations'].items.some((item) => item.id === station.id) + ? 'core' + : 'suburb', + }, + ]) + ), + resourcesById: Object.fromEntries( + Object.values(resourceMap).map((resource) => [ + resource.id, + { + id: resource.id, + title: translate(resource.title, baseLabels), + description: resource.description ? translate(resource.description, baseLabels) : '', + category: resource.category, + canvas: resource.canvas || null, + slug: resource.slug, + }, + ]) + ), + }; + + await ensureDir(generatedDataDir); + await fsPromises.writeFile( + path.join(generatedDataDir, 'journey-runtime.json'), + JSON.stringify(journeyRuntime, null, 2) + '\n' + ); + let regenerate = false; if ( cache['stations.json'] !== newCache['stations.json'] || diff --git a/src/components/app/JourneyFlowPage.astro b/src/components/app/JourneyFlowPage.astro new file mode 100644 index 0000000..3919c00 --- /dev/null +++ b/src/components/app/JourneyFlowPage.astro @@ -0,0 +1,124 @@ +--- +import JourneyIntake from './JourneyIntake.astro'; +import StationChecklist from './StationChecklist.astro'; +import ArtifactPanel from './ArtifactPanel.astro'; +import NextStationCard from './NextStationCard.astro'; +import journeyRuntime from '../../data/generated/journey-runtime.json'; + +const { stationIdsInOrder, stationStateById, stationMetaById, criteriaLabelsById, resourcesById } = journeyRuntime; + +const readyStationIds = stationIdsInOrder.filter((stationId) => stationStateById[stationId]?.status === 'ready'); +const recommendedStationIds = readyStationIds.length ? readyStationIds.slice(0, 2) : stationIdsInOrder.slice(0, 2); +const focusStationId = recommendedStationIds[0] || stationIdsInOrder[0]; +const focusStation = stationMetaById[focusStationId]; +const focusStationState = stationStateById[focusStationId] || { + requiredCriteriaIds: [], + missingCriteriaIds: [], + stepArtifactActions: [], +}; + +const criteria = (focusStationState.requiredCriteriaIds || []).map((criterionId) => ({ + label: criteriaLabelsById[criterionId] || criterionId, + done: !(focusStationState.missingCriteriaIds || []).includes(criterionId), +})); + +const artifacts = (focusStationState.stepArtifactActions || []) + .map((action) => { + const resource = resourcesById[action.resourceId]; + if (!resource) return null; + return { + name: resource.title, + description: resource.description || `Required ${resource.category} artifact.`, + canvasUrl: resource.canvas + ? `https://canvascreator.apiopscycles.com/?canvas=${resource.canvas}` + : `/${resource.slug}/`, + }; + }) + .filter(Boolean); + +const previewCanvasId = artifacts.length + ? resourcesById[(focusStationState.stepArtifactActions || [])[0]?.resourceId]?.canvas || undefined + : undefined; + +const focusIndex = stationIdsInOrder.indexOf(focusStationId); +const nextStationId = + stationIdsInOrder + .slice(focusIndex + 1) + .find((stationId) => ['ready', 'blocked'].includes(stationStateById[stationId]?.status || '')) || + stationIdsInOrder.find((stationId) => stationStateById[stationId]?.status !== 'completed'); +const nextStation = nextStationId ? stationMetaById[nextStationId] : undefined; +--- + + + +

Step 2 — Recommended first station(s)

+
+ {recommendedStationIds.map((stationId, index) => ( + + ))} +
+ + + + + +

Step 5 — Completion + next station recommendation

+{nextStation ? ( + +) : ( +

All stations are currently marked as completed in the generated runtime state.

+)} + + diff --git a/src/content/docs/getting-started/journey-flow.mdx b/src/content/docs/getting-started/journey-flow.mdx index 10906ab..18da1e2 100644 --- a/src/content/docs/getting-started/journey-flow.mdx +++ b/src/content/docs/getting-started/journey-flow.mdx @@ -4,84 +4,8 @@ sidebar: order: 2 --- -import JourneyIntake from '../../../components/app/JourneyIntake.astro'; -import StationChecklist from '../../../components/app/StationChecklist.astro'; -import ArtifactPanel from '../../../components/app/ArtifactPanel.astro'; -import NextStationCard from '../../../components/app/NextStationCard.astro'; +import JourneyFlowPage from '../../../components/app/JourneyFlowPage.astro'; -Use this guided page to move from initiative intake to recommended next stations without leaving the method docs. +Use this guided page to move from initiative intake to recommended next stations using generated method runtime data. - - -## Step 2 — Recommended first station(s) - -
- - -
- - - - - -## Step 5 — Completion + next station recommendation - - + diff --git a/src/data/generated/journey-runtime.json b/src/data/generated/journey-runtime.json new file mode 100644 index 0000000..ffaaf26 --- /dev/null +++ b/src/data/generated/journey-runtime.json @@ -0,0 +1,2214 @@ +{ + "stationIdsInOrder": [ + "api-product-strategy", + "api-consumer-experience", + "api-platform-architecture", + "api-design", + "api-delivery", + "api-audit", + "api-publishing", + "monitoring-and-improving", + "user-experience", + "market-insights", + "business-goals", + "competitive-analysis", + "ecosystem-vision", + "scalable-infrastructure", + "legal-and-compliance", + "security-and-privacy", + "design-standards", + "vendor-management", + "contract-design", + "development", + "ci-cd", + "test-automation", + "release-management", + "service-agreements", + "api-consumer-adoption", + "api-promotion", + "partner-integration", + "api-mindset", + "roles-and-responsibilities", + "upskilling", + "operating-guidelines", + "portfolio-management", + "budget-and-resource-management" + ], + "stationStateById": { + "api-product-strategy": { + "status": "blocked", + "requiredCriteriaIds": [ + "metrics-feedback-available", + "business-goals-defined", + "market-research-done", + "stakeholder-approval" + ], + "missingCriteriaIds": [ + "metrics-feedback-available", + "business-goals-defined", + "market-research-done", + "stakeholder-approval" + ], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "customer-journey-canvas", + "resourceCategory": "canvas", + "canvasId": "customerJourneyCanvas", + "artifactActions": [ + { + "type": "canvas-json", + "format": "json", + "canvasId": "customerJourneyCanvas" + }, + { + "type": "canvas-svg", + "format": "svg", + "canvasId": "customerJourneyCanvas" + }, + { + "type": "canvas-png", + "format": "png", + "canvasId": "customerJourneyCanvas" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "domain-canvas", + "resourceCategory": "canvas", + "canvasId": "domainCanvas", + "artifactActions": [ + { + "type": "canvas-json", + "format": "json", + "canvasId": "domainCanvas" + }, + { + "type": "canvas-svg", + "format": "svg", + "canvasId": "domainCanvas" + }, + { + "type": "canvas-png", + "format": "png", + "canvasId": "domainCanvas" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "api-value-proposition-canvas", + "resourceCategory": "canvas", + "canvasId": "apiValuePropositionCanvas", + "artifactActions": [ + { + "type": "canvas-json", + "format": "json", + "canvasId": "apiValuePropositionCanvas" + }, + { + "type": "canvas-svg", + "format": "svg", + "canvasId": "apiValuePropositionCanvas" + }, + { + "type": "canvas-png", + "format": "png", + "canvasId": "apiValuePropositionCanvas" + } + ] + }, + { + "stepIndex": 3, + "resourceId": "api-business-model-canvas", + "resourceCategory": "canvas", + "canvasId": "apiBusinessModelCanvas", + "artifactActions": [ + { + "type": "canvas-json", + "format": "json", + "canvasId": "apiBusinessModelCanvas" + }, + { + "type": "canvas-svg", + "format": "svg", + "canvasId": "apiBusinessModelCanvas" + }, + { + "type": "canvas-png", + "format": "png", + "canvasId": "apiBusinessModelCanvas" + } + ] + } + ] + }, + "api-consumer-experience": { + "status": "blocked", + "requiredCriteriaIds": [ + "api-opportunity-documented", + "api-reusability", + "hide-backend-discrepancies", + "value-prop-validated", + "consumer-segments-identified", + "api-roadmap-defined" + ], + "missingCriteriaIds": [ + "api-opportunity-documented", + "api-reusability", + "hide-backend-discrepancies", + "value-prop-validated", + "consumer-segments-identified", + "api-roadmap-defined" + ], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "api-value-proposition-canvas", + "resourceCategory": "canvas", + "canvasId": "apiValuePropositionCanvas", + "artifactActions": [ + { + "type": "canvas-json", + "format": "json", + "canvasId": "apiValuePropositionCanvas" + }, + { + "type": "canvas-svg", + "format": "svg", + "canvasId": "apiValuePropositionCanvas" + }, + { + "type": "canvas-png", + "format": "png", + "canvasId": "apiValuePropositionCanvas" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "customer-journey-canvas", + "resourceCategory": "canvas", + "canvasId": "customerJourneyCanvas", + "artifactActions": [ + { + "type": "canvas-json", + "format": "json", + "canvasId": "customerJourneyCanvas" + }, + { + "type": "canvas-svg", + "format": "svg", + "canvasId": "customerJourneyCanvas" + }, + { + "type": "canvas-png", + "format": "png", + "canvasId": "customerJourneyCanvas" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "api-onboarding-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "api-platform-architecture": { + "status": "blocked", + "requiredCriteriaIds": [ + "api-reusability", + "hide-backend-discrepancies", + "value-prop-validated", + "consumer-segments-identified", + "api-roadmap-defined" + ], + "missingCriteriaIds": [ + "api-reusability", + "hide-backend-discrepancies", + "value-prop-validated", + "consumer-segments-identified", + "api-roadmap-defined" + ], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "business-impact-canvas", + "resourceCategory": "canvas", + "canvasId": "businessImpactCanvas", + "artifactActions": [ + { + "type": "canvas-json", + "format": "json", + "canvasId": "businessImpactCanvas" + }, + { + "type": "canvas-svg", + "format": "svg", + "canvasId": "businessImpactCanvas" + }, + { + "type": "canvas-png", + "format": "png", + "canvasId": "businessImpactCanvas" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "location-canvas", + "resourceCategory": "canvas", + "canvasId": "locationsCanvas", + "artifactActions": [ + { + "type": "canvas-json", + "format": "json", + "canvasId": "locationsCanvas" + }, + { + "type": "canvas-svg", + "format": "svg", + "canvasId": "locationsCanvas" + }, + { + "type": "canvas-png", + "format": "png", + "canvasId": "locationsCanvas" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "capacity-canvas", + "resourceCategory": "canvas", + "canvasId": "capacityCanvas", + "artifactActions": [ + { + "type": "canvas-json", + "format": "json", + "canvasId": "capacityCanvas" + }, + { + "type": "canvas-svg", + "format": "svg", + "canvasId": "capacityCanvas" + }, + { + "type": "canvas-png", + "format": "png", + "canvasId": "capacityCanvas" + } + ] + }, + { + "stepIndex": 3, + "resourceId": "api-metrics-and-analytics", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "api-design": { + "status": "blocked", + "requiredCriteriaIds": [ + "architecture-patterns-validated", + "hide-backend-discrepancies", + "design-reflects-business-value", + "api-consistency" + ], + "missingCriteriaIds": [ + "architecture-patterns-validated", + "hide-backend-discrepancies", + "design-reflects-business-value", + "api-consistency" + ], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "domain-canvas", + "resourceCategory": "canvas", + "canvasId": "domainCanvas", + "artifactActions": [ + { + "type": "canvas-json", + "format": "json", + "canvasId": "domainCanvas" + }, + { + "type": "canvas-svg", + "format": "svg", + "canvasId": "domainCanvas" + }, + { + "type": "canvas-png", + "format": "png", + "canvasId": "domainCanvas" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "interaction-canvas", + "resourceCategory": "canvas", + "canvasId": "interactionCanvas", + "artifactActions": [ + { + "type": "canvas-json", + "format": "json", + "canvasId": "interactionCanvas" + }, + { + "type": "canvas-svg", + "format": "svg", + "canvasId": "interactionCanvas" + }, + { + "type": "canvas-png", + "format": "png", + "canvasId": "interactionCanvas" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "rest-canvas", + "resourceCategory": "canvas", + "canvasId": "restCanvas", + "artifactActions": [ + { + "type": "canvas-json", + "format": "json", + "canvasId": "restCanvas" + }, + { + "type": "canvas-svg", + "format": "svg", + "canvasId": "restCanvas" + }, + { + "type": "canvas-png", + "format": "png", + "canvasId": "restCanvas" + } + ] + }, + { + "stepIndex": 3, + "resourceId": "event-canvas", + "resourceCategory": "canvas", + "canvasId": "eventCanvas", + "artifactActions": [ + { + "type": "canvas-json", + "format": "json", + "canvasId": "eventCanvas" + }, + { + "type": "canvas-svg", + "format": "svg", + "canvasId": "eventCanvas" + }, + { + "type": "canvas-png", + "format": "png", + "canvasId": "eventCanvas" + } + ] + }, + { + "stepIndex": 4, + "resourceId": "graphql-canvas", + "resourceCategory": "canvas", + "canvasId": "graphqlCanvas", + "artifactActions": [ + { + "type": "canvas-json", + "format": "json", + "canvasId": "graphqlCanvas" + }, + { + "type": "canvas-svg", + "format": "svg", + "canvasId": "graphqlCanvas" + }, + { + "type": "canvas-png", + "format": "png", + "canvasId": "graphqlCanvas" + } + ] + }, + { + "stepIndex": 5, + "resourceId": "api-design-principles", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 6, + "resourceId": "contract-first-design", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "api-delivery": { + "status": "blocked", + "requiredCriteriaIds": [ + "architecture-patterns-validated", + "hide-backend-discrepancies", + "design-reflects-business-value", + "api-consistency" + ], + "missingCriteriaIds": [ + "architecture-patterns-validated", + "hide-backend-discrepancies", + "design-reflects-business-value", + "api-consistency" + ], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "api-development-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "api-development-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "api-testing-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 3, + "resourceId": "apiops-CI-CD-for-apis", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 4, + "resourceId": "api-security-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "api-audit": { + "status": "blocked", + "requiredCriteriaIds": [ + "architecture-patterns-validated", + "design-reflects-business-value", + "api-description-available", + "api-consistency", + "api-contract-tested" + ], + "missingCriteriaIds": [ + "architecture-patterns-validated", + "design-reflects-business-value", + "api-description-available", + "api-consistency", + "api-contract-tested" + ], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 1, + "resourceId": "api-compliance-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "api-publishing": { + "status": "blocked", + "requiredCriteriaIds": [ + "audit-passed", + "audit-reports-shared", + "api-ready-for-publishing", + "api-documentation-ready" + ], + "missingCriteriaIds": [ + "audit-passed", + "audit-reports-shared", + "api-ready-for-publishing", + "api-documentation-ready" + ], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 1, + "resourceId": "api-onboarding-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "monitoring-and-improving": { + "status": "blocked", + "requiredCriteriaIds": [ + "api-documentation-ready", + "consumer-support-ready", + "legal-compliance-clear" + ], + "missingCriteriaIds": [ + "api-documentation-ready", + "consumer-support-ready", + "legal-compliance-clear" + ], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "api-metrics-and-analytics", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "api-community-engagement-strategies", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "user-experience": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "customer-journey-canvas", + "resourceCategory": "canvas", + "canvasId": "customerJourneyCanvas", + "artifactActions": [ + { + "type": "canvas-json", + "format": "json", + "canvasId": "customerJourneyCanvas" + }, + { + "type": "canvas-svg", + "format": "svg", + "canvasId": "customerJourneyCanvas" + }, + { + "type": "canvas-png", + "format": "png", + "canvasId": "customerJourneyCanvas" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "customer-journey-canvas", + "resourceCategory": "canvas", + "canvasId": "customerJourneyCanvas", + "artifactActions": [ + { + "type": "canvas-json", + "format": "json", + "canvasId": "customerJourneyCanvas" + }, + { + "type": "canvas-svg", + "format": "svg", + "canvasId": "customerJourneyCanvas" + }, + { + "type": "canvas-png", + "format": "png", + "canvasId": "customerJourneyCanvas" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "customer-journey-canvas", + "resourceCategory": "canvas", + "canvasId": "customerJourneyCanvas", + "artifactActions": [ + { + "type": "canvas-json", + "format": "json", + "canvasId": "customerJourneyCanvas" + }, + { + "type": "canvas-svg", + "format": "svg", + "canvasId": "customerJourneyCanvas" + }, + { + "type": "canvas-png", + "format": "png", + "canvasId": "customerJourneyCanvas" + } + ] + } + ] + }, + "market-insights": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "ecosystem-vision-template", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "competitor-analysis-template", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "industry-standards-and-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "business-goals": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "api-business-model-canvas", + "resourceCategory": "canvas", + "canvasId": "apiBusinessModelCanvas", + "artifactActions": [ + { + "type": "canvas-json", + "format": "json", + "canvasId": "apiBusinessModelCanvas" + }, + { + "type": "canvas-svg", + "format": "svg", + "canvasId": "apiBusinessModelCanvas" + }, + { + "type": "canvas-png", + "format": "png", + "canvasId": "apiBusinessModelCanvas" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "api-metrics-and-analytics", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "stakeholder-engagement-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "competitive-analysis": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "competitor-analysis-template", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "ecosystem-vision-template", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "ecosystem-vision-template", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "ecosystem-vision": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "ecosystem-vision-template", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "ecosystem-vision-template", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "ecosystem-vision-template", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "scalable-infrastructure": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "scalable-infrastructure-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "capacity-canvas", + "resourceCategory": "canvas", + "canvasId": "capacityCanvas", + "artifactActions": [ + { + "type": "canvas-json", + "format": "json", + "canvasId": "capacityCanvas" + }, + { + "type": "canvas-svg", + "format": "svg", + "canvasId": "capacityCanvas" + }, + { + "type": "canvas-png", + "format": "png", + "canvasId": "capacityCanvas" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "scalable-infrastructure-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "legal-and-compliance": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "api-compliance-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "api-compliance-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "api-compliance-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "security-and-privacy": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "api-security-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "data-privacy-guidelines", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "design-standards": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "api-design-principles", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "vendor-management": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "vendor-management-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "vendor-management-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "vendor-management-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "contract-design": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "contract-first-design", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "contract-first-design", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "development": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "api-development-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "contract-first-design", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "api-development-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "ci-cd": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "apiops-CI-CD-for-apis", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "api-testing-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "api-security-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "test-automation": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "api-testing-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "api-testing-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "apiops-CI-CD-for-apis", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "release-management": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "api-versioning-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "apiops-CI-CD-for-apis", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "service-agreements": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "service-agreement-template", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "service-agreement-template", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "service-agreement-template", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "api-consumer-adoption": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "api-onboarding-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "api-community-engagement-strategies", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "api-community-engagement-strategies", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "api-promotion": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "api-community-engagement-strategies", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "api-community-engagement-strategies", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "api-community-engagement-strategies", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "partner-integration": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "ecosystem-vision-template", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "partner-integration-guidelines", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "partner-integration-guidelines", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "api-mindset": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "ecosystem-vision-template", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "cross-functional-collaboration-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "api-training-programs", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "roles-and-responsibilities": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "api-team-structure-guidelines", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "role-communication-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "role-communication-best-practices", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "upskilling": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "api-training-programs", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "api-training-programs", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "api-training-programs", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "operating-guidelines": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 2, + "resourceId": "api-metrics-and-analytics", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "portfolio-management": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "api-portfolio-management-guidelines", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "api-portfolio-management-guidelines", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "api-portfolio-management-guidelines", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + }, + "budget-and-resource-management": { + "status": "ready", + "requiredCriteriaIds": [], + "missingCriteriaIds": [], + "completedCriteriaIds": [], + "isMarkedDone": false, + "hasArtifacts": false, + "artifacts": [], + "stepArtifactActions": [ + { + "stepIndex": 0, + "resourceId": "api-portfolio-management-guidelines", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 1, + "resourceId": "api-portfolio-management-guidelines", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + }, + { + "stepIndex": 2, + "resourceId": "api-portfolio-management-guidelines", + "resourceCategory": "guideline", + "artifactActions": [ + { + "type": "guidance-read-and-acknowledge" + } + ] + } + ] + } + }, + "criteriaLabelsById": { + "metrics-feedback-available": "Insights from metrics and feedback are available to optimize existing APIs.", + "business-goals-defined": "Business goals are defined.", + "market-research-done": "Market research identifies API opportunities.", + "stakeholder-approval": "Stakeholders approve strategy discussions.", + "api-opportunity-documented": "Individual API opportunities are identified and documented.", + "api-reusability": "The API meets a clear business need and is reusable for multiple API consumers.", + "hide-backend-discrepancies": "The goal of the API is to hide backend data models and discrepancies.", + "value-prop-validated": "The value proposition validates with key stakeholders.", + "consumer-segments-identified": "API consumer segments (internal and external) are identified.", + "api-roadmap-defined": "High-level roadmaps for API development are established.", + "architecture-patterns-validated": "The API architecture uses patterns that promote reusability and integration, and is validated with stakeholders.", + "design-reflects-business-value": "The API's design and endpoints have a clear connection to their business value and features.", + "api-consistency": "API has a consistent design with our other API products.", + "api-contract-tested": "The API contract is tested and meets functional and non-functional requirements.", + "api-description-available": "The API and its endpoints have descriptions that explain their business value and features.", + "audit-passed": "The API passes compliance, security, and audit checks.", + "audit-reports-shared": "Audit reports are shared with stakeholders.", + "api-ready-for-publishing": "The API is ready to be published to the appropriate gateways and environments to support reusability for multiple API consumers.", + "api-documentation-ready": "API documentation is complete and ready for publishing.", + "consumer-support-ready": "API registration, support, and communication processes are in place.", + "legal-compliance-clear": "Legal and compliance requirements are precise enough for publishing." + }, + "stationMetaById": { + "api-product-strategy": { + "id": "api-product-strategy", + "title": "API Product Strategy - Turn APIs Into Strategic Products", + "slug": "method/api-product-strategy", + "stationType": "core" + }, + "api-consumer-experience": { + "id": "api-consumer-experience", + "title": "API Consumer Experience - Design for Your API’s Real Users", + "slug": "method/api-consumer-experience", + "stationType": "core" + }, + "api-platform-architecture": { + "id": "api-platform-architecture", + "title": "API Platform Architecture - Architect APIs for Scalability and Reuse", + "slug": "method/api-platform-architecture", + "stationType": "core" + }, + "api-design": { + "id": "api-design", + "title": "API Design - Design APIs That Deliver Value", + "slug": "method/api-design", + "stationType": "core" + }, + "api-delivery": { + "id": "api-delivery", + "title": "API Delivery - Deliver Secure and Reliable APIs", + "slug": "method/api-delivery", + "stationType": "core" + }, + "api-audit": { + "id": "api-audit", + "title": "API Audit - Audit APIs for Compliance and Quality", + "slug": "method/api-audit", + "stationType": "core" + }, + "api-publishing": { + "id": "api-publishing", + "title": "API Publishing - Publish APIs with Confidence", + "slug": "method/api-publishing", + "stationType": "core" + }, + "monitoring-and-improving": { + "id": "monitoring-and-improving", + "title": "Monitoring and Improving - for API Value", + "slug": "method/monitoring-and-improving", + "stationType": "core" + }, + "user-experience": { + "id": "user-experience", + "title": "User Experience - Design APIs with the User in Mind", + "slug": "method/user-experience", + "stationType": "suburb" + }, + "market-insights": { + "id": "market-insights", + "title": "Market Insights - Understand the API Landscape", + "slug": "method/market-insights", + "stationType": "suburb" + }, + "business-goals": { + "id": "business-goals", + "title": "Business Goals - Align APIs with Business Objectives", + "slug": "method/business-goals", + "stationType": "suburb" + }, + "competitive-analysis": { + "id": "competitive-analysis", + "title": "Competitive Analysis - Stay Ahead in the API Market", + "slug": "method/competitive-analysis", + "stationType": "suburb" + }, + "ecosystem-vision": { + "id": "ecosystem-vision", + "title": "Ecosystem Vision - Build APIs for a Thriving Ecosystem", + "slug": "method/ecosystem-vision", + "stationType": "suburb" + }, + "scalable-infrastructure": { + "id": "scalable-infrastructure", + "title": "Scalable Infrastructure - Build APIs on a Solid Foundation", + "slug": "method/scalable-infrastructure", + "stationType": "suburb" + }, + "legal-and-compliance": { + "id": "legal-and-compliance", + "title": "Legal and Compliance - Ensure APIs Meet Regulatory Standards", + "slug": "method/legal-and-compliance", + "stationType": "suburb" + }, + "security-and-privacy": { + "id": "security-and-privacy", + "title": "Security and Privacy - Protect Your APIs and Users", + "slug": "method/security-and-privacy", + "stationType": "suburb" + }, + "design-standards": { + "id": "design-standards", + "title": "Design Standards - Ensure Consistent and High-Quality API Design", + "slug": "method/design-standards", + "stationType": "suburb" + }, + "vendor-management": { + "id": "vendor-management", + "title": "Vendor Management - Manage Third-Party API Integrations", + "slug": "method/vendor-management", + "stationType": "suburb" + }, + "contract-design": { + "id": "contract-design", + "title": "Contract Design - Define Clear API Contracts", + "slug": "method/contract-design", + "stationType": "suburb" + }, + "development": { + "id": "development", + "title": "Development - Build APIs with Best Practices", + "slug": "method/development", + "stationType": "suburb" + }, + "ci-cd": { + "id": "ci-cd", + "title": "CI/CD - Automate API Delivery", + "slug": "method/ci-cd", + "stationType": "suburb" + }, + "test-automation": { + "id": "test-automation", + "title": "Test Automation - Ensure API Quality", + "slug": "method/test-automation", + "stationType": "suburb" + }, + "release-management": { + "id": "release-management", + "title": "Release Management - Manage API Releases Effectively", + "slug": "method/release-management", + "stationType": "suburb" + }, + "service-agreements": { + "id": "service-agreements", + "title": "Service Agreements - Define API Service Levels", + "slug": "method/service-agreements", + "stationType": "suburb" + }, + "api-consumer-adoption": { + "id": "api-consumer-adoption", + "title": "API Consumer Adoption - Drive API Usage", + "slug": "method/api-consumer-adoption", + "stationType": "suburb" + }, + "api-promotion": { + "id": "api-promotion", + "title": "API Promotion - Increase API Visibility and Usage", + "slug": "method/api-promotion", + "stationType": "suburb" + }, + "partner-integration": { + "id": "partner-integration", + "title": "Partner Integration - Collaborate with Partners", + "slug": "method/partner-integration", + "stationType": "suburb" + }, + "api-mindset": { + "id": "api-mindset", + "title": "API Mindset - Foster an API-First Culture", + "slug": "method/api-mindset", + "stationType": "suburb" + }, + "roles-and-responsibilities": { + "id": "roles-and-responsibilities", + "title": "Roles and Responsibilities - Define API Team Structures", + "slug": "method/roles-and-responsibilities", + "stationType": "suburb" + }, + "upskilling": { + "id": "upskilling", + "title": "Upskilling - Enhance API Skills and Knowledge", + "slug": "method/upskilling", + "stationType": "suburb" + }, + "operating-guidelines": { + "id": "operating-guidelines", + "title": "Operating Guidelines - Establish API Governance", + "slug": "method/operating-guidelines", + "stationType": "suburb" + }, + "portfolio-management": { + "id": "portfolio-management", + "title": "Portfolio Management - Manage API Portfolio Effectively", + "slug": "method/portfolio-management", + "stationType": "suburb" + }, + "budget-and-resource-management": { + "id": "budget-and-resource-management", + "title": "Budget and Resource Management - Optimize API Investments", + "slug": "method/budget-and-resource-management", + "stationType": "suburb" + } + }, + "resourcesById": { + "api-community-engagement-strategies": { + "id": "api-community-engagement-strategies", + "title": "API Community Engagement Strategies", + "description": "A playbook for fostering API adoption by cultivating communities through content, support channels, feedback loops, and social engagement strategies.", + "category": "guideline", + "canvas": null, + "slug": "resources/api-community-engagement-strategies" + }, + "api-compliance-best-practices": { + "id": "api-compliance-best-practices", + "title": "API Compliance Best Practices", + "description": "Ensure APIs meet legal, regulatory, and internal compliance through documentation, controls, and automated validations.", + "category": "guideline", + "canvas": null, + "slug": "resources/api-compliance-best-practices" + }, + "api-development-best-practices": { + "id": "api-development-best-practices", + "title": "API Development Best Practices", + "description": "Implementation guidance for turning a validated API contract into a consistent, maintainable API codebase using standard libraries, reusable patterns, and aligned development workflows.", + "category": "guideline", + "canvas": null, + "slug": "resources/api-development-best-practices" + }, + "api-metrics-and-analytics": { + "id": "api-metrics-and-analytics", + "title": "API Metrics And Analytics", + "description": "A resource for defining, collecting, and analyzing API performance and usage data to align technical KPIs with business outcomes.", + "category": "guideline", + "canvas": null, + "slug": "resources/api-metrics-and-analytics" + }, + "api-onboarding-best-practices": { + "id": "api-onboarding-best-practices", + "title": "API Onboarding Best Practices", + "description": "Best practices to streamline API consumer onboarding journeys with step-by-step registration, discovery, and first-call guidance.", + "category": "guideline", + "canvas": null, + "slug": "resources/api-onboarding-best-practices" + }, + "api-portfolio-management-guidelines": { + "id": "api-portfolio-management-guidelines", + "title": "API Portfolio Management Guidelines", + "description": "A guide to strategically manage an organization's API suite—prioritizing APIs, allocating resources, and monitoring performance across lifecycles.", + "category": "guideline", + "canvas": null, + "slug": "resources/api-portfolio-management-guidelines" + }, + "api-security-best-practices": { + "id": "api-security-best-practices", + "title": "API Security Best Practices", + "description": "A set of actionable controls for securing APIs, including authentication, authorization, encryption, rate-limiting, and pipeline-level compliance checks.", + "category": "guideline", + "canvas": null, + "slug": "resources/api-security-best-practices" + }, + "api-team-structure-guidelines": { + "id": "api-team-structure-guidelines", + "title": "API Team Structure Guidelines", + "description": "Organizational guidance for defining roles and responsibilities within API teams to ensure clarity, collaboration, and accountability.", + "category": "guideline", + "canvas": null, + "slug": "resources/api-team-structure-guidelines" + }, + "api-testing-best-practices": { + "id": "api-testing-best-practices", + "title": "API Testing Best Practices", + "description": "Guidelines for implementing automated functional, performance, and security testing throughout the API lifecycle.", + "category": "guideline", + "canvas": null, + "slug": "resources/api-testing-best-practices" + }, + "api-training-programs": { + "id": "api-training-programs", + "title": "API Training Programs", + "description": "A roadmap for upskilling teams with structured learning paths in API design, governance, performance, and security.", + "category": "guideline", + "canvas": null, + "slug": "resources/api-training-programs" + }, + "api-versioning-best-practices": { + "id": "api-versioning-best-practices", + "title": "API Versioning Best Practices", + "description": "Strategies for introducing, maintaining, and retiring API versions while preserving backward compatibility and consumer trust.", + "category": "guideline", + "canvas": null, + "slug": "resources/api-versioning-best-practices" + }, + "apiops-CI-CD-for-apis": { + "id": "apiops-CI-CD-for-apis", + "title": "APIOps CI/CD For APIs", + "description": "Deployment guidance that integrates API lifecycle tasks—design, testing, governance—into continuous integration and delivery pipelines.", + "category": "guideline", + "canvas": null, + "slug": "resources/apiops-CI-CD-for-apis" + }, + "competitor-analysis-template": { + "id": "competitor-analysis-template", + "title": "Competitor Analysis Template", + "description": "A structured worksheet to benchmark your API offerings against competitors by mapping strengths, weaknesses, and value differentiators.", + "category": "guideline", + "canvas": null, + "slug": "resources/competitor-analysis-template" + }, + "contract-first-design": { + "id": "contract-first-design", + "title": "Contract First Design", + "description": "A guideline advocating for API-first approaches using formal contracts (e.g., OpenAPI) to align stakeholders before development.", + "category": "guideline", + "canvas": null, + "slug": "resources/contract-first-design" + }, + "cross-functional-collaboration-best-practices": { + "id": "cross-functional-collaboration-best-practices", + "title": "Cross Functional Collaboration Best Practices", + "description": "Practices to facilitate communication and alignment between business and tech teams when planning or delivering APIs.", + "category": "guideline", + "canvas": null, + "slug": "resources/cross-functional-collaboration-best-practices" + }, + "data-privacy-guidelines": { + "id": "data-privacy-guidelines", + "title": "Data Privacy Guidelines", + "description": "Design considerations to ensure APIs meet data protection regulations like GDPR through anonymization and access controls.", + "category": "guideline", + "canvas": null, + "slug": "resources/data-privacy-guidelines" + }, + "domain-canvas": { + "id": "domain-canvas", + "title": "Domain Canvas", + "description": "A modeling tool to define and communicate the key entities and relationships in your domain, ensuring semantic consistency across APIs.", + "category": "canvas", + "canvas": "domainCanvas", + "slug": "resources/domain-canvas" + }, + "ecosystem-vision-template": { + "id": "ecosystem-vision-template", + "title": "Ecosystem Vision Template", + "description": "A strategic planning tool to define the API ecosystem, including target partners, value chains, and integration opportunities.", + "category": "guideline", + "canvas": null, + "slug": "resources/ecosystem-vision-template" + }, + "industry-standards-and-best-practices": { + "id": "industry-standards-and-best-practices", + "title": "Industry Standards And Best Practices", + "description": "A reference resource for aligning API design and operation with widely recognized industry frameworks and specifications.", + "category": "guideline", + "canvas": null, + "slug": "resources/industry-standards-and-best-practices" + }, + "partner-integration-guidelines": { + "id": "partner-integration-guidelines", + "title": "Partner Integration Guidelines", + "description": "Integration checklists and communication patterns to manage technical and legal aspects of third-party API relationships.", + "category": "guideline", + "canvas": null, + "slug": "resources/partner-integration-guidelines" + }, + "role-communication-best-practices": { + "id": "role-communication-best-practices", + "title": "Role Communication Best Practices", + "description": "Tools to define and document who is responsible for what within API initiatives, ensuring handoffs and accountability are clear.", + "category": "guideline", + "canvas": null, + "slug": "resources/role-communication-best-practices" + }, + "scalable-infrastructure-best-practices": { + "id": "scalable-infrastructure-best-practices", + "title": "Scalable Infrastructure Best Practices", + "description": "Architectural guidance to ensure APIs are deployed on infrastructure that can elastically handle usage spikes and growth.", + "category": "guideline", + "canvas": null, + "slug": "resources/scalable-infrastructure-best-practices" + }, + "service-agreement-template": { + "id": "service-agreement-template", + "title": "Service Agreement Template", + "description": "A customizable agreement format that defines expectations, SLAs, responsibilities, and access terms for API consumption.", + "category": "guideline", + "canvas": null, + "slug": "resources/service-agreement-template" + }, + "stakeholder-engagement-best-practices": { + "id": "stakeholder-engagement-best-practices", + "title": "Stakeholder Engagement Best Practices", + "description": "Engagement tactics for aligning internal and external stakeholders around shared API goals, value, and governance.", + "category": "guideline", + "canvas": null, + "slug": "resources/stakeholder-engagement-best-practices" + }, + "test-automation-frameworks": { + "id": "test-automation-frameworks", + "title": "Test Automation Frameworks", + "description": "Recommended tools and patterns for automating API contract, regression, and integration testing across environments.", + "category": "guideline", + "canvas": null, + "slug": "resources/test-automation-frameworks" + }, + "vendor-management-best-practices": { + "id": "vendor-management-best-practices", + "title": "Vendor Management Best Practices", + "description": "Framework for evaluating and managing external API vendors and third-party integrations based on risk, performance, and compliance.", + "category": "guideline", + "canvas": null, + "slug": "resources/vendor-management-best-practices" + }, + "api-audit-checklist": { + "id": "api-audit-checklist", + "title": "API Audit Checklist", + "description": "A comprehensive checklist to verify API readiness before publishing, covering design, documentation, security, and policy compliance.", + "category": "checklist", + "canvas": null, + "slug": "resources/api-audit-checklist" + }, + "api-business-model-canvas": { + "id": "api-business-model-canvas", + "title": "API Business Model Canvas", + "description": "Strategically assess API business viability by mapping value propositions, consumer segments, and key resources.", + "category": "canvas", + "canvas": "apiBusinessModelCanvas", + "slug": "resources/api-business-model-canvas" + }, + "api-design-principles": { + "id": "api-design-principles", + "title": "API Design Principles", + "description": "A concise guide to API usability, discoverability, and consistency grounded in proven design philosophies and user needs.", + "category": "guideline", + "canvas": null, + "slug": "resources/api-design-principles" + }, + "api-value-proposition-canvas": { + "id": "api-value-proposition-canvas", + "title": "API Value Proposition Canvas", + "description": "Align API features with user needs by mapping tasks, pains, and gains to API products.", + "category": "canvas", + "canvas": "apiValuePropositionCanvas", + "slug": "resources/api-value-proposition-canvas" + }, + "business-impact-canvas": { + "id": "business-impact-canvas", + "title": "Business Impact Canvas", + "description": "Design a scalable and secure API platform architecture that meets business and technical requirements.", + "category": "canvas", + "canvas": "businessImpactCanvas", + "slug": "resources/business-impact-canvas" + }, + "capacity-canvas": { + "id": "capacity-canvas", + "title": "Capacity Canvas", + "description": "Plan API capacity to meet current and future business demands, ensuring scalability and performance.", + "category": "canvas", + "canvas": "capacityCanvas", + "slug": "resources/capacity-canvas" + }, + "customer-journey-canvas": { + "id": "customer-journey-canvas", + "title": "Customer Journey Canvas", + "description": "Map customer journeys to identify needs and pain points, enhancing API design and user experience.", + "category": "canvas", + "canvas": "customerJourneyCanvas", + "slug": "resources/customer-journey-canvas" + }, + "event-canvas": { + "id": "event-canvas", + "title": "Event Canvas", + "description": "Design event-driven APIs by defining events, triggers, and processing logic.", + "category": "canvas", + "canvas": "eventCanvas", + "slug": "resources/event-canvas" + }, + "graphql-canvas": { + "id": "graphql-canvas", + "title": "GraphQL Canvas", + "description": "Design GraphQL APIs by defining types, queries, mutations, and subscriptions.", + "category": "canvas", + "canvas": "graphqlCanvas", + "slug": "resources/graphql-canvas" + }, + "interaction-canvas": { + "id": "interaction-canvas", + "title": "Interaction Canvas", + "description": "Define API interactions, workflows, and expected responses to ensure a consistent API consumer experience.", + "category": "canvas", + "canvas": "interactionCanvas", + "slug": "resources/interaction-canvas" + }, + "location-canvas": { + "id": "location-canvas", + "title": "Location Canvas", + "description": "Map API provider and consumer locations to ensure compliance and performance across regions.", + "category": "canvas", + "canvas": "locationsCanvas", + "slug": "resources/location-canvas" + }, + "rest-canvas": { + "id": "rest-canvas", + "title": "REST Canvas", + "description": "Design APIs using RESTful principles, defining resources, verbs, and example requests and responses.", + "category": "canvas", + "canvas": "restCanvas", + "slug": "resources/rest-canvas" + } + } +} From 394b9f115261c59e4ed8739cacb7cec95afe2df9 Mon Sep 17 00:00:00 2001 From: Marjukka Niinioja Date: Fri, 27 Mar 2026 05:16:44 +0200 Subject: [PATCH 05/11] Add intent-based station scoring for journey recommendations --- src/components/app/JourneyFlowPage.astro | 44 +++++++-- .../docs/getting-started/journey-flow.mdx | 1 + src/data/journey-intents.ts | 94 +++++++++++++++++++ 3 files changed, 131 insertions(+), 8 deletions(-) create mode 100644 src/data/journey-intents.ts diff --git a/src/components/app/JourneyFlowPage.astro b/src/components/app/JourneyFlowPage.astro index 3919c00..60a1e41 100644 --- a/src/components/app/JourneyFlowPage.astro +++ b/src/components/app/JourneyFlowPage.astro @@ -4,11 +4,35 @@ import StationChecklist from './StationChecklist.astro'; import ArtifactPanel from './ArtifactPanel.astro'; import NextStationCard from './NextStationCard.astro'; import journeyRuntime from '../../data/generated/journey-runtime.json'; +import { + normalizeJourneyIntentTags, + rankStationsForJourneyIntent, + type JourneyIntentTag, +} from '../../data/journey-intents'; +interface Props { + selectedIntents?: JourneyIntentTag[]; +} + +const { selectedIntents: selectedIntentsFromProps = [] } = Astro.props as Props; const { stationIdsInOrder, stationStateById, stationMetaById, criteriaLabelsById, resourcesById } = journeyRuntime; -const readyStationIds = stationIdsInOrder.filter((stationId) => stationStateById[stationId]?.status === 'ready'); -const recommendedStationIds = readyStationIds.length ? readyStationIds.slice(0, 2) : stationIdsInOrder.slice(0, 2); +const selectedIntentsFromQuery = normalizeJourneyIntentTags( + Astro.url.searchParams + .getAll('intent') + .flatMap((intent) => intent.split(',')) + .map((intent) => intent.trim()) + .filter(Boolean) +); +const selectedIntents = + selectedIntentsFromProps.length > 0 ? selectedIntentsFromProps : selectedIntentsFromQuery; + +const rankedStations = rankStationsForJourneyIntent({ + stationIdsInOrder, + stationStateById, + selectedIntents, +}); +const recommendedStationIds = rankedStations.slice(0, 3).map((rankedStation) => rankedStation.stationId); const focusStationId = recommendedStationIds[0] || stationIdsInOrder[0]; const focusStation = stationMetaById[focusStationId]; const focusStationState = stationStateById[focusStationId] || { @@ -51,9 +75,13 @@ const nextStation = nextStationId ? stationMetaById[nextStationId] : undefined; {recommendedStationIds.map((stationId, index) => ( diff --git a/src/data/journey-intents.ts b/src/data/journey-intents.ts new file mode 100644 index 0000000..072ea4c --- /dev/null +++ b/src/data/journey-intents.ts @@ -0,0 +1,94 @@ +export type JourneyIntentTag = 'new-api' | 'major-redesign' | 'governance' | 'platform-scale'; + +export type JourneyIntentMapping = Record>; + +export const journeyIntentMappings: JourneyIntentMapping = { + 'new-api': { + 'api-product-strategy': 100, + 'api-consumer-experience': 85, + 'api-design': 80, + 'api-platform-architecture': 75, + 'api-delivery': 60, + }, + 'major-redesign': { + 'api-audit': 95, + 'api-design': 90, + 'api-consumer-experience': 80, + 'api-delivery': 70, + 'monitoring-and-improving': 60, + }, + governance: { + 'legal-and-compliance': 100, + 'security-and-privacy': 95, + 'service-agreements': 85, + 'operating-guidelines': 80, + 'portfolio-management': 75, + }, + 'platform-scale': { + 'api-platform-architecture': 100, + 'scalable-infrastructure': 95, + 'ci-cd': 85, + development: 75, + 'release-management': 70, + }, +}; + +interface StationState { + status?: string; +} + +interface RankInput { + stationIdsInOrder: string[]; + stationStateById: Record; + selectedIntents: JourneyIntentTag[]; +} + +export interface RankedStation { + stationId: string; + score: number; + intentScore: number; + readinessScore: number; + positionScore: number; +} + +function getReadinessScore(status?: string): number { + if (status === 'ready') return 100; + if (status === 'completed') return 40; + return 0; +} + +export function rankStationsForJourneyIntent({ + stationIdsInOrder, + stationStateById, + selectedIntents, +}: RankInput): RankedStation[] { + const hasIntentSelection = selectedIntents.length > 0; + + return stationIdsInOrder + .map((stationId, index) => { + const intentScore = selectedIntents.reduce((total, intent) => { + return total + (journeyIntentMappings[intent]?.[stationId] || 0); + }, 0); + + const readinessScore = getReadinessScore(stationStateById[stationId]?.status); + const positionScore = Math.max(stationIdsInOrder.length - index, 0); + + const score = hasIntentSelection + ? intentScore * 1.5 + readinessScore + positionScore * 0.25 + : positionScore; + + return { + stationId, + score, + intentScore, + readinessScore, + positionScore, + }; + }) + .sort((a, b) => b.score - a.score); +} + +export function normalizeJourneyIntentTags(tags: string[]): JourneyIntentTag[] { + const validTags = new Set(['new-api', 'major-redesign', 'governance', 'platform-scale']); + return tags.filter((tag): tag is JourneyIntentTag => validTags.has(tag as JourneyIntentTag)); +} From 53b7b669726aefcad0b48b15e3ba2902733786d5 Mon Sep 17 00:00:00 2001 From: Marjukka Niinioja Date: Fri, 27 Mar 2026 05:41:44 +0200 Subject: [PATCH 06/11] Remove query-parameter intent parsing from static journey page --- src/components/app/JourneyFlowPage.astro | 11 +---------- src/content/docs/getting-started/journey-flow.mdx | 1 - 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/components/app/JourneyFlowPage.astro b/src/components/app/JourneyFlowPage.astro index 60a1e41..f994220 100644 --- a/src/components/app/JourneyFlowPage.astro +++ b/src/components/app/JourneyFlowPage.astro @@ -16,16 +16,7 @@ interface Props { const { selectedIntents: selectedIntentsFromProps = [] } = Astro.props as Props; const { stationIdsInOrder, stationStateById, stationMetaById, criteriaLabelsById, resourcesById } = journeyRuntime; - -const selectedIntentsFromQuery = normalizeJourneyIntentTags( - Astro.url.searchParams - .getAll('intent') - .flatMap((intent) => intent.split(',')) - .map((intent) => intent.trim()) - .filter(Boolean) -); -const selectedIntents = - selectedIntentsFromProps.length > 0 ? selectedIntentsFromProps : selectedIntentsFromQuery; +const selectedIntents = normalizeJourneyIntentTags(selectedIntentsFromProps); const rankedStations = rankStationsForJourneyIntent({ stationIdsInOrder, diff --git a/src/content/docs/getting-started/journey-flow.mdx b/src/content/docs/getting-started/journey-flow.mdx index 392962d..18da1e2 100644 --- a/src/content/docs/getting-started/journey-flow.mdx +++ b/src/content/docs/getting-started/journey-flow.mdx @@ -7,6 +7,5 @@ sidebar: import JourneyFlowPage from '../../../components/app/JourneyFlowPage.astro'; Use this guided page to move from initiative intake to recommended next stations using generated method runtime data. -Optional query parameter: `?intent=new-api` (or comma-separated, e.g. `?intent=new-api,governance`) to influence station scoring. From 1c54c93dfb853d673e879e0390c5f55f672df5bd Mon Sep 17 00:00:00 2001 From: Marjukka Niinioja Date: Fri, 27 Mar 2026 05:52:51 +0200 Subject: [PATCH 07/11] Add quiz-style criteria intake to generate guided journey --- src/components/app/JourneyFlowPage.astro | 185 +++++++++++++++++++++++ 1 file changed, 185 insertions(+) diff --git a/src/components/app/JourneyFlowPage.astro b/src/components/app/JourneyFlowPage.astro index f994220..e6e3148 100644 --- a/src/components/app/JourneyFlowPage.astro +++ b/src/components/app/JourneyFlowPage.astro @@ -5,6 +5,7 @@ import ArtifactPanel from './ArtifactPanel.astro'; import NextStationCard from './NextStationCard.astro'; import journeyRuntime from '../../data/generated/journey-runtime.json'; import { + journeyIntentMappings, normalizeJourneyIntentTags, rankStationsForJourneyIntent, type JourneyIntentTag, @@ -17,6 +18,7 @@ interface Props { const { selectedIntents: selectedIntentsFromProps = [] } = Astro.props as Props; const { stationIdsInOrder, stationStateById, stationMetaById, criteriaLabelsById, resourcesById } = journeyRuntime; const selectedIntents = normalizeJourneyIntentTags(selectedIntentsFromProps); +const criteriaOptions = Object.entries(criteriaLabelsById).sort((a, b) => a[1].localeCompare(b[1])); const rankedStations = rankStationsForJourneyIntent({ stationIdsInOrder, @@ -83,6 +85,40 @@ const nextStation = nextStationId ? stationMetaById[nextStationId] : undefined; ]} /> +
+

Journey planner (quiz-style intake)

+

Select criteria your API initiative already meets, then generate a guided journey from pre-built station data.

+ +
+
+ Optional intent tags +
+ + + + +
+
+ +
+ Entry criteria your initiative already meets +
+ { + criteriaOptions.map(([criterionId, criterionLabel]) => ( + + )) + } +
+
+ + +
+ +
+
+

Step 2 — Recommended first station(s)

{recommendedStationIds.map((stationId, index) => ( @@ -134,6 +170,48 @@ const nextStation = nextStationId ? stationMetaById[nextStationId] : undefined; )} + + From 5eb846f069947514740cd8cea8967ba9b5fea4bd Mon Sep 17 00:00:00 2001 From: Marjukka Niinioja Date: Fri, 27 Mar 2026 06:11:07 +0200 Subject: [PATCH 08/11] Make planner output fully dynamic with station and line links --- scripts/generate-method-pages.mjs | 19 +++ src/components/app/JourneyFlowPage.astro | 143 ++++++---------------- src/data/generated/journey-runtime.json | 145 +++++++++++++++++++++++ 3 files changed, 198 insertions(+), 109 deletions(-) diff --git a/scripts/generate-method-pages.mjs b/scripts/generate-method-pages.mjs index 8890a91..ccaa319 100644 --- a/scripts/generate-method-pages.mjs +++ b/scripts/generate-method-pages.mjs @@ -586,6 +586,13 @@ async function generate() { for (const ln of linesData.lines.items) { lineMap[ln.id] = ln; } + const stationLinesById = {}; + for (const line of linesData.lines.items) { + for (const stationId of line.stations || []) { + if (!stationLinesById[stationId]) stationLinesById[stationId] = []; + stationLinesById[stationId].push(line.id); + } + } const nextStationCriteria = {}; const coreItems = stationsData['core-stations'].items; @@ -638,6 +645,18 @@ async function generate() { }, ]) ), + stationLinesById, + lineMetaById: Object.fromEntries( + linesData.lines.items.map((line) => [ + line.id, + { + id: line.id, + title: translate(line.title, baseLabels), + slug: line.slug, + color: line.color || '#000000', + }, + ]) + ), }; await ensureDir(generatedDataDir); diff --git a/src/components/app/JourneyFlowPage.astro b/src/components/app/JourneyFlowPage.astro index e6e3148..c3d121f 100644 --- a/src/components/app/JourneyFlowPage.astro +++ b/src/components/app/JourneyFlowPage.astro @@ -1,13 +1,9 @@ --- import JourneyIntake from './JourneyIntake.astro'; -import StationChecklist from './StationChecklist.astro'; -import ArtifactPanel from './ArtifactPanel.astro'; -import NextStationCard from './NextStationCard.astro'; import journeyRuntime from '../../data/generated/journey-runtime.json'; import { journeyIntentMappings, normalizeJourneyIntentTags, - rankStationsForJourneyIntent, type JourneyIntentTag, } from '../../data/journey-intents'; @@ -16,54 +12,17 @@ interface Props { } const { selectedIntents: selectedIntentsFromProps = [] } = Astro.props as Props; -const { stationIdsInOrder, stationStateById, stationMetaById, criteriaLabelsById, resourcesById } = journeyRuntime; -const selectedIntents = normalizeJourneyIntentTags(selectedIntentsFromProps); -const criteriaOptions = Object.entries(criteriaLabelsById).sort((a, b) => a[1].localeCompare(b[1])); - -const rankedStations = rankStationsForJourneyIntent({ +const { stationIdsInOrder, stationStateById, - selectedIntents, -}); -const recommendedStationIds = rankedStations.slice(0, 3).map((rankedStation) => rankedStation.stationId); -const focusStationId = recommendedStationIds[0] || stationIdsInOrder[0]; -const focusStation = stationMetaById[focusStationId]; -const focusStationState = stationStateById[focusStationId] || { - requiredCriteriaIds: [], - missingCriteriaIds: [], - stepArtifactActions: [], -}; - -const criteria = (focusStationState.requiredCriteriaIds || []).map((criterionId) => ({ - label: criteriaLabelsById[criterionId] || criterionId, - done: !(focusStationState.missingCriteriaIds || []).includes(criterionId), -})); - -const artifacts = (focusStationState.stepArtifactActions || []) - .map((action) => { - const resource = resourcesById[action.resourceId]; - if (!resource) return null; - return { - name: resource.title, - description: resource.description || `Required ${resource.category} artifact.`, - canvasUrl: resource.canvas - ? `https://canvascreator.apiopscycles.com/?canvas=${resource.canvas}` - : `/${resource.slug}/`, - }; - }) - .filter(Boolean); - -const previewCanvasId = artifacts.length - ? resourcesById[(focusStationState.stepArtifactActions || [])[0]?.resourceId]?.canvas || undefined - : undefined; - -const focusIndex = stationIdsInOrder.indexOf(focusStationId); -const nextStationId = - stationIdsInOrder - .slice(focusIndex + 1) - .find((stationId) => ['ready', 'blocked'].includes(stationStateById[stationId]?.status || '')) || - stationIdsInOrder.find((stationId) => stationStateById[stationId]?.status !== 'completed'); -const nextStation = nextStationId ? stationMetaById[nextStationId] : undefined; + stationMetaById, + criteriaLabelsById, + resourcesById, + stationLinesById, + lineMetaById, +} = journeyRuntime; +const selectedIntents = normalizeJourneyIntentTags(selectedIntentsFromProps); +const criteriaOptions = Object.entries(criteriaLabelsById).sort((a, b) => a[1].localeCompare(b[1])); ---
-

Step 2 — Recommended first station(s)

-
- {recommendedStationIds.map((stationId, index) => ( - - ))} -
- - - - - -

Step 5 — Completion + next station recommendation

-{nextStation ? ( - -) : ( -

All stations are currently marked as completed in the generated runtime state.

-)} -