diff --git a/.env b/.env new file mode 100644 index 0000000..df6d0f8 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +PORT=1337 +USE_CACHE_REDIS=0 \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..52f4238 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,8 @@ +/* eslint-env bun */ +module.exports = { + extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint'], + root: true, + ignorePatterns: ['**/*.js'], +} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..8db1894 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,33 @@ +# Workflow for tests + +name: Test + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Setup Bun + uses: antongolub/action-setup-bun@v1 + + - name: Setup repo + uses: actions/checkout@v3 + + - name: Install + run: bun install + + - name: Run linter + run: bun lint + + - name: Run tests + run: bun test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b8c49d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,132 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# Build +build/ diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..94dc90a --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,134 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +huulbaek@gmail.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..712c661 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM oven/bun:latest + +WORKDIR /app +COPY package.json ./ +COPY bun.lockb ./ + +RUN bun install --no-save + +COPY . . + +CMD ["bun", "run", "src/index.ts"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..63ae4ab --- /dev/null +++ b/LICENSE @@ -0,0 +1,11 @@ +Permission to use, copy, modify, and/or +distribute this software for any purpose with or without fee is hereby +granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER +RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE +OR PERFORMANCE OF THIS SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f65e2c7 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# Agiliate Engine +The Agiliate Engine is a sophisticated computing engine designed to calculate the required office space for a company. It takes into account various parameters such as the number of employees, workspace types, and government regulations to provide an accurate estimation of the space needed. + +Read this quick start file or the [documentation](./docs/index.md) to learn more about the Agiliate Engine. + +## Configuration +The application's configuration parameters are stored in the src/config/default.json file. These parameters can be modified to suit the specific needs of your company. The configuration file to use is defined in the constructor of the `Calculator` class in `src/calculator.js`. You can also change the variables and constants in the request, as shown in the example Postman collection, `postman_collection.json`. + +## Installation and requirements +The application requires either [Bun](https://bun.sh/) or [Docker](https://www.docker.com/) to run. If using Bun run + +```bash +bun install +``` + +to install the dependencies. If using Docker Compose, run + +```bash +docker-compose up +``` + +to start the application. Or use + +```bash +docker build -t agiliate-engine . && docker run -p 1337:1337 agiliate-engine +``` + +## Development server +Start a development hot reload server with + +```bash +bun run --watch src/index.ts +``` + +## Running +Run the application with + +```bash +bun run src/index.ts +``` + +## Tests +Run the tests with + +```bash +bun test +``` diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..147b2ac Binary files /dev/null and b/bun.lockb differ diff --git a/docker-compose-with-redis-cache.yml b/docker-compose-with-redis-cache.yml new file mode 100644 index 0000000..68b5cda --- /dev/null +++ b/docker-compose-with-redis-cache.yml @@ -0,0 +1,10 @@ +version: '3.8' + +services: + agiliate-engine: + command: bun run start-with-redis + redis: + container_name: redis + image: redis:latest + ports: + - 6379:6379 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..01d572b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +version: '3.8' + +services: + agiliate-engine: + build: + context: . + container_name: agiliate-engine + restart: unless-stopped + working_dir: /app + ports: + - 1337:1337 + volumes: + - .:/app + - node_modules:/app/node_modules + command: bun run start + redis: + container_name: redis + image: redis:latest + ports: + - 6379:6379 + +volumes: + node_modules: diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 0000000..4b2effe --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,37 @@ +# Contributing to Agiliate Engine +First and foremost, please do! We welcome all contributions, big or small, no matter your level of experience – be that: + +- Reporting a bug +- Discussing the current state of the code +- Submitting a fix +- Proposing new features + +Please also read our [code of conduct](../CODE_OF_CONDUCT.md) before you start. + +We use GitHub to host code, to track issues and feature requests, as well as accept pull requests – pull requests are the best way to propose changes to the codebase: + +1. Fork the repo and create your branch from `main`. +2. If you've added code that should be tested, add or update tests in the `test` folder. +3. If needed, update the documentation. +4. Ensure the test suite passes. +5. Make sure your code lints. +6. Let's get your changes merged! + +**Any contributions you make will be under the 0BSD license** +In short, when you submit code changes, your submissions are understood to be under the same [0BSD license](https://opensource.org/license/0bsd/) that covers the project. Feel free to contact the maintainers if that's a concern. + +Please report [bugs and issues on GitHub](https://github.com/nodalit/agiliate-engine). + +**Great Bug Reports** could contain: + +- A quick summary and/or background +- Steps to reproduce + - Be specific + - Give sample code if you can. +- What you expected would happen +- What actually happens + +We *love* good bug reports. + +*Be constistent with style* +Lint that mother – run `bun lint`. diff --git a/docs/calculation.md b/docs/calculation.md new file mode 100644 index 0000000..7d16a6f --- /dev/null +++ b/docs/calculation.md @@ -0,0 +1,92 @@ +# Calculation +The [Calculator](../src/calculations/calculator.ts) class is the main class responsible for performing calculations. It is initialized with variables, custom space constants, custom constants, and an optional configuration file. + +```typescript +new Calculator(variables, customSpaceConstants, customConstants, config) +``` + +where the `variables`, `customSpaceConstants`, `customConstants` and `config` are in the JSON body of a POST request: + +```json +{ + "variables": { + "accessToCoworking": false, + "accessToCanteen": true, + "accessToCourseSpace": true, + "accessToAuditorium": true, + "accessToCellOffice": true, + "accessToReception": false, + "accessToExercise": true, + "specialAreaOffice": 80, + "specialAreaShared": 0, + "specialAreaCommon": 100, + "seatsInAuditorium": 50, + "numberOfEmployees": 330, + "concurrencyAttendanceShare": 1.0, + "peakConcurrencyAttendanceShare": 1.0, + "overCapacityShare": 0.0, + "homeOfficeAverageShare": 0.0, + "shareOfEmployeesInAuditorium": 0.30, + "touchdownShare": 0.0555, + "dockinShare": 0.2197, + "coworkingShare": 0.00, + "cellOfficeShare": 0.00, + "landscapeShare": 0.3630, + "projectroomShare": 0.1891, + "focusroomShare": 0.1054, + "quietzoneShare": 0.0672, + "miniMeetingroomShare": 0.2105, + "smallMeetingroomShare": 0.3017, + "mediumMeetingroomShare": 0.3600, + "largeMeetingroomShare": 0.1277 + }, + "customSpaceConstants": { + "auditorium": { + "minimumSquareMeters": 120 + } + }, + "customConstants": { + "governmentMinimumSquaremetersPerWorkSpace": 6 + } +} +``` + +There is also an example of a complete request in the [Postman collection](../postman_collection.json) + +The config property is populated by reading the provided configuration file. The constants property is a merge of the default constants from the configuration file and the custom constants. + +## Calculation Process + +The main calculation process is performed by the `result` method. This method performs several steps to calculate the results: + +1. *First Run*: The `#processSpacesFirst` method is called to calculate the initial results for all spaces. These results are needed in subsequent calculations. + +2. *Aggregate Subspace Results*: The `#sumResults` method is called to aggregate the results of the subspaces. + +3. *Calculate Total Workplace Area*: The `#calculateTotalWorkplaceArea` method is called to calculate the total workplace area. + +4. *Calculate Total Compensation Area*: The `#calculateTotalCompensationArea` method is called to calculate the total area that needs compensation calculations. + +5. *Calculate Total Employees Per Workspace Type Unadjusted*: The `#calculateTotalEmployeesPerWorkspaceTypeUnadjusted` method is called to calculate the total employees per workspace type (unadjusted). + +6. *Second Run*: The `#processSpacesSecond` method is called to calculate the second run results for all spaces. + +7. *Calculate Total Unadjusted Area*: The `#calculateTotalUnadjustedArea` method is called to calculate the total unadjusted area. + +8. *Third Run*: The `#processSpacesThird` method is called to calculate the third run results for all spaces. + +9. *Fourth Run*: The `#processSpacesFourth` method is called to calculate the fourth run results for all spaces. + +10. *Calculate Total Adjusted Area Including Compensation and Adjustment*: The `#calculateTotalAdjustedAreaInclCompensationAndAdjustment` method is called to calculate the total area including compensation and adjustment. + +11. *Compute Utility and Inner Walls*: The `#computeUtilityAndInnerWalls` method is called to calculate the utility floor space and inner walls of the top level spaces. + +12. *Compute Technical and Communication Area*: The `#computeTechnicalAndCommunicationArea` method is called to calculate the technical and communication areas. + +13. *Add Technical and Communication Area*: The `#addTechnicalAndCommunicationArea` method is called to add the technical and communication areas to the result. + +14. *Calculate Final Results*: The final results are calculated and returned. + +## Helper methods + +The Calculator class also contains several private helper methods that are used to perform specific calculations. These include methods to calculate the corridor area, inner wall area, and to add custom spaces to the result. diff --git a/docs/constants.md b/docs/constants.md new file mode 100644 index 0000000..075241a --- /dev/null +++ b/docs/constants.md @@ -0,0 +1,7 @@ +# Constants + +Constants are predefined values used in the Agiliate Engine. They are documented in the [constant.ts](../src/calculations/interfaces/constant.ts) file + +The `constants` property holds the default constants for the calculations. The `customConstants` property can be used to override the default constants. + +An example of a constant is `governmentMinimumSquaremetersPerWorkSpace`. It is used to define the minimum square meters per workspace required by the government. The default value can be overridden in the `customConstants` object. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..4e93203 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,12 @@ +# Agiliate Engine documentation + +The Agiliate Engine is a sophisticated computing engine designed to calculate the required office space for a company. It takes into account various parameters such as the number of employees, workspace types, and government regulations to provide an accurate estimation of the space needed. + +You are most welcome to contribute to this project. Please read the [contribution guidelines](./contribution.md) before you start. + +You should read about the main calculation process in the [calculation.md](./calculation.md) file. + +## Input +- [Variables](./variables.md) +- [Constants](./constants.md) +- [Space Constants](./space_constants.md) diff --git a/docs/space_constants.md b/docs/space_constants.md new file mode 100644 index 0000000..69f47c6 --- /dev/null +++ b/docs/space_constants.md @@ -0,0 +1,17 @@ +# Space Constants + +Space constants are predefined space values used in the Agiliate Engine. They are documented in the [space_constant.ts](../src/calculations/interfaces/space_constant.ts) file + +The `customSpaceConstants` object can be used to override the default constants for specific spaces. + +For example, if you wish to overwrite the `minimumSquareMeters` for the auditorium space, you can do so by setting the `auditorium` property in the `customSpaceConstants` object. + +```json +{ + "customSpaceConstants": { + "auditorium": { + "minimumSquareMeters": 100 + } + } +} +``` diff --git a/docs/variables.md b/docs/variables.md new file mode 100644 index 0000000..0fb0a6c --- /dev/null +++ b/docs/variables.md @@ -0,0 +1,5 @@ +# Variables + +The IVariable interface in the codebase represents a set of variables used in the calculations. They are sent to the application as a *POST request* at the `calculate` enpoint. See the example [Postman collection](../postman_collection.json) + +The variables are documented in the [variables.ts](../src/calculations/interfaces/variable.ts) file. diff --git a/package.json b/package.json new file mode 100644 index 0000000..97069f8 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "agiliate-engine", + "version": "1.0.0", + "description": "This is the Open Source version of the Agiliate Engine.", + "main": "src/index.ts", + "scripts": { + "dev": "bun run --watch src/index.ts", + "start": "bun install --no-save && bun run src/index.ts", + "start-with-redis": "bun install --no-save && USE_CACHE_REDIS=1 bun run src/index.ts", + "test": "bun test", + "lint": "bun eslint ." + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "bun-types": "latest", + "@typescript-eslint/eslint-plugin": "^6.8.0", + "@typescript-eslint/parser": "^6.8.0", + "eslint": "^8.52.0", + "typescript": "^5.1.3" + }, + "peerDependencies": { + "typescript": "^5.1.3", + "redis": "^4.6.10" + }, + "dependencies": { + "redis": "^4.6.10" + } +} diff --git a/postman_collection.json b/postman_collection.json new file mode 100644 index 0000000..567f615 --- /dev/null +++ b/postman_collection.json @@ -0,0 +1,64 @@ +{ + "info": { + "_postman_id": "ea84e19f-893b-4463-9400-6a99fd19272b", + "name": "Agiliate OSS", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "1849425", + "_collection_link": "https://meetrteam.postman.co/workspace/Team-Workspace~2b7cece5-f18f-4293-8e9d-f8467b285c02/collection/1849425-ea84e19f-893b-4463-9400-6a99fd19272b?action=share&source=collection_link&creator=1849425" + }, + "item": [ + { + "name": "Calculate", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"variables\": {\n \"accessToCoworking\": false,\n \"accessToCanteen\": true,\n \"accessToCourseSpace\": true,\n \"accessToAuditorium\": true,\n \"accessToCellOffice\": true,\n \"accessToReception\": false,\n \"accessToExercise\": true,\n \"specialAreaOffice\": 80,\n \"specialAreaShared\": 0,\n \"specialAreaCommon\": 100,\n \"seatsInAuditorium\": 50,\n \"numberOfEmployees\": 330,\n \"concurrencyAttendanceShare\": 1.0,\n \"peakConcurrencyAttendanceShare\": 1.0,\n \"overCapacityShare\": 0.0,\n \"homeOfficeAverageShare\": 0.0,\n \"shareOfEmployeesInAuditorium\": 0.30,\n \"touchdownShare\": 0.0555,\n \"dockinShare\": 0.2197,\n \"coworkingShare\": 0.00,\n \"cellOfficeShare\": 0.00,\n \"landscapeShare\": 0.3630,\n \"projectroomShare\": 0.1891,\n \"focusroomShare\": 0.1054,\n \"quietzoneShare\": 0.0672,\n \"miniMeetingroomShare\": 0.2105,\n \"smallMeetingroomShare\": 0.3017,\n \"mediumMeetingroomShare\": 0.3600,\n \"largeMeetingroomShare\": 0.1277\n },\n \"customSpaceConstants\": {\n \"auditorium\": {\n \"minimumSquareMeters\": 120\n }\n },\n \"customConstants\": {\n \"governmentMinimumSquaremetersPerWorkSpace\": 6\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/calculate", + "host": [ + "{{base_url}}" + ], + "path": [ + "calculate" + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "base_url", + "value": "http://localhost:1337", + "type": "string" + } + ] +} \ No newline at end of file diff --git a/src/calculations/calculator.ts b/src/calculations/calculator.ts new file mode 100644 index 0000000..06c4bcd --- /dev/null +++ b/src/calculations/calculator.ts @@ -0,0 +1,510 @@ +import { readFileSync } from 'fs' +import { join } from 'path' +import { ISpace } from './interfaces/space' +import { IConfig } from './interfaces/config' +import { ISpaceCalculation } from './interfaces/space_calculation' +import { ISpaceResult } from './interfaces/space_result' +import { ISpaceConstant } from './interfaces/space_constant' +import { IVariable } from './interfaces/variable' +import { IConstant } from './interfaces/constant' +import { ICalculationResult } from './interfaces/calculationresult' +import getSpace from './spaces' +import Dummy from './spaces/dummy' + +/** + * This is the main class for the calculator. + */ +export default class Calculator { + variables: IVariable + customSpaceConstants: ISpaceConstant + customConstants: IConstant + config: IConfig + constants: IConstant + totalWorkplaceArea: number = 0 + totalCompensationArea: number = 0 + totalUnadjustedArea: number = 0 + totalEmployeesPerWorkplaceTypeUnadjusted: number = 0 + totalAdjustedAreaInclCompensationWithAdjustmentAndCompensation: number = 0 + allEmployeesPerWorkplaceTypeUnadjusted: number[] = [] + + /** + * Initialize the calculator with the context from Koa and the optional config file to use + * @param {IVariable} variables – The variables to use + * @param {ISpaceConstant} customSpaceConstants – Custom space constants + * @param {IConstant} customConstants – Custom constants + * @param {string} [configFile] – The config file to use + */ + constructor(variables: IVariable, customSpaceConstants: ISpaceConstant, customConstants: IConstant , configFile: string = 'default.json') { + this.variables = variables + this.customSpaceConstants = customSpaceConstants + this.customConstants = customConstants + this.config = JSON.parse(readFileSync(join(import.meta.dir, '..', 'config', configFile), 'utf-8')) + // Merge custom constants with the default constants + this.constants = { + ...this.config.constants, + ...customConstants + } + } + + /** + * This is the main method responsible for calculating the results + * @returns {ICalculationResult} The result of the calculation + */ + result (): ICalculationResult { + // Get the results for all spaces, first run + let spaceResults = this.#processSpacesFirst(this.config.spaces) + // Get aggregate subspace results, first run + for (const space of spaceResults) { + space.result = this.#sumResults(space) + } + // Get some aggregate summed results + this.#calculateTotalWorkplaceArea(spaceResults) + this.#calculateTotalCompensationArea(spaceResults) + this.#calculateTotalEmployeesPerWorkspaceTypeUnadjusted(spaceResults) + + // Get aggregate subspace results, second run + spaceResults = this.#processSpacesSecond(spaceResults) + + // Get some more sums + this.#calculateTotalUnadjustedArea(spaceResults) + + // Get aggregate subspace results, third run + spaceResults = this.#processSpacesThird(spaceResults) + + // Get aggregate subspace results, fourth run + spaceResults = this.#processSpacesFourth(spaceResults) + + this.#calculateTotalAdjustedAreaInclCompensationAndAdjustment(spaceResults) + + // Get aggregate subspace results, second run (not checked yet! maybe another sumresults function only calculating the needed) + for (const space of spaceResults) { + space.result = this.#sumResults(space) + } + + // Calculate the utility floor space and inner walls of the top level spaces + const { utilityFloorSpace, innerwallsAreaSum, updatedSpaceResults } = this.#computeUtilityAndInnerWalls(spaceResults) + spaceResults = updatedSpaceResults + + const netArea = utilityFloorSpace - innerwallsAreaSum + const netAreaPerEmployee = netArea / this.variables.numberOfEmployees + + // Calculate the technical and communication areas + const { technicalArea, communicationArea } = this.#computeTechnicalAndCommunicationArea(utilityFloorSpace) + spaceResults = this.#addTechnicalAndCommunicationArea(spaceResults, technicalArea) + + const grossArea = utilityFloorSpace + technicalArea + communicationArea + + const grossAreaPerEmployee = grossArea / this.variables.numberOfEmployees + const utilityFloorSpacePerEmployee = utilityFloorSpace / this.variables.numberOfEmployees + const grossAreaPerDimensionedAttendance = grossArea / this.#dimensionedAttendance() + const grossNetFactor = grossArea / netArea + + return { + totals: { + unadjustedArea: Math.ceil(this.totalUnadjustedArea), + workplaceArea: Math.ceil(this.totalWorkplaceArea), + compensationArea: Math.ceil(this.totalCompensationArea), + employeesPerWorkplaceTypeUnadjusted: this.totalEmployeesPerWorkplaceTypeUnadjusted, + adjustedAreaInclCompensationWithAdjustmentAndCompensation: Math.ceil(this.totalAdjustedAreaInclCompensationWithAdjustmentAndCompensation), + utilityFloorSpace: Math.ceil(utilityFloorSpace), + netArea: Math.ceil(netArea), + grossArea: Math.ceil(grossArea), + netAreaPerEmployee: Math.round(netAreaPerEmployee * 100) / 100, + grossAreaPerEmployee: Math.round(grossAreaPerEmployee * 100) / 100, + utilityFloorSpacePerEmployee: Math.round(utilityFloorSpacePerEmployee * 100) / 100, + grossAreaPerDimensionedAttendance: Math.round(grossAreaPerDimensionedAttendance * 100) / 100, + grossNetFactor: Math.round(grossNetFactor * 100) / 100, + }, + spaces: spaceResults + } + } + + /** + * This method is used to add a custom space to the result, for example corridors and inner walls, which are not really spaces + * @param {string} name - The name of the space + * @param {ISpaceResult} result - The result of the space + * @returns {ISpace} + */ + #addCustomSpace (name: string, result: ISpaceResult): ISpace { + return { + name, + result, + constants: { + areaPerRole: 0, + personsPerType: 0, + shouldCalculateCompensation: false, + adhereToGovernmentMinimum: false, + } + } + } + + /** + * This method add the technical and communication areas to the result + * @param {number} area - The area + * @param {ISpace[]} spaceResults - The space results to add to + * @returns {ISpace[]} The updated space results + * @todo This function will be deprecated once we have a better way of handling technical and communication areas + */ + #addTechnicalAndCommunicationArea (spaceResults: ISpace[], area: number): ISpace[] { + const spaceResult: ISpaceResult = { + areaExclCompensation: area, + adjustedAreaInclCompensation: area, + adjustedAreaInclCompensationWithAdjustmentAndCompensation: area, + } + spaceResults.push(this.#addCustomSpace('communication area', spaceResult)) + spaceResults.push(this.#addCustomSpace('technical area', spaceResult)) + return spaceResults + } + + /** + * This method computes the technical and communication areas + * @param {number} utilityFloorSpace - The utility floor space + * @returns {object} + */ + #computeTechnicalAndCommunicationArea (utilityFloorSpace: number): {technicalArea: number, communicationArea: number} { + const pct = ((utilityFloorSpace / 0.74) - utilityFloorSpace) / (utilityFloorSpace * 2) + const technicalArea = utilityFloorSpace * pct + const communicationArea = utilityFloorSpace * pct + return { + technicalArea, + communicationArea + } + } + + /** + * Calculate the corridors and inner walls of the top level spaces + * @param {ISpace[]} spaceResults - The space results to calculate + * @returns {object} + */ + #computeUtilityAndInnerWalls (spaceResults: ISpace[]): {utilityFloorSpace: number, innerwallsAreaSum: number, updatedSpaceResults: ISpace[]} { + let utilityFloorSpace: number = this.totalAdjustedAreaInclCompensationWithAdjustmentAndCompensation // utilityFloorSpace is the total adjusted area incl. compensation and adjustment + corridors and inner walls + let innerwallsAreaSum: number = 0 + for (const space of spaceResults) { + if (space.shouldCalculateCorridor) { + const corridorArea = this.#calculateCorridorArea(space.result.adjustedAreaInclCompensation!) + const corridorResult: ISpaceResult = { + areaExclCompensation: corridorArea, + adjustedAreaInclCompensation: corridorArea, + adjustedAreaInclCompensationWithAdjustmentAndCompensation: corridorArea, + } + space.spaces?.push(this.#addCustomSpace(`corridor – ${space.name}`, corridorResult)) + utilityFloorSpace += corridorArea + // Update top level space results + space.result.netArea = space.result.adjustedAreaInclCompensationWithAdjustmentAndCompensation! + corridorArea + } + if (space.shouldCalculateInnerwalls) { + const innerwallsArea = this.#calculateInnerwallArea(space.result.areaExclCompensation!) + const innerwallsResult: ISpaceResult = { + areaExclCompensation: innerwallsArea, + adjustedAreaInclCompensation: innerwallsArea, + adjustedAreaInclCompensationWithAdjustmentAndCompensation: innerwallsArea, + } + space.spaces?.push(this.#addCustomSpace(`inner wall – ${space.name}`, innerwallsResult)) + utilityFloorSpace += innerwallsArea + innerwallsAreaSum += innerwallsArea + // Update top level space results + space.result.utilityFloorSpace = space.result.netArea! + innerwallsArea + } + } + return { + utilityFloorSpace, + innerwallsAreaSum, + updatedSpaceResults: spaceResults + } + } + + /** + * This method calculates the sum results for spaces + * @param {ISpace} space - The space to calculate + * @returns {ISpaceResult} + */ + #sumResults(space: ISpace): ISpaceResult { + // Sum up the results of the children, if they exist + let childSum_areaExclCompensation = 0 + let childSum_adjustedAreaInclCompensation = 0 + let childSum_notAdjustedAddonArea = 0 + let childSum_adjustedAreaInclCompensationWithAdjustmentAndCompensation = 0 + + if (space.spaces) { + for (const child of space.spaces) { + const r = this.#sumResults(child) + childSum_areaExclCompensation += r.areaExclCompensation || 0 + childSum_adjustedAreaInclCompensation += r.adjustedAreaInclCompensation || 0 + childSum_notAdjustedAddonArea += r.notAdjustedAddonArea || 0 + childSum_adjustedAreaInclCompensationWithAdjustmentAndCompensation += r.adjustedAreaInclCompensationWithAdjustmentAndCompensation || 0 + } + } + + // Use the sum if we don't already have a result + space.result.areaExclCompensation = space.result.areaExclCompensation || childSum_areaExclCompensation + space.result.adjustedAreaInclCompensation = space.result.adjustedAreaInclCompensation || childSum_adjustedAreaInclCompensation + space.result.notAdjustedAddonArea = space.result.notAdjustedAddonArea || childSum_notAdjustedAddonArea + space.result.adjustedAreaInclCompensationWithAdjustmentAndCompensation = space.result.adjustedAreaInclCompensationWithAdjustmentAndCompensation || childSum_adjustedAreaInclCompensationWithAdjustmentAndCompensation + + // Return the total sum + return { + areaExclCompensation: space.result.areaExclCompensation, + adjustedAreaInclCompensation: space.result.adjustedAreaInclCompensation, + notAdjustedAddonArea: space.result.notAdjustedAddonArea, + adjustedAreaInclCompensationWithAdjustmentAndCompensation: space.result.adjustedAreaInclCompensationWithAdjustmentAndCompensation + } + } + + /** + * Gets dimensioned attendance. We use a dummy because it's not space dependent. + * @returns {number} + */ + #dimensionedAttendance (): number { + return new Dummy(this.variables, this.config).dimensionedAttendance() + } + + /** + * Loop through all spaces and calculate the first run + * @param {ISpace[]} spaces + * @returns {ISpace[]} + */ + #processSpacesFirst(spaces: ISpace[]): ISpace[] { + for (const space of spaces) { + // If this space has nested spaces, process those + if (space.spaces) { + this.#processSpacesFirst(space.spaces) + } else { + space.result = this.#calculateSpaceFirstRun(space) + } + } + return spaces + } + + /** + * Loop through all spaces and calculate the second run + * @param {ISpace[]} spaces + * @returns {ISpace[]} + */ + #processSpacesSecond(spaces: ISpace[]): ISpace[] { + for (const space of spaces) { + // If this space has nested spaces, process those + if (space.spaces) { + this.#processSpacesSecond(space.spaces) + } else { + space.result = { + ... space.result, + ... this.#calculateSpaceSecondRun(space) + } + this.allEmployeesPerWorkplaceTypeUnadjusted.push(space.result.employeesPerWorkplaceTypeUnadjusted || 0) + } + } + return spaces + } + + /** + * Loop through all spaces and calculate the third run + * @param {ISpace[]} spaces + * @returns {ISpace[]} + */ + #processSpacesThird(spaces: ISpace[]): ISpace[] { + for (const space of spaces) { + // If this space has nested spaces, process those + if (space.spaces) { + this.#processSpacesThird(space.spaces) + } else { + space.result = { + ... space.result, + ... this.#calculateSpaceThirdRun(space) + } + } + } + return spaces + } + + /** + * Loop through all spaces and calculate the fourth run + * @param {ISpace[]} spaces + * @returns {ISpace[]} + */ + #processSpacesFourth(spaces: ISpace[]): ISpace[] { + for (const space of spaces) { + // If this space has nested spaces, process those + if (space.spaces) { + this.#processSpacesFourth(space.spaces) + } else { + // Calculate the space + space.result = { + ... space.result, + ... this.#calculateSpaceFourthRun(space) + } + } + } + return spaces + } + + /** + * Calculates a space based on the first run + * @param {ISpace} space - The space to calculate + * @returns {ISpaceResult} The calculated result object + */ + #calculateSpaceFirstRun (space: ISpace): ISpaceResult { + const Space = getSpace(space.name) + const spaceCalculation: ISpaceCalculation = new Space(space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + return { + areaExclCompensation: spaceCalculation.calculateAreaExclCompensation(), + employeesPerWorkplaceTypeUnadjusted: spaceCalculation.calculateEmployeesPerWorkplaceTypeUnadjusted(), + } + } + + /** + * Calculates a space the second time + * @param {ISpace} space - The space to calculate + * @returns {ISpaceResult} The calculated space + */ + #calculateSpaceSecondRun (space: ISpace): ISpaceResult { + const Space = getSpace(space.name) + const spaceCalculation: ISpaceCalculation = new Space(space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + return { + notAdjustedAddonArea: spaceCalculation.calculateNotAdjustedAddonArea(this.totalWorkplaceArea, this.totalCompensationArea), + } + } + + /** + * Calculates a space the third time + * @param {ISpace} space - The space to calculate + * @returns {ISpaceResult} The calculated space + */ + #calculateSpaceThirdRun (space: ISpace): ISpaceResult { + const Space = getSpace(space.name) + const spaceCalculation: ISpaceCalculation = new Space(space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + return { + notAdjustedAddonPart: spaceCalculation.calculateNotAdjustedAddonPart(this.totalWorkplaceArea, this.totalCompensationArea), + adjustedAddonArea: spaceCalculation.calculateAdjustedAddonArea(this.totalWorkplaceArea, this.totalCompensationArea, this.totalUnadjustedArea), + adjustedAreaInclCompensation: spaceCalculation.calculateAdjustedAreaInclCompensation(this.totalWorkplaceArea, this.totalCompensationArea, this.totalUnadjustedArea), + employeesPerWorkplaceTypeAdjusted: spaceCalculation.calculateEmployeesPerWorkplaceTypeAdjusted(this.totalEmployeesPerWorkplaceTypeUnadjusted, this.allEmployeesPerWorkplaceTypeUnadjusted), + } + } + + /** + * Calculates a space the fourth time + * @param {ISpace} space - The space to calculate + * @returns {ISpaceResult} The calculated space + */ + #calculateSpaceFourthRun (space: ISpace): ISpaceResult { + const Space = getSpace(space.name) + const spaceCalculation: ISpaceCalculation = new Space(space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + return { + adjustedAreaInclCompensationWithAdjustmentAndCompensation: spaceCalculation.calculateAdjustedAreaInclCompensationWithAdjustmentAndCompensation(), + numberOfRooms: spaceCalculation.calculateNumberOfRooms(), + numberOfSeats: spaceCalculation.calculateNumberOfSeats(), + } + } + + /** + * Calculates the total work place area for all spaces + * @param {ISpace[]} spaces - The spaces to calculate + * @returns {number} The total work place area + */ + #calculateTotalWorkplaceArea (spaces: ISpace[]): number { + for (const space of spaces) { + // If this space has nested spaces, process those + if (space.spaces) { + this.totalWorkplaceArea = this.#calculateTotalWorkplaceArea(space.spaces) + } else { + // Calculate the total area + if (space.constants.countsTowardsWorkspaceArea) { + this.totalWorkplaceArea += space.result.areaExclCompensation ?? 0 + } + } + } + return this.totalWorkplaceArea + } + + /** + * Calculates the total area that needs compensation + * @param {ISpace[]} spaces - The spaces to calculate + * @returns {number} The total work place area + */ + #calculateTotalCompensationArea (spaces: ISpace[]): number { + for (const space of spaces) { + // If this space has nested spaces, process those + if (space.spaces) { + this.totalCompensationArea = this.#calculateTotalCompensationArea(space.spaces) + } else { + // Calculate the total area + if (space.constants.shouldCalculateCompensation) { + this.totalCompensationArea += space.result.areaExclCompensation ?? 0 + } + } + } + return this.totalCompensationArea + } + + /** + * Calculates the total area including compensation and adjustment + * @param {ISpace[]} spaces - The spaces to calculate + * @returns {number} The total work place area + */ + #calculateTotalAdjustedAreaInclCompensationAndAdjustment (spaces: ISpace[]): number { + for (const space of spaces) { + // If this space has nested spaces, process those + if (space.spaces) { + this.totalAdjustedAreaInclCompensationWithAdjustmentAndCompensation = this.#calculateTotalAdjustedAreaInclCompensationAndAdjustment(space.spaces) + } else { + // Calculate the total area + this.totalAdjustedAreaInclCompensationWithAdjustmentAndCompensation += space.result.adjustedAreaInclCompensationWithAdjustmentAndCompensation ?? 0 + } + } + return this.totalAdjustedAreaInclCompensationWithAdjustmentAndCompensation + } + + /** + * This method gets the un-adjusted area sum + * @param {ISpace[]} spaces - The spaces to calculate + * @returns {number} The total un-adjusted area + */ + #calculateTotalUnadjustedArea (spaces: ISpace[]): number { + for (const space of spaces) { + // If this space has nested spaces, process those + if (space.spaces) { + this.totalUnadjustedArea = this.#calculateTotalUnadjustedArea(space.spaces) + } else { + // Calculate the total area + if (space.constants.shouldCalculateCompensation) { + this.totalUnadjustedArea += space.result.notAdjustedAddonArea ?? 0 + } + } + } + return this.totalUnadjustedArea + } + + /** + * This method gets the employees per workplacetype (unadjusted) sum + * @param {ISpace[]} spaces - The spaces to calculate + * @returns {number} The total employees per workplace type (unadjusted) + */ + #calculateTotalEmployeesPerWorkspaceTypeUnadjusted (spaces: ISpace[]): number { + for (const space of spaces) { + // If this space has nested spaces, process those + if (space.spaces) { + this.totalEmployeesPerWorkplaceTypeUnadjusted = this.#calculateTotalEmployeesPerWorkspaceTypeUnadjusted(space.spaces) + } else { + this.totalEmployeesPerWorkplaceTypeUnadjusted += space.result.employeesPerWorkplaceTypeUnadjusted ?? 0 + } + } + return this.totalEmployeesPerWorkplaceTypeUnadjusted + } + + /** + * Calculates the area of corridors. + * @param {number} areaInclCompensation - The area including compensation + * @returns {number} + */ + #calculateCorridorArea (areaInclCompensation: number): number { + return areaInclCompensation * this.constants.corridorAddonShare + } + + /** + * Calculates the area of corridors. + * @param {number} areaExclCompensation - The area excluding compensation + * @returns {number} + */ + #calculateInnerwallArea (areaExclCompensation: number): number { + return areaExclCompensation * this.constants.innerwallsAddonShare + } +} diff --git a/src/calculations/interfaces/calculationresult.ts b/src/calculations/interfaces/calculationresult.ts new file mode 100644 index 0000000..812fde3 --- /dev/null +++ b/src/calculations/interfaces/calculationresult.ts @@ -0,0 +1,26 @@ +import { ISpace } from './space' + +export interface ICalculationResult { + /** + * Object containing various total values. + */ + totals: { + unadjustedArea: number // Total amount of unadjusted area + workplaceArea: number // Total area dedicated to workplaces + compensationArea: number // Total area used for compensation calculation + employeesPerWorkplaceTypeUnadjusted: number // Total number of employees per unadjusted workplace type + adjustedAreaInclCompensationWithAdjustmentAndCompensation: number // Total adjusted area including compensation with adjustment and compensation + netArea: number // Total net area + utilityFloorSpace: number // Total utility floor space + grossArea: number // Total gross area + utilityFloorSpacePerEmployee: number // Utility floor space per employee + netAreaPerEmployee: number // Net area per employee + grossAreaPerEmployee: number // Gross area per employee + grossAreaPerDimensionedAttendance: number // Gross area per dimensioned attendance + grossNetFactor: number // Gross net factor + } + /** + * Object containing all spaces and detailed calculation results + */ + spaces: ISpace[] +} diff --git a/src/calculations/interfaces/config.ts b/src/calculations/interfaces/config.ts new file mode 100644 index 0000000..9c3ca85 --- /dev/null +++ b/src/calculations/interfaces/config.ts @@ -0,0 +1,7 @@ +import { IConstant } from './constant' +import { ISpace } from './space' + +export interface IConfig { + constants: IConstant // The constants used in the calculations + spaces: ISpace[] // The array of spaces to be calculated +} diff --git a/src/calculations/interfaces/constant.ts b/src/calculations/interfaces/constant.ts new file mode 100644 index 0000000..a6185ce --- /dev/null +++ b/src/calculations/interfaces/constant.ts @@ -0,0 +1,5 @@ +export interface IConstant { + governmentMinimumSquaremetersPerWorkSpace: number // Minimum required square meters per workspace by regulation + corridorAddonShare: number // Share of corridor area + innerwallsAddonShare: number // Share of inner walls area +} diff --git a/src/calculations/interfaces/space.ts b/src/calculations/interfaces/space.ts new file mode 100644 index 0000000..ed567ff --- /dev/null +++ b/src/calculations/interfaces/space.ts @@ -0,0 +1,11 @@ +import { ISpaceConstant } from './space_constant' +import { ISpaceResult } from './space_result' + +export interface ISpace { + name: string, // The name of the space + constants: ISpaceConstant // The constants specific to this space + spaces?: ISpace[] // Optional nested spaces within this space + result: ISpaceResult // The result of the calculations for this space + shouldCalculateCorridor?: boolean // Whether to calculate the corridor area needed for this space + shouldCalculateInnerwalls?: boolean // Whether to calculate the inner walls area needed for this space +} diff --git a/src/calculations/interfaces/space_calculation.ts b/src/calculations/interfaces/space_calculation.ts new file mode 100644 index 0000000..6ab15c8 --- /dev/null +++ b/src/calculations/interfaces/space_calculation.ts @@ -0,0 +1,13 @@ +export interface ISpaceCalculation { + calculateEmployeesPerWorkplaceTypeUnadjusted: () => number // Function to calculate the unadjusted number of employees per workplace type + calculateAreaExclCompensation: () => number // Function to calculate the area excluding compensation + calculateAdjustedAreaInclCompensation: (totalWorkplceArea: number, totalCompensationArea: number, totalUnadjustedArea: number) => number // Function to calculate the adjusted area including compensation + calculateNotAdjustedAddonArea: (totalWorkplaceArea: number, totalCompensationArea: number) => number // Function to calculate the not adjusted add-on area + calculateAdjustedAddonArea: (totalWorkplaceArea: number, totalCompensationArea: number, totalUnadjustedArea: number) => number // Function to calculate the adjusted add-on area + calculateNotAdjustedAddonPart: (totalWorkplaceArea: number, totalCompensationArea: number) => number // Function to calculate the not adjusted add-on part + calculateEmployeesPerWorkplaceTypeAdjusted: (totalEmployeesPerWorkplaceTypeUnadjusted: number, allEmployeesPerWorkplaceTypeUnadjusted: number[]) => number // Function to calculate the adjusted number of employees per workplace type + calculateAdjustedAreaInclCompensationWithAdjustmentAndCompensation: () => number // Function to calculate the adjusted area including compensation with adjustment and compensation + dimensionedAttendance: () => number // Function to calculate the dimensioned attendance + calculateNumberOfRooms: () => number // Function to calculate the number of rooms, if applicable + calculateNumberOfSeats: () => number // Function to calculate the number of seats, if applicable +} diff --git a/src/calculations/interfaces/space_constant.ts b/src/calculations/interfaces/space_constant.ts new file mode 100644 index 0000000..5d6fbf9 --- /dev/null +++ b/src/calculations/interfaces/space_constant.ts @@ -0,0 +1,17 @@ +export interface ISpaceConstant { + seatPersonRoom?: string // Optional, defines the type of space: seat, person or room + shouldCalculateNumberOfRooms?: boolean // Whether to calculate the number of rooms + shouldCalculateNumberOfSeats?: boolean // Whether to calculate the number of seats + areaPerRole: number // The area required per role + personsPerType: number // The number of persons per type of space + minimumPersons?: number // The minimum number of persons for this space + minimumSquareMeters?: number // The minimum square meters required for this space + shouldCalculateCompensation: boolean // Whether to calculate compensation for this space + countsTowardsWorkspaceArea?: boolean // Whether this space counts towards the total workspace area + seatAreaPerSpot?: number // The seat area per spot + requiresAdditionalStorage?: boolean // Whether this space requires additional storage + adhereToGovernmentMinimum: boolean // Whether to adhere to the government minimum for this space + surchargeInternalCorridorShare?: number // The surcharge for the internal corridor share + areaPerPersonExcludingCorridor?: number // The area per person excluding the corridor + unitsPerPerson?: number // The units per person for this space +} diff --git a/src/calculations/interfaces/space_result.ts b/src/calculations/interfaces/space_result.ts new file mode 100644 index 0000000..c7bf556 --- /dev/null +++ b/src/calculations/interfaces/space_result.ts @@ -0,0 +1,14 @@ +export interface ISpaceResult { + areaExclCompensation?: number // The area excluding compensation + notAdjustedAddonArea?: number // The not adjusted add-on area + notAdjustedAddonPart?: number // The not adjusted add-on part + adjustedAddonArea?: number // The adjusted add-on area + adjustedAreaInclCompensation?: number // The adjusted area including compensation + adjustedAreaInclCompensationWithAdjustmentAndCompensation?: number // The adjusted area including compensation with adjustment and compensation + employeesPerWorkplaceTypeUnadjusted?: number // The unadjusted number of employees per workplace type + employeesPerWorkplaceTypeAdjusted?: number // The adjusted number of employees per workplace type + netArea?: number // The net area of the space + utilityFloorSpace?: number // The utility floor space of the space + numberOfRooms?: number // The number of rooms, if applicable + numberOfSeats?: number // The number of seats, if applicable +} diff --git a/src/calculations/interfaces/variable.ts b/src/calculations/interfaces/variable.ts new file mode 100644 index 0000000..4b1adec --- /dev/null +++ b/src/calculations/interfaces/variable.ts @@ -0,0 +1,32 @@ +export interface IVariable { + accessToCanteen: boolean // Whether there is a need for access to a canteen + accessToAuditorium: boolean // Whether there is a need for access to an auditorium + seatsInAuditorium: number // The number of seats in the auditorium + shareOfEmployeesInAuditorium: number // The percentage of employees in the auditorium + accessToCourseSpace: boolean // Whether there is a need for access to a course space + accessToGym: boolean // Whether there is a need for access to a gym + accessToCellOffice: boolean // Whether there is a need for access to a cell office + accessToCoworking: boolean // Whether there is a need for access to a coworking space + accessToReception: boolean // Whether there is a need for access to a reception + specialAreaOffice: number // The area of the special office + specialAreaShared: number // The area of the shared space + specialAreaCommon: number // The area of the common space + accessToExercise: boolean // Whether there is a need for access to an exercise area + coworkingShare: number // The percentage of coworking space + touchdownShare: number // The percentage of touchdown space + dockinShare: number // The percentage of dock-in space + cellOfficeShare: number // The percentage of cell office space + landscapeShare: number // The percentage of landscape space + numberOfEmployees: number // The total number of employees + concurrencyAttendanceShare: number // The percentage of concurrent attendance + peakConcurrencyAttendanceShare: number // The peak percentage of concurrent attendance + overCapacityShare: number // The percentage of overcapacity + homeOfficeAverageShare: number // The average percentage of home office + projectroomShare: number // The percentage of project room space + focusroomShare: number // The percentage of focus room space + quietzoneShare: number // The percentage of quiet zone space + miniMeetingroomShare: number // The percentage of mini meeting room space + smallMeetingroomShare: number // The percentage of small meeting room space + mediumMeetingroomShare: number // The percentage of medium meeting room space + largeMeetingroomShare: number // The percentage of large meeting room space +} diff --git a/src/calculations/spaces/common area/auditorium.ts b/src/calculations/spaces/common area/auditorium.ts new file mode 100644 index 0000000..8c4df0a --- /dev/null +++ b/src/calculations/spaces/common area/auditorium.ts @@ -0,0 +1,14 @@ +import MainSpace from '../main_space_class' + +export default class Auditorium extends MainSpace { + /** + * Calculates the area of the auditorum. 0 if there is no canteen. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + if (this.variables.accessToAuditorium) { + return this.spaceConstants.areaPerRole / this.spaceConstants.personsPerType * this.variables.seatsInAuditorium + } + return 0 + } +} diff --git a/src/calculations/spaces/common area/canteen.ts b/src/calculations/spaces/common area/canteen.ts new file mode 100644 index 0000000..0180ebd --- /dev/null +++ b/src/calculations/spaces/common area/canteen.ts @@ -0,0 +1,14 @@ +import MainSpace from '../main_space_class' + +export default class Canteen extends MainSpace { + /** + * Calculates the area of the canteen. 0 if there is no canteen. + * @returns + */ + calculateAreaExclCompensation = (): number => { + if (this.variables.accessToCanteen) { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() + this.addedPeakArea() + } + return 0 + } +} diff --git a/src/calculations/spaces/common area/cleaning.ts b/src/calculations/spaces/common area/cleaning.ts new file mode 100644 index 0000000..c0eb733 --- /dev/null +++ b/src/calculations/spaces/common area/cleaning.ts @@ -0,0 +1,11 @@ +import MainSpace from '../main_space_class' + +export default class CommonCleaning extends MainSpace { + /** + * Calculates the area of common cleaning. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() + } +} diff --git a/src/calculations/spaces/common area/course.ts b/src/calculations/spaces/common area/course.ts new file mode 100644 index 0000000..2afe965 --- /dev/null +++ b/src/calculations/spaces/common area/course.ts @@ -0,0 +1,14 @@ +import MainSpace from '../main_space_class' + +export default class Course extends MainSpace { + /** + * Calculates the area of course room. 0 if there is no need. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + if (this.variables.accessToCourseSpace) { + return this.dimensionedAttendance() * this.spaceConstants.areaPerRole / this.spaceConstants.personsPerType + } + return 0 + } +} diff --git a/src/calculations/spaces/common area/exerciseroom.ts b/src/calculations/spaces/common area/exerciseroom.ts new file mode 100644 index 0000000..1100c71 --- /dev/null +++ b/src/calculations/spaces/common area/exerciseroom.ts @@ -0,0 +1,21 @@ +import MainSpace from '../main_space_class' + +export default class CommonExerciseRoom extends MainSpace { + /** + * Area per person excluding corridor + */ + areaPerPersonExcludingCorridor = (): number => { + return this.spaceConstants.areaPerRole / this.spaceConstants.personsPerType * this.spaceConstants.unitsPerPerson! + } + + /** + * Calculates the area of the work toilet + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + if (this.variables.accessToExercise) { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() + } + return 0 + } +} diff --git a/src/calculations/spaces/common area/hwc.ts b/src/calculations/spaces/common area/hwc.ts new file mode 100644 index 0000000..51ee150 --- /dev/null +++ b/src/calculations/spaces/common area/hwc.ts @@ -0,0 +1,18 @@ +import MainSpace from '../main_space_class' + +export default class CommonHwc extends MainSpace { + /** + * Area per person excluding corridor + */ + areaPerPersonExcludingCorridor = (): number => { + return this.spaceConstants.areaPerRole / this.spaceConstants.personsPerType * this.spaceConstants.unitsPerPerson! + } + + /** + * Calculates the area of the common HWC + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() + } +} diff --git a/src/calculations/spaces/common area/kitchen.ts b/src/calculations/spaces/common area/kitchen.ts new file mode 100644 index 0000000..fc4ac2a --- /dev/null +++ b/src/calculations/spaces/common area/kitchen.ts @@ -0,0 +1,14 @@ +import MainSpace from '../main_space_class' + +export default class Kitchen extends MainSpace { + /** + * Calculates the area of the kitchen based on the number of employees and the area per role. It is 0 if there is no canteen. + * @returns {number} The area of the kitchen + */ + calculateAreaExclCompensation = (): number => { + if (this.variables.accessToCanteen) { + return this.spaceConstants.areaPerRole * this.variables.numberOfEmployees + } + return 0 + } +} diff --git a/src/calculations/spaces/common area/lobby.ts b/src/calculations/spaces/common area/lobby.ts new file mode 100644 index 0000000..97b547f --- /dev/null +++ b/src/calculations/spaces/common area/lobby.ts @@ -0,0 +1,26 @@ +import MainSpace from '../main_space_class' +import Auditorium from './auditorium' +import Course from './course' + +export default class Lobby extends MainSpace { + /** + * Calculates the lobby area. 0 if there is no need. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + if (this.variables.accessToAuditorium || this.variables.accessToCourseSpace) { + // Get the area of the auditorium and the course space + const auditorium = new Auditorium(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const course = new Course(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const areaPerPerson = this.spaceConstants.areaPerRole / this.spaceConstants.personsPerType + + const x = auditorium.calculateAreaExclCompensation() / areaPerPerson + const y = course.calculateAreaExclCompensation() / this.spaceConstants.areaPerRole * (this.spaceConstants.seatAreaPerSpot ? this.spaceConstants.seatAreaPerSpot : 0) + + // Take the max of the two + const max = Math.max(x, y) + return max * areaPerPerson + this.addedPeakArea() + } + return 0 + } +} diff --git a/src/calculations/spaces/common area/reception.ts b/src/calculations/spaces/common area/reception.ts new file mode 100644 index 0000000..49e7a88 --- /dev/null +++ b/src/calculations/spaces/common area/reception.ts @@ -0,0 +1,11 @@ +import MainSpace from '../main_space_class' + +export default class CommonReception extends MainSpace { + /** + * Calculates the area of common area reception. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() + } +} diff --git a/src/calculations/spaces/common area/special.ts b/src/calculations/spaces/common area/special.ts new file mode 100644 index 0000000..83aebb9 --- /dev/null +++ b/src/calculations/spaces/common area/special.ts @@ -0,0 +1,11 @@ +import MainSpace from '../main_space_class' + +export default class CommonSpecial extends MainSpace { + /** + * Calculates the area of the special rooms. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return this.variables.specialAreaCommon + } +} diff --git a/src/calculations/spaces/common area/stockarchive.ts b/src/calculations/spaces/common area/stockarchive.ts new file mode 100644 index 0000000..924c560 --- /dev/null +++ b/src/calculations/spaces/common area/stockarchive.ts @@ -0,0 +1,11 @@ +import MainSpace from '../main_space_class' + +export default class CommonStockArchive extends MainSpace { + /** + * Calculates the area of stock/archive. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() + } +} diff --git a/src/calculations/spaces/common area/toilet.ts b/src/calculations/spaces/common area/toilet.ts new file mode 100644 index 0000000..b6d1b2c --- /dev/null +++ b/src/calculations/spaces/common area/toilet.ts @@ -0,0 +1,18 @@ +import MainSpace from '../main_space_class' + +export default class CommonToilet extends MainSpace { + /** + * Area per person excluding corridor + */ + areaPerPersonExcludingCorridor = (): number => { + return this.spaceConstants.areaPerRole / this.spaceConstants.personsPerType * this.spaceConstants.unitsPerPerson! + } + + /** + * Calculates the area of the work toilet + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() + } +} diff --git a/src/calculations/spaces/common area/waitingzone.ts b/src/calculations/spaces/common area/waitingzone.ts new file mode 100644 index 0000000..140ddcb --- /dev/null +++ b/src/calculations/spaces/common area/waitingzone.ts @@ -0,0 +1,11 @@ +import MainSpace from '../main_space_class' + +export default class CommonWaitingZone extends MainSpace { + /** + * Calculates the area of common waiting zone. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() + } +} diff --git a/src/calculations/spaces/common area/wardrobe.ts b/src/calculations/spaces/common area/wardrobe.ts new file mode 100644 index 0000000..1f2adbc --- /dev/null +++ b/src/calculations/spaces/common area/wardrobe.ts @@ -0,0 +1,11 @@ +import MainSpace from '../main_space_class' + +export default class CommonWardrobe extends MainSpace { + /** + * Calculates the area of the common wardrobe + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() + } +} diff --git a/src/calculations/spaces/dummy.ts b/src/calculations/spaces/dummy.ts new file mode 100644 index 0000000..c2d2240 --- /dev/null +++ b/src/calculations/spaces/dummy.ts @@ -0,0 +1,32 @@ +import MainSpace from './main_space_class' +import { ISpace } from '../interfaces/space' +import { IConfig } from '../interfaces/config' +import { IVariable } from '../interfaces/variable' + +export default class Dummy extends MainSpace { + /** + * Constructor for the dummy space class + */ + constructor(variables: IVariable, config: IConfig) { + const space: ISpace = { + name: "dummy", + result: {}, + constants: { + seatPersonRoom: "person", + areaPerRole: 0, + personsPerType: 1, + minimumPersons: 1, + minimumSquareMeters: 0, + shouldCalculateCompensation: false, + countsTowardsWorkspaceArea: false, + adhereToGovernmentMinimum: false + } + } + super(space, variables, config) + this.name = this.space.name + this.space = space + this.spaceConstants = space.constants + this.variables = variables + this.config = config + } +} diff --git a/src/calculations/spaces/index.ts b/src/calculations/spaces/index.ts new file mode 100644 index 0000000..6a2e2f6 --- /dev/null +++ b/src/calculations/spaces/index.ts @@ -0,0 +1,115 @@ +import Kitchen from './common area/kitchen' +import Canteen from './common area/canteen' +import Auditorium from './common area/auditorium' +import Course from './common area/course' +import Lobby from './common area/lobby' +import WorkTouchdown from './work related area/touchdown' +import SharedTouchdown from './shared area/touchdown' +import WorkDockin from './work related area/dockin' +import SharedDockin from './shared area/dockin' +import CellOffice from './work related area/celloffice' +import Landscape from './work related area/landscape' +import Projectroom from './work related area/projectroom' +import Focusroom from './work related area/focusroom' +import Quietzone from './work related area/quietzone' +import CoffeeStation from './work related area/coffeestation' +import MiniKitchenLounge from './work related area/minikitchenlounge' +import SharedSmallMeetingroom from './shared area/meetingroom_small' +import SharedMediumMeetingroom from './shared area/meetingroom_medium' +import SharedLargeMeetingroom from './shared area/meetingroom_large' +import WorkMiniMeetingroom from './work related area/meetingroom_mini' +import WorkSmallMeetingroom from './work related area/meetingroom_small' +import WorkMediumMeetingroom from './work related area/meetingroom_medium' +import WorkLargeMeetingroom from './work related area/meetingroom_large' +import SharedMultiroom from './shared area/multiroom' +import WorkMultiroom from './work related area/multiroom' +import WorkReception from './work related area/reception' +import SharedReception from './shared area/reception' +import WorkWaitingZone from './work related area/waitingzone' +import SharedWaitingZone from './shared area/waitingzone' +import CopyArchive from './work related area/copyarchive' +import Cleaning from './work related area/cleaning' +import WorkPersonalStorage from './work related area/personalstorage' +import SharedPersonalStorage from './shared area/personalstorage' +import WorkWardrobe from './work related area/wardrobe' +import SharedWardrobe from './shared area/wardrobe' +import WorkToilet from './work related area/toilet' +import SharedToilet from './shared area/toilet' +import WorkHwc from './work related area/hwc' +import SharedHwc from './shared area/hwc' +import WorkSpecial from './work related area/special' +import CommonReception from './common area/reception' +import CommonWaitingZone from './common area/waitingzone' +import CommonStockArchive from './common area/stockarchive' +import CommonCleaning from './common area/cleaning' +import CommonToilet from './common area/toilet' +import CommonHwc from './common area/hwc' +import CommonWardrobe from './common area/wardrobe' +import CommonExerciseRoom from './common area/exerciseroom' +import SharedSpecial from './shared area/special' +import CommonSpecial from './common area/special' +import SharedProjectroom from './shared area/projectroom' +import SharedLounge from './shared area/lounge' + +/** + * This function returns the correct space class based on the space name. + * @param {string} spaceName - The name of the space + * @returns {any} + */ +const getSpace = (spaceName: string): any => { // eslint-disable-line @typescript-eslint/no-explicit-any + if (spaceName === 'kitchen') return Kitchen + else if (spaceName === 'canteen') return Canteen + else if (spaceName === 'auditorium') return Auditorium + else if (spaceName === 'course') return Course + else if (spaceName === 'lobby') return Lobby + else if (spaceName === 'touchdown – work related area') return WorkTouchdown + else if (spaceName === 'touchdown – shared area') return SharedTouchdown + else if (spaceName === 'dock in – work related area') return WorkDockin + else if (spaceName === 'dock in – shared area') return SharedDockin + else if (spaceName === 'cell office') return CellOffice + else if (spaceName === 'landscape') return Landscape + else if (spaceName === 'project room') return Projectroom + else if (spaceName === 'focus room') return Focusroom + else if (spaceName === 'quiet zone') return Quietzone + else if (spaceName === 'coffee station') return CoffeeStation + else if (spaceName === 'mini kitchen lounge') return MiniKitchenLounge + else if (spaceName === 'small meeting room – shared area') return SharedSmallMeetingroom + else if (spaceName === 'medium meeting room – shared area') return SharedMediumMeetingroom + else if (spaceName === 'large meeting room – shared area') return SharedLargeMeetingroom + else if (spaceName === 'mini meeting room – work related area') return WorkMiniMeetingroom + else if (spaceName === 'small meeting room – work related area') return WorkSmallMeetingroom + else if (spaceName === 'medium meeting room – work related area') return WorkMediumMeetingroom + else if (spaceName === 'large meeting room – work related area') return WorkLargeMeetingroom + else if (spaceName === 'multi room – shared area') return SharedMultiroom + else if (spaceName === 'multi room – work related area') return WorkMultiroom + else if (spaceName === 'reception – work related area') return WorkReception + else if (spaceName === 'reception – shared area') return SharedReception + else if (spaceName === 'waiting zone – work related area') return WorkWaitingZone + else if (spaceName === 'waiting zone – shared area') return SharedWaitingZone + else if (spaceName === 'copy archive') return CopyArchive + else if (spaceName === 'cleaning') return Cleaning + else if (spaceName === 'personal storage – work related area') return WorkPersonalStorage + else if (spaceName === 'personal storage – shared area') return SharedPersonalStorage + else if (spaceName === 'wardrobe – work related area') return WorkWardrobe + else if (spaceName === 'wardrobe – shared area') return SharedWardrobe + else if (spaceName === 'toilet – work related area') return WorkToilet + else if (spaceName === 'toilet – shared area') return SharedToilet + else if (spaceName === 'hwc – work related area') return WorkHwc + else if (spaceName === 'hwc – shared area') return SharedHwc + else if (spaceName === 'special – work related area') return WorkSpecial + else if (spaceName === 'reception – common area') return CommonReception + else if (spaceName === 'waiting zone – common area') return CommonWaitingZone + else if (spaceName === 'stock archive – common area') return CommonStockArchive + else if (spaceName === 'cleaning – common area') return CommonCleaning + else if (spaceName === 'toilet – common area') return CommonToilet + else if (spaceName === 'hwc – common area') return CommonHwc + else if (spaceName === 'wardrobe – common area') return CommonWardrobe + else if (spaceName === 'exercise room – common area') return CommonExerciseRoom + else if (spaceName === 'special – shared area') return SharedSpecial + else if (spaceName === 'special – common area') return CommonSpecial + else if (spaceName === 'project room – shared area') return SharedProjectroom + else if (spaceName === 'lounge area – shared area') return SharedLounge + else throw new Error(`Space ${spaceName} not found`) +} + +export default getSpace diff --git a/src/calculations/spaces/main_space_class.ts b/src/calculations/spaces/main_space_class.ts new file mode 100644 index 0000000..4e73ee5 --- /dev/null +++ b/src/calculations/spaces/main_space_class.ts @@ -0,0 +1,279 @@ +import { ISpace } from '../interfaces/space' +import { IConfig } from '../interfaces/config' +import { ISpaceConstant } from '../interfaces/space_constant' +import { ISpaceCalculation } from '../interfaces/space_calculation' +import { IConstant } from '../interfaces/constant' +import { IVariable } from '../interfaces/variable' + +type TCustomSpaceConstants = {[key:string]:ISpaceConstant} + +/** + * This is the main class for the spaces. It contains shared methods for all spaces. + */ +export default class MainSpace implements ISpaceCalculation { + name: string + constants: IConstant + customConstants?: IConstant + space: ISpace + spaceConstants: ISpaceConstant + variables: IVariable + customSpaceConstants?: TCustomSpaceConstants + config: IConfig + + /** + * Constructor for the main space class. + * @param {Ispace} space - The space to calculate + * @param {IVariable} variables - The variables to use + * @param {IConfig} config - The config to use + * @param {TCustomSpaceConstants} [customSpaceConstants] - Custom space constants, optional + * @param {IConstant} [customConstants] - Custom constants, optional + */ + constructor(space: ISpace, variables: IVariable, config: IConfig, customSpaceConstants?: TCustomSpaceConstants, customConstants?: IConstant) { + this.name = space.name + this.space = space + this.spaceConstants = space.constants + this.variables = variables + this.config = config + this.customSpaceConstants = customSpaceConstants + // Merge custom constants with the default constants + this.constants = { + ...config.constants, + ...customConstants + } + // Merge custom space constants with the default space constants + for (const spaceName in this.customSpaceConstants) { + if (this.name === spaceName) { + this.spaceConstants = { + ...space.constants, + ...this.customSpaceConstants[spaceName] + } + } + } + } + + /** + * Calculates the area per person for this space. + * @returns {number} + */ + areaPerPersonExcludingCorridor(): number { + return this.spaceConstants.areaPerRole / this.spaceConstants.personsPerType + } + + /** + * Peak attendance is the number of employees times the peak concurrency attendance share. + * @returns {number} + */ + #peakAttendance (): number { + return this.variables.numberOfEmployees * this.variables.peakConcurrencyAttendanceShare + } + + /** + * This method calculates the added peak area for this space. + * @returns {number} + */ + addedPeakArea (): number { + return this.#diffPeak() * this.areaPerPersonExcludingCorridor() + } + + /** + * This method gets number of person per workspace type + * @returns {number} + */ + personsPerType (): number { + return this.dimensionedAttendance() * this.sharePerWorkspaceType() + } + + /** + * This method gets number of person per workspace type, adjusted + * @todo Missing implementation + * @returns {number} + */ + personsPerTypeAdjusted (): number { + return 0 + } + + /** + * This method computes the share of this workspace type. + * @returns {number} + */ + sharePerWorkspaceType (): number { + return 0 + } + + /** + * This method calculates the dimensioned attendance for this space. + * @todo Write detailed description of this method + * @returns {number} + */ + dimensionedAttendance (): number { + return (this.variables.numberOfEmployees * this.variables.concurrencyAttendanceShare) + + (this.variables.numberOfEmployees * this.variables.concurrencyAttendanceShare * this.variables.overCapacityShare) - + (this.variables.numberOfEmployees * this.variables.homeOfficeAverageShare) + } + + /** + * This method calculates the difference between peak attendance and dimensioned attendance. + * @returns {number} + */ + #diffPeak (): number { + return this.#peakAttendance() - this.dimensionedAttendance() + } + + /** + * This method calculates the government required area for this space. + * @returns {number} + */ + #governmentRequiredArea (): number { + return this.dimensionedAttendance() * this.constants.governmentMinimumSquaremetersPerWorkSpace + } + + /** + * Calculate remainder area for space. + * @returns {number} + */ + #remainderArea (): number { + if (this.spaceConstants.seatPersonRoom === 'seat') { + return this.space.result.adjustedAreaInclCompensation! - (this.#areaPerSeatUnAdjusted() * this.calculateNumberOfSeats()) + } else if (this.spaceConstants.seatPersonRoom === 'room') { + return this.space.result.adjustedAreaInclCompensation! - (this.spaceConstants.minimumSquareMeters! * this.calculateNumberOfRooms()) + } + return 0 + } + + #areaPerSeatUnAdjusted (): number { + if (this.space.result.adjustedAreaInclCompensation! > 0 && this.calculateNumberOfSeats() > 0) { + return Math.round(this.space.result.adjustedAreaInclCompensation! / this.calculateNumberOfSeats() * 10) / 10 + } + return 0 + } + + /** + * Calculates number of rooms + * @returns {number} + */ + calculateNumberOfRooms (): number { + if (this.spaceConstants.shouldCalculateNumberOfRooms) { + return Math.ceil(this.space.result.adjustedAreaInclCompensation! / this.spaceConstants.minimumSquareMeters!) + } + return 0 + } + + /** + * Calculates number of seats. + * @returns {number} + */ + calculateNumberOfSeats (): number { + if (this.spaceConstants.shouldCalculateNumberOfSeats) { + return Math.round((this.space.result.employeesPerWorkplaceTypeAdjusted || 0) + this.#employeesPerSeatTypeInPeak()) + } + return 0 + } + + /** + * Calculates employees per seat type in peak as integer + * @returns {number} + */ + #employeesPerSeatTypeInPeak (): number { + return Math.round(this.#diffPeak() * this.sharePerWorkspaceType()) + } + + /** + * Calculates un-adjusted add on part. + * @param {number} totalWorkplaceArea - The total area of all workspaces already calculated + * @param {number} totalCompensationArea - The total area of all spaces requiring compensation + * @returns {number} + */ + calculateNotAdjustedAddonPart (totalWorkplaceArea: number, totalCompensationArea: number): number { + if (this.spaceConstants.shouldCalculateCompensation && totalWorkplaceArea < this.#governmentRequiredArea()) { + return (this.#governmentRequiredArea() / totalWorkplaceArea) * (this.space.result.areaExclCompensation! / totalCompensationArea) + } + return 0 + } + + /** + * Calculates un-adjusted add on area. + * @param {number} totalWorkplaceArea - The total area of all workspaces already calculated + * @param {number} totalCompensationArea - The total area of all spaces requiring compensation + * @returns {number} + */ + calculateNotAdjustedAddonArea (totalWorkplaceArea: number, totalCompensationArea: number): number { + if (this.spaceConstants.shouldCalculateCompensation && totalWorkplaceArea < this.#governmentRequiredArea()) { + return this.calculateNotAdjustedAddonPart(totalWorkplaceArea, totalCompensationArea) * this.space.result.areaExclCompensation! + } + return 0 + } + + /** + * Calculates the difference between the government required area and the total workplace area. + * @param {number} totalWorkplaceArea - The total area of all workspaces already calculated + * @returns {number} + */ + #differenceGovernnmentRequiredAreaWorkplaceArea (totalWorkplaceArea: number): number { + return this.#governmentRequiredArea() - totalWorkplaceArea + } + + /** + * Calculates employees per workplace type, unadjusted. + * @returns {number} + */ + calculateEmployeesPerWorkplaceTypeUnadjusted (): number { + return Math.round(this.dimensionedAttendance() * this.sharePerWorkspaceType()) + } + + /** + * Calculates employees per workplace type, adjusted + * @param {number} totalEmployeesPerWorkplaceTypeUnadjusted - The total number of employees per workplace type, unadjusted + */ + calculateEmployeesPerWorkplaceTypeAdjusted (totalEmployeesPerWorkplaceTypeUnadjusted: number, allEmployeesPerWorkplaceTypeUnadjusted: number[]): number { + if (totalEmployeesPerWorkplaceTypeUnadjusted > this.dimensionedAttendance() && this.calculateEmployeesPerWorkplaceTypeUnadjusted() === Math.max(...allEmployeesPerWorkplaceTypeUnadjusted)) { + return this.calculateEmployeesPerWorkplaceTypeUnadjusted() - (totalEmployeesPerWorkplaceTypeUnadjusted - this.dimensionedAttendance()) + } else if (totalEmployeesPerWorkplaceTypeUnadjusted < this.dimensionedAttendance() && this.calculateEmployeesPerWorkplaceTypeUnadjusted() === Math.max(...allEmployeesPerWorkplaceTypeUnadjusted)) { + return this.calculateEmployeesPerWorkplaceTypeUnadjusted() + (this.dimensionedAttendance() - totalEmployeesPerWorkplaceTypeUnadjusted) + } else { + return this.calculateEmployeesPerWorkplaceTypeUnadjusted() + } + } + + /** + * Calculates adjusted add on area. + * @param {number} totalWorkplaceArea - The total area of all workspaces already calculated + * @param {number} totalCompensationArea - The total area of all spaces requiring compensation + * @param {number} totalUnadjustedArea - The total unadjusted area + * @returns {number} + */ + calculateAdjustedAddonArea (totalWorkplaceArea: number, totalCompensationArea: number, totalUnadjustedArea: number): number { + if (this.spaceConstants.shouldCalculateCompensation && totalWorkplaceArea < this.#governmentRequiredArea()) { + return this.calculateNotAdjustedAddonArea(totalWorkplaceArea, totalCompensationArea) / totalUnadjustedArea * this.#differenceGovernnmentRequiredAreaWorkplaceArea(totalWorkplaceArea) + } + return 0 + } + + /** + * Default is returning the areaInclCompensation but is overridden in some spaces. + * @param {number} totalWorkplaceArea - The total area of all workspaces already calculated + * @param {number} totalCompensationArea - The total area of all spaces requiring compensation + * @param {number} totalUnadjustedArea - The total unadjusted area + * @returns {number} + */ + calculateAdjustedAreaInclCompensation(totalWorkplaceArea: number, totalCompensationArea: number, totalUnadjustedArea: number): number { + if (this.spaceConstants.shouldCalculateCompensation) { + return this.calculateAdjustedAddonArea(totalWorkplaceArea, totalCompensationArea, totalUnadjustedArea) + this.space.result.areaExclCompensation! + } + return this.space.result.areaExclCompensation! + } + + /** + * Calculates the adjusted area including adjustment and compensation. + */ + calculateAdjustedAreaInclCompensationWithAdjustmentAndCompensation (): number { + return this.space.result.adjustedAreaInclCompensation! - this.#remainderArea() + } + + /** + * Calculates the area excluding compensation. Not implemented in this class. + * @returns {number} + */ + calculateAreaExclCompensation (): number { + return 0 + } +} diff --git a/src/calculations/spaces/shared area/dockin.ts b/src/calculations/spaces/shared area/dockin.ts new file mode 100644 index 0000000..6f54fd5 --- /dev/null +++ b/src/calculations/spaces/shared area/dockin.ts @@ -0,0 +1,25 @@ +import MainSpace from '../main_space_class' + +export default class SharedDockin extends MainSpace { + /** + * This method computes the share of this workspace type + * @returns {number} + */ + sharePerWorkspaceType = (): number => { + if (this.variables.accessToCoworking) { + return this.variables.dockinShare * this.variables.coworkingShare + } + return 0 + } + + /** + * Calculates the area of shared dockin. 0 if there is no desire. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + if (this.variables.accessToCoworking) { + return this.personsPerType() * this.areaPerPersonExcludingCorridor() + this.addedPeakArea() + } + return 0 + } +} diff --git a/src/calculations/spaces/shared area/hwc.ts b/src/calculations/spaces/shared area/hwc.ts new file mode 100644 index 0000000..d3df86b --- /dev/null +++ b/src/calculations/spaces/shared area/hwc.ts @@ -0,0 +1,21 @@ +import MainSpace from '../main_space_class' + +export default class SharedHwc extends MainSpace { + /** + * Area per person excluding corridor + */ + areaPerPersonExcludingCorridor = (): number => { + return this.spaceConstants.areaPerRole / this.spaceConstants.personsPerType * this.spaceConstants.unitsPerPerson! + } + + /** + * Calculates the area of the shared hwc. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + if (this.variables.accessToCoworking) { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() * this.variables.coworkingShare + } + return 0 + } +} diff --git a/src/calculations/spaces/shared area/lounge.ts b/src/calculations/spaces/shared area/lounge.ts new file mode 100644 index 0000000..11cf4be --- /dev/null +++ b/src/calculations/spaces/shared area/lounge.ts @@ -0,0 +1,15 @@ +import MainSpace from '../main_space_class' + +export default class SharedLounge extends MainSpace { + /** + * Calculates the area of shared dockin. 0 if there is no desire. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + // SUM((AJ4*AR24*AQ24)+(AB24*AQ24)) + if (this.variables.accessToCoworking) { + return (this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() * this.sharePerWorkspaceType()) + (this.addedPeakArea() * this.sharePerWorkspaceType()) + } + return 0 + } +} diff --git a/src/calculations/spaces/shared area/meetingroom_large.ts b/src/calculations/spaces/shared area/meetingroom_large.ts new file mode 100644 index 0000000..bdbfe21 --- /dev/null +++ b/src/calculations/spaces/shared area/meetingroom_large.ts @@ -0,0 +1,35 @@ +import MainSpace from '../main_space_class' +import WorkDockin from './dockin' +import CellOffice from '../work related area/celloffice' +import Landscape from '../work related area/landscape' +import Projectroom from '../work related area/projectroom' +import Focusroom from '../work related area/focusroom' +import Quietzone from '../work related area/quietzone' + +export default class SharedLargeMeetingroom extends MainSpace { + /** + * Calculates the area per person for this space. + * @returns {number} + */ + areaPerPersonExcludingCorridor(): number { + return this.spaceConstants.areaPerRole / this.spaceConstants.personsPerType * this.variables.largeMeetingroomShare + } + + /** + * Calculates the area of the meeting room. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + if (this.variables.accessToCoworking) { + const workDockin = new WorkDockin(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const cellOffice = new CellOffice(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const landscape = new Landscape(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const projectroom = new Projectroom(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const focusroom = new Focusroom(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const quietzone = new Quietzone(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const addedPeakAreaSum = workDockin.calculateEmployeesPerWorkplaceTypeUnadjusted() + cellOffice.calculateEmployeesPerWorkplaceTypeUnadjusted() + landscape.calculateEmployeesPerWorkplaceTypeUnadjusted() + projectroom.calculateEmployeesPerWorkplaceTypeUnadjusted() + focusroom.calculateEmployeesPerWorkplaceTypeUnadjusted() + quietzone.calculateEmployeesPerWorkplaceTypeUnadjusted() + return addedPeakAreaSum * this.areaPerPersonExcludingCorridor() * this.variables.coworkingShare + this.addedPeakArea() + } + return 0 + } +} diff --git a/src/calculations/spaces/shared area/meetingroom_medium.ts b/src/calculations/spaces/shared area/meetingroom_medium.ts new file mode 100644 index 0000000..7e46c00 --- /dev/null +++ b/src/calculations/spaces/shared area/meetingroom_medium.ts @@ -0,0 +1,35 @@ +import MainSpace from '../main_space_class' +import WorkDockin from './dockin' +import CellOffice from '../work related area/celloffice' +import Landscape from '../work related area/landscape' +import Projectroom from '../work related area/projectroom' +import Focusroom from '../work related area/focusroom' +import Quietzone from '../work related area/quietzone' + +export default class SharedMediumMeetingroom extends MainSpace { + /** + * Calculates the area per person for this space. + * @returns {number} + */ + areaPerPersonExcludingCorridor(): number { + return this.spaceConstants.areaPerRole / this.spaceConstants.personsPerType * this.variables.mediumMeetingroomShare + } + + /** + * Calculates the area of the meeting room. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + if (this.variables.accessToCoworking) { + const workDockin = new WorkDockin(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const cellOffice = new CellOffice(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const landscape = new Landscape(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const projectroom = new Projectroom(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const focusroom = new Focusroom(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const quietzone = new Quietzone(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const addedPeakAreaSum = workDockin.calculateEmployeesPerWorkplaceTypeUnadjusted() + cellOffice.calculateEmployeesPerWorkplaceTypeUnadjusted() + landscape.calculateEmployeesPerWorkplaceTypeUnadjusted() + projectroom.calculateEmployeesPerWorkplaceTypeUnadjusted() + focusroom.calculateEmployeesPerWorkplaceTypeUnadjusted() + quietzone.calculateEmployeesPerWorkplaceTypeUnadjusted() + return addedPeakAreaSum * this.areaPerPersonExcludingCorridor() * this.variables.coworkingShare + this.addedPeakArea() + } + return 0 + } +} diff --git a/src/calculations/spaces/shared area/meetingroom_small.ts b/src/calculations/spaces/shared area/meetingroom_small.ts new file mode 100644 index 0000000..862ddf1 --- /dev/null +++ b/src/calculations/spaces/shared area/meetingroom_small.ts @@ -0,0 +1,35 @@ +import MainSpace from '../main_space_class' +import WorkDockin from './dockin' +import CellOffice from '../work related area/celloffice' +import Landscape from '../work related area/landscape' +import Projectroom from '../work related area/projectroom' +import Focusroom from '../work related area/focusroom' +import Quietzone from '../work related area/quietzone' + +export default class SharedSmallMeetingroom extends MainSpace { + /** + * Calculates the area per person for this space. + * @returns {number} + */ + areaPerPersonExcludingCorridor(): number { + return this.spaceConstants.areaPerRole / this.spaceConstants.personsPerType * this.variables.smallMeetingroomShare + } + + /** + * Calculates the area of the meeting room. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + if (this.variables.accessToCoworking) { + const workDockin = new WorkDockin(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const cellOffice = new CellOffice(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const landscape = new Landscape(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const projectroom = new Projectroom(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const focusroom = new Focusroom(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const quietzone = new Quietzone(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const addedPeakAreaSum = workDockin.calculateEmployeesPerWorkplaceTypeUnadjusted() + cellOffice.calculateEmployeesPerWorkplaceTypeUnadjusted() + landscape.calculateEmployeesPerWorkplaceTypeUnadjusted() + projectroom.calculateEmployeesPerWorkplaceTypeUnadjusted() + focusroom.calculateEmployeesPerWorkplaceTypeUnadjusted() + quietzone.calculateEmployeesPerWorkplaceTypeUnadjusted() + return addedPeakAreaSum * this.areaPerPersonExcludingCorridor() * this.variables.coworkingShare + this.addedPeakArea() + } + return 0 + } +} diff --git a/src/calculations/spaces/shared area/multiroom.ts b/src/calculations/spaces/shared area/multiroom.ts new file mode 100644 index 0000000..9023dde --- /dev/null +++ b/src/calculations/spaces/shared area/multiroom.ts @@ -0,0 +1,28 @@ +import MainSpace from '../main_space_class' +import WorkDockin from '../work related area/dockin' +import CellOffice from '../work related area/celloffice' +import Landscape from '../work related area/landscape' +import Projectroom from '../work related area/projectroom' +import Focusroom from '../work related area/focusroom' +import Quietzone from '../work related area/quietzone' + +export default class SharedMultiroom extends MainSpace { + /** + * Calculates the area of shared multiroom. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + if (this.variables.accessToCoworking) { + //=SUM((AP4:AP10)*AR26*AQ26)+(AB26) + const workDockin = new WorkDockin(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const cellOffice = new CellOffice(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const landscape = new Landscape(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const projectroom = new Projectroom(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const focusroom = new Focusroom(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const quietzone = new Quietzone(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const addedPeakAreaSum = workDockin.calculateEmployeesPerWorkplaceTypeUnadjusted() + cellOffice.calculateEmployeesPerWorkplaceTypeUnadjusted() + landscape.calculateEmployeesPerWorkplaceTypeUnadjusted() + projectroom.calculateEmployeesPerWorkplaceTypeUnadjusted() + focusroom.calculateEmployeesPerWorkplaceTypeUnadjusted() + quietzone.calculateEmployeesPerWorkplaceTypeUnadjusted() + return addedPeakAreaSum * this.areaPerPersonExcludingCorridor() * this.sharePerWorkspaceType() + this.addedPeakArea() + } + return 0 + } +} diff --git a/src/calculations/spaces/shared area/personalstorage.ts b/src/calculations/spaces/shared area/personalstorage.ts new file mode 100644 index 0000000..965ae59 --- /dev/null +++ b/src/calculations/spaces/shared area/personalstorage.ts @@ -0,0 +1,14 @@ +import MainSpace from '../main_space_class' + +export default class SharedPersonalStorage extends MainSpace { + /** + * Calculates the area of the personal storage + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + if (this.variables.accessToCoworking) { + return this.calculateEmployeesPerWorkplaceTypeUnadjusted() * this.sharePerWorkspaceType() * this.areaPerPersonExcludingCorridor() + } + return 0 + } +} diff --git a/src/calculations/spaces/shared area/projectroom.ts b/src/calculations/spaces/shared area/projectroom.ts new file mode 100644 index 0000000..1161f2f --- /dev/null +++ b/src/calculations/spaces/shared area/projectroom.ts @@ -0,0 +1,14 @@ +import MainSpace from '../main_space_class' + +export default class SharedProjectroom extends MainSpace { + /** + * Calculates the area of the shred project room. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + if (this.variables.accessToReception) { + return this.calculateEmployeesPerWorkplaceTypeUnadjusted() * this.areaPerPersonExcludingCorridor() + } + return 0 + } +} diff --git a/src/calculations/spaces/shared area/reception.ts b/src/calculations/spaces/shared area/reception.ts new file mode 100644 index 0000000..43b59a3 --- /dev/null +++ b/src/calculations/spaces/shared area/reception.ts @@ -0,0 +1,14 @@ +import MainSpace from '../main_space_class' + +export default class SharedReception extends MainSpace { + /** + * Calculates the area of shared reception. 0 if there is no desire. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + if (this.variables.accessToReception) { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() * this.variables.coworkingShare + } + return 0 + } +} diff --git a/src/calculations/spaces/shared area/special.ts b/src/calculations/spaces/shared area/special.ts new file mode 100644 index 0000000..c64ae50 --- /dev/null +++ b/src/calculations/spaces/shared area/special.ts @@ -0,0 +1,11 @@ +import MainSpace from '../main_space_class' + +export default class SharedSpecial extends MainSpace { + /** + * Calculates the area of the special rooms. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return this.variables.specialAreaShared + } +} diff --git a/src/calculations/spaces/shared area/toilet.ts b/src/calculations/spaces/shared area/toilet.ts new file mode 100644 index 0000000..910d22f --- /dev/null +++ b/src/calculations/spaces/shared area/toilet.ts @@ -0,0 +1,21 @@ +import MainSpace from '../main_space_class' + +export default class SharedToilet extends MainSpace { + /** + * Area per person excluding corridor + */ + areaPerPersonExcludingCorridor = (): number => { + return this.spaceConstants.areaPerRole / this.spaceConstants.personsPerType * this.spaceConstants.unitsPerPerson! + } + + /** + * Calculates the area of the shared toilet. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + if (this.variables.accessToCoworking) { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() * this.variables.coworkingShare + } + return 0 + } +} diff --git a/src/calculations/spaces/shared area/touchdown.ts b/src/calculations/spaces/shared area/touchdown.ts new file mode 100644 index 0000000..8298ac3 --- /dev/null +++ b/src/calculations/spaces/shared area/touchdown.ts @@ -0,0 +1,25 @@ +import MainSpace from '../main_space_class' + +export default class SharedTouchdown extends MainSpace { + /** + * This method computes the share of this workspace type + * @returns {number} + */ + sharePerWorkspaceType = (): number => { + if (this.variables.accessToCoworking) { + return this.variables.touchdownShare * this.variables.coworkingShare + } + return 0 + } + + /** + * Calculates the area of shared touchdown. 0 if there is no desire. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + if (this.variables.accessToCoworking) { + return this.personsPerType() * this.areaPerPersonExcludingCorridor() + this.addedPeakArea() + } + return 0 + } +} diff --git a/src/calculations/spaces/shared area/waitingzone.ts b/src/calculations/spaces/shared area/waitingzone.ts new file mode 100644 index 0000000..0a2dd5e --- /dev/null +++ b/src/calculations/spaces/shared area/waitingzone.ts @@ -0,0 +1,14 @@ +import MainSpace from '../main_space_class' + +export default class SharedWaitingZone extends MainSpace { + /** + * Calculates the area of shared wating zone in reception. 0 if there is no desire. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + if (this.variables.accessToReception) { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() * this.variables.coworkingShare + } + return 0 + } +} diff --git a/src/calculations/spaces/shared area/wardrobe.ts b/src/calculations/spaces/shared area/wardrobe.ts new file mode 100644 index 0000000..592474f --- /dev/null +++ b/src/calculations/spaces/shared area/wardrobe.ts @@ -0,0 +1,14 @@ +import MainSpace from '../main_space_class' + +export default class SharedWardrobe extends MainSpace { + /** + * Calculates the area of the wardrobe + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + if (this.variables.accessToCoworking) { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() * this.variables.coworkingShare + } + return 0 + } +} diff --git a/src/calculations/spaces/work related area/celloffice.ts b/src/calculations/spaces/work related area/celloffice.ts new file mode 100644 index 0000000..ea4ddbd --- /dev/null +++ b/src/calculations/spaces/work related area/celloffice.ts @@ -0,0 +1,22 @@ +import MainSpace from '../main_space_class' + +export default class CellOffice extends MainSpace { + /** + * This method computes the share of this workspace type + * @returns {number} + */ + sharePerWorkspaceType = (): number => { + if (this.variables.accessToCellOffice) { + return this.variables.cellOfficeShare + } + return 0 + } + + /** + * Calculates the area of the cell. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() * this.sharePerWorkspaceType() + } +} diff --git a/src/calculations/spaces/work related area/cleaning.ts b/src/calculations/spaces/work related area/cleaning.ts new file mode 100644 index 0000000..a7d7d54 --- /dev/null +++ b/src/calculations/spaces/work related area/cleaning.ts @@ -0,0 +1,11 @@ +import MainSpace from '../main_space_class' + +export default class Cleaning extends MainSpace { + /** + * Calculates the area of cleaning + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() + } +} diff --git a/src/calculations/spaces/work related area/coffeestation.ts b/src/calculations/spaces/work related area/coffeestation.ts new file mode 100644 index 0000000..35e0e15 --- /dev/null +++ b/src/calculations/spaces/work related area/coffeestation.ts @@ -0,0 +1,18 @@ +import MainSpace from '../main_space_class' + +export default class CoffeeStation extends MainSpace { + /** + * Area per person excluding corridor + */ + areaPerPersonExcludingCorridor = (): number => { + return this.spaceConstants.areaPerRole / this.spaceConstants.personsPerType * this.spaceConstants.unitsPerPerson! + } + + /** + * Calculates the area of the coffee station. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() + this.addedPeakArea() + } +} diff --git a/src/calculations/spaces/work related area/copyarchive.ts b/src/calculations/spaces/work related area/copyarchive.ts new file mode 100644 index 0000000..51562c4 --- /dev/null +++ b/src/calculations/spaces/work related area/copyarchive.ts @@ -0,0 +1,11 @@ +import MainSpace from '../main_space_class' + +export default class CopyArchive extends MainSpace { + /** + * Calculates the area of the copy/archive + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() + } +} diff --git a/src/calculations/spaces/work related area/dockin.ts b/src/calculations/spaces/work related area/dockin.ts new file mode 100644 index 0000000..ea839fa --- /dev/null +++ b/src/calculations/spaces/work related area/dockin.ts @@ -0,0 +1,22 @@ +import MainSpace from '../main_space_class' +import SharedDockin from '../shared area/dockin' + +export default class WorkDockin extends MainSpace { + /** + * This method computes the share of this workspace type + * @returns {number} + */ + sharePerWorkspaceType = (): number => { + // We need the shared dock in share for the calculation of the work dock in share + const sharedDockin = new SharedDockin(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + return this.variables.dockinShare - sharedDockin.sharePerWorkspaceType() + } + + /** + * Calculates the area of touchdown. 0 if there is no need. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return (this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor()*this.sharePerWorkspaceType())+(this.addedPeakArea()*this.sharePerWorkspaceType()) + } +} diff --git a/src/calculations/spaces/work related area/focusroom.ts b/src/calculations/spaces/work related area/focusroom.ts new file mode 100644 index 0000000..2bb40d2 --- /dev/null +++ b/src/calculations/spaces/work related area/focusroom.ts @@ -0,0 +1,19 @@ +import MainSpace from '../main_space_class' + +export default class Focusroom extends MainSpace { + /** + * This method computes the share of this workspace type + * @returns {number} + */ + sharePerWorkspaceType = (): number => { + return this.variables.focusroomShare + } + + /** + * Calculates the area of the project room. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() * this.sharePerWorkspaceType() + } +} diff --git a/src/calculations/spaces/work related area/hwc.ts b/src/calculations/spaces/work related area/hwc.ts new file mode 100644 index 0000000..0ce6308 --- /dev/null +++ b/src/calculations/spaces/work related area/hwc.ts @@ -0,0 +1,18 @@ +import MainSpace from '../main_space_class' + +export default class WorkHwc extends MainSpace { + /** + * Area per person excluding corridor + */ + areaPerPersonExcludingCorridor = (): number => { + return this.spaceConstants.areaPerRole / this.spaceConstants.personsPerType * this.spaceConstants.unitsPerPerson! + } + + /** + * Calculates the area of the work hwc. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() + } +} diff --git a/src/calculations/spaces/work related area/landscape.ts b/src/calculations/spaces/work related area/landscape.ts new file mode 100644 index 0000000..da35dcb --- /dev/null +++ b/src/calculations/spaces/work related area/landscape.ts @@ -0,0 +1,24 @@ +import MainSpace from '../main_space_class' + +export default class Landscape extends MainSpace { + /** + * This method computes the share of this workspace type + * @returns {number} + */ + sharePerWorkspaceType = (): number => { + if (this.variables.accessToCellOffice) { + return this.variables.landscapeShare + } else { + return this.variables.landscapeShare + this.variables.cellOfficeShare + } + return 0 + } + + /** + * Calculates the area of the landscape. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() * this.sharePerWorkspaceType() + } +} diff --git a/src/calculations/spaces/work related area/meetingroom_large.ts b/src/calculations/spaces/work related area/meetingroom_large.ts new file mode 100644 index 0000000..0032e90 --- /dev/null +++ b/src/calculations/spaces/work related area/meetingroom_large.ts @@ -0,0 +1,37 @@ +import MainSpace from '../main_space_class' +import WorkTouchdown from './touchdown' +import WorkDockin from './dockin' +import CellOffice from './celloffice' +import Landscape from './landscape' +import Projectroom from './projectroom' +import Focusroom from './focusroom' +import Quietzone from './quietzone' +import SharedLargeMeetingroom from '../shared area/meetingroom_large' + +export default class WorkLargeMeetingroom extends MainSpace { + /** + * Calculates the area per person for this space. + * @returns {number} + */ + areaPerPersonExcludingCorridor(): number { + return this.spaceConstants.areaPerRole / this.spaceConstants.personsPerType * this.variables.largeMeetingroomShare + } + + /** + * Calculates the area of the meeting room. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + // =SUM((AP4:AP10)*AR13)+(AB13)-(K27) + const workTouchdown = new WorkTouchdown(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const workDockin = new WorkDockin(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const cellOffice = new CellOffice(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const landscape = new Landscape(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const projectroom = new Projectroom(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const focusroom = new Focusroom(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const quietzone = new Quietzone(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const addedPeakAreaSum = workTouchdown.calculateEmployeesPerWorkplaceTypeUnadjusted() + workDockin.calculateEmployeesPerWorkplaceTypeUnadjusted() + cellOffice.calculateEmployeesPerWorkplaceTypeUnadjusted() + landscape.calculateEmployeesPerWorkplaceTypeUnadjusted() + projectroom.calculateEmployeesPerWorkplaceTypeUnadjusted() + focusroom.calculateEmployeesPerWorkplaceTypeUnadjusted() + quietzone.calculateEmployeesPerWorkplaceTypeUnadjusted() + const sharedLargeMeetingRoom = new SharedLargeMeetingroom(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + return addedPeakAreaSum * this.areaPerPersonExcludingCorridor() + this.addedPeakArea() - sharedLargeMeetingRoom.calculateAreaExclCompensation() + } +} diff --git a/src/calculations/spaces/work related area/meetingroom_medium.ts b/src/calculations/spaces/work related area/meetingroom_medium.ts new file mode 100644 index 0000000..dfff61e --- /dev/null +++ b/src/calculations/spaces/work related area/meetingroom_medium.ts @@ -0,0 +1,37 @@ +import MainSpace from '../main_space_class' +import WorkTouchdown from './touchdown' +import WorkDockin from './dockin' +import CellOffice from './celloffice' +import Landscape from './landscape' +import Projectroom from './projectroom' +import Focusroom from './focusroom' +import Quietzone from './quietzone' +import SharedMediumMeetingroom from '../shared area/meetingroom_medium' + +export default class WorkMediumMeetingroom extends MainSpace { + /** + * Calculates the area per person for this space. + * @returns {number} + */ + areaPerPersonExcludingCorridor(): number { + return this.spaceConstants.areaPerRole / this.spaceConstants.personsPerType * this.variables.mediumMeetingroomShare + } + + /** + * Calculates the area of the meeting room. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + // =SUM((AP4:AP10)*AR13)+(AB13)-(K27) + const workTouchdown = new WorkTouchdown(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const workDockin = new WorkDockin(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const cellOffice = new CellOffice(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const landscape = new Landscape(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const projectroom = new Projectroom(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const focusroom = new Focusroom(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const quietzone = new Quietzone(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const addedPeakAreaSum = workTouchdown.calculateEmployeesPerWorkplaceTypeUnadjusted() + workDockin.calculateEmployeesPerWorkplaceTypeUnadjusted() + cellOffice.calculateEmployeesPerWorkplaceTypeUnadjusted() + landscape.calculateEmployeesPerWorkplaceTypeUnadjusted() + projectroom.calculateEmployeesPerWorkplaceTypeUnadjusted() + focusroom.calculateEmployeesPerWorkplaceTypeUnadjusted() + quietzone.calculateEmployeesPerWorkplaceTypeUnadjusted() + const sharedMediumMeetingRoom = new SharedMediumMeetingroom(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + return addedPeakAreaSum * this.areaPerPersonExcludingCorridor() + this.addedPeakArea() - sharedMediumMeetingRoom.calculateAreaExclCompensation() + } +} diff --git a/src/calculations/spaces/work related area/meetingroom_mini.ts b/src/calculations/spaces/work related area/meetingroom_mini.ts new file mode 100644 index 0000000..43158b0 --- /dev/null +++ b/src/calculations/spaces/work related area/meetingroom_mini.ts @@ -0,0 +1,34 @@ +import MainSpace from '../main_space_class' +import WorkTouchdown from './touchdown' +import WorkDockin from './dockin' +import CellOffice from './celloffice' +import Landscape from './landscape' +import Projectroom from './projectroom' +import Focusroom from './focusroom' +import Quietzone from './quietzone' + +export default class WorkMiniMeetingroom extends MainSpace { + /** + * Calculates the area per person for this space. + * @returns {number} + */ + areaPerPersonExcludingCorridor(): number { + return this.spaceConstants.areaPerRole / this.spaceConstants.personsPerType * this.variables.miniMeetingroomShare + } + + /** + * Calculates the area of the meeting room. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + const workTouchdown = new WorkTouchdown(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const workDockin = new WorkDockin(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const cellOffice = new CellOffice(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const landscape = new Landscape(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const projectroom = new Projectroom(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const focusroom = new Focusroom(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const quietzone = new Quietzone(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const addedPeakAreaSum = workTouchdown.calculateEmployeesPerWorkplaceTypeUnadjusted() + workDockin.calculateEmployeesPerWorkplaceTypeUnadjusted() + cellOffice.calculateEmployeesPerWorkplaceTypeUnadjusted() + landscape.calculateEmployeesPerWorkplaceTypeUnadjusted() + projectroom.calculateEmployeesPerWorkplaceTypeUnadjusted() + focusroom.calculateEmployeesPerWorkplaceTypeUnadjusted() + quietzone.calculateEmployeesPerWorkplaceTypeUnadjusted() + return addedPeakAreaSum * this.areaPerPersonExcludingCorridor() + this.addedPeakArea() + } +} diff --git a/src/calculations/spaces/work related area/meetingroom_small.ts b/src/calculations/spaces/work related area/meetingroom_small.ts new file mode 100644 index 0000000..309640d --- /dev/null +++ b/src/calculations/spaces/work related area/meetingroom_small.ts @@ -0,0 +1,37 @@ +import MainSpace from '../main_space_class' +import WorkTouchdown from './touchdown' +import WorkDockin from './dockin' +import CellOffice from './celloffice' +import Landscape from './landscape' +import Projectroom from './projectroom' +import Focusroom from './focusroom' +import Quietzone from './quietzone' +import SharedSmallMeetingRoom from '../shared area/meetingroom_small' + +export default class WorkSmallMeetingroom extends MainSpace { + /** + * Calculates the area per person for this space. + * @returns {number} + */ + areaPerPersonExcludingCorridor(): number { + return this.spaceConstants.areaPerRole / this.spaceConstants.personsPerType * this.variables.smallMeetingroomShare + } + + /** + * Calculates the area of the meeting room. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + // =SUM((AP4:AP10)*AR13)+(AB13)-(K27) + const workTouchdown = new WorkTouchdown(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const workDockin = new WorkDockin(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const cellOffice = new CellOffice(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const landscape = new Landscape(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const projectroom = new Projectroom(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const focusroom = new Focusroom(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const quietzone = new Quietzone(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const addedPeakAreaSum = workTouchdown.calculateEmployeesPerWorkplaceTypeUnadjusted() + workDockin.calculateEmployeesPerWorkplaceTypeUnadjusted() + cellOffice.calculateEmployeesPerWorkplaceTypeUnadjusted() + landscape.calculateEmployeesPerWorkplaceTypeUnadjusted() + projectroom.calculateEmployeesPerWorkplaceTypeUnadjusted() + focusroom.calculateEmployeesPerWorkplaceTypeUnadjusted() + quietzone.calculateEmployeesPerWorkplaceTypeUnadjusted() + const sharedSmallMeetingRoom = new SharedSmallMeetingRoom(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + return addedPeakAreaSum * this.areaPerPersonExcludingCorridor() + this.addedPeakArea() - sharedSmallMeetingRoom.calculateAreaExclCompensation() + } +} diff --git a/src/calculations/spaces/work related area/minikitchenlounge.ts b/src/calculations/spaces/work related area/minikitchenlounge.ts new file mode 100644 index 0000000..77bc4e7 --- /dev/null +++ b/src/calculations/spaces/work related area/minikitchenlounge.ts @@ -0,0 +1,18 @@ +import MainSpace from '../main_space_class' + +export default class MiniKitchenLounge extends MainSpace { + /** + * Area per person excluding corridor + */ + areaPerPersonExcludingCorridor = (): number => { + return this.spaceConstants.areaPerRole / this.spaceConstants.personsPerType + } + + /** + * Calculates the area of the zone. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() + this.addedPeakArea() + } +} diff --git a/src/calculations/spaces/work related area/multiroom.ts b/src/calculations/spaces/work related area/multiroom.ts new file mode 100644 index 0000000..fb7b33f --- /dev/null +++ b/src/calculations/spaces/work related area/multiroom.ts @@ -0,0 +1,26 @@ +import MainSpace from '../main_space_class' +import WorkTouchdown from './touchdown' +import WorkDockin from './dockin' +import Landscape from './landscape' +import Focusroom from './focusroom' + +export default class WorkMultiroom extends MainSpace { + /** + * Calculates the area of the multi room. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + const workDockin = new WorkDockin(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const landscape = new Landscape(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const workTouchdown = new WorkTouchdown(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const focusroom = new Focusroom(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + + const touchdownDockinLandscapeSum = workTouchdown.calculateEmployeesPerWorkplaceTypeUnadjusted() + workDockin.calculateEmployeesPerWorkplaceTypeUnadjusted() + landscape.calculateEmployeesPerWorkplaceTypeUnadjusted() + + if (touchdownDockinLandscapeSum / 15 * 2 * this.areaPerPersonExcludingCorridor() > focusroom.calculateAreaExclCompensation()) { + return touchdownDockinLandscapeSum / 15 * 2 * this.areaPerPersonExcludingCorridor() - focusroom.calculateAreaExclCompensation() + } else { + return touchdownDockinLandscapeSum / 15 * 2 * this.areaPerPersonExcludingCorridor() + } + } +} diff --git a/src/calculations/spaces/work related area/personalstorage.ts b/src/calculations/spaces/work related area/personalstorage.ts new file mode 100644 index 0000000..cb46ee3 --- /dev/null +++ b/src/calculations/spaces/work related area/personalstorage.ts @@ -0,0 +1,22 @@ +import MainSpace from '../main_space_class' +import WorkTouchdown from './touchdown' +import WorkDockin from './dockin' +import Focusroom from './focusroom' +import Quietzone from './quietzone' +import SharedPersonalStorage from '../shared area/personalstorage' + +export default class WorkPersonalStorage extends MainSpace { + /** + * Calculates the area of the personal storage + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + const workTouchdown = new WorkTouchdown(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const workDockin = new WorkDockin(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const focusroom = new Focusroom(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const quietzone = new Quietzone(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const sharedPersonalStorage = new SharedPersonalStorage(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + const addedPeakAreaSum = workTouchdown.calculateEmployeesPerWorkplaceTypeUnadjusted() + workDockin.calculateEmployeesPerWorkplaceTypeUnadjusted() + focusroom.calculateEmployeesPerWorkplaceTypeUnadjusted() + quietzone.calculateEmployeesPerWorkplaceTypeUnadjusted() + return addedPeakAreaSum * this.areaPerPersonExcludingCorridor() - sharedPersonalStorage.calculateAreaExclCompensation() + } +} diff --git a/src/calculations/spaces/work related area/projectroom.ts b/src/calculations/spaces/work related area/projectroom.ts new file mode 100644 index 0000000..de02240 --- /dev/null +++ b/src/calculations/spaces/work related area/projectroom.ts @@ -0,0 +1,19 @@ +import MainSpace from '../main_space_class' + +export default class Projectroom extends MainSpace { + /** + * This method computes the share of this workspace type + * @returns {number} + */ + sharePerWorkspaceType = (): number => { + return this.variables.projectroomShare + } + + /** + * Calculates the area of the project room. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() * this.sharePerWorkspaceType() + } +} diff --git a/src/calculations/spaces/work related area/quietzone.ts b/src/calculations/spaces/work related area/quietzone.ts new file mode 100644 index 0000000..12038f6 --- /dev/null +++ b/src/calculations/spaces/work related area/quietzone.ts @@ -0,0 +1,19 @@ +import MainSpace from '../main_space_class' + +export default class Quietzone extends MainSpace { + /** + * This method computes the share of this workspace type + * @returns {number} + */ + sharePerWorkspaceType = (): number => { + return this.variables.quietzoneShare + } + + /** + * Calculates the area of the quiet zone. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() * this.sharePerWorkspaceType() + } +} diff --git a/src/calculations/spaces/work related area/reception.ts b/src/calculations/spaces/work related area/reception.ts new file mode 100644 index 0000000..a28d478 --- /dev/null +++ b/src/calculations/spaces/work related area/reception.ts @@ -0,0 +1,16 @@ +import MainSpace from '../main_space_class' +import SharedReception from '../shared area/reception' + +export default class WorkReception extends MainSpace { + /** + * Calculates the area of the reception. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + if (this.variables.accessToReception) { + const sharedReception = new SharedReception(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() - sharedReception.calculateAreaExclCompensation() + } + return 0 + } +} diff --git a/src/calculations/spaces/work related area/special.ts b/src/calculations/spaces/work related area/special.ts new file mode 100644 index 0000000..e444f53 --- /dev/null +++ b/src/calculations/spaces/work related area/special.ts @@ -0,0 +1,11 @@ +import MainSpace from '../main_space_class' + +export default class WorkSpecial extends MainSpace { + /** + * Calculates the area of the special rooms. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return this.variables.specialAreaOffice + } +} diff --git a/src/calculations/spaces/work related area/toilet.ts b/src/calculations/spaces/work related area/toilet.ts new file mode 100644 index 0000000..8c8881e --- /dev/null +++ b/src/calculations/spaces/work related area/toilet.ts @@ -0,0 +1,18 @@ +import MainSpace from '../main_space_class' + +export default class WorkToilet extends MainSpace { + /** + * Area per person excluding corridor + */ + areaPerPersonExcludingCorridor = (): number => { + return this.spaceConstants.areaPerRole / this.spaceConstants.personsPerType * this.spaceConstants.unitsPerPerson! + } + + /** + * Calculates the area of the work toilet. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() + } +} diff --git a/src/calculations/spaces/work related area/touchdown.ts b/src/calculations/spaces/work related area/touchdown.ts new file mode 100644 index 0000000..052780b --- /dev/null +++ b/src/calculations/spaces/work related area/touchdown.ts @@ -0,0 +1,22 @@ +import MainSpace from '../main_space_class' +import SharedTouchdown from '../shared area/touchdown' + +export default class WorkTouchdown extends MainSpace { + /** + * This method computes the share of this workspace type + * @returns {number} + */ + sharePerWorkspaceType = (): number => { + // We need the shared touchdown share for the calculation of the work touchdown share + const sharedTouchdown = new SharedTouchdown(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + return this.variables.touchdownShare - sharedTouchdown.sharePerWorkspaceType() + } + + /** + * Calculates the area of touchdown. 0 if there is no need. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() * this.sharePerWorkspaceType() + this.addedPeakArea() + } +} diff --git a/src/calculations/spaces/work related area/waitingzone.ts b/src/calculations/spaces/work related area/waitingzone.ts new file mode 100644 index 0000000..8ef7a13 --- /dev/null +++ b/src/calculations/spaces/work related area/waitingzone.ts @@ -0,0 +1,16 @@ +import MainSpace from '../main_space_class' +import SharedWaitingZone from '../shared area/waitingzone' + +export default class WorkWaitingZone extends MainSpace { + /** + * Calculates the area of the reception. + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + if (this.variables.accessToReception) { + const sharedWaitingZone = new SharedWaitingZone(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() - sharedWaitingZone.calculateAreaExclCompensation() + } + return 0 + } +} diff --git a/src/calculations/spaces/work related area/wardrobe.ts b/src/calculations/spaces/work related area/wardrobe.ts new file mode 100644 index 0000000..8b4b9c0 --- /dev/null +++ b/src/calculations/spaces/work related area/wardrobe.ts @@ -0,0 +1,13 @@ +import MainSpace from '../main_space_class' +import SharedWardrobe from '../shared area/wardrobe' + +export default class WorkWardrobe extends MainSpace { + /** + * Calculates the area of the personal storage + * @returns {number} + */ + calculateAreaExclCompensation = (): number => { + const sharedWardrobe = new SharedWardrobe(this.space, this.variables, this.config, this.customSpaceConstants, this.customConstants) + return this.dimensionedAttendance() * this.areaPerPersonExcludingCorridor() - sharedWardrobe.calculateAreaExclCompensation() + } +} diff --git a/src/config/default.json b/src/config/default.json new file mode 100644 index 0000000..fc0caa1 --- /dev/null +++ b/src/config/default.json @@ -0,0 +1,965 @@ +{ + "constants": { + "governmentMinimumSquaremetersPerWorkSpace": 6, + "corridorAddonShare": 0.18, + "innerwallsAddonShare": 0.09 + }, + "spaces": [ + { + "name": "work related area", + "shouldCalculateCorridor": true, + "shouldCalculateInnerwalls": true, + "result": {}, + "spaces": [ + { + "name": "flexible seats", + "result": {}, + "spaces": [ + { + "name": "touchdown – work related area", + "result": {}, + "constants": { + "seatPersonRoom": "seat", + "areaPerRole": 1.90, + "personsPerType": 1, + "minimumPersons": 4, + "minimumSquareMeters": 7.6, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false, + "requiresAdditionalStorage": true, + "surchargeInternalCorridorShare": 0.06, + "shouldCalculateNumberOfSeats": true + } + }, + { + "name": "dock in – work related area", + "result": {}, + "constants": { + "seatPersonRoom": "seat", + "areaPerRole": 4.20, + "personsPerType": 1, + "minimumPersons": 2, + "minimumSquareMeters": 8.4, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": true, + "adhereToGovernmentMinimum": true, + "requiresAdditionalStorage": true, + "surchargeInternalCorridorShare": 0.31, + "shouldCalculateNumberOfSeats": true + } + } + ] + }, + { + "name": "fixed seats", + "result": {}, + "spaces": [ + { + "name": "cell office", + "result": {}, + "constants": { + "seatPersonRoom": "room", + "areaPerRole": 8.0, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 8.0, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": true, + "adhereToGovernmentMinimum": false, + "shouldCalculateNumberOfRooms": true + } + }, + { + "name": "landscape", + "result": {}, + "constants": { + "seatPersonRoom": "seat", + "areaPerRole": 5.44, + "personsPerType": 1, + "minimumPersons": 4, + "minimumSquareMeters": 21.8, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": true, + "adhereToGovernmentMinimum": true, + "requiresAdditionalStorage": true, + "surchargeInternalCorridorShare": 0.36, + "shouldCalculateNumberOfSeats": true + } + } + ] + }, + { + "name": "project seats", + "result": {}, + "spaces": [ + { + "name": "project room", + "result": {}, + "constants": { + "seatPersonRoom": "room", + "areaPerRole": 6.0, + "personsPerType": 1, + "minimumPersons": 6, + "minimumSquareMeters": 36.0, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": true, + "adhereToGovernmentMinimum": true, + "shouldCalculateNumberOfRooms": true, + "shouldCalculateNumberOfSeats": true + } + } + ] + }, + { + "name": "quiet area / seats", + "result": {}, + "spaces": [ + { + "name": "focus room", + "result": {}, + "constants": { + "seatPersonRoom": "room", + "areaPerRole": 6.0, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 6.0, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": true, + "adhereToGovernmentMinimum": false, + "requiresAdditionalStorage": true, + "shouldCalculateNumberOfRooms": true + } + }, + { + "name": "quiet zone", + "result": {}, + "constants": { + "seatPersonRoom": "seat", + "areaPerRole": 4.2, + "personsPerType": 1, + "minimumPersons": 4, + "minimumSquareMeters": 16.8, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": true, + "adhereToGovernmentMinimum": false, + "requiresAdditionalStorage": true, + "surchargeInternalCorridorShare": 0.36, + "shouldCalculateNumberOfSeats": true + } + } + ] + }, + { + "name": "social area", + "result": {}, + "spaces": [ + { + "name": "coffee station", + "result": {}, + "constants": { + "seatPersonRoom": "room", + "areaPerRole": 4.0, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 4.0, + "shouldCalculateCompensation": true, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false, + "unitsPerPerson": 0.02, + "shouldCalculateNumberOfRooms": true + } + }, + { + "name": "mini kitchen lounge", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 1.2, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 1.2, + "shouldCalculateCompensation": true, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false + } + } + ] + }, + { + "name": "meeting area", + "result": {}, + "spaces": [ + { + "name": "multi room – work related area", + "result": {}, + "constants": { + "seatPersonRoom": "room", + "areaPerRole": 6.0, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 6.0, + "shouldCalculateCompensation": true, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false, + "shouldCalculateNumberOfRooms": true + } + }, + { + "name": "meeting room", + "result": {}, + "spaces": [ + { + "name": "mini meeting room – work related area", + "result": {}, + "constants": { + "seatPersonRoom": "room", + "areaPerRole": 1.8, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 1.8, + "shouldCalculateCompensation": true, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false, + "shouldCalculateNumberOfRooms": true + } + }, + { + "name": "small meeting room – work related area", + "result": {}, + "constants": { + "seatPersonRoom": "room", + "areaPerRole": 2.2, + "personsPerType": 1, + "minimumPersons": 4, + "minimumSquareMeters": 8.7, + "shouldCalculateCompensation": true, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false, + "shouldCalculateNumberOfRooms": true + } + }, + { + "name": "medium meeting room – work related area", + "result": {}, + "constants": { + "seatPersonRoom": "room", + "areaPerRole": 1.4, + "personsPerType": 1, + "minimumPersons": 8, + "minimumSquareMeters": 11.0, + "shouldCalculateCompensation": true, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false, + "shouldCalculateNumberOfRooms": true + } + }, + { + "name": "large meeting room – work related area", + "result": {}, + "constants": { + "seatPersonRoom": "room", + "areaPerRole": 1.3, + "personsPerType": 1, + "minimumPersons": 16, + "minimumSquareMeters": 21.0, + "shouldCalculateCompensation": true, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false, + "shouldCalculateNumberOfRooms": true + } + } + ] + } + ] + }, + { + "name": "support functions", + "result": {}, + "spaces": [ + { + "name": "reception area", + "result": {}, + "spaces": [ + { + "name": "reception – work related area", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 0.14, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 10.0, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false + } + }, + { + "name": "waiting zone – work related area", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 0.06, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 4.0, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false + } + } + ] + }, + { + "name": "copy/archive/cleaning", + "result": {}, + "spaces": [ + { + "name": "copy archive", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 0.14, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 10.0, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false, + "shouldCalculateNumberOfRooms": true + } + }, + { + "name": "cleaning", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 0.02, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 2.0, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false, + "shouldCalculateNumberOfRooms": true + } + } + ] + }, + { + "name": "personal storage – work related area", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 0.7, + "personsPerType": 3, + "minimumPersons": 1, + "minimumSquareMeters": 3.0, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false + } + }, + { + "name": "wardrobe – work related area", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 0.21, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 2.0, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false + } + }, + { + "name": "restroom – work related area", + "result": {}, + "spaces": [ + { + "name": "toilet – work related area", + "result": {}, + "constants": { + "seatPersonRoom": "room", + "areaPerRole": 2.10, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 2.0, + "unitsPerPerson": 0.067, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false, + "shouldCalculateNumberOfRooms": true + } + }, + { + "name": "hwc – work related area", + "result": {}, + "constants": { + "seatPersonRoom": "room", + "areaPerRole": 6.0, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 6.0, + "unitsPerPerson": 0.01, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false, + "shouldCalculateNumberOfRooms": true + } + } + ] + } + ] + }, + { + "name": "special – work related area", + "result": {}, + "spaces": [ + { + "name": "special – work related area", + "result": {}, + "constants": { + "seatPersonRoom": null, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false + } + } + ] + } + ] + }, + { + "name": "shared area", + "shouldCalculateCorridor": true, + "shouldCalculateInnerwalls": true, + "result": {}, + "spaces": [ + { + "name": "flexible seats", + "result": {}, + "spaces": [ + { + "name": "touchdown – shared area", + "result": {}, + "constants": { + "seatPersonRoom": "seat", + "areaPerRole": 1.90, + "personsPerType": 1, + "minimumPersons": 4, + "minimumSquareMeters": 7.6, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false, + "requiresAdditionalStorage": true, + "surchargeInternalCorridorShare": 0.06, + "shouldCalculateNumberOfSeats": true + } + }, + { + "name": "dock in – shared area", + "result": {}, + "constants": { + "seatPersonRoom": "seat", + "areaPerRole": 4.20, + "personsPerType": 1, + "minimumPersons": 2, + "minimumSquareMeters": 8.4, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": true, + "adhereToGovernmentMinimum": true, + "requiresAdditionalStorage": true, + "surchargeInternalCorridorShare": 0.31, + "shouldCalculateNumberOfSeats": true + } + } + ] + }, + { + "name": "social area", + "result": {}, + "spaces": [ + { + "name": "lounge area – shared area", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 0.95, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 40.0, + "shouldCalculateCompensation": true, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false + } + } + ] + }, + { + "name": "project and meeting area", + "result": {}, + "spaces": [ + { + "name": "project room – shared area", + "result": {}, + "constants": { + "seatPersonRoom": "room", + "areaPerRole": 6.0, + "personsPerType": 1, + "minimumPersons": 6, + "minimumSquareMeters": 36.0, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": true, + "adhereToGovernmentMinimum": true, + "shouldCalculateNumberOfRooms": true, + "shouldCalculateNumberOfSeats": true + } + }, + { + "name": "multi room – shared area", + "result": {}, + "constants": { + "seatPersonRoom": "room", + "areaPerRole": 1.8, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 6.0, + "shouldCalculateCompensation": true, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false, + "shouldCalculateNumberOfRooms": true + } + }, + { + "name": "meeting room", + "result": {}, + "spaces": [ + { + "name": "small meeting room – shared area", + "result": {}, + "constants": { + "seatPersonRoom": "room", + "areaPerRole": 2.2, + "personsPerType": 1, + "minimumPersons": 4, + "minimumSquareMeters": 8.7, + "shouldCalculateCompensation": true, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false, + "shouldCalculateNumberOfRooms": true + } + }, + { + "name": "medium meeting room – shared area", + "result": {}, + "constants": { + "seatPersonRoom": "room", + "areaPerRole": 1.4, + "personsPerType": 1, + "minimumPersons": 8, + "minimumSquareMeters": 11.0, + "shouldCalculateCompensation": true, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false, + "shouldCalculateNumberOfRooms": true + } + }, + { + "name": "large meeting room – shared area", + "result": {}, + "constants": { + "seatPersonRoom": "room", + "areaPerRole": 1.3, + "personsPerType": 1, + "minimumPersons": 16, + "minimumSquareMeters": 21.0, + "shouldCalculateCompensation": true, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false, + "shouldCalculateNumberOfRooms": true + } + } + ] + } + ] + }, + { + "name": "support functions", + "result": {}, + "spaces": [ + { + "name": "reception area", + "result": {}, + "spaces": [ + { + "name": "reception – shared area", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 0.14, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 10.0, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false + } + }, + { + "name": "waiting zone – shared area", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 0.35, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 20.0, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false + } + } + ] + }, + { + "name": "personal storage – shared area", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 0.7, + "personsPerType": 3, + "minimumPersons": 1, + "minimumSquareMeters": 3.0, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false + } + }, + { + "name": "wardrobe – shared area", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 0.21, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 2.0, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false + } + }, + { + "name": "restroom – shared area", + "result": {}, + "spaces": [ + { + "name": "toilet – shared area", + "result": {}, + "constants": { + "seatPersonRoom": "room", + "areaPerRole": 2.10, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 2.0, + "unitsPerPerson": 0.067, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false, + "shouldCalculateNumberOfRooms": true + } + }, + { + "name": "hwc – shared area", + "result": {}, + "constants": { + "seatPersonRoom": "room", + "areaPerRole": 6.0, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 6.0, + "unitsPerPerson": 0.01, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false, + "shouldCalculateNumberOfRooms": true + } + } + ] + } + ] + }, + { + "name": "special – shared area", + "result": {}, + "spaces": [ + { + "name": "special – shared area", + "result": {}, + "constants": { + "seatPersonRoom": null, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false + } + } + ] + } + ] + }, + { + "name": "common area", + "shouldCalculateCorridor": true, + "shouldCalculateInnerwalls": true, + "result": {}, + "spaces": [ + { + "name": "canteen area", + "result": {}, + "spaces": [ + { + "name": "canteen", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 0.72, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 100, + "shouldCalculateCompensation": true, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false + } + }, + { + "name": "kitchen", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 0.17, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 20, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false + } + } + ] + }, + { + "name": "conference area", + "result": {}, + "spaces": [ + { + "name": "auditorium", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 1.1, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 50, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false + } + }, + { + "name": "course", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 0.50, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 50, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false + } + }, + { + "name": "lobby", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 0.30, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 40, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "seatAreaPerSpot": 0.40, + "adhereToGovernmentMinimum": false + } + } + ] + }, + { + "name": "support functions", + "result": {}, + "spaces": [ + { + "name": "reception area", + "result": {}, + "spaces": [ + { + "name": "reception – common area", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 0.14, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 20.0, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false + } + }, + { + "name": "waiting zone – common area", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 0.35, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 40.0, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false + } + } + ] + }, + { + "name": "stock area", + "result": {}, + "spaces": [ + { + "name": "stock archive – common area", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 0.14, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 15.0, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false, + "shouldCalculateNumberOfRooms": true + } + }, + { + "name": "cleaning – common area", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 0.02, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 5.0, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false, + "shouldCalculateNumberOfRooms": true + } + } + ] + }, + { + "name": "restroom – common area", + "result": {}, + "spaces": [ + { + "name": "toilet – common area", + "result": {}, + "constants": { + "seatPersonRoom": "room", + "areaPerRole": 2.10, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 2.0, + "unitsPerPerson": 0.033, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false, + "shouldCalculateNumberOfRooms": true + } + }, + { + "name": "hwc – common area", + "result": {}, + "constants": { + "seatPersonRoom": "room", + "areaPerRole": 6.0, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 6.0, + "unitsPerPerson": 0.01, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false, + "shouldCalculateNumberOfRooms": true + } + } + ] + }, + { + "name": "wardrobe – common area", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 0.26, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 26.0, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false + } + }, + { + "name": "exercise room – common area", + "result": {}, + "constants": { + "seatPersonRoom": "person", + "areaPerRole": 1.0, + "personsPerType": 1, + "minimumPersons": 1, + "minimumSquareMeters": 20.0, + "unitsPerPerson": 0.20, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false + } + } + ] + }, + { + "name": "special – common area", + "result": {}, + "spaces": [ + { + "name": "special – common area", + "result": {}, + "constants": { + "seatPersonRoom": null, + "shouldCalculateCompensation": false, + "countsTowardsWorkspaceArea": false, + "adhereToGovernmentMinimum": false + } + } + ] + } + ] + } + ] +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..e6432ba --- /dev/null +++ b/src/index.ts @@ -0,0 +1,59 @@ +import { ISpaceConstant } from './calculations/interfaces/space_constant' +import { IConstant } from './calculations/interfaces/constant' +import { IVariable } from './calculations/interfaces/variable' +import Calculator from './calculations/calculator' +import redis from 'redis' +import { RedisClientType } from '@redis/client' + +let redisClient: RedisClientType +if (process.env.USE_CACHE_REDIS === '1') { + (async () => { + redisClient = redis.createClient({ + url: 'redis://redis:6379', + }) + redisClient.on("error", (error) => console.error(`Error : ${error}`)) + await redisClient.connect() + })() +} + +const headers = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'OPTIONS, POST, GET', + 'Access-Control-Allow-Headers': 'Content-Type', +} + +Bun.serve({ + port: process.env.PORT, + async fetch(req) { + if (req.method === 'OPTIONS') { + const res = new Response('Ok', { headers }) + return res + } + const url = new URL(req.url) + if (url.pathname === "/") return new Response("Agiliate is running", { headers }) + if (url.pathname === "/calculate") { + const jsonReq = await req.json() + if (process.env.CACHE === '1') { + const cachedResult = await redisClient.get(jsonReq) + if (cachedResult) { + return Response.json(JSON.parse(cachedResult), { headers }) + } + } + const variables: IVariable = { + ...jsonReq.variables + } + const customSpaceConstants: ISpaceConstant = { + ...jsonReq?.customSpaceConstants + } + const customConstants: IConstant = { + ...jsonReq?.customConstants + } + const calculator = new Calculator(variables, customSpaceConstants, customConstants) + const result = calculator.result() + if (process.env.CACHE === '1') + redisClient.set(JSON.stringify(jsonReq), JSON.stringify(result)) + return Response.json(result, { headers }) + } + return new Response("404!", { headers }) + }, +}) diff --git a/test/main.test.ts b/test/main.test.ts new file mode 100644 index 0000000..6c93bcb --- /dev/null +++ b/test/main.test.ts @@ -0,0 +1,10 @@ +import { expect, test } from "bun:test" +import Calculator from '../src/calculations/calculator' + +test("totals", async () => { + const file = Bun.file('test/scenarios/1.json') + const scenario = await file.json() + const calculator = new Calculator(scenario.input.variables, scenario.input.customSpaceConstants, scenario.input.customConstants) + const result = calculator.result() + expect(result.totals.grossArea).toBe(scenario.result.totals.grossArea) +}) diff --git a/test/scenarios/1.json b/test/scenarios/1.json new file mode 100644 index 0000000..29e07ad --- /dev/null +++ b/test/scenarios/1.json @@ -0,0 +1,48 @@ +{ + "result": { + "totals": { + "grossArea": 7809 + } + }, + "input": { + "variables": { + "accessToCoworking": false, + "accessToCanteen": true, + "accessToCourseSpace": true, + "accessToAuditorium": true, + "accessToCellOffice": true, + "accessToReception": false, + "accessToExercise": true, + "specialAreaOffice": 80, + "specialAreaShared": 0, + "specialAreaCommon": 100, + "seatsInAuditorium": 50, + "numberOfEmployees": 330, + "concurrencyAttendanceShare": 1.0, + "peakConcurrencyAttendanceShare": 1.0, + "overCapacityShare": 0.0, + "homeOfficeAverageShare": 0.0, + "shareOfEmployeesInAuditorium": 0.30, + "touchdownShare": 0.0555, + "dockinShare": 0.2197, + "coworkingShare": 0.00, + "cellOfficeShare": 0.00, + "landscapeShare": 0.3630, + "projectroomShare": 0.1891, + "focusroomShare": 0.1054, + "quietzoneShare": 0.0672, + "miniMeetingroomShare": 0.2105, + "smallMeetingroomShare": 0.3017, + "mediumMeetingroomShare": 0.3600, + "largeMeetingroomShare": 0.1277 + }, + "customSpaceConstants": { + "auditorium": { + "minimumSquareMeters": 120 + } + }, + "customConstants": { + "governmentMinimumSquaremetersPerWorkSpace": 6 + } + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..83654f0 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "module": "esnext", + "target": "esnext", + "moduleDetection": "force", + "allowImportingTsExtensions": true, + "noEmit": true, + "composite": true, + "strict": true, + "downlevelIteration": true, + "jsx": "react-jsx", + "allowSyntheticDefaultImports": true, + "allowJs": true, + "moduleResolution": "bundler", + "esModuleInterop": true, + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "skipLibCheck": true, + "lib": [ + "esnext" + ], + "forceConsistentCasingInFileNames": true, + "types": [ + "bun-types", + ] + }, + "include": [ + "**/*.ts", + "types/*.ts" + ], + "exclude": [ + "node_modules" + ] +}