diff --git a/dictionary.txt b/dictionary.txt index 925a27926..8cea352ec 100644 --- a/dictionary.txt +++ b/dictionary.txt @@ -351,3 +351,4 @@ nagging wrongdoing demystify dysfunctional +outlier diff --git a/docs/risks/Feature-Risks/Feature-Fit-Risk.md b/docs/risks/Feature-Risks/Feature-Fit-Risk.md index d8bf9c329..1cec30007 100644 --- a/docs/risks/Feature-Risks/Feature-Fit-Risk.md +++ b/docs/risks/Feature-Risks/Feature-Fit-Risk.md @@ -14,9 +14,32 @@ part_of: Feature Risk -This is the risk that the feature that clients want to use in the software _isn't there_. +This is the risk that the feature that clients want to use in the software _isn't there_ or isn't quite what you need. - This might manifest itself as complete _absence_ of something you need, e.g "Why is there no word count in this editor?" - It could be that the implementation isn't complete enough, e.g "why can't I add really long numbers in this calculator?" + - It might be that the software isn't designed with your client's use case in mind, e.g. "I really needed this to handle UK taxes but it only works for the US." -[Feature Fit Risks](/tags/Feature-Fit-Risk) are mitigated by talking to clients, product development and delivery (as shown in the above diagram). But that leads on to... \ No newline at end of file +## Worked Example + +![Using analysis to ensure feature fit](/img/generated/risks/posters/feature-fit-risk.svg) + +In the above diagram, we can see Feature Fit Risk being addressed by [Analysis](/practices/External-Relations/Analysis). One should be careful about the attendant risks of analysis - it can go on for too long, or get bogged down in debate (so called Analysis Paralysis) which can lead to [Schedule Risk](/tags/Schedule-Risk) or [Funding Risk](/tags/Funding-Risk). + +## Example Threats + +### 1. Misunderstanding of Client Needs + +**Threat:** Lack of clarity around client requirements could lead to delivering features that don't quite solve the client's problem. + +### 2. Ignoring Edge Cases + +**Threat:** Assuming all users will use the software in the same way may result in ignoring the outlier cases. + +### 3. Lack of Iterative Feedback + +**Threat:**: Not gathering feedback during development phases could result in delivering features that are not fully aligned with client needs. + +### 4. Evolving Requirements + +**Threat:** Not reassessing the design when new requirements emerge can lead to delivering features that no longer align with the client’s current needs. \ No newline at end of file diff --git a/docs/risks/Risk-Landscape.md b/docs/risks/Risk-Landscape.md index d0b55ce23..6f69b2bc1 100644 --- a/docs/risks/Risk-Landscape.md +++ b/docs/risks/Risk-Landscape.md @@ -67,6 +67,10 @@ Below is a table outlining the different risks we'll see. There _is_ an order t |[Map And Territory Risk](/tags/Map-And-Territory-Risk) |Risks due to the fact that people don't see the world as it really is. (After all, they're working off different, imperfect [Internal Models](/tags/Internal-Model).)| |[Operational Risk](/tags/Operational-Risk) |Software is embedded in a system containing people, buildings, machines and other services. Operational risk considers this wider picture of risk associated with running a software service or business in the real world.| + + + + After the last stop on the tour, in [Staging and Classifying](Staging-And-Classifying) we'll have a recap about what we've seen and make some guesses about how things fit together. Also on that page is a [periodic table](Staging-And-Classifying#towards-a-periodic-table-of-risks) showing a diagrammatic view of how all these risks fit together. diff --git a/numbers/Practices.numbers b/numbers/Practices.numbers index 576021e48..0de14f818 100644 Binary files a/numbers/Practices.numbers and b/numbers/Practices.numbers differ diff --git a/src/images/generated/risks/posters/feature-fit-risk.adl b/src/images/generated/risks/posters/feature-fit-risk.adl new file mode 100644 index 000000000..e4a657631 --- /dev/null +++ b/src/images/generated/risks/posters/feature-fit-risk.adl @@ -0,0 +1,24 @@ + + + + + + + + Analysis + + + + + + + diff --git a/src/plugins/category-listing/index.js b/src/plugins/category-listing/index.js index 568bba061..81a1948b6 100644 --- a/src/plugins/category-listing/index.js +++ b/src/plugins/category-listing/index.js @@ -21,10 +21,11 @@ module.exports = async function myPlugin(context, options) { const allTags = [...tagNames, ...mitigates, ...attendant, ...practices, ...partOf] + const isRisk = allTags.includes("Risks") + const isPractice = allTags.includes("Practice") + const isMethod = allTags.includes("Method") + if (!allTags.includes(title)) { - const isRisk = allTags.includes("Risks") - const isPractice = allTags.includes("Practice") - const isMethod = allTags.includes("Method") if (isRisk || isPractice || isMethod) { console.warn(`${doc.title} is not self-tagged risk =${isRisk} practice=${isPractice} tags=${JSON.stringify(allTags)}`) } @@ -41,6 +42,9 @@ module.exports = async function myPlugin(context, options) { description: doc.description, order: doc.sidebarPosition ?? 0, tags: doc.tags, + isRisk, + isPractice, + isMethod, frontMatter: doc.frontMatter, } diff --git a/src/theme/MDXComponents/index.js b/src/theme/MDXComponents/index.js index 4472c6394..9cff1a3da 100644 --- a/src/theme/MDXComponents/index.js +++ b/src/theme/MDXComponents/index.js @@ -5,6 +5,7 @@ import PracticeIntro from '../PracticeIntro' import RiskIntro from '../RiskIntro' import MethodIntro from '../MethodIntro' import TermList from '../TermList'; +import Matrix from '../Matrix'; export default { ...MDXComponents, @@ -13,7 +14,8 @@ export default { PracticeIntro, RiskIntro, MethodIntro, - TermList + TermList, + Matrix }; diff --git a/src/theme/Matrix/index.js b/src/theme/Matrix/index.js new file mode 100644 index 000000000..9a3b0c363 --- /dev/null +++ b/src/theme/Matrix/index.js @@ -0,0 +1,225 @@ +import React from 'react'; +import styles from './styles.module.css' +import { useDocById } from '@docusaurus/theme-common/internal' +import { usePluginData } from '@docusaurus/useGlobalData' +import { useLocation } from '@docusaurus/router'; + + +/*function formatReadableTag(page) { + return page.replaceAll("-", " ").substring(page.lastIndexOf("/") + 1) +} + +function tagUrl(tag) { + return "/tags/" + tag.replaceAll(" ", "-") +} + +const Risk = ({tag}) => { + return ( +
  • {tag}
  • + ) +} + +const Practice = ({ article, permalink, reason }) => { + return ( +
  • {article}: {reason}
  • + ) +}*/ + +function status(p, r) { + const mit = isPracticeMitigating(p, r.title) + const att = isPracticeAttendant(p, r.title) + + if (mit) { + return m + } else if (att) { + return a + } else { + return + } +} + + + +function isPracticeMitigating(article, title) { + const matching = (article.frontMatter?.practice?.mitigates ?? []).find(m => m.tag == title); + if (matching) { + return true + } else { + return false; + } +} + +function isPracticeAttendant(article, title) { + const matching = (article.frontMatter?.practice?.attendant ?? []).find(m => m.tag == title); + if (matching) { + return true + } else { + return false; + } +} + +function practiceDetails(map) { + const listOfPractices = [] + + function extractCategory(l) { + const parts = l.split("/") + return parts[2] + } + + Object.entries(map).forEach(([key, value]) => { + value.filter(a => a.isPractice) + .forEach(e => { + listOfPractices.push({ + title: e.title, + category: extractCategory(e.permalink), + url: e.permalink, + frontMatter: e.frontMatter + }) + }) + }) + + + const uniqueArray = listOfPractices.filter(function(item, pos) { + return listOfPractices.findIndex(e => e.title == item.title) == pos; + }) + + const sorted = uniqueArray.sort((a, b) => { + if (a.category != b.category) { + return a.category.localeCompare(b.category) + } else { + return a.title.localeCompare(b.title) + } + }) + + return sorted +} + +function riskDetails(map) { + const listOfRisks = [] + + Object.entries(map).forEach(([key, value]) => { + value.filter(a => a.isRisk) + .forEach(e => { + listOfRisks.push({ + title: e.title, + parent: e.frontMatter.part_of ?? null, + url: e.permalink + }) + }) + }) + + const uniqueArray = listOfRisks.filter(function(item, pos) { + return listOfRisks.findIndex(e => e.title == item.title) == pos; + }) + + // next, include parents + function hierarchy(x) { + const entry = uniqueArray.find(e => e.title == x) + if (!entry.parent) { + return [ x ] + } else { + return [ ...hierarchy(entry.parent), x ] + } + } + + uniqueArray.forEach(x => { + // sets depth on each one + x.hierarchy = hierarchy(x.title) + }) + + + const sorted = uniqueArray.sort((a, b) => { + for (let i=0; i < Math.min(a.hierarchy.length, b.hierarchy.length); i++) { + const ai = a.hierarchy[i] + const bi = b.hierarchy[i] + if (ai != bi) { + return ai.localeCompare(bi) + } + } + + return a.hierarchy.length - b.hierarchy.length + }) + + return sorted +} + +function buildRowspans(i, listOfRisks) { + const layer = listOfRisks.map(r => r.hierarchy[i]) + console.log("Layer", layer) + const out = [] + var last = layer[0] + var count = 0 + layer.forEach(l => { + if (l == last) { + count ++; + } else { + out.push({last}) + last = l + count = 1 + } + }) + + out.push({last}) + + return out +} + + +export default ({ fm }) => { + + const allData = usePluginData('category-listing'); + + const listOfRisks = riskDetails(allData) + + const listOfPractices = practiceDetails(allData) + + + return ( + + + + + + { + buildRowspans(0, listOfRisks) + } + + + + + + + + + + { + listOfPractices.map(p => + + + + + { listOfRisks.map(r => status(p, r)) } + + + ) + } + +
    CategoryPractice
    + + { + buildRowspans(1, listOfRisks) + } +
    + + { + buildRowspans(2, listOfRisks) + } +
    + + { + buildRowspans(3, listOfRisks) + } +
    {p.category}{p.title}
    + ) + +} diff --git a/src/theme/Matrix/styles.module.css b/src/theme/Matrix/styles.module.css new file mode 100644 index 000000000..93b3d8994 --- /dev/null +++ b/src/theme/Matrix/styles.module.css @@ -0,0 +1,39 @@ +.riskIntro { + border: 1px solid lightgray; + border-radius: 1rem; + margin: 2rem 0rem; + padding: 2rem; + font-size: smaller; +} + + +.columns { + display: flex; + gap: 1rem; + align-items: center; + margin-bottom: 1rem; +} + +.left { + flex-basis: 10rem; + flex-shrink: .5; + flex-grow: 0; + padding: 0; + margin: 0; +} + +.left img { + padding: 0; + display: block; +} + +.description { + color: grey; +} + +.right { + flex-basis: 15rem; + flex-grow: .7; + flex-shrink: .5; + padding: 1rem; +} \ No newline at end of file