-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
16132b8
commit 57b87d1
Showing
3 changed files
with
178 additions
and
118 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
# Algorithm documentation | ||
|
||
This app contains an algorithm that calculates different risks for the project based on the answers user has provided in the survey. The algorithm is located in the backend at `util/algorithm`. In general, the algorithm receives a `FormValues` object and returns a `RiskData` object which is appended to the entry that is saved to the database. | ||
|
||
## RiskData | ||
|
||
`RiskData` object is one of the most important components of the app because it contains information of the risks related to the project. The object consists of the following properties. | ||
|
||
### Answers | ||
|
||
`Answers` contains all the data collected in the survey. It is a `FormValues` object. | ||
|
||
### Country | ||
|
||
`Country` includes all the country risks. These risks are based on the country the user has selected. It is a simple key-value object where the key is a title of the risk and value is either a risk level (a number from 1 to 3) or a some other value if it requires information from the survey before it can be converted to a risk level. | ||
|
||
The object includes the following data. The source for data is in the brackets: | ||
|
||
- Academic freedom (CSV) | ||
- Corruption (Worldbank API) | ||
- Human development index (CSV) | ||
- Safety level (Ministry for Foreign Affairs of Finland's matkustustiedotteet, RSS feed) | ||
- Sanctions (EU sanctions map API) | ||
- Political stability (Worldbank API) | ||
- Rule of Law (CSV) | ||
- Universities (WHED, web scraper) | ||
|
||
Risk levels of sanctions, safety level and universities are calculated based on the form data. | ||
|
||
### Risks | ||
|
||
`Risks` property is an array of `Risk` objects that are not included in `CountryData`. `Risk` object looks like this | ||
|
||
```typescript | ||
export interface Risk { | ||
id: string // unique id | ||
title: string // title for the risk that is rendered in the risk table | ||
level: any // risk level which can be a number from 1 to 3 or a falsy value | ||
infoText?: string // info text tells more information about the risk level and if the level requires some additional actions. The text is appended to the risk object in the client-side. | ||
} | ||
``` | ||
|
||
### UpdatedData | ||
|
||
The app can recalculate the risks. This action is triggered in two occasions: | ||
|
||
1. The user presses a button in the user page | ||
2. A cron job recalculates them once a week | ||
|
||
The recalculated data is saved to `UpdatedData`. It is an array of objects, and one object contains the same properties as `RiskData` and additionally `createdAt` string. | ||
|
||
## Structure | ||
|
||
The algorithm consists of the following files: | ||
|
||
### createRiskData.ts | ||
|
||
**What does it do** | ||
|
||
This file is the main file of the algorithm. It contains one function - `createRiskData` - that is responsible for collecting all the risks together and returning the ready `RiskData` object to `entryRouter`. | ||
|
||
In addition, this function gets all the country risks. | ||
|
||
**Props** | ||
|
||
`createRiskData` accepts an object with type `FormValues` as props. | ||
|
||
--- | ||
|
||
### getCountryRisks.ts | ||
|
||
**What does it do** | ||
|
||
This file contains a function `getCountryRisks`. It updates `sanctions`, `gdpr` and `safetyLevel` risks so that they have numeric values. | ||
|
||
**Props** | ||
|
||
`getCountryRisks` has two parameters: `countryData: CountryData` and `formData: FormValues` | ||
|
||
--- | ||
|
||
### getOtherRisks.ts | ||
|
||
**What does it do** | ||
|
||
The file contains a `getOtherRisks` function that creates a `Risk[]` object which looks like this: | ||
|
||
```typescript | ||
const riskArray: Risk[] = [ | ||
{ | ||
id: 'country', | ||
title: 'riskTable:countryRiskLevel', | ||
level: countryRiskValues ? countryRiskValues[0] : null, | ||
}, | ||
{ | ||
id: 'university', | ||
title: 'riskTable:universityRiskLevel', | ||
level: universityRisk(formData['20'], country?.universities), | ||
}, | ||
{ | ||
id: 'duration', | ||
title: 'riskTable:durationRiskLevel', | ||
level: questions | ||
.find((question) => question.id === 12) | ||
?.optionData.options.find((o) => o.id === formData[12])?.risk, | ||
}, | ||
{ | ||
id: 'dualUse', | ||
title: 'riskTable:dualUseRiskLevel', | ||
level: dualUseRiskValue, | ||
}, | ||
{ | ||
id: 'organisation', | ||
title: 'riskTable:organisationRiskLevel', | ||
level: organisationRiskValue, | ||
}, | ||
{ | ||
id: 'economic', | ||
title: 'riskTable:economicRiskLevel', | ||
level: questions | ||
.find((question) => question.id === 16) | ||
?.optionData.options.find((o) => o.id === formData[16])?.risk, | ||
}, | ||
{ | ||
id: 'ethical', | ||
title: 'riskTable:ethicalRiskLevel', | ||
level: ethicalRiskValue, | ||
}, | ||
] | ||
``` | ||
|
||
Consortium risk is added to the array if necessary. | ||
|
||
Finally all risks that have null or undefined risk level are filtered out. | ||
|
||
**Props** | ||
|
||
`getOtherRisks(country: UpdatedCountryData, questions: Question[], formData: FormValues)` | ||
|
||
--- | ||
|
||
### getTotalRisk.ts | ||
|
||
**What does it do** | ||
|
||
This file contains a function `getTotalRisk` which calculates a total risk level for the projects based on all the individual risks. | ||
|
||
The level is calculated by calculating an average level of all risks and rounding it to the nearest integer. After that, the level is multiplied with a few multiplier and if the level happens to be over 3, it is set to 3. | ||
|
||
**Props** | ||
|
||
`getTotalRisk(riskArray: Risk[], country: UpdatedCountryData | undefined, formData: FormValues)` | ||
|
||
--- | ||
|
||
### countryLists.ts | ||
|
||
This file contains static lists of country codes used in the algorithm. There are four lists: `euCountries`, `eeaCountries`, `adequateProtectionCountries` and `globalNorthCountries` | ||
|
||
--- | ||
|
||
### utils.ts | ||
|
||
`utils.ts` contains a few different utility functions that are used to calculate specific risks. The file contains functions for: | ||
|
||
- GDPR | ||
- University | ||
- Dual Use | ||
- Organisation | ||
- Consortium | ||
- Total country risk |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters