Skip to content

Commit

Permalink
Added matrix, first draft of feature fit risk
Browse files Browse the repository at this point in the history
  • Loading branch information
robmoffat committed Nov 13, 2024
1 parent b192dd6 commit 893001d
Show file tree
Hide file tree
Showing 9 changed files with 328 additions and 6 deletions.
1 change: 1 addition & 0 deletions dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -351,3 +351,4 @@ nagging
wrongdoing
demystify
dysfunctional
outlier
27 changes: 25 additions & 2 deletions docs/risks/Feature-Risks/Feature-Fit-Risk.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,32 @@ part_of: Feature Risk

<RiskIntro fm={frontMatter} />

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...
## 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.
4 changes: 4 additions & 0 deletions docs/risks/Risk-Landscape.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.|
<Matrix />
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.
Expand Down
Binary file modified numbers/Practices.numbers
Binary file not shown.
24 changes: 24 additions & 0 deletions src/images/generated/risks/posters/feature-fit-risk.adl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<diagram xmlns="http://www.kite9.org/schema/adl"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xslt="http://www.kite9.org/schema/xslt"
id="diagram-113"
xslt:template="/public/templates/risk-first/risk-first-template.xsl">
<container bordered="true" id="c" style="--kite9-layout: down; ">
<risk class="feature-fit"/>
<label id="id_16">
Risk that the needs of the client
don't coincide with services
provided by you, the supplier.
</label>
</container>
<group style="--kite9-layout: down; --kite9-horizontal-align: left;">
<action style="--kite9-horizontal-align: left;">Analysis</action>
</group>
<container id="d" style="--kite9-vertical-sizing: maximize; ">
<risk class="funding" style="--kite9-vertical-align: top; " />
<risk class="schedule" style="--kite9-vertical-align: top; "/>
<label id="id_16-oa">Attendant Risks</label>
</container>
</diagram>
10 changes: 7 additions & 3 deletions src/plugins/category-listing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)}`)
}
Expand All @@ -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,
}

Expand Down
4 changes: 3 additions & 1 deletion src/theme/MDXComponents/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -13,7 +14,8 @@ export default {
PracticeIntro,
RiskIntro,
MethodIntro,
TermList
TermList,
Matrix
};


225 changes: 225 additions & 0 deletions src/theme/Matrix/index.js
Original file line number Diff line number Diff line change
@@ -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 (
<li><a href={tagUrl(tag)}>{tag}</a></li>
)
}
const Practice = ({ article, permalink, reason }) => {
return (
<li><a href={permalink}>{article}</a>: {reason}</li>
)
}*/

function status(p, r) {
const mit = isPracticeMitigating(p, r.title)
const att = isPracticeAttendant(p, r.title)

if (mit) {
return <td>m</td>
} else if (att) {
return <td>a</td>
} else {
return <td />
}
}



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(<th colspan={count}>{last}</th>)
last = l
count = 1
}
})

out.push(<th colspan={count}>{last}</th>)

return out
}


export default ({ fm }) => {

const allData = usePluginData('category-listing');

const listOfRisks = riskDetails(allData)

const listOfPractices = practiceDetails(allData)


return (
<table>
<thead>
<tr>
<th>Category</th>
<th>Practice</th>
{
buildRowspans(0, listOfRisks)
}
</tr>
<tr>
<th />
<th />
{
buildRowspans(1, listOfRisks)
}
</tr>
<tr>
<th />
<th />
{
buildRowspans(2, listOfRisks)
}
</tr>
<tr>
<th />
<th />
{
buildRowspans(3, listOfRisks)
}
</tr>
</thead>
<tbody>
{
listOfPractices.map(p =>
<tr key={p.title}>
<td>{p.category}</td>
<td>{p.title}</td>

{ listOfRisks.map(r => status(p, r)) }

</tr>
)
}
</tbody>
</table>
)

}
39 changes: 39 additions & 0 deletions src/theme/Matrix/styles.module.css
Original file line number Diff line number Diff line change
@@ -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;
}

0 comments on commit 893001d

Please sign in to comment.