diff --git a/.commitlintrc.cjs b/.commitlintrc.cjs new file mode 100644 index 000000000..30bdd333a --- /dev/null +++ b/.commitlintrc.cjs @@ -0,0 +1,34 @@ +const { utils: { getProjects } } = require('@commitlint/config-nx-scopes'); + +module.exports = { + extends: [ + '@commitlint/config-conventional', + '@commitlint/config-nx-scopes' + ], + rules: { + 'scope-enum': async (ctx) => { + const projectFilter = ({ name }) => + !name.includes('e2e'); + const projectNames = new Set(); + (await getProjects(ctx, projectFilter)) + .forEach(element => { + projectNames.add(element); + projectNames.add(element.replaceAll('itmat-', '')); + }); + return [ + 2, + 'always', + [ + ...projectNames + ] + ]; + }, + 'subject-case': [ + 2, + 'always', + [ + 'sentence-case' + ] + ] + } +}; diff --git a/.editorconfig b/.editorconfig index 1a9f5e7c5..c7c16d044 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,12 +1,16 @@ - +# Editor configuration, see http://editorconfig.org root = true -[*.{js,ts,jsx,tsx,json}] +[*] charset = utf-8 -indent_size = 4 indent_style = space +indent_size = 4 insert_final_newline = true trim_trailing_whitespace = true +[.yml] +indent_size = 2 + [*.md] +max_line_length = off trim_trailing_whitespace = false diff --git a/.env b/.env new file mode 100644 index 000000000..11ed57e43 --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +# Nx 18 enables using plugins to infer targets by default +# This is disabled for existing workspaces to maintain compatibility +# For more info, see: https://nx.dev/concepts/inferred-tasks +NX_ADD_PLUGINS=false \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 746d36312..38231b16d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -30,22 +30,47 @@ const javascriptRules = { 'comma-dangle': ['error', 'never'], 'no-trailing-spaces': 'error', 'no-extra-semi': 'error', - 'no-unused-vars': ['error', { args: 'after-used', varsIgnorePattern: '^__unused' }], + 'no-unused-vars': ['error', { + args: 'after-used', + argsIgnorePattern: '^__unused', + caughtErrorsIgnorePattern: '^__unused', + destructuredArrayIgnorePattern: '^__unused', + varsIgnorePattern: '^__unused', + ignoreRestSiblings: true + }], 'semi': ['error', 'always'] }; const typescriptRules = { ...javascriptRules, 'no-unused-vars': 'off', - '@typescript-eslint/no-unused-vars': ['error', { args: 'after-used', varsIgnorePattern: '^__unused' }], - '@typescript-eslint/no-this-alias': 'off', - '@typescript-eslint/no-explicit-any': 'warn' + '@typescript-eslint/no-explicit-any': 'error', + '@typescript-eslint/no-non-null-assertion': 'error', + '@typescript-eslint/no-unused-vars': [ + 'error', + { + args: 'after-used', + argsIgnorePattern: '^__unused', + caughtErrorsIgnorePattern: '^__unused', + destructuredArrayIgnorePattern: '^__unused', + varsIgnorePattern: '^__unused', + ignoreRestSiblings: true + } + ], + '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/promise-function-async': 'error', + '@typescript-eslint/no-misused-promises': 'error' }; module.exports = { root: true, + env: { es6: true }, parserOptions: { + ecmaVersion: 2022, tsconfigRootDir: __dirname, + projectService: true, + allowDefaultProject: true, + warnOnUnsupportedTypeScriptVersion: false, project: [ './tsconfig.eslint.json', './packages/*-e2e/tsconfig.json', @@ -55,12 +80,30 @@ module.exports = { ] }, ignorePatterns: ['**/*', '!**/*.json', '!**/*.js', '!**/*.ts', '!scripts', '!tools', '!.vscode'], - plugins: ['@nx', 'json', 'import'], + plugins: ['@nx'], overrides: [ { - files: ['*.ts', '*.tsx'], + files: ['*.ts', '*.tsx', '*.mts'], + extends: ['plugin:@nx/typescript'], + rules: typescriptRules, + parserOptions: { + project: [ + './tsconfig.eslint.json' + ] + } + }, + { + files: ['*.test.ts', '*.test.tsx', '*.spec.ts', '*.spec.tsx'], extends: ['plugin:@nx/typescript'], - rules: typescriptRules + rules: { + ...typescriptRules, + '@typescript-eslint/no-explicit-any': 'warn' + }, + parserOptions: { + project: [ + './tsconfig.eslint.json' + ] + } }, { files: ['*.js', '*.jsx'], @@ -68,15 +111,17 @@ module.exports = { rules: javascriptRules }, { - files: ['*.spec.ts', '*.spec.tsx', '*.spec.js', '*.spec.jsx'], - env: { - jest: true - }, - rules: {} + files: ['*.mjs'], + extends: ['plugin:@nx/javascript'], + rules: javascriptRules, + parserOptions: { + sourceType: 'module' + } }, { files: ['*.json'], - extends: ['plugin:json/recommended'], + parser: 'jsonc-eslint-parser', + extends: ['plugin:jsonc/recommended-with-json'], rules: jsonRules } ] diff --git a/.gitguardian.yml b/.gitguardian.yml index 0bc6a2a0d..4e615e204 100644 --- a/.gitguardian.yml +++ b/.gitguardian.yml @@ -1,3 +1,3 @@ -paths-ignore: - - "**/*.test.*" - - "**/*.sample.json" +paths-ignore: + - "**/*.test.*" + - "**/*.sample.json" diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ee634af37..6777fb4b8 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,10 +4,45 @@ updates: directory: "/" schedule: interval: "weekly" - open-pull-requests-limit: 10 target-branch: "develop" + open-pull-requests-limit: 10 labels: - "dependencies" + groups: + nx: + patterns: + - "nx" + - "@nx/*" + - "@jscutlery/semver" + lint: + patterns: + - "eslint*" + - "@typescript-eslint/*" + dev: + patterns: + - "commitlint" + - "@commitlint/*" + - "@swc/*" + - "@svgr/webpack" + - "esbuild*" + - "postcss" + - "prettier" + - "webpack" + - "verdaccio" + - "typscript" + test: + patterns: + - "jsdom" + - "sinon-chrome" + - "jest*" + - "babel-jest" + babel: + patterns: + - "babel*" + - "@babel/*" + types: + patterns: + - "@types/*" - package-ecosystem: "github-actions" directory: "/" schedule: diff --git a/.github/workflows/99_ci.yml b/.github/workflows/99_ci.yml index ba878d72e..89d7162b1 100644 --- a/.github/workflows/99_ci.yml +++ b/.github/workflows/99_ci.yml @@ -1,90 +1,90 @@ -name: Test and Build CI - -on: [push] - -jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest] - node: [20.x] - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Derive appropriate SHAs for base and head for `nx affected` commands - uses: nrwl/nx-set-shas@v4 - with: - main-branch-name: master - error-on-no-successful-workflow: true - - - run: | - echo "Nx base: ${{ env.NX_BASE }}" - echo "Git head: ${{ env.NX_HEAD }}" - - - name: Using Node.js ${{ matrix.node }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node }} - cache: yarn - - - name: Disable TCP/UDP Offloading - shell: bash - run: | - if [ "$RUNNER_OS" == "Linux" ]; then - sudo ethtool -K eth0 tx off rx off - exit 0 - elif [ "$RUNNER_OS" == "macOS" ]; then - sudo sysctl -w net.link.generic.system.hwcksum_tx=0 - sudo sysctl -w net.link.generic.system.hwcksum_rx=0 - exit 0 - else - echo "$RUNNER_OS not supported" - exit 0 - fi - - - name: Installing dependencies - run: | - yarn install --network-timeout 1000000 --forzen-lockfile - - - name: Checking for lint - run: | - yarn run nx affected --target=lint --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} - - - name: Building packages - run: | - yarn run nx affected --target=build --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --parallel=1 - - - name: Running unit tests - run: | - yarn run nx affected --target=test --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --parallel=1 - env: - CI: true - - # email_test: - # runs-on: itmat - # strategy: - # matrix: - # node: [10.x, 12.x] - # steps: - # - uses: actions/checkout@v1 - # - name: Using Node.js ${{ matrix.node }} - # uses: actions/setup-node@v1 - # with: - # node-version: ${{ matrix.node }} - # - name: Installing dependencies - # run: yarn install - # - name: Checking for lint - # run: yarn run lint - # - name: Building packages - # run: yarn run build - # - name: Running unit tests - # run: yarn run test - # env: - # CI: true - # TEST_SMTP_CRED: ${{ secrets.TEST_SMTP_CRED }} - # TEST_SMTP_USERNAME: ${{ secrets.TEST_SMTP_USERNAME }} - # TEST_RECEIVER_EMAIL_ADDR: ${{ secrets.TEST_RECEIVER_EMAIL_ADDR }} +name: Test and Build CI + +on: [push] + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + node: [20.x] + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Derive appropriate SHAs for base and head for `nx affected` commands + uses: nrwl/nx-set-shas@v4 + with: + main-branch-name: master + error-on-no-successful-workflow: false + + - run: | + echo "Nx base: ${{ env.NX_BASE }}" + echo "Git head: ${{ env.NX_HEAD }}" + + - name: Using Node.js ${{ matrix.node }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + cache: yarn + + - name: Disable TCP/UDP Offloading + shell: bash + run: | + if [ "$RUNNER_OS" == "Linux" ]; then + sudo ethtool -K eth0 tx off rx off + exit 0 + elif [ "$RUNNER_OS" == "macOS" ]; then + sudo sysctl -w net.link.generic.system.hwcksum_tx=0 + sudo sysctl -w net.link.generic.system.hwcksum_rx=0 + exit 0 + else + echo "$RUNNER_OS not supported" + exit 0 + fi + + - name: Installing dependencies + run: | + yarn install --network-timeout 1000000 --forzen-lockfile + + - name: Checking for lint + run: | + yarn run nx affected --target=lint --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} + + - name: Building packages + run: | + yarn run nx affected --target=build --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --parallel=1 + + - name: Running unit tests + run: | + yarn run nx affected --target=test --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --parallel=1 + env: + CI: true + + # email_test: + # runs-on: itmat + # strategy: + # matrix: + # node: [10.x, 12.x] + # steps: + # - uses: actions/checkout@v1 + # - name: Using Node.js ${{ matrix.node }} + # uses: actions/setup-node@v1 + # with: + # node-version: ${{ matrix.node }} + # - name: Installing dependencies + # run: yarn install + # - name: Checking for lint + # run: yarn run lint + # - name: Building packages + # run: yarn run build + # - name: Running unit tests + # run: yarn run test + # env: + # CI: true + # TEST_SMTP_CRED: ${{ secrets.TEST_SMTP_CRED }} + # TEST_SMTP_USERNAME: ${{ secrets.TEST_SMTP_USERNAME }} + # TEST_RECEIVER_EMAIL_ADDR: ${{ secrets.TEST_RECEIVER_EMAIL_ADDR }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index b4c03545f..73c3f95fb 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,50 +1,49 @@ name: "Code scanning - action" on: - push: - # Dependabot triggered push events have read-only access, but uploading code - # scanning requires write access. - branches-ignore: [dependabot/**] - pull_request: - schedule: - - cron: '0 19 * * 0' + push: + # Dependabot triggered push events have read-only access, but uploading code + # scanning requires write access. + branches-ignore: [dependabot/**] + pull_request: + schedule: + - cron: "0 19 * * 0" jobs: - CodeQL-Build: - - # CodeQL runs on ubuntu-latest and windows-latest - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - # We must fetch at least the immediate parents so that if this is - # a pull request then we can checkout the head. - fetch-depth: 2 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - # Override language selection by uncommenting this and choosing your languages - # with: - # languages: go, javascript, csharp, python, cpp, java - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v3 - - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + CodeQL-Build: + # CodeQL runs on ubuntu-latest and windows-latest + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + # Override language selection by uncommenting this and choosing your languages + # with: + # languages: go, javascript, csharp, python, cpp, java + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.gitignore b/.gitignore index daec03df8..4bf62e191 100644 --- a/.gitignore +++ b/.gitignore @@ -62,7 +62,7 @@ typings/ .yarn-integrity # dotenv environment variables file -.env +.env*local # Certificate files server.crt diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 000000000..c366f5f8c --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1 @@ +yarn run lint-commit $1 \ No newline at end of file diff --git a/.husky/post-checkout b/.husky/post-checkout new file mode 100644 index 000000000..cab40f264 --- /dev/null +++ b/.husky/post-checkout @@ -0,0 +1,3 @@ +#!/bin/sh +command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting .git/hooks/post-checkout.\n"; exit 2; } +git lfs post-checkout "$@" diff --git a/.husky/post-commit b/.husky/post-commit new file mode 100644 index 000000000..9443f4161 --- /dev/null +++ b/.husky/post-commit @@ -0,0 +1,3 @@ +#!/bin/sh +command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting .git/hooks/post-commit.\n"; exit 2; } +git lfs post-commit "$@" diff --git a/.husky/post-merge b/.husky/post-merge new file mode 100644 index 000000000..828b70891 --- /dev/null +++ b/.husky/post-merge @@ -0,0 +1,3 @@ +#!/bin/sh +command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting .git/hooks/post-merge.\n"; exit 2; } +git lfs post-merge "$@" diff --git a/.husky/pre-commit b/.husky/pre-commit index ab0d663d6..965b10947 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -yarn lint-fix +yarn run lint-staged \ No newline at end of file diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100644 index 000000000..aeffc4a12 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,6 @@ +#!/bin/sh +command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting .git/hooks/pre-push.\n"; exit 2; } +git lfs pre-push "$@" + +yarn run lint-check +yarn run format-check \ No newline at end of file diff --git a/.lintstagedrc.json b/.lintstagedrc.json new file mode 100644 index 000000000..91c0f003a --- /dev/null +++ b/.lintstagedrc.json @@ -0,0 +1,3 @@ +{ + "package.json": "yarn lint --fix" +} \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..73b832d65 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,19 @@ +# Add files here to ignore them from prettier formatting + +/.nx/cache +/.nx/workspace-dat +*.json +*.ts +*.tsx +*.mts +*.mtsx +*.cts +*.ctsx +*.js +*.jsx +*.mjs +*.mjsx +*.cjs +*.cjsx +*.yaml +*.yml diff --git a/.vscode/settings.json b/.vscode/settings.json index 9af37e332..cafc29373 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,4 @@ { - "eslint.autoFixOnSave": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }, @@ -19,5 +18,11 @@ "**/.factorypath": true }, "css.format.enable": true, - "typescript.tsdk": "node_modules\\typescript\\lib" + "workbench.colorCustomizations": { + "titleBar.activeBackground": "#af2b70", + "titleBar.activeForeground": "#f1cee1", + "titleBar.inactiveBackground": "#d53488", + "titleBar.inactiveForeground": "#e6d5dd" + }, + "typescript.tsdk": "node_modules/typescript/lib" } \ No newline at end of file diff --git a/README.md b/README.md index 9eb9f8834..e99dc1292 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,8 @@ -[![Total alerts](https://img.shields.io/lgtm/alerts/g/ideafast/ideafast-portal.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/ideafast/ideafast-portal/alerts/) -[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/ideafast/ideafast-portal.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/ideafast/ideafast-portal/context:javascript) -[![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fideafast%2Fideafast-portal%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/ideafast/ideafast-portal/master) -![Build status](https://github.com/ideafast/ideafast-portal/workflows/Test%20and%20Build%20CI/badge.svg) -[![Dependency Status](https://img.shields.io/david/ideafast/ideafast-portal.svg)](https://david-dm.org/ideafast/ideafast-portal) -[![devDependency Status](https://img.shields.io/david/dev/ideafast/ideafast-portal.svg)](https://david-dm.org/ideafast/ideafast-portal?type=dev) - -![IDEAFast ID Generator](https://avatars3.githubusercontent.com/u/60649739?s=100&v=4) - -# The IDEAFast Feasibility Portal +# The Data Science Institute Data Management Portal ## Usage -The project is meant to be used as part of a Docker/Kubernetes container deployment. At present these container are not yet published and we invite you to look at the developer setup below. +The project is meant to be used as part of a Docker/Kubernetes container deployment. ## Local development @@ -34,17 +25,15 @@ Building the library is easy. First run `yarn install && yarn build`, to build t - itmat-docker - itmat-interface - itmat-job-executor -- itmat-models - itmat-setup - itmat-types - itmat-ui-react -- itmat-ui-react-e2e ### Requirements bootstrap (quick treatment) Using Docker to quickly setup a development environment for the DMP is easy. -*Note: The IP addresses, ranges, paths and container names used below serve as examples only, those should be adjusted to match you environment settings and requirements.* +_Note: The IP addresses, ranges, paths and container names used below serve as examples only, those should be adjusted to match you environment settings and requirements._ First create an environment file for the MinIO server: @@ -82,7 +71,7 @@ rs0 [direct: primary] admin> rs.status().ok 1 ``` -Depending on your DNS configuration you may need to add the container hostname map to IP (MongoDB listening) to your `/etc/hosts` file. +Depending on your DNS configuration you may need to add the container hostname map to IP (MongoDB listening) to your `/etc/hosts` file. `127.0.0.1 [hostname of MongoDB container]` (If your dmp service is running on the host machine, you need to add this line to the hosts file on your host machine, because the MongoDB client in the host machine will try to connect to the MongoDB server by its hostname of the container) @@ -132,5 +121,5 @@ Big shout out goes to all the members of the Data Science Institute team at Impe ## Contributing Pull requests are welcome! -See the [list of open issues](https://github.com/ideafast/ideafast-portal/issues) to get an idea of what you could work on. -Or, if you have an awesome idea, please [create a new issue](https://github.com/ideafast/ideafast-portal/issues/new). +See the [list of open issues](https://github.com/dsi-icl/dmp/issues) to get an idea of what you could work on. +Or, if you have an awesome idea, please [create a new issue](https://github.com/dsi-icl/dmp/issues/new). diff --git a/SECURITY.md b/SECURITY.md index 62844fc8f..93ecfdda4 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -7,10 +7,9 @@ currently being supported with security updates. | Version | Supported | | ------- | ------------------ | -| 2.5.1 | :white_check_mark: | -| < 2 | :x: | +| 3.1.0 | :white_check_mark: | +| < 3 | :x: | ## Reporting a Vulnerability -Please use to report security issues to you deem sensitive. -Otherwise, please feel free to use GitHub issues to let us know of any potential problem. +Please feel free to use GitHub issues to let us know of any potential problem. diff --git a/jest.config.ts b/jest.config.ts index 0330c7385..45309bd99 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,5 +1,5 @@ -import { getJestProjects } from '@nx/jest'; +import { getJestProjectsAsync } from '@nx/jest'; -export default { - projects: getJestProjects() -}; \ No newline at end of file +export default async () => ({ + projects: await getJestProjectsAsync() +}); \ No newline at end of file diff --git a/jest.preset.js b/jest.preset.cjs similarity index 100% rename from jest.preset.js rename to jest.preset.cjs diff --git a/lgtm.yml b/lgtm.yml index d1e1374df..e4cb7b808 100644 --- a/lgtm.yml +++ b/lgtm.yml @@ -1,3 +1,3 @@ -path_classifiers: - library: - - exclude: "**/msrCrypto.js" \ No newline at end of file +path_classifiers: + library: + - exclude: "**/msrCrypto.js" diff --git a/nx.json b/nx.json index 885ce7401..4d68b6371 100644 --- a/nx.json +++ b/nx.json @@ -7,9 +7,6 @@ } }, "extends": "nx/presets/npm.json", - "affected": { - "defaultBase": "master" - }, "generators": { "@nx/react": { "application": { @@ -27,14 +24,6 @@ } }, "targetDefaults": { - "lint": { - "inputs": [ - "default", - "{workspaceRoot}/.eslintrc.json", - "{workspaceRoot}/.eslintrc.js" - ], - "cache": true - }, "e2e": { "inputs": [ "default", @@ -48,7 +37,7 @@ "inputs": [ "default", "^default", - "{workspaceRoot}/jest.preset.js" + "{workspaceRoot}/jest.preset.cjs" ], "cache": true, "options": { @@ -60,9 +49,20 @@ "codeCoverage": true } } + }, + "@nx/eslint:lint": { + "inputs": [ + "default", + "{workspaceRoot}/.eslintrc.json", + "{workspaceRoot}/.eslintrc.js" + ], + "cache": true } }, "defaultProject": "itmat-ui-react", "$schema": "./node_modules/nx/schemas/nx-schema.json", - "nxCloudAccessToken": "ZDQ1NzgyNGEtZTAzMC00MWE2LWIxZTAtOTRmYzhjMzdjYWYyfHJlYWQtd3JpdGU=" + "defaultBase": "master", + "useInferencePlugins": false, + "nxCloudAccessToken": "YzA0MTA2MTEtNWRkNC00Y2QzLWFhYTItZDE0YjkxMDVlOGMwfHJlYWQtd3JpdGU=", + "useLegacyCache": true } \ No newline at end of file diff --git a/package.json b/package.json index f0a426b65..81faad826 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "itmat-broker", - "version": "2.5.1", + "version": "3.1.0", "private": true, "license": "MIT", "scripts": { - "prepare": "husky install", - "postinstall": "husky install", + "prepare": "husky", + "postinstall": "husky", "preinstall": "node ./scripts/hintYarn.js", "dev": "yarn serve", "serve": "nx run-many --parallel=5 --projects=itmat-job-executor,itmat-interface,itmat-ui-react --target=serve", @@ -19,149 +19,180 @@ "setupDatabase": "nx run-many --projects=itmat-setup --target=setup", "test": "nx affected --parallel=1 --target=test", "test-all": "nx run-many --parallel=1 --target=test", - "lint": "nx affected --target=lint", - "lint-fix": "nx affected --target=lint --fix" + "lint": "eslint .", + "lint-check": "nx affected --target=lint --parallel=1 && yarn lint", + "lint-fix": "nx affected --target=lint --parallel=1 --fix && yarn lint --fix", + "lint-commit": "commitlint -c false --edit", + "lint-staged": "lint-staged", + "format-check": "nx format:check --all", + "format-fix": "nx format --all" }, "devDependencies": { - "@babel/core": "^7.23.6", - "@babel/preset-react": "^7.23.3", - "@nx/cypress": "17.2.7", - "@nx/esbuild": "17.2.7", - "@nx/eslint": "17.2.7", - "@nx/eslint-plugin": "17.2.7", - "@nx/express": "17.2.7", - "@nx/jest": "17.2.7", - "@nx/js": "17.2.7", - "@nx/node": "17.2.7", - "@nx/react": "17.2.7", - "@nx/web": "17.2.7", - "@nx/webpack": "17.2.7", - "@nx/workspace": "17.2.7", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", + "@babel/core": "^7.26.9", + "@babel/preset-react": "^7.26.3", + "@commitlint/config-conventional": "^18.6.3", + "@commitlint/config-nx-scopes": "^18.6.1", + "@nx/esbuild": "20.4.6", + "@nx/eslint": "20.4.6", + "@nx/eslint-plugin": "20.4.6", + "@nx/express": "20.4.6", + "@nx/jest": "20.4.6", + "@nx/js": "20.4.6", + "@nx/node": "20.4.6", + "@nx/react": "20.4.6", + "@nx/web": "20.4.6", + "@nx/webpack": "20.4.6", + "@nx/workspace": "20.4.6", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", "@svgr/webpack": "8.1.0", - "@swc/cli": "0.1.63", - "@swc/core": "1.3.101", - "@swc/jest": "0.2.29", - "@testing-library/react": "14.1.2", + "@swc/cli": "0.6.0", + "@swc/core": "1.11.6", + "@swc/jest": "0.2.37", + "@testing-library/react": "16.2.0", "@types/bcrypt": "5.0.2", "@types/connect-timeout": "0.0.39", "@types/cors": "2.8.17", "@types/express": "4.17.21", - "@types/express-session": "1.17.10", + "@types/express-session": "1.18.1", "@types/fs-extra": "11.0.4", - "@types/jest": "29.5.11", + "@types/jest": "29.5.14", "@types/json2csv": "5.0.7", - "@types/jsonwebtoken": "9.0.5", - "@types/node": "18.16.9", - "@types/nodemailer": "6.4.14", - "@types/passport": "1.0.16", + "@types/jsonwebtoken": "9.0.9", + "@types/node": "^22.13.9", + "@types/nodemailer": "6.4.17", + "@types/passport": "1.0.17", "@types/qrcode": "1.5.5", - "@types/react": "18.2.45", - "@types/react-dom": "18.2.18", - "@types/react-highlight-words": "0.16.7", - "@types/supertest": "2.0.16", + "@types/react": "18.3.18", + "@types/react-dom": "18.3.5", + "@types/react-highlight-words": "0.20.0", + "@types/supertest": "6.0.2", "@types/tmp": "0.2.6", - "@types/uuid": "9.0.7", - "@types/webpack-env": "1.18.4", - "@typescript-eslint/eslint-plugin": "6.9.1", - "@typescript-eslint/parser": "6.9.1", - "css-loader": "^6.8.1", - "cypress": "^13.6.1", - "eslint": "8.48.0", - "eslint-config-prettier": "9.1.0", - "eslint-plugin-cypress": "2.15.1", - "eslint-plugin-import": "2.29.1", - "eslint-plugin-json": "3.1.0", - "eslint-plugin-jsx-a11y": "6.8.0", - "eslint-plugin-react": "7.33.2", - "eslint-plugin-react-hooks": "4.6.0", - "get-port": "7.0.0", + "@types/uuid": "10.0.0", + "@types/webpack-env": "1.18.8", + "@typescript-eslint/eslint-plugin": "8.26.0", + "@typescript-eslint/parser": "8.26.0", + "commitlint": "^18.6.1", + "esbuild": "^0.25.0", + "eslint": "8.57.1", + "eslint-config-prettier": "10.0.2", + "eslint-plugin-import": "2.31.0", + "eslint-plugin-jsonc": "2.19.1", + "eslint-plugin-jsx-a11y": "6.10.2", + "eslint-plugin-react": "7.37.4", + "eslint-plugin-react-hooks": "5.2.0", + "get-port": "7.1.0", "git-rev-sync": "3.0.2", - "husky": "8.0.3", - "jest": "29.7.0", - "jest-environment-jsdom": "29.7.0", - "mongodb-memory-server": "9.1.3", - "nx": "17.2.7", - "react-refresh": "^0.14.0", - "style-loader": "^3.3.3", - "stylus": "^0.62.0", - "stylus-loader": "^7.1.3", - "supertest": "6.3.3", - "swc-loader": "0.2.3", - "ts-essentials": "9.4.1", - "ts-jest": "29.1.1", + "husky": "^9.1.7", + "jest": "^30.0.0-alpha.7", + "jest-environment-jsdom": "^30.0.0-alpha.7", + "mongodb-memory-server": "10.1.4", + "nx": "20.4.6", + "prettier": "^3.5.3", + "react-refresh": "^0.16.0", + "supertest": "7.0.0", + "swc-loader": "0.2.6", + "ts-essentials": "10.0.4", + "ts-jest": "29.2.6", "ts-node": "10.9.2", - "typescript": "5.3.3", + "typescript": "5.8.2", "url-loader": "^4.1.1", - "webpack": "^5.89.0", - "webpack-merge": "^5.10.0" + "webpack": "^5.98.0", + "webpack-merge": "^6.0.1" }, "dependencies": { - "@ant-design/icons": "5.2.6", - "@ant-design/plots": "1.2.6", - "@apollo/client": "3.8.8", - "@apollo/server": "4.9.5", - "@ideafast/idgen": "0.1.1", - "@swc/helpers": "0.5.3", + "@ant-design/icons": "5.6.1", + "@nivo/bar": "0.88.0", + "@nivo/calendar": "0.88.0", + "@nivo/core": "0.88.0", + "@nivo/line": "0.88.0", + "@nivo/pie": "0.88.0", + "@nivo/treemap": "0.88.0", + "@simplewebauthn/browser": "^10.0.0", + "@simplewebauthn/server": "^10.0.1", + "@swc/helpers": "0.5.15", + "@tanstack/react-query": "4.36.1", + "@trpc/client": "10.45.2", + "@trpc/react-query": "10.45.2", + "@trpc/server": "10.45.2", "JSONStream": "1.3.5", - "antd": "5.12.5", - "apollo-upload-client": "18.0.1", - "axios": "1.6.2", + "antd": "5.24.2", + "antd-img-crop": "4.24.0", + "axios": "1.8.1", + "axios-retry": "^4.5.0", "bcrypt": "5.1.1", "connect-mongo": "5.1.0", "connect-timeout": "1.9.0", - "core-js": "^3.34.0", + "core-js": "^3.41.0", "cors": "2.8.5", - "csv-parse": "5.5.3", - "dayjs": "1.11.10", + "csv-parse": "5.6.0", + "dayjs": "1.11.13", "deepmerge": "4.3.1", - "esbuild": "0.19.10", - "export-from-json": "1.7.3", - "express": "^4.18.2", - "express-rate-limit": "7.1.5", - "express-session": "1.17.3", - "fs-extra": "11.2.0", - "graphql": "16.8.1", - "graphql-scalars": "1.22.4", - "graphql-subscriptions": "2.0.0", - "graphql-tag": "2.12.6", - "graphql-type-json": "0.3.2", - "graphql-upload-minimal": "1.5.5", - "graphql-ws": "5.14.3", + "export-from-json": "1.7.4", + "express": "^4.21.2", + "express-rate-limit": "7.5.0", + "express-session": "1.18.1", + "fs-extra": "11.3.0", "hi-base32": "0.5.1", - "http-proxy-middleware": "2.0.6", + "http-proxy-middleware": "3.0.3", "https-browserify": "1.0.0", + "json-bigint-patch": "0.0.8", "jsonwebtoken": "9.0.2", "jstat": "1.9.6", - "minio": "7.1.3", - "mongodb": "6.3.0", - "nodemailer": "6.9.7", + "lint-staged": "15.4.3", + "localforage": "^1.10.0", + "lodash-es": "4.17.21", + "minio": "8.0.4", + "mongodb": "6.14.2", + "multer": "1.4.4", + "nodemailer": "6.10.0", "passport": "0.7.0", "path-browserify": "1.0.1", - "qrcode": "1.5.3", - "react": "18.2.0", + "qrcode": "1.5.4", + "rc-picker": "4.11.3", + "react": "18.3.1", "react-csv": "2.2.2", - "react-dom": "18.2.0", - "react-dropzone": "14.2.3", - "react-helmet-async": "2.0.4", - "react-highlight-words": "0.20.0", - "react-router-dom": "6.21.1", + "react-dom": "18.3.1", + "react-dropzone": "14.3.8", + "react-helmet-async": "2.0.5", + "react-highlight-words": "0.21.0", + "react-router-dom": "6.30.0", + "react-spinners": "0.15.0", "regenerator-runtime": "0.14.1", "sanitize-filename": "1.6.3", "stream-browserify": "3.0.0", "stream-http": "3.2.0", - "tmp": "0.2.1", - "tslib": "^2.6.2", - "url": "0.11.3", - "uuid": "9.0.1" + "tmp": "0.2.3", + "tslib": "^2.8.1", + "tsutils": "^3.21.0", + "url": "0.11.4", + "uuid": "11.1.0", + "webdav-server": "2.6.2", + "zod": "3.24.2" }, "resolutions": { - "**/@jest/create-cache-key-function": "^29", - "**/@jest/reporters": "^29", - "**/@jest/test-result": "^29", - "**/jest-config": "^29", - "**/jest-resolve": "^29", - "**/jest-util": "^29", + "**/@jest/create-cache-key-function": "^30.0.0-alpha.7", + "**/@jest/reporters": "^30.0.0-alpha.7", + "**/@jest/schemas": "^30.0.0-alpha.7", + "**/@jest/test-result": "^30.0.0-alpha.7", + "**/@jest/types": "^30.0.0-alpha.7", + "**/@types/express": "^4", + "**/@module-federation/enhanced": "^0.10", + "**/@module-federation/runtime": "^0.10", + "**/@module-federation/runtime-tools": "^0.10", + "**/@module-federation/sdk": "^0.10", + "**/@types/express-serve-static-core": "^4", + "**/@types/react": "^18", + "**/@types/react-dom": "^18", + "**/dicer": "^0.3.1", + "**/expect": "^30.0.0-alpha.7", + "**/mongodb": "^6", + "**/jest-config": "^30.0.0-alpha.7", + "**/jest-diff": "^30.0.0-alpha.7", + "**/jest-get-type": "^30.0.0-alpha.7", + "**/jest-resolve": "^30.0.0-alpha.7", + "**/jest-util": "^30.0.0-alpha.7", + "**/jest-worker": "^30.0.0-alpha.7", "**/pretty-format": "^29" - } -} \ No newline at end of file + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" +} diff --git a/packages/itmat-models/.eslintrc.json b/packages/itmat-apis/.eslintrc.json similarity index 100% rename from packages/itmat-models/.eslintrc.json rename to packages/itmat-apis/.eslintrc.json diff --git a/packages/itmat-models/.lib.swcrc b/packages/itmat-apis/.lib.swcrc similarity index 91% rename from packages/itmat-models/.lib.swcrc rename to packages/itmat-apis/.lib.swcrc index 5442653a0..4bc7f5daf 100644 --- a/packages/itmat-models/.lib.swcrc +++ b/packages/itmat-apis/.lib.swcrc @@ -1,6 +1,6 @@ { "jsc": { - "target": "es2017", + "target": "ES2022", "parser": { "syntax": "typescript", "decorators": true, @@ -17,7 +17,7 @@ "module": { "type": "commonjs", "strict": true, - "noInterop": true + "noInterop": false }, "sourceMaps": true, "exclude": [ diff --git a/packages/itmat-apis/README.md b/packages/itmat-apis/README.md new file mode 100644 index 000000000..80d02546d --- /dev/null +++ b/packages/itmat-apis/README.md @@ -0,0 +1,11 @@ +# itmat-apis + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build itmat-apis` to build the library. + +## Running unit tests + +Run `nx test itmat-apis` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/packages/itmat-apis/config/config.sample.json b/packages/itmat-apis/config/config.sample.json new file mode 100644 index 000000000..2bcd4fff3 --- /dev/null +++ b/packages/itmat-apis/config/config.sample.json @@ -0,0 +1,60 @@ +{ + "appName": "ITMAT Interface", + "database": { + "mongo_url": "mongodb://localhost:27017", + "database": "itmat", + "collections": { + "jobs_collection": "JOB_COLLECTION", + "users_collection": "USER_COLLECTION", + "organisations_collection": "ORGANISATION_COLLECTION", + "studies_collection": "STUDY_COLLECTION", + "projects_collection": "PROJECT_COLLECTION", + "queries_collection": "QUERY_COLLECTION", + "log_collection": "LOG_COLLECTION", + "data_collection": "DATA_COLLECTION", + "roles_collection": "ROLE_COLLECTION", + "field_dictionary_collection": "FIELD_COLLECTION", + "files_collection": "FILES_COLLECTION", + "sessions_collection": "SESSIONS_COLLECTION", + "pubkeys_collection": "PUBKEY_COLLECTION", + "standardizations_collection": "STANDARDIZATION_COLLECTION", + "configs_collection": "CONFIG_COLLECTION", + "ontologies_collection": "ONTOLOGY_COLLECTION", + "docs_collection": "DOC_COLLECTION", + "cache_collection": "CACHE_COLLECTION", + "drives_collection": "DRIVE_COLLECTION", + "colddata_collection": "COLDDATA_COLLECTION", + "domains_collection": "DOMAIN_COLLECTION" + } + }, + "server": { + "port": 3333 + }, + "bcrypt": { + "saltround": 2 + }, + "objectStore": { + "host": "localhost", + "port": 8080, + "accessKey": "minioadmin", + "secretKey": "minioadmin", + "bucketRegion": "region", + "useSSL": false + }, + "sessionsSecret": "Change_Me", + "aesSecret": "change_this", + "nodemailer": { + "auth": { + "user": "change_this", + "pass": "change_this" + } + }, + "lxdCertFile": { + "cert": "/path/mylxd.crt", + "key": "/path/mylxd.key" + }, + "adminEmail": "admin@example.com", + "aeEndpoint": "http://localhost:9090", + "useWebdav": true, + "webdavPort": 1900 +} \ No newline at end of file diff --git a/packages/itmat-apis/express-user.d.ts b/packages/itmat-apis/express-user.d.ts new file mode 100644 index 000000000..de9b48a82 --- /dev/null +++ b/packages/itmat-apis/express-user.d.ts @@ -0,0 +1,17 @@ +import type { IUserWithoutToken } from '@itmat-broker/itmat-types'; + +declare global { + + namespace Express { + + // eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/no-empty-object-type + interface User extends IUserWithoutToken { } + + interface Request { + user?: User; + headers: IncomingHttpHeaders; + login(user: User, done: (err: unknown) => void): void; + logout(done: (err: unknown) => void): void; + } + } +} \ No newline at end of file diff --git a/packages/itmat-models/jest.config.ts b/packages/itmat-apis/jest.config.ts similarity index 56% rename from packages/itmat-models/jest.config.ts rename to packages/itmat-apis/jest.config.ts index 7e19952c4..7601125db 100644 --- a/packages/itmat-models/jest.config.ts +++ b/packages/itmat-apis/jest.config.ts @@ -7,11 +7,17 @@ const { exclude: _, ...swcJestConfig } = JSON.parse( readFileSync(`${__dirname}/.lib.swcrc`, 'utf-8') ); export default { - displayName: 'itmat-models', - preset: '../../jest.preset.js', + displayName: 'itmat-apis', + preset: '../../jest.preset.cjs', transform: { '^.+\\.[tj]s$': ['@swc/jest', swcJestConfig], }, moduleFileExtensions: ['ts', 'js', 'html'], - coverageDirectory: '../../coverage/packages/itmat-models' + coverageDirectory: '../../coverage/packages/itmat-apis', + testEnvironment: "/../../test/fixtures/_minioJestEnv", + transformIgnorePatterns: [ + "node_modules", + "\\.pnp\\.[^\\\/]+$", + "test[\\/]fixtures[\\/]_minio" + ] }; diff --git a/packages/itmat-models/package.json b/packages/itmat-apis/package.json similarity index 54% rename from packages/itmat-models/package.json rename to packages/itmat-apis/package.json index a8d6b71cb..5eb7c3322 100644 --- a/packages/itmat-models/package.json +++ b/packages/itmat-apis/package.json @@ -1,5 +1,5 @@ { - "name": "@itmat-broker/itmat-models", + "name": "@itmat-broker/itmat-apis", "version": "0.0.1", "type": "commonjs" } \ No newline at end of file diff --git a/packages/itmat-apis/project.json b/packages/itmat-apis/project.json new file mode 100644 index 000000000..a468a634e --- /dev/null +++ b/packages/itmat-apis/project.json @@ -0,0 +1,42 @@ +{ + "name": "itmat-apis", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/itmat-apis/src", + "projectType": "library", + "tags": [], + "targets": { + "build": { + "executor": "@nx/esbuild:esbuild", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/itmat-apis", + "main": "packages/itmat-apis/src/index.ts", + "tsConfig": "packages/itmat-apis/tsconfig.lib.json", + "assets": [ + "packages/itmat-apis/*.md" + ], + "thirdParty": true, + "external": [ + "bcrypt" + ] + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ] + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": [ + "{workspaceRoot}/coverage/packages/itmat-apis" + ], + "options": { + "jestConfig": "packages/itmat-apis/jest.config.ts" + } + } + } +} \ No newline at end of file diff --git a/packages/itmat-apis/src/index.ts b/packages/itmat-apis/src/index.ts new file mode 100644 index 000000000..49a930b30 --- /dev/null +++ b/packages/itmat-apis/src/index.ts @@ -0,0 +1,16 @@ +// trpc +export * from './trpc/configProcedure'; +export * from './trpc/dataProcedure'; +export * from './trpc/domainProcedure'; +export * from './trpc/driveProcedure'; +export * from './trpc/logProcedure'; +export * from './trpc/organisationProcedure'; +export * from './trpc/roleProcedure'; +export * from './trpc/studyProcedure'; +export * from './trpc/trpc'; +export * from './trpc/tRPCRouter'; +export * from './trpc/userProcedure'; +export * from './trpc/middleware'; +export * from './trpc/webauthnProcedure'; +export * from './trpc/instanceProcedure'; +export * from './trpc/lxdProcedure'; diff --git a/packages/itmat-apis/src/trpc/configProcedure.ts b/packages/itmat-apis/src/trpc/configProcedure.ts new file mode 100644 index 000000000..c735169f6 --- /dev/null +++ b/packages/itmat-apis/src/trpc/configProcedure.ts @@ -0,0 +1,148 @@ +import { TRPCBaseProcedure, TRPCRouter } from './trpc'; +import { z } from 'zod'; +import { ConfigCore } from '@itmat-broker/itmat-cores'; +import { enumConfigType, enumStudyBlockColumnValueType } from '@itmat-broker/itmat-types'; + +const ZBase = z.object({ + id: z.string(), + life: z.object({ + createdTime: z.number(), + createdUser: z.string(), + deletedTime: z.number().nullable(), + deletedUser: z.string().nullable() + }), + metadata: z.record(z.unknown()) +}); + +// ZSystemConfig schema +const ZSystemConfig = ZBase.extend({ + defaultBackgroundColor: z.string(), + defaultMaximumFileSize: z.number(), + defaultFileBucketId: z.string(), + defaultProfileBucketId: z.string(), + logoLink: z.string().nullable(), + logoSize: z.array(z.string()), + archiveAddress: z.string(), + defaultEventTimeConsumptionBar: z.array(z.number()), + defaultUserExpireDays: z.number() +}); + +// ZStudyConfig schema +const ZStudyConfig = ZBase.extend({ + defaultStudyProfile: z.string().nullable(), + defaultMaximumFileSize: z.number(), + defaultRepresentationForMissingValue: z.string(), + defaultFileBlocks: z.array(z.object({ + title: z.string(), + fieldIds: z.array(z.string()), + defaultFileColumns: z.array(z.object({ + title: z.string(), + property: z.string(), + type: z.nativeEnum(enumStudyBlockColumnValueType) + })), + defaultFileColumnsPropertyColor: z.string() + })), + defaultVersioningKeys: z.array(z.string()) +}); + +// ZUserConfig schema +const ZUserConfig = ZBase.extend({ + defaultUserExpiredDays: z.number(), + defaultMaximumFileSize: z.number(), + defaultMaximumFileRepoSize: z.number(), + defaultMaximumRepoSize: z.number(), + defaultFileBucketId: z.string(), + defaultMaximumQPS: z.number(), + defaultLXDMaximumInstances: z.number(), + defaultLXDMaximumInstanceCPUCores: z.number(), + defaultLXDMaximumInstanceDiskSize: z.number(), + defaultLXDMaximumInstanceMemory: z.number(), + defaultLXDMaximumInstanceLife: z.number() +}); + +// ZOrganisationConfig schema + +// ZCacheConfig schema +const ZCacheConfig = ZBase.extend({ + defaultFileBucketId: z.string(), + defaultMaximumFileSize: z.number() +}); + +// ZDocConfig schema +const ZDocConfig = ZBase.extend({ + defaultFileBucketId: z.string(), + defaultMaximumFileSize: z.number() +}); + +// ZDomainConfig schema +const ZDomainConfig = ZBase.extend({ + defaultFileBucketId: z.string(), + defaultMaximumFileSize: z.number() +}); + +export class ConfigRouter { + baseProcedure: TRPCBaseProcedure; + router: TRPCRouter; + configCore: ConfigCore; + constructor(baseProcedure: TRPCBaseProcedure, router: TRPCRouter, configCore: ConfigCore) { + this.baseProcedure = baseProcedure; + this.router = router; + this.configCore = configCore; + } + + _router() { + return this.router({ + /** + * Get the config. + * + * @param configType - The type of the config. + * @param key - The key of the config. studyid, userid, or null for system. + * @param useDefault - Whether to use the default config if not found. + * + * @returns IConfig + */ + getConfig: this.baseProcedure.input(z.object({ + configType: z.nativeEnum(enumConfigType), + key: z.union([z.string(), z.null()]), + useDefault: z.boolean() + })).query(async (opts) => { + return await this.configCore.getConfig(opts.input.configType, opts.input.key, opts.input.useDefault); + }), + /** + * Edit the config. + * + * @param configType - The type of the config. + * @param key - The key of the config. + * @param properties - The updated properties. + * + * @returns IGenericResponse + */ + editConfig: this.baseProcedure.input(z.object({ + configType: z.nativeEnum(enumConfigType), + key: z.union([z.string(), z.null()]), + properties: z.union([ZSystemConfig, ZStudyConfig, ZUserConfig, ZDocConfig, ZCacheConfig, ZDomainConfig]) + })).mutation(async (opts) => { + return await this.configCore.editConfig(opts.ctx.user, opts.input.configType, opts.input.key, opts.input.properties); + }), + /** + * Create the config. + * + * @param configType - The type of the config. + * @param key - The key of the config. + * @param properties - The updated properties. + * + * @returns IGenericResponse + */ + createConfig: this.baseProcedure.input(z.object({ + configType: z.nativeEnum(enumConfigType), + key: z.union([z.string(), z.null()]), + properties: z.union([ZSystemConfig, ZStudyConfig, ZUserConfig, ZDocConfig, ZCacheConfig, ZDomainConfig]) + })).mutation(async (opts) => { + return await this.configCore.createConfig(opts.ctx.user, opts.input.configType, opts.input.key, opts.input.properties); + }) + }); + } +} + + + diff --git a/packages/itmat-apis/src/trpc/dataProcedure.ts b/packages/itmat-apis/src/trpc/dataProcedure.ts new file mode 100644 index 000000000..9336611bd --- /dev/null +++ b/packages/itmat-apis/src/trpc/dataProcedure.ts @@ -0,0 +1,391 @@ +import { FileUploadSchema, IAST, enumASTNodeTypes, enumConditionOps, enumDataTransformationOperation, enumDataTypes, enumMathOps } from '@itmat-broker/itmat-types'; +import { z } from 'zod'; +import { TRPCBaseProcedure, TRPCRouter } from './trpc'; +import { DataCore } from '@itmat-broker/itmat-cores'; + +const ZAST: z.ZodType = z.lazy(() => z.object({ + type: z.nativeEnum(enumASTNodeTypes), + operator: z.union([z.nativeEnum(enumMathOps), z.null()]), + value: z.union([z.number(), z.string(), z.null()]), + parameters: z.record(z.string(), z.unknown()), + children: z.union([z.array(ZAST), z.null()]) // null for lead node; OPERATION type should not be a lead node. +})); + +const ZValueVerifier = z.object({ + formula: ZAST, + condition: z.nativeEnum(enumConditionOps), + value: z.union([z.string(), z.number()]), + parameters: z.record(z.string(), z.unknown()) +}); + +const ZFieldProperty = z.object({ + name: z.string(), + verifier: z.optional(z.array(z.array(ZValueVerifier))), + description: z.optional(z.string()), + required: z.boolean() +}); + +const ZCategoricalOption = z.object({ + code: z.string(), + description: z.string() +}); + +const CreateFieldInputSchema = z.object({ + studyId: z.string(), + fieldName: z.string(), + fieldId: z.string(), + description: z.optional(z.string()), + dataType: z.nativeEnum(enumDataTypes), + categoricalOptions: z.optional(z.array(ZCategoricalOption)), + unit: z.optional(z.string()), + comments: z.optional(z.string()), + verifier: z.optional(z.array(z.array(ZValueVerifier))), + properties: z.optional(z.array(ZFieldProperty)), + metadata: z.optional(z.record(z.string(), z.unknown())) +}); + +const EditFieldInputSchema = CreateFieldInputSchema; + +const ZDataClipInput = z.object({ + fieldId: z.string(), + value: z.string(), + timestamps: z.optional(z.number()), + properties: z.optional(z.any()) +}); + +export class DataRouter { + baseProcedure: TRPCBaseProcedure; + router: TRPCRouter; + dataCore: DataCore; + constructor(baseProcedure: TRPCBaseProcedure, router: TRPCRouter, dataCore: DataCore) { + this.baseProcedure = baseProcedure; + this.router = router; + this.dataCore = dataCore; + } + + _router() { + return this.router({ + /** + * Get the list of fields of a study. + * + * @param studyId - The id of the study. + * @param projectId - The id of the project. + * @param versionId - The id of the version. By default, we will return data until this version. If not specificed, will return the latest versioned data. + * + * @return IField - The list of objects of IField. + */ + getStudyFields: this.baseProcedure.input(z.object({ + studyId: z.string(), + versionId: z.optional(z.union([z.string(), z.null(), z.array(z.union([z.string(), z.null()]))])), + fieldIds: z.optional(z.array(z.string())) + })).query(async (opts) => { + return await this.dataCore.getStudyFields(opts.ctx.user, opts.input.studyId, opts.input.versionId, opts.input.fieldIds); + }), + /** + * Create a field of a study. To adjust to data versioning, create an existing field wil not throw an error. + * + * @param studyId - The id of the study. + * @param fieldName - The name of the field. + * @param fieldId - The value of the id of the field. Should be unique. + * @param description - The description of the field. + * @param dataType - The dataType of the field. + * @param categoricalOptions - The options of the field if the field is a categorical field. + * @param unit - The unit of the field. + * @param comments - The comments of the field. + * @param verifier - The verifier of the field. + * @param properties - The properties of the field. + * + * @return IField + */ + createStudyField: this.baseProcedure.input(CreateFieldInputSchema).mutation(async (opts) => { + return await this.dataCore.createField(opts.ctx.user, { + studyId: opts.input.studyId, + fieldName: opts.input.fieldName, + fieldId: opts.input.fieldId, + description: opts.input.description, + dataType: opts.input.dataType, + categoricalOptions: opts.input.categoricalOptions, + unit: opts.input.unit, + comments: opts.input.comments, + verifier: opts.input.verifier, + properties: opts.input.properties, + metadata: opts.input.metadata + }); + }), + /** + * Edit a field of a study. + * + * @param studyId - The id of the study. + * @param fieldName - The name of the field. + * @param fieldId - The value of the id of the field. Should be unique. + * @param description - The description of the field. + * @param dataType - The dataType of the field. + * @param categoricalOptions - The options of the field if the field is a categorical field. + * @param unit - The unit of the field. + * @param comments - The comments of the field. + * @param verifier - The verifier of the field. + * @param properties - The properties of the field. + * + * @return IField + */ + editStudyField: this.baseProcedure.input(EditFieldInputSchema).mutation(async (opts) => { + return await this.dataCore.editField(opts.ctx.user, { + studyId: opts.input.studyId, + fieldName: opts.input.fieldName, + fieldId: opts.input.fieldId, + description: opts.input.description, + dataType: opts.input.dataType, + categoricalOptions: opts.input.categoricalOptions, + unit: opts.input.unit, + comments: opts.input.comments, + verifier: opts.input.verifier, + properties: opts.input.properties + }); + }), + /** + * Delete a field of a study. + * + * @param studyId - The id of the stduy. + * @param fieldId - The id of the field. + * + * @return IGenericResponse + */ + deleteStudyField: this.baseProcedure.input(z.object({ + studyId: z.string(), + fieldId: z.string() + })).mutation(async (opts) => { + return await this.dataCore.deleteField(opts.ctx.user, opts.input.studyId, opts.input.fieldId); + }), + /** + * Upload data clips to a study. + * + * @param requester - The id of the requester. + * @param studyId - The id of the study. + * @param data - The list of data clips. + * + * @return IGenericResponse - The list of objects of IGenericResponse + */ + uploadStudyData: this.baseProcedure.input(z.object({ + studyId: z.string(), + data: z.array(ZDataClipInput) + })).mutation(async (opts) => { + return await this.dataCore.uploadData( + opts.ctx.req.user, + opts.input.studyId, + opts.input.data + ); + }), + /** + * Get the data of a study. + * + * @param studyId - The id of the study. + * @param versionId - The id of the data version. By default not specified for the latest version. + * @param aggregation - The aggregation pipeline. Used for data post preocessing. + * @param fieldIds - The list of fields to return. + * @param useCache - Whether to use fetch the data from cache. + * @param forceUpdate - Whether to update the cache with the results from this call. + * + * @return Partial[] - The list of objects of Partial + */ + getStudyData: this.baseProcedure.input(z.object({ + studyId: z.string(), + versionId: z.optional(z.union([z.string(), z.null(), z.array(z.union([z.string(), z.null()]))])), + aggregation: z.optional(z.record(z.string(), z.array(z.object({ + operationName: z.nativeEnum(enumDataTransformationOperation), + params: z.record(z.string(), z.unknown()) + })))), + fieldIds: z.optional(z.array(z.string())), + useCache: z.optional(z.boolean()), + forceUpdate: z.optional(z.boolean()), + fromCold: z.optional(z.boolean()) + })).query(async (opts) => { + return await this.dataCore.getStudyData( + opts.ctx.req.user, + opts.input.studyId, + opts.input.fieldIds, + opts.input.versionId, + opts.input.aggregation, + opts.input.useCache, + opts.input.forceUpdate, + opts.input.fromCold + ); + }), + /** + * Get the data of a study filtered by dataVersion. This is a simplified version of the getStudyData function. + * + * @param studyId - The id of the study. + * @param versionId - The id of the data version. By default not specified for the latest version. + * @param aggregation - The aggregation pipeline. Used for data post preocessing. + * @param fieldIds - The list of fields to return. + * @param useCache - Whether to use fetch the data from cache. + * @param forceUpdate - Whether to update the cache with the results from this call. + * + * @return Partial[] - The list of objects of Partial + */ + getStudyDataLatest: this.baseProcedure.input(z.object({ + studyId: z.string(), + fieldIds: z.optional(z.array(z.string())) + })).query(async (opts) => { + return await this.dataCore.getDataLatest( + opts.ctx.req.user, + opts.input.studyId, + opts.input.fieldIds + ); + }), + /** + * Delete data of a study. We add a deleted document in the database. + * + * @param requester - The id of the requester. + * @param studyId - The id of the study. + * @param documentId - The id of the mongo document. + * + * @return IGenreicResponse - The object of IGenericResponse. + */ + deleteStudyData: this.baseProcedure.input(z.object({ + studyId: z.string(), + fieldId: z.string(), + properties: z.optional(z.any()) + })).mutation(async (opts) => { + return await this.dataCore.deleteData( + opts.ctx.req.user, + opts.input.studyId, + opts.input.fieldId, + opts.input.properties + ); + }), + /** + * Upload a data file. + * + * @param studyId - The id of the study. + * @param file - The file to upload. + * @param properties - The properties of the file. Need to match field properties if defined. + * @param fieldId - The id of the field. + * + * @return IData + */ + uploadStudyFileData: this.baseProcedure.input(z.object({ + studyId: z.string(), + files: z.object({ + file: z.array(FileUploadSchema) + }), + properties: z.optional(z.string()), // pass the json string of the properties + fieldId: z.string() + })).mutation(async (opts) => { + return await this.dataCore.uploadFileData( + opts.ctx.req.user, + opts.input.studyId, + opts.input.files.file?.[0], + opts.input.fieldId, + opts.input.properties + ); + }), + /** + * Get the files of a study. + * + * @param studyId - The id of the study. + * @param versionId - The id of the data version. By default not specified for the latest version. + * @param fieldIds - The list of fields to return. + * + * @return IFile[] - The list of objects of IFile. + */ + getStudyFiles: this.baseProcedure.input(z.object({ + studyId: z.string(), + versionId: z.optional(z.union([z.string(), z.null(), z.array(z.union([z.string(), z.null()]))])), + fieldIds: z.optional(z.array(z.string())), + readable: z.optional(z.boolean()), + useCache: z.optional(z.boolean()), + forceUpdate: z.optional(z.boolean()) + })).query(async (opts) => { + return await this.dataCore.getStudyFiles( + opts.ctx.req.user, + opts.input.studyId, + opts.input.fieldIds, + opts.input.versionId, + opts.input.readable, + opts.input.useCache, + opts.input.forceUpdate + ); + }), + + /** + * Get the file of a study. + * + * @param fileId - The id of the file. + * + * @return IFile - The object of IFile. + */ + deleteFile: this.baseProcedure.input(z.object({ + fileId: z.string() + })).mutation(async (opts) => { + return await this.dataCore.deleteFile(opts.ctx.req.user, opts.input.fileId); + }), + /** + * Get the summary of a study. + * + * @param studyId - The id of the study. + * + * @return The object of IStudySummary. + */ + getStudyDataSummary: this.baseProcedure.input(z.object({ + studyId: z.string(), + useCache: z.optional(z.boolean()), + forceUpdate: z.optional(z.boolean()) + })).query(async (opts) => { + return await this.dataCore.getStudySummary(opts.ctx.req.user, opts.input.studyId, opts.input.useCache, opts.input.forceUpdate); + }) + }); + } +} + + +/** Example of data versioning aggregation */ +/** +{ + clinical: [ + { operationName: 'Group', params: { keys: ['fieldId', 'properties.Participant ID', 'properties.Visit ID'], skipUnmatch: true } }, + { operationName: 'LeaveOne', params: { scoreFormula: { type: enumASTNodeTypes.VARIABLE, operator: null, value: 'life.createdTime', parameters: {}, children: null }, isDescend: true } }, + { + operationName: 'Filter', params: { + filters: { + deleted: { + formula: { + type: enumASTNodeTypes.VARIABLE, + operation: null, + value: 'life.deletedTime', + parameter: {}, + children: null + }, + condition: enumConditionOps.GENERALISNULL, + value: '', + parameters: {} + } + } + } + } + ], + device: [ + { operationName: 'Group', params: { keys: ['properties.Participant ID', 'properties.Device Type', 'properties.Device ID', 'properties.Start Date', 'properties.End Date'], skipUnmatch: true } }, + { operationName: 'LeaveOne', params: { scoreFormula: { type: enumASTNodeTypes.VARIABLE, operator: null, value: 'life.createdTime', parameters: {}, children: null }, isDescend: true } }, + { + operationName: 'Filter', params: { + filters: { + deleted: { + formula: { + type: enumASTNodeTypes.VARIABLE, + operation: null, + value: 'life.deletedTime', + parameter: {}, + children: null + }, + condition: enumConditionOps.GENERALISNULL, + value: '', + parameters: {} + } + } + } + } + // { operationName: 'Concat', params: { concatKeys: ['properties', 'life'] } } + ] + } + +*/ diff --git a/packages/itmat-apis/src/trpc/domainProcedure.ts b/packages/itmat-apis/src/trpc/domainProcedure.ts new file mode 100644 index 000000000..bf5bbd53c --- /dev/null +++ b/packages/itmat-apis/src/trpc/domainProcedure.ts @@ -0,0 +1,106 @@ +import { z } from 'zod'; +import { FileUploadSchema } from '@itmat-broker/itmat-types'; +import { DomainCore } from '@itmat-broker/itmat-cores'; +import { TRPCBaseProcedure, TRPCRouter } from './trpc'; + + + +export class DomainRouter { + baseProcedure: TRPCBaseProcedure; + router: TRPCRouter; + domainCore: DomainCore; + constructor(baseProcedure: TRPCBaseProcedure, router: TRPCRouter, domainCore: DomainCore) { + this.baseProcedure = baseProcedure; + this.router = router; + this.domainCore = domainCore; + } + + _router() { + return this.router({ + /** + * Get domain by domainId, domainName or domainPath. + * + * @param domainId - The id of the domain. + * @param domainName - The name of the domain. + * @param domainPath - The path of the domain. + * + * @returns The domain objects. + */ + getDomains: this.baseProcedure.input(z.object({ + domainId: z.optional(z.string()), + domainName: z.optional(z.string()), + domainPath: z.optional(z.string()) + })).query(async (opts) => { + return await this.domainCore.getDomains(opts.ctx.req?.user ?? opts.ctx.user, opts.input.domainId, opts.input.domainName, opts.input.domainPath); + }), + /** + * Create a domain. + * + * @param domainName - The name of the domain. + * @param domainPath - The path of the domain. + * @param files - The files of the domain. + * + * @returns The domain object. + */ + createDomain: this.baseProcedure.input(z.object({ + domainName: z.string(), + domainPath: z.string(), + files: z.optional(z.object({ + logo: z.optional(z.array(FileUploadSchema)) + })), + color: z.optional(z.string()) + })).mutation(async (opts) => { + return await this.domainCore.createDomain(opts.ctx.req?.user ?? opts.ctx.user, opts.input.domainName, opts.input.domainPath, opts.input.files?.logo?.[0], opts.input.color); + }), + /** + * Edit a domain. + * + * @param domainId - The id of the domain. + * @param domainName - The name of the domain. + * @param domainPath - The path of the domain. + * @param files - The files of the domain. + * + * @returns The domain object. + */ + editDomain: this.baseProcedure.input(z.object({ + domainId: z.string(), + domainName: z.optional(z.string()), + domainPath: z.optional(z.string()), + files: z.optional(z.object({ + logo: z.optional(z.array(FileUploadSchema)) + })), + color: z.optional(z.string()) + })).mutation(async (opts) => { + return await this.domainCore.editDomain(opts.ctx.req?.user ?? opts.ctx.user, opts.input.domainId, opts.input.domainName, opts.input.domainPath, opts.input.files?.logo?.[0], opts.input.color); + }), + /** + * Delete a domain. + * + * @param domainId - The id of the domain. + * + * @returns The domain object. + */ + deleteDomain: this.baseProcedure.input(z.object({ + domainId: z.string() + })).mutation(async (opts) => { + return await this.domainCore.deleteDomain(opts.ctx.req?.user ?? opts.ctx.user, opts.input.domainId); + }), + /** + * Get current sub path. + * + * @returns The current sub path. + */ + getCurrentSubPath: this.baseProcedure.query(async (opts) => { + return this.domainCore.getCurrentSubPath(opts); + }), + /** + * Get current domain. Note this can be used for not logged in users. + * + * @returns The current domain. + */ + getCurrentDomain: this.baseProcedure.query(async (opts) => { + return await this.domainCore.getCurrentDomain(opts); + }) + }); + } +} \ No newline at end of file diff --git a/packages/itmat-apis/src/trpc/driveProcedure.ts b/packages/itmat-apis/src/trpc/driveProcedure.ts new file mode 100644 index 000000000..7e1cda273 --- /dev/null +++ b/packages/itmat-apis/src/trpc/driveProcedure.ts @@ -0,0 +1,169 @@ + +import { FileUploadSchema, enumFileTypes } from '@itmat-broker/itmat-types'; +import { DriveCore } from '@itmat-broker/itmat-cores'; +import { z } from 'zod'; +import { TRPCBaseProcedure, TRPCRouter } from './trpc'; + + + +export const ZDrivePermission = z.object({ + iid: z.string(), + read: z.boolean(), + write: z.boolean(), + delete: z.boolean() +}); + +export class DriveRouter { + baseProcedure: TRPCBaseProcedure; + router: TRPCRouter; + driveCore: DriveCore; + constructor(baseProcedure: TRPCBaseProcedure, router: TRPCRouter, driveCore: DriveCore) { + this.baseProcedure = baseProcedure; + this.router = router; + this.driveCore = driveCore; + } + + _router() { + return this.router({ + /** + * Create a drive folder. + * + * @param folderName - The name of the folder. + * @param parentId - The id of the parent. Null for default root node. + * @param description - The description of the folder. + * + * @return IDriveNode + */ + createDriveFolder: this.baseProcedure.input(z.object({ + folderName: z.string(), + parentId: z.optional(z.union([z.string(), z.null()])), + description: z.optional(z.string()) + })).mutation(async (opts) => { + return await this.driveCore.createDriveFolder(opts.ctx.user, opts.input.folderName, opts.input.parentId ?? null, false, opts.input.description); + }), + /** + * Create a drive file. + * + * @param parentId - The id of the parent node. + * + * @return IDriveNode + */ + createDriveFile: this.baseProcedure.input(z.object({ + parentId: z.optional(z.union([z.string(), z.null()])), + description: z.optional(z.string()), + files: z.object({ + file: z.array(FileUploadSchema) + }) + })).mutation(async (opts) => { + return await this.driveCore.createDriveFile(opts.ctx.user, opts.input.parentId ?? null, opts.input.description, + enumFileTypes[(opts.input.files.file[0].filename.split('.').pop() || '').toUpperCase() as keyof typeof enumFileTypes], opts.input.files.file[0]); + }), + createRecursiveDrives: this.baseProcedure.input(z.object({ + parentId: z.string(), + files: z.object({ + files: z.array(FileUploadSchema) + }), + paths: z.union([z.array(z.array(z.string())), z.string()]) + })).mutation(async (opts) => { + return await this.driveCore.createRecursiveDrives(opts.ctx.user, opts.input.parentId, opts.input.files.files, typeof opts.input.paths === 'string' ? JSON.parse(opts.input.paths) : opts.input.paths); + }), + /** + * Get the drive nodes of a user, including own drives and shared drives. + * + * @param userId - The id of the user. + * @param rootId - The id of the root drive if specified. + * + * @return Record { + return this.driveCore.getDrives(opts.ctx.user, opts.input.rootId); + }), + /** + * Edit a drive node. + * + * @param requester - The id of the requester. + * @param driveId - The id of the driver. + * @param managerId - The id of the manager. + * @param name - The name of the drive. + * @param description - The description of the drive. + * @param parentId - The id of the parent node. + * @param children - The ids of the childeren. + * @param sharedUsers - Shared users. + * @param sharedGroups - Shared user groups. + * + * @return driveIds - The list of drive ids influenced. + */ + editDrive: this.baseProcedure.input(z.object({ + driveId: z.string(), + managerId: z.optional(z.string()), + name: z.optional(z.string()), + description: z.optional(z.string()), + parentId: z.optional(z.string()), + children: z.optional(z.array(z.string())), + sharedUsers: z.optional(z.array(ZDrivePermission)), + sharedGroups: z.optional(z.array(ZDrivePermission)) + })).mutation(async (opts) => { + return await this.driveCore.editDrive( + opts.ctx.user, + opts.input.driveId, + opts.input.managerId, + opts.input.name, + opts.input.description, + opts.input.parentId, + opts.input.children, + opts.input.sharedUsers + ); + }), + /** + * Share a drive to a user via email. The children drives will also be influenced. + * + * @param userEmails - The emails of the users. + * @param driveId - The id of the drive. + * @param permissions - The permission object. + * + * @return driveIds - The list of drive ids influenced. + */ + shareDriveToUserViaEmail: this.baseProcedure.input(z.object({ + userEmails: z.array(z.string()), + driveId: z.string(), + permissions: z.object({ + read: z.boolean(), + write: z.boolean(), + delete: z.boolean() + }) + })).mutation(async (opts) => { + return this.driveCore.shareDriveToUserViaEmail(opts.ctx.req.user, opts.input.userEmails, opts.input.driveId, opts.input.permissions); + }), + /** + * Delete a drive node. + * + * @param driveId - The id of the drive. + * + * @return IDriveNode + */ + deleteDrive: this.baseProcedure.input(z.object({ + driveId: z.string() + })).mutation(async (opts) => { + return this.driveCore.deleteDrive(opts.ctx.user, opts.input.driveId); + }), + /** + * Copy a drive node to another parent. + * + * @param driveId - The id of the drive. + * @param targetParentId - The id of the target parent. + * + * @return IDriveNode + */ + copyDrive: this.baseProcedure.input(z.object({ + driveId: z.string(), + targetParentId: z.string() + })).mutation(async (opts) => { + return this.driveCore.copyDrive(opts.ctx.user, opts.input.driveId, opts.input.targetParentId); + }) + }); + } +} + + diff --git a/packages/itmat-apis/src/trpc/index.ts b/packages/itmat-apis/src/trpc/index.ts new file mode 100644 index 000000000..ddf8080ab --- /dev/null +++ b/packages/itmat-apis/src/trpc/index.ts @@ -0,0 +1,13 @@ +export * from './configProcedure'; +export * from './dataProcedure'; +export * from './domainProcedure'; +export * from './driveProcedure'; +export * from './logProcedure'; +export * from './middleware'; +export * from './organisationProcedure'; +export * from './roleProcedure'; +export * from './studyProcedure'; +export * from './trpc'; +export * from './tRPCRouter'; +export * from './userProcedure'; +export * from './webauthnProcedure'; \ No newline at end of file diff --git a/packages/itmat-apis/src/trpc/instanceProcedure.ts b/packages/itmat-apis/src/trpc/instanceProcedure.ts new file mode 100644 index 000000000..31d6945ee --- /dev/null +++ b/packages/itmat-apis/src/trpc/instanceProcedure.ts @@ -0,0 +1,177 @@ +import { z } from 'zod'; +import { TRPCBaseProcedure, TRPCRouter } from './trpc'; +import { InstanceCore } from '@itmat-broker/itmat-cores'; +import { enumAppType, enumInstanceStatus, LXDInstanceTypeEnum, CoreError, enumCoreErrors, enumOpeType, enumUserTypes } from '@itmat-broker/itmat-types'; +export class InstanceRouter { + baseProcedure: TRPCBaseProcedure; + router: TRPCRouter; + instanceCore: InstanceCore; + constructor(baseProcedure: TRPCBaseProcedure, router: TRPCRouter, instanceCore: InstanceCore) { + this.baseProcedure = baseProcedure; + this.router = router; + this.instanceCore = instanceCore; + } + + _router() { + return this.router({ + /** + * Create an instance + */ + createInstance: this.baseProcedure.input( + z.object({ + name: z.string(), + type: z.nativeEnum(LXDInstanceTypeEnum), + appType: z.nativeEnum(enumAppType), + lifeSpan: z.number(), + cpuLimit: z.number().optional(), + memoryLimit: z.string().optional(), + diskLimit: z.string().optional() + }) + ).mutation(async ({ input, ctx }) => { + + if (!ctx.req.user || !ctx.req.user.id) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + 'User must be authenticated.'); + } + const userId = ctx.req.user.id; + + // Check if requested resources exceed the user's quota + await this.instanceCore.checkQuotaBeforeCreation(userId, input.cpuLimit ?? 0, input.memoryLimit ?? '0', input.diskLimit ?? '0', 1); + + + return await this.instanceCore.createInstance( + userId, + ctx.req.user.username, + input.name, + input.type, + input.appType, + input.lifeSpan, + input.cpuLimit, + input.memoryLimit, + input.diskLimit + ); + }), + + /** + * Start or stop an instance + */ + startStopInstance: this.baseProcedure.input( + z.object({ + instanceId: z.string(), + action: z.enum([enumOpeType.START, enumOpeType.STOP]) + }) + ).mutation(async ({ input, ctx }) => { + if (!ctx.req.user || !ctx.req.user.id) { + throw new CoreError(enumCoreErrors.NOT_LOGGED_IN, 'User must be authenticated.'); + } + const userId = ctx.req.user.id; + + return await this.instanceCore.startStopInstance(userId, input.instanceId, input.action); + }), + + /** + * Restart an instance with a new lifespan + */ + restartInstance: this.baseProcedure.input( + z.object({ + instanceId: z.string(), + lifeSpan: z.number() + }) + ).mutation(async ({ input, ctx }) => { + if (!ctx.req.user || !ctx.req.user.id) { + throw new CoreError(enumCoreErrors.NOT_LOGGED_IN, 'User must be authenticated.'); + } + const userId = ctx.req.user.id; + + return await this.instanceCore.restartInstance(userId, input.instanceId, input.lifeSpan); + }), + + /** + * Get all instances for an user + */ + getInstances: this.baseProcedure.query(async ({ ctx }) => { + const user = ctx.req.user; + if (!user) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'Insufficient permissions.' + ); + } + return await this.instanceCore.getInstances(user.id); + }), + + /** + * Edit an instance + */ + editInstance: this.baseProcedure.input( + z.object({ + instanceId: z.string().optional(), + instanceName: z.string().optional(), + // set the update to the type LxdConfiguration + updates: z.object({ + name: z.string().optional(), + type: z.nativeEnum(LXDInstanceTypeEnum).optional(), + appType: z.nativeEnum(enumAppType).optional(), + lifeSpan: z.number().optional(), + project: z.string().optional(), + status: z.nativeEnum(enumInstanceStatus).optional(), + cpuLimit: z.number().optional(), + memoryLimit: z.string().optional() + }).passthrough() + }) + ).mutation(async ({ input, ctx }) => { + if (!ctx.req.user || !ctx.req.user.id) { + throw new CoreError(enumCoreErrors.NOT_LOGGED_IN, 'User must be authenticated.'); + } + + return await this.instanceCore.editInstance(ctx.req.user, input.instanceId, input.instanceName, input.updates); + }), + + /** + * Delete an instance + */ + deleteInstance: this.baseProcedure.input( + z.object({ + instanceId: z.string() + }) + ).mutation(async ({ input, ctx }) => { + const user = ctx.req.user; + const instance = await this.instanceCore.getInstanceById(input.instanceId); + + + if (user.type !== enumUserTypes.ADMIN && user.id !== instance.userId) { + + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'Insufficient permissions.' + ); + } + + return await this.instanceCore.deleteInstance(user.id, input.instanceId); + }), + + /** + * Extend the lifespan of an instance + */ + extendInstanceLifespan: this.baseProcedure.input( + z.object({ + instanceId: z.string(), + additionalTime: z.number() + }) + ).mutation(async ({ input, ctx }) => { + if (!ctx.req.user || !ctx.req.user.id) { + throw new CoreError(enumCoreErrors.NOT_LOGGED_IN, 'User must be authenticated.'); + } + + return await this.instanceCore.extendInstanceLifespan(ctx.req.user.id, input.instanceId, input.additionalTime); + }), + getQuotaAndFlavors: this.baseProcedure.query(async ({ ctx }) => { + if (!ctx.req.user || !ctx.req.user.id) { + throw new CoreError(enumCoreErrors.NOT_LOGGED_IN, 'User must be authenticated.'); + } + return await this.instanceCore.getQuotaAndFlavors(ctx.req.user); + }) + }); + } +} diff --git a/packages/itmat-apis/src/trpc/logProcedure.ts b/packages/itmat-apis/src/trpc/logProcedure.ts new file mode 100644 index 000000000..7bf36a916 --- /dev/null +++ b/packages/itmat-apis/src/trpc/logProcedure.ts @@ -0,0 +1,50 @@ +import { enumAPIResolver, enumEventStatus, enumEventType } from '@itmat-broker/itmat-types'; +import { z } from 'zod'; +import { LogCore } from '@itmat-broker/itmat-cores'; +import { TRPCBaseProcedure, TRPCRouter } from './trpc'; + + +export class LogRouter { + baseProcedure: TRPCBaseProcedure; + router: TRPCRouter; + logCore: LogCore; + constructor(baseProcedure: TRPCBaseProcedure, router: TRPCRouter, logCore: LogCore) { + this.baseProcedure = baseProcedure; + this.router = router; + this.logCore = logCore; + } + + _router() { + return this.router({ + /** + * Get the list of logs. + * + * @param caller - The caller of the event. + * @param type - The type of the event. + * @param apiResolver - The resolver of the event. + * @param event - The event. + * @param status - The status of the event. + * @param range - The range of the indexes. + * + * @return IOrganisation[] - The list of objects of IOrganisation. + */ + getLogs: this.baseProcedure.input(z.object({ + caller: z.optional(z.string()), + type: z.optional(z.array(z.nativeEnum(enumEventType)), z.null()), + apiResolver: z.optional(z.array(z.nativeEnum(enumAPIResolver)), z.null()), + event: z.optional(z.array(z.string()), z.null()), + status: z.optional(z.array(z.nativeEnum(enumEventStatus)), z.null()), + indexRange: z.optional(z.array(z.number()), z.null()), + timeRange: z.optional(z.array(z.number()), z.null()) + })).query(async (opts) => { + return await this.logCore.getLogs(opts.ctx.req.user, opts.input.caller, opts.input.type, opts.input.apiResolver, opts.input.event, opts.input.status, opts.input.indexRange, opts.input.timeRange); + }), + /** + * Get the summary of the logs. + */ + getLogsSummary: this.baseProcedure.query(async () => { + return await this.logCore.getLogsSummary(); + }) + }); + } +} \ No newline at end of file diff --git a/packages/itmat-apis/src/trpc/lxdProcedure.ts b/packages/itmat-apis/src/trpc/lxdProcedure.ts new file mode 100644 index 000000000..9c370d147 --- /dev/null +++ b/packages/itmat-apis/src/trpc/lxdProcedure.ts @@ -0,0 +1,147 @@ +import { z } from 'zod'; +import { TRPCBaseProcedure, TRPCRouter } from './trpc'; +import { LxdManager } from '@itmat-broker/itmat-cores'; +import { CoreError, enumCoreErrors, LXDInstanceTypeEnum } from '@itmat-broker/itmat-types'; +export class LXDRouter { + baseProcedure: TRPCBaseProcedure; + router: TRPCRouter; + lxdManager: LxdManager; + + constructor(baseProcedure: TRPCBaseProcedure, router: TRPCRouter, lxdManager: LxdManager) { + this.baseProcedure = baseProcedure; + this.router = router; + this.lxdManager = lxdManager; + } + + _router() { + return this.router({ + getResources: this.baseProcedure.query(async ({ ctx }) => { + if (!ctx.req?.user) { + throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'User must be authenticated.'); + } + return await this.lxdManager.getResources(); + }), + + getInstances: this.baseProcedure.query(async ({ ctx }) => { + if (!ctx.req?.user) { + throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'User must be authenticated.'); + } + return await this.lxdManager.getInstances(); + }), + + getInstanceState: this.baseProcedure.input(z.object({ + container: z.string(), + project: z.string() + })).query(async ({ input, ctx }) => { + if (!ctx.req?.user) { + throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'User must be authenticated.'); + } + return await this.lxdManager.getInstanceState(input.container, input.project); + }), + + getOperations: this.baseProcedure.input(z.object({})).query(async ({ ctx }) => { + if (!ctx.req?.user) { + throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'User must be authenticated.'); + } + return await this.lxdManager.getOperations(); + }), + + getOperationStatus: this.baseProcedure.input(z.object({ + operationId: z.string() + })).query(async ({ input, ctx }) => { + if (!ctx.req?.user) { + throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'User must be authenticated.'); + } + return await this.lxdManager.getOperationStatus(`/1.0/operations/${input.operationId}`); + }), + + getInstanceConsole: this.baseProcedure.input(z.object({ + container: z.string(), + options: z.object({ + height: z.number(), + width: z.number(), + type: z.string() + }) + })).mutation(async ({ input, ctx }) => { + if (!ctx.req?.user) { + throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'User must be authenticated.'); + } + return await this.lxdManager.getInstanceConsole(input.container, input.options); + }), + + getInstanceConsoleLog: this.baseProcedure.input(z.object({ + container: z.string() + })).mutation(async ({ input, ctx }) => { + if (!ctx.req?.user) { + throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'User must be authenticated.'); + } + return await this.lxdManager.getInstanceConsoleLog(input.container); + }), + + createInstance: this.baseProcedure.input(z.object({ + // set the config to LxdConfiguration + name: z.string(), + architecture: z.literal('x86_64'), + config: z.object({ + 'limits.cpu': z.string(), + 'limits.memory': z.string(), + 'user.username': z.string(), + 'user.user-data': z.string() + }), + source: z.object({ + type: z.string(), + alias: z.string() + }), + profiles: z.array(z.string()), + // use LXDInstanceTypeEnum + type: z.nativeEnum(LXDInstanceTypeEnum), + project: z.string() + })).mutation(async ({ input, ctx }) => { + if (!ctx.req?.user) { + throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'User must be authenticated.'); + } + return await this.lxdManager.createInstance({ + name: input.name, + architecture: input.architecture, + config: input.config, + source: input.source, + profiles: input.profiles, + type: input.type + } + , input.project); + }), + + updateInstance: this.baseProcedure.input(z.object({ + instanceName: z.string(), + payload: z.any(), + project: z.string() + })).mutation(async ({ input, ctx }) => { + if (!ctx.req?.user) { + throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'User must be authenticated.'); + } + return await this.lxdManager.updateInstance(input.instanceName, input.payload, input.project); + }), + + startStopInstance: this.baseProcedure.input(z.object({ + instanceName: z.string(), + action: z.enum(['start', 'stop']), + project: z.string() + })).mutation(async ({ input, ctx }) => { + if (!ctx.req?.user) { + throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'User must be authenticated.'); + } + return await this.lxdManager.startStopInstance(input.instanceName, input.action, input.project); + }), + + deleteInstance: this.baseProcedure.input(z.object({ + instanceName: z.string(), + project: z.string() + })).mutation(async ({ input, ctx }) => { + if (!ctx.req?.user) { + throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'User must be authenticated.'); + } + return await this.lxdManager.deleteInstance(input.instanceName, input.project); + }) + }); + } +} \ No newline at end of file diff --git a/packages/itmat-apis/src/trpc/middleware.ts b/packages/itmat-apis/src/trpc/middleware.ts new file mode 100644 index 000000000..83af6d82c --- /dev/null +++ b/packages/itmat-apis/src/trpc/middleware.ts @@ -0,0 +1,53 @@ +import { enumAPIResolver, enumEventStatus, enumEventType } from '@itmat-broker/itmat-types'; +import { v4 as uuid } from 'uuid'; + +function parseRequestinput(url: string) { + const [apiCall, queryString] = url.slice(1).split('?'); + let apiParams: { [key: string]: unknown } = {}; + if (queryString) { + const inputParam = (new URLSearchParams(queryString ?? {})).get('input') ?? ''; + apiParams = JSON.parse(decodeURIComponent(inputParam)); + } + return { apiCall, apiParams }; + +} + + +export const tRPCBaseProcedureMilldeware = (async (db, opts) => { + const startTime = Date.now(); + + const result = await opts.next(); + + const executionTime = Date.now() - startTime; + const { apiCall, apiParams } = parseRequestinput(opts.ctx.req.url); + + let errorMessage: string | undefined = undefined; + + if ('error' in result) { + errorMessage = result.error.message; + } + + await db.collections.log_collection.insertOne({ + id: uuid(), + requester: opts.ctx.req.user?.id ?? 'NA', + type: enumEventType.API_LOG, + apiResolver: enumAPIResolver.tRPC, + event: apiCall, + parameters: apiParams, + status: errorMessage ? enumEventStatus.FAIL : enumEventStatus.SUCCESS, + errors: errorMessage, + timeConsumed: executionTime, + life: { + createdTime: Date.now(), + createdUser: opts.ctx.req.user?.id ?? 'NA', + deletedTime: null, + deletedUser: null + }, + metadata: { + startTime: startTime, + endTime: startTime + executionTime + } + }); + + return result; +}); diff --git a/packages/itmat-apis/src/trpc/organisationProcedure.ts b/packages/itmat-apis/src/trpc/organisationProcedure.ts new file mode 100644 index 000000000..e5ade2b4e --- /dev/null +++ b/packages/itmat-apis/src/trpc/organisationProcedure.ts @@ -0,0 +1,82 @@ +import { FileUploadSchema } from '@itmat-broker/itmat-types'; +import { z } from 'zod'; +import { OrganisationCore } from '@itmat-broker/itmat-cores'; +import { TRPCBaseProcedure, TRPCRouter } from './trpc'; + +export class OrganisationRouter { + baseProcedure: TRPCBaseProcedure; + router: TRPCRouter; + organisationCore: OrganisationCore; + constructor(baseProcedure: TRPCBaseProcedure, router: TRPCRouter, organisationCore: OrganisationCore) { + this.baseProcedure = baseProcedure; + this.router = router; + this.organisationCore = organisationCore; + } + + _router() { + return this.router({ + /** + * Get organisations. + * + * @param organisationId - The organisation id. + * + * @returns - IOrganisation[] + */ + getOrganisations: this.baseProcedure.input(z.object({ + organisationId: z.optional(z.string()) + })).query(async (opts) => { + return this.organisationCore.getOrganisations(opts.ctx.user, opts.input.organisationId); + }), + /** + * Create a new organisation. + * + * @param name - The name of the organisation. + * @param shortname - The shortname of the organisation. + * @param files - The files of the organisation. + * + * @returns - IOrganisation + */ + createOrganisation: this.baseProcedure.input(z.object({ + name: z.string(), + shortname: z.optional(z.string()), + files: z.optional(z.object({ + profile: z.optional(z.array(FileUploadSchema)) + })) + })).mutation(async (opts) => { + return this.organisationCore.createOrganisation(opts.ctx.user, opts.input.name, opts.input.shortname, opts.input.files?.profile?.[0]); + }), + /** + * Edit an organisation. + * + * @param organisationId - The organisation id. + * @param name - The name of the organisation. + * @param shortname - The shortname of the organisation. + * @param files - The files of the organisation. + * + * @returns - IOrganisation + */ + editOrganisation: this.baseProcedure.input(z.object({ + organisationId: z.string(), + name: z.string(), + shortname: z.string(), + files: z.optional(z.object({ + profile: z.optional(z.array(FileUploadSchema)) + })) + })).mutation(async (opts) => { + return this.organisationCore.editOrganisation(opts.ctx.user, opts.input.organisationId, opts.input.name, opts.input.shortname, opts.input.files?.profile?.[0]); + }), + /** + * Delete an organisation. + * + * @param organisationId - The organisation id. + * + * @returns - IOrganisation + */ + deleteOrganisation: this.baseProcedure.input(z.object({ + organisationId: z.string() + })).mutation(async (opts) => { + return this.organisationCore.deleteOrganisation(opts.ctx.user, opts.input.organisationId); + }) + }); + } +} \ No newline at end of file diff --git a/packages/itmat-apis/src/trpc/roleProcedure.ts b/packages/itmat-apis/src/trpc/roleProcedure.ts new file mode 100644 index 000000000..9edb0d504 --- /dev/null +++ b/packages/itmat-apis/src/trpc/roleProcedure.ts @@ -0,0 +1,143 @@ +import { z } from 'zod'; +import { PermissionCore } from '@itmat-broker/itmat-cores'; +import { enumStudyRoles } from '@itmat-broker/itmat-types'; +import { TRPCBaseProcedure, TRPCRouter } from './trpc'; + +export class RoleRouter { + baseProcedure: TRPCBaseProcedure; + router: TRPCRouter; + permissionCore: PermissionCore; + constructor(baseProcedure: TRPCBaseProcedure, router: TRPCRouter, permissionCore: PermissionCore) { + this.baseProcedure = baseProcedure; + this.router = router; + this.permissionCore = permissionCore; + } + + _router() { + return this.router({ + /** + * Get the roles of a user. + * + * @param studyId - The id of the study. + * + * @returns IRole[] + */ + getUserRoles: this.baseProcedure.input(z.object({ + userId: z.string(), + studyId: z.optional(z.string()) + })).query(async (opts) => { + return await this.permissionCore.getRolesOfUser(opts.ctx.user, opts.input.userId, opts.input.studyId); + }), + /** + * Get the roles of a study. + * + * @param studyId - The id of the study. + * + * @returns IRole[] + */ + getStudyRoles: this.baseProcedure.input(z.object({ + studyId: z.string() + })).query(async (opts) => { + return await this.permissionCore.getRolesOfStudy(opts.ctx.user, opts.input.studyId); + }), + /** + * Create a new study role. + * + * @param studyId - The id of the study. + * @param name - The name of the role. + * @param description - The description of the role. + * @param dataPermissions - The data permissions for the role. + * @param studyRole - The role of the study. + * @param users - The users of the role. + * + * @returns IRole + */ + createStudyRole: this.baseProcedure.input(z.object({ + studyId: z.string(), + name: z.string(), + description: z.optional(z.string().optional()), + dataPermissions: z.optional(z.array(z.object({ + fields: z.array(z.string()), + dataProperties: z.record(z.array(z.string())), + includeUnVersioned: z.boolean(), + permission: z.number() + }))), + studyRole: z.optional(z.nativeEnum(enumStudyRoles)), + users: z.optional(z.array(z.string())) + })).mutation(async (opts) => { + return await this.permissionCore.createStudyRole( + opts.ctx.user, + opts.input.studyId, + opts.input.name, + opts.input.description, + opts.input.dataPermissions, + opts.input.studyRole, + opts.input.users + ); + }), + /** + * Edit a study role. + * + * @param roleId - The id of the role. + * @param name - The name of the role. + * @param description - The description of the role. + * @param dataPermissions - The data permissions for the role. + * @param studyRole - The role of the study. + * @param users - The users of the role. + * + * @returns IRole + */ + editStudyRole: this.baseProcedure.input(z.object({ + roleId: z.string(), + name: z.optional(z.string()), + description: z.optional(z.string().optional()), + dataPermissions: z.optional(z.array(z.object({ + fields: z.array(z.string()), + dataProperties: z.record(z.array(z.string())), + includeUnVersioned: z.boolean(), + permission: z.number() + }))), + studyRole: z.optional(z.nativeEnum(enumStudyRoles)), + users: z.optional(z.array(z.string())) + })).mutation(async (opts) => { + return await this.permissionCore.editStudyRole( + opts.ctx.user, + opts.input.roleId, + opts.input.name, + opts.input.description, + opts.input.dataPermissions, + opts.input.studyRole, + opts.input.users + ); + }), + + /** + * Add a user to a study role. + * + * @param roleId - The id of the role. + * + * @returns IRole + */ + addGuestUser: this.baseProcedure.input(z.object({ + username: z.string() + + })).mutation(async (opts) => { + return await this.permissionCore.addGuestUser(opts.input.username); + }), + /** + * Delete a study role. + * + * @param roleId - The id of the role. + * + * @returns IRole + */ + deleteStudyRole: this.baseProcedure.input(z.object({ + roleId: z.string() + })).mutation(async (opts) => { + return await this.permissionCore.deleteStudyRole(opts.ctx.user, opts.input.roleId); + }) + }); + } +} + + diff --git a/packages/itmat-apis/src/trpc/studyProcedure.ts b/packages/itmat-apis/src/trpc/studyProcedure.ts new file mode 100644 index 000000000..de441a797 --- /dev/null +++ b/packages/itmat-apis/src/trpc/studyProcedure.ts @@ -0,0 +1,127 @@ +import { IUser, FileUploadSchema } from '@itmat-broker/itmat-types'; +import { z } from 'zod'; +import { StudyCore } from '@itmat-broker/itmat-cores'; +import { TRPCBaseProcedure, TRPCRouter } from './trpc'; + +export class StudyRouter { + baseProcedure: TRPCBaseProcedure; + router: TRPCRouter; + studyCore: StudyCore; + constructor(baseProcedure: TRPCBaseProcedure, router: TRPCRouter, studyCore: StudyCore) { + this.baseProcedure = baseProcedure; + this.router = router; + this.studyCore = studyCore; + } + + _router() { + return this.router({ + /** + * Get the info of studies. + * + * @param studyId - The if of the study. + * + * @return Partial + */ + getStudies: this.baseProcedure.input(z.object({ + studyId: z.optional(z.string()) + })).query(async (opts) => { + const requester: IUser = opts.ctx.req?.user ?? opts.ctx.user; + return await this.studyCore.getStudies(requester, opts.input.studyId); + }), + /** + * Create a study. + * + * @param name - The name of the study. + * @param description - The description of the study. + * @param profile - The profile of the study. + * + * @return IStudy + */ + createStudy: this.baseProcedure.input(z.object({ + name: z.string(), + description: z.optional(z.string()), + files: z.optional(z.object({ + profile: z.optional(z.array(FileUploadSchema)) + })) + })).mutation(async (opts) => { + return await this.studyCore.createStudy(opts.ctx.req.user, opts.input.name, opts.input.description, opts.input.files?.profile?.[0]); + }), + /** + * Edit a study. + * + * @param studyId - The id of the study. + * @param name - The name of the study. + * @param description - The description of the study. + * @param profile - The profile of the user. + * + * @return Partial + */ + editStudy: this.baseProcedure.input(z.object({ + studyId: z.string(), + name: z.optional(z.string()), + description: z.optional(z.string()), + files: z.optional(z.object({ + profile: z.optional(z.array(FileUploadSchema)) + })) + })).mutation(async (opts) => { + return await this.studyCore.editStudy(opts.ctx.req.user, opts.input.studyId, opts.input.name, opts.input.description, opts.input.files?.profile?.[0]); + }), + /** + * Edit the visibility of a study. + * + * @param studyId - The id of the study. + * @param isPublic - The visibility status of the study. + * + * @return Partial + */ + editStudyVisibility: this.baseProcedure.input(z.object({ + studyId: z.string(), + isPublic: z.boolean() + })).mutation(async (opts) => { + return await this.studyCore.editStudyVisibility(opts.ctx.req.user, opts.input.studyId, opts.input.isPublic); + }), + /** + * Delete a study. + * + * @param studyId - The id of the study. + * + * @return IGenericResponse - The obejct of IGenericResponse. + */ + deleteStudy: this.baseProcedure.input(z.object({ + studyId: z.string() + })).mutation(async (opts) => { + return await this.studyCore.deleteStudy(opts.ctx.req.user, opts.input.studyId); + }), + /** + * Create a new data version of the study. + * + * @param studyId - The id of the study. + * @param tag - The tag of the study. + * @param dataVersion - The new version of the study. User float number. + * + * @return IGenericResponse - The object of IGenericResponse. + */ + createDataVersion: this.baseProcedure.input(z.object({ + studyId: z.string(), + dataVersion: z.string(), + tag: z.string() + })).mutation(async (opts) => { + return await this.studyCore.createDataVersion(opts.ctx.req.user, opts.input.studyId, opts.input.tag, opts.input.dataVersion); + }), + /** + * Set a data version as the current data version of a study. + * + * @param studyId - The id of the study. + * @param dataVersionId - The id of the data version. + * + * @return IGenreicResponse + */ + setDataversionAsCurrent: this.baseProcedure.input(z.object({ + studyId: z.string(), + dataVersionId: z.string() + })).mutation(async (opts) => { + return await this.studyCore.setDataversionAsCurrent(opts.ctx.req.user, opts.input.studyId, opts.input.dataVersionId); + }) + }); + } +} \ No newline at end of file diff --git a/packages/itmat-apis/src/trpc/tRPCRouter.ts b/packages/itmat-apis/src/trpc/tRPCRouter.ts new file mode 100644 index 000000000..3b47bc6a7 --- /dev/null +++ b/packages/itmat-apis/src/trpc/tRPCRouter.ts @@ -0,0 +1,66 @@ +import { ConfigRouter } from './configProcedure'; +import { DataRouter } from './dataProcedure'; +import { DomainRouter } from './domainProcedure'; +import { DriveRouter } from './driveProcedure'; +import { LogRouter } from './logProcedure'; +import { OrganisationRouter } from './organisationProcedure'; +import { RoleRouter } from './roleProcedure'; +import { StudyRouter } from './studyProcedure'; +import { TRPCRouter } from './trpc'; +import { UserRouter } from './userProcedure'; +import { WebAuthnRouter } from './webauthnProcedure'; +import { InstanceRouter } from './instanceProcedure'; +import { LXDRouter } from './lxdProcedure'; + + +export class TRPCAggRouter { + router: TRPCRouter; + userRouter: UserRouter; + driveRouter: DriveRouter; + studyRouter: StudyRouter; + dataRouter: DataRouter; + roleRouter: RoleRouter; + configRouter: ConfigRouter; + logRouter: LogRouter; + domainRouter: DomainRouter; + organisationRouter: OrganisationRouter; + webAuthnRouter: WebAuthnRouter; + instanceRouter: InstanceRouter; + lxdRouter: LXDRouter; + + constructor(router: TRPCRouter, userRouter: UserRouter, driveRouter: DriveRouter, studyRouter: StudyRouter, dataRouter: DataRouter, roleRouter: RoleRouter, configRouter: ConfigRouter, logRouter: LogRouter, domainRouter: DomainRouter, organisationRouter: OrganisationRouter, webAuthnRouter: WebAuthnRouter, instanceRouter: InstanceRouter, lxdRouter: LXDRouter) { + this.router = router; + this.userRouter = userRouter; + this.driveRouter = driveRouter; + this.studyRouter = studyRouter; + this.dataRouter = dataRouter; + this.roleRouter = roleRouter; + this.configRouter = configRouter; + this.logRouter = logRouter; + this.domainRouter = domainRouter; + this.organisationRouter = organisationRouter; + this.webAuthnRouter = webAuthnRouter; + this.instanceRouter = instanceRouter; + this.lxdRouter = lxdRouter; + } + + _routers() { + return this.router({ + user: this.userRouter._router(), + drive: this.driveRouter._router(), + study: this.studyRouter._router(), + data: this.dataRouter._router(), + role: this.roleRouter._router(), + config: this.configRouter._router(), + log: this.logRouter._router(), + domain: this.domainRouter._router(), + organisation: this.organisationRouter._router(), + webauthn: this.webAuthnRouter._router(), + instance: this.instanceRouter._router(), + lxd: this.lxdRouter._router() + }); + } +} + + +export type APPTRPCRouter = ReturnType; \ No newline at end of file diff --git a/packages/itmat-apis/src/trpc/trpc.ts b/packages/itmat-apis/src/trpc/trpc.ts new file mode 100644 index 000000000..14052e4eb --- /dev/null +++ b/packages/itmat-apis/src/trpc/trpc.ts @@ -0,0 +1,19 @@ +import { inferAsyncReturnType, initTRPC } from '@trpc/server'; +import type { CreateNextContextOptions } from '@trpc/server/adapters/next'; + +export const createtRPCContext = async (opts: CreateNextContextOptions) => { + return { + user: opts.req.user, + req: opts.req, + res: opts.res + }; +}; + +type Context = inferAsyncReturnType; +const t = initTRPC.context().create(); + +export const router = t.router; +export const publicProcedure = t.procedure; + +export type TRPCBaseProcedure = typeof publicProcedure; +export type TRPCRouter = typeof router; diff --git a/packages/itmat-apis/src/trpc/userProcedure.ts b/packages/itmat-apis/src/trpc/userProcedure.ts new file mode 100644 index 000000000..d05d68157 --- /dev/null +++ b/packages/itmat-apis/src/trpc/userProcedure.ts @@ -0,0 +1,332 @@ +import { UserCore } from '@itmat-broker/itmat-cores'; +import { z } from 'zod'; +import { FileUploadSchema, enumUserTypes } from '@itmat-broker/itmat-types'; +import { TRPCBaseProcedure, TRPCRouter } from './trpc'; + + +export class UserRouter { + baseProcedure: TRPCBaseProcedure; + router: TRPCRouter; + userCore: UserCore; + constructor(baseProcedure: TRPCBaseProcedure, router: TRPCRouter, userCore: UserCore) { + this.baseProcedure = baseProcedure; + this.router = router; + this.userCore = userCore; + } + + _router() { + return this.router({ + /** + * Return the object of IUser of the current requester. + * + * @return Record - The object of IUser. + */ + whoAmI: this.baseProcedure.query(async (opts) => { + return opts.ctx.req.user ?? null; + }), + /** + * Get a user. + * + * @param userId - The id of the user. If null, return all users. + * @return IUserWithoutToken + */ + getUser: this.baseProcedure.input(z.object({ + userId: z.optional(z.string()), + username: z.optional(z.string()), + email: z.optional(z.string()) + })).query(async (opts) => { + return await this.userCore.getUser(opts.ctx.user, opts.input.userId, opts.input.username, opts.input.email); + }), + /** + * Get all users. + * + * @param includedDeleted - If true, return all users including deleted ones. + * @return IUserWithoutToken + */ + getUsers: this.baseProcedure.input(z.object({ + includedDeleted: z.optional(z.boolean()) + })).query(async (opts) => { + return await this.userCore.getUsers(opts.ctx.user, opts.input.includedDeleted); + }), + /** + * Refresh the existing session to avoid timeout. Express will update the session as long as there is a new query in. + * + * @return IGenericResponse - The obejct of IGenericResponse. + */ + recoverSessionExpireTime: this.baseProcedure.query(() => { + return null; + }), + /** + * Validate the token from the reset password request. + * + * @param encryptedEmail - The encrypted email. + * @param token - The token for resetting password. + * @returns IGenericResponse + */ + validateResetPassword: this.baseProcedure.input(z.object({ + encryptedEmail: z.string(), + token: z.string() + })).query(async (opts) => { + return await this.userCore.validateResetPassword(opts.input.encryptedEmail, opts.input.token); + }), + /** + * Ask for a request to extend account expiration time. Send notifications to user and admin. + * + * @param email - The email of the user. + * + * @return IGenericResponse - The object of IGenericResponse + */ + requestExpiryDate: this.baseProcedure.input(z.object({ + username: z.optional(z.string()), + email: z.optional(z.string()) + })).mutation(async (opts) => { + return await this.userCore.requestExpiryDate(opts.input.username, opts.input.email); + }), + /** + * Request for resetting password. + * + * @param forgotUsername - Whether user forget the username. + * @param forgotPassword - Whether user forgot the password. + * @param email - The email of the user. If using email to reset password. + * @param username - The username of the uer. If using username to reset password. + * + * @return IGenericResponse - The object of IGenericResponse. + */ + requestUsernameOrResetPassword: this.baseProcedure.input(z.object({ + forgotUsername: z.boolean(), + forgotPassword: z.boolean(), + email: z.optional(z.string()), + username: z.optional(z.string()) + })).mutation(async (opts) => { + return await this.userCore.requestUsernameOrResetPassword(opts.input.forgotUsername, opts.input.forgotPassword, opts.ctx.req.headers.origin, opts.input.email, opts.input.username); + }), + /** + * Log in to the system. + * + * @param username - The username of the user. + * @param password - The password of the user. + * @param totp - The totp of the user. + * @param requestexpirydate - Whether to request for extend the expiration time of the user. + * + * @return Partial - The object of Partial + */ + login: this.baseProcedure.input(z.object({ + username: z.string(), + password: z.string(), + totp: z.string(), + requestexpirydate: z.optional(z.boolean()) + })).mutation(async (opts) => { + return await this.userCore.login(opts.ctx.req, opts.input.username, opts.input.password, opts.input.totp, opts.input.requestexpirydate); + }), + /** + * Logout an account. + * + * @return IGenericResponse - The object of IGenericResponse. + */ + logout: this.baseProcedure.mutation(async (opts) => { + return await this.userCore.logout(opts.ctx.user, opts.ctx.req); + }), + /** + * Create a user. + * + * @param username - The username of the user. + * @param firstname - The firstname of the user. + * @param lastname - The lastname of the user. + * @param email - The email of the user. + * @param password - The password of the user. + * @param description - The description of the user. + * @param organisation - The organisation of the user. + * @param profile - The profile of the user. + * + * @return IUser + */ + createUser: this.baseProcedure.input(z.object({ + username: z.string(), + firstname: z.string(), + lastname: z.string(), + email: z.string(), + password: z.string(), + description: z.optional(z.string()), + organisation: z.string(), + files: z.optional(z.object({ + profile: z.optional(z.array(FileUploadSchema)) + })) + })).mutation(async (opts) => { + return await this.userCore.createUser( + opts.ctx.user, + opts.input.username, + opts.input.email, + opts.input.firstname, + opts.input.lastname, + opts.input.organisation, + enumUserTypes.GUEST, + false, + opts.input.password, + opts.input.files?.profile?.[0] + ); + }), + /** + * Delete a user. + * + * @param userId - The id of the user. + * + * @return IGenericResponse - The object of IGenericResponse. + */ + deleteUser: this.baseProcedure.input(z.object({ + userId: z.string() + })).mutation(async (opts) => { + return await this.userCore.deleteUser(opts.ctx.user, opts.input.userId); + }), + /** + * Reset the password of an account. + * + * @param encryptedEmail - The encrypted email of the user. + * @param token - The id of the reset password request of the user. + * @param newPassword - The new password. + * + * @return IGenericResponse - The object of IGenericResponse. + */ + resetPassword: this.baseProcedure.input(z.object({ + encryptedEmail: z.string(), + token: z.string(), + newPassword: z.string() + })).mutation(async (opts) => { + return await this.userCore.resetPassword(opts.input.encryptedEmail, opts.input.token, opts.input.newPassword); + }), + /** + * Edit a user. Besides description, other fields whose values is null will not be updated. + * + * @param userId - The id of the user. + * @param username - The username of the user. + * @param type - The type of the user. + * @param firstname - The first name of the user. + * @param lastname - The last name of the user. + * @param email - The email of the user. + * @param emailNotificationsActivated - Whether the email notification is activated. + * @param password - The password of the user. + * @param description - The description of the user. + * @param organisaiton - The organisation of the user. + * @param expiredAt - The expiration time of the user. + * @param profile - The profile of the user. + * + * @return Partial - The object of IUser. + */ + editUser: this.baseProcedure.input(z.object({ + userId: z.string(), + username: z.optional(z.string()), + type: z.optional(z.nativeEnum(enumUserTypes)), + firstname: z.optional(z.string()), + lastname: z.optional(z.string()), + email: z.optional(z.string()), + password: z.optional(z.string()), + description: z.optional(z.string()), + organisation: z.optional(z.string()), + emailNotificationsActivated: z.optional(z.boolean()), + otpSecret: z.optional(z.string()), + expiredAt: z.optional(z.number()), + files: z.optional(z.object({ + profile: z.optional(z.array(FileUploadSchema)) + })) + })).mutation(async (opts) => { + return await this.userCore.editUser( + opts.ctx.user, + opts.input.userId, + opts.input.username, + opts.input.email, + opts.input.firstname, + opts.input.lastname, + opts.input.organisation, + opts.input.type, + opts.input.emailNotificationsActivated, + opts.input.password, + opts.input.otpSecret, + opts.input.files?.profile?.[0], + opts.input.description, + opts.input.expiredAt + + ); + }), + /** + * Get keys of a user. + * + * @param userId - The id of the user. + * @return IPubkeys[] + */ + getUserKeys: this.baseProcedure.input(z.object({ + userId: z.string() + })).query(async (opts) => { + return await this.userCore.getUserKeys(opts.ctx.user, opts.input.userId); + }), + /** + * Register a public key. + * + * @param pubkey - The public key. + * @param signature - The signature of the public key. + * @param associatedUserId - The id of the user. + * @return IPubkey + */ + registerPubkey: this.baseProcedure.input(z.object({ + pubkey: z.string(), + hashedPrivateKey: z.string(), + associatedUserId: z.string() + })).mutation(async (opts) => { + return await this.userCore.registerPubkey(opts.ctx.user, opts.input.pubkey, opts.input.hashedPrivateKey, opts.input.associatedUserId); + }), + /** + * Request an access token. + * @param username - The username of the user. + * @param pubkey - The public key. + * + * @return challenge + */ + requestAccessToken: this.baseProcedure.input(z.object({ + username: z.string(), + hashedPrivateKey: z.string() + })).mutation(async (opts) => { + return await this.userCore.requestAccessToken(opts.input.username, opts.input.hashedPrivateKey); + }), + /** + * Get an access token. + * @param username - The username of the user. + * @param pubkey - The public key. + * + * @return token + */ + getAccessToken: this.baseProcedure.input(z.object({ + username: z.string(), + hashedPrivateKey: z.string(), + signature: z.string(), + life: z.optional(z.number()) + })).mutation(async (opts) => { + return await this.userCore.getAccessToken(opts.input.username, opts.input.hashedPrivateKey, opts.input.signature, opts.input.life); + }), + /** + * Issue an access token. + * + * @param pubkey - The public key. + * @param signature - The signature of the public key. + * @param life - The life of the token. + * @return IAccessToken + */ + issueAccessToken: this.baseProcedure.input(z.object({ + pubkey: z.string(), + signature: z.string(), + life: z.optional(z.number()) + })).mutation(async (opts) => { + return await this.userCore.issueAccessToken(opts.input.pubkey, opts.input.signature, opts.input.life); + }), + /** + * Delete a public key. + * + * @param keyId - The id of the public key. + * @param associatedUserId - The id of the user. + */ + deletePubkey: this.baseProcedure.input(z.object({ + associatedUserId: z.string(), + keyId: z.string() + })).mutation(async (opts) => { + return await this.userCore.deletePubkey(opts.ctx.user, opts.input.associatedUserId, opts.input.keyId); + }) + }); + } +} diff --git a/packages/itmat-apis/src/trpc/webauthnProcedure.ts b/packages/itmat-apis/src/trpc/webauthnProcedure.ts new file mode 100644 index 000000000..968e194ca --- /dev/null +++ b/packages/itmat-apis/src/trpc/webauthnProcedure.ts @@ -0,0 +1,82 @@ +import { z } from 'zod'; +import { TRPCBaseProcedure, TRPCRouter } from './trpc'; +import { WebauthnCore} from '@itmat-broker/itmat-cores'; + + + +export class WebAuthnRouter { + baseProcedure: TRPCBaseProcedure; + router: TRPCRouter; + webAuthnCore: WebauthnCore; + + constructor(baseProcedure: TRPCBaseProcedure, router: TRPCRouter, webAuthnCore: WebauthnCore) { + this.baseProcedure = baseProcedure; + this.router = router; + this.webAuthnCore = webAuthnCore; + } + + _router() { + return this.router({ + getWebauthn: this.baseProcedure.input(z.object({ + webauthn_ids: z.array(z.string()) + })).query(async ({ input }) => { + const { webauthn_ids } = input; + return await this.webAuthnCore.getWebauthn(webauthn_ids); + }), + + getWebauthnRegisteredDevices: this.baseProcedure.query(async ({ ctx }) => { + return await this.webAuthnCore.getWebauthnDevices(ctx.req.user); + }), + + getWebauthnID: this.baseProcedure.query(async ({ ctx }) => { + return await this.webAuthnCore.getUserWebAuthnID(ctx.req.user); + }), + + webauthnRegister: this.baseProcedure.mutation(async ({ ctx }) => { + const {rpID} = await this.webAuthnCore.getCurrentOriginAndRpID(ctx); + return await this.webAuthnCore.getWebauthnRegistrationOptions(ctx.user, rpID); + }), + + webauthnRegisterVerify: this.baseProcedure.input(z.object({ + attestationResponse: z.any() + })).mutation(async ({ input, ctx }) => { + const {origin, rpID} = await this.webAuthnCore.getCurrentOriginAndRpID(ctx); + return await this.webAuthnCore.handleRegistrationVerify(ctx.req.user, input.attestationResponse,origin, rpID); + }), + + webauthnAuthenticate: this.baseProcedure.input(z.object({ + userId: z.string() + })).mutation(async ({ input, ctx}) => { + const {rpID} = await this.webAuthnCore.getCurrentOriginAndRpID(ctx); + return await this.webAuthnCore.getWebauthnAuthenticationOptions(input.userId, rpID); + }), + + webauthnAuthenticateVerify: this.baseProcedure.input(z.object({ + userId: z.string(), + assertionResponse: z.any() + })).mutation(async ({ input, ctx }) => { + const {origin, rpID} = await this.webAuthnCore.getCurrentOriginAndRpID(ctx); + return await this.webAuthnCore.handleAuthenticationVerify(input.userId, input.assertionResponse, origin, rpID); + }), + + deleteWebauthnRegisteredDevices: this.baseProcedure.input(z.object({ + deviceId: z.string() + })).mutation(async ({ input, ctx }) => { + return await this.webAuthnCore.deleteWebauthnDevices(ctx.user, input.deviceId); + }), + + updateWebauthnDeviceName: this.baseProcedure.input(z.object({ + deviceId: z.string(), + name: z.string() + })).mutation(async ({ input, ctx }) => { + return await this.webAuthnCore.updateWebauthnDeviceName(ctx.user, input.deviceId, input.name); + }), + webauthnLogin: this.baseProcedure.input(z.object({ + userId: z.string(), + requestExpiryDate: z.boolean().optional() + })).mutation(async ({ input, ctx }) => { + return await this.webAuthnCore.webauthnLogin(ctx.req, input.userId, input.requestExpiryDate); + }) + }); + } +} \ No newline at end of file diff --git a/packages/itmat-models/tsconfig.json b/packages/itmat-apis/tsconfig.json similarity index 89% rename from packages/itmat-models/tsconfig.json rename to packages/itmat-apis/tsconfig.json index da97c912e..781e6aac1 100644 --- a/packages/itmat-models/tsconfig.json +++ b/packages/itmat-apis/tsconfig.json @@ -1,12 +1,13 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "module": "commonjs", + "module": "ES2022", "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, + "noImplicitAny": false, "noFallthroughCasesInSwitch": true }, "files": [], diff --git a/packages/itmat-models/tsconfig.lib.json b/packages/itmat-apis/tsconfig.lib.json similarity index 100% rename from packages/itmat-models/tsconfig.lib.json rename to packages/itmat-apis/tsconfig.lib.json diff --git a/packages/itmat-models/tsconfig.spec.json b/packages/itmat-apis/tsconfig.spec.json similarity index 90% rename from packages/itmat-models/tsconfig.spec.json rename to packages/itmat-apis/tsconfig.spec.json index e85ec32ef..c744cb73a 100644 --- a/packages/itmat-models/tsconfig.spec.json +++ b/packages/itmat-apis/tsconfig.spec.json @@ -2,7 +2,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", + "module": "ES2022", "types": [ "jest", "node" diff --git a/packages/itmat-commons/.lib.swcrc b/packages/itmat-commons/.lib.swcrc index a9390f7d7..e6032199d 100644 --- a/packages/itmat-commons/.lib.swcrc +++ b/packages/itmat-commons/.lib.swcrc @@ -1,6 +1,6 @@ { "jsc": { - "target": "es2017", + "target": "ES2022", "parser": { "syntax": "typescript", "decorators": true, @@ -28,4 +28,4 @@ "./**/jest-setup.ts$", ".*.js$" ] -} \ No newline at end of file +} diff --git a/packages/itmat-commons/README.md b/packages/itmat-commons/README.md index a60ec17fc..abc221ef5 100644 --- a/packages/itmat-commons/README.md +++ b/packages/itmat-commons/README.md @@ -2,18 +2,10 @@ This library was generated with [Nx](https://nx.dev). - - ## Building Run `nx build itmat-commons` to build the library. - - - - ## Running unit tests Run `nx test itmat-commons` to execute the unit tests via [Jest](https://jestjs.io). - - diff --git a/packages/itmat-commons/jest.config.ts b/packages/itmat-commons/jest.config.ts index c87e9b5b6..cb4630206 100644 --- a/packages/itmat-commons/jest.config.ts +++ b/packages/itmat-commons/jest.config.ts @@ -8,7 +8,7 @@ const { exclude: _, ...swcJestConfig } = JSON.parse( ); export default { displayName: 'itmat-commons', - preset: '../../jest.preset.js', + preset: '../../jest.preset.cjs', transform: { '^.+\\.[tj]s$': ['@swc/jest', swcJestConfig], }, @@ -19,5 +19,6 @@ export default { "node_modules", "\\.pnp\\.[^\\\/]+$", "test[\\/]fixtures[\\/]_minio" - ] + ], + moduleNameMapper:{"^uuid$": "uuid"} }; diff --git a/packages/itmat-commons/project.json b/packages/itmat-commons/project.json index 95aac03dc..f1e5257e4 100644 --- a/packages/itmat-commons/project.json +++ b/packages/itmat-commons/project.json @@ -3,6 +3,7 @@ "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "packages/itmat-commons/src", "projectType": "library", + "tags": [], "targets": { "build": { "executor": "@nx/esbuild:esbuild", @@ -16,7 +17,6 @@ "assets": [ "packages/itmat-commons/*.md" ], - "generatePackageJson": true, "thirdParty": true } }, @@ -35,6 +35,5 @@ "jestConfig": "packages/itmat-commons/jest.config.ts" } } - }, - "tags": [] + } } \ No newline at end of file diff --git a/packages/itmat-commons/src/utils/database.ts b/packages/itmat-commons/src/utils/database.ts index 299b2729e..23d4befc8 100644 --- a/packages/itmat-commons/src/utils/database.ts +++ b/packages/itmat-commons/src/utils/database.ts @@ -10,8 +10,8 @@ export interface IDatabaseBaseConfig { }; } -export interface IDatabase { - collections?: Coll; +export interface IDatabase { + collections?: Colls; connect: ( config: Conf, mongoClient: typeof MongoClient @@ -21,16 +21,25 @@ export interface IDatabase { closeConnection: () => Promise; } -export class Database implements IDatabase { +export class Database> implements IDatabase { get db(): Db { - return this.localClient!.db(this.config!.database); + if (!this.localClient || !this.config) { + throw new Error('Client error.'); + } + return this.localClient.db(this.config.database); } - get client(): MongoClient | undefined { + get client(): MongoClient { + if (!this.localClient) { + throw new Error('Client error.'); + } return this.localClient; } - public collections?: C; + + // This assumes that the collections are already present in the database + // This assumes the server will never proceed if the database is not connected + public collections: C = {} as C; private localClient?: MongoClient; private config?: configType; @@ -40,18 +49,18 @@ export class Database { this.config = config; const shouldOutput = process.env['JEST_WORKER_ID'] !== undefined; - shouldOutput && Logger.log('Connecting to the database..'); + if (shouldOutput) Logger.log('Connecting to the database..'); /* any error throw here will be caught by the server */ this.localClient = await (new mongoClient(config.mongo_url)).connect(); - shouldOutput && Logger.log('Connected to database.'); + if (shouldOutput) Logger.log('Connected to database.'); - shouldOutput && Logger.log('Performing basic checks..'); + if (shouldOutput) Logger.log('Performing basic checks..'); await this.checkAllCollectionsArePresent(); - shouldOutput && Logger.log('Done basic checks.'); + if (shouldOutput) Logger.log('Done basic checks.'); this.assignCollections(); - shouldOutput && Logger.log('Finished with database initialisation.'); + if (shouldOutput) Logger.log('Finished with database initialisation.'); return this.localClient; } @@ -59,22 +68,31 @@ export class Database { + if (!this.config) { + throw new Error('Config Missing.'); + } + const collections = Object.entries(this.config.collections).reduce((a, e) => { a[e[0]] = this.db.collection(e[1]); return a; - }, {}); + }, {} as Record) as C; this.collections = collections; } private async checkAllCollectionsArePresent(): Promise { + if (!this.config) { + throw new Error('Config Missing.'); + } const collectionList: string[] = (await this.db.listCollections({}).toArray()).map((el) => el.name); - for (const each of Object.values(this.config!.collections)) { + for (const each of Object.values(this.config.collections)) { if (!collectionList.includes(each)) { throw new CustomError(`Collection ${each} does not exist.`); } diff --git a/packages/itmat-commons/src/utils/emailer.ts b/packages/itmat-commons/src/utils/emailer.ts new file mode 100644 index 000000000..64d262308 --- /dev/null +++ b/packages/itmat-commons/src/utils/emailer.ts @@ -0,0 +1,13 @@ +import nodemailer, { SendMailOptions } from 'nodemailer'; + +export class Mailer { + private readonly _client: nodemailer.Transporter; + + constructor(config: Parameters[0]) { + this._client = nodemailer.createTransport(config); + } + + public async sendMail(mail: SendMailOptions): Promise { + await this._client.sendMail(mail); + } +} diff --git a/packages/itmat-commons/src/utils/index.ts b/packages/itmat-commons/src/utils/index.ts index cb0a37b90..bd6d7ee7f 100644 --- a/packages/itmat-commons/src/utils/index.ts +++ b/packages/itmat-commons/src/utils/index.ts @@ -5,5 +5,7 @@ export type { IServerBaseConfig } from './serverBase'; export { ServerBase } from './serverBase'; export type { IObjectStoreConfig } from './objStore'; export { ObjectStore } from './objStore'; -export { Logger } from './logger'; +export * from './logger'; +export * from './logThrottler'; export { JobPoller } from './poller'; +export { Mailer } from './emailer'; diff --git a/packages/itmat-commons/src/utils/logThrottler.ts b/packages/itmat-commons/src/utils/logThrottler.ts new file mode 100644 index 000000000..e6eac7980 --- /dev/null +++ b/packages/itmat-commons/src/utils/logThrottler.ts @@ -0,0 +1,149 @@ +import { Logger } from './logger'; + +interface ErrorRecord { + count: number; + lastLogged: number; + firstSeen: number; + message: string; +} + +/** + * Utility class for throttling repeated log messages + */ +export class LogThrottler { + private static errorRecords: Map = new Map(); + + /** + * Log a message with throttling to avoid flooding logs with repeated messages + * + * @param key - A unique identifier for the type of error (e.g. "instance-not-found-jupyter1") + * @param message - The error message to log + * @param level - Log level to use + * @param throttleOptions - Options for controlling throttling behavior + */ + public static throttledLog( + key: string, + message: string, + level: 'error' | 'warn' | 'info' = 'error', + throttleOptions: { + initialLogInterval?: number; // Time in ms between initial logs + subsequentLogInterval?: number; // Time in ms between logs after repeated occurrences + maxOccurrences?: number; // After this many occurrences, reduce logging frequency + summarizeInterval?: number; // Time in ms to periodically summarize occurrences + } = {} + ): void { + const now = Date.now(); + const options = { + initialLogInterval: 60000, // 1 minute + subsequentLogInterval: 300000, // 5 minutes + maxOccurrences: 10, // After 10 occurrences, reduce logging frequency + summarizeInterval: 1800000, // Every 30 minutes, provide a summary + ...throttleOptions + }; + + // Get or create record + const record = this.errorRecords.get(key) || { + count: 0, + lastLogged: 0, + firstSeen: now, + message + }; + + // Increment counter + record.count++; + + let shouldLog = false; + + // Always log the first occurrence + if (record.count === 1) { + shouldLog = true; + } + // For repeated occurrences, throttle logging based on frequency + else { + const timeSinceLastLog = now - record.lastLogged; + const logInterval = record.count <= options.maxOccurrences + ? options.initialLogInterval + : options.subsequentLogInterval; + + // Log if enough time has passed since the last log + if (timeSinceLastLog >= logInterval) { + shouldLog = true; + } + + // Periodically provide a summary regardless of regular throttling + const timeSinceFirstSeen = now - record.firstSeen; + if (timeSinceFirstSeen >= options.summarizeInterval && + (now - record.lastLogged) >= options.summarizeInterval) { + shouldLog = true; + } + } + + if (shouldLog) { + // For repeat occurrences, include count information + const logMessage = record.count > 1 + ? `${message} (occurred ${record.count} times in the last ${this.formatDuration(now - record.firstSeen)})` + : message; + + // Log at the appropriate level + switch (level) { + case 'error': + Logger.error(logMessage); + break; + case 'warn': + Logger.warn(logMessage); + break; + case 'info': + Logger.log(logMessage); + break; + } + + record.lastLogged = now; + } + + // Update record + this.errorRecords.set(key, record); + } + + /** + * Reset the tracking for a specific error key + */ + public static resetTracking(key: string): void { + this.errorRecords.delete(key); + } + + /** + * Format a duration in milliseconds to a human-readable string + */ + private static formatDuration(ms: number): string { + const seconds = Math.floor(ms / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + + if (hours > 0) { + return `${hours}h ${minutes % 60}m`; + } else if (minutes > 0) { + return `${minutes}m ${seconds % 60}s`; + } else { + return `${seconds}s`; + } + } + + /** + * Extract meaningful error information from various error types + */ + public static getErrorMessage(error: unknown): string { + if (error instanceof Error) { + return error.message; + } else if (typeof error === 'string') { + return error; + } else if (error && typeof error === 'object') { + try { + return JSON.stringify(error); + } catch { + return String(error); + } + } else { + return String(error); + } + } +} diff --git a/packages/itmat-commons/src/utils/objStore.ts b/packages/itmat-commons/src/utils/objStore.ts index 298188f9b..924aca65c 100644 --- a/packages/itmat-commons/src/utils/objStore.ts +++ b/packages/itmat-commons/src/utils/objStore.ts @@ -17,9 +17,10 @@ export class ObjectStore { public async isConnected(): Promise { try { - await this.client!.listBuckets(); + if (this.client) + await this.client.listBuckets(); return true; - } catch (e) { + } catch (__unused__exception) { return false; } } @@ -43,35 +44,61 @@ export class ObjectStore { return await minioClient.listBuckets(); } - public async uploadFile(fileStream: Readable, studyId: string, uri: string): Promise { - const lowercasestudyid = studyId.toLowerCase(); - const bucketExists = await this.client!.bucketExists(lowercasestudyid); - + public async uploadFile(fileStream: Readable, bucketId: string, uri: string, size?: number): Promise { + if (!this.client) { + throw new Error('Connection failed.'); + } + const lowerCaseBucketId = bucketId.toLowerCase(); + const bucketExists = await this.client.bucketExists(lowerCaseBucketId); if (!bucketExists) { - await this.client!.makeBucket(lowercasestudyid, this.config?.bucketRegion ?? ''); + await this.client.makeBucket(lowerCaseBucketId, this.config?.bucketRegion ?? ''); } /* check if object already exists because if it does, minio supplant the old file without warning*/ let fileExists; try { - await this.client!.statObject(lowercasestudyid, uri); + await this.client.statObject(lowerCaseBucketId, uri); fileExists = true; - } catch (e) { + } catch (__unused__exception) { fileExists = false; } if (fileExists) { - throw new Error(`File "${uri}" of study "${studyId}" already exists.`); + throw new Error(`File "${uri}" of bucket "${bucketId}" already exists.`); } - - const result = await this.client!.putObject(lowercasestudyid, uri, fileStream); + const result = await this.client.putObject(lowerCaseBucketId, uri, fileStream, size); return result.etag; } - public async downloadFile(studyId: string, uri: string): Promise { + public async copyObject(sourceBucket: string, sourceUri: string, targetBucket: string, targetUri: string) { + if (!this.client) { + throw new Error('Connection failed.'); + } + + const lowerSourceBucket = sourceBucket.toLowerCase(); + const lowerTargetBucket = targetBucket.toLowerCase(); + + // Ensure target bucket exists + const bucketExists = await this.client.bucketExists(lowerTargetBucket); + if (!bucketExists) { + await this.client.makeBucket(lowerTargetBucket, this.config?.bucketRegion ?? ''); + } + // Copy the object + const conds = new Minio.CopyConditions(); + const result = await this.client.copyObject(lowerTargetBucket, targetUri, `/${lowerSourceBucket}/${sourceUri}`, conds); + if (Object.hasOwn(result, 'Etag')) + return (result as Record)?.['Etag']; + else + return (result as Record)?.['etag']; + } + + public async downloadFile(buckerId: string, uri: string): Promise { + if (!this.client) { + throw new Error('Connection failed.'); + } // PRECONDITION: studyId and file exists (checked by interface resolver) - const lowercasestudyid = studyId.toLowerCase(); - const stream = this.client!.getObject(lowercasestudyid, uri); + const lowerBuckerId = buckerId.toLowerCase(); + const stream = this.client.getObject(lowerBuckerId, uri); return stream as Promise; } } diff --git a/packages/itmat-commons/src/utils/poller.ts b/packages/itmat-commons/src/utils/poller.ts index fe3762a20..ad8d8960b 100644 --- a/packages/itmat-commons/src/utils/poller.ts +++ b/packages/itmat-commons/src/utils/poller.ts @@ -1,24 +1,18 @@ import type * as mongodb from 'mongodb'; -import { IJobEntry } from '@itmat-broker/itmat-types'; +import { enumJobHistoryStatus, enumJobStatus, IJob, IJobPollerConfig, IJobSchedulerConfig, IJobActionReturn } from '@itmat-broker/itmat-types'; import { Logger } from './logger'; -export interface IJobPollerConfig { - identity: string; // a string identifying the server; this is just to keep track in mongo - jobType?: string; // if undefined, matches all jobs - jobCollection: mongodb.Collection>; // collection to poll - pollingInterval: number; // in ms - action: (document: any) => void; // gets called every time there is new document -} - export class JobPoller { - private intervalObj?: NodeJS.Timeout; - private readonly matchObj: any; + private intervalObj?: NodeJS.Timer; + private readonly matchObj: unknown; private readonly identity: string; private readonly jobType?: string; - private readonly jobCollection: mongodb.Collection>; + private readonly jobCollection: mongodb.Collection; private readonly pollingInterval: number; - private readonly action: (document: any) => void; + private readonly action: (document: IJob) => Promise; + private readonly jobScheduler: JobScheduler; + private readonly jobSchedulerConfig: IJobSchedulerConfig; constructor(config: IJobPollerConfig) { this.identity = config.identity; @@ -26,50 +20,153 @@ export class JobPoller { this.jobCollection = config.jobCollection; this.pollingInterval = config.pollingInterval; this.action = config.action; - this.setInterval = this.setInterval.bind(this); this.checkForJobs = this.checkForJobs.bind(this); - this.matchObj = { - claimedBy: undefined, - status: 'QUEUED' - /*, lastClaimed: more then 0 */ - }; - - /* if this.jobType = config.jobType is undefined that this poller polls every job type */ - if (this.jobType !== undefined) { this.matchObj.jobType = this.jobType; } + + this.jobScheduler = new JobScheduler({ + ...config.jobSchedulerConfig, + jobCollection: config.jobCollection + }); + this.jobSchedulerConfig = config.jobSchedulerConfig; + } public setInterval(): void { - this.intervalObj = setInterval(this.checkForJobs, this.pollingInterval); + this.intervalObj = setInterval(() => { + void this.checkForJobs(this.jobSchedulerConfig); // Wrap the async call + }, this.pollingInterval); } - private async checkForJobs() { - // Logger.log(`${this.identity} polling for new jobs of type ${this.jobType || 'ALL'}.`); + private async checkForJobs(config: IJobSchedulerConfig){ + let job: IJob | null; try { - const updateResult = await this.jobCollection.findOneAndUpdate(this.matchObj, { - $set: { - claimedBy: this.identity, - lastClaimed: new Date().valueOf(), - status: 'PROCESSING' - } - }); - - if (updateResult !== null) { - Logger.log(`${this.identity} Claimed job of type ${updateResult.jobType} - id: ${updateResult.id}`); - if (this.intervalObj) - clearInterval(this.intervalObj); - await this.action(updateResult); - Logger.log(`${this.identity} Finished processing job of type ${updateResult.jobType} - id: ${updateResult.id}.`); - this.setInterval(); - } else { - Logger.log(`${this.identity} No job found.`); - // this.setInterval(); - } - + // implement the scheduler here + job = await this.jobScheduler.findNextJob(); } catch (err) { - //TODO Handle error recording Logger.error(`${this.identity} Errored picking up a job: ${err}`); return; } + if (job) { + // update log status + const setObj: mongodb.UpdateFilter = {}; + try { + + const result = await this.action(job); + // Logger.log(`[JOB] Job Execution finished: ${new Date((Date.now())).toISOString()}, ${JSON.stringify(result?.error)}, ${JSON.stringify(result)}`); + + if (job.period) { + setObj['status'] = enumJobStatus.PENDING; + setObj['nextExecutionTime'] = Date.now() + job.period; + } + let newHistoryEntry; + if (result) { + if (!result.successful) { + newHistoryEntry = { + time: Date.now(), + status: enumJobHistoryStatus.FAILED, + errors: [result.error] + }; + // update the job status to failed if not periodic + // if periodic, keep it pending, else if oneoff, set it to error + setObj['status'] = job.period ? enumJobStatus.PENDING : enumJobStatus.ERROR; + + // if the job has failed and it's not period, counter less than maxAttempts, set the job nextExecutionTime to now + failedJobDelayTime + if (!job.period && job.counter < config.maxAttempts) { + setObj['nextExecutionTime'] = Date.now() + config.failedJobDelayTime; + } + + } else { + newHistoryEntry = { + time: Date.now(), + status: enumJobHistoryStatus.SUCCESS, + result: result.result ? [result.result] : [] + }; + // update the job status to success + // numJobStatus.FINISHED if !job.period else enumJobStatus.PENDING; + setObj['status'] = job.period ? enumJobStatus.PENDING : enumJobStatus.FINISHED; + } + } + setObj['counter'] = job.counter + 1; + + const jobUpdate = await this.jobCollection.findOne({ id: job.id }); + if (jobUpdate) { + const currentHistory = jobUpdate.history || []; + setObj['history'] = [...currentHistory]; + if (newHistoryEntry) { + setObj['history'].push(newHistoryEntry); + } + await this.jobCollection.findOneAndUpdate({ id: job.id }, { + $set: setObj + }); + } + } catch (error) { + const currentHistory = job.history || []; + setObj['history'] = [...currentHistory, { + time: Date.now(), + status: enumJobHistoryStatus.FAILED, + errors: [error] + }]; + await this.jobCollection.findOneAndUpdate({ id: job.id }, { + $set: setObj + }); + + } + } } } + +export class JobScheduler { + private config: Required; + constructor(config: Required) { + this.config = config; + } + + public async findNextJob() { + let availableJobs = await this.config.jobCollection.find({ + status: { $in: [enumJobStatus.PENDING] } + }).toArray(); + // we sort jobs based on the config + availableJobs = availableJobs.filter(el => { + // If the job has a non-null period, it should always be included + if (el.period !== null) { + return true; + } + // ERROR jobs are always included + if (this.config.reExecuteFailedJobs && el.history.filter(ek => ek.status === enumJobHistoryStatus.FAILED).length > this.config.maxAttempts) { + return false; + } + if (Date.now() < el.nextExecutionTime) { + return false; + } + return true; + }); + + + // Sort jobs based on the config + availableJobs = availableJobs.sort((a, b) => { + if (this.config.usePriority) { + if (a.priority !== b.priority) { + return b.priority - a.priority; // Higher priority first + } else { + return a.nextExecutionTime - b.nextExecutionTime; // Earlier execution time first + } + } else { + return a.nextExecutionTime - b.nextExecutionTime; // Earlier execution time first + } + }); + + + const job = availableJobs[0]; + if (!job) { + return null; + } + + + // Update nextExecutionTime for periodic jobs + if (job.period !== null) { + job.nextExecutionTime = Date.now() + job.period; + await this.config.jobCollection.updateOne({ _id: job._id }, { $set: { nextExecutionTime: job.nextExecutionTime, status: enumJobStatus.PENDING } }); + } + return job; + } +} \ No newline at end of file diff --git a/packages/itmat-commons/test/unitTests/objStore.test.ts b/packages/itmat-commons/test/unitTests/objStore.test.ts index eaff8650f..85af4ef10 100644 --- a/packages/itmat-commons/test/unitTests/objStore.test.ts +++ b/packages/itmat-commons/test/unitTests/objStore.test.ts @@ -74,7 +74,7 @@ if (global.hasMinio) describe('OBJECT STORE CLASS TESTS', () => { ); let uploadResult; - let error: any; + let error: unknown; try { uploadResult = await client.uploadFile( fs.createReadStream(path.join(__dirname, '../files/fakefile.txt')), @@ -85,7 +85,9 @@ if (global.hasMinio) describe('OBJECT STORE CLASS TESTS', () => { error = e; } expect(uploadResult).toBeUndefined(); - expect(error.toString()).toBe('Error: File "jkfljsdkfjij042rjio2-fi0-ds9a" of study "fakeStudy3" already exists.'); + expect(error).toBeInstanceOf(Error); + if (error instanceof Error) + expect(error.toString()).toBe('Error: File "jkfljsdkfjij042rjio2-fi0-ds9a" of bucket "fakeStudy3" already exists.'); }); test('Download file', async () => { @@ -99,7 +101,7 @@ if (global.hasMinio) describe('OBJECT STORE CLASS TESTS', () => { 'sdfsad0fjds0fafdsj21' ); - const streamToString = (inputStream: Readable) => { + const streamToString = async (inputStream: Readable) => { return new Promise((resolve, reject) => { let string = ''; inputStream diff --git a/packages/itmat-commons/tsconfig.json b/packages/itmat-commons/tsconfig.json index da97c912e..781e6aac1 100644 --- a/packages/itmat-commons/tsconfig.json +++ b/packages/itmat-commons/tsconfig.json @@ -1,12 +1,13 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "module": "commonjs", + "module": "ES2022", "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, + "noImplicitAny": false, "noFallthroughCasesInSwitch": true }, "files": [], diff --git a/packages/itmat-commons/tsconfig.spec.json b/packages/itmat-commons/tsconfig.spec.json index e85ec32ef..c744cb73a 100644 --- a/packages/itmat-commons/tsconfig.spec.json +++ b/packages/itmat-commons/tsconfig.spec.json @@ -2,7 +2,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", + "module": "ES2022", "types": [ "jest", "node" diff --git a/packages/itmat-ui-react-e2e/.eslintrc.json b/packages/itmat-cores/.eslintrc.json similarity index 65% rename from packages/itmat-ui-react-e2e/.eslintrc.json rename to packages/itmat-cores/.eslintrc.json index b25e24a5c..4de975cd3 100644 --- a/packages/itmat-ui-react-e2e/.eslintrc.json +++ b/packages/itmat-cores/.eslintrc.json @@ -1,6 +1,5 @@ { "extends": [ - "plugin:cypress/recommended", "../../.eslintrc.json" ], "ignorePatterns": [ @@ -19,12 +18,17 @@ }, { "files": [ - "src/plugins/index.js" + "*.ts", + "*.tsx" ], - "rules": { - "@typescript-eslint/no-var-requires": "off", - "no-undef": "off" - } + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} } ] } \ No newline at end of file diff --git a/packages/itmat-cores/.lib.swcrc b/packages/itmat-cores/.lib.swcrc new file mode 100644 index 000000000..e6032199d --- /dev/null +++ b/packages/itmat-cores/.lib.swcrc @@ -0,0 +1,31 @@ +{ + "jsc": { + "target": "ES2022", + "parser": { + "syntax": "typescript", + "decorators": true, + "dynamicImport": true + }, + "transform": { + "decoratorMetadata": true, + "legacyDecorator": true + }, + "keepClassNames": true, + "externalHelpers": true, + "loose": true + }, + "module": { + "type": "commonjs", + "strict": true, + "noInterop": false + }, + "sourceMaps": true, + "exclude": [ + "jest.config.ts", + ".*.spec.tsx?$", + ".*.test.tsx?$", + "./src/jest-setup.ts$", + "./**/jest-setup.ts$", + ".*.js$" + ] +} diff --git a/packages/itmat-cores/README.md b/packages/itmat-cores/README.md new file mode 100644 index 000000000..5feddf1e1 --- /dev/null +++ b/packages/itmat-cores/README.md @@ -0,0 +1,11 @@ +# itmat-cores + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build itmat-cores` to build the library. + +## Running unit tests + +Run `nx test itmat-cores` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/packages/itmat-cores/config/config.sample.json b/packages/itmat-cores/config/config.sample.json new file mode 100644 index 000000000..96b86808e --- /dev/null +++ b/packages/itmat-cores/config/config.sample.json @@ -0,0 +1,81 @@ +{ + "appName": "ITMAT Interface", + "database": { + "mongo_url": "mongodb://localhost:27017", + "database": "itmat", + "collections": { + "jobs_collection": "JOB_COLLECTION", + "users_collection": "USER_COLLECTION", + "organisations_collection": "ORGANISATION_COLLECTION", + "studies_collection": "STUDY_COLLECTION", + "queries_collection": "QUERY_COLLECTION", + "log_collection": "LOG_COLLECTION", + "data_collection": "DATA_COLLECTION", + "roles_collection": "ROLE_COLLECTION", + "field_dictionary_collection": "FIELD_COLLECTION", + "files_collection": "FILES_COLLECTION", + "sessions_collection": "SESSIONS_COLLECTION", + "pubkeys_collection": "PUBKEY_COLLECTION", + "standardizations_collection": "STANDARDIZATION_COLLECTION", + "configs_collection": "CONFIG_COLLECTION", + "ontologies_collection": "ONTOLOGY_COLLECTION", + "docs_collection": "DOC_COLLECTION", + "cache_collection": "CACHE_COLLECTION", + "drives_collection": "DRIVE_COLLECTION", + "colddata_collection": "COLDDATA_COLLECTION", + "domains_collection": "DOMAIN_COLLECTION", + "webauthn_collection": "WEBAUTHN_COLLECTION", + "instance_collection": "INSTANCE_COLLECTION" + } + }, + "server": { + "port": 3333 + }, + "bcrypt": { + "saltround": 2 + }, + "objectStore": { + "host": "localhost", + "port": 8080, + "accessKey": "minioadmin", + "secretKey": "minioadmin", + "bucketRegion": "region", + "useSSL": false + }, + "sessionsSecret": "Change_Me", + "aesSecret": "change_this", + "nodemailer": { + "auth": { + "user": "change_this", + "pass": "change_this" + } + }, + "adminEmail": "admin@example.com", + "aeEndpoint": "http://localhost:9090", + "useWebdav": true, + "webdavPort": 1900, + "lxdEndpoint": "https://localhost:8443", + "lxdPortRange": [ + 30000, + 40000 + ], + "lxdSquidProxy": "http://traget_ip:3128", + "lxdStoragePool": "newpool", + "lxdProject": "sample-project", + "webdavServer": "http://localhost", + "systemKey":{ + "pubkey": "path/system_key.pub", + "privkey": "path/system_key.pem" + }, + "lxdCertFile": { + "cert": "/path/mylxd.crt", + "key": "/path/mylxd.key" + }, + "lxdRejectUnauthorized": false, + "jupyterPort": 8888, + "dmp": { + "host": "localhost", + "port": 3080 + } + +} diff --git a/packages/itmat-cores/express-user.d.ts b/packages/itmat-cores/express-user.d.ts new file mode 100644 index 000000000..de9b48a82 --- /dev/null +++ b/packages/itmat-cores/express-user.d.ts @@ -0,0 +1,17 @@ +import type { IUserWithoutToken } from '@itmat-broker/itmat-types'; + +declare global { + + namespace Express { + + // eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/no-empty-object-type + interface User extends IUserWithoutToken { } + + interface Request { + user?: User; + headers: IncomingHttpHeaders; + login(user: User, done: (err: unknown) => void): void; + logout(done: (err: unknown) => void): void; + } + } +} \ No newline at end of file diff --git a/packages/itmat-cores/jest.config.ts b/packages/itmat-cores/jest.config.ts new file mode 100644 index 000000000..5d7751b07 --- /dev/null +++ b/packages/itmat-cores/jest.config.ts @@ -0,0 +1,23 @@ +/* eslint-disable */ +import { readFileSync } from 'fs'; + +// Reading the SWC compilation config and remove the "exclude" +// for the test files to be compiled by SWC +const { exclude: _, ...swcJestConfig } = JSON.parse( + readFileSync(`${__dirname}/.lib.swcrc`, 'utf-8') +); +export default { + displayName: 'itmat-cores', + preset: '../../jest.preset.cjs', + transform: { + '^.+\\.[tj]s$': ['@swc/jest', swcJestConfig], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/packages/itmat-cores', + testEnvironment: "/../../test/fixtures/_minioJestEnv", + transformIgnorePatterns: [ + "node_modules", + "\\.pnp\\.[^\\\/]+$", + "test[\\/]fixtures[\\/]_minio" + ] +}; diff --git a/packages/itmat-cores/package.json b/packages/itmat-cores/package.json new file mode 100644 index 000000000..e52ad7906 --- /dev/null +++ b/packages/itmat-cores/package.json @@ -0,0 +1,5 @@ +{ + "name": "@itmat-broker/itmat-cores", + "version": "0.0.1", + "type": "commonjs" +} \ No newline at end of file diff --git a/packages/itmat-models/project.json b/packages/itmat-cores/project.json similarity index 54% rename from packages/itmat-models/project.json rename to packages/itmat-cores/project.json index 512b10b6e..70acf92f4 100644 --- a/packages/itmat-models/project.json +++ b/packages/itmat-cores/project.json @@ -1,8 +1,9 @@ { - "name": "itmat-models", + "name": "itmat-cores", "$schema": "../../node_modules/nx/schemas/project-schema.json", - "sourceRoot": "packages/itmat-models/src", + "sourceRoot": "packages/itmat-cores/src", "projectType": "library", + "tags": [], "targets": { "build": { "executor": "@nx/esbuild:esbuild", @@ -10,14 +11,17 @@ "{options.outputPath}" ], "options": { - "outputPath": "dist/packages/itmat-models", - "main": "packages/itmat-models/src/index.ts", - "tsConfig": "packages/itmat-models/tsconfig.lib.json", + "outputPath": "dist/packages/itmat-cores", + "main": "packages/itmat-cores/src/index.ts", + "tsConfig": "packages/itmat-cores/tsconfig.lib.json", "assets": [ - "packages/itmat-models/*.md" + "packages/itmat-cores/*.md" ], "generatePackageJson": true, - "thirdParty": true + "thirdParty": true, + "external": [ + "bcrypt" + ] } }, "lint": { @@ -29,12 +33,11 @@ "test": { "executor": "@nx/jest:jest", "outputs": [ - "{workspaceRoot}/coverage/packages/itmat-models" + "{workspaceRoot}/coverage/packages/itmat-cores" ], "options": { - "jestConfig": "packages/itmat-models/jest.config.ts" + "jestConfig": "packages/itmat-cores/jest.config.ts" } } - }, - "tags": [] + } } \ No newline at end of file diff --git a/packages/itmat-cores/src/authentication/pubkeyAuthentication.ts b/packages/itmat-cores/src/authentication/pubkeyAuthentication.ts new file mode 100644 index 000000000..d80742ac5 --- /dev/null +++ b/packages/itmat-cores/src/authentication/pubkeyAuthentication.ts @@ -0,0 +1,55 @@ +import { DBType } from '../database/database'; +import { CoreError, IUserWithoutToken, enumCoreErrors } from '@itmat-broker/itmat-types'; + + +export async function userRetrieval(db: DBType, pubkey: string, isSystemToken?: boolean, userId?: string): Promise { + if (isSystemToken && userId) { + // Handle system token - directly fetch user by userId + const associatedUser: IUserWithoutToken | null = await db.collections.users_collection.findOne( + { 'life.deletedTime': null, 'id': userId }, + { projection: { password: 0, otpSecret: 0 } } + ); + + if (!associatedUser) { + throw new CoreError( + enumCoreErrors.AUTHENTICATION_ERROR, + 'The user associated with the system token does not exist or is deleted!' + ); + } + + return associatedUser; + } else { + // Handle regular token - existing pubkey-based authentication + const pubkeyrec = await db.collections.pubkeys_collection.findOne({ 'jwtPubkey': pubkey, 'life.deletedTime': null }); + if (!pubkeyrec) { + throw new CoreError( + enumCoreErrors.AUTHENTICATION_ERROR, + 'The public-key embedded in the JWT is not valid!' + ); + } + if (!pubkeyrec.associatedUserId) { + throw new CoreError( + enumCoreErrors.AUTHENTICATION_ERROR, + 'The public-key embedded in the JWT is not associated with any user!' + ); + } + + const associatedUser: IUserWithoutToken | null = await db.collections.users_collection.findOne( + { 'life.deletedTime': null, 'id': pubkeyrec.associatedUserId }, + { projection: { password: 0, otpSecret: 0 } } + ); + + if (!associatedUser) { + throw new CoreError( + enumCoreErrors.AUTHENTICATION_ERROR, + 'The user associated with the public-key does not exist or is deleted!' + ); + } + // delete the password and otpSecret from the user object + delete associatedUser['password']; + delete associatedUser['otpSecret']; + + return associatedUser; + } +} + diff --git a/packages/itmat-cores/src/coreFunc/configCore.ts b/packages/itmat-cores/src/coreFunc/configCore.ts new file mode 100644 index 000000000..c98c0b4de --- /dev/null +++ b/packages/itmat-cores/src/coreFunc/configCore.ts @@ -0,0 +1,162 @@ +import { CoreError, ICacheConfig, IConfig, IDocConfig, IDomainConfig, IStudyConfig, ISystemConfig, IUserConfig, IUserWithoutToken, defaultSettings, enumConfigType, enumCoreErrors, enumReservedUsers, enumUserTypes } from '@itmat-broker/itmat-types'; +import { DBType } from '../database/database'; +import { v4 as uuid } from 'uuid'; +import { makeGenericResponse } from '../utils'; + +export class ConfigCore { + db: DBType; + constructor(db: DBType) { + this.db = db; + } + /** + * Get the config. Suggestions: always set useDefault to true. + * + * @param configType - The type of the config.. + * @param key - The key of the config. studyid, userid, or null for system. + * @param useDefault - Whether to use the default config if not found. + * + * @return IConfig + */ + public async getConfig(configType: enumConfigType, key: string | null, useDefault: boolean) { + const config = await this.db.collections.configs_collection.findOne({ 'type': configType, 'key': key, 'life.deletedTime': null }); + if (!config) { + if (useDefault) { + return { + id: uuid(), + type: configType, + key: key, + life: { + createdTime: Date.now(), + createdUser: enumReservedUsers.SYSTEM, + deletedTime: null, + deletedUser: null + }, + metadata: {}, + properties: (() => { + if (configType === enumConfigType.CACHECONFIG) { + return defaultSettings.cacheConfig; + } else if (configType === enumConfigType.STUDYCONFIG) { + return defaultSettings.studyConfig; + } else if (configType === enumConfigType.SYSTEMCONFIG) { + return defaultSettings.systemConfig; + } else if (configType === enumConfigType.USERCONFIG) { + return defaultSettings.userConfig; + } else if (configType === enumConfigType.DOCCONFIG) { + return defaultSettings.docConfig; + } else if (configType === enumConfigType.DOMAINCONFIG) { + return defaultSettings.domainConfig; + } else { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Config type not recognised.' + ); + } + })() + }; + } else { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Config does not exist.' + ); + } + } + return config; + } + + /** + * Edit the config. + * + * @param requester - The requester. + * @param configType - The type of the config. + * @param key - The key of the config. + * @param properties - The updated properties. + * + * @returns IGenericResponse + */ + public async editConfig(requester: IUserWithoutToken | undefined, configType: enumConfigType, key: string | null, properties: ISystemConfig | IStudyConfig | IUserConfig | IDocConfig | ICacheConfig | IDomainConfig) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + // for now only admin can edit config, update it for further use + if (requester.type !== enumUserTypes.ADMIN) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + // validate properties and config type match + if (!((configType === enumConfigType.SYSTEMCONFIG && 'defaultBackgroundColor' in properties) || + (configType === enumConfigType.STUDYCONFIG && 'defaultStudyProfile' in properties) || + (configType === enumConfigType.USERCONFIG && 'defaultUserExpiredDays' in properties) || + (configType === enumConfigType.DOCCONFIG && properties['defaultFileBucketId'] === 'doc') || + (configType === enumConfigType.CACHECONFIG && properties['defaultFileBucketId'] === 'cache') || + (configType === enumConfigType.DOMAINCONFIG && properties['defaultFileBucketId'] === 'domain') + )) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Config type and properties do not match.' + ); + } + + const config = await this.db.collections.configs_collection.findOne({ 'type': configType, 'key': key, 'life.deletedTime': null }); + if (!config) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Config does not exist.' + ); + } + await this.db.collections.configs_collection.updateOne({ 'type': configType, 'key': key, 'life.deletedTime': null }, { $set: { properties: properties } }); + + return makeGenericResponse(config.id, true, undefined, 'Config updated.'); + } + + /** + * Create a new config. + * Note, this API is used in case sth unusual happen. In most cases the API should neven be used. + * + * @param requester - The requester. + * @param configType - The type of the config. + * @param key - The key of the config. + * @param properties - The properties of the config. + * + * @returns IConfig + */ + public async createConfig(requester: IUserWithoutToken | undefined, configType: enumConfigType, key: string | null, properties: ISystemConfig | IStudyConfig | IUserConfig | IDocConfig | ICacheConfig | IDomainConfig) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + if (requester.type !== enumUserTypes.ADMIN) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + const config: IConfig = { + id: uuid(), + type: configType, + key: key, + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + }, + metadata: {}, + properties: properties + }; + + await this.db.collections.configs_collection.insertOne(config); + return config; + + } +} \ No newline at end of file diff --git a/packages/itmat-cores/src/coreFunc/dataCore.ts b/packages/itmat-cores/src/coreFunc/dataCore.ts new file mode 100644 index 000000000..7b8a077f5 --- /dev/null +++ b/packages/itmat-cores/src/coreFunc/dataCore.ts @@ -0,0 +1,1701 @@ +import { IField, enumDataTypes, ICategoricalOption, IValueVerifier, IGenericResponse, enumConfigType, defaultSettings, IAST, enumConditionOps, enumFileTypes, enumFileCategories, IFieldProperty, IFile, IData, enumASTNodeTypes, IRole, IStudyConfig, enumUserTypes, enumCoreErrors, IUserWithoutToken, CoreError, enumDataAtomicPermissions, enumDataTransformationOperation, enumCacheStatus, enumCacheType, FileUpload, enumStudyRoles, IDataSetSummary } from '@itmat-broker/itmat-types'; +import { v4 as uuid } from 'uuid'; +import { DBType } from '../database/database'; +import { FileCore } from './fileCore'; +import { PermissionCore } from './permissionCore'; +import { makeGenericResponse, convertToBufferAndUpload, getJsonFileContents } from '../utils'; +import { UtilsCore } from './utilsCore'; +import { Filter } from 'mongodb'; +import { DataTransformationCore } from './transformationCore'; +import { Readable } from 'stream'; +import { ObjectStore } from '@itmat-broker/itmat-commons'; + +type IDataTransformationClip = Record; + +type IDataTransformationClipArray = IDataTransformationClip[]; + +interface IDataInput { + fieldId: string; + value: string; // null for deleted data + properties?: Record; +} + +interface ValueVerifierInput { + formula: IAST; + condition: enumConditionOps; + value: string | number; + parameters: Record; +} + +interface CategoticalOptionInput { + code: string; + description: string; +} + +interface CreateFieldInput { + studyId: string; + fieldName: string; + fieldId: string; + description?: string; + dataType: enumDataTypes; + categoricalOptions?: CategoticalOptionInput[]; + unit?: string; + comments?: string; + verifier?: ValueVerifierInput[][]; + properties?: IFieldProperty[]; + metadata?: Record; +} + +type EditFieldInput = CreateFieldInput; + +export class DataCore { + db: DBType; + objStore: ObjectStore; + fileCore: FileCore; + permissionCore: PermissionCore; + utilsCore: UtilsCore; + dataTransformationCore: DataTransformationCore; + constructor(db: DBType, objStore: ObjectStore, fileCore: FileCore, permissionCore: PermissionCore, utilsCore: UtilsCore, dataTransformationCore: DataTransformationCore) { + this.db = db; + this.objStore = objStore; + this.fileCore = fileCore; + this.permissionCore = permissionCore; + this.utilsCore = utilsCore; + this.dataTransformationCore = dataTransformationCore; + } + + /** + * Get the list of fields of a study. Note, duplicate fields will be joined and only remain the latest one. + * + * @param requester - The requester. + * @param studyId - The id of the study. + * @param dataVersions - The data version; if not specified, use the latest one (include all previous ones) by default. + * @param selectedFields - The list of ids of fields to return. + * + * @return IField[] - The list of objects of IField. + */ + public async getStudyFields(requester: IUserWithoutToken | undefined, studyId: string, dataVersion?: string | null | Array, selectedFields?: string[]) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + const roles = await this.permissionCore.getRolesOfUser(requester, requester.id, studyId); + if (roles.length === 0) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + const study = await this.db.collections.studies_collection.findOne({ 'id': studyId, 'life.deletedTime': null }); + if (!study) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Study does not exist.' + ); + } + + const regularExpressions: string[] = []; + for (const role of roles) { + for (const permission of role.dataPermissions) { + for (const fieldRE of permission.fields) { + if ((permission.permission & 4) === 4) { + regularExpressions.push(fieldRE); + } + } + } + } + let availableDataVersions: Array = []; + if (dataVersion === null) { + availableDataVersions.push(null); + } else if (typeof dataVersion === 'string') { + availableDataVersions.push(dataVersion); + } else if (Array.isArray(dataVersion)) { + availableDataVersions.push(...dataVersion); + } else { + availableDataVersions = (study.currentDataVersion === -1 ? [] : study.dataVersions.filter((__unused__el, index) => index <= study.currentDataVersion)).map(el => el.id); + } + const fields = await this.db.collections.field_dictionary_collection.aggregate([{ + $match: { + $and: [ + { studyId: studyId, dataVersion: { $in: availableDataVersions } }, + { fieldId: selectedFields ? { $in: selectedFields } : { $in: regularExpressions.map(el => new RegExp(el)) } }, + { fieldId: { $in: [new RegExp('^.*$')] } } + ] + } + }, { + $sort: { + 'life.createdTime': -1 + } + }, { + $group: { + _id: '$fieldId', + doc: { $first: '$$ROOT' } + } + }, { + $replaceRoot: { + newRoot: '$doc' + } + }], { allowDiskUse: true }).toArray(); + return fields.filter(el => el.life.deletedTime === null); + } + /** + * Create a field of a study. To adjust to data versioning, create an existing field wil not throw an error. + * + * @param requester - The requester. + * @param studyId - The id of the study. + * @param fieldName - The name of the field. + * @param fieldId - The value of the id of the field. Should be unique. + * @param description - The description of the field. + * @param dataType - The dataType of the field. + * @param categoricalOptions - The options of the field if the field is a categorical field. + * @param unit - The unit of the field. + * @param comments - The comments of the field. + * @param verifier - The verifier of the field. + * @param properties - The properties of the field. + * + * @return IField + */ + public async createField(requester: IUserWithoutToken | undefined, fieldInput: CreateFieldInput): Promise { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + const roles = await this.permissionCore.getRolesOfUser(requester, requester.id, fieldInput.studyId); + if (roles.length === 0) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + let hasPermission = false; + for (const role of roles) { + for (const permission of role.dataPermissions) { + for (const fieldRE of permission.fields) { + if (new RegExp(fieldRE).test(fieldInput.fieldId) && (permission.permission & 2) === 2) { + hasPermission = true; + } + } + } + } + + if (!hasPermission) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'No permission to create this field.' + ); + } + + const errors = this.validateFieldEntry(fieldInput); + if (errors.length > 0) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + errors[0] + ); + } + + // add id and life for verifier; + const verifierWithId: IValueVerifier[][] = []; + if (fieldInput.verifier) { + for (let i = 0; i < fieldInput.verifier.length; i++) { + verifierWithId.push([]); + for (let j = 0; j < fieldInput.verifier[i].length; j++) { + verifierWithId[verifierWithId.length - 1].push({ + ...fieldInput.verifier[i][j] + }); + } + } + } + + let categoricalOptionsWithId: ICategoricalOption[] | undefined = undefined; + if (fieldInput.categoricalOptions) { + categoricalOptionsWithId = []; + for (let i = 0; i < fieldInput.categoricalOptions.length; i++) { + categoricalOptionsWithId.push({ + ...fieldInput.categoricalOptions[i], + id: uuid(), + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }); + } + } + + const fieldEntry: IField = { + id: uuid(), + studyId: fieldInput.studyId, + fieldId: fieldInput.fieldId, + fieldName: fieldInput.fieldName, + description: fieldInput.description, + dataType: fieldInput.dataType, + categoricalOptions: categoricalOptionsWithId, + unit: fieldInput.unit, + comments: fieldInput.comments, + dataVersion: null, + verifier: verifierWithId, + properties: fieldInput.dataType === enumDataTypes.FILE ? [ + ...(fieldInput.properties ?? []), + { + name: 'FileName', + required: true + } + ] : fieldInput.properties, + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + }, + metadata: fieldInput.metadata ?? {} + }; + + await this.db.collections.field_dictionary_collection.insertOne(fieldEntry); + + return fieldEntry; + } + /** + * Edit a field of a study. + * + * @param requester - The requester. + * @param studyId - The id of the study. + * @param fieldName - The name of the field. + * @param fieldId - The value of the id of the field. Should be unique. + * @param description - The description of the field. + * @param dataType - The dataType of the field. + * @param categoricalOptions - The options of the field if the field is a categorical field. + * @param unit - The unit of the field. + * @param comments - The comments of the field. + * @param verifier - The verifier of the field. + * @param properties - The properties of the field. + * + * @return IField + */ + public async editField(requester: IUserWithoutToken | undefined, fieldInput: EditFieldInput): Promise { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + const roles = await this.permissionCore.getRolesOfUser(requester, requester.id, fieldInput.studyId); + if (roles.length === 0) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + let hasPermission = false; + for (const role of roles) { + for (const permission of role.dataPermissions) { + for (const fieldRE of permission.fields) { + if (new RegExp(fieldRE).test(fieldInput.fieldId) && (permission.permission & 2) === 2) { + hasPermission = true; + } + } + } + } + + if (!hasPermission) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'No permission to create this field.' + ); + } + + const field = await this.db.collections.field_dictionary_collection.findOne({ 'studyId': fieldInput.studyId, 'fieldId': fieldInput.fieldId, 'life.deletedTime': null }); + if (!field) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Field does not exist.' + ); + } + + const errors = this.validateFieldEntry(fieldInput); + + if (errors.length > 0) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + errors[0] + ); + } + + const verifierWithId: IValueVerifier[][] = []; + if (fieldInput.verifier) { + for (let i = 0; i < fieldInput.verifier.length; i++) { + verifierWithId.push([]); + for (let j = 0; j < fieldInput.verifier[i].length; j++) { + verifierWithId[verifierWithId.length - 1].push({ + ...fieldInput.verifier[i][j] + }); + } + } + } + + let categoricalOptionsWithId: ICategoricalOption[] | undefined = field.categoricalOptions; + if (fieldInput.categoricalOptions) { + categoricalOptionsWithId = []; + for (let i = 0; i < fieldInput.categoricalOptions.length; i++) { + categoricalOptionsWithId.push({ + ...fieldInput.categoricalOptions[i], + id: uuid(), + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }); + } + } + + // insert directly + await this.db.collections.field_dictionary_collection.insertOne({ + id: uuid(), + studyId: fieldInput.studyId, + fieldId: fieldInput.fieldId, + fieldName: fieldInput.fieldName ?? field.fieldName, + description: fieldInput.description ?? field.description, + dataType: fieldInput.dataType ?? field.dataType, + categoricalOptions: categoricalOptionsWithId, + unit: fieldInput.unit ?? field.unit, + comments: fieldInput.comments ?? field.comments, + dataVersion: null, + verifier: fieldInput.verifier ? verifierWithId : field.verifier, + properties: fieldInput.properties ?? field.properties, + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }); + + return makeGenericResponse(fieldInput.fieldId, true, undefined, `Field ${fieldInput.fieldId} has been edited.`); + } + + /** + * Delete a field of a study. + * + * @param requester - The requester. + * @param studyId - The id of the stduy. + * @param fieldId - The id of the field. + * + * @return IGenericResponse + */ + public async deleteField(requester: IUserWithoutToken | undefined, studyId: string, fieldId: string) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + const roles = await this.permissionCore.getRolesOfUser(requester, requester.id, studyId); + if (roles.length === 0) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + let hasPermission = false; + for (const role of roles) { + for (const permission of role.dataPermissions) { + for (const fieldRE of permission.fields) { + if (new RegExp(fieldRE).test(fieldId) && (permission.permission & 1) === 1) { + hasPermission = true; + } + } + } + } + + if (!hasPermission) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'No permission to delete this field.' + ); + } + + const field = (await this.db.collections.field_dictionary_collection.find({ studyId: studyId, fieldId: fieldId }).sort({ 'life.createdTime': -1 }).limit(1).toArray())[0]; + if (!field || field.life.deletedTime) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Field does not exist.' + ); + } + + await this.db.collections.field_dictionary_collection.insertOne({ + id: uuid(), + studyId: studyId, + fieldId: fieldId, + fieldName: field.fieldName, + description: field.description, + dataType: field.dataType, + categoricalOptions: field.categoricalOptions, + unit: field.unit, + comments: field.comments, + dataVersion: null, + verifier: field.verifier, + properties: field.properties, + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: Date.now(), + deletedUser: requester.id + }, + metadata: {} + }); + return makeGenericResponse(fieldId, true, undefined, `Field ${fieldId} has been deleted.`); + } + + /** + * Validate field entry. This function only checks the input parameters without interacting with the database. + * + * @param fieldInput - The field input object. + * + * @return array[] - The error array, empty for null errors. + */ + public validateFieldEntry(fieldInput: CreateFieldInput): string[] { + const errors: string[] = []; + // check missing field + const complusoryField: Array = [ + 'fieldId', + 'fieldName', + 'dataType' + ]; + for (const key of complusoryField) { + if (fieldInput[key] === undefined && fieldInput[key] === null) { + errors.push(`${key} should not be empty.`); + return errors; + } + } + + // only english letters, numbers and _ are allowed in fieldIds + if (!/^[a-zA-Z0-9_]*$/.test(fieldInput.fieldId || '')) { + errors.push('FieldId should contain letters, numbers and _ only.'); + return errors; + } + // data types + if (!Object.values(enumDataTypes).includes(fieldInput.dataType)) { + errors.push(`Data type shouldn't be ${fieldInput.dataType}: use 'INTEGER' for integer, 'DECIMAL' for decimal, 'STRING' for string, 'BOOLEAN' for boolean, 'DATETIME' for datetime, 'FILE' for file, 'JSON' for json and 'CATEGORICAL' for categorical.`); + return errors; + } + // check possiblevalues to be not-empty if datatype is categorical + if (fieldInput.dataType === enumDataTypes.CATEGORICAL) { + if (fieldInput.categoricalOptions !== undefined && fieldInput.categoricalOptions !== null) { + if (fieldInput.categoricalOptions.length === 0) { + errors.push(`${fieldInput.fieldId}-${fieldInput.fieldName}: possible values can't be empty if data type is categorical.`); + return errors; + } + for (let i = 0; i < fieldInput.categoricalOptions.length; i++) { + fieldInput.categoricalOptions[i]['id'] = uuid(); + } + } else { + errors.push(`${fieldInput.fieldId}-${fieldInput.fieldName}: possible values can't be empty if data type is categorical.`); + return errors; + } + } + return errors; + } + + /** + * Upload data clips to a study. + * + * @param requester - The requester. + * @param studyId - The id of the study. + * @param data - The list of data clips. + * + * @return IGenericResponse - The list of objects of IGenericResponse + */ + public async uploadData(requester: IUserWithoutToken | undefined, studyId: string, data: IDataInput[]) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + const study = await this.db.collections.studies_collection.findOne({ 'id': studyId, 'life.deletedTime': null }); + if (!study) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Study does not exist.' + ); + } + + const availableDataVersions: Array = (study.currentDataVersion === -1 ? [] : study.dataVersions.filter((__unused__el, index) => index <= study.currentDataVersion)).map(el => el.id); + availableDataVersions.push(null); + const availableFields = await this.getStudyFields(requester, studyId, availableDataVersions); + const availableFieldsMapping: Record = availableFields.reduce((acc: Record, el: IField) => { + acc[el.fieldId] = el; + return acc; + }, {}); + + + const studyConfig = ((await this.db.collections.configs_collection.findOne({ type: enumConfigType.STUDYCONFIG, key: studyId }))?.properties ?? defaultSettings.studyConfig) as IStudyConfig; + + const response: IGenericResponse[] = []; + let bulk = this.db.collections.data_collection.initializeUnorderedBulkOp(); + let counter = -1; // index of the data + for (const dataClip of data) { + counter++; + const hasPermission = await this.permissionCore.checkFieldOrDataPermission(requester, studyId, dataClip, enumDataAtomicPermissions.WRITE); + if (!hasPermission) { + response.push(makeGenericResponse(counter.toString(), false, enumCoreErrors.NO_PERMISSION_ERROR, enumCoreErrors.NO_PERMISSION_ERROR)); + continue; + } + + if (!(dataClip.fieldId in availableFieldsMapping)) { + response.push(makeGenericResponse(counter.toString(), false, enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, `Field ${dataClip.fieldId}: Field not found`)); + continue; + } + + /* Check value is value */ + let error: IGenericResponse | undefined = undefined; + let parsedValue: unknown; + if (dataClip.value.toString() === studyConfig.defaultRepresentationForMissingValue) { + parsedValue = studyConfig.defaultRepresentationForMissingValue; + } else { + if (!(dataClip.fieldId in availableFieldsMapping)) { + error = makeGenericResponse(counter.toString(), false, enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, `Field ${dataClip.fieldId}: Field not found`); + response.push(error); + continue; + } + const field = availableFieldsMapping[dataClip.fieldId]; + switch (field.dataType) { + case enumDataTypes.DECIMAL: {// decimal + if (typeof (dataClip.value) !== 'string') { + error = makeGenericResponse(counter.toString(), false, enumCoreErrors.CLIENT_MALFORMED_INPUT, `Field ${dataClip.fieldId}: Cannot parse as decimal.`); + break; + } + if (!/^-?\d+(\.\d+)?$/.test(dataClip.value)) { + error = makeGenericResponse(counter.toString(), false, enumCoreErrors.CLIENT_MALFORMED_INPUT, `Field ${dataClip.fieldId}: Cannot parse as decimal.`); + break; + } + parsedValue = parseFloat(dataClip.value); + break; + } + case enumDataTypes.INTEGER: {// integer + if (typeof (dataClip.value) !== 'string') { + error = makeGenericResponse(counter.toString(), false, enumCoreErrors.CLIENT_MALFORMED_INPUT, `Field ${dataClip.fieldId}: Cannot parse as integer.`); + break; + } + if (!/^-?\d+$/.test(dataClip.value)) { + error = makeGenericResponse(counter.toString(), false, enumCoreErrors.CLIENT_MALFORMED_INPUT, `Field ${dataClip.fieldId}: Cannot parse as integer.`); + break; + } + parsedValue = parseInt(dataClip.value, 10); + break; + } + case enumDataTypes.BOOLEAN: {// boolean + if (typeof (dataClip.value) !== 'string') { + error = makeGenericResponse(counter.toString(), false, enumCoreErrors.CLIENT_MALFORMED_INPUT, `Field ${dataClip.fieldId}: Cannot parse as boolean.`); + break; + } + if (dataClip.value.toString().toLowerCase() === 'true' || dataClip.value.toString().toLowerCase() === 'false') { + parsedValue = dataClip.value.toLowerCase() === 'true'; + } else { + error = makeGenericResponse(counter.toString(), false, enumCoreErrors.CLIENT_MALFORMED_INPUT, `Field ${dataClip.fieldId}: Cannot parse as boolean.`); + break; + } + break; + } + case enumDataTypes.STRING: { + if (typeof (dataClip.value) !== 'string') { + error = makeGenericResponse(counter.toString(), false, enumCoreErrors.CLIENT_MALFORMED_INPUT, `Field ${dataClip.fieldId}: Cannot parse as string.`); + break; + } + parsedValue = dataClip.value.toString(); + break; + } + case enumDataTypes.DATETIME: { + if (typeof (dataClip.value) !== 'string') { + error = makeGenericResponse(counter.toString(), false, enumCoreErrors.CLIENT_MALFORMED_INPUT, `Field ${dataClip.fieldId}: Cannot parse as date. Value for date type must be in ISO format.`); + break; + } + const matcher = /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?/; + if (!dataClip.value.match(matcher)) { + error = makeGenericResponse(counter.toString(), false, enumCoreErrors.CLIENT_MALFORMED_INPUT, `Field ${dataClip.fieldId}: Cannot parse as date. Value for date type must be in ISO format.`); + break; + } + parsedValue = dataClip.value.toString(); + break; + } + case enumDataTypes.JSON: { + parsedValue = JSON.parse(dataClip.value); + break; + } + case enumDataTypes.FILE: { + parsedValue = dataClip.value; + break; + } + case enumDataTypes.CATEGORICAL: { + if (!(availableFieldsMapping[dataClip.fieldId].categoricalOptions)) { + error = makeGenericResponse(counter.toString(), false, enumCoreErrors.CLIENT_MALFORMED_INPUT, `Field ${dataClip.fieldId}: Cannot parse as categorical, possible values not defined.`); + break; + } + if (!((availableFieldsMapping[dataClip.fieldId].categoricalOptions as ICategoricalOption[]).map((el) => el.code).includes(dataClip.value?.toString()))) { + error = makeGenericResponse(counter.toString(), false, enumCoreErrors.CLIENT_MALFORMED_INPUT, `Field ${dataClip.fieldId}: Cannot parse as categorical, value not in value list.`); + break; + } else { + parsedValue = dataClip.value?.toString(); + } + break; + } + default: { + error = makeGenericResponse(counter.toString(), false, enumCoreErrors.CLIENT_MALFORMED_INPUT, `Field ${dataClip.fieldId}: Invalid data Type.`); + break; + } + } + const verifier = availableFieldsMapping[dataClip.fieldId].verifier; + if (verifier && verifier.length) { + const resEach: boolean[] = []; + for (let i = 0; i < verifier.length; i++) { + resEach.push(true); + for (let j = 0; j < verifier[i].length; j++) { + if ((typeof parsedValue !== 'string' && typeof parsedValue !== 'number') || !this.utilsCore.validValueWithVerifier(parsedValue, verifier[i][j])) { + resEach[resEach.length - 1] = false; + break; + } + } + } + if (resEach.every(el => !el)) { + error = makeGenericResponse(counter.toString(), false, enumCoreErrors.CLIENT_MALFORMED_INPUT, `Field ${dataClip.fieldId} value ${parsedValue}: Failed to pass the verifier.`); + } + } + if (field.properties) { + for (const property of field.properties) { + if (property.required && (!dataClip.properties || dataClip.properties[property.name] === undefined)) { + error = makeGenericResponse(counter.toString(), false, enumCoreErrors.CLIENT_MALFORMED_INPUT, `Field ${dataClip.fieldId}: Property ${property.name} is required.`); + break; + } + if (property.verifier && dataClip.properties) { + const resEach: boolean[] = []; + for (let i = 0; i < property.verifier.length; i++) { + resEach.push(true); + for (let j = 0; j < property.verifier[i].length; j++) { + if ((typeof dataClip.properties[property.name] !== 'string' && typeof dataClip.properties[property.name] !== 'number') || + !this.utilsCore.validValueWithVerifier(dataClip.properties[property.name] as string | number, property.verifier[i][j])) { + resEach[resEach.length - 1] = false; + break; + } + } + } + if (resEach.every(el => !el)) { + error = makeGenericResponse(counter.toString(), false, enumCoreErrors.CLIENT_MALFORMED_INPUT, `Field ${dataClip.fieldId} value ${dataClip.properties[property.name]}: Property ${property.name} failed to pass the verifier.`); + } + } + } + } + } + if (error) { + response.push(error); + continue; + } else { + response.push(makeGenericResponse(counter.toString(), true, undefined, `Field ${dataClip.fieldId} value ${dataClip.value} successfully uploaded.`)); + } + + bulk.insert({ + id: uuid(), + studyId: study.id, + fieldId: dataClip.fieldId, + dataVersion: null, + value: parsedValue, + properties: dataClip.properties, + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }); + + if (bulk.batches.length > 999) { + await bulk.execute(); + bulk = this.db.collections.data_collection.initializeUnorderedBulkOp(); + } + } + if (bulk.batches.length !== 0) + await bulk.execute(); + return response; + } + + /** + * Get the data of a study. + * + * @param requester - The requester. + * @param studyId - The id of the study. + * @param fieldIds - The list of regular expressions of fields to return. + * @param dataVersions - The list of data versions to return. + * @param aggregation - The pipeline of the data aggregation. + * @param useCache - Whether to use the cached data. + * @param forceUpdate - Whether to force update the cache. + * + * @return Partial[] - The list of objects of Partial + */ + public async getStudyData(requester: IUserWithoutToken | undefined, studyId: string, selectedFieldIds?: string[], dataVersion?: string | null | Array, aggregation?: Record }>>, useCache?: boolean, forceUpdate?: boolean, fromCold?: boolean) { + console.time('label 1'); + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + const roles = (await this.permissionCore.getRolesOfUser(requester, requester.id, studyId)); + if (roles.length === 0) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + const study = await this.db.collections.studies_collection.findOne({ 'id': studyId, 'life.deletedTime': null }); + if (!study) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Study does not exist.' + ); + } + + const config = await this.db.collections.configs_collection.findOne({ type: enumConfigType.STUDYCONFIG, key: studyId }); + if (!config) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Study config not found.' + ); + } + + let fieldIds: string[] | undefined = selectedFieldIds; + let availableDataVersions: Array = []; + if (dataVersion === null) { + availableDataVersions.push(null); + } else if (typeof dataVersion === 'string') { + availableDataVersions.push(dataVersion); + } else if (Array.isArray(dataVersion)) { + availableDataVersions.push(...dataVersion); + } else { + availableDataVersions = (study.currentDataVersion === -1 ? [] : study.dataVersions.filter((__unused__el, index) => index <= study.currentDataVersion)).map(el => el.id); + availableDataVersions.push(null); + } + if (!fieldIds) { + fieldIds = (await this.getStudyFields(requester, studyId, availableDataVersions)).map(el => el.fieldId); + } + + /** Check hash first */ + let hash: string; + if (useCache) { + hash = this.utilsCore.computeHash({ + query: 'getStudyData', + requester: requester.id, + studyId: studyId, + fieldIds: fieldIds, + dataVersion: dataVersion, + aggregation: aggregation + }); + const hashedInfo = await this.db.collections.cache_collection.find({ 'keyHash': hash, 'life.deletedTime': null, 'status': enumCacheStatus.INUSE }).sort({ 'life.createdTime': -1 }).limit(1).toArray(); + if (hashedInfo.length === 1 && !forceUpdate) { + return await getJsonFileContents(this.objStore, 'cache', hashedInfo[0].uri); + } else { + // raw data by the permission + const data = await this.getDataByRoles(requester, roles, studyId, availableDataVersions, fieldIds, fromCold); + // data transformation if aggregation is provided + const transformed = aggregation ? this.dataTransformationCore.transformationAggregate(data as unknown as IDataTransformationClipArray, aggregation) : data; + // write to minio and cache collection + const info = await convertToBufferAndUpload(this.fileCore, requester, { data: data }); + const newHashInfo = { + id: uuid(), + keyHash: hash, + uri: info.uri, + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + }, + status: enumCacheStatus.INUSE, + keys: { + query: 'getStudyData', + requester: requester, + studyId: studyId, + fieldIds: fieldIds, + dataVersions: availableDataVersions, + aggregation: aggregation + }, + type: enumCacheType.API, + metadata: {} + }; + await this.db.collections.cache_collection.insertOne(newHashInfo); + return transformed; + } + } else { + // raw data by the permission + const data = await this.getDataByRoles(requester, roles, studyId, availableDataVersions, fieldIds, fromCold); + // data transformation if aggregation is provided + const transformed = aggregation ? this.dataTransformationCore.transformationAggregate(data as unknown as IDataTransformationClipArray, aggregation) : data; + return transformed; + } + } + + /** + * This is a shortcut function to get the latest data of a study. + * + * @param requester - The requester. + * @param studyId - The id of the study. + * @param fieldIds - The list of regular expressions of fields to return. + * @returns + */ + public async getDataLatest(requester: IUserWithoutToken | undefined, studyId: string, selectedFieldIds?: string[]) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + const roles = (await this.permissionCore.getRolesOfUser(requester, requester.id, studyId)); + if (roles.length === 0) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + const study = await this.db.collections.studies_collection.findOne({ 'id': studyId, 'life.deletedTime': null }); + if (!study) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Study does not exist.' + ); + } + + const config = await this.db.collections.configs_collection.findOne({ type: enumConfigType.STUDYCONFIG, key: studyId }); + if (!config) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Study config not found.' + ); + } + + const fieldIds: string[] | undefined = selectedFieldIds; + const matchFilter: Filter = { + studyId: studyId + }; + + const roleArr: Filter[] = []; + for (const role of roles) { + const permissionArr: Filter[] = []; + for (let i = 0; i < role.dataPermissions.length; i++) { + if (role.dataPermissions[i].fields.length === 0) { + continue; + } + const obj = { + fieldId: { $in: role.dataPermissions[i].fields.map(el => new RegExp(el)) } + }; + if (role.dataPermissions[i].dataProperties) { + for (const key of Object.keys(role.dataPermissions[i].dataProperties)) { + obj[`properties.${key}`] = { $in: role.dataPermissions[i].dataProperties[key].map(el => new RegExp(el)) }; + } + } + if (!role.dataPermissions[i].includeUnVersioned) { + obj['dataVersion'] = { $ne: null }; + } + permissionArr.push(obj); + } + if (permissionArr.length === 0) { + return []; + } + roleArr.push({ $or: permissionArr }); + } + const dataVersions: Array = study.dataVersions.map(el => el.id); + dataVersions.push(null); + // we need to query each field based on its properties + const availableFields = (await this.getStudyFields(requester, studyId, dataVersions)).reduce((a, c) => { + a[c.fieldId] = c; + return a; + }, {}); + const availableFieldIds = Object.keys(availableFields); + const refactoredFieldIds = fieldIds ?? Object.keys(availableFields); + let res: IData[] = []; + + const queries = refactoredFieldIds.map(async (fieldId) => { + if (availableFieldIds.includes(fieldId) || availableFieldIds.some(el => new RegExp(el).test(fieldId))) { + const propertyFilter: Record = {}; + if (availableFields[fieldId].properties) { + for (const property of availableFields[fieldId].properties) { + propertyFilter[`${property.name}`] = `$properties.${property.name}`; + } + } + + const data = await this.db.collections.colddata_collection.aggregate([{ + $match: { ...matchFilter, fieldId: fieldId } + }, { + $match: { $or: [{ $or: roleArr }, { 'life.createdUser': requester.id }] } + }, { + $sort: { + 'life.createdTime': -1 + } + }, { + $group: { + _id: { + ...propertyFilter + }, + latestDocument: { $first: '$$ROOT' } + } + }, { + $replaceRoot: { newRoot: '$latestDocument' } + }, { + $project: { + _id: 0, + id: 0, + dataVersion: 0, + life: 0, + metadata: 0 + } + }], { allowDiskUse: true }).toArray(); + + return data; + } else { + return []; + } + }); + + // Execute all queries in parallel and wait for the results + const results = await Promise.all(queries); + + // Flatten the array of results and concatenate it into the final `res` array + res = results.flat(); + return res; + + } + + /** + * Get the files of a study. This function reuse the getStudyData function. + * + * @param requester - The requester. + * @param studyId - The id of the study. + * @param selectedFieldIds - The list of regular expressions of fields to return. + * @param dataVersion - The list of data versions to return. + * @returns IFile[] - The list of objects of IFile + */ + public async getStudyFiles(requester: IUserWithoutToken | undefined, studyId: string, selectedFieldIds?: string[], dataVersion?: string | null | Array, readable?: boolean, useCache?: boolean, forceUpdate?: boolean) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + const roles = (await this.permissionCore.getRolesOfUser(requester, requester.id, studyId)); + if (roles.length === 0) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + const study = await this.db.collections.studies_collection.findOne({ 'id': studyId, 'life.deletedTime': null }); + if (!study) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Study does not exist.' + ); + } + + const config = await this.db.collections.configs_collection.findOne({ type: enumConfigType.STUDYCONFIG, key: studyId }); + if (!config) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Study config not found.' + ); + } + + const readFiles = async () => { + let fieldIds: string[] | undefined = selectedFieldIds; + let availableDataVersions: Array = []; + if (dataVersion === null) { + availableDataVersions.push(null); + } else if (typeof dataVersion === 'string') { + availableDataVersions.push(dataVersion); + } else if (Array.isArray(dataVersion)) { + availableDataVersions.push(...dataVersion); + } else { + availableDataVersions = (study.currentDataVersion === -1 ? [] : study.dataVersions.filter((__unused__el, index) => index <= study.currentDataVersion)).map(el => el.id); + availableDataVersions.push(null); + } + if (!fieldIds) { + fieldIds = (await this.getStudyFields(requester, studyId, availableDataVersions)).filter(el => el.dataType === enumDataTypes.FILE).map(el => el.fieldId); + } else { + const fields = (await this.getStudyFields(requester, studyId, availableDataVersions)).filter(el => el.dataType === enumDataTypes.FILE).map(el => el.fieldId); + fieldIds = fieldIds.filter(el => fields.includes(el)); + } + if (fieldIds.length === 0) { + return []; + } + const fileDataRecords: IData[] = (await this.getStudyData( + requester, + studyId, + fieldIds, + availableDataVersions, + undefined, + false + )) as unknown as IData[]; + if (!Array.isArray(fileDataRecords)) { + return []; + } + const batchSize = 10000; // Define a suitable batch size + const promises: Promise[]>[] = []; + + for (let i = 0; i < fileDataRecords.length; i += batchSize) { + const batchIds = fileDataRecords.slice(i, i + batchSize).map(el => String(el.value)); + const promise = this.db.collections.files_collection.find({ id: { $in: batchIds } }, { allowDiskUse: true }) + .project({ + '_id': 0, + 'fileType': 0, + 'fileCategory': 0, + 'sharedUsers': 0, + 'life.deletedTime': 0, + 'life.deletedUser': 0 + }).toArray(); + promises.push(promise); + } + + const files = (await Promise.all(promises)).flat(); // Flatten the array of arrays + if (readable) { + const users = await this.db.collections.users_collection.find({}).toArray(); + const edited = [...files]; + for (const file of edited) { + const user = users.find(el => el.id === file.life?.createdUser); + if (file.life) { + file.life.createdUser = user ? `${user.firstname} ${user.lastname}` : file.life?.createdUser; + } + } + return edited; + } else { + return files; + } + }; + + if (useCache) { + const hash = this.utilsCore.computeHash({ + query: 'getStudyFiles', + studyId: studyId, + roles: roles, + fieldIds: selectedFieldIds + }); + const hashedInfo = await this.db.collections.cache_collection.find({ 'keyHash': hash, 'life.deletedTime': null, 'status': enumCacheStatus.INUSE }).sort({ 'life.createdTime': -1 }).limit(1).toArray(); + // if hash is not found, generate the new summary and cache it + if (forceUpdate || !hashedInfo || hashedInfo.length === 0) { + const newFiles = await readFiles(); + const info = await convertToBufferAndUpload(this.fileCore, requester, { files: newFiles }); + await this.db.collections.cache_collection.insertOne({ + id: uuid(), + keyHash: hash, + uri: info.uri, + status: enumCacheStatus.INUSE, + keys: { + query: 'getStudyFiles', + studyId: studyId, + roles: roles, + fieldIds: selectedFieldIds + }, + type: enumCacheType.API, + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }); + return newFiles; + } else { + return ((await getJsonFileContents(this.objStore, 'cache', hashedInfo[0].uri)) as unknown as { files: IFile[] }).files; + } + } else { + return await readFiles(); + } + + } + + + public async getDataByRoles(requester: IUserWithoutToken, roles: IRole[], studyId: string, dataVersions: Array, fieldIds?: string[], fromCold?: boolean) { + const matchFilter: Filter = { + studyId: studyId, + dataVersion: { $in: dataVersions } + }; + + const dbCollection = fromCold ? this.db.collections.colddata_collection : this.db.collections.data_collection; + + const roleArr: Filter[] = []; + for (const role of roles) { + const permissionArr: Filter[] = []; + for (let i = 0; i < role.dataPermissions.length; i++) { + if (role.dataPermissions[i].fields.length === 0) { + continue; + } + const obj: Filter = { + fieldId: { $in: role.dataPermissions[i].fields.map(el => new RegExp(el)) } + }; + if (role.dataPermissions[i].dataProperties) { + for (const key of Object.keys(role.dataPermissions[i].dataProperties)) { + obj[`properties.${key}`] = { $in: role.dataPermissions[i].dataProperties[key].map(el => new RegExp(el)) }; + } + } + if (!role.dataPermissions[i].includeUnVersioned) { + obj['dataVersion'] = { $ne: null }; + } + permissionArr.push(obj); + } + if (permissionArr.length === 0) { + return []; + } + roleArr.push({ $or: permissionArr }); + } + + const availableFields = (await this.getStudyFields(requester, studyId, dataVersions)).reduce((a, c) => { + a[c.fieldId] = c; + return a; + }, {}); + const availableFieldIds = Object.keys(availableFields); + const refactoredFieldIds = fieldIds ?? Object.keys(availableFields); + console.timeEnd('label 3.3'); + console.time('label 3.4'); + const queryField = async (fieldId: string) => { + if (availableFieldIds.includes(fieldId) || availableFieldIds.some(el => new RegExp(el).test(fieldId))) { + const propertyFilter: Record = {}; + if (!availableFields[fieldId]) { + return []; + } + if (availableFields[fieldId].properties) { + for (const property of availableFields[fieldId].properties) { + propertyFilter[`${property.name}`] = `$properties.${property.name}`; + } + } + const data = await dbCollection.aggregate([{ + $match: { + ...matchFilter, + fieldId: fieldId, + $or: [{ $or: roleArr }, { 'life.createdUser': requester.id }] + } + }, { + $sort: { + 'life.createdTime': -1 + } + }, { + $group: { + _id: { + ...propertyFilter + }, + latestDocument: { $first: '$$ROOT' } + } + }, { + $replaceRoot: { newRoot: '$latestDocument' } + }, { + $match: { + 'life.deletedTime': null + } + }, { + $project: { + _id: 0, // Exclude the _id field + id: 0, // Exclude the id field + life: 0, // Exclude the life field + metadata: 0 // Exclude the metadata field + } + }], { allowDiskUse: true }).toArray(); + return data; + } + + return []; + }; + + const timedQueryField = async (fieldId: string) => { + const result = await queryField(fieldId); + return result; // Return the original result without modification + }; + + let res: IData[] = []; + const promises = refactoredFieldIds.map(async (fieldId) => timedQueryField(fieldId)); + const results = await Promise.all(promises); + + // Optionally flatten the results if necessary + res = results.flat(); + return res; + } + + public genVersioningAggregation(keys: string[], hasVersioning: boolean) { + const aggregation: Array<{ operationName: enumDataTransformationOperation, params: unknown }> = []; + if (!hasVersioning) { + aggregation.push({ + operationName: enumDataTransformationOperation.FILTER, params: { + filters: { + deleted: [{ + formula: { + type: enumASTNodeTypes.VARIABLE, + operation: null, + value: 'dataVersion', + parameter: {}, + children: null + }, + condition: enumConditionOps.GENERALISNOTNULL, + value: '', + parameters: {} + }] + } + } + }); + } + if (keys.length > 0) { + aggregation.push({ operationName: enumDataTransformationOperation.GROUP, params: { keys: keys, skipUnmatch: false } }); + aggregation.push({ operationName: enumDataTransformationOperation.LEAVEONE, params: { scoreFormula: { type: enumASTNodeTypes.VARIABLE, operator: null, value: 'life.createdTime', parameters: {}, children: null }, isDescend: true } }); + aggregation.push({ + operationName: enumDataTransformationOperation.FILTER, params: { + filters: { + deleted: [{ + formula: { + type: enumASTNodeTypes.VARIABLE, + operation: null, + value: 'life.deletedTime', + parameter: {}, + children: null + }, + condition: enumConditionOps.GENERALISNULL, + value: '', + parameters: {} + }] + } + } + }); + } + return aggregation; + } + + /** + * Delete data of a study. We add a deleted document in the database. + * + * @param requester - The requester. + * @param studyId - The id of the study. + * @param fieldId - The id of the field. + * @param properties - The properties. + * + * @return IGenreicResponse - The object of IGenericResponse. + */ + public async deleteData(requester: IUserWithoutToken | undefined, studyId: string, fieldId: string, properties: Record): Promise { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + const roles = await this.permissionCore.getRolesOfUser(requester, requester.id, studyId); + if (roles.length === 0 || roles.every(el => el.studyRole !== enumStudyRoles.STUDY_MANAGER)) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + const study = await this.db.collections.studies_collection.findOne({ 'id': studyId, 'life.deletedTime': null }); + if (!study) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Study does not exist.' + ); + } + + await this.permissionCore.checkFieldOrDataPermission(requester, studyId, { fieldId: fieldId, properties: properties }, enumDataAtomicPermissions.DELETE); + await this.db.collections.data_collection.insertOne({ + id: uuid(), + studyId: studyId, + fieldId: fieldId, + dataVersion: null, + value: null, + properties: properties, + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: Date.now(), + deletedUser: requester.id + }, + metadata: {} + }); + return makeGenericResponse(undefined, undefined, undefined, 'Data deleted.'); + } + + /** + * Upload a file data. + * + * @param requester - The requester. + * @param studyId - The id of the study. + * @param file - The file to upload. + * @param fieldId - The id of the field. + * @param properties - The properties of the file. Need to match field properties if defined. + * + * @return IData + */ + public async uploadFileData(requester: IUserWithoutToken | undefined, studyId: string, file: FileUpload, fieldId: string, properties?: string) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + const roles = await this.permissionCore.getRolesOfUser(requester, requester.id, studyId); + if (roles.length === 0) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + const study = await this.db.collections.studies_collection.findOne({ 'id': studyId, 'life.deletedTime': null }); + if (!study) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Study does not exist.' + ); + } + const fileType = (file.filename.split('.').pop() as string).toUpperCase(); + if (!Object.keys(enumFileTypes).includes(fileType)) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + `File type ${fileType} not supported.` + ); + } + try { + const fileEntry = await this.fileCore.uploadFile( + requester, studyId, null, file, enumFileTypes[fileType], enumFileCategories.STUDY_DATA_FILE, undefined, properties ? JSON.parse(properties) : undefined); + const dataInput: IDataInput[] = [{ + fieldId: fieldId, + value: fileEntry.id, + properties: properties ? { + ...JSON.parse(properties), + FileName: fileEntry.fileName + } : undefined + }]; + const res = await this.uploadData(requester, studyId, dataInput); + if (!res[0].successful) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + res[0].description ?? 'Failed to upload file.' + ); + } + // invalidate the cache + await this.db.collections.cache_collection.updateMany({ 'keys.studyId': studyId, 'keys.query': 'getStudyFiles' }, { $set: { status: enumCacheStatus.OUTDATED } }); + return fileEntry; + } catch (error) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + `${(error as Error).message}` + ); + } + } + + public async uploadFileDataWithFileEntry(requester: IUserWithoutToken | undefined, studyId: string, fieldId: string, fileEntry: IFile, properties?: string) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + const roles = await this.permissionCore.getRolesOfUser(requester, requester.id, studyId); + if (roles.length === 0) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + const study = await this.db.collections.studies_collection.findOne({ 'id': studyId, 'life.deletedTime': null }); + if (!study) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Study does not exist.' + ); + } + try { + const parsedProperties = properties ? JSON.parse(properties) : {}; + const dataInput: IDataInput[] = [{ + fieldId: fieldId, + value: fileEntry.id, + properties: parsedProperties + }]; + const res = await this.uploadData(requester, studyId, dataInput); + if (!res[0].successful) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + res[0].description ?? 'Failed to upload file.' + ); + } + // invalidate the cache + await this.db.collections.cache_collection.updateMany({ 'keys.studyId': studyId, 'keys.query': 'getStudyFiles' }, { $set: { status: enumCacheStatus.OUTDATED } }); + return fileEntry; + } catch (error) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + `${(error as Error).message}` + ); + } + } + + /** + * Get the summary of a study. + * Admins can study managers can access this function. + * + * @param studyId - The id of the study. + * @param useCache - Whether to use the cached data. + * @param forceUpdate - Whether to force update the cache. + * + * @return Record - The object of Record + */ + public async getStudySummary(requester: IUserWithoutToken | undefined, studyId: string, useCache?: boolean, forceUpdate?: boolean) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + const roles = await this.permissionCore.getRolesOfUser(requester, requester.id, studyId); + if (requester.type !== enumUserTypes.ADMIN && roles.every(el => el.studyRole !== enumStudyRoles.STUDY_MANAGER)) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + const study = await this.db.collections.studies_collection.findOne({ 'id': studyId, 'life.deletedTime': null }); + if (!study) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Study does not exist.' + ); + } + + const generatedSummary = async () => { + const [ + numberOfDataRecords, + numberOfDataDeletes, + numberOfVersionedDeletes, + numberOfUnversionedRecords, + numberOfUnversionedDeletes, + numberOfFields, + numberOfUnversionedFields, + dataByUploaders, + dataByUsers + ] = await Promise.all([ + (async () => { + const result = await this.db.collections.data_collection.countDocuments({ studyId }, { allowDiskUse: true }); + return result; + })(), + + (async () => { + const result = await this.db.collections.data_collection.countDocuments({ studyId, value: null }, { allowDiskUse: true }); + return result; + })(), + (async () => { + const result = await this.db.collections.data_collection.countDocuments({ studyId, dataVersion: { $ne: null }, value: null }, { allowDiskUse: true }); + return result; + })(), + (async () => { + const result = await this.db.collections.data_collection.countDocuments({ studyId, dataVersion: null }, { allowDiskUse: true }); + return result; + })(), + (async () => { + const result = await this.db.collections.data_collection.countDocuments({ studyId, dataVersion: null, value: null }, { allowDiskUse: true }); + return result; + })(), + (async () => { + const result = (await this.db.collections.field_dictionary_collection.distinct('fieldId', { studyId })).length; + return result; + })(), + (async () => { + const result = (await this.db.collections.field_dictionary_collection.distinct('fieldId', { studyId, dataVersion: null })).length; + return result; + })(), + (async () => { + const result = await this.db.collections.data_collection.aggregate<{ userId: string, count: number }>([ + { $match: { studyId } }, + { + $group: { + _id: '$life.createdUser', + count: { $sum: 1 } + } + }, + { + $project: { + _id: 0, + userId: '$_id', + count: 1 + } + } + ], { allowDiskUse: true }).toArray(); + return result; + })(), + (async () => { + const result = await this.db.collections.log_collection.aggregate<{ userId: string, count: number }>([ + { + $match: { + 'parameters.studyId': studyId, + 'event': { $in: ['GET_DATA_RECORDS', 'GET_STUDY_FIELDS', 'GET_STUDY', 'data.getStudyFields', 'data.getStudyData', 'data.getStudyDataLatest', 'data.getStudyFiles'] } + } + }, + { + $group: { + _id: '$requester', + count: { $sum: 1 } + } + }, + { + $project: { + _id: 0, + userId: '$_id', + count: 1 + } + } + ], { allowDiskUse: true }).toArray(); + return result; + })() + ]); + + const numberOfVersionedRecords = numberOfDataRecords - numberOfUnversionedRecords; + const numberOfDataAdds = numberOfDataRecords - numberOfDataDeletes; + const numberOfVersionedAdds = numberOfVersionedRecords - numberOfVersionedDeletes; + const numberOfUnversionedAdds = numberOfUnversionedRecords - numberOfUnversionedDeletes; + const numberOfVersionedFields = numberOfFields - numberOfUnversionedFields; + // Return the final result object + return { + numberOfDataRecords, + numberOfDataAdds, + numberOfDataDeletes, + numberOfVersionedRecords, + numberOfVersionedAdds, + numberOfVersionedDeletes, + numberOfUnversionedRecords, + numberOfUnversionedAdds, + numberOfUnversionedDeletes, + numberOfFields, + numberOfVersionedFields, + numberOfUnversionedFields, + dataByUploaders, + dataByUsers + }; + }; + + if (useCache) { + const hash = this.utilsCore.computeHash({ + query: 'getStudySummary', + dataVersion: study.currentDataVersion, + studyId: studyId + }); + const hashedInfo = await this.db.collections.cache_collection.find({ 'keyHash': hash, 'life.deletedTime': null, 'status': enumCacheStatus.INUSE }).sort({ 'life.createdTime': -1 }).limit(1).toArray(); + // if hash is not found, generate the new summary and cache it + if (forceUpdate || !hashedInfo || hashedInfo.length === 0) { + const newStudySummary = await generatedSummary(); + const info = await convertToBufferAndUpload(this.fileCore, requester, newStudySummary); + await this.db.collections.cache_collection.insertOne({ + id: uuid(), + keyHash: hash, + uri: info.uri, + status: enumCacheStatus.INUSE, + keys: { + query: 'getStudySummary', + studyId: studyId + }, + type: enumCacheType.API, + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }); + return newStudySummary; + } else { + return (await getJsonFileContents(this.objStore, 'cache', hashedInfo[0].uri)) as unknown as IDataSetSummary; + } + } else { + return await generatedSummary() as IDataSetSummary; + } + } + + /* TODO: Data Transformation */ + /* TODO: This is a placeholder in case required. */ + // public async dataTransform(fields: IField[], data: IData[], rules: any) { + // } + + /** + * Upload a json object as a file to minio. + * + * @param jsonObject - The json object. + * @param fileName - The name of the file. + * @param requester - The requester. + * @returns + */ + public async convertToBufferAndUpload( + jsonObject: Record, + fileName: string, + requester: IUserWithoutToken + ): Promise { + // Convert the JSON object to a buffer + const buffer = Buffer.from(JSON.stringify(jsonObject)); + + // Create a readable stream from the buffer + const createReadStream = (): Readable => { + const stream = new Readable(); + stream.push(buffer); + stream.push(null); // No more data + return stream; + }; + + // Prepare the file data for upload + const fileUpload: FileUpload = { + createReadStream: createReadStream, + filename: fileName, + mimetype: 'application/json', + encoding: 'utf-8' + }; + + // Upload the file using the provided upload function + return this.fileCore.uploadFile( + requester, + null, // studyId + null, // userId + fileUpload, // fileUpload + enumFileTypes.JSON, + enumFileCategories.CACHE, + undefined // description + ); + } + + /** + * Delete a file from Id. + * + * @param requester - The requester. + * @param fileId - The id of the file. + * + * @returns - The object of IGenericResponse. + */ + public async deleteFile(requester: IUserWithoutToken | undefined, fileId: string) { + const data = await this.db.collections.data_collection.findOne({ value: fileId }); + if (!data) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Data entry not found.' + ); + } + return await this.deleteData(requester, data.studyId, data.fieldId, data.properties); + } +} diff --git a/packages/itmat-cores/src/coreFunc/domainCore.ts b/packages/itmat-cores/src/coreFunc/domainCore.ts new file mode 100644 index 000000000..bd38e82d3 --- /dev/null +++ b/packages/itmat-cores/src/coreFunc/domainCore.ts @@ -0,0 +1,256 @@ +import { CoreError, FileUpload, IDomain, IUserWithoutToken, enumCoreErrors, enumFileCategories, enumFileTypes, enumUserTypes } from '@itmat-broker/itmat-types'; +import { v4 as uuid } from 'uuid'; +import { DBType } from '../database/database'; +import { FileCore } from './fileCore'; +import { Filter, UpdateFilter } from 'mongodb'; +import { makeGenericResponse } from '../utils'; + + +export class DomainCore { + db: DBType; + fileCore: FileCore; + constructor(db: DBType, fileCore: FileCore) { + this.db = db; + this.fileCore = fileCore; + } + /** + * Get domain by domainId, domainName or domainPath. + * + * @param requester - The requester. + * @param domainId - The id of the domain. + * @param domainName - The name of the domain. + * @param domainPath - The path of the domain. + * + * @returns The domain objects. + */ + public async getDomains(requester: IUserWithoutToken | undefined, domainId?: string, domainName?: string, domainPath?: string) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + if (requester.type !== enumUserTypes.ADMIN) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + const obj: Filter = { + 'life.deletedTime': null + }; + if (domainId) { + obj.id = domainId; + } + if (domainName) { + obj.name = domainName; + } + if (domainPath) { + obj.domainPath = domainPath; + } + + return (await this.db.collections.domains_collection.find(obj).toArray()) as IDomain[]; + } + + /** + * Get the domain of an endpoint. Note this is a helper function for not logged in users. + * + * @param endpoint - The endpoint. + * + * @returns The domain object. + */ + public async getCurrentDomain(opts) { + const endpoint = await this.getCurrentSubPath(opts); + if (!endpoint) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Domain does not exist.' + ); + } + return await this.db.collections.domains_collection.findOne({ + domainPath: { $regex: new RegExp(`^${endpoint}$`, 'i') } + }); + } + + /** + * Create a domain. + * + * @param requester - The requester. + * @param domainName - The name of the domain. + * @param domainPath - The path of the domain. + * @param profile - The profile of the domain. + * @param color - The color of the domain. + * + * @returns The domain object. + */ + public async createDomain(requester: IUserWithoutToken | undefined, domainName: string, domainPath: string, logo?: FileUpload, color?: string) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + if (requester.type !== enumUserTypes.ADMIN) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + const domain = await this.db.collections.domains_collection.findOne({ 'domainPath': domainPath, 'life.deletedTime': null }); + if (domain) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Domain already exists.' + ); + } + let fileEntry; + + if (logo) { + if (!Object.keys(enumFileTypes).includes((logo?.filename?.split('.').pop() || '').toUpperCase())) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'File type not supported.' + ); + } + fileEntry = await this.fileCore.uploadFile(requester, null, null, logo, enumFileTypes[(logo.filename.split('.').pop() || '').toUpperCase() as keyof typeof enumFileTypes], enumFileCategories.DOMAIN_FILE); + } + + const entry: IDomain = { + id: uuid(), + name: domainName, + domainPath: domainPath, + logo: (logo && fileEntry) ? fileEntry.id : undefined, + color: color, + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + await this.db.collections.domains_collection.insertOne(entry); + return entry; + } + + public async editDomain(requester: IUserWithoutToken | undefined, domainId: string, domainName?: string, domainPath?: string, logo?: FileUpload, color?: string) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + if (requester.type !== enumUserTypes.ADMIN) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + const domain = await this.db.collections.domains_collection.findOne({ 'id': domainId, 'life.deletedTime': null }); + if (!domain) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Domain does not exist.' + ); + } + + if (domainPath) { + const domainWithSamePath = await this.db.collections.domains_collection.findOne({ 'domainPath': domainPath, 'life.deletedTime': null }); + if (domainWithSamePath && domainWithSamePath.id !== domainId) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Domain path already exists.' + ); + } + } + + let fileEntry; + if (logo) { + if (!Object.keys(enumFileTypes).includes((logo?.filename?.split('.').pop() || '').toUpperCase())) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'File type not supported.' + ); + } + fileEntry = await this.fileCore.uploadFile(requester, null, null, logo, enumFileTypes[(logo.filename.split('.').pop() || '').toUpperCase() as keyof typeof enumFileTypes], enumFileCategories.DOMAIN_FILE); + } + + const updateObj: UpdateFilter = {}; + if (domainName) { + updateObj['name'] = domainName; + } + if (domainPath) { + updateObj['domainPath'] = domainPath; + } + if (color) { + updateObj['color'] = color; + } + if (fileEntry) { + updateObj['logo'] = fileEntry.id; + } + + await this.db.collections.domains_collection.findOneAndUpdate({ id: domain.id }, { + $set: updateObj + }); + return makeGenericResponse(domainId, true, undefined, `Domain ${domain.name} has been updated`); + } + + /** + * Delete a domain. + * + * @param requester - The requester. + * @param domainId - The id of the domain. + * + * @returns - The response object. + */ + public async deleteDomain(requester: IUserWithoutToken | undefined, domainId: string) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + if (requester.type !== enumUserTypes.ADMIN) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + const domain = await this.db.collections.domains_collection.findOne({ 'id': domainId, 'life.deletedTime': null }); + if (!domain) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Domain does not exist.' + ); + } + + await this.db.collections.domains_collection.findOneAndUpdate({ id: domain.id }, { + $set: { + 'life.deletedUser': requester.id, + 'life.deletedTime': Date.now() + } + }); + if (domain.logo) { + await this.db.collections.files_collection.findOneAndUpdate({ id: domain.logo }, { + $set: { + 'life.deletedUser': requester.id, + 'life.deletedTime': Date.now() + } + }); + } + return makeGenericResponse(domainId, true, undefined, `Domain ${domain.name} has been deleted`); + } + + public async getCurrentSubPath(opts) { + const req = opts.ctx.req; + return req.hostname ?? null; + } +} diff --git a/packages/itmat-cores/src/coreFunc/driveCore.ts b/packages/itmat-cores/src/coreFunc/driveCore.ts new file mode 100644 index 000000000..373c3b138 --- /dev/null +++ b/packages/itmat-cores/src/coreFunc/driveCore.ts @@ -0,0 +1,840 @@ +import { DBType } from '../database/database'; +import { enumUserTypes, enumFileTypes, enumFileCategories, IDrive, enumDriveNodeTypes, IFile, IDrivePermission, FileUpload, CoreError, enumCoreErrors, IUserWithoutToken, enumConfigType, defaultSettings, IUserConfig } from '@itmat-broker/itmat-types'; +import { v4 as uuid } from 'uuid'; +import { FileCore } from './fileCore'; +import { UpdateFilter } from 'mongodb'; +import { ObjectStore } from '@itmat-broker/itmat-commons'; +import { makeGenericResponse } from '../utils'; + +export class DriveCore { + db: DBType; + fileCore: FileCore; + objectStore: ObjectStore; + constructor(db: DBType, fileCore: FileCore, ObjectStore: ObjectStore) { + this.db = db; + this.fileCore = fileCore; + this.objectStore = ObjectStore; + } + /** + * Create a drive folder. + * + * @param requester - The requester. + * @param folderName - The name of the folder. + * @param parentId - The id of the parent. Null for default root node. + * @param protected - Whether this folder is protected. Protected folder could not be deleted. + * @param description - The description of the folder. + * + * @return IDrive + */ + public async createDriveFolder(requester: IUserWithoutToken | undefined, folderName: string, parentId: string | null, restricted: boolean, description?: string) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + // check parent existence + let parent; + if (parentId) { + parent = await this.db.collections.drives_collection.findOne({ 'id': parentId, 'life.deletedTime': null }); + } else { + // if parent is not specified, use default root node + parent = await this.db.collections.drives_collection.findOne({ managerId: requester.id, parent: null }); + } + + if (!parent || (parent.sharedUsers.filter(el => el.iid === requester.id && el.write === true).length === 0 + && parent.managerId !== requester.id)) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'No permission to create folder.' + ); + } + + if (parent.type !== enumDriveNodeTypes.FOLDER) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Parent is not a folder.' + ); + } + + // check duplicates, there should not be a same folder in this path + const children = await this.db.collections.drives_collection.find({ 'parent': parent.id, 'life.deletedTime': null }).toArray(); + if (children.filter(el => el.type === enumDriveNodeTypes.FOLDER).map(el => el.name).includes(folderName)) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Folder already exists.' + ); + } + + const folderDriveId = uuid(); + const driveEntry: IDrive = { + id: folderDriveId, + path: parent?.path.concat(folderDriveId) ?? [], + // we allow users to create drive under another user's drive; but the manager of the new drive is the managerId of the parent drive + managerId: parent.managerId == requester.id ? requester.id : parent.managerId, + restricted: restricted, + name: folderName, + description: description, + fileId: null, + type: enumDriveNodeTypes.FOLDER, + parent: parent.id, + children: [], + sharedUsers: parent ? parent.sharedUsers : [], + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + + await this.db.collections.drives_collection.insertOne(driveEntry); + + // update parent + await this.db.collections.drives_collection.findOneAndUpdate({ id: parent.id }, { + $push: { children: driveEntry.id } + }); + + return driveEntry; + } + + /** + * Add/Upload a file to the user file repo. + * + * @param requester - The requester. + * @param parentId - The id of the file Node. Null for default root node. + * @param description - The description of the file. + * @param fileType - The type of the file. + * @param file - The file to upload. + * + * @return IDrive + */ + public async createDriveFile(requester: IUserWithoutToken | undefined, parentId: string | null, description: string | undefined, fileType: enumFileTypes, file: FileUpload) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + // check parent existence + let parent; + if (parentId && parentId !== '') { + parent = await this.db.collections.drives_collection.findOne({ 'id': parentId, 'life.deletedTime': null }); + if (!parent) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + } else { + parent = await this.db.collections.drives_collection.findOne({ managerId: requester.id, parent: null }); + if (!parent) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + } + + if (parent.sharedUsers.filter(el => el.iid === requester && el.write === true).length === 0 + && parent.managerId !== requester.id) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + if (parent.type !== enumDriveNodeTypes.FOLDER) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Parent is not a folder.' + ); + } + + let fileEntry: IFile | null = null; + if (file && fileType) { + // check duplicates, there should not be a same folder in this path + const children = await this.db.collections.drives_collection.find({ 'parent': parentId, 'life.deletedTime': null }).toArray(); + if (children.filter(el => el.type === enumDriveNodeTypes.FILE).map(el => el.name).includes(file?.filename)) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'File already exists.' + ); + } + fileEntry = await this.fileCore.uploadFile(requester, null, requester.id, file, fileType, enumFileCategories.USER_DRIVE_FILE, description); + } else { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'File or filetype not valid.' + ); + } + + const fileDriveId = uuid(); + const driveEntry: IDrive = { + id: fileDriveId, + path: parent.path.concat(fileDriveId) ?? [], + managerId: requester.id, + restricted: false, + name: file?.filename, + description: description, + fileId: fileEntry.id, + type: enumDriveNodeTypes.FILE, + parent: parent.id, + children: [], + sharedUsers: parent.sharedUsers, + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + }, + metadata: { + fileSize: fileEntry.fileSize + } + }; + await this.db.collections.drives_collection.insertOne(driveEntry); + + // update parent + await this.db.collections.drives_collection.findOneAndUpdate({ id: parent.id }, { + $push: { children: driveEntry.id } + }); + + return driveEntry; + } + + /** + * Create a drive recursively. This is used for creating a folder with subfolders and files. + * + * @param requester - The requester. + * @param parentId - The id of the parent node. + * @param files - The files to upload. + * @param paths - The paths of the files. + * @returns + */ + public async createRecursiveDrives(requester: IUserWithoutToken | undefined, parentId: string, files: FileUpload[], paths: string[][]) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + // check parent existence + let parent; + if (parentId && parentId !== '') { + parent = await this.db.collections.drives_collection.findOne({ 'id': parentId, 'life.deletedTime': null }); + if (!parent) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Parent node does not exist.' + ); + } + } else { + parent = await this.db.collections.drives_collection.findOne({ managerId: requester.id, parent: null }); + if (!parent) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Default root node not found.' + ); + } + } + if (parent.type !== enumDriveNodeTypes.FOLDER) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Parent is not a folder.' + ); + } + + const createdDrives: IDrive[] = []; + for (let i = 0; i < paths.length; i++) { + if (i < files.length) { + // upload files + let currentParent = parentId; + for (let j = 0; j < paths[i].length - 1; j++) { + const existing = await this.db.collections.drives_collection.findOne({ 'parent': currentParent, 'name': paths[i][j], 'life.deletedTime': null }); + if (!existing) { + const res = await this.createDriveFolder(requester, paths[i][j], currentParent, false, undefined); + currentParent = res.id; + createdDrives.push(res); + } else { + currentParent = existing.id; + createdDrives.push(existing); + } + } + const existing = await this.db.collections.drives_collection.findOne({ 'parent': currentParent, 'name': files[i].filename, 'life.deletedTime': null }); + if (!existing) { + let fileType = enumFileTypes.UNKNOWN; + if (Object.keys(enumFileTypes).includes((files[i]?.filename?.split('.').pop() || '').toUpperCase())) { + fileType = enumFileTypes[(files[i].filename.split('.').pop() || '').toUpperCase() as keyof typeof enumFileTypes]; + } + const res = await this.createDriveFile(requester, currentParent, undefined, fileType, files[i]); + createdDrives.push(res); + } + } else { + // upload empty folders + let currentParent = parentId; + for (let j = 0; j < paths[i].length; j++) { + const existing = await this.db.collections.drives_collection.findOne({ 'parent': currentParent, 'name': paths[i][j], 'life.deletedTime': null }); + if (!existing) { + const res = await this.createDriveFolder(requester, paths[i][j], currentParent, false, undefined); + currentParent = res.id; + createdDrives.push(res); + } else { + currentParent = existing.id; + createdDrives.push(existing); + } + } + } + } + return createdDrives; + } + + /** + * Get the drive of a user, including own drives and shared drives. + * + * @param requester - The requester. + * @param rootId - The id of the root drive if specified. + * + * @return Record([ + { + $match: { + 'life.deletedTime': null, + 'id': rootId ? new RegExp(`^${rootId}$`) : new RegExp('^.*$'), + '$or': [ + { managerId: requester.id }, + { 'sharedUsers.iid': requester.id } + ] + } + }, + { + $group: { + _id: '$managerId', + drives: { $push: '$$ROOT' } + } + }, + { + $project: { + managerId: '$_id', + drives: 1, + _id: 0 + } + } + ]).toArray(); + // we generate the results as a dict, the key of which is the managerId + // each value is a list of drives of the associated manager + const resultObject: Record = {}; + for (const drives of availableDrives) { + resultObject[drives['managerId']] = []; + for (const drive of drives['drives']) { + // filtered the permission objects + if (requester.type !== enumUserTypes.ADMIN && drive.managerId !== requester.id) { + drive.sharedUsers = drive.sharedUsers.filter((el: { iid: string; }) => el.iid === requester.id); + } + resultObject[drives.managerId].push(drive); + } + if (drives.drives.filter(el => el.parent === null).length === 0) { + const rootDrive: IDrive = { + id: `shared_by_${drives.managerId}`, + managerId: drives.managerId, + restricted: false, + path: [`shared_by_${drives.managerId}`], + name: 'Shared', + fileId: null, + type: enumDriveNodeTypes.FOLDER, + parent: null, + children: [], + sharedUsers: [{ + iid: requester.id, + read: true, + write: false, + delete: false + }], + life: { + createdTime: Date.now(), + createdUser: drives.managerId, + deletedTime: null, + deletedUser: null + }, + metadata: { + } + }; + const existingIds = drives.drives.map(el => el.id); + for (const drive of resultObject[drives.managerId]) { + if (!drive.parent || !existingIds.includes(drive.parent)) { + drive.parent = rootDrive.id; + rootDrive.children.push(drive.id); + } + } + resultObject[drives.managerId].push(rootDrive); + } + } + return resultObject; + } + + /** + * Edit a drive node. Note this could be used for moving a drive node to another parent. + * + * @param requester - The requester. + * @param driveId - The id of the driver. + * @param managerId - The id of the manager. + * @param name - The name of the drive. + * @param description - The description of the drive. + * @param parentId - The id of the parent node. + * @param children - The ids of the childeren. + * @param sharedUsers - Shared users. + * @param sharedGroups - Shared user groups. + * + * @return driveIds - The list of drive ids influenced. + */ + public async editDrive(requester: IUserWithoutToken | undefined, driveId: string, managerId?: string, name?: string, description?: string, parentId?: string, children?: string[], sharedUsers?: IDrivePermission[]) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + const drive = await this.db.collections.drives_collection.findOne({ 'id': driveId, 'life.deletedTime': null }); + if (!drive) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + if (requester.type !== enumUserTypes.ADMIN && drive.managerId !== requester.id) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + const setObj: UpdateFilter = {}; + + if (managerId) { + const manager = await this.db.collections.users_collection.findOne({ 'id': managerId, 'life.deletedTime': null }); + if (!manager) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Manager does not exist.' + ); + } + setObj['managerId'] = manager; + } + + if (name) { + const siblingNames = (await this.db.collections.drives_collection.find({ 'parent': drive.parent, 'life.deletedTime': null }).toArray()).map(el => el.name); + if (siblingNames.includes(name)) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Name already exists.' + ); + } + setObj['name'] = name; + } + + if (description) { + setObj['description'] = description; + } + + if (parentId) { + const parentNode = await this.db.collections.drives_collection.findOne({ 'id': parentId, 'life.deletedTime': null }); + if (!parentNode) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Parent does not exist.' + ); + } + setObj['parent'] = parentId; + } + + if (children) { + const childrenDriveIds: string[] = (await this.db.collections.drives_collection.find({ 'life.deletedTime': null }).toArray()).map(el => el.id); + if (children.some(el => !childrenDriveIds.includes(el))) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Children do not exist.' + ); + } + setObj['childeren'] = children; + } + + if (sharedUsers) { + const userIds: string[] = (await this.db.collections.users_collection.find({ 'life.deletedTime': null }).toArray()).map(el => el.id); + if (sharedUsers.map(el => el.iid).some(el => !userIds.includes(el))) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Shared users do not exist.' + ); + } + setObj['sharedUsers'] = sharedUsers; + } + + if (Object.keys(setObj).length === 0) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'You have to edit at lease one property.' + ); + } + + // TODO:check sharedUsers and sharedGroups are legal + const updated = await this.db.collections.drives_collection.findOneAndUpdate({ id: driveId }, { + $set: setObj + }, { + returnDocument: 'after' + }); + + + // update children drive parameters + const driveIds: string[] = []; + if (sharedUsers || managerId) { + await this.recursiveFindDrives(drive, [], driveIds); + const setObjTmp: UpdateFilter = {}; + if (sharedUsers) { + setObjTmp['sharedUsers'] = sharedUsers; + } + if (managerId) { + setObjTmp['managerId'] = managerId; + } + await this.db.collections.drives_collection.updateMany({ + id: { $in: driveIds } + }, { + $set: setObjTmp + }); + } else { + driveIds.push(driveId); + } + + // for mv command, edit parent + if (parentId) { + const parentNode = await this.db.collections.drives_collection.findOne({ 'id': parentId, 'life.deletedTime': null }); + if (parentNode) { + await this.db.collections.drives_collection.findOneAndUpdate({ id: parentId }, { + $push: { + children: driveId + } + }); + // update children path + for (const childId of driveIds) { + const childNode = await this.db.collections.drives_collection.findOne({ id: childId }); + if (!childNode) { + continue; + } + // update path + const path = childNode.path; + while (path.length) { + if (path[0] !== driveId) { + path.shift(); + } + else { + break; + } + } + const newPath = parentNode.path.concat(path); + await this.db.collections.drives_collection.findOneAndUpdate({ id: childId }, { + $set: { + path: newPath + } + }); + } + } + } + + if (!updated) { + throw new CoreError( + enumCoreErrors.DATABASE_ERROR, + enumCoreErrors.DATABASE_ERROR + ); + } + return { + driveIds: driveIds, + drive: updated + }; + } + + /** + * Copy a drive to another parent. + * + * @param requester + * @param driveId + * @param targetParentId + * @returns + */ + public async copyDrive(requester: IUserWithoutToken | undefined, driveId: string, targetParentId: string) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + const driveToCopy = await this.db.collections.drives_collection.findOne({ 'id': driveId, 'life.deletedTime': null }); + if (!driveToCopy || driveToCopy.parent === null || (driveToCopy.managerId !== requester.id && driveToCopy.sharedUsers.filter(el => el.iid === requester.id && el.read === true).length === 0)) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + const targetParentDrive = await this.db.collections.drives_collection.findOne({ 'id': targetParentId, 'life.deletedTime': null }); + if (!targetParentDrive || (targetParentDrive.managerId !== requester.id && targetParentDrive.sharedUsers.filter(el => el.iid === requester.id && el.write === true).length === 0)) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + // get the drive config for the bucket name + const userConfig = (await this.db.collections.configs_collection.findOne({ type: enumConfigType.USERCONFIG, key: requester.id }))?.properties ?? defaultSettings.userConfig; + + // update children drive parameters + const driveIds: string[] = []; + await this.recursiveFindDrives(driveToCopy, [], driveIds); + const drives = await this.db.collections.drives_collection.find({ id: { $in: driveIds } }).toArray() ?? []; + + // check the size of the files does not exceed the limit + const aggregationResult = await this.db.collections.files_collection.aggregate([ + { + $match: { 'userId': requester.id, 'life.deletedTime': null } + }, + { + $group: { _id: '$userId', totalSize: { $sum: '$fileSize' } } + } + ]).toArray(); + const totalSize: number = (aggregationResult.length > 0) ? aggregationResult[0]['totalSize'] : 0; + const userRepoRemainingSpace = (userConfig as IUserConfig).defaultMaximumFileRepoSize - totalSize; + const defaultFileBucketId = (userConfig as IUserConfig).defaultFileBucketId; + + const fileIds = drives + .filter(el => (el.type === enumDriveNodeTypes.FILE && el.fileId !== null)) + .map(el => el.fileId) + .filter((id): id is string => id !== null); // Filter out null values + + const files = await this.db.collections.files_collection.find({ id: { $in: fileIds } }).toArray(); + + const totalFilesSize = files.reduce((acc, el) => acc + el.fileSize, 0); + if (totalFilesSize > userRepoRemainingSpace) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'User repo size exceeded.' + ); + } + + // List to hold the new drives for database insertion + const newDrivesForDatabase: IDrive[] = []; + + const idMap: { [oldId: string]: string } = {}; + for (const drive of drives) { + idMap[drive.id] = uuid(); + } + + for (const drive of drives) { + if (!drive.parent) { + continue; + } + const newId = idMap[drive.id]; + const newPath = targetParentDrive.path.concat(drive.path.slice(drive.path.indexOf(driveId)).map(el => idMap[el])); + const newChildren = drive.children.map(childId => idMap[childId]); + + let newFileUri: string | null = null; + if (drive.fileId !== null) { + newFileUri = uuid(); + const file = await this.db.collections.files_collection.findOne({ id: drive.fileId }); + if (!file) { + continue; + } + await this.objectStore.copyObject(defaultFileBucketId, file.uri, defaultFileBucketId, newFileUri); + } + + const newNode: IDrive = { + id: newId, + managerId: requester.id, + path: newPath, + restricted: false, + name: drive.name, + description: drive.description, + fileId: newFileUri, + type: drive.type, + parent: idMap[drive.parent] ?? targetParentDrive.id, + children: newChildren, + sharedUsers: [], + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + }, + metadata: drive.metadata + }; + + newDrivesForDatabase.push(newNode); + } + + await this.db.collections.drives_collection.insertMany(newDrivesForDatabase); + + return makeGenericResponse(driveId, true, undefined, 'Drive copied successfully.'); + } + /** + * Delete a file node. + * + * @param requester - The id of the requester. + * @param userId - The id of the user. + * @param driveNodeId - The id of the file node. + * + * @return IDrive - The deleted drive. + */ + public async deleteDrive(requester: IUserWithoutToken | undefined, driveId: string): Promise { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + const drive = await this.db.collections.drives_collection.findOne({ 'id': driveId, 'life.deletedTime': null }); + if (!drive) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Drive does not exist.' + ); + } + if (drive.restricted || drive.parent === null) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'You can not delete this drive.' + ); + } + + const driveIds: string[] = []; + const driveFileIds: string[] = []; + await this.recursiveFindDrives(drive, driveFileIds, driveIds); + // delete metadata in drive collection + await this.db.collections.drives_collection.updateMany({ id: { $in: driveIds } }, { + $set: { + 'life.deletedTime': Date.now(), + 'life.deletedUser': requester.id + } + }); + // delete metadata in file collection + await this.db.collections.files_collection.updateMany({ id: { $in: driveFileIds } }, { + $set: { + 'life.deletedTime': Date.now(), + 'life.deletedUser': requester.id + } + }); + await this.db.collections?.files_collection.updateMany({ id: drive.parent ?? '' }, { + $pull: { + children: drive.id + } + }); + return drive; + } + + public async recursiveFindDrives(root: IDrive, driveFileIds: string[], driveIds: string[]) { + /** + * Recursive find the files and file nodes that are in the same tree. + * + * @param root - The root from which to start. + * @param filesList - The ids of the files that belongs to this root. + * @param nodesList - The ids of the file nodes that belongs to this root. + * + * @return null + */ + if (!root) { + return; + } + driveIds.push(root.id); + if (root.type === enumDriveNodeTypes.FILE && root.fileId) { + driveFileIds.push(root.fileId); + return; + } + if (root.type === enumDriveNodeTypes.FOLDER) { + for (const child of root.children) { + const thisDrive = await this.db.collections.drives_collection.findOne({ 'id': child, 'life.deletedTime': null }); + if (!thisDrive) { + continue; + } else { + await this.recursiveFindDrives(thisDrive, driveFileIds, driveIds); + } + } + } + return; + } + + /** + * Share a drive to a user via email. Only the manager of the drive can share the drive. + * + * @param requester - The requester. + * @param userEmails - The emails of the users. + * @param driveId - The id of the drive. + * @param permissions - The permission object. + */ + public async shareDriveToUserViaEmail(requester: IUserWithoutToken | undefined, userEmails: string[], driveId: string, permissions: { read: boolean, write: boolean, delete: boolean }) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + const drive = await this.db.collections.drives_collection.findOne({ 'id': driveId, 'life.deletedTime': null, 'managerId': requester.id }); + if (!drive) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'You do not have permission to share this drive.' + ); + } + + const userIds: IDrivePermission[] = []; + for (const email of userEmails) { + const user = await this.db.collections.users_collection.findOne({ 'email': email, 'life.deletedTime': null }); + if (!user) { + continue; + } + userIds.push({ + iid: user.id, + read: permissions.read, + write: permissions.write, + delete: permissions.delete + }); + } + + // find and update all recursive drives + const driveIds: string[] = []; + await this.recursiveFindDrives(drive, [], driveIds); + for (const driveId of driveIds) { + const drive = await this.db.collections.drives_collection.findOne({ 'id': driveId, 'life.deletedTime': null }); + if (!drive) { + continue; + } + await this.editDrive(requester, driveId, undefined, undefined, undefined, undefined, undefined, userIds); + } + return driveIds; + } +} + diff --git a/packages/itmat-cores/src/coreFunc/fileCore.ts b/packages/itmat-cores/src/coreFunc/fileCore.ts new file mode 100644 index 000000000..ecfc4a576 --- /dev/null +++ b/packages/itmat-cores/src/coreFunc/fileCore.ts @@ -0,0 +1,277 @@ +import { enumConfigType, IFile, defaultSettings, enumFileTypes, enumFileCategories, IUserWithoutToken, IStudy, FileUpload, CoreError, enumCoreErrors, ISystemConfig, IStudyConfig, IUserConfig, IDocConfig, ICacheConfig, IDomainConfig, IGenericResponse } from '@itmat-broker/itmat-types'; +import { v4 as uuid } from 'uuid'; +import crypto from 'crypto'; +import { DBType } from '../database/database'; +import { ObjectStore } from '@itmat-broker/itmat-commons'; +import { makeGenericResponse } from '../utils'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { PassThrough, Readable } from 'stream'; + +/** + * This class provides methods to interact with files. + * Note that all core functions are should be called by other core functions or resolvers, not by the client. + * Necessary permission check should be done in the caller functions. + */ +export class FileCore { + db: DBType; + objStore: ObjectStore; + constructor(db: DBType, objStore: ObjectStore) { + this.db = db; + this.objStore = objStore; + } + /** + * Upload a file to storage. + * Note this function will upload file based on the input parameters regardless of the requester's permission and file metadata. + * + * @param requester - The requester. + * @param studyId - The id of the study. Could be null for non-study files. + * @param userId - The id of the user. + * @param fileUpload - The file to upload. + * @param fileType - The type of the file. + * @param fileCategory - The category of the file. + * @param description - The description of the file. + * @param properties - The properties of the file. Note if the data is attached to a field, the fieldproperties will be used. + * + * @return IFile - The object of IFile. + */ + public async uploadFile( + requester: IUserWithoutToken, + studyId: string | null, + userId: string | null, + fileUpload: FileUpload, + fileType: enumFileTypes, + fileCategory: enumFileCategories, + description?: string, + properties?: Record + ): Promise { + let study: IStudy | null = null; + if (studyId) { + study = await this.db.collections.studies_collection.findOne({ 'id': studyId, 'life.deletedTime': null }); + if (!study) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Study does not exist.' + ); + } + } + let fileConfig: ISystemConfig | IStudyConfig | IUserConfig | IDocConfig | ICacheConfig | IDomainConfig | null = null; + let userRepoRemainingSpace = 0; + let fileSizeLimit: number; + let defaultFileBucketId: string; + + if (fileCategory === enumFileCategories.STUDY_DATA_FILE && studyId) { + const config = await this.db.collections.configs_collection.findOne({ type: enumConfigType.STUDYCONFIG, key: studyId }); + fileConfig = config ? config.properties : defaultSettings.studyConfig; + fileSizeLimit = (fileConfig as IStudyConfig).defaultMaximumFileSize; + defaultFileBucketId = studyId; + } else if (fileCategory === enumFileCategories.USER_DRIVE_FILE) { + const config = await this.db.collections.configs_collection.findOne({ type: enumConfigType.USERCONFIG, key: requester.id }); + fileConfig = config ? config.properties as IUserConfig : defaultSettings.userConfig; + const totalSize = (await this.db.collections.files_collection.aggregate([ + { $match: { 'userId': requester.id, 'life.deletedTime': null } }, + { $group: { _id: '$userId', totalSize: { $sum: '$fileSize' } } } + ]))[0]?.totalSize ?? 0; + userRepoRemainingSpace = (fileConfig as IUserConfig).defaultMaximumFileRepoSize - totalSize; + fileSizeLimit = Math.min((fileConfig as IUserConfig).defaultMaximumFileSize, userRepoRemainingSpace); + defaultFileBucketId = (fileConfig as IUserConfig).defaultFileBucketId; + } else if (fileCategory === enumFileCategories.DOC_FILE) { + const config = await this.db.collections.configs_collection.findOne({ type: enumConfigType.DOCCONFIG, key: null }); + fileConfig = config ? config.properties as IDocConfig : defaultSettings.docConfig; + fileSizeLimit = (fileConfig as IDocConfig).defaultMaximumFileSize; + defaultFileBucketId = (fileConfig as IDocConfig).defaultFileBucketId; + } else if (fileCategory === enumFileCategories.CACHE) { + const config = await this.db.collections.configs_collection.findOne({ type: enumConfigType.CACHECONFIG, key: null }); + fileConfig = config ? config.properties as ICacheConfig : defaultSettings.cacheConfig; + fileSizeLimit = (fileConfig as ICacheConfig).defaultMaximumFileSize; + defaultFileBucketId = (fileConfig as ICacheConfig).defaultFileBucketId; + } else if (fileCategory === enumFileCategories.PROFILE_FILE) { + const config = await this.db.collections.configs_collection.findOne({ type: enumConfigType.SYSTEMCONFIG, key: null }); + fileConfig = config ? config.properties as ISystemConfig : defaultSettings.systemConfig; + fileSizeLimit = (fileConfig as ISystemConfig).defaultMaximumFileSize; + defaultFileBucketId = (fileConfig as ISystemConfig).defaultProfileBucketId; + } else if (fileCategory === enumFileCategories.DOMAIN_FILE) { + const config = await this.db.collections.configs_collection.findOne({ type: enumConfigType.DOMAINCONFIG, key: null }); + fileConfig = config ? config.properties as IDomainConfig : defaultSettings.domainConfig; + fileSizeLimit = (fileConfig as IDomainConfig).defaultMaximumFileSize; + defaultFileBucketId = (fileConfig as IDomainConfig).defaultFileBucketId; + } else { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'File category does not exist.' + ); + } + + const fileUri = uuid(); + const hash = crypto.createHash('sha256'); + const stream = fileUpload.createReadStream(); + let fileSize = 0; + const MAX_CHUNK_SIZE = 64 * 1024; // 64KB, adjust this value if necessary + + // Array to store the chunks temporarily + const chunks: Buffer[] = []; + + return new Promise((resolve, reject) => { + stream.on('data', (chunk: Buffer) => { + try { + fileSize += chunk.length; + + if (fileSize > fileSizeLimit) { + stream.destroy(new CoreError( + enumCoreErrors.UNQUALIFIED_ERROR, + 'File size exceeds the limit.' + )); + } + + // Store the chunk + chunks.push(chunk); + + // Manually break down large chunks + for (let i = 0; i < chunk.length; i += MAX_CHUNK_SIZE) { + const subChunk = chunk.slice(i, i + MAX_CHUNK_SIZE); + hash.update(subChunk); + } + + } catch (err) { + reject(new CoreError( + enumCoreErrors.FILE_STREAM_ERROR, + 'Error updating hash: ' + (err as Error).message + )); + } + }); + + stream.on('end', () => { + const hashString = hash.digest('hex'); + + // Create a single stream from the stored chunks + const combinedStream = new Readable(); + // eslint-disable-next-line @typescript-eslint/no-empty-function + combinedStream._read = () => { }; // _read is required but you can noop it + combinedStream.push(Buffer.concat(chunks)); + combinedStream.push(null); // Indicate end of stream + + this.handleFileUpload( + fileSize, fileSizeLimit, combinedStream, hashString, fileUpload, defaultFileBucketId, + fileUri, requester, studyId, userId, fileType, fileCategory, description, properties + ).then(fileEntry => resolve(fileEntry)) + .catch(error => reject(error)); + }); + + stream.on('error', (err) => { + reject(new CoreError( + enumCoreErrors.FILE_STREAM_ERROR, + 'Error reading file stream: ' + err.message + )); + }); + }); + } + + private async handleFileUpload( + fileSize: number, + fileSizeLimit: number, + stream: Readable, + hashString: string, + fileUpload: FileUpload, + defaultFileBucketId: string, + fileUri: string, + requester: IUserWithoutToken, + studyId: string | null, + userId: string | null, + fileType: enumFileTypes, + fileCategory: enumFileCategories, + description?: string, + properties?: Record + ): Promise { + if (fileSize > fileSizeLimit) { + throw new CoreError( + enumCoreErrors.UNQUALIFIED_ERROR, + 'File size exceeds the limit.' + ); + } + + await this.objStore.uploadFile(stream, defaultFileBucketId, fileUri, fileSize); + + const fileEntry: IFile = { + id: uuid(), + studyId: studyId, + userId: userId, + fileName: fileUpload.filename, + fileSize: fileSize, + description: description, + uri: fileUri, + hash: hashString, + fileType: fileType, + fileCategory: fileCategory, + properties: properties ? properties : {}, + sharedUsers: [], + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + + const insertResult = await this.db.collections.files_collection.insertOne(fileEntry as IFile); + if (!insertResult.acknowledged) { + throw new CoreError( + enumCoreErrors.DATABASE_ERROR, + enumCoreErrors.DATABASE_ERROR + ); + } + + return fileEntry; + } + + /** + * Delete a file. + * + * @param requester - The requester. + * @param fileId - The id of the file. + * + * @return IGenericResponse + */ + public async deleteFile(requester: string, fileId: string): Promise { + const file = await this.db.collections.files_collection.findOne({ 'id': fileId, 'life.deletedTime': null }); + if (!file) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'File does not exist.' + ); + } + try { + await this.db.collections.files_collection.findOneAndUpdate({ id: fileId }, { $set: { 'life.deletedTime': Date.now().valueOf(), 'life.deletedUser': requester } }); + return makeGenericResponse(fileId, undefined, undefined, 'File has been deleted.'); + } catch { + throw new CoreError( + enumCoreErrors.DATABASE_ERROR, + enumCoreErrors.DATABASE_ERROR + ); + } + } + + /** + * Get the list of files by fileIds in a simplified format or detailed format with explicit username. + * The aim is to avoid leaking user information in the frontend. + * + * @param fileIds + * @param readable + * @returns + */ + public async findFiles(fileIds: string[], readable?: boolean): Promise { + const result = await this.db.collections.files_collection.find({ id: { $in: fileIds } }).toArray(); + if (readable) { + const users = await this.db.collections.users_collection.find({ 'life.deletedTime': null }).toArray(); + result.forEach(el => { + const user = users.filter(ek => ek.id === el.life.createdUser)[0]; + if (!user) { + el.life.createdUser = 'UNKNOWN'; + } else { + el.life.createdUser = `${user.firstname} ${user.lastname}`; + } + }); + } + return result; + } +} + diff --git a/packages/itmat-cores/src/coreFunc/instanceCore.ts b/packages/itmat-cores/src/coreFunc/instanceCore.ts new file mode 100644 index 000000000..991549375 --- /dev/null +++ b/packages/itmat-cores/src/coreFunc/instanceCore.ts @@ -0,0 +1,878 @@ +import { IUserConfig, enumConfigType, CoreError, enumCoreErrors, IInstance, LXDInstanceTypeEnum, enumInstanceStatus, enumAppType, IUser, enumUserTypes, enumJobType, enumOpeType, enumMonitorType, enumJobStatus, ISystemConfig} from '@itmat-broker/itmat-types'; +import { v4 as uuid } from 'uuid'; +import { DBType } from '../database/database'; +import { Logger, Mailer} from '@itmat-broker/itmat-commons'; +import { IConfiguration} from '../utils'; +import { ConfigCore } from './configCore'; +import { JobCore} from './jobCore'; // Ensure you have the correct import path +import { UserCore } from './userCore'; +import { cloudInitUserDataJupyterContainer, cloudInitUserDataMatlabContainer } from '../lxd/lxd.config'; + +export class InstanceCore { + db: DBType; + mailer: Mailer; + config: IConfiguration; + configCore: ConfigCore; + JobCore: JobCore; + UserCore: UserCore; + lxdProject: string; + private previousCpuUsage: Record = {}; + private previousCpuTimestamp: Record = {}; + + constructor(db: DBType, mailer: Mailer, config: IConfiguration, jobCore: JobCore, userCore: UserCore) { + this.db = db; + this.mailer = mailer; + this.config = config; + this.configCore = new ConfigCore(db); + this.JobCore = jobCore; + this.UserCore = userCore; + // set lxd project from config + this.lxdProject = this.config.lxdProject || 'default'; + } + /** + * Convert memory size in bytes to a formatted string like '4GB'. + * @param memory - The memory size in bytes. + * @returns Formatted memory size string. + */ + private formatMemory(memory: number): string { + const units = ['B', 'KB', 'MB', 'GB', 'TB']; + let index = 0; + let size = memory; + + while (size >= 1024 && index < units.length - 1) { + size /= 1024; + index++; + } + + return `${Math.round(size)}${units[index]}`; + } + + /** + * Convert memory string like '4GB' to bytes. + * @param memoryStr - The memory string to parse. + * @returns Memory size in bytes. + */ + private parseMemory(memoryStr: string): number { + const units: Record = { + B: 1, + KB: 1024, + MB: 1024 * 1024, + GB: 1024 * 1024 * 1024, + TB: 1024 * 1024 * 1024 * 1024 + }; + // if memory string is undefined, return 0 + if (!memoryStr) { + return 0; + } + const match = memoryStr.match(/^(\d+(?:\.\d+)?)([KMGT]?B)$/); + if (!match) { + throw new Error(`Invalid memory string: ${memoryStr}`); + } + + const value = parseFloat(match[1]); + const unit = match[2]; + + return value * (units[unit] || 1); + } + /** + * Get an instance by ID. + * + * @param instanceId - The ID of the instance to retrieve. + * @return IInstance - The instance object. + */ + public async getInstanceById(instanceId: string): Promise { + const instance = await this.db.collections.instance_collection.findOne({ id: instanceId }); + if (!instance) { + throw new CoreError(enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Instance not found.' + ); + } + return instance; + } /** + * TODO: get the instance by name + * create the host map port for the instance according to the latest valid port (port range: 30000 - 40000) + * @returns the valid port number + */ + private async getValidHostPort(): Promise { + const instances = await this.db.collections.instance_collection.find({}).toArray(); + // Filter out null/undefined values and then sort + // get the base port range from config, default to [30000, 40000] + const lxdPortRange: number[] = this.config.lxdPortRange || [30000, 40000]; + + // Ensure we have a valid port range - if the minimum port is 0, use 30000 as minimum + const minPort = lxdPortRange[0] === 0 ? 30000 : lxdPortRange[0]; + const maxPort = lxdPortRange[1] === 0 ? 40000 : lxdPortRange[1]; + + const existingPorts = instances + .map(instance => instance.hostMapPort) + .filter(port => port !== undefined && port !== null); + + // If no valid ports are found, start from the base port + if (existingPorts.length === 0) { + return minPort; // Use the corrected minimum port + } + + // Find the highest port and increment + const latestPort = Math.max(...existingPorts); + // Wrap around if we reach the maximum port + const nextPort = latestPort < maxPort ? latestPort + 1 : minPort; + return nextPort; // Return the next port, wrapping around if necessary; + } + + private formatProxyAddress(address: string): string { + // Remove any protocol prefixes if present + if (address.includes('://')) { + address = address.split('://')[1]; + } + return address; + } + /** + * Create an instance. + * + * @param userId - The id of the user creating the instance. + * @param name - The name of the instance. + * @param type - The type of the instance ('virtual-machine' or 'container'). + * @param appType - The application type of the instance (e.g., 'Jupyter', 'Matlab'). + * @param lifeSpan - The life span of the instance in seconds. + * @param project - The LXD project of the instance (optional, defaults to 'default'). + * + * @return IInstance + */ + public async createInstance(userId: string, username: string, name: string, type: LXDInstanceTypeEnum, + appType: enumAppType, lifeSpan: number, cpuLimit?: number, memoryLimit?: string, diskLimit?: string): Promise { + + const instance_id = uuid(); // Generate a unique ID for the instance + + // generate the token for instance + let instanceSystemToken; + try { + // fake Id + const data = await this.UserCore.issueSystemAccessToken(userId, null); // null for unlimited token + instanceSystemToken = data.accessToken; + } catch (error) { + Logger.error(`Error generating token: ${error}`); + throw new Error('Error generating instance token.'); + } + + // const webdavServer = this.config.webdavServer; + // const webdavMountPath = `/home/ubuntu/${username}_Drive`; // Ensure the correct path is used + + const instanceProfile = appType===enumAppType.MATLAB? 'matlab-profile' : 'jupyter-profile'; + + const instanceMapPort = await this.getValidHostPort(); + + // Prepare user-data for cloud-init to initialize the instance + const cloudInitUserData = appType === enumAppType.MATLAB + ? cloudInitUserDataMatlabContainer(instanceSystemToken, username, instance_id) + : cloudInitUserDataJupyterContainer(instanceSystemToken, username, instance_id, this.config.jupyterPort); + // add boot-time script, to be executed on first boot + const instaceConfig = { + 'limits.cpu': cpuLimit ? cpuLimit.toString() : '4', + 'limits.memory': memoryLimit ? memoryLimit : '16GB', + 'user.disk': diskLimit ? diskLimit : '20GB', + 'user.username': username, // store username to instance config + 'user.user-data': cloudInitUserData // set the cloud-init user-data + }; + + const instanceEntry: IInstance = { + id: instance_id, + name, + userId, + username, + status: enumInstanceStatus.PENDING, + type, + appType, + createAt: Date.now(), + lifeSpan, + instanceToken: instanceSystemToken, + project: this.lxdProject, // the project name by config, idea-fast staging or production + webDavToken: instanceSystemToken, // Assign or generate as needed, System token + life: { + createdTime: Date.now(), + createdUser: userId, + deletedTime: null, + deletedUser: null + }, + metadata: {}, + config: instaceConfig, + // add the port mapping for the instance + hostMapPort: instanceMapPort + }; + + await this.db.collections.instance_collection.insertOne(instanceEntry); + + // Create the job to create the LXD instance on LXD server + const jobName = `Create ${appType} Instance: ${name}`; + const jobType = enumJobType.LXD; + const executorPath = '/lxd'; // The executor path for LXD jobs + + // Override defaults if cpuLimit and memoryLimit are provided + if (cpuLimit) { + instaceConfig['limits.cpu'] = cpuLimit.toString(); // Ensure it's a string + } + if (memoryLimit) { + instaceConfig['limits.memory'] = memoryLimit; + } + + // Construct the payload from the job document parameters + // Prepare job data including the operation and instanceId + const lxd_metadata = { + operation: enumOpeType.CREATE, + instanceId: instanceEntry.id, + userId: userId, + payload: { + name: name, + architecture: 'x86_64', + config: instaceConfig, + devices: { + root: { + path: '/', + pool: this.config.lxdStoragePool, + size: diskLimit ? diskLimit : '20GB', + type: 'disk' + }, + // add a squid proxy device for the container, get the squid ip from config + squid0: { + bind: 'instance', + connect: `tcp:${this.formatProxyAddress(this.config.lxdSquidProxy)}`, + listen: 'tcp:0.0.0.0:3128', + type: 'proxy' + }, + // Add proxy device for Jupyter + jupyter0: { + connect: `tcp:127.0.0.1:${this.config.jupyterPort || 8888}`, + listen: `tcp:0.0.0.0:${instanceMapPort}`, + type: 'proxy' + }, + // Add proxy device for dmp server + dmp0: { + bind: 'instance', + listen: 'tcp:127.0.0.1:3080', + connect: `tcp:${this.config.dmp['host']}:${this.config.dmp['port']}`, + type: 'proxy' + } + + } as Record, + source: { + type: 'image', + alias: appType ===enumAppType.MATLAB? 'ubuntu-matlab-container-image' : 'ubuntu-jupyter-container-image' + }, + profiles: [instanceProfile], + type: type, // 'virtual-machine' or 'container' + project: this.lxdProject // Ensure the correct project is used + } + }; + + // Call the createJob method of JobCore to create a new job + await this.JobCore.createJob( + userId, + jobName, + jobType, + undefined, + undefined, + { path: executorPath, type: 'lxd', id: instance_id }, + null, + null, + 8, // High priority for create operations + lxd_metadata); + + return instanceEntry; + } + + /** + * Start and Stop instance + */ + public async startStopInstance(userId: string, instanceId: string, action: enumOpeType.START | enumOpeType.STOP): Promise { + // Retrieve instance details from the database + const instance = await this.db.collections.instance_collection.findOne({ id: instanceId }); + if (!instance) { + throw new Error('Instance not found.'); + } + // update the instance status + // Optimistically update the instance status + const newStatus = action === 'start' ? enumInstanceStatus.STARTING : enumInstanceStatus.STOPPING; + await this.db.collections.instance_collection.updateOne({ id: instanceId }, { + $set: { status: newStatus } + }); + + + // Create the job to start/stop the LXD instance on the LXD server + const jobName = `${action.toUpperCase()} ${instance.appType} Instance: ${instance.name}`; + const jobType = enumJobType.LXD; + const executorPath = `/lxd/${action}`; + + const lxd_metadata = { + operation: action, + instanceId: instance.id, + userId: userId // Include userId in metadata + }; + + // Call the createJob method of JobCore to create a new job for starting/stopping the instance + await this.JobCore.createJob(userId, jobName, jobType, undefined, undefined, { id: instanceId, type: 'lxd', path: executorPath }, null, null, 5, lxd_metadata); + + // Optionally, immediately return the instance object or wait for the job to complete based on your application's needs + return instance; + } + + /** + * restartInstance with new lifespan, and update the instance's create time + */ + public async restartInstance(userId: string, instanceId: string, lifeSpan: number): Promise { + + // Update the instance's create time and lifespan + const result = await this.db.collections.instance_collection.findOneAndUpdate({ id: instanceId }, { + $set: { + createAt: Date.now(), + lifeSpan: lifeSpan, + status: enumInstanceStatus.STARTING + + } + }, { + returnDocument: 'after' + }); + + if (!result) { // Check if a document was found and updated + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Instance does not exist or update failed.'); + } + const instance = result; + + // Create the job to update the LXD instance on the LXD server + const jobName = `Restart ${instance.appType} Instance: ${instance.name} for user ${userId}`; + const jobType = enumJobType.LXD; + const executorPath = '/lxd/start'; + + const lxd_metadata = { + operation: enumOpeType.START, + instanceId: instance.id, + userId: userId // Include userId in metadata + }; + + // Call the createJob method of JobCore to create a new job for restarting the instance + await this.JobCore.createJob(userId, jobName, jobType, undefined, undefined, { id: instanceId, type: 'lxd', path: executorPath }, null, null, 5, lxd_metadata); + + + return instance; + } + + /** + * Delete an instance. + */ + public async deleteInstance(UserId: string, instanceId: string): Promise { + const result = await this.db.collections.instance_collection.findOneAndUpdate({ id: instanceId }, { + $set: { + 'life.deletedTime': Date.now(), + 'life.deletedUser': UserId, + 'status': enumInstanceStatus.DELETED + } + }, { + returnDocument: 'after' + }); + // log that set the instance status to deleted + Logger.warn(`Instance ${instanceId} is deleted by user ${UserId}`); + + if (!result) { // Check if a document was found and updated + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Instance does not exist or delete failed.'); + } + + const instance = result; // Access the updated document + const appType = instance.appType; + + // Create the job to delete the LXD instance on the LXD server + const jobName = `DELETE ${appType} Instance: ${instance.name}`; + const jobType = enumJobType.LXD; + const executorPath = '/lxd/delete'; + + const lxd_metadata = { + operation: enumOpeType.DELETE, + instanceId: instance.id, + userId: UserId // Include userId in metadata + }; + + // Create the delete job and get its id + const deleteJobResult = await this.JobCore.createJob( + instance.userId, + jobName, + jobType, + undefined, + undefined, + { id: instanceId, type: 'lxd', path: executorPath }, + null, + null, + 10, + lxd_metadata + ); + + // Get the ID of the newly created delete job + const deleteJobId = deleteJobResult?.id; + + // Cancel all other pending jobs related to this instance, except the delete job + await this.db.collections.jobs_collection.updateMany( + { + status: enumJobStatus.PENDING, + id: { $ne: deleteJobId }, // Exclude the delete job we just created + $or: [ + { 'metadata.instanceId': instanceId }, + { 'executor.id': instanceId } + ] + }, + { + $set: { + 'status': enumJobStatus.CANCELLED, + 'life.deletedTime': Date.now(), + 'life.deletedUser': UserId + } + } + ); + + return true; + } + + /** + * Get all instances and update their status based on lifespan. + * + * @param userId The ID of the user managing instances. + * @return IInstance[] The list of instances. + */ + public async getInstances(userId: string): Promise { + // Retrieve all instances that haven't been deleted + const instances = await this.db.collections.instance_collection.find({ + // status is not DELETED + status: { $nin: [enumInstanceStatus.DELETED] }, + userId: userId // Ensure to only fetch instances related to the userId if necessary + }).toArray(); + + // create a LXD_MONITOR job to update the status of the instances + const jobName = `Update Instance Status of User: ${userId}`; + const jobType = enumJobType.LXD_MONITOR; + const executorPath = '/lxd/monitor'; + const period = 1 * 60 * 1000; // set 5 minute future + // Check if there is a pending job for the user instances update + const existingJobs = await this.JobCore.getJob({ name: jobName, type: jobType, status: enumJobStatus.PENDING }); + + if (existingJobs.length === 0) { + const metadata = { + operation: enumMonitorType.STATE, + userId: userId + }; + const instanceIds = instances.map(instance => instance.id).join('|'); + await this.JobCore.createJob(userId, jobName, jobType, undefined, period, { id: instanceIds, type: 'lxd', path: executorPath }, null, null, 1, metadata); + } + + // Create a job for synchronizing instance deletion status between DB and LXD server + const syncDeletionJobName = `Synchronize Deleted Instances for User: ${userId}`; + const existingSyncDeletionJobs = await this.JobCore.getJob({ + name: syncDeletionJobName, + type: jobType, + status: enumJobStatus.PENDING + }); + + if (existingSyncDeletionJobs.length === 0) { + const syncDeletionMetadata = { + operation: enumMonitorType.SYNC_DELETION, + userId: userId + }; + // Create the sync deletion job with a slightly longer period to avoid resource contention + const syncDeletionPeriod = 5 * 60 * 1000; // 5 minutes + await this.JobCore.createJob( + userId, + syncDeletionJobName, + jobType, + undefined, + syncDeletionPeriod, + { id: 'sync-deletion', type: 'lxd', path: executorPath }, + null, + null, + 1, + syncDeletionMetadata + ); + } + + const now = Date.now(); + + // Create a series of promises to handle lifespan and status updates + const updates = instances.map(async (instance) => { + const lifeDuration = now - instance.createAt; + const remainingLife = instance.lifeSpan - lifeDuration; + + // Check if the lifespan has been exceeded + if (remainingLife <= 0) { + // Check if the instance is not already stopped + if (instance.status !== enumInstanceStatus.STOPPED && instance.status !== enumInstanceStatus.STOPPING + && instance.status !== enumInstanceStatus.FAILED + ) { + // Stop the instance and update status in the database + await this.startStopInstance(userId, instance.id, enumOpeType.STOP); + } + + await this.db.collections.instance_collection.updateOne( + { id: instance.id }, + { + $set: { + lifeSpan: 0 // Reset lifespan to zero as it's now considered ended + } + } + ); + + } + // Ensure instance config and limits exist + const cpuLimit = 'limits.cpu' in instance.config ? parseInt(instance.config['limits.cpu'] as string) : 1; + const memoryLimit = 'limits.memory' in instance.config ? this.parseMemory(instance.config['limits.memory'] as string) : 16 * 1024 * 1024 * 1024; // Default to 16GB + + let cpuUsage = 0; + let memoryUsage = 0; + + if (instance.status === enumInstanceStatus.RUNNING && instance.lxdState) { + const cpuUsageRaw = instance.lxdState.cpu.usage; + const memoryUsageRaw = instance.lxdState.memory.usage; + + // Retrieve previous values from local storage (or instance metadata if necessary) + const previousCpuUsageRaw = this.previousCpuUsage[instance.id] || 0; + const previousTimestamp = this.previousCpuTimestamp[instance.id] || now; + + + // Calculate the time interval in seconds + const intervalSeconds = (now - previousTimestamp) / 1000; + + // Calculate the difference in CPU usage + const cpuUsageDelta = cpuUsageRaw - previousCpuUsageRaw; + + // Convert CPU usage delta to seconds + const cpuUsageDeltaSeconds = cpuUsageDelta / 1e9; + + // Calculate CPU usage percentage + if (intervalSeconds > 0) { + cpuUsage = (cpuUsageDeltaSeconds / (cpuLimit * intervalSeconds)) * 100; + } + + // Calculate memory usage percentage + memoryUsage = (memoryUsageRaw / memoryLimit) * 100; + + // Store the current values for the next calculation + this.previousCpuUsage[instance.id] = cpuUsageRaw; + this.previousCpuTimestamp[instance.id] = now; + + } + + // Assign CPU and memory usage to instance metadata + instance.metadata = { + ...instance.metadata, + cpuUsage: cpuUsage > 100 ? 100 : (cpuUsage < 0 ? 0 : Math.round(cpuUsage)), // Clamp values between 0 and 100 + memoryUsage: memoryUsage > 100 ? 100 : (memoryUsage < 0 ? 0 : Math.round(memoryUsage)) // Clamp values between 0 and 100 + }; + + // resolve the promise with the instance object + return instance; + }); + + // Wait for all updates to complete + await Promise.all(updates); + + // Fetch and return the updated list of instances + return instances.map(instance => { + // calculate the cpu and memory usage percentage, only for running instances + + // This will provide the updated remaining life span without persisting it + const lifeDuration = now - instance.createAt; + const remainingLifeSpan = (instance.lifeSpan - lifeDuration); + return { + ...instance, + lifeSpan: remainingLifeSpan > 0 ? remainingLifeSpan : 0 + }; + }); + } + + /** + * Edit an instance. + * + * @param userId - The id of the user editing the instance. + * @param instanceId - The id of the instance to edit. + * @param updates - Object containing the fields to update. + * + * @return IInstance + */ + public async editInstance(requester: IUser, instanceId: string | null | undefined, instanceName: string | null | undefined, updates: Record): Promise { + + // Check that at least one of the identifier fields is provided + if (!instanceId && !instanceName) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Instance ID or name must be provided.' + ); + } + + // Find the instance by either ID or name + const instanceQuery: Record = {}; + if (instanceId) instanceQuery['id'] = instanceId; + if (instanceName) instanceQuery['name'] = instanceName; + + const instance = await this.db.collections.instance_collection.findOne(instanceQuery); + + if (!instance) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Instance does not exist.' + ); + } + + + // Check if the requester has permission to edit the instance + if (requester.username !== instance.username && requester.type !== enumUserTypes.ADMIN) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'User does not have permission to edit this instance.' + ); + } + + // Update the config object directly if cpuLimit or memoryLimit are provided + if (updates['cpuLimit'] || updates['memoryLimit']) { + const currentConfig = instance.config ?? {}; + + updates['config'] = { + ...currentConfig, // Preserve existing config values + 'limits.cpu': updates['cpuLimit']?.toString() || currentConfig['limits.cpu'], + 'limits.memory': updates['memoryLimit'] || currentConfig['limits.memory'] + }; + + // Remove top-level cpuLimit and memoryLimit after applying to config + delete updates['cpuLimit']; + delete updates['memoryLimit']; + } + // Update the instance in the database + const result = await this.db.collections.instance_collection.findOneAndUpdate( + { _id: instance._id }, // Use the unique `_id` from MongoDB + { $set: updates }, + { returnDocument: 'after' } + ); + + if (!result) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Failed to update the instance.' + ); + } + + // Create the job to update the LXD instance on the LXD server + // Prepare job metadata for the LXD update operation + const metadata = { + operation: enumOpeType.UPDATE, + instanceToken: instance.instanceToken ?? '', + instanceId: result.id, + updates: updates + }; + const appType = result.appType; + + // Create the job for the LXD operation + // TODO: better to package all of them to the jobCore as a attch function + const jobName = `Update Config for ${appType} Instance: ${result.name}`; + const executorPath = '/lxd/update'; + await this.JobCore.createJob(requester.id, jobName, enumJobType.LXD, undefined, undefined, { id: instanceId || instanceName || '', type: 'lxd', path: executorPath }, null, null, 2, metadata); + + + return result; + } + + // get the ip of the instance by instanceId or update the state of the instance + + /** + * Get or update the container IP (state) from the database or trigger monitor if not available. + * + * @param instance_id - The ID of the instance. + * @param user_id - The user ID (optional, for ownership validation). + * @return string | null - The container IP, or null if not available. + */ + public async getContainerIP(instance_id: string, user_id?: string) { + // Retrieve the instance by the instance_id + const instance: IInstance = await this.getInstanceById(instance_id); + + // Check instance ownership + if (user_id && instance.userId !== user_id) { + Logger.error('User not authorized to access the instance'); + throw new Error('User not authorized to access the instance'); + } + + // If instance is not running, return null (no IP available) + if (instance.status !== 'RUNNING') { + Logger.warn(`Instance ${instance_id} is not running, no IP available.`); + return null; + } + + // Check if the instance state is available in the database (via the monitor job) + if (instance.lxdState && instance.lxdState.network && instance.lxdState.network['eth0']) { + const ipv4Address = instance.lxdState.network['eth0'].addresses + .filter((addr) => addr.family === 'inet') + .map((addr) => addr.address)[0]; + + if (ipv4Address) { + return {ip: ipv4Address, port: this.config.jupyterPort}; + } + } + + // If the state is not available, trigger the monitor job if it doesn't exist + const existingMonitorJob = await this.JobCore.getJob({ + name: `Update Instance Status of User: ${instance.userId}`, + type: enumJobType.LXD_MONITOR, + status: enumJobStatus.PENDING + }); + + if (existingMonitorJob.length === 0) { + // No pending monitor job, create one + const jobName = `Update Instance Status of User: ${instance.userId}`; + const jobType = enumJobType.LXD_MONITOR; + const executorPath = '/lxd/monitor'; + const period = 5 * 60 * 1000; // 5 minute + + const metadata = { + operation: enumMonitorType.STATE, + userId: user_id + }; + + await this.JobCore.createJob( + instance.userId, + jobName, + jobType, + undefined, + period, + { id: instance.id, type: 'lxd', path: executorPath }, + null, + null, + 1, + metadata + ); + + Logger.warn(`Monitor job created for instance ${instance_id} to update state.`); + } + // Return null IP for now, as the monitor job will update the instance state + return null; + } + + public async checkQuotaBeforeCreation(userId: string, requestedCpu: number, requestedMemory: string, requestedDisk: string, requestedInstances: number): Promise { + + const {properties: userQuota} = await this.configCore.getConfig(enumConfigType.USERCONFIG, userId, true )as { properties: IUserConfig }; + const instances: IInstance[] = await this.getInstances(userId); + + let currentCpu = 0, currentMemory = 0, currentDisk = 0; + instances.forEach(instance => { + currentCpu += parseInt(instance.config['limits.cpu'] as string); + + currentMemory += this.parseMemory(instance.config['limits.memory'] as string); + currentDisk += this.parseMemory(instance.config['user.disk'] as string); + }); + + if (currentCpu + requestedCpu > userQuota.defaultLXDMaximumInstanceCPUCores) { + throw new CoreError(enumCoreErrors.NO_PERMISSION_ERROR, 'Requested CPU exceeds available quota. Please delete some instances or contact an administrator.'); + } + if (this.parseMemory(requestedMemory) + currentMemory > userQuota.defaultLXDMaximumInstanceMemory) { + throw new CoreError(enumCoreErrors.NO_PERMISSION_ERROR, 'Requested memory exceeds available quota. Please delete some instances or contact an administrator.'); + } + if (this.parseMemory(requestedDisk) + currentDisk > userQuota.defaultLXDMaximumInstanceDiskSize) { + throw new CoreError(enumCoreErrors.NO_PERMISSION_ERROR, 'Requested disk space exceeds available quota. Please delete some instances or contact an administrator.'); + } + if (instances.length + requestedInstances > userQuota.defaultLXDMaximumInstances) { + throw new CoreError(enumCoreErrors.NO_PERMISSION_ERROR, 'Requested instance count exceeds available quota. Please delete some instances or contact an administrator.'); + } + } + + /** + * Extend the lifespan of an instance + * + * @param userId - The ID of the user extending the instance + * @param instanceId - The ID of the instance to extend + * @param additionalTime - The additional time to add in milliseconds + * @returns The updated instance + */ + public async extendInstanceLifespan(userId: string, instanceId: string, additionalTime: number): Promise { + // Retrieve instance details from the database + const instance = await this.db.collections.instance_collection.findOne({ id: instanceId }); + if (!instance) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Instance not found.' + ); + } + + // Get the current time to check for cooldown period + const now = Date.now(); + + // Check if user has permission to extend this instance + if (instance.userId !== userId) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'User does not have permission to extend this instance.' + ); + } + + // Calculate remaining lifespan + const lifeDuration = now - instance.createAt; + const remainingLifespan = instance.lifeSpan - lifeDuration; + + // Check if lifespan is already sufficiently long (more than 7 days) + if (remainingLifespan > 7 * 24 * 60 * 60 * 1000) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'Instance already has sufficient lifespan (more than 7 days). No need to extend now.' + ); + } + + const newLifespan = instance.lifeSpan + additionalTime; + + // Update the instance's lifespan + const result = await this.db.collections.instance_collection.findOneAndUpdate( + { id: instanceId }, + { $set: { lifeSpan: newLifespan } }, + { returnDocument: 'after' } + ); + + if (!result) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Failed to update the instance lifespan.' + ); + } + + Logger.log(`Extended lifespan of instance ${instanceId} by ${additionalTime / (3600 * 1000)} hours`); + + return result; + } + + public async getQuotaAndFlavors(requester: IUser) { + + // key: userId, + const {properties: userQuota} = await this.configCore.getConfig(enumConfigType.USERCONFIG, requester.id, true ); + const { properties: systemConfig } = await this.configCore.getConfig(enumConfigType.SYSTEMCONFIG, null, true); + + const isAdmin = requester.type === enumUserTypes.ADMIN; + // const userFlavors = isAdmin ? (systemConfig as ISystemConfig).defaultLXDFlavor.keys(): (userQuota as IUserConfig).defaultLXDflavor; + const userFlavors = isAdmin + ? Object.keys((systemConfig as ISystemConfig).defaultLXDFlavor) + : (userQuota as IUserConfig).defaultLXDflavor; + + + // Transform flavors into an object with flavor names as keys + const transformedFlavors: Record = {}; + userFlavors.forEach(flavor => { + const flavorDetails = (systemConfig as ISystemConfig).defaultLXDFlavor[flavor]; + if (!flavorDetails) { + Logger.warn(`Flavor ${flavor} is not defined in systemConfig.defaultLXDFlavor.`); + return; // Skip this flavor if it's not defined + } + + transformedFlavors[flavor] = { + ...flavorDetails, + memoryLimit: this.formatMemory(flavorDetails.memoryLimit), + diskLimit: this.formatMemory(flavorDetails.diskLimit) + }; + }); + + return { + userQuota: userQuota as IUserConfig, + userFlavors: transformedFlavors + }; + } + + + +} \ No newline at end of file diff --git a/packages/itmat-cores/src/coreFunc/jobCore.ts b/packages/itmat-cores/src/coreFunc/jobCore.ts new file mode 100644 index 000000000..97e482ffb --- /dev/null +++ b/packages/itmat-cores/src/coreFunc/jobCore.ts @@ -0,0 +1,143 @@ +import { CoreError, IJob, enumCoreErrors, enumJobType, enumJobStatus, IExecutor} from '@itmat-broker/itmat-types'; +import { v4 as uuid } from 'uuid'; +import { DBType } from '../database/database'; + + +export class JobCore { + db: DBType; + constructor(db: DBType) { + this.db = db; + } + + /** + * Create a job. + * + * @param requester - The ID of the requester creating the job. + * @param name - The name of the job. + * @param type - The type of the job (enumJobType). + * @param nextExecutionTime - When the job should start. Null for immediate execution. + * @param period - The period for recurring jobs. + * @param executor - The executor responsible for running the job. + * @param data - Any input data required for the job. + * @param parameters - Additional parameters for the job. + * @param priority - The job's priority level. + * @param metadata - Additional metadata for the job. + * + * @return IJobEntry + */ + public async createJob( + requester: string, + name: string, + type: enumJobType, + nextExecutionTime?: number, + period?: number, + executor?: IExecutor, + data?: JSON | null | undefined, + parameters?: JSON | null | undefined, + priority?: number, + metadata?: Record + ): Promise { + const jobEntry: IJob = { + id: uuid(), + name: name, + nextExecutionTime: nextExecutionTime ?? Date.now(), + period: period ?? null, + type: type, + executor: executor ?? null, + data: data ?? null, + parameters: parameters ?? null, + priority: priority ?? 0, + status: enumJobStatus.PENDING, + history: [], + counter: 0, + life: { + createdTime: Date.now(), + createdUser: requester, + deletedTime: null, + deletedUser: null + }, + metadata: metadata ?? {} + }; + + await this.db.collections.jobs_collection.insertOne(jobEntry); + return jobEntry; + } + + /** + * Retrieve jobs based on optional filters. + * + * @param filter - Partial filter to query jobs (e.g., by name, type, status). + * + * @return IJobEntry[] + */ + public async getJobs(filter: Partial = {}) { + return await this.db.collections.jobs_collection.find(filter).toArray(); + } + + /** + * Edit an existing job by updating its priority, next execution time, or period. + * + * @param requester - The ID of the user requesting the edit. + * @param jobId - The ID of the job to edit. + * @param priority - New priority value (optional). + * @param nextExecutionTime - New next execution time (optional). + * @param period - New period for recurring jobs (optional). + * + * @return IJobEntry + */ + public async editJob( + requester: string, + jobId: string, + priority?: number | null, + nextExecutionTime?: number | null, + period?: number | null + ): Promise { + const job = await this.db.collections.jobs_collection.findOne({ id: jobId }); + + if (!job) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Job does not exist.' + ); + } + + const setObj: Partial = {}; + if (priority !== undefined && priority !== null) { + setObj.priority = priority; + } + if (nextExecutionTime !== undefined && nextExecutionTime !== null) { + setObj.nextExecutionTime = nextExecutionTime; + setObj.status = enumJobStatus.PENDING; + } + if (period !== undefined) { + setObj.period = period; + } + + const result = await this.db.collections.jobs_collection.findOneAndUpdate( + { id: jobId }, + { $set: setObj }, + { returnDocument: 'after' } + ); + + if (!result) { + throw new CoreError( + enumCoreErrors.DATABASE_ERROR, + 'Failed to update the job.' + ); + } + + return result; + } + + /** + * Get a job based on specific filters, like job ID, name, type, status, etc. + * + * @param filter - The filter for querying jobs. + * + * @return IJobEntry[] + */ + public async getJob(filter: Partial) { + return await this.db.collections.jobs_collection.find(filter).toArray(); + } + +} diff --git a/packages/itmat-cores/src/coreFunc/logCore.ts b/packages/itmat-cores/src/coreFunc/logCore.ts new file mode 100644 index 000000000..b92fc44c7 --- /dev/null +++ b/packages/itmat-cores/src/coreFunc/logCore.ts @@ -0,0 +1,251 @@ +import { CoreError, ILog, IUserWithoutToken, enumAPIResolver, enumCoreErrors, enumEventStatus, enumEventType, enumUserTypes } from '@itmat-broker/itmat-types'; +import { DBType } from '../database/database'; +import { Filter } from 'mongodb'; + +export class LogCore { + db: DBType; + constructor(db: DBType) { + this.db = db; + } + + /** + * Get logs. + * + * @param requester - The requester. + * @param caller - The caller of the event. + * @param type - The type of the event. + * @param apiResolver - The resolver of the event. + * @param event - The event. + * @param status - The status of the event. + * @param indexRange - The range of the indexes. + * @param timeRange - The range of the time. + * Note that if parameters set to null, no restrictions will be applied. + * + * @return ILog[] + */ + public async getLogs(requester: IUserWithoutToken | undefined, caller?: string, type?: enumEventType[], apiResolver?: enumAPIResolver[], event?: string[], status?: enumEventStatus[], indexRange?: number[], timeRange?: number[]) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + if (requester.type !== enumUserTypes.ADMIN) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + const filters: Filter = {}; + if (caller) { + filters.requester = caller; + } + if (type) { + filters.type = { $in: type }; + } + if (apiResolver) { + filters.apiResolver = { $in: apiResolver }; + } + if (event) { + filters.event = { $in: event }; + } + if (status) { + filters.status = { $in: status }; + } + if (timeRange) { + filters['life.createdTime'] = { + $gte: timeRange[0], + $lte: timeRange[1] + }; + } + let logs: ILog[]; + if (indexRange) { + logs = await this.db.collections.log_collection.find(filters).sort({ 'life.createdTime': -1 }).skip(indexRange[0]).limit(indexRange[1] - indexRange[0]).toArray(); + } else { + logs = await this.db.collections.log_collection.find(filters).toArray(); + } + return logs; + } + + /** + * Provide several statistics of the logs. + * This function provides several statistics of the logs, which is used by the frontend. + * Modify it as needed. + * + */ + public async getLogsSummary() { + const thirtyDaysInMilliseconds = 30 * 24 * 60 * 60 * 1000; + const startTime = Date.now() - thirtyDaysInMilliseconds; + + const eventStatistics = await this.db.collections.log_collection.aggregate([ + { + $match: { + 'life.createdTime': { $gte: startTime }, + 'timeConsumed': { $ne: null } // Skip documents with null timeConsumed + } + }, + { + $addFields: { + dayIndex: { + $trunc: { + $divide: [ + { $subtract: ['$life.createdTime', startTime] }, + 86400000 // Milliseconds in a day + ] + } + } + } + }, + { + $group: { + _id: { event: '$event', dayIndex: '$dayIndex' }, + countPerDay: { $sum: 1 }, + avgTimeConsumed: { $avg: '$timeConsumed' }, + stdDevTimeConsumed: { $stdDevSamp: '$timeConsumed' } + } + }, + { + $group: { + _id: '$_id.event', + totalEventCount: { $sum: '$countPerDay' }, + data: { + $push: { + dayIndex: '$_id.dayIndex', + count: '$countPerDay' + } + }, + avgTimeConsumed: { $avg: '$avgTimeConsumed' }, + stdDevTimeConsumed: { $avg: '$stdDevTimeConsumed' } + } + }, + { + $project: { + _id: 0, + event: '$_id', + count: '$totalEventCount', + dailyCounts: { + $map: { + input: { $range: [0, 30] }, // From 0 to 29 (inclusive) + as: 'dayIndex', + in: { + $let: { + vars: { + matchingDayData: { + $arrayElemAt: [ + { + $filter: { + input: '$data', + as: 'd', + cond: { $eq: ['$$d.dayIndex', '$$dayIndex'] } + } + }, + 0 + ] + } + }, + in: { + dayIndex: { $subtract: ['$$dayIndex', 29] }, // Shift to correct day range + count: { $ifNull: ['$$matchingDayData.count', 0] } + } + } + } + } + }, + avgTimeConsumed: 1, + stdDevTimeConsumed: 1 + } + }, + { + $sort: { count: -1 } + } + ]).toArray(); + + const requesterStatistics = await this.db.collections.log_collection.aggregate([ + { + $match: { + 'life.createdTime': { $gte: startTime }, + 'timeConsumed': { $ne: null } // Skip documents with null timeConsumed + } + }, + { + $addFields: { + dayIndex: { + $trunc: { + $divide: [ + { $subtract: ['$life.createdTime', startTime] }, + 86400000 // Milliseconds in a day + ] + } + } + } + }, + { + $group: { + _id: { requester: '$requester', dayIndex: '$dayIndex' }, + countPerDay: { $sum: 1 }, + avgTimeConsumed: { $avg: '$timeConsumed' }, + stdDevTimeConsumed: { $stdDevSamp: '$timeConsumed' } + } + }, + { + $group: { + _id: '$_id.requester', + totalRequesterCount: { $sum: '$countPerDay' }, + data: { + $push: { + dayIndex: '$_id.dayIndex', + count: '$countPerDay' + } + }, + avgTimeConsumed: { $avg: '$avgTimeConsumed' }, + stdDevTimeConsumed: { $avg: '$stdDevTimeConsumed' } + } + }, + { + $project: { + _id: 0, + requester: '$_id', + count: '$totalRequesterCount', + dailyCounts: { + $map: { + input: { $range: [0, 30] }, // From 0 to 29 (inclusive) + as: 'dayIndex', + in: { + $let: { + vars: { + matchingDayData: { + $arrayElemAt: [ + { + $filter: { + input: '$data', + as: 'd', + cond: { $eq: ['$$d.dayIndex', '$$dayIndex'] } + } + }, + 0 + ] + } + }, + in: { + dayIndex: { $subtract: ['$$dayIndex', 29] }, // Shift to correct day range + count: { $ifNull: ['$$matchingDayData.count', 0] } + } + } + } + } + }, + avgTimeConsumed: 1, + stdDevTimeConsumed: 1 + } + }, + { + $sort: { count: -1 } + } + ]).toArray(); + + return { eventStatistics, requesterStatistics }; + } +} + diff --git a/packages/itmat-cores/src/coreFunc/organisationCore.ts b/packages/itmat-cores/src/coreFunc/organisationCore.ts new file mode 100644 index 000000000..3cff548e8 --- /dev/null +++ b/packages/itmat-cores/src/coreFunc/organisationCore.ts @@ -0,0 +1,191 @@ +import { v4 as uuid } from 'uuid'; +import { CoreError, FileUpload, IFile, IOrganisation, IUserWithoutToken, enumCoreErrors, enumFileCategories, enumFileTypes, enumUserTypes } from '@itmat-broker/itmat-types'; +import { DBType } from '../database/database'; +import { FileCore } from './fileCore'; +import { UpdateFilter } from 'mongodb'; +import { makeGenericResponse } from '../utils'; + +export class OrganisationCore { + db: DBType; + fileCore: FileCore; + constructor(db: DBType, fileCore: FileCore) { + this.db = db; + this.fileCore = fileCore; + } + + /** + * Get organisations. + * + * @param requester - The requester. + * @param organisationId - The organisation id. + * + * @returns - IOrganisation[] + */ + public async getOrganisations(requester: IUserWithoutToken | undefined, organisationId?: string) { + return organisationId ? await this.db.collections.organisations_collection.find({ 'id': organisationId, 'life.deletedTime': null }).toArray() : await this.db.collections.organisations_collection.find({ 'life.deletedTime': null }).toArray(); + } + + /** + * Creates a new organisation. + * + * @param requester - The requester. + * @param name - The name of the organisation. + * @param shortname - The shortname of the organisation. + * @param profile - The profile of the organisation. + * + * @returns - IOrganisation + */ + public async createOrganisation(requester: IUserWithoutToken | undefined, name: string, shortname?: string, profile?: FileUpload) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + if (requester.type !== enumUserTypes.ADMIN) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + const organisation = await this.db.collections.organisations_collection.findOne({ 'name': name, 'life.deletedTime': null }); + if (organisation) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Organisation already exists.' + ); + } + + let fileEntry: IFile | undefined = undefined; + if (profile) { + if (!Object.keys(enumFileTypes).includes((profile?.filename?.split('.').pop() || '').toUpperCase())) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'File type not supported.' + ); + } + fileEntry = await this.fileCore.uploadFile(requester, null, null, profile, + enumFileTypes[(profile.filename.split('.').pop() || '').toUpperCase() as keyof typeof enumFileTypes], enumFileCategories.PROFILE_FILE); + } + + const entry: IOrganisation = { + id: uuid(), + name: name, + shortname: shortname, + profile: fileEntry ? fileEntry.id : undefined, + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + + await this.db.collections.organisations_collection.insertOne(entry); + return entry; + } + + /** + * Edits an organisation. + * + * @param requester - The requester. + * @param organisationId - The organisation id. + * @param name - The name of the organisation. + * @param shortname - The shortname of the organisation. + * @param profile - The profile of the organisation. + * + * @returns - IOrganisation + */ + public async editOrganisation(requester: IUserWithoutToken | undefined, organisationId: string, name?: string, shortname?: string, profile?: FileUpload) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + if (requester.type !== enumUserTypes.ADMIN) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + const organisation = await this.db.collections.organisations_collection.findOne({ 'id': organisationId, 'life.deletedTime': null }); + if (!organisation) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Organisation not found.' + ); + } + + const updateFilter: UpdateFilter = {}; + if (name) { + const org = await this.db.collections.organisations_collection.findOne({ 'name': name, 'life.deletedTime': null }); + if (org && org.id !== organisationId) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Organisation already exists.' + ); + } + updateFilter['name'] = name; + } + if (shortname) { + updateFilter['shortname'] = shortname; + } + if (profile) { + if (!Object.keys(enumFileTypes).includes((profile?.filename?.split('.').pop() || '').toUpperCase())) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'File type not supported.' + ); + } + const fileEntry = await this.fileCore.uploadFile(requester, null, null, profile, + enumFileTypes[(profile.filename.split('.').pop() || '').toUpperCase() as keyof typeof enumFileTypes], enumFileCategories.PROFILE_FILE); + updateFilter['profile'] = fileEntry.id; + } + + await this.db.collections.organisations_collection.updateOne({ id: organisationId }, { $set: updateFilter }); + + return makeGenericResponse(organisationId, true, undefined, 'Organisation updated successfully.'); + } + + /** + * Deletes an organisation. + * + * @param requester - The requester. + * @param organisationId - The organisation id. + * + * @returns - IOrganisation + */ + public async deleteOrganisation(requester: IUserWithoutToken | undefined, organisationId: string) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + if (requester.type !== enumUserTypes.ADMIN) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + const organisation = await this.db.collections.organisations_collection.findOne({ 'id': organisationId, 'life.deletedTime': null }); + if (!organisation) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Organisation not found.' + ); + } + + await this.db.collections.organisations_collection.updateOne({ id: organisationId }, { $set: { 'life.deletedTime': Date.now(), 'life.deletedUser': requester.id } }); + + return makeGenericResponse(organisationId, true, undefined, 'Organisation deleted successfully.'); + } +} \ No newline at end of file diff --git a/packages/itmat-cores/src/coreFunc/permissionCore.ts b/packages/itmat-cores/src/coreFunc/permissionCore.ts new file mode 100644 index 000000000..5e82a1f59 --- /dev/null +++ b/packages/itmat-cores/src/coreFunc/permissionCore.ts @@ -0,0 +1,341 @@ +import { CoreError, IData, IDataPermission, IField, IRole, IUserWithoutToken, enumCoreErrors, enumDataAtomicPermissions, enumStudyRoles, enumUserTypes, permissionString } from '@itmat-broker/itmat-types'; +import { DBType } from '../database/database'; +import { v4 as uuid } from 'uuid'; +import { makeGenericResponse } from '../utils'; + +export class PermissionCore { + db: DBType; + constructor(db: DBType) { + this.db = db; + } + + /** + * Get the roles of a user. + * + * @param roleId - The id of the role. + * + * @returns - IRole + */ + public async getUsersOfRole(roleId: string) { + const role = await this.db.collections.roles_collection.findOne({ 'id': roleId, 'life.deletedTime': null }); + if (!role) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + return await this.db.collections.users_collection.find({ id: { $in: role.users } }, { projection: { _id: 0, password: 0, otpSecret: 0 } }).toArray(); + } + + /** + * Get the roles of a user. + * + * @param user + * @param studyId + * + * @returns - IRole[] + */ + public async getRolesOfUser(requester: IUserWithoutToken | undefined, userId: string, studyId?: string) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + // if studyId is provided, only admins, study managers and user him/herself can see the roles + // if studyId is not provided, only admins and user him/herself can see the roles + if (studyId) { + return await this.db.collections.roles_collection.find({ 'studyId': studyId, 'users': userId, 'life.deletedTime': null }).toArray(); + } else { + if (requester.type !== enumUserTypes.ADMIN && requester.id !== userId) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + return await this.db.collections.roles_collection.find({ 'users': userId, 'life.deletedTime': null }).toArray(); + } + } + + /** + * Get the roles of a study. + * + * @param requester - The requester. + * @param studyId - The id of the study. + * + * @returns - IRole[] + */ + public async getRolesOfStudy(requester: IUserWithoutToken | undefined, studyId: string) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + const roles = await this.getRolesOfUser(requester, requester.id, studyId); + if (requester.type !== enumUserTypes.ADMIN && roles.every(role => role.studyRole !== enumStudyRoles.STUDY_MANAGER)) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + return await this.db.collections.roles_collection.find({ 'studyId': studyId, 'life.deletedTime': null }).toArray(); + } + + /** + * Check if a user has a certain permission of a field or data. + * + * @param user - The user to check permission for. + * @param studyId - The id of the study. + * @param entry - The field or data to check permission for. + * @param permission - The permission to check. + * + * @returns boolean + */ + public async checkFieldOrDataPermission(user: IUserWithoutToken, studyId: string, entry: Partial | Partial, permission: enumDataAtomicPermissions) { + const roles = await this.getRolesOfUser(user, user.id, studyId); + const tags: boolean[] = []; + for (const role of roles) { + let tag = true; + const dataPermissions = role.dataPermissions; + for (const dataPermission of dataPermissions) { + if (dataPermission.fields.every(field => !(new RegExp(field).test(String(entry.fieldId))))) { + tag = false; + } + if ('value' in entry && dataPermission.dataProperties) { + if (entry.properties) { + for (const property in dataPermission.dataProperties) { + if (dataPermission.dataProperties[property].every(prop => !(new RegExp(prop).test(String(entry.properties?.[property]))))) { + tag = false; + } + } + } + } + if (!permissionString[permission].includes(dataPermission.permission)) { + tag = false; + } + } + tags.push(tag); + } + if (tags.every(tag => tag === false)) { + return false; + } + return true; + } + + /** + * Create a new role. + * + * @param requester - The requester. + * @param studyId - The id of the study. + * @param name - The name of the role. + * @param description - The description of the role. + * @param dataPermissions - The data permissions of the role. + * @param studyRole - The role of the study. + * @param users - The users of the role. + * @returns IRole + */ + public async createStudyRole(requester: IUserWithoutToken | undefined, studyId: string, name: string, description?: string, dataPermissions?: IDataPermission[], studyRole?: enumStudyRoles, users?: string[]) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + const roles = await this.getRolesOfUser(requester, requester.id, studyId); + if (requester.type !== enumUserTypes.ADMIN && roles.every(role => role.studyRole !== enumStudyRoles.STUDY_MANAGER)) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + const newRole: IRole = { + id: uuid(), + studyId: studyId, + name: name, + description: description, + dataPermissions: dataPermissions ?? [{ + fields: [], + dataProperties: {}, + includeUnVersioned: false, + permission: 0 + }], + studyRole: studyRole ?? enumStudyRoles.STUDY_USER, + users: users ?? [], + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + + await this.db.collections.roles_collection.insertOne(newRole); + return newRole; + } + + /** + * Edit a role. + * + * @param requester - The requester. + * @param roleId - The id of the role. + * @param name - The name of the role. + * @param description - The description of the role. + * @param dataPermissions - The data permissions of the role. + * @param studyRole - The role of the study. + * @param users - The users of the role. + * + * @returns IRole + */ + public async editStudyRole(requester: IUserWithoutToken | undefined, roleId: string, name?: string, description?: string, dataPermissions?: IDataPermission[], studyRole?: enumStudyRoles, users?: string[]) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + const role = await this.db.collections.roles_collection.findOne({ 'id': roleId, 'life.deletedTime': null }); + if (!role) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + const roles = await this.getRolesOfUser(requester, requester.id, role.studyId); + if (requester.type !== enumUserTypes.ADMIN && roles.every(role => role.studyRole !== enumStudyRoles.STUDY_MANAGER)) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + const res = await this.db.collections.roles_collection.findOneAndUpdate({ id: roleId }, { + $set: { + name: name ?? role.name, + description: description ?? role.description, + dataPermissions: dataPermissions ?? role.dataPermissions, + studyRole: studyRole ?? role.studyRole, + users: users ?? role.users + } + }, { + returnDocument: 'after' + }); + return res; + } + /** + * Add a guest user to all public studies with read permissions. + * + * @param username - The username of the guest user to add. + * + * @returns IGenericResponse + */ + public async addGuestUser(username: string) { + + if (!username) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Username is required to add a guest user.' + ); + } + + const user = await this.db.collections.users_collection.findOne( + { username: username }, + { projection: { _id: 0, password: 0, otpSecret: 0 } } + ) as IUserWithoutToken | null; + + if (!user) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'User not found' + ); + } + if (user.type !== enumUserTypes.GUEST) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'User is not a guest user.' + ); + } + const publicStudies = await this.db.collections.studies_collection.find({ + 'isPublic': true, + 'life.deletedTime': null + }).toArray(); + if (publicStudies.length === 0) { + return makeGenericResponse(user.id, true, undefined, 'No public studies found.'); + } + // Get all roles with Guest permission in public studies + // and add the user to those roles + const studyIds = publicStudies.map(study => study.id); + const guestRoles = await this.db.collections.roles_collection.find({ + 'studyId': { $in: studyIds }, + 'name': 'Guest', + 'dataPermissions': { + $elemMatch: { permission: { $bitsAllSet: 4 } } + }, + 'life.deletedTime': null + }).toArray(); + + if (guestRoles.length === 0) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'No guest roles found for public studies.' + ); + } + // Update each guest role to include the user + const updatePromises = guestRoles.map(async role => + this.db.collections.roles_collection.updateOne( + { id: role.id, users: { $ne: user.id } }, + { $push: { users: user.id } } + ) + ); + + await Promise.all(updatePromises); + + return makeGenericResponse(user.id, true, undefined, `User added to ${updatePromises.length} public study roles`); + } + + + /** + * Delete a role. + * + * @param requester - The requester. + * @param roleId - The id of the role. + * @returns - IGenericResponse + */ + public async deleteStudyRole(requester: IUserWithoutToken | undefined, roleId: string) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + const role = await this.db.collections.roles_collection.findOne({ 'id': roleId, 'life.deletedTime': null }); + if (!role) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + const roles = await this.getRolesOfUser(requester, requester.id, role.studyId); + if (requester.type !== enumUserTypes.ADMIN && roles.every(role => role.studyRole !== enumStudyRoles.STUDY_MANAGER)) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + await this.db.collections.roles_collection.findOneAndUpdate({ id: roleId }, { + $set: { + 'life.deletedTime': Date.now(), + 'life.deletedUser': requester.id + } + }); + + return makeGenericResponse(roleId, true, undefined, 'Role deleted successfully.'); + } +} diff --git a/packages/itmat-cores/src/coreFunc/standardizationCore.ts b/packages/itmat-cores/src/coreFunc/standardizationCore.ts new file mode 100644 index 000000000..228b0cb39 --- /dev/null +++ b/packages/itmat-cores/src/coreFunc/standardizationCore.ts @@ -0,0 +1,158 @@ +import { CoreError, IStandardization, IUserWithoutToken, enumCoreErrors, enumStudyRoles } from '@itmat-broker/itmat-types'; +import { v4 as uuid } from 'uuid'; +import { makeGenericResponse } from '../utils/responses'; +import { DBType } from '../database/database'; +import { ObjectStore } from '@itmat-broker/itmat-commons'; +import { PermissionCore } from './permissionCore'; +import { StudyCore } from './studyCore'; + + +/** + * TODO: This file is not yet implemented. It is a placeholder for the standardization core. + */ + +export class StandarizationCore { + db: DBType; + permissionCore: PermissionCore; + studyCore: StudyCore; + constructor(db: DBType, objStore: ObjectStore, permissionCore: PermissionCore, studyCore: StudyCore) { + this.db = db; + this.permissionCore = permissionCore; + this.studyCore = studyCore; + } + + public async getStandardization(requester: IUserWithoutToken | undefined, versionId: string | null, studyId: string, projectId?: string, type?: string) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + /* check study exists */ + if (!studyId && !projectId) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Either studyId or projectId should be provided.' + ); + } + + /* check permission */ + const roles = await this.permissionCore.getRolesOfUser(requester, studyId); + if (!roles.length) { throw new CoreError(enumCoreErrors.NO_PERMISSION_ERROR, enumCoreErrors.NO_PERMISSION_ERROR); } + + const study = (await this.studyCore.getStudies(requester, studyId))[0]; + + // get all dataVersions that are valid (before/equal the current version) + const availableDataVersions: Array = (study.currentDataVersion === -1 ? [] : study.dataVersions.filter((__unused__el, index) => index <= study.currentDataVersion)).map(el => el.id); + if (versionId === null) { + availableDataVersions.push(null); + } + const standardizations = await this.db.collections.standardizations_collection.aggregate([{ + $sort: { uploadedAt: -1 } + }, { + $match: { dataVersion: { $in: availableDataVersions } } + }, { + $match: { studyId: studyId, type: type ?? /^.*$/ } + }, { + $group: { + _id: { + type: '$type', + field: '$field' + }, + doc: { $first: '$$ROOT' } + } + }, { + $replaceRoot: { newRoot: '$doc' } + }, { + $match: { deleted: null } + } + ]).toArray(); + return standardizations as IStandardization[]; + } + + public async createStandardization(requester: IUserWithoutToken | undefined, studyId, standardization) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + /* check permission */ + const roles = await this.permissionCore.getRolesOfUser(requester, studyId); + if (!roles.length || roles.every(el => el.studyRole !== enumStudyRoles.STUDY_MANAGER)) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + /* check study exists */ + const studySearchResult = await this.db.collections.studies_collection.findOne({ id: studyId, deleted: null }); + if (studySearchResult === null || studySearchResult === undefined) { + throw new CoreError(enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, 'Study does not exist.'); + } + const stdRulesWithId = [...standardization.stdRules]; + stdRulesWithId.forEach(el => { + el.id = uuid(); + }); + const standardizationEntry: IStandardization = { + id: uuid(), + studyId: studyId, + type: standardization.type, + field: standardization.field, + path: standardization.path, + stdRules: stdRulesWithId || [], + joinByKeys: standardization.joinByKeys || [], + dataVersion: null, + metadata: standardization.metadata, + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + } + }; + + await this.db.collections.standardizations_collection.findOneAndUpdate({ studyId: studyId, type: standardization.type, field: standardization.field, dataVersion: null }, { + $set: { ...standardizationEntry } + }, { + upsert: true + }); + return standardizationEntry; + } + + public async deleteStandardization(requester: IUserWithoutToken | undefined, studyId, type, field) { + if (!requester) { + throw new CoreError(enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); + } + /* check permission */ + const roles = await this.permissionCore.getRolesOfUser(requester, studyId); + if (!roles.length) { throw new CoreError(enumCoreErrors.NO_PERMISSION_ERROR, enumCoreErrors.NO_PERMISSION_ERROR); } + /* check study exists */ + const studySearchResult = await this.db.collections.studies_collection.findOne({ id: studyId, deleted: null }); + if (studySearchResult === null || studySearchResult === undefined) { + throw new CoreError(enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, 'Study does not exist.'); + } + + // check type exists + const types: string[] = await this.db.collections.standardizations_collection.distinct('type', { studyId: studyId, deleted: null }); + if (!types.includes(type)) { + throw new CoreError(enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, 'Type does not exist.'); + } + const result = await this.db.collections.standardizations_collection.findOneAndUpdate({ studyId: studyId, field: field, type: type, dataVersion: null }, { + $set: { + id: uuid(), + studyId: studyId, + field: field, + type: type, + dataVersion: null, + uploadedAt: Date.now(), + deleted: Date.now() + } + }, { + upsert: true + }); + return makeGenericResponse(result?.id || ''); + } +} + diff --git a/packages/itmat-cores/src/coreFunc/studyCore.ts b/packages/itmat-cores/src/coreFunc/studyCore.ts new file mode 100644 index 000000000..8321114aa --- /dev/null +++ b/packages/itmat-cores/src/coreFunc/studyCore.ts @@ -0,0 +1,553 @@ +import { ObjectStore } from '@itmat-broker/itmat-commons'; +import { DBType } from '../database/database'; +import { CoreError, FileUpload, IFile, IStudy, IStudyDataVersion, IUserWithoutToken, enumCacheStatus, enumConfigType, enumCoreErrors, enumFileCategories, enumFileTypes, enumStudyRoles, enumUserTypes } from '@itmat-broker/itmat-types'; +import { Filter, UpdateFilter } from 'mongodb'; +import { PermissionCore } from './permissionCore'; +import { v4 as uuid } from 'uuid'; +import { FileCore } from './fileCore'; +import { makeGenericResponse } from '../utils'; + +export class StudyCore { + db: DBType; + objStore: ObjectStore; + permissionCore: PermissionCore; + fileCore: FileCore; + constructor(db: DBType, objStore: ObjectStore, permissionCore: PermissionCore, fileCore: FileCore) { + this.db = db; + this.objStore = objStore; + this.permissionCore = permissionCore; + this.fileCore = fileCore; + } + /** + * Get the info of a study. + * + * @param studyId - The id of the study. + * + * @return IStudy - The object of IStudy. + */ + public async getStudies(requester: IUserWithoutToken | undefined, studyId?: string) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + let studies: IStudy[] = []; + if (requester.type === enumUserTypes.ADMIN) { + studies = studyId ? await this.db.collections.studies_collection.find({ 'id': studyId, 'life.deletedTime': null }).toArray() : + await this.db.collections.studies_collection.find({ 'life.deletedTime': null }).toArray(); + } else { + const roleStudyIds = (await this.permissionCore.getRolesOfUser(requester, requester.id)).map(role => role.studyId); + const query: Filter = { 'life.deletedTime': null }; + if (studyId) { + if (!roleStudyIds.includes(studyId)) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'No permission to access the study.' + ); + } + query.id = studyId; + } else { + query.id = { $in: roleStudyIds }; + } + studies = await this.db.collections.studies_collection.find(query).toArray(); + } + if (studies.length === 0 && studyId) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'No permission to access the study.' + ); + } + return studies; + } + + /** + * Create a study. + * + * @param requester - The id of the requester. + * @param studyName - The name of the study. + * @param description - The description of the study. + * + * @return IStudy - The object of the IStudy. + */ + public async createStudy(requester: IUserWithoutToken | undefined, studyName: string, description?: string, profile?: FileUpload): Promise> { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + if (requester.type !== enumUserTypes.ADMIN) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'Only admin can create a study.' + ); + } + + const studyId = uuid(); + const existing = await this.db.collections.studies_collection.findOne({ + 'name': { $regex: new RegExp('^' + studyName + '$', 'i') }, + 'life.deletedTime': null + }); + if (existing) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Study name already used.' + ); + } + + const studyEntry: IStudy = { + id: studyId, + name: studyName, + currentDataVersion: -1, + dataVersions: [], + description: description ?? '', + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + }, + metadata: {}, + isPublic: false + }; + // create a default study config + await this.db.collections.configs_collection.insertOne({ + id: uuid(), + type: enumConfigType.STUDYCONFIG, + key: studyEntry.id, + properties: { + id: uuid(), + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + }, + metadata: {}, + defaultStudyProfile: null, + defaultMaximumFileSize: 8 * 1024 * 1024 * 1024, // 8 GB, + defaultRepresentationForMissingValue: '99999', + defaultFileBlocks: [], + defaultVersioningKeys: [] + }, + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }); + + await this.db.collections.studies_collection.insertOne(studyEntry); + let fileEntry: IFile | null = null; + if (profile) { + if (!Object.keys(enumFileTypes).includes((profile?.filename?.split('.').pop() || '').toUpperCase())) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'File type not supported.' + ); + } + fileEntry = await this.fileCore.uploadFile(requester, studyId, null, profile, enumFileTypes[(profile.filename.split('.').pop() || '').toUpperCase() as keyof typeof enumFileTypes], enumFileCategories.PROFILE_FILE); + await this.db.collections.studies_collection.findOneAndUpdate({ id: studyId }, { + $set: { + profile: fileEntry.id + } + }, { + returnDocument: 'after' + }); + + return { + ...studyEntry, + profile: fileEntry.id + }; + } + return studyEntry; + } + + /** + * Edit the description of the study. + * + * @param requester - The requester. + * @param studyId - The id of the study. + * @param name - The name of the study. + * @param description - The description of the study. + * @param profile - The profile of the study. + * + * @return IStudy - The object of IStudy + */ + public async editStudy(requester: IUserWithoutToken | undefined, studyId: string, name?: string, description?: string, profile?: FileUpload) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + const roleStudyRoles: enumStudyRoles[] = (await this.permissionCore.getRolesOfUser(requester, requester.id, studyId)).map(role => role.studyRole); + if (requester.type !== enumUserTypes.ADMIN && !roleStudyRoles.includes(enumStudyRoles.STUDY_MANAGER)) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'Only admin or study manager can edit a study.' + ); + } + + const study = await this.db.collections.studies_collection.findOne({ 'id': studyId, 'life.deletedTime': null }); + if (!study) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Study does not exist.' + ); + } + + const setObj: UpdateFilter = {}; + let fileEntry; + if (profile) { + try { + if (!Object.keys(enumFileTypes).includes((profile?.filename?.split('.').pop() || '').toUpperCase())) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'File format not supported' + ); + } + fileEntry = await this.fileCore.uploadFile(requester, studyId, null, profile, enumFileTypes[(profile.filename.split('.').pop() || '').toUpperCase() as keyof typeof enumFileTypes], enumFileCategories.PROFILE_FILE); + setObj['profile'] = fileEntry.id; + } catch (__unused__exception) { + setObj['profile'] = study.profile; + } + } + if (name) { + if (name !== study.name) { + const existing = await this.db.collections.studies_collection.findOne({ 'name': name, 'life.deletedTime': null }); + if (existing) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Study name already used.' + ); + } + } + setObj['name'] = name; + } + if (description) { + setObj['description'] = description; + } + const response = await this.db.collections.studies_collection.findOneAndUpdate({ id: studyId }, { + $set: setObj + }, { + returnDocument: 'after' + }); + return response; + + } + /** + * Edit the visibility of a study. + * + * @param requester - The requester. + * @param studyId - The id of the study. + * @param isPublic - The visibility status of the study. + * + * @return IStudy - The updated study object. + */ + public async editStudyVisibility(requester: IUserWithoutToken | undefined, studyId: string, isPublic: boolean) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + // Check permissions - only admin or study manager can change visibility + const roleStudyRoles: enumStudyRoles[] = (await this.permissionCore.getRolesOfUser(requester, requester.id, studyId)).map(role => role.studyRole); + if (requester.type !== enumUserTypes.ADMIN && !roleStudyRoles.includes(enumStudyRoles.STUDY_MANAGER)) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'Only admin or study manager can change study visibility.' + ); + } + + const study = await this.db.collections.studies_collection.findOne({ 'id': studyId, 'life.deletedTime': null }); + if (!study) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Study does not exist.' + ); + } + + // Update the study visibility + const setObj: UpdateFilter = { + isPublic: isPublic + }; + + const response = await this.db.collections.studies_collection.findOneAndUpdate( + { id: studyId }, + { $set: setObj }, + { returnDocument: 'after' } + ); + + return response; + } + /** + * Delete a study. + * + * @param requester - The id of the requester. + * @param studyId - The id of the study. + * + * @return IGenericResponse - The obejct of IGenericResponse. + */ + public async deleteStudy(requester: IUserWithoutToken | undefined, studyId: string) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + if (requester.type !== 'ADMIN') { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'Only admin can delete a study.' + ); + } + + const study = await this.db.collections.studies_collection.findOne({ 'id': studyId, 'life.deletedTime': null }); + if (!study) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Study does not exist.' + ); + } + + const timestamp = new Date().valueOf(); + /* delete the study */ + await this.db.collections.studies_collection.findOneAndUpdate({ 'id': studyId, 'life.deletedTime': null }, { $set: { 'life.deletedTime': timestamp, 'life.deletedUser': requester } }); + + /* delete all roles related to the study */ + // await this.localPermissionCore.removeRoleFromStudyOrProject({ studyId }); + + /* delete all files belong to the study*/ + await this.db.collections.files_collection.updateMany({ 'studyId': studyId, 'life.deletedTime': null }, { $set: { 'life.deletedTime': timestamp, 'life.deletedUser': requester } }); + + /* delete all fields belong to the study*/ + await this.db.collections.field_dictionary_collection.updateMany({ 'studyId': studyId, 'life.deletedTime': null }, { $set: { 'life.deletedTime': timestamp, 'life.deletedUser': requester } }); + + /* delete all data belong to the study*/ + await this.db.collections.data_collection.updateMany({ 'studyId': studyId, 'life.deletedTime': null }, { $set: { 'life.deletedTime': timestamp, 'life.deletedUser': requester } }); + + /* delete all config belong to the study*/ + await this.db.collections.configs_collection.updateMany({ 'type': enumConfigType.STUDYCONFIG, 'key': studyId, 'life.deletedTime': null }, { $set: { 'life.deletedTime': timestamp, 'life.deletedUser': requester } }); + + return makeGenericResponse(studyId, true, undefined, `Study ${study.name} has been deleted.`); + } + /** + * Create a new data version of the study. + * + * @param requester - The id of the requester. + * @param studyId - The id of the study. + * @param tag - The tag of the study. + * @param dataVersion - The new version of the study. Use float number. + * + * @return IGenericResponse - The object of IGenericResponse. + */ + public async createDataVersion(requester: IUserWithoutToken | undefined, studyId: string, tag: string, dataVersion: string, syncCold?: boolean) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + const roleStudyRoles: enumStudyRoles[] = (await this.permissionCore.getRolesOfUser(requester, requester.id)).map(role => role.studyRole); + if (requester.type !== enumUserTypes.ADMIN && !roleStudyRoles.includes(enumStudyRoles.STUDY_MANAGER)) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'Only admin or study manager can create a study version.' + ); + } + + const study = await this.db.collections.studies_collection.findOne({ 'id': studyId, 'life.deletedTime': null }); + if (!study) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Study does not exist.' + ); + } + + const decimalRegex = /^[0-9]+(\.[0-9]+)?$/; + if (!decimalRegex.test(dataVersion)) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Version must be a float number.' + ); + } + + if (study.dataVersions.map(el => el.version).includes(dataVersion)) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Version has been used.' + ); + } + + const newDataVersionId = uuid(); + + + // update data + const resData = await this.db.collections.data_collection.updateMany({ + studyId: studyId, + dataVersion: null + }, { + $set: { + dataVersion: newDataVersionId + } + }); + // update field + const resField = await this.db.collections.field_dictionary_collection.updateMany({ + studyId: studyId, + dataVersion: null + }, { + $set: { + dataVersion: newDataVersionId + } + }); + + // TODO: invalidate cache + await this.db.collections.cache_collection.updateMany({ + 'keys.studyId': studyId + }, { + $set: { + status: enumCacheStatus.OUTDATED + } + }); + + // TODO: update stds, ontologies + + // TODO: update cold storage + if (syncCold) { + const batchSize = 1000; // Define the batch size for processing + const cursor = this.db.collections.data_collection.find({ studyId: studyId, dataVersion: newDataVersionId }).batchSize(batchSize); + + let bulkOperation = this.db.collections.colddata_collection.initializeUnorderedBulkOp(); + let documentCount = 0; + + while (await cursor.hasNext()) { + const doc = await cursor.next(); + if (!doc) { + break; + } + const filters = { + studyId: studyId, + fieldId: doc.fieldId, + properties: doc.properties + }; + + bulkOperation.find(filters).upsert().update({ + $set: { + id: uuid(), + value: doc.value, + dataVersion: newDataVersionId, + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + }, + metadata: {} + } + }); + + documentCount++; + + // Execute batch if it reaches 1000 operations or if batch size limit is hit + if (documentCount % 1000 === 0) { + await bulkOperation.execute(); + bulkOperation = this.db.collections.colddata_collection.initializeUnorderedBulkOp(); // Re-initialize bulk operation + } + } + + // Execute any remaining operations in bulk + if (documentCount % 1000 !== 0) { + await bulkOperation.execute(); + } + } + + const newDataVersion: IStudyDataVersion = { + id: newDataVersionId, + version: dataVersion, + tag: tag, + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + await this.db.collections.studies_collection.updateOne({ id: studyId }, { + $push: { dataVersions: newDataVersion }, + $inc: { + currentDataVersion: 1 + } + }); + if (resData.modifiedCount === 0 && resField.modifiedCount === 0) { + throw new CoreError( + enumCoreErrors.UNQUALIFIED_ERROR, + 'Nothing to update.' + ); + } else { + return newDataVersion; + } + } + + /** + * Set a data version as the current data version of a study. + * + * @param requester - The requester. + * @param studyId - The id of the study. + * @param dataVersionId - The id of the data version. + * @returns IGenericResponse + */ + public async setDataversionAsCurrent(requester: IUserWithoutToken | undefined, studyId: string, dataVersionId: string) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + /* check privileges */ + const roleStudyRoles: enumStudyRoles[] = (await this.permissionCore.getRolesOfUser(requester, requester.id)).map(role => role.studyRole); + if (requester.type !== enumUserTypes.ADMIN && !roleStudyRoles.includes(enumStudyRoles.STUDY_MANAGER)) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'Only admin or study manager can set a study version.' + ); + } + + const study = await this.db.collections.studies_collection.findOne({ 'id': studyId, 'life.deletedTime': null }); + if (!study) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Study does not exist.' + ); + } + + /* check whether the dataversion exists */ + const selectedataVersionFiltered = study.dataVersions.filter((el) => el.id === dataVersionId); + if (selectedataVersionFiltered.length !== 1) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Data version does not exist.' + ); + } + + /* update the currentversion field in database */ + const versionIdsList = study.dataVersions.map((el) => el.id); + await this.db.collections.studies_collection.findOneAndUpdate({ id: studyId, deleted: null }, { + $set: { currentDataVersion: versionIdsList.indexOf(dataVersionId) } + }, { + returnDocument: 'after' + }); + + return makeGenericResponse(studyId, true, undefined, `Data version ${dataVersionId} has been set as the current version of study ${study.name}.`); + } +} diff --git a/packages/itmat-cores/src/coreFunc/transformationCore.ts b/packages/itmat-cores/src/coreFunc/transformationCore.ts new file mode 100644 index 000000000..e3b87bf7d --- /dev/null +++ b/packages/itmat-cores/src/coreFunc/transformationCore.ts @@ -0,0 +1,769 @@ +import { IAST, IValueVerifier, enumDataTransformationOperation } from '@itmat-broker/itmat-types'; +import { UtilsCore } from './utilsCore'; + +type IDataTransformationClip = Record; + +type IDataTransformationClipArray = IDataTransformationClip[]; + +type IDataTransformationType = IDataTransformationClipArray | IDataTransformationClipArray[]; + +abstract class DataTransformation { + abstract transform(data: IDataTransformationType): IDataTransformationType; +} + +/** + * Group data by keys. + * + * @input A[] + * @OUTPUT A[][] + * + * @param keys - The keys to group by. + * @param skipUnmatch - Whether to skip the ungrouped data. + */ +class tGrouping extends DataTransformation { + protected keys: string[]; + protected skipUnmatch: boolean; + protected utilsCore: UtilsCore; + + constructor(params: { keys: string[], skipUnmatch: boolean }, utilsCore: UtilsCore) { + super(); + this.keys = params.keys; + this.skipUnmatch = params.skipUnmatch; + this.utilsCore = utilsCore; + } + + transform(data: IDataTransformationType): IDataTransformationType { + // Check input type + if (!Array.isArray(data) || (data.length > 0 && Array.isArray(data[0]))) { + throw new Error('Input data must be of type IDataTransformationClipArray (A[]) and not A[][]'); + } + + // Type assertion: after this check, `data` is `IDataTransformationClipArray` + const clipsArray = data as IDataTransformationClipArray; + + // We'll use a map to group the records. The key will be a string representation of the values of the keys. + const groupsMap: Record = {}; + const unmatched: IDataTransformationClipArray[] = []; // This will hold arrays + for (const item of clipsArray) { + // Check if all specified keys exist in the data item + const allKeysExist = this.keys.every(key => this.getValueFromNestedKey(item, key) !== undefined); + + if (!allKeysExist) { + if (this.skipUnmatch) { + continue; // skip this item + } else { + unmatched.push([item]); // wrap the unmatched item in an array + continue; + } + } + + // For each record, we'll generate a key by concatenating the values of the specified keys + const groupKey = this.keys.map(key => String(this.getValueFromNestedKey(item, key))).join('|'); + // If this group doesn't exist yet, we'll create it + if (!groupsMap[groupKey]) { + groupsMap[groupKey] = []; + } + + // We'll add the current item to its group + groupsMap[groupKey].push(item); + } + // Now, combine matched groups and unmatched items + const result = [...Object.values(groupsMap), ...unmatched]; + // Otherwise, return the array of groups + return result as IDataTransformationClipArray[]; + } + + getValueFromNestedKey(obj: Record, key: string): unknown { + const keys = key.split('.'); + return this.findValue(obj, keys); + } + + findValue(obj: Record | unknown, keys: string[]): unknown { + if (keys.length === 0 || obj === undefined || obj === null) { + return obj; + } + + const [firstKey, ...remainingKeys] = keys; + + if (typeof obj === 'object' && obj !== null && firstKey in obj) { + return this.findValue((obj as Record)[firstKey], remainingKeys); + } else { + return undefined; + } + } +} + +/** + * Convert or delete each value of the data. Note, by default, the execution order is: adding keys -> affine -> remove keys + * + * @input A[] + * @output A[] + * + * @param removedKeys - Keys to remove. + * @param addedKeyRules - Keys to add. + * @param rules - Rules to convert the values. + */ +class tAffine extends DataTransformation { + protected removedKeys?: string[]; + protected addedKeyRules?: Array<{ key: IAST, value: IAST }>; + protected rules?: Record; + protected utilsCore: UtilsCore; + + constructor(params: { removedKeys: string[], rules: Record, addedKeyRules: Array<{ key: IAST, value: IAST }> }, utilsCore: UtilsCore) { + super(); + this.removedKeys = params.removedKeys; + this.addedKeyRules = params.addedKeyRules; + this.rules = params.rules; + this.utilsCore = utilsCore; + } + + transform(data: IDataTransformationType): IDataTransformationClipArray { + if (!Array.isArray(data) || (data.length > 0 && Array.isArray(data[0]))) { + throw new Error('Input data must be of type IDataTransformationClipArray (A[]) and not A[][]'); + } + + const affinedData: IDataTransformationClipArray = []; + for (const item of data as IDataTransformationClipArray) { + // Add keys + if (this.addedKeyRules) { + for (const pair of this.addedKeyRules) { + const newKey = this.utilsCore.IASTHelper(pair.key, item) as string; + const newValue = this.utilsCore.IASTHelper(pair.value, item); + item[newKey] = newValue; + } + } + + // Apply rules for affine transformations + if (this.rules) { + for (const key of Object.keys(item)) { + if (this.rules[key]) { + item[key] = this.utilsCore.IASTHelper(this.rules[key], item[key] as number | string | Record); + } + } + } + + // Remove specified keys + if (this.removedKeys) { + for (const key of this.removedKeys) { + delete item[key]; + } + } + + // Add transformed item to the result if it has keys left + if (Object.keys(item).length > 0) { + affinedData.push(item); + } + } + + return affinedData; + } +} + +/** + * Leave one data from a group. + * + * @input A[][] + * @output A[] + * + * @param scoreFormula - The formula to give rank of the data. + * @param isDescend - Whether to rank in descend order. + */ +class tLeaveOne extends DataTransformation { + protected scoreFormula: IAST; + protected isDescend: boolean; + protected utilsCore: UtilsCore; + + constructor(params: { scoreFormula: IAST, isDescend: boolean }, utilsCore: UtilsCore) { + super(); + this.scoreFormula = params.scoreFormula; + this.isDescend = params.isDescend; + this.utilsCore = utilsCore; + } + + transform(data: IDataTransformationType): IDataTransformationType { + if (!Array.isArray(data) || (data.length > 0 && !Array.isArray(data[0]))) { + throw new Error('Input data must be of type A[][] (array of arrays) and not A[]'); + } + + const mergedData: IDataTransformationClipArray = []; + + for (const items of data as IDataTransformationClipArray[]) { + const scores: number[] = []; + for (const item of items) { + scores.push(this.utilsCore.IASTHelper(this.scoreFormula, item)); + } + + const index = this.isDescend ? scores.indexOf(Math.max(...scores)) : scores.indexOf(Math.min(...scores)); + mergedData.push(items[index]); + } + return mergedData; + } +} + +class tJoin extends DataTransformation { + protected reservedKeys: string[]; + protected utilsCore: UtilsCore; + + constructor(params: { reservedKeys: string[] }, utilsCore: UtilsCore) { + super(); + this.reservedKeys = params.reservedKeys; + this.utilsCore = utilsCore; + } + + transform(data: IDataTransformationType): IDataTransformationClipArray { + if (!Array.isArray(data) || (data.length > 0 && !Array.isArray(data[0]))) { + throw new Error('Input data must be of type A[][] (array of arrays) and not A[]'); + } + + const joinedData: IDataTransformationClipArray = []; + for (const items of data as IDataTransformationClipArray[]) { + let obj: IDataTransformationClip = {}; + for (const item of items) { + obj = { + ...obj, + ...item + }; + } + joinedData.push(obj); + } + return joinedData; + } +} + +class tConcat extends DataTransformation { + protected concatKeys: string[]; + protected utilsCore: UtilsCore; + + constructor(params: { concatKeys: string[] }, utilsCore: UtilsCore) { + super(); + this.concatKeys = params.concatKeys; + this.utilsCore = utilsCore; + } + + transform(data: IDataTransformationType): IDataTransformationClipArray { + if (!Array.isArray(data) || (data.length > 0 && !Array.isArray(data[0]))) { + throw new Error('Input data must be of type A[][] (array of arrays) and not A[]'); + } + + const results: IDataTransformationClipArray = []; + data.forEach(array => { + const result: IDataTransformationClip = {}; + + array.forEach((item: IDataTransformationClip) => { + Object.keys(item).forEach(key => { + if (this.concatKeys.includes(key)) { + if (!result[key]) { + result[key] = []; + } + (result[key] as unknown[]).push(item[key]); + } else { + if (!result[key]) { + result[key] = item[key]; + } + } + }); + }); + + results.push(result); + }); + + return results; + } +} + +/** + * Deconcat values into an array. + * + * @input A[] + * @output A[][] + */ +class tDeconcat extends DataTransformation { + protected deconcatKeys: string[]; + protected matchMode: 'combinations' | 'sequential'; + protected utilsCore: UtilsCore; + + constructor(params: { deconcatKeys: string[], matchMode?: 'combinations' | 'sequential' }, utilsCore: UtilsCore) { + super(); + this.deconcatKeys = params.deconcatKeys; + this.matchMode = params.matchMode || 'combinations'; + this.utilsCore = utilsCore; + } + + private cartesianProduct(arr: unknown[][]): unknown[][] { + return arr.reduce((a: unknown[][], b: unknown[]): unknown[][] => { + return a.flatMap((x: unknown[]): unknown[][] => + b.map((y: unknown): unknown[] => x.concat([y])) + ); + }, [[]]); + } + + transform(data: IDataTransformationClipArray): IDataTransformationClipArray[] { + const results: IDataTransformationClipArray[] = []; + + data.forEach((item: IDataTransformationClip) => { + const subResults: IDataTransformationClip[] = []; + + if (this.matchMode === 'combinations') { + const arraysToDeconcat: unknown[][] = this.deconcatKeys.map(key => item[key] as unknown[] || []); + + const product = this.cartesianProduct(arraysToDeconcat); + + product.forEach(combination => { + const newObj = this.createDeconcatObject(item, combination); + subResults.push(newObj); + }); + } else if (this.matchMode === 'sequential') { + const maxLength = Math.max(...this.deconcatKeys.map(key => (item[key] as unknown[])?.length || 0)); + + for (let i = 0; i < maxLength; i++) { + const sequentialValues = this.deconcatKeys.map(key => (item[key] as unknown[])?.[i]); + const newObj = this.createDeconcatObject(item, sequentialValues); + subResults.push(newObj); + } + } + + results.push(subResults); + }); + + return results; + } + + private createDeconcatObject(item: IDataTransformationClip, values: unknown[]): IDataTransformationClip { + const newObj: IDataTransformationClip = {}; + + this.deconcatKeys.forEach((key, index) => { + newObj[key] = values[index]; + }); + + Object.keys(item).forEach((key: string) => { + if (!this.deconcatKeys.includes(key)) { + newObj[key] = item[key]; + } + }); + + return newObj; + } +} +/** + * Filter the data. + * + * @input A[] | A[][] + * @output A[] | A[][] + */ +class tFilter extends DataTransformation { + protected filters: Record; + protected utilsCore: UtilsCore; + + constructor(params: { filters: Record }, utilsCore: UtilsCore) { + super(); + this.filters = params.filters; + this.utilsCore = utilsCore; + } + + transform(data: IDataTransformationType): IDataTransformationType { + if (Array.isArray(data) && data.length && Array.isArray(data[0])) { + return (data as IDataTransformationClipArray[]).map(subArray => + subArray.filter(item => this.isValidItem(item)) + ); + } else { + return (data as IDataTransformationClipArray).filter(item => this.isValidItem(item)); + } + } + + private isValidItem(data: IDataTransformationClip): boolean { + const x = Object.keys(this.filters).some(key => { + return this.filters[key].every(el => { + return (this.utilsCore.validValueWithVerifier(data as number | string | IDataTransformationClip, el)); + }); + }); + return x; + } +} + +/** + * Count the data. This should be used after grouping. + * + * @input A[][] + * @output { count: ..., ...}[] + */ +class tCount extends DataTransformation { + protected addedKeyRules?: Array<{ key: IAST, value: IAST }>; + protected utilsCore: UtilsCore; + + constructor(params: { addedKeyRules: Array<{ key: IAST, value: IAST }> }, utilsCore: UtilsCore) { + super(); + this.addedKeyRules = params.addedKeyRules; + this.utilsCore = utilsCore; + } + + transform(data: IDataTransformationType): IDataTransformationClipArray { + if (!Array.isArray(data) || (data.length > 0 && !Array.isArray(data[0]))) { + throw new Error('Input data must be of type A[][] (array of arrays) and not A[]'); + } + const mergerArrays: IDataTransformationClipArray = []; + for (const group of data as IDataTransformationClipArray[]) { + const item: IDataTransformationClip = { count: group.length }; + if (this.addedKeyRules) { + for (const pair of this.addedKeyRules) { + item[this.utilsCore.IASTHelper(pair.key, group[0]) as string] = this.utilsCore.IASTHelper(pair.value, group[0]); + } + } + mergerArrays.push(item); + } + return mergerArrays; + } +} + +/** + * Split a data into multiple data. + * + * @input A[] + * @output A[][] + * + * @param sharedKeys - The kyes to kept in the new data. + * @param targetKeyGroups - The keys to add with the shared keys. + */ +class tDegroup extends DataTransformation { + protected sharedKeys: string[]; + protected targetKeyGroups: string[][]; + protected utilsCore: UtilsCore; + + constructor(params: { sharedKeys: string[], targetKeyGroups: string[][] }, utilsCore: UtilsCore) { + super(); + this.sharedKeys = params.sharedKeys; + this.targetKeyGroups = params.targetKeyGroups; + this.utilsCore = utilsCore; + } + + transform(data: IDataTransformationType): IDataTransformationClipArray[] { + if (!Array.isArray(data) || (data.length > 0 && Array.isArray(data[0]))) { + throw new Error('Input data must be of type IDataTransformationClipArray (A[]) and not A[][]'); + } + + const splitData: IDataTransformationClipArray[] = []; + for (const item of data as IDataTransformationClipArray) { + const saved: IDataTransformationClipArray = []; + for (let i = 0; i < this.targetKeyGroups.length; i++) { + const obj: IDataTransformationClip = {}; + for (const key of this.sharedKeys) { + obj[key] = item[key]; + } + for (const key of this.targetKeyGroups[i]) { + obj[key] = item[key]; + } + saved.push(obj); + } + splitData.push(saved); + } + return splitData; + } +} +/** + * Flatten an object. Keys within the object will be keys in the data clip. + * + * @input A[] | A[][] + * @output A[] | A[][] + * + * @param keepFlattened - Whether to keep the values from the object if conflicts. + * @param flattenedKey - The key to flatten. + * @param keepFlattenedKey - Whether to keep the flattened key. + */ +class tFlatten extends DataTransformation { + protected keepFlattened: boolean; + protected flattenedKey: string; + protected keepFlattenedKey: boolean; + protected utilsCore: UtilsCore; + + constructor(params: { keepFlattened: boolean, flattenedKey: string, keepFlattenedKey: boolean }, utilsCore: UtilsCore) { + super(); + this.keepFlattened = params.keepFlattened; + this.flattenedKey = params.flattenedKey; + this.keepFlattenedKey = params.keepFlattenedKey; + this.utilsCore = utilsCore; + } + + transform(data: IDataTransformationType): IDataTransformationType { + if (Array.isArray(data) && data.length > 0 && Array.isArray(data[0])) { + return (data as IDataTransformationClipArray[]).map(group => group.map(item => this.flattenItem(item))); + } else if (Array.isArray(data)) { + return (data as IDataTransformationClipArray).map(item => this.flattenItem(item)); + } else { + throw new Error('Invalid input format for tFlatten transform.'); + } + } + + private flattenItem(item: IDataTransformationClip): IDataTransformationClip { + const objectToFlatten = item[this.flattenedKey]; + if (typeof objectToFlatten !== 'object' || objectToFlatten === null) { + return item; + } + + const flattenedItem: IDataTransformationClip = { ...item }; + for (const key in objectToFlatten) { + if (!this.keepFlattened || !(key in item)) { + flattenedItem[key] = objectToFlatten[key]; + } + } + + if (!this.keepFlattenedKey) { + delete flattenedItem[this.flattenedKey]; + } + + return flattenedItem; + } +} + + + + +export class DataTransformationCore { + protected utilsCore: UtilsCore; + constructor(utilsCore: UtilsCore) { + this.utilsCore = utilsCore; + } + + public transformationRegistry = { + GROUP: tGrouping, + AFFINE: tAffine, + LEAVEONE: tLeaveOne, + CONCAT: tConcat, + DECONCAT: tDeconcat, + JOIN: tJoin, + DEGROUP: tDegroup, + FILTER: tFilter, + FLATTEN: tFlatten, + COUNT: tCount + }; + + public transformationCompose(data: IDataTransformationClipArray, transformations: Array<{ operationName: string, params: unknown }>): IDataTransformationType { + return transformations.reduce((currentData, transformation) => { + const TransformationClass = this.transformationRegistry[transformation.operationName]; + + if (!TransformationClass) { + throw new Error(`Transformation ${transformation.operationName} is not registered.`); + } + + const instance = new TransformationClass(transformation.params, this.utilsCore); + return instance.transform(currentData); + }, data); + } + + public transformationAggregate( + data: IDataTransformationClipArray, + transformationsAgg: Record> + ): Record { + if (!transformationsAgg || Object.keys(transformationsAgg).length === 0) { + return { raw: data }; + } + + const aggregation: Record = {}; + for (const key of Object.keys(transformationsAgg)) { + const transformedData = this.transformationCompose(data, transformationsAgg[key]); + + // Ensure the final result is always a flat array + // aggregation[key] = this.flattenArray(transformedData); + aggregation[key] = transformedData; + } + return aggregation; + } + + private flattenArray(data: IDataTransformationType): IDataTransformationClipArray { + if (Array.isArray(data) && Array.isArray(data[0])) { + return (data as IDataTransformationClipArray[]).flat(); + } + return data as IDataTransformationClipArray; + } +} + +/** + * Examples of data transformation + 1. Generate a simplified version of data: + { + "aggregation": { + "device": [ + { + "params": { + "keys": [ + "properties.Device Type", + "properties.Participant ID", + "properties.Device ID", + "properties.End Date", + "properties.Start Date" + ], + "skipUnmatch": true + }, + "operationName": "Group" + }, + { + "params": { + "scoreFormula": { + "operator": null, + "type": "VARIABLE", + "value": "life.createdTime", + "children": null, + "parameters": {} + }, + "isDescend": true + }, + "operationName": "LeaveOne" + }, + { + "operationName": "Filter", + "params": { + "filters": { + "deleted": { + "formula": { + "value": "life.deletedTime", + "operation": null, + "type": "VARIABLE", + "parameter": {}, + "children": null + }, + "value": "", + "condition": "general:=null", + "parameters": {} + } + } + } + }, + { + "operationName": "Affine", + "params": { + "removedKeys": [ + "_id", + "id", + "studyId", + "dataVersion", + "life", + "metadata" + ], + "addedKeyRules": [], + "rules": {} + } + } + ], + "clinical": [ + { + "operationName": "Group", + "params": { + "keys": [ + "properties.Visit ID", + "properties.Participant ID", + "fieldId" + ], + "skipUnmatch": true + } + }, + { + "operationName": "LeaveOne", + "params": { + "scoreFormula": { + "operator": null, + "children": null, + "type": "VARIABLE", + "value": "life.createdTime", + "parameters": {} + }, + "isDescend": true + } + }, + { + "params": { + "filters": { + "deleted": { + "parameters": {}, + "value": "", + "condition": "general:=null", + "formula": { + "parameter": {}, + "value": "life.deletedTime", + "operation": null, + "children": null, + "type": "VARIABLE" + } + } + } + }, + "operationName": "Filter" + }, + { + "operationName": "Affine", + "params": { + "removedKeys": [ + "_id", + "id", + "studyId", + "dataVersion", + "life", + "metadata" + ], + "addedKeyRules": [], + "rules": {} + } + }, + { + "operationName": "Flatten", + "params": { + "keepFlattened": true, + "flattenedKey": "properties", + "keepFlattenedKey": false + } + }, + { + "operationName": "Affine", + "params": { + "removedKeys": [ + "fieldId", + "value" + ], + "addedKeyRules": [ + { + "key": { + "type": "VARIABLE", + "operator": null, + "value": "fieldId", + "parameters": {}, + "children": null + }, + "value": { + "type": "VARIABLE", + "operator": null, + "value": "value", + "parameters": {}, + "children": null + } + } + ], + "rules": {} + } + }, + { + "operationName": "Group", + "params": { + "keys": [ + "Participant ID", + "Visit ID" + ], + "skipUnMatch": false + } + }, + { + "operationName": "Join", + "params": { + "reservedKeys": [ + "Participant ID", + "Visit ID" + ] + } + } + ] + }, + "useCache": false, + "studyId": "96f17282-e0a3-43d3-8f38-326949b786ef", + "versionId": null, + "forceUpdate": false +} + + 2. Generate data standardization pipeline: + + */ \ No newline at end of file diff --git a/packages/itmat-cores/src/coreFunc/userCore.ts b/packages/itmat-cores/src/coreFunc/userCore.ts new file mode 100644 index 000000000..c67ce7c25 --- /dev/null +++ b/packages/itmat-cores/src/coreFunc/userCore.ts @@ -0,0 +1,1589 @@ +import { CoreError, FileUpload, IFile, IPubkey, IResetPasswordRequest, IUser, IUserWithoutToken, defaultSettings, enumConfigType, enumCoreErrors, enumDriveNodeTypes, enumFileCategories, enumFileTypes, enumUserTypes } from '@itmat-broker/itmat-types'; +import { DBType } from '../database/database'; +import { Logger, Mailer, ObjectStore } from '@itmat-broker/itmat-commons'; +import { IConfiguration, makeGenericResponse, rsakeygen, rsaverifier, tokengen } from '../utils'; +import { FileCore } from './fileCore'; +import { ConfigCore } from './configCore'; +import { v4 as uuid } from 'uuid'; +import bcrypt from 'bcrypt'; +import * as mfa from '../utils/mfa'; +import QRCode from 'qrcode'; +import tmp from 'tmp'; +import { UpdateFilter } from 'mongodb'; +import * as pubkeycrypto from '../utils/pubkeycrypto'; +import crypto from 'crypto'; +import fs from 'fs'; + +export class UserCore { + db: DBType; + mailer: Mailer; + config: IConfiguration; + objStore: ObjectStore; + fileCore: FileCore; + configCore: ConfigCore; + systemSecret: { publickey: string, privatekey: string }; + constructor(db: DBType, mailer: Mailer, config: IConfiguration, objStore: ObjectStore) { + this.db = db; + this.mailer = mailer; + this.config = config; + this.objStore = objStore; + this.fileCore = new FileCore(db, objStore); + this.configCore = new ConfigCore(db); + + // Initialize with empty strings, will be populated in init() + this.systemSecret = { + publickey: '', + privatekey: '' + }; + + // Initialize synchronously to avoid async constructor + void this.initialize(); + } + private async initialize(): Promise { + try { + // Load the system secret key pairs + this.systemSecret = { + publickey: await this.loadKey(this.config.systemKey['pubkey']), + privatekey: await this.loadKey(this.config.systemKey['privkey']) + }; + Logger.log('System keys loaded successfully'); + } catch (error) { + // Handle error but allow system to start + Logger.error(`Failed to load system keys: ${error}`); + this.systemSecret = { + publickey: '', + privatekey: '' + }; + } + } + + /** + * Load key from text content or file path. + * @param keyPathOrContent - Either the key content or a file path to the key. + * @returns The key as a string. + */ + private async loadKey(keyPathOrContent: string): Promise { + try { + if (!keyPathOrContent) { + Logger.warn('Empty key path or content provided'); + return ''; + } + + if (keyPathOrContent.includes('-----BEGIN')) { + // Key is provided as direct content + return keyPathOrContent; + } else { + // Key is provided as a file path, read from file + try { + return fs.readFileSync(keyPathOrContent, 'utf8'); + } catch (fileError) { + Logger.warn(`Could not read key file at ${keyPathOrContent}: ${fileError}`); + return ''; + } + } + } catch (error) { + Logger.warn(`Error processing key: ${error}`); + return ''; + } + } + + /** + * Get a user. One of the parameters should not be null, we will find users by the following order: usreId, username, email. + * + * @param requester - The requester. + * @param userId - The id of the user. + * @param username - The username of the user. + * @param email - The email of the user. + * + * @return IUserWithoutToken | null - Thu user object; or null if the user does not exist. + */ + public async getUser(requester: IUserWithoutToken | undefined, userId?: string, username?: string, email?: string): Promise { + if (!userId && !username && !email) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'At least one of the parameters should not be empty.' + ); + } + if (!requester || (requester.type !== enumUserTypes.ADMIN && ((userId && requester.id !== userId) || (username && requester.username !== username) || (email && requester.email !== email)))) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + + let user: IUserWithoutToken | null = null; + if (userId) { + user = await this.db.collections.users_collection.findOne({ 'id': userId, 'life.deletedTime': null }, { projection: { password: 0, otpSecret: 0 } }); + } else if (username) { + user = await this.db.collections.users_collection.findOne({ 'username': username, 'life.deletedTime': null }, { projection: { password: 0, otpSecret: 0 } }); + } else if (email) { + user = await this.db.collections.users_collection.findOne({ 'email': email, 'life.deletedTime': null }, { projection: { password: 0, otpSecret: 0 } }); + } else { + return null; + } + + if (!user) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'User does not exist.' + ); + } + return user; + } + + /** + * Get all users. + * + * @param requester - The id of the requester. + * @param includeDeleted - Whether to include users that have been deleted. + * + * @return IUserWithoutToken[] - The list of users. + */ + public async getUsers(requester: IUserWithoutToken | undefined, includeDeleted?: boolean): Promise { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + + if (requester?.type === enumUserTypes.ADMIN) { + return includeDeleted ? await this.db.collections.users_collection.find({}).toArray() : await this.db.collections.users_collection.find({ 'life.deletedTime': null }).toArray(); + } else { + // we return users who share files to this user + const userIds = Array.from(new Set((await this.db.collections.drives_collection.find({ 'sharedUsers.iid': requester?.id }).toArray()).map(el => el.managerId))); + return await this.db.collections.users_collection.find({ id: { $in: userIds } }).toArray(); + } + } + + /** + * Validate the token from the reset password request. + * + * @param encryptedEmail - The encrypted email. + * @param token - The token for resetting password. + * @returns IGenericResponse + */ + public async validateResetPassword(encryptedEmail: string, token: string) { + /* decrypt email */ + const salt = makeAESKeySalt(token); + const iv = makeAESIv(token); + let email; + try { + email = await decryptEmail(this.config.aesSecret, encryptedEmail, salt, iv); + } catch (__unused__exception) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Token is not valid.' + ); + } + + /* check whether username and token is valid */ + /* not changing password too in one step (using findOneAndUpdate) because bcrypt is costly */ + const TIME_NOW = new Date().valueOf(); + const ONE_HOUR_IN_MILLISEC = 60 * 60 * 1000; + const user: IUser | null = await this.db.collections.users_collection.findOne({ + email, + 'resetPasswordRequests': { + $elemMatch: { + id: token, + timeOfRequest: { $gt: TIME_NOW - ONE_HOUR_IN_MILLISEC }, + used: false + } + }, + 'life.deletedTime': null + }); + if (!user) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'User does not exist.' + ); + } + return makeGenericResponse(user.id, true); + } + + /** + * Create a user. + * + * @param username - The username of the user, should be unique. + * @param email - The email address of the user. + * @param firstname - The first name of the user. + * @param lastname - The last name of the user. + * @param organisation - The id of the user's organisation. Should be one of the organisaiton in the database. + * @param type - The user type of the user. + * @param emailNotificationsActivated - Whether email notification service is activared. + * @param password - The password of the user, should be hashed. + * @param profile - The profile of the user. + * @param description - The description of the user. + * @param requester - The id of the requester. + * + * @return Partial - The object of IUser. Remove private information. + */ + public async createUser(requester: IUserWithoutToken | undefined, username: string, email: string, firstname: string, lastname: string, organisation: string, type: enumUserTypes, emailNotificationsActivated: boolean, password: string, profile?: FileUpload, description?: string): Promise> { + /* check email is valid form */ + if (!/^([a-zA-Z0-9_\-.]+)@([a-zA-Z0-9_\-.]+)\.([a-zA-Z]{2,5})$/.test(email)) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Email is not the right format.' + ); + } + + /* check password validity */ + if (password && !passwordIsGoodEnough(password)) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Password has to be at least 8 character long.' + ); + } + + /* check that username and password dont have space */ + if (username.indexOf(' ') !== -1 || password.indexOf(' ') !== -1) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Username or password cannot have spaces.' + ); + } + /* randomly generate a secret for Time-based One Time Password*/ + const otpSecret = mfa.generateSecret(); + + const user = await this.db.collections.users_collection.findOne({ + $or: [ + { username: username }, + { email: email } + ] + }); + if (user) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Username or email already exists.' + ); + } + const org = await this.db.collections.organisations_collection.findOne({ 'id': organisation, 'life.deletedTime': null }); + if (!org) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Organisation does not exist.' + ); + } + // fetch the config file + const userConfig = defaultSettings.userConfig; + + const userId: string = uuid(); + const hashedPassword: string = await bcrypt.hash(password, this.config.bcrypt.saltround); + const expiredAt = Date.now() + 86400 * 1000 /* millisec per day */ * (userConfig.defaultUserExpiredDays); + + const entry: IUser = { + id: userId, + username: username, + email: email, + firstname: firstname, + lastname: lastname, + organisation: organisation, + type: type, + emailNotificationsActivated: emailNotificationsActivated, + emailNotificationsStatus: { + expiringNotification: false + }, + resetPasswordRequests: [], + password: hashedPassword, + otpSecret: otpSecret, + profile: undefined, + description: description ?? '', + expiredAt: expiredAt, + life: { + createdTime: Date.now(), + createdUser: requester?.id ?? userId, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + let fileEntry: IFile; + if (profile) { + if (!Object.keys(enumFileTypes).includes((profile?.filename?.split('.').pop() || '').toUpperCase())) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'File type not supported.' + ); + } + fileEntry = await this.fileCore.uploadFile(entry, null, userId, profile, + enumFileTypes[(profile.filename.split('.').pop() || '').toUpperCase() as keyof typeof enumFileTypes], enumFileCategories.PROFILE_FILE); + entry.profile = fileEntry?.id; + } + await this.db.collections.users_collection.insertOne(entry); + + const driveId = uuid(); + await this.db.collections.drives_collection.insertOne({ + id: driveId, + path: [driveId], + restricted: true, + name: 'My Drive', + description: 'This is your own drive.', + fileId: null, + type: enumDriveNodeTypes.FOLDER, + parent: null, + children: [], + sharedUsers: [], + life: { + createdTime: Date.now(), + createdUser: userId, + deletedTime: null, + deletedUser: null + }, + metadata: {}, + managerId: userId + }); + + // add a default user config + await this.db.collections.configs_collection.insertOne({ + id: uuid(), + type: enumConfigType.USERCONFIG, + key: userId, + life: { + createdTime: Date.now(), + createdUser: userId, + deletedTime: null, + deletedUser: null + }, + metadata: {}, + properties: userConfig + }); + + /* send email to the registered user */ + // get QR Code for the otpSecret. + const oauth_uri = `otpauth://totp/${this.config.appName}:${username}?secret=${otpSecret}&issuer=Data%20Science%20Institute`; + const tmpobj = tmp.fileSync({ mode: 0o644, prefix: 'qrcodeimg-', postfix: '.png' }); + + QRCode.toFile(tmpobj.name, oauth_uri, {}, function (err) { + if (err) { + throw new CoreError( + enumCoreErrors.UNQUALIFIED_ERROR, + err.message + ); + } + }); + + const attachments = [{ filename: 'qrcode.png', path: tmpobj.name, cid: 'qrcode_cid' }]; + await this.mailer.sendMail({ + from: `${this.config.appName} <${this.config.nodemailer.auth.user}>`, + to: email, + subject: `[${this.config.appName}] Registration Successful`, + html: ` +

+ Dear ${firstname}, +

+

+ Welcome to the ${this.config.appName} data portal!
+ Your username is ${username}.
+

+

+ To login you will need to use a MFA authenticator app for one time passcode (TOTP).
+ Scan the QRCode below in your MFA application of choice to configure it:
+ QR code
+ If you need to type the token in use ${otpSecret.toLowerCase()} +

+
+

+ The ${this.config.appName} Team. +

+ `, + attachments: attachments + }); + tmpobj.removeCallback(); + + return entry; + } + + /** + * Edit an existing user. Note, this function will use all default values, so if you want to keep some fields the same, you need to first fetch the original values as the inputs. + * + * @param requester - The id of the requester. + * @param userId - The id of the user. + * @param username - The username of the user, should be unique. + * @param email - Optional. The emailAddress of the user. + * @param firstname - Optional. The first name of the user. + * @param lastname - Optional. The last name of the user. + * @param organisation - Optional. The id of the user's organisation. Should be one of the organisaiton in the database. + * @param type - Optional. The user type of the user. + * @param emailNotificationsActivated - Optional. Whether email notification service is activared. + * @param password - Optional. The password of the user, should be hashed. + * @param otpSecret - Optional. The otp secret of the user. + * @param profile - Optional. The image of the profile of the user. Could be null. + * @param description - Optional. The description of the user. + * @param expiredAt - Optional. The expired timestamps of the user. + * + * @return Partial - The object of IUser. Remove private information. + */ + public async editUser(requester: IUserWithoutToken | undefined, userId: string, username?: string, email?: string, firstname?: string, lastname?: string, organisation?: string, type?: enumUserTypes, emailNotificationsActivated?: boolean, password?: string, otpSecret?: string, profile?: FileUpload, description?: string, expiredAt?: number): Promise> { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + if (requester.type !== enumUserTypes.ADMIN && requester.id !== userId) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'User can only edit his/her own account.' + ); + } + + if (requester.type !== enumUserTypes.ADMIN && (type || expiredAt)) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'Standard user can not change their type, expiration time. Please contact admins for help.' + ); + } + + if ((password || otpSecret) && requester.id !== userId) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'User can only edit his/her own account.' + ); + } + + if (password && !passwordIsGoodEnough(password)) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Password has to be at least 8 character long.' + ); + } + + /* check that username and password dont have space */ + if ((username && username.indexOf(' ') !== -1) || (password && password.indexOf(' ') !== -1)) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Username or password cannot have spaces.' + ); + } + + /* check email is valid form */ + if (email && !/^([a-zA-Z0-9_\-.]+)@([a-zA-Z0-9_\-.]+)\.([a-zA-Z]{2,5})$/.test(email)) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Email is not the right format.' + ); + } + + if (expiredAt && expiredAt < Date.now()) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Cannot set to a previous time.' + ); + } + + + const user = await this.db.collections.users_collection.findOne({ 'id': userId, 'life.deletedTime': null }); + if (!user) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'User does not exist.' + ); + } + const setObj: UpdateFilter = {}; + if (username && username !== user.username) { + const existUsername = await this.db.collections.users_collection.findOne({ 'username': username, 'life.deletedTime': null }); + if (existUsername) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Username already used.' + ); + } + setObj['username'] = username; + } + + if (email && email !== user.email) { + const existEmail = await this.db.collections.users_collection.findOne({ 'email': email, 'life.deletedTime': null }); + if (existEmail) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Email already used.' + ); + } + setObj['email'] = email; + } + + if (firstname) { + setObj['firstname'] = firstname; + } + + if (lastname) { + setObj['lastname'] = lastname; + } + + if (description) { + setObj['description'] = description; + } + + if (emailNotificationsActivated) { + setObj['emailNotificationsActivated'] = emailNotificationsActivated; + } + + if (organisation) { + const org = await this.db.collections.organisations_collection.findOne({ 'id': organisation, 'life.deletedTime': null }); + if (!org) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Organisation does not exist.' + ); + } + setObj['organisation'] = organisation; + } + + if (password) { + const hashedPassword: string = await bcrypt.hash(password, this.config.bcrypt.saltround); + if (await bcrypt.compare(password, user.password)) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'You need to select a new password.' + ); + } + setObj['password'] = hashedPassword; + } + + if (otpSecret) { + setObj['otpSecret'] = otpSecret; + } + + let fileEntry; + if (profile) { + if (!Object.keys(enumFileTypes).includes((profile?.filename?.split('.').pop() || '').toUpperCase())) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'File type not supported.' + ); + } + fileEntry = await this.fileCore.uploadFile(requester, null, user.id, profile, enumFileTypes[(profile.filename.split('.').pop() || '').toUpperCase() as keyof typeof enumFileTypes], enumFileCategories.PROFILE_FILE); + setObj['profile'] = fileEntry.id; + } + + if (expiredAt) { + setObj['expiredAt'] = expiredAt; + } + + const result = await this.db.collections.users_collection.findOneAndUpdate({ id: user.id }, { + $set: setObj + }, { + returnDocument: 'after' + }); + if (!result) { + throw new CoreError( + enumCoreErrors.DATABASE_ERROR, + enumCoreErrors.DATABASE_ERROR + ); + } else { + if (expiredAt) { + /* send email to client */ + await this.mailer.sendMail(formatEmailRequestExpiryDateNotification({ + config: this.config, + to: result.email, + username: result.username + })); + } + return result; + } + } + + /** + * Delete an user. + * + * @param requester - The requester. + * @param userId - The id of the user. + * + * @return IGenericResponse - General response. + */ + public async deleteUser(requester: IUserWithoutToken | undefined, userId: string) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + /* Admins can delete anyone, while general user can only delete themself */ + if (!(requester.type === enumUserTypes.ADMIN) && !(requester.id === userId)) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'User can only delete his/her own account.' + ); + } + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + /* Admins can delete anyone, while general user can only delete themself */ + if (!(requester.type === enumUserTypes.ADMIN) && !(requester.id === userId)) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + enumCoreErrors.NO_PERMISSION_ERROR + ); + } + const user = await this.db.collections.users_collection.findOne({ 'id': userId, 'life.deletedTime': null }); + if (!user) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'User does not exist.' + ); + } + + const session = this.db.client.startSession(); + session.startTransaction(); + try { + /* delete the user */ + await this.db.collections.users_collection.findOneAndUpdate({ 'id': userId, 'life.deletedTime': null }, { $set: { 'life.deletedTime': Date.now(), 'life.deletedUser': requester.id, 'password': 'DeletedUserDummyPassword', 'otpSecret': 'DeletedUserDummpOtpSecret' } }, { returnDocument: 'after' }); + + /* delete all user records in roles related to the study */ + await this.db.collections.roles_collection.updateMany({ + 'life.deletedTime': null, + 'users': userId + }, { + $pull: { users: userId } + }); + + await session.commitTransaction(); + await session.endSession(); + return makeGenericResponse(userId, true, undefined, `User ${user.username} has been deleted.`); + } catch (error) { + // If an error occurred, abort the whole transaction and + // undo any changes that might have happened + await session.abortTransaction(); + await session.endSession(); + throw new CoreError( + enumCoreErrors.DATABASE_ERROR, + String(error) + ); + } + } + + /** + * Get keys of a user. + * + * @param requester - The requester. + * @param userId - The id of the user. + */ + public async getUserKeys(requester: IUserWithoutToken | undefined, userId: string) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + if (requester.type !== enumUserTypes.ADMIN && requester.id !== userId) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'User can only get his/her own keys.' + ); + } + return await this.db.collections.pubkeys_collection.find({ 'associatedUserId': userId, 'life.deletedTime': null }).toArray(); + } + + /** + * Generate a key pair for a user. + * + * @returns - The key pair. + */ + public async keyPairGenwSignature() { + // Generate RSA key-pair with Signature for robot user + const keyPair = pubkeycrypto.rsakeygen(); + //default message = hash of the public key (SHA256) + const messageToBeSigned = pubkeycrypto.hashdigest(keyPair.publicKey); + const signature = pubkeycrypto.rsasigner(keyPair.privateKey, messageToBeSigned); + + return { privateKey: keyPair.privateKey, publicKey: keyPair.publicKey, signature: signature }; + } + + /** + * Sign a message with a private key. + * + * @param privateKey - The private key. + * @param message - The message to be signed. + * + * @returns - The signature. + */ + public async rsaSigner(privateKey, message) { + let messageToBeSigned; + privateKey = privateKey.replace(/\\n/g, '\n'); + if (message === undefined) { + //default message = hash of the public key (SHA256) + try { + const reGenPubkey = pubkeycrypto.reGenPkfromSk(privateKey); + messageToBeSigned = pubkeycrypto.hashdigest(reGenPubkey); + } catch (__unused__exception) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Error: private-key incorrect!' + ); + } + + } else { + messageToBeSigned = message; + } + const signature = pubkeycrypto.rsasigner(privateKey, messageToBeSigned); + return { signature: signature }; + } + + /** + * Register a pubkey to a user. + * + * @param requester - The id of the requester. + * @param pubkey - The public key. + * @param signature - The signature of the key. + * @param associatedUserId - The user whom to attach the publick key to. + * + * @return IPubkey - The object of ther registered key. + */ + public async registerPubkey(requester: IUserWithoutToken | undefined, pubkey: string, hashedPrivateKey: string | undefined, associatedUserId: string): Promise { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + if (requester.type !== enumUserTypes.ADMIN && requester.id !== associatedUserId) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'User can only register his/her own public key.' + ); + } + + // refine the public-key parameter from browser + pubkey = pubkey.replace(/\\n/g, '\n'); + const alreadyExist = await this.db.collections.pubkeys_collection.findOne({ pubkey, 'life.deletedTime': null }); + if (alreadyExist) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'This public-key has already been registered.' + ); + } + + const user = await this.db.collections.users_collection.findOne({ 'id': requester.id, 'life.deletedTime': null }); + if (!user) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'User does not exist.' + ); + } + + /* Generate a public key-pair for generating and authenticating JWT access token later */ + const keypair = rsakeygen(); + + const entry: IPubkey = { + id: uuid(), + pubkey: pubkey, + hashedPrivateKey: hashedPrivateKey, + associatedUserId: associatedUserId, + jwtPubkey: keypair.publicKey, + jwtSeckey: keypair.privateKey, + refreshCounter: 0, + life: { + createdTime: Date.now(), + createdUser: requester.id, + deletedTime: null, + deletedUser: null + }, + challenge: null, + lastUsedTime: null, + metadata: {} + }; + + await this.db.collections.pubkeys_collection.insertOne(entry); + return entry; + } + /** + * Generate a random challenge. + * + * @param length - The length of the challenge. + * @returns - The challenge. + */ + public generateChallenge(length) { + // Generate a random buffer of the desired byte length + const randomBytes = crypto.randomBytes(length / 2); // `length / 2` to get a hex string of desired length + // Convert the buffer to a hex string and return it + return randomBytes.toString('hex').slice(0, length); + } + + /** + * Request an access token. + * + * @param username - The username. + * @param pubkeyKey - The public key. + * @returns - The challenge. + */ + public async requestAccessToken(username: string, hashedPrivateKey: string) { + const user = await this.db.collections.users_collection.findOne({ 'username': username, 'life.deletedTime': null }); + if (!user) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'This public-key has not been registered yet.' + ); + } + const pubkeyrec = await this.db.collections.pubkeys_collection.findOne({ 'associatedUserId': user.id, 'hashedPrivateKey': hashedPrivateKey, 'life.deletedTime': null }); + + if (!pubkeyrec) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'This public-key has not been registered yet.' + ); + } + const challenge = this.generateChallenge(128); + const hashedChallenge = crypto.createHash('sha256').update(challenge).digest('hex'); + await this.db.collections.pubkeys_collection.findOneAndUpdate({ id: pubkeyrec.id }, { $set: { challenge: hashedChallenge } }); + return { challenge: hashedChallenge }; + } + + /** + * Get an access token. + * + * @param username - The username. + * @param pubkeyKey - The public key. + * @param signature - The signature. + * @param life - The life of the token. + * @returns - The token. + */ + public async getAccessToken(username: string, hashedPrivateKey: string, signature: string, life = 120000) { + const user = await this.db.collections.users_collection.findOne({ 'username': username, 'life.deletedTime': null }); + if (!user) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'This public-key has not been registered yet.' + ); + } + const pubkeyrec = await this.db.collections.pubkeys_collection.findOne({ 'associatedUserId': user.id, 'hashedPrivateKey': hashedPrivateKey, 'life.deletedTime': null }); + if (!pubkeyrec) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'This public-key has not been registered yet.' + ); + } + + if (!pubkeyrec.challenge) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Please request a challenge first.' + ); + } + const isVerified = crypto.verify( + null, // No hash algorithm because the challenge is already hashed + Buffer.from(pubkeyrec.challenge, 'hex'), // Convert the hex-encoded challenge to binary + { + key: pubkeyrec.pubkey, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING + }, + Buffer.from(signature, 'hex') // Decode the Base64-encoded signature + ); + + if (!isVerified) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Signature is not correct.' + ); + } + const token = tokengen({ + username: user.username, + publicKey: pubkeyrec.jwtPubkey + }, pubkeyrec.jwtSeckey, undefined, undefined, life); + + await this.db.collections.pubkeys_collection.findOneAndUpdate({ id: pubkeyrec.id }, { $set: { lastUsedTime: Date.now() } }); + return token; + } + /** + * Issue an access token. + * + * @param pubkey - The public key. + * @param signature - The signature of the key. + * @param life - The life of the token. + * @returns + */ + public async issueAccessToken(pubkey: string, signature: string, life = 120000) { + // refine the public-key parameter from browser + pubkey = pubkey.replace(/\\n/g, '\n'); + + /* Validate the signature with the public key */ + if (!await rsaverifier(pubkey, signature)) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Signature vs Public key mismatched.' + ); + } + + const pubkeyrec = await this.db.collections.pubkeys_collection.findOne({ pubkey, deleted: null }); + if (pubkeyrec === null || pubkeyrec === undefined) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'This public-key has not been registered yet.' + ); + } + + // payload of the JWT for storing user information + const payload = { + publicKey: pubkeyrec.jwtPubkey, + associatedUserId: pubkeyrec.associatedUserId, + refreshCounter: pubkeyrec.refreshCounter, + Issuer: 'IDEA-FAST DMP' + }; + + // update the counter + const fieldsToUpdate = { + refreshCounter: (pubkeyrec.refreshCounter + 1) + }; + const updateResult = await this.db.collections.pubkeys_collection.findOneAndUpdate({ pubkey, deleted: null }, { $set: fieldsToUpdate }, { returnDocument: 'after' }); + if (!updateResult) { + throw new CoreError( + enumCoreErrors.DATABASE_ERROR, + enumCoreErrors.DATABASE_ERROR + ); + } + // return the acccess token + const accessToken = { + accessToken: tokengen(payload, pubkeyrec.jwtSeckey, undefined, undefined, life) + }; + + return accessToken; + } + + public async issueSystemAccessToken(userId: string, life?: number | null) { + /** + * Get the user to verify existence + * Minimal payload for system token + * @param userId - The id of the user. + * @param life - The life of the token. + * @returns + */ + // Get the user to verify existence + const user = await this.db.collections.users_collection.findOne( + { 'id': userId, 'life.deletedTime': null }, + { projection: { password: 0, otpSecret: 0 } } + ); + + if (!user) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'User does not exist.' + ); + } + + // Minimal payload for system token + const payload = { + publicKey: this.systemSecret.publickey, + userId: user.id, + isSystemToken: true, + issuer: 'IDEA-FAST DMP SYSTEM', + timestamp: Date.now() + }; + + // set the life time to one year if not specified + life = life || (365 * 24 * 60 * 60 * 1000); + + const accessToken = { + accessToken: tokengen(payload, this.systemSecret.privatekey, undefined, undefined, life) + }; + + return accessToken; + } + + + /** + * Delete a pubkey. + * + * @param requester - The requester. + * @param userId - The id of the user. + * @param keyId - The id of the key. + * @returns IGenericResponse + */ + public async deletePubkey(requester: IUserWithoutToken | undefined, associatedUserId: string, keyId: string) { + if (!requester) { + throw new CoreError( + enumCoreErrors.NOT_LOGGED_IN, + enumCoreErrors.NOT_LOGGED_IN + ); + } + if (requester.type !== enumUserTypes.ADMIN && requester.id !== associatedUserId) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'User can only delete his/her own public key.' + ); + } + const key = await this.db.collections.pubkeys_collection.findOne({ + 'id': keyId, + 'associatedUserId': associatedUserId, + 'life.deletedTime': null + }); + if (!key) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Key does not exist.' + ); + } + if (key.associatedUserId !== associatedUserId) { + throw new CoreError( + enumCoreErrors.NO_PERMISSION_ERROR, + 'The key does not match the user.' + ); + } + + await this.db.collections.pubkeys_collection.findOneAndUpdate({ id: keyId }, { + $set: { + 'life.deletedTime': Date.now(), + 'life.deletedUser': requester.id + } + }); + + return makeGenericResponse(keyId, true, undefined, undefined); + } + + /** + * Add a reset password request to the user. + * + * @param userId - The id of the user. + * @param resetPasswordRequest - The reset password request. + * @returns IGenericResponse + */ + public async addResetPasswordRequest(userId: string, resetPasswordRequest: IResetPasswordRequest) { + const invalidateAllTokens = await this.db.collections.users_collection.findOneAndUpdate( + { id: userId }, + { + $set: { + 'resetPasswordRequests.$[].used': true + } + } + ); + if (!invalidateAllTokens) { + throw new CoreError( + enumCoreErrors.DATABASE_ERROR, + enumCoreErrors.DATABASE_ERROR + ); + } + const updateResult = await this.db.collections.users_collection.findOneAndUpdate( + { id: userId }, + { + $push: { + resetPasswordRequests: resetPasswordRequest + } + } + ); + if (!updateResult) { + throw new CoreError( + enumCoreErrors.DATABASE_ERROR, + enumCoreErrors.DATABASE_ERROR + ); + } + + return makeGenericResponse(resetPasswordRequest.id, true, undefined, undefined); + } + + public async processResetPasswordRequest(token: string, email: string, password: string) { + /* check whether username and token is valid */ + /* not changing password too in one step (using findOneAndUpdate) because bcrypt is costly */ + const TIME_NOW = new Date().valueOf(); + const ONE_HOUR_IN_MILLISEC = 60 * 60 * 1000; + const user = await this.db.collections.users_collection.findOne({ + email, + 'resetPasswordRequests': { + $elemMatch: { + id: token, + timeOfRequest: { $gt: TIME_NOW - ONE_HOUR_IN_MILLISEC }, + used: false + } + }, + 'life.deletedTime': null + }); + if (!user) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'User does not exist.' + ); + } + + /* randomly generate a secret for Time-based One Time Password*/ + const otpSecret = mfa.generateSecret(); + + /* all ok; change the user's password */ + const hashedPw = await bcrypt.hash(password, this.config.bcrypt.saltround); + const updateResult = await this.db.collections.users_collection.findOneAndUpdate( + { + id: user.id, + resetPasswordRequests: { + $elemMatch: { + id: token, + timeOfRequest: { $gt: TIME_NOW - ONE_HOUR_IN_MILLISEC }, + used: false + } + } + }, + { $set: { 'password': hashedPw, 'otpSecret': otpSecret, 'resetPasswordRequests.$.used': true } }); + if (!updateResult) { + throw new CoreError( + enumCoreErrors.DATABASE_ERROR, + enumCoreErrors.DATABASE_ERROR + ); + } + + return updateResult; + } + + /** + * Ask for a request to extend account expiration time. Send notifications to user and admin. + * + * @param email - The email of the user. + * + * @return IGenericResponse + */ + public async requestExpiryDate(username?: string, email?: string) { + if ((!username && !email) || (username && email)) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Either username or email should be provided.' + ); + } + + const user = username ? await this.db.collections.users_collection.findOne({ username }) : await this.db.collections.users_collection.findOne({ email }); + if (!user || !user.email || !user.username) { + /* even user is null. send successful response: they should know that a user doesn't exist */ + await new Promise(resolve => setTimeout(resolve, Math.random() * 6000)); + return makeGenericResponse(email, false, undefined, 'User information is not correct.'); + } + /* send email to the DMP admin mailing-list */ + await this.mailer.sendMail(formatEmailRequestExpiryDatetoAdmin({ + config: this.config, + userEmail: user.email, + username: user.username + })); + + /* send email to client */ + await this.mailer.sendMail(formatEmailRequestExpiryDatetoClient({ + config: this.config, + to: user.email, + username: user.username + })); + + return makeGenericResponse(email, true, undefined, 'Request successfully sent.'); + } + + /** + * Request for resetting password. + * + * @param forgotUsername - Whether user forget the username. + * @param forgotPassword - Whether user forgot the password. + * @param email - The email of the user. If using email to reset password. + * @param username - The username of the uer. If using username to reset password. + * + * @return IGenericResponse - The object of IGenericResponse. + */ + public async requestUsernameOrResetPassword(forgotUsername: boolean, forgotPassword: boolean, origin: string, email?: string, username?: string) { + /* checking the args are right */ + if ((forgotUsername && !email) // should provide email if no username + || (forgotUsername && username) // should not provide username if it's forgotten. + || (!email && !username)) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Inputs are invalid.' + ); + } else if (email && username) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Inputs are invalid.' + ); + } + + /* check user existence */ + const user = await this.db.collections.users_collection.findOne({ '$or': [{ email }, { username }], 'life.deletedTime': null }); + if (!user) { + /* even user is null. send successful response: they should know that a user doesn't exist */ + await new Promise(resolve => setTimeout(resolve, Math.random() * 6000)); + return makeGenericResponse(undefined, true, undefined, enumCoreErrors.UNQUALIFIED_ERROR); + } + + if (forgotPassword) { + /* make link to change password */ + const passwordResetToken = uuid(); + const resetPasswordRequest: IResetPasswordRequest = { + id: passwordResetToken, + timeOfRequest: new Date().valueOf(), + used: false + }; + await this.addResetPasswordRequest(user.id ?? '', resetPasswordRequest); + + /* send email to client */ + await this.mailer.sendMail(await formatEmailForForgottenPassword({ + config: this.config, + to: user.email ?? '', + resetPasswordToken: passwordResetToken, + username: user.username ?? '', + firstname: user.firstname ?? '', + origin: origin + })); + } else { + /* send email to client */ + await this.mailer.sendMail(formatEmailForFogettenUsername({ + config: this.config, + to: user.email ?? '', + username: user.username ?? '' + })); + } + return makeGenericResponse(user.id, true, undefined, 'Request of resetting password successfully sent.'); + } + + /** + * Login a user. + * + * @param req - The request object. + * @param username - The username of the user. + * @param password - The password of the user. + * @param totp - The one-time password of the user. + * @returns IUserWithoutToken + */ + public async login(req: Express.Request, username: string, password: string, totp: string, requestExpiryDate?: boolean) { + const user = await this.db.collections.users_collection.findOne({ username }); + if (!user || !user.password || !user.otpSecret || !user.email || !user.username) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'User does not exist.' + ); + } + + const passwordMatched = await bcrypt.compare(password, user.password); + if (!passwordMatched) { + if (process.env['NODE_ENV'] === 'development') + console.warn('Incorrect password. Continuing in development ...'); + else { + throw new CoreError( + enumCoreErrors.AUTHENTICATION_ERROR, + 'Incorrect password.' + ); + } + } + + /* validate the TOTP */ + const totpValidated = mfa.verifyTOTP(totp, user.otpSecret); + if (!totpValidated) { + if (process.env['NODE_ENV'] === 'development') + console.warn('Incorrect One-Time password. Continuing in development ...'); + else { + throw new CoreError( + enumCoreErrors.AUTHENTICATION_ERROR, + 'Incorrect One-Time password.' + ); + } + } + + /* validate if account expired */ + if (user.expiredAt && user.expiredAt < Date.now() && user.type === enumUserTypes.STANDARD) { + if (requestExpiryDate) { + /* send email to the DMP admin mailing-list */ + await this.mailer.sendMail(formatEmailRequestExpiryDatetoAdmin({ + config: this.config, + userEmail: user.email, + username: user.username + })); + /* send email to client */ + await this.mailer.sendMail(formatEmailRequestExpiryDatetoClient({ + config: this.config, + to: user.email, + username: user.username + })); + throw new CoreError( + enumCoreErrors.UNQUALIFIED_ERROR, + 'New expiry date has been requested! Wait for ADMIN to approve.' + ); + } + throw new CoreError( + enumCoreErrors.AUTHENTICATION_ERROR, + 'Account Expired. Please request a new expiry date!' + ); + } + + const { password: __unusedPassword, otpSecret: __unusedOtpSecret, ...filteredUser } = user; + return new Promise((resolve) => { + req.login(filteredUser, (err) => { + if (err) { + Logger.error(err); + throw new CoreError( + enumCoreErrors.AUTHENTICATION_ERROR, + 'Cannot log in. Please try again later.' + ); + } + resolve(filteredUser as IUserWithoutToken); + }); + }); + } + + public async logout(requester: IUserWithoutToken | undefined, req: Express.Request) { + if (!requester) { + return makeGenericResponse(undefined, false, undefined, 'Requester not known.'); + } + return new Promise((resolve) => { + req.logout((err) => { + if (err) { + Logger.error(err); + throw new CoreError( + enumCoreErrors.AUTHENTICATION_ERROR, + 'Cannot log out. Please try again later.' + ); + } else { + resolve(makeGenericResponse(requester.id)); + } + }); + }); + } + + /** + * Reset password. + * @param encryptedEmail - The encrypted email. + * @param token - The token. + * @param newPassword - The new password. + * @returns IGnericResponse + */ + public async resetPassword(encryptedEmail: string, token: string, newPassword: string) { + if (!passwordIsGoodEnough(newPassword)) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Password has to be at least 8 character long.' + ); + } + + /* check that username and password dont have space */ + if (newPassword.indexOf(' ') !== -1) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Password cannot have spaces.' + ); + } + + /* decrypt email */ + if (token.length < 16) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Token is invalid.' + ); + } + + // TODO + const salt = makeAESKeySalt(token); + const iv = makeAESIv(token); + let email; + try { + email = await decryptEmail(this.config.aesSecret, encryptedEmail, salt, iv); + } catch (__unused__exception) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Token is invalid.' + ); + } + + const user = await this.processResetPasswordRequest(token, email, newPassword); + /* need to log user out of all sessions */ + // TO_DO + + /* send email to the registered user */ + // get QR Code for the otpSecret. + const oauth_uri = `otpauth://totp/${this.config.appName}:${user.username}?secret=${user.otpSecret}&issuer=Data%20Science%20Institute`; + const tmpobj = tmp.fileSync({ mode: 0o644, prefix: 'qrcodeimg-', postfix: '.png' }); + + QRCode.toFile(tmpobj.name, oauth_uri, {}, function (err) { + if (err) { + throw new CoreError( + enumCoreErrors.UNQUALIFIED_ERROR, + err.message + ); + } + }); + + const attachments = [{ filename: 'qrcode.png', path: tmpobj.name, cid: 'qrcode_cid' }]; + await this.mailer.sendMail({ + from: `${this.config.appName} <${this.config.nodemailer.auth.user}>`, + to: email, + subject: `[${this.config.appName}] Password reset`, + html: ` +

+ Dear ${user.firstname}, +

+

+ Your password on ${this.config.appName} is now reset!
+ You will need to update your MFA application for one-time passcode.
+

+

+ To update your MFA authenticator app you can scan the QRCode below to configure it:
+ QR code
+ If you need to type the token in use ${(user.otpSecret as string).toLowerCase()} +

+
+

+ The ${this.config.appName} Team. +

+ `, + attachments: attachments + }); + tmpobj.removeCallback(); + return makeGenericResponse(); + } + + /** + * Ping the server. This will refresh the session expire time. + * + * @returns - IGenericResponse + */ + public async recoverSessionExpireTime() { + return makeGenericResponse(); + } + + +} + +function passwordIsGoodEnough(pw: string): boolean { + if (pw.length < 8) { + return false; + } + return true; +} + +async function formatEmailForForgottenPassword({ config, username, firstname, to, resetPasswordToken, origin }: { config: IConfiguration, resetPasswordToken: string, to: string, username: string, firstname: string, origin: string }) { + const keySalt = makeAESKeySalt(resetPasswordToken); + const iv = makeAESIv(resetPasswordToken); + const encryptedEmail = await encryptEmail(config.aesSecret, to, keySalt, iv); + + const link = `${origin}/reset/${encryptedEmail}/${resetPasswordToken}`; + return ({ + from: `${config.appName} <${config.nodemailer.auth.user}>`, + to, + subject: `[${config.appName}] password reset`, + html: ` +

+ Dear ${firstname}, +

+

+ Your username is ${username}. +

+

+ You can reset you password by click the following link (active for 1 hour):
+ ${link} +

+
+

+ The ${config.appName} Team. +

+ ` + }); +} + +function formatEmailForFogettenUsername({ config, username, to }: { config: IConfiguration, username: string, to: string }) { + return ({ + from: `${config.appName} <${config.nodemailer.auth.user}>`, + to, + subject: `[${config.appName}] password reset`, + html: ` +

+ Dear user, +

+

+ Your username is ${username}. +

+
+

+ The ${config.appName} Team. +

+ ` + }); +} + +export function formatEmailRequestExpiryDatetoClient({ config, username, to }: { config: IConfiguration, username: string, to: string }) { + return ({ + from: `${config.appName} <${config.nodemailer.auth.user}>`, + to, + subject: `[${config.appName}] New expiry date has been requested!`, + html: ` +

+ Dear user, +

+

+ New expiry date for your ${username} account has been requested. + You will get a notification email once the request is approved. +

+
+

+ The ${config.appName} Team. +

+ ` + }); +} + +export function formatEmailRequestExpiryDatetoAdmin({ config, username, userEmail }: { config: IConfiguration, username: string, userEmail: string }) { + return ({ + from: `${config.appName} <${config.nodemailer.auth.user}>`, + to: `${config.adminEmail}`, + subject: `[${config.appName}] New expiry date has been requested from ${username} account!`, + html: ` +

+ Dear ADMINs, +

+

+ A expiry date request from the ${username} account (whose email address is ${userEmail}) has been submitted. + Please approve or deny the request ASAP. +

+
+

+ The ${config.appName} Team. +

+ ` + }); +} + +function formatEmailRequestExpiryDateNotification({ config, username, to }: { config: IConfiguration, username: string, to: string }) { + return ({ + from: `${config.appName} <${config.nodemailer.auth.user}>`, + to, + subject: `[${config.appName}] New expiry date has been updated!`, + html: ` +

+ Dear user, +

+

+ New expiry date for your ${username} account has been updated. + You now can log in as normal. +

+
+

+ The ${config.appName} Team. +

+ ` + }); +} + +export function makeAESKeySalt(str: string): string { + return str; +} + +export function makeAESIv(str: string): string { + if (str.length < 16) { throw new Error('IV cannot be less than 16 bytes long.'); } + return str.slice(0, 16); +} + +export async function encryptEmail(aesSecret: string, email: string, keySalt: string, iv: string) { + const algorithm = 'aes-256-cbc'; + return new Promise((resolve, reject) => { + crypto.scrypt(aesSecret, keySalt, 32, (err, derivedKey) => { + if (err) reject(err); + const cipher = crypto.createCipheriv(algorithm, derivedKey, iv); + let encoded = cipher.update(email, 'utf8', 'hex'); + encoded += cipher.final('hex'); + resolve(encoded); + }); + }); + +} + +export async function decryptEmail(aesSecret: string, encryptedEmail: string, keySalt: string, iv: string) { + const algorithm = 'aes-256-cbc'; + return new Promise((resolve, reject) => { + crypto.scrypt(aesSecret, keySalt, 32, (err, derivedKey) => { + if (err) reject(err); + try { + const decipher = crypto.createDecipheriv(algorithm, derivedKey, iv); + let decoded = decipher.update(encryptedEmail, 'hex', 'utf8'); + decoded += decipher.final('utf-8'); + resolve(decoded); + } catch (e) { + reject(e); + } + }); + }); +} diff --git a/packages/itmat-cores/src/coreFunc/utilsCore.ts b/packages/itmat-cores/src/coreFunc/utilsCore.ts new file mode 100644 index 000000000..ad4b78f7a --- /dev/null +++ b/packages/itmat-cores/src/coreFunc/utilsCore.ts @@ -0,0 +1,156 @@ +import { IValueVerifier, IAST, enumASTNodeTypes, enumMathOps, enumConditionOps, CoreError, enumCoreErrors } from '@itmat-broker/itmat-types'; +import crypto from 'crypto'; + +type IDataTransformationClip = Record; + +export class UtilsCore { + /** + * This function checks scalar data only. + * + * @param value - The input value. + * @param verifier - The input IAST. + * @returns If the value pass the verifier. + */ + public validValueWithVerifier(value: number | string | IDataTransformationClip, verifier: IValueVerifier): boolean { + const calculatedValue = this.IASTHelper(verifier.formula, value); + if (verifier.condition === enumConditionOps.NUMERICALEQUAL) { + return calculatedValue === this.parseInputToNumber(verifier.value); + } else if (verifier.condition === enumConditionOps.NUMERICALNOTEQUAL) { + return calculatedValue !== verifier.value; + } else if (verifier.condition === enumConditionOps.NUMERICALLESSTHAN) { + return calculatedValue < verifier.value; + } else if (verifier.condition === enumConditionOps.NUMERICALGREATERTHAN) { + return calculatedValue > verifier.value; + } else if (verifier.condition === enumConditionOps.NUMERICALNOTLESSTHAN) { + return calculatedValue >= verifier.value; + } else if (verifier.condition === enumConditionOps.NUMERICALNOTGREATERTHAN) { + return calculatedValue <= verifier.value; + } else if (verifier.condition === enumConditionOps.STRINGREGEXMATCH) { + return new RegExp(verifier.value.toString()).test(calculatedValue.toString()); + } else if (verifier.condition === enumConditionOps.STRINGEQUAL) { + return calculatedValue === verifier.value; + } else if (verifier.condition === enumConditionOps.GENERALISNOTNULL) { + return calculatedValue !== null; + } else if (verifier.condition === enumConditionOps.GENERALISNULL) { + return calculatedValue === null; + } + return false; + } + + public parseInputToNumber(input: number | string): number { + // If the input is already a number, return it + if (typeof input === 'number') { + return input; + } + + // If the input is a string + if (typeof input === 'string') { + // If the string contains a decimal point, use parseFloat + if (input.includes('.')) { + return parseFloat(input); + } + // Otherwise, use parseInt + return parseInt(input, 10); + } + + // If the input is neither a number nor a string, throw an error + throw new Error('Input must be a number or a string'); + } + + public IASTHelper(root: IAST, data: number | string | IDataTransformationClip) { + if (root.type === enumASTNodeTypes.VALUE) { + return root.value; + } + if (root.type === enumASTNodeTypes.SELF) { + return data; + } + if (typeof data === 'string' && root.type === enumASTNodeTypes.MAP) { + return root.parameters[data] ?? data; + } + // in this case, the data should be a json + if (root.type === enumASTNodeTypes.VARIABLE) { + if (root.value) { + const keys = (root.value as string).split('.'); + let current = data; + + for (const k of keys) { + if (current[k] !== undefined) { + current = current[k]; + } else { + return undefined; + } + } + + return current; + } + } + if ((typeof data === 'number' || typeof data === 'string') && root.type === enumASTNodeTypes.OPERATION) { + if (!root.operator) { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'OPEARTION node must have an operator' + ); + } + if (root.operator === enumMathOps.NUMERICALADD && root.children && root.children.length === 2) { + return this.IASTHelper(root.children[0], this.parseInputToNumber(data)) + this.IASTHelper(root.children[1], this.parseInputToNumber(data)); + } else if (root.operator === enumMathOps.NUMERICALMINUS && root.children && root.children.length === 2) { + return this.IASTHelper(root.children[0], this.parseInputToNumber(data)) - this.IASTHelper(root.children[1], this.parseInputToNumber(data)); + } else if (root.operator === enumMathOps.NUMERICALMULTIPLY && root.children && root.children.length === 2) { + return this.IASTHelper(root.children[0], this.parseInputToNumber(data)) * this.IASTHelper(root.children[1], this.parseInputToNumber(data)); + } else if (root.operator === enumMathOps.NUMERICALDIVIDE && root.children && root.children.length === 2) { + return this.IASTHelper(root.children[0], this.parseInputToNumber(data)) / this.IASTHelper(root.children[1], this.parseInputToNumber(data)); + } else if (root.operator === enumMathOps.NUMERICALPOW && root.children && root.children.length === 2) { + return Math.pow(this.IASTHelper(root.children[0], this.parseInputToNumber(data)), this.IASTHelper(root.children[1], this.parseInputToNumber(data))); + } else if (root.operator === enumMathOps.STRINGCONCAT && root.children && root.children.length) { + return root.children.reduce((a, c) => { + return a + this.IASTHelper(c, data).toString(); + }, ''); + } else if (root.operator === enumMathOps.STRINGSUBSTR && root.children && root.children.length === 3) { + return (this.IASTHelper(root.children[0], data).toString()).substr(this.IASTHelper(root.children[1], data), this.IASTHelper(root.children[2], data.toString())); + } else if (root.operator === enumMathOps.TYPECONVERSION && root.children && root.children.length === 2) { + const newType = this.IASTHelper(root.children[0], data); + if (newType === 'INT') { + return Math.floor(Number(this.IASTHelper(root.children[0], data))); + } else if (newType === 'FLOAT') { + return parseFloat((this.IASTHelper(root.children[0], data) as string | number).toString()); + } else if (newType === 'STRING') { + return this.IASTHelper(root.children[0], data).toString(); + } else { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Type converstion only supports INT, FLOAT and STRING.' + ); + } + } else { + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Operator and children does not match.' + ); + } + } + throw new CoreError( + enumCoreErrors.CLIENT_MALFORMED_INPUT, + 'Node type must be OPERATION,, SELF or VALUE' + ); + } + + public normalize(obj) { + if (Array.isArray(obj)) { + return obj.map(this.normalize.bind(this)).sort(); + } else if (typeof obj === 'object' && obj !== null) { + const sortedObj: { [key: string]: unknown } = {}; + Object.keys(obj).sort().forEach(key => { + sortedObj[key] = this.normalize.bind(this)(obj[key]); + }); + return sortedObj; + } else { + return obj; + } + } + + public computeHash(inputObject): string { + const normalizedObj = this.normalize.bind(this)(inputObject); + const str = JSON.stringify(normalizedObj); + return crypto.createHash('sha256').update(str).digest('hex'); + } +} diff --git a/packages/itmat-cores/src/coreFunc/webauthnCore.ts b/packages/itmat-cores/src/coreFunc/webauthnCore.ts new file mode 100644 index 000000000..1a520449e --- /dev/null +++ b/packages/itmat-cores/src/coreFunc/webauthnCore.ts @@ -0,0 +1,465 @@ +import { CoreError,IUser,enumCoreErrors, AuthenticatorDevice, IUserWithoutToken, enumUserTypes} from '@itmat-broker/itmat-types'; +import { DBType } from '../database/database'; +import { Logger, Mailer, ObjectStore } from '@itmat-broker/itmat-commons'; +import { IConfiguration, makeGenericResponse} from '../utils'; +import { ConfigCore } from './configCore'; +import {formatEmailRequestExpiryDatetoAdmin, formatEmailRequestExpiryDatetoClient} from './userCore'; +import { v4 as uuid } from 'uuid'; +import * as mfa from '../utils/mfa'; + + +import { + generateRegistrationOptions, + verifyRegistrationResponse, + generateAuthenticationOptions, + verifyAuthenticationResponse +} from '@simplewebauthn/server'; + +import type { + RegistrationResponseJSON, + PublicKeyCredentialRequestOptionsJSON, + AuthenticationResponseJSON +} from '@simplewebauthn/types'; + + +import { isoBase64URL} from '@simplewebauthn/server/helpers'; + + +export class WebauthnCore { + db: DBType; + mailer: Mailer; + config: IConfiguration; + objStore: ObjectStore; + configCore: ConfigCore; + rpName: string; + constructor(db: DBType, mailer: Mailer, config: IConfiguration, objStore: ObjectStore) { + this.db = db; + this.mailer = mailer; + this.config = config; + this.objStore = objStore; + this.configCore = new ConfigCore(db); + // get the rpName,from the config + this.rpName = config.appName; + } + /** + * webauthn functions + * + * generate a random challenge for webauthn registration and authentication + * @returns - the challenge: Uint8Array + */ + private generate_challenge() { + + const randomStringFromServer = mfa.generateSecret(20); + const challenge: Uint8Array = new TextEncoder().encode(randomStringFromServer); + return challenge; + } + /** + * get the webauthn data for a list of webauthn_ids + * @param webauthn_ids + * @returns + */ + public async getWebauthn(webauthn_ids: string[]) { + if (webauthn_ids.length === 0) return []; + const queryObj = {id: { $in: webauthn_ids } }; + const cursor = this.db.collections.webauthn_collection.find(queryObj, { projection: { _id: 0 } }); + return cursor.toArray(); + } + /** + * get the registered webauthn device for a user. + * @param user - The user. + * @returns - The registration options. + */ + public async getWebauthnDevices(user: IUser): Promise { + + const webauthnCursor = await this.db.collections.webauthn_collection.findOne({userId: user.id}, { projection: { _id: 0 } }); + + return webauthnCursor?.devices ?? []; + } + /** + * get the webauthn id for a user. + * @param user - The user. + * @returns - The webauthn object. + */ + public async getUserWebAuthnID(user: IUser) { + + const webauthnCursor = await this.db.collections.webauthn_collection.findOne({ userId: user.id }); + + if (!webauthnCursor) { + return null; // No record found for the user, returning an empty + } + return webauthnCursor; + } + + /** + * delete a webauthn device for a user. + * @param user - The user. + * @param device_id - The device id. + * @returns - The updated list of devices. + */ + public async deleteWebauthnDevices(user: IUser, device_id: string) { + const webAuthnData = await this.db.collections.webauthn_collection.findOne({ userId: user.id }); + if (webAuthnData){ + const deviceIndex = webAuthnData.devices.findIndex(device => device.id === device_id); + if (deviceIndex === -1) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Device does not exist.' + ); + } + webAuthnData.devices.splice(deviceIndex, 1); + await this.db.collections.webauthn_collection.updateOne({ userId: user.id }, { $set: { devices: webAuthnData.devices } }); + } + + return webAuthnData?.devices ?? []; + } + /** + * get the webauthn registration options for a user. + * @param user + * @returns - The registration options. + */ + public async getWebauthnRegistrationOptions(user: IUser, rpID: string) { + let webauthn_id; + const challenge = this.generate_challenge(); + const webauthnStore = await this.db.collections.webauthn_collection.findOne({ + userId: user.id + }); + + if (webauthnStore) { + // update and get the webauthn_id + webauthn_id = webauthnStore.id; + await this.db.collections.webauthn_collection.updateOne({ + userId: user.id + }, { + $set: { + challenge, + challengeTimestamp: Date.now() + } + }); + } else { + webauthn_id = uuid(); + await this.db.collections.webauthn_collection.insertOne({ + id: webauthn_id, + userId: user.id, + username: user.username, + devices: [], + challenge, + challengeTimestamp: Date.now(), + life: { + createdTime: Date.now(), + createdUser: user.id, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }); + } + + const devices = webauthnStore?.devices ?? []; + try { + const options = await generateRegistrationOptions({ + rpName: this.rpName, + rpID: rpID, + userID: Buffer.from(user.id) as Uint8Array, + userName: user.username, + timeout: 60000, + attestationType: 'none', + excludeCredentials: devices.map((authenticator: AuthenticatorDevice) => ({ + id: authenticator.credentialID, + type: 'public-key', + transports: authenticator.transports + })), + challenge: challenge.buffer as Uint8Array, + authenticatorSelection: { + residentKey: 'discouraged' + }, + supportedAlgorithmIDs: [-7, -257] + + }); + return { + webauthn_id: webauthn_id, + options: options + }; + } + catch (error) { + Logger.error(`Error generating registration options: ${JSON.stringify(error)}`); + throw new CoreError( + enumCoreErrors.AUTHENTICATION_ERROR, + 'Failed to generate registration options' + );} + } + /** + * handle the registration verify for a user. + * @param user - The user. + * @param attestationResponse - The attestation response. + * @returns - The response. + */ + public async handleRegistrationVerify(user: IUser, attestationResponse: RegistrationResponseJSON, origin: string, rpID: string) { + let device_id; + const webauthn = await this.db.collections.webauthn_collection.findOne({ + userId: user.id + }); + + if (!webauthn) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Webauthn does not exist.' + ); + } + + const { devices, challenge} = webauthn; + + const decodedString = isoBase64URL.fromBuffer(challenge.buffer as Uint8Array); + + try{ + // verify the registration response + const {verified, registrationInfo} = await verifyRegistrationResponse({ + response: attestationResponse, + expectedChallenge: decodedString, + expectedOrigin: origin, + expectedRPID: rpID, + requireUserVerification: true + }); + + if (verified && registrationInfo) { + const { credentialPublicKey, credentialID, counter } = registrationInfo; + device_id = uuid(); + + const newDevice: AuthenticatorDevice = { + credentialPublicKey, + credentialID, + counter, + transports: attestationResponse.response.transports, + id: device_id, + origin: origin + }; + devices.push(newDevice); + + const updateResult = await this.db.collections.webauthn_collection.updateOne( + { id: webauthn.id }, + { + $set: { + devices: devices + } + } + ); + if (!updateResult.acknowledged) { + throw new CoreError( + enumCoreErrors.OBJ_STORE_ERROR, + 'Failed to update devices list.' + ); + } + return makeGenericResponse(device_id, true); + } + return makeGenericResponse(undefined, false); + } catch (_error) { + Logger.error(`Error for registeration verify ${JSON.stringify(_error)}`); + return makeGenericResponse(undefined, false); + } + } + /** + * get the webauthn authentication options for a user. + * @param userId - The user id. + * @returns - The authentication options. + */ + public async getWebauthnAuthenticationOptions(userId: string, rpID: string): Promise{ + + const webauthn = await this.db.collections.webauthn_collection.findOne({ + userId: userId + }); + + if (!webauthn) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Webauthn not initialised.' + ); + } + const {devices, challenge} = webauthn; + + try{ + const options = await generateAuthenticationOptions({ + challenge: challenge.buffer as Uint8Array, + timeout: 60000, + allowCredentials: devices.map((authenticator) => ({ + id: authenticator.credentialID, + type: 'public-key', + transports: authenticator.transports + })), + userVerification: 'required', + rpID: rpID + }); + return options; + } + catch (_error) { + Logger.error(`Error generating authentication options: ${JSON.stringify(_error)}`); + throw new CoreError( + enumCoreErrors.AUTHENTICATION_ERROR, + 'Failed to generate authentication options' + ); + } + } + /** + * handle the authentication verify for a user. + * @param userId - The user id. + * @param assertionResponse - The assertion response. + * @returns - The response. + */ + public async handleAuthenticationVerify(userId: string, assertionResponse: AuthenticationResponseJSON, origin: string, rpID: string) { + + + const webauthn = await this.db.collections.webauthn_collection.findOne({ + userId: userId + }); + if (!webauthn) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Webauthn not initialised.' + ); + } + + const { devices, challenge} = webauthn; + + const decodedChallengeString = isoBase64URL.fromBuffer(challenge.buffer as Uint8Array); + const bodyCredIDString = assertionResponse.rawId; + const deviceIndex = devices.findIndex(d =>d.credentialID === bodyCredIDString); + + if (deviceIndex < 0) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Authenticator is not registered with this site.' + ); + } + const device = devices[deviceIndex]; + if (!device.credentialPublicKey) { + throw new CoreError( + enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, + 'Credential public key is missing.' + ); + } + + try { + const verification = await verifyAuthenticationResponse({ + response: assertionResponse, + expectedChallenge: decodedChallengeString, + expectedOrigin: origin, + expectedRPID: rpID, + authenticator: { + credentialPublicKey: device.credentialPublicKey.buffer as Uint8Array, + credentialID: device.credentialID, + counter: device.counter + }, + requireUserVerification: true + }); + + const { verified, authenticationInfo } = verification; + + if (verified) { + + const { newCounter } = authenticationInfo; + devices[deviceIndex].counter = newCounter; + + const updateResult = await this.db.collections.webauthn_collection.updateOne( + { id: webauthn.id }, + { $set: { devices } } + ); + + if (!updateResult.acknowledged) { + throw new CoreError( + enumCoreErrors.OBJ_STORE_ERROR, + 'Failed to update authenticators.' + ); + } + return makeGenericResponse(undefined, true); + } + return makeGenericResponse(undefined, false); + } catch (_error) { + Logger.error(JSON.stringify(_error)); + return makeGenericResponse(undefined, false) ; + } + + } + /** + * login a user using webauthn. + * @param req - The request. + * @param userId - The user id. + * @param requestExpiryDate - The request expiry date. + * @returns - The user without token. + */ + public async webauthnLogin(req: Express.Request, userId: string, requestExpiryDate?: boolean): Promise { + const user = await this.db.collections.users_collection.findOne({ id: userId, deleted: null }); + if (!user) { + throw new CoreError(enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, 'User does not exist.'); + } + + if (user.expiredAt && user.expiredAt < Date.now() && user.type === enumUserTypes.STANDARD) { + if (requestExpiryDate) { + await this.mailer.sendMail(formatEmailRequestExpiryDatetoAdmin({ + config: this.config, + userEmail: user.email, + username: user.username + })); + await this.mailer.sendMail(formatEmailRequestExpiryDatetoClient({ + config: this.config, + to: user.email, + username: user.username + })); + throw new CoreError(enumCoreErrors.UNQUALIFIED_ERROR, 'New expiry date has been requested! Wait for ADMIN to approve.'); + } + throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'Account Expired. Please request a new expiry date!'); + } + + const { password: __unusedPassword, otpSecret: __unusedOtpSecret, ...filteredUser } = user; + return new Promise((resolve, reject) => { + req.login(filteredUser, (err) => { + if (err) { + Logger.error(err); + reject(new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'Cannot log in. Please try again later.')); + } else { + resolve(filteredUser as IUserWithoutToken); + } + }); + }); + } + /** + * register a user using webauthn. + * @param user - The user. + * @param deviceId - The device id. + * @param deviceName - The device name. + * @returns - The user without token. + */ + public async updateWebauthnDeviceName(user: IUser, deviceId: string, deviceName: string) { + const result = await this.db.collections.webauthn_collection.findOneAndUpdate( + { + 'userId': user.id, + 'devices.id': deviceId + }, + { + $set: { 'devices.$.name': deviceName } + }, + { + returnDocument: 'after' + } + ); + + if (!result) { + throw new CoreError( + enumCoreErrors.OBJ_STORE_ERROR, + 'Device not found or user mismatch.' + ); + } + + return result.devices; + } + /** + * get the current origin and rpID. + * @param ctx - The context. + * @returns - The origin and rpID. + */ + + public async getCurrentOriginAndRpID(ctx) { + const req = ctx.req; + const origin = req.headers.origin; + const rpID = (new URL(origin)).hostname; + return {origin, rpID}; + } + +} diff --git a/packages/itmat-cores/src/database/database.ts b/packages/itmat-cores/src/database/database.ts new file mode 100644 index 000000000..27f7f08ae --- /dev/null +++ b/packages/itmat-cores/src/database/database.ts @@ -0,0 +1,57 @@ +import type { IField, IFile, IJob, ILog, IOrganisation, IProject, IPubkey, IQueryEntry, IRole, IStudy, IUser, IStandardization, IConfig, IData, IDrive, ICache, IDomain, IOntologyTree, IBase, IWebAuthn, IInstance} from '@itmat-broker/itmat-types'; +import { Database as DatabaseBase, IDatabaseBaseConfig } from '@itmat-broker/itmat-commons'; +import type { Collection } from 'mongodb'; + +export interface IDatabaseConfig extends IDatabaseBaseConfig { + collections: { + users_collection: string, + jobs_collection: string, + studies_collection: string, + projects_collection: string, + queries_collection: string, + field_dictionary_collection: string, + roles_collection: string, + files_collection: string, + organisations_collection: string, + log_collection: string, + pubkeys_collection: string, + data_collection: string, + standardizations_collection: string, + configs_collection: string, + ontologies_collection: string, + drives_collection: string, + colddata_collection: string, + cache_collection: string, + domains_collection: string, + doc_collection: string, + webauthn_collection: string, + instance_collection: string + }; +} + +export interface IDatabaseCollectionConfig { + users_collection: Collection, + jobs_collection: Collection, + studies_collection: Collection, + projects_collection: Collection, + queries_collection: Collection, + field_dictionary_collection: Collection, + roles_collection: Collection, + files_collection: Collection, + organisations_collection: Collection, + log_collection: Collection, + pubkeys_collection: Collection, + data_collection: Collection, + standardizations_collection: Collection, + configs_collection: Collection, + ontologies_collection: Collection, + drives_collection: Collection, + colddata_collection: Collection, + cache_collection: Collection, + domains_collection: Collection, + // TODO: Implemet doc feature + docs_collection: Collection + webauthn_collection: Collection + instance_collection: Collection +} +export type DBType = DatabaseBase; diff --git a/packages/itmat-cores/src/index.ts b/packages/itmat-cores/src/index.ts new file mode 100644 index 000000000..71a7e1ad7 --- /dev/null +++ b/packages/itmat-cores/src/index.ts @@ -0,0 +1,26 @@ +// TRPCCore +export * from './coreFunc/configCore'; +export * from './coreFunc/driveCore'; +export * from './coreFunc/fileCore'; +export * from './coreFunc/studyCore'; +export * from './coreFunc/userCore'; +export * from './coreFunc/dataCore'; +export * from './coreFunc/transformationCore'; +export * from './coreFunc/permissionCore'; +export * from './coreFunc/logCore'; +export * from './coreFunc/domainCore'; +export * from './coreFunc/organisationCore'; +export * from './coreFunc/jobCore'; +export * from './coreFunc/standardizationCore'; +export * from './coreFunc/utilsCore'; +export * from './rest/fileDownload'; +export * from './authentication/pubkeyAuthentication'; +export * from './database/database'; +export * from './utils'; +// webdav +export * from './webdav/dmpWebDAV'; +// webauthn +export * from './coreFunc/webauthnCore'; +// instance, lxd, Analystic Environment +export * from './coreFunc/instanceCore'; +export * from './lxd/lxdManager'; diff --git a/packages/itmat-cores/src/lxd/lxd.config.ts b/packages/itmat-cores/src/lxd/lxd.config.ts new file mode 100644 index 000000000..63fa22ad4 --- /dev/null +++ b/packages/itmat-cores/src/lxd/lxd.config.ts @@ -0,0 +1,338 @@ +// Prepare user-data for cloud-init to initialize the instance +export const cloudInitUserDataJupyterContainer = (instanceSystemToken: string, username: string, instance_id: string, jupyterPort: number) =>` +#cloud-config +users: + - name: ubuntu + groups: sudo,root + sudo: "ALL=(ALL) NOPASSWD:ALL" + shell: /bin/bash +write_files: + - path: /etc/profile.d/instance_token.sh + content: | + #!/bin/bash + export DMP_TOKEN="${instanceSystemToken}" + export HTTP_PROXY=http://127.0.0.1:3128 + export HTTPS_PROXY=http://127.0.0.1:3128 + export NO_PROXY=127.0.0.1,localhost + permissions: '0755' + - path: /etc/apt/apt.conf.d/95proxy + content: | + Acquire::http::Proxy "http://127.0.0.1:3128"; + Acquire::https::Proxy "http://127.0.0.1:3128"; + Acquire::http::Proxy "http://[::1]:3128"; + Acquire::https::Proxy "http://[::1]:3128"; + permissions: '0644' + - path: /etc/bash.bashrc + append: true + content: | + export HTTP_PROXY=http://127.0.0.1:3128 + export HTTPS_PROXY=http://127.0.0.1:3128 + export NO_PROXY=127.0.0.1,localhost + export http_proxy=$HTTP_PROXY + export https_proxy=$HTTPS_PROXY + export no_proxy=$NO_PROXY + export DMP_TOKEN="${instanceSystemToken}" + permissions: '0644' + - path: /etc/systemd/system.conf.d/proxy.conf + content: | + [Manager] + DefaultEnvironment="HTTP_PROXY=http://127.0.0.1:3128" "HTTPS_PROXY=http://127.0.0.1:3128" "NO_PROXY=127.0.0.1,localhost" + permissions: '0644' + - path: /root/.ipython/profile_default/startup/00-proxy.py + content: | + import os + import sys + os.environ['HTTP_PROXY'] = 'http://127.0.0.1:3128' + os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:3128' + os.environ['NO_PROXY'] = '127.0.0.1,localhost' + os.environ['DMP_TOKEN'] = '${instanceSystemToken}' + sys.path.insert(0, '/root/dmpy') + permissions: '0644' + - path: /root/.jupyter/jupyter_notebook_config.py + content: | + c.NotebookApp.ip = "0.0.0.0" + c.NotebookApp.port = ${jupyterPort} + c.NotebookApp.open_browser = False + c.NotebookApp.token = "" + c.NotebookApp.password = "" + c.NotebookApp.allow_root = True + c.NotebookApp.base_url = "/jupyter/${instance_id}" + c.NotebookApp.notebook_dir = "/home/ubuntu" + c.Spawner.environment = { + 'PYTHONPATH': '/root/dmpy:$PYTHONPATH', + 'HTTP_PROXY': 'http://127.0.0.1:3128', + 'HTTPS_PROXY': 'http://127.0.0.1:3128', + 'NO_PROXY': '127.0.0.1,localhost', + 'DMP_TOKEN': '${instanceSystemToken}' + } + permissions: '0644' + - path: /etc/systemd/system/jupyter.service + content: | + [Unit] + Description=Jupyter Notebook Server + After=network.target + [Service] + Type=simple + User=root + Environment="HTTP_PROXY=http://127.0.0.1:3128" + Environment="HTTPS_PROXY=http://127.0.0.1:3128" + Environment="NO_PROXY=127.0.0.1,localhost" + EnvironmentFile=-/etc/environment + ExecStart=/usr/bin/python3 /usr/local/bin/jupyter-notebook --config=/root/.jupyter/jupyter_notebook_config.py --allow-root + WorkingDirectory=/root + Restart=always + RestartSec=60 + StartLimitInterval=300 + StartLimitBurst=5 + [Install] + WantedBy=multi-user.target + permissions: '0644' +runcmd: + - | + grep -qxF 'DMP_TOKEN="${instanceSystemToken}"' /etc/environment || \ + echo 'DMP_TOKEN="${instanceSystemToken}"' >> /etc/environment + grep -qxF 'PYTHONPATH="/root/dmpy:$PYTHONPATH"' /etc/environment || \ + echo 'PYTHONPATH="/root/dmpy:$PYTHONPATH"' >> /etc/environment + grep -qxF 'HTTP_PROXY=http://127.0.0.1:3128' /etc/environment || \ + echo 'HTTP_PROXY=http://127.0.0.1:3128' >> /etc/environment + grep -qxF 'HTTPS_PROXY=http://127.0.0.1:3128' /etc/environment || \ + echo 'HTTPS_PROXY=http://127.0.0.1:3128' >> /etc/environment + grep -qxF 'NO_PROXY=127.0.0.1,localhost' /etc/environment || \ + echo 'NO_PROXY=127.0.0.1,localhost' >> /etc/environment + - | + export HTTP_PROXY=http://127.0.0.1:3128 + export HTTPS_PROXY=http://127.0.0.1:3128 + export NO_PROXY=127.0.0.1,localhost + apt-get update + apt-get install -y nginx + - | + DEFAULT_USER="\${USERNAME:-ubuntu}" + if ! getent group nopasswdlogin > /dev/null; then + addgroup nopasswdlogin + fi + if ! id -u \${DEFAULT_USER} > /dev/null 2>&1; then + adduser \${DEFAULT_USER} nopasswdlogin || true + fi + passwd -d \${DEFAULT_USER} || true + echo "@reboot \${DEFAULT_USER} DISPLAY=:0 /home/\${DEFAULT_USER}/disable_autolock.sh" | crontab -u \${DEFAULT_USER} - + cat << 'EOF' > "/home/\${DEFAULT_USER}/disable_autolock.sh" + #!/bin/bash + if [ -z "\${DISPLAY}" ]; then + echo "No DISPLAY available. Skipping GUI settings." + else + dbus-launch gsettings set org.gnome.desktop.screensaver lock-enabled false + dbus-launch gsettings set org.gnome.desktop.session idle-delay 0 + fi + EOF + chmod +x "/home/\${DEFAULT_USER}/disable_autolock.sh" + chown \${DEFAULT_USER}: "/home/\${DEFAULT_USER}/disable_autolock.sh" + mkdir -p /usr/local/share/jupyter/kernels/python3 + cat > /usr/local/share/jupyter/kernels/python3/kernel.json << EOF + { + "argv": ["/usr/bin/python3", "-m", "ipykernel_launcher", "-f", "{connection_file}"], + "display_name": "Python 3", + "language": "python", + "env": { + "PYTHONPATH": "/root/dmpy:$PYTHONPATH", + "HTTP_PROXY": "http://127.0.0.1:3128", + "HTTPS_PROXY": "http://127.0.0.1:3128", + "NO_PROXY": "127.0.0.1,localhost", + "DMP_TOKEN": "${instanceSystemToken}" + } + } + EOF + - sleep 10 + - systemctl daemon-reload + - systemctl restart systemd-journald + - systemctl enable jupyter.service + - systemctl start jupyter.service +`; + +// MATLAB Container configuration with WebDAV removed +export const cloudInitUserDataMatlabContainer = (instanceSystemToken: string, username: string, instance_id: string) =>` +#cloud-config +users: + - name: ubuntu + groups: sudo,root + sudo: "ALL=(ALL) NOPASSWD:ALL" + shell: /bin/bash +write_files: + - path: /etc/profile.d/instance_token.sh + content: | + #!/bin/bash + export DMP_TOKEN="${instanceSystemToken}" + export HTTP_PROXY=http://127.0.0.1:3128 + export HTTPS_PROXY=http://127.0.0.1:3128 + export NO_PROXY=127.0.0.1,localhost + permissions: '0755' + - path: /etc/apt/apt.conf.d/95proxy + content: | + Acquire::http::Proxy "http://127.0.0.1:3128"; + Acquire::https::Proxy "http://127.0.0.1:3128"; + Acquire::http::Proxy "http://[::1]:3128"; + Acquire::https::Proxy "http://[::1]:3128"; + permissions: '0644' + - path: /etc/bash.bashrc + append: true + content: | + export HTTP_PROXY=http://127.0.0.1:3128 + export HTTPS_PROXY=http://127.0.0.1:3128 + export NO_PROXY=127.0.0.1,localhost + export http_proxy=$HTTP_PROXY + export https_proxy=$HTTPS_PROXY + export no_proxy=$NO_PROXY + permissions: '0644' + - path: /usr/local/MATLAB/R2023b/bin/matlab_env.sh + content: | + #!/bin/bash + export DMP_TOKEN="${instanceSystemToken}" + export HTTP_PROXY=http://127.0.0.1:3128 + export HTTPS_PROXY=http://127.0.0.1:3128 + export NO_PROXY=127.0.0.1,localhost + permissions: '0755' + - path: /etc/systemd/system.conf.d/proxy.conf + content: | + [Manager] + DefaultEnvironment="HTTP_PROXY=http://127.0.0.1:3128" "HTTPS_PROXY=http://127.0.0.1:3128" "NO_PROXY=127.0.0.1,localhost" + permissions: '0644' + - path: /root/.ipython/profile_default/startup/00-proxy.py + content: | + import os + os.environ['HTTP_PROXY'] = 'http://127.0.0.1:3128' + os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:3128' + os.environ['NO_PROXY'] = '127.0.0.1,localhost' + os.environ['DMP_TOKEN'] = '${instanceSystemToken}' + permissions: '0644' + - path: /etc/nginx/sites-available/vnc_proxy + content: | + server { + listen 8888; + server_name _; + # Use the instance_id in the path: + location ~ ^/matlab/${instance_id}(/.*)?$ { + # Strip the prefix and forward to noVNC on 6080 + rewrite ^/matlab/${instance_id}(/.*)$ $1 break; + proxy_pass http://localhost:6080; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $http_connection; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Increase timeouts + proxy_read_timeout 36000; + proxy_send_timeout 36000; + } + location / { + return 404; + } + } + permissions: '0644' + - path: /root/dummy-mac.sh + content: | + #!/bin/bash + + # Read the machine-id (persistent across reboots) + MACHINE_ID=$(cat /etc/machine-id) + + # Generate a deterministic MAC address from the machine-id + # - Use a hash to derive 6 bytes (SHA-256 truncated to 48 bits) + # - Ensure it's a locally administered unicast MAC (second bit of first byte = 1) + MAC_ADDR=$(echo "\${MACHINE_ID}" | sha256sum | awk '{print $1}' | head -c 12 | sed 's/\\(..\\)\\(..\\)\\(..\\)\\(..\\)\\(..\\)\\(..\\)/\\1:\\2:\\3:\\4:\\5:\\6/') + + # Force the first byte to be locally administered (e.g., 0x02, 0x06, etc.) + FIRST_BYTE=$(echo "\${MAC_ADDR}" | cut -d':' -f1) + FIRST_BYTE_HEX=$(( 0x\${FIRST_BYTE} | 0x02 )) # Set the second bit + FIRST_BYTE=$(printf "%02x" "\${FIRST_BYTE_HEX}") + + # Rebuild the MAC address + MAC_ADDR="\${FIRST_BYTE}:$(echo "\${MAC_ADDR}" | cut -d':' -f2-6)" + + # Create the dummy interface and set the MAC + ip link add eth0 type dummy + ip link set eth0 address "\${MAC_ADDR}" + ip link set eth0 up + permissions: '0755' + - path: /etc/systemd/system/dummy-eth0.service + content: | + [Unit] + Description=Create dummy eth0 with persistent MAC + After=network.target + + [Service] + Type=oneshot + ExecStart=/root/dummy-mac.sh + RemainAfterExit=yes + + [Install] + WantedBy=multi-user.target + permissions: '0644' +runcmd: + - | + grep -qxF 'DMP_TOKEN="${instanceSystemToken}"' /etc/environment || \ + echo 'DMP_TOKEN="${instanceSystemToken}"' >> /etc/environment + grep -qxF 'PYTHONPATH="/root/dmpy:$PYTHONPATH"' /etc/environment || \ + echo 'PYTHONPATH="/root/dmpy:$PYTHONPATH"' >> /etc/environment + grep -qxF 'HTTP_PROXY=http://127.0.0.1:3128' /etc/environment || \ + echo 'HTTP_PROXY=http://127.0.0.1:3128' >> /etc/environment + grep -qxF 'HTTPS_PROXY=http://127.0.0.1:3128' /etc/environment || \ + echo 'HTTPS_PROXY=http://127.0.0.1:3128' >> /etc/environment + grep -qxF 'NO_PROXY=127.0.0.1,localhost' /etc/environment || \ + echo 'NO_PROXY=127.0.0.1,localhost' >> /etc/environment + - | + export HTTP_PROXY=http://127.0.0.1:3128 + export HTTPS_PROXY=http://127.0.0.1:3128 + export NO_PROXY=127.0.0.1,localhost + apt-get update + apt-get install -y nginx + - systemctl enable vncserver.service + - systemctl start vncserver.service + - systemctl enable novnc.service + - systemctl start novnc.service + - ln -sf /etc/nginx/sites-available/vnc_proxy /etc/nginx/sites-enabled/ + - systemctl enable nginx + - systemctl start nginx + - systemctl restart nginx + - | + DEFAULT_USER="\${USERNAME:-ubuntu}" + if ! getent group nopasswdlogin > /dev/null; then + addgroup nopasswdlogin + fi + if ! id -u \${DEFAULT_USER} > /dev/null 2>&1; then + adduser \${DEFAULT_USER} nopasswdlogin || true + fi + passwd -d \${DEFAULT_USER} || true + echo "@reboot \${DEFAULT_USER} DISPLAY=:0 /home/\${DEFAULT_USER}/disable_autolock.sh" | crontab -u \${DEFAULT_USER} - + cat << 'EOF' > "/home/\${DEFAULT_USER}/disable_autolock.sh" + #!/bin/bash + if [ -z "\${DISPLAY}" ]; then + echo "No DISPLAY available. Skipping GUI settings." + else + dbus-launch gsettings set org.gnome.desktop.screensaver lock-enabled false + dbus-launch gsettings set org.gnome.desktop.session idle-delay 0 + fi + EOF + chmod +x "/home/\${DEFAULT_USER}/disable_autolock.sh" + chown \${DEFAULT_USER}: "/home/\${DEFAULT_USER}/disable_autolock.sh" + mkdir -p /usr/local/share/jupyter/kernels/python3 + cat > /usr/local/share/jupyter/kernels/python3/kernel.json << EOF + { + "argv": ["/usr/bin/python3", "-m", "ipykernel_launcher", "-f", "{connection_file}"], + "display_name": "Python 3", + "language": "python", + "env": { + "HTTP_PROXY": "http://127.0.0.1:3128", + "HTTPS_PROXY": "http://127.0.0.1:3128", + "NO_PROXY": "127.0.0.1,localhost" + } + } + EOF + - sleep 10 + - systemctl daemon-reload + - netplan apply + - systemctl enable dummy-eth0.service + - systemctl start dummy-eth0.service + - rm -rf /usr/local/MATLAB/R2022b/licenses/* +`; \ No newline at end of file diff --git a/packages/itmat-cores/src/lxd/lxd.util.ts b/packages/itmat-cores/src/lxd/lxd.util.ts new file mode 100644 index 000000000..b0a8014a4 --- /dev/null +++ b/packages/itmat-cores/src/lxd/lxd.util.ts @@ -0,0 +1,35 @@ +import {LxdConfiguration} from '@itmat-broker/itmat-types'; + +// sanitized the update payload +export const sanitizeUpdatePayload = (payload: LxdConfiguration) => { + const sanitizedPayload: LxdConfiguration = { ...payload }; + + // Check if config property exists + if (sanitizedPayload.config) { + // Check and format CPU limit + if (typeof sanitizedPayload.config['limits.cpu'] !== 'string') { + sanitizedPayload.config['limits.cpu'] = '2'; // Default to '2' if not a string + } else { + sanitizedPayload.config['limits.cpu'] = sanitizeCpuLimit(sanitizedPayload.config['limits.cpu']); + } + + // Check and format memory limit + if (typeof sanitizedPayload.config['limits.memory'] !== 'string') { + sanitizedPayload.config['limits.memory'] = '16GB'; // Default to '16GB' if not a string + } else { + sanitizedPayload.config['limits.memory'] = sanitizeMemoryLimit(sanitizedPayload.config['limits.memory']); + } + } + + return sanitizedPayload; +}; + +// Sanitize CPU limit +const sanitizeCpuLimit = (cpuLimit: string): string => { + return cpuLimit ? cpuLimit.toString() : '2'; // Default to '2' if not provided +}; + +// Sanitize memory limit +const sanitizeMemoryLimit = (memoryLimit: string): string => { + return memoryLimit ? memoryLimit.toString() : '16GB'; // Default to '16GB' if not provided +}; \ No newline at end of file diff --git a/packages/itmat-cores/src/lxd/lxdManager.ts b/packages/itmat-cores/src/lxd/lxdManager.ts new file mode 100644 index 000000000..d3729d664 --- /dev/null +++ b/packages/itmat-cores/src/lxd/lxdManager.ts @@ -0,0 +1,615 @@ +import * as https from 'https'; +import * as fs from 'fs'; +import { WebSocket } from 'ws'; +import axios, { AxiosInstance, isAxiosError } from 'axios'; +import axiosRetry from 'axios-retry'; +import { LXDInstanceState, LxdConfiguration, Cpu, Memory, Storage, Gpu , LxdOperation, LxdGetInstanceConsoleResponse} from '@itmat-broker/itmat-types'; +import { Logger } from '@itmat-broker/itmat-commons'; +import { sanitizeUpdatePayload } from './lxd.util'; +import { IConfiguration } from '../utils/configManager'; + + +export class LxdManager { + private lxdInstance!: AxiosInstance; + private lxdAgent!: https.Agent; + config: IConfiguration; + + constructor(config: IConfiguration) { + this.config = config; + // Initialize synchronously to avoid async constructor + void this.initialize(); + } + + private async initialize(): Promise { + try { + await this.initializeLXDClient(); + } catch (error) { + Logger.error('Failed to initialize LXD manager: ' + error); + throw error; + } + } + + private async initializeLXDClient(): Promise { + try { + let lxdSslCert = ''; + let lxdSslKey = ''; + + // Load the SSL certificate and key with error handling + try { + if (!this.config.lxdCertFile['cert']) { + Logger.warn('LXD SSL certificate path/content is empty'); + } else if (this.config.lxdCertFile['cert'].includes('-----BEGIN')) { + lxdSslCert = this.config.lxdCertFile['cert']; + Logger.log('Using provided LXD SSL certificate content'); + } else { + try { + lxdSslCert = fs.readFileSync(this.config.lxdCertFile['cert'], 'utf8'); + Logger.log('Loaded LXD SSL certificate from file'); + } catch (fileError) { + Logger.error(`Failed to read LXD SSL certificate file: ${fileError}`); + // Continue with empty cert - this will fail later but not crash the app + } + } + + if (!this.config.lxdCertFile['key']) { + Logger.warn('LXD SSL key path/content is empty'); + } else if (this.config.lxdCertFile['key'].includes('-----BEGIN')) { + lxdSslKey = this.config.lxdCertFile['key']; + Logger.log('Using provided LXD SSL key content'); + } else { + try { + lxdSslKey = fs.readFileSync(this.config.lxdCertFile['key'], 'utf8'); + Logger.log('Loaded LXD SSL key from file'); + } catch (fileError) { + Logger.error(`Failed to read LXD SSL key file: ${fileError}`); + // Continue with empty key - this will fail later but not crash the app + } + } + } catch (certError) { + Logger.error(`Error processing LXD certificates: ${certError}`); + // Continue with empty cert/key + } + + // Don't proceed with HTTPS agent creation if certificates are empty + if (!lxdSslCert || !lxdSslKey) { + Logger.error('Cannot create HTTPS agent: LXD SSL certificate or key is missing'); + return; // Exit early but don't throw - allows app to start in degraded mode + } + + // Create HTTPS agent with proper error handling + try { + this.lxdAgent = new https.Agent({ + cert: lxdSslCert, + key: lxdSslKey, + rejectUnauthorized: this.config.lxdRejectUnauthorized, + timeout: 60000, // Increased timeout for HTTPS agent + keepAlive: true + }); + + // Initialize axios instance with retry logic + this.lxdInstance = axios.create({ + baseURL: this.config.lxdEndpoint, + httpsAgent: this.lxdAgent, + timeout: 60000, + validateStatus: (status) => status < 500, + maxRedirects: 10 + }); + + axiosRetry(this.lxdInstance, { + retries: 3, + retryDelay: (retryCount) => retryCount * 1000, // Exponential backoff + retryCondition: (error) => + error.code === 'ECONNABORTED' || (error.response?.status ?? 0) >= 500 + }); + + Logger.log('LXD client initialized successfully'); + } catch (agentError) { + Logger.error(`Failed to create HTTPS agent: ${agentError}`); + // Don't throw - allow app to start without LXD capabilities + } + + } catch (error) { + Logger.error(`Failed to initialize LXD client: ${error}`); + // Don't throw - allow app to start without LXD capabilities + } + } + + // Add a separate method to test the connection + async testConnection(): Promise { + try { + if (!this.isInitialized()) { + await this.initialize(); + } + const __unusedResponse = await this.lxdInstance.get('/1.0'); + Logger.log('LXD connection test successful'); + return true; + } catch (error) { + Logger.warn(`LXD connection test failed: ${error}`); + return false; + } + } + + // Add isInitialized check method + private isInitialized(): boolean { + return this.lxdInstance !== undefined && this.lxdAgent !== undefined; + } + + // Add method to ensure initialization + private async ensureInitialized(): Promise { + if (!this.isInitialized()) { + await this.initialize(); + } + } + + // get resources of lxd server. + async getResources() { + await this.ensureInitialized(); + try { + const project = this.config.lxdProject || 'default'; + const response = await this.lxdInstance.get(`/1.0/resources?project=${project}`); + const data = response.data.metadata; + + return { + data: { + cpu: data.cpu as Cpu, + memory: data.memory as Memory, + storage: data.storage as Storage, + gpu: data.gpu as Gpu + } + }; + + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + Logger.error('getResources axios error' + error.message); + return { + error: true, + data: error.message + }; + } + Logger.error('getResources unknown error 1' + error); + return { + error: true, + data: String(error) + }; + } + } + + // getProfile + async getProfile(profileName: string, project: string) { + try { + const response = await this.lxdInstance.get(`/1.0/profiles/${encodeURIComponent(profileName)}?project=${project}`); + if (response.status === 200) { + return { + data: response.data.metadata // assuming this is the format in which LXD returns profile data + }; + } else { + Logger.error(`Failed to fetch profile data. ${response.data}`); + return { + error: true, + data: `Failed to fetch profile data. ${response.data}` + }; + } + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + Logger.error(`Failed to fetch profile data. ${error.response}`); + return { + error: true, + data: `Failed to fetch profile data. ${error.response}` + }; + } else { + Logger.error('Error fetching profile data from LXD:' + error); + return { + error: true, + data: String(error) + }; + } + } + } + + // This should almost never be run, only for admin user + async getInstances() { + await this.ensureInitialized(); + try { + const project = this.config.lxdProject || 'default'; + const instanceUrls = await this.lxdInstance.get(`/1.0/instances?project=${project}`); + const instances = await Promise.allSettled(instanceUrls.data.metadata.map(async (instanceUrl: string) => await this.lxdInstance.get(instanceUrl))); + + const sanitizedInstances = instances.map((instance) => { + if (instance.status === 'fulfilled') { + const { metadata } = instance.value.data; + return { + name: metadata.name, + description: metadata.description, + status: metadata.status, + statusCode: metadata.status_code, + profiles: metadata.profiles, + type: metadata.type, + architecture: metadata.architecture, + creationDate: metadata.created_at, + lastUsedDate: metadata.last_used_at, + username: metadata.config['user.username'] || 'N/A', + cpuLimit: metadata.config['limits.cpu'] || 'N/A', + memoryLimit: metadata.config['limits.memory'] || 'N/A' + }; + } else { + Logger.error('Error fetching instance data: ' + instance.reason); + return null; + } + }).filter(instance => instance !== null); + + return { + data: sanitizedInstances + }; + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + Logger.error('getInstances axios error' + error.message); + return { + error: true, + data: error.message + }; + } + Logger.error('getInstances unknown error 1' + error); + return { + error: true, + data: String(error) + }; + } + } + + async getInstanceInfo(instanceName: string, project: string) { + instanceName = encodeURIComponent(instanceName); + return await this.lxdInstance.get(`/1.0/instances/${instanceName}?project=${project}`); + } + + async getInstanceState(instanceName: string, project: string) { + instanceName = encodeURIComponent(instanceName); + try { + const response = await this.lxdInstance.get(`/1.0/instances/${instanceName}/state?project=${project}`); + const instanceState: LXDInstanceState = response.data.metadata; + return { + data: instanceState + }; + } catch (error: unknown) { + + Logger.error(`Error fetching Instance ${instanceName} state from LXD:` + error); + if (axios.isAxiosError(error)) { + Logger.error(`Error fetching Instance state from LXD: ${error.message}`); + if (error.response) { + Logger.error(`Status: ${error.response.status}`); + Logger.error(`Headers: ${JSON.stringify(error.response.headers)}`); + Logger.error(`Data: ${JSON.stringify(error.response.data)}`); + } + } else { + Logger.error(`Error fetching Instance state from LXD: ${String(error)}`); + } + return { + error: true, + data: String(error) + }; + } + } + + + /** + * Get the instance location and the host IP by its node name + * For cluster mode: GET /1.0/cluster/members/{nodeName} + * For single node: Use server environment information + * + * @param nodeName - The name of the cluster node + * @returns Promise - The host IP address + */ + async getInstanceHostIp(nodeName: string): Promise { + await this.ensureInitialized(); + + try { + // First check if it's in a cluster + const serverInfo = await this.lxdInstance.get('/1.0'); + const isClustered = serverInfo.data.metadata?.environment?.server_clustered || false; + + if (isClustered) { + try { + // cluster mode, get the specific node info + const response = await this.lxdInstance.get(`/1.0/cluster/members/${encodeURIComponent(nodeName)}`); + + if (response.status === 200) { + // Check for custom public IP field first + const publicIpAddress = response.data.metadata?.config?.['user.public_address']; + if (publicIpAddress) { + const publicIp = publicIpAddress.replace(/^https?:\/\//, ''); + // regard it to like as single host ip or the url like 'https://146.169.10.90:8443' + const publicHostIp = publicIp.includes(':') ? publicIp.split(':')[0] : publicIp; + // Logger.log(`Using custom public IP ${publicHostIp} for node ${nodeName}`); + return publicHostIp; + } + + // Fall back to URL if no custom IP is set + if (response.data.metadata?.url) { + // Extract the host from URL (e.g., "https://sample-project-lxd-c1-0-1:8443" → "sample-project-lxd-c1-0-1") + const url = new URL(response.data.metadata.url); + return url.hostname; + } + } + } catch (clusterError) { + Logger.error(`Error fetching cluster member "${nodeName}" info: ${clusterError}`); + throw clusterError; + // Fall through to return the default server address + } + } + + // For non-clustered environments or if cluster member lookup failed + // Use the server's primary address + if (serverInfo.data.metadata?.environment?.addresses && + serverInfo.data.metadata.environment.addresses.length > 0) { + const address = serverInfo.data.metadata.environment.addresses[0]; + return address.includes(':') ? address.split(':')[0] : address; + } + + // If no addresses found, use the hostname from the endpoint URL + const endpointUrl = new URL(this.config.lxdEndpoint); + return endpointUrl.hostname; + } catch (error) { + Logger.error(`Error fetching host IP from LXD: ${error}`); + throw error; + } + } + + /** + * Get the node name where an instance is running + * + * @param instanceName - The name of the instance + * @param project - The LXD project name + * @returns Promise - The node name where the instance is running + */ + async getInstanceLocation(instanceName: string, project: string): Promise { + await this.ensureInitialized(); + + try { + // Get instance info to find its location + const response = await this.lxdInstance.get( + `/1.0/instances/${encodeURIComponent(instanceName)}?project=${encodeURIComponent(project)}` + ); + + if (response.status !== 200) { + throw new Error(`Failed to get instance info: ${response.status} ${JSON.stringify(response.data)}`); + } + + // For clustered environments, the location field contains the node name + const location = response.data.metadata?.location; + + if (location) { + return location; + } + + // For non-clustered environments, extract hostname from the LXD endpoint + const serverInfo = await this.lxdInstance.get('/1.0'); + const serverName = serverInfo.data.metadata?.environment?.server_name; + + if (serverName) { + return serverName; + } + + // Last resort: use the hostname from the endpoint URL + const endpointUrl = new URL(this.config.lxdEndpoint); + return endpointUrl.hostname; + } catch (error) { + Logger.error(`Error determining instance location for ${instanceName}: ${error}`); + throw error; + } + } + + async getInstanceConsole(instanceName: string, options: { height: number; width: number; type: string; }): Promise { + try { + instanceName = encodeURIComponent(instanceName); + const project = this.config.lxdProject || 'default'; + const consoleInfo = await this.lxdInstance.post(`/1.0/instances/${instanceName}/console?project=${project}&wait=10`, options); + + return { + operationId: consoleInfo.data.metadata.id, + operationSecrets: consoleInfo.data.metadata.metadata.fds + }; + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + Logger.error(`getInstanceConsole unknown error : ${error.message}`); + return { + error: true, + data: error.message + }; + } + Logger.error(`getInstanceConsole unknown error : ${error}`); + return { + error: true, + data: String(error) + }; + } + } + + async getInstanceConsoleLog(instanceName: string) { + instanceName = encodeURIComponent(instanceName); + const project = this.config.lxdProject || 'default'; + try { + const response = await this.lxdInstance.get(`/1.0/instances/${instanceName}/console?project=${project}`); + if (response.status === 200) { + return response.data; + } else { + const errorMessage = `Failed to fetch Logger log data. Status: ${response.status}, Data: ${JSON.stringify(response.data)}`; + Logger.error(errorMessage); + return { + error: true, + data: errorMessage + }; + } + } catch (error: unknown) { + let errorMessage = 'Error fetching instance Logger log from LXD:'; + if (axios.isAxiosError(error)) { + if (error.response) { + if (error.response.status === 404) { + errorMessage = `Logger log file not found. Status: ${error.response.status}, Data: ${JSON.stringify(error.response.data)}`; + Logger.error(errorMessage); + // Return an empty string to the frontend + return { + error: false, + data: '' + }; + } else { + errorMessage = `Failed to fetch Logger log data. Status: ${error.response.status}, Data: ${JSON.stringify(error.response.data)}`; + } + } else { + errorMessage = `Axios error without response: ${error.message}`; + } + } else { + errorMessage = `Unknown error: ${String(error)}`; + } + Logger.error(errorMessage); + return { + error: true, + data: errorMessage + }; + } + } + + async getOperations() { + try { + const project = this.config.lxdProject || 'default'; + // add project to the url + const operationUrls = await this.lxdInstance.get(`/1.0/operations?project=${project}`); + return { + data: operationUrls.data.metadata + }; + } catch (e) { + if (isAxiosError(e)) { + Logger.error('getOperations axios error' + e.message); + return { + error: true, + data: e.message + }; + } + Logger.error('getOperations unknown error' + e); + return { + error: true, + data: e + }; + } + } + + async getOperationStatus(operationUrl: string) { + try { + const opResponse = await this.lxdInstance.get(operationUrl); + return opResponse.data; + } catch (error) { + Logger.error('Error fetching operation status from LXD:' + error); + throw error; + } + } + + getOperationSocket(operationId: string, operationSecret: string) { + operationId = encodeURIComponent(operationId); + operationSecret = encodeURIComponent(operationSecret); + const containerConsoleSocket = new WebSocket(`wss://${this.config.lxdEndpoint.replace('https://', '')}/1.0/operations/${operationId}/websocket?secret=${operationSecret}`, { + agent: this.lxdAgent + }); + containerConsoleSocket.binaryType = 'arraybuffer'; + return containerConsoleSocket; + } + + async createInstance(payload: LxdConfiguration, project: string) { + await this.ensureInitialized(); + try { + const lxdResponse = await this.lxdInstance.post(`/1.0/instances?project=${encodeURIComponent(project)}`, payload, { + headers: { 'Content-Type': 'application/json' }, + httpsAgent: this.lxdAgent, + timeout: 30000, // adjust as needed, + params: { force: true } // Add force parameter + }); + return lxdResponse.data; + } catch (error) { + if (isAxiosError(error)) { + console.error('Error response:', error.response?.data); + } + Logger.error(`Error creating instance ${payload.name} on LXD: ${JSON.stringify(error)}`); + throw new Error(`Error creating instance ${payload.name} on LXD`); + } + } + + async updateInstance(instanceName: string, payload: LxdConfiguration, project: string) { + try { + const sanitizedPayload = sanitizeUpdatePayload(payload); + // Perform the PATCH request to LXD to update the instance configuration + const response = await this.lxdInstance.patch(`/1.0/instances/${encodeURIComponent(instanceName)}?project=${project}`, sanitizedPayload, { + headers: { 'Content-Type': 'application/json' } + }); + return response.data; // Return the response or format as needed + } catch (error) { + Logger.error(`Error updating instance ${instanceName} on LXD:` + error); + throw error; + } + } + + async startStopInstance(instanceName: string, action: string, project: string) { + try { + const response = await this.lxdInstance.put(`/1.0/instances/${instanceName}/state?project=${project}`, { + action: action, + timeout: 300, // adjust as needed, + force: true // Add force parameter + }); + return response.data; + } catch (error) { + Logger.error(`Error ${action} instance ${instanceName} on LXD: ${error}`); + throw error; // Propagate the error + } + } + + // restart , set the stateful to true + + async deleteInstance(instanceName: string, project: string) { + try { // Ensure correct encoding and reduce verbosity for routine operations + instanceName = encodeURIComponent(instanceName); + // Commented out to reduce log verbosity for routine operations + // Logger.warn(`Attempting to delete instance ${instanceName} in project ${project}`); + + // First check if instance exists to provide clearer error messages + try { + await this.getInstanceState(instanceName, project); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + if (errorMsg.includes('404') || errorMsg.includes('Instance not found')) { // Commented out to reduce log verbosity for routine operations + // Logger.warn(`Instance ${instanceName} not found in project ${project} - may have been already deleted`); + return { + data: { + message: `Instance ${instanceName} already deleted or not found` + } + }; + } + // For other errors, continue with deletion attempt + } + + const response = await this.lxdInstance.delete(`/1.0/instances/${instanceName}?project=${project}`, { + params: { force: true } // Add force parameter + }); + + // Logger.log(`Successfully initiated deletion for instance ${instanceName}`); + return response.data; + } catch (error) { + // Enhanced error handling with more detailed information + if (isAxiosError(error)) { + const statusCode = error.response?.status; + const errorData = error.response?.data; + + Logger.error(`Error deleting instance ${instanceName} on LXD (Status ${statusCode}): ${JSON.stringify(errorData)}`); + + // Return a more structured error + return { + error: true, + status: statusCode, + message: `Error deleting instance: ${error.message}`, + details: errorData + }; + } + + Logger.error(`Unexpected error deleting instance ${instanceName} on LXD: ${error}`); + return { + error: true, + message: `Unexpected error: ${error instanceof Error ? error.message : String(error)}` + }; + } + } +} diff --git a/packages/itmat-cores/src/rest/fileDownload.ts b/packages/itmat-cores/src/rest/fileDownload.ts new file mode 100644 index 000000000..318fc7824 --- /dev/null +++ b/packages/itmat-cores/src/rest/fileDownload.ts @@ -0,0 +1,156 @@ +import { Request, Response } from 'express'; +import { CoreError, ILog, IUserWithoutToken, defaultSettings, enumAPIResolver, enumCoreErrors, enumEventStatus, enumEventType, enumFileCategories } from '@itmat-broker/itmat-types'; +import jwt from 'jsonwebtoken'; +import { userRetrieval } from '../authentication/pubkeyAuthentication'; +import { DBType } from '../database/database'; +import { ObjectStore } from '@itmat-broker/itmat-commons'; +import { PermissionCore } from '../coreFunc/permissionCore'; +import { v4 as uuid } from 'uuid'; +export class FileDownloadController { + private _permissionCore: PermissionCore; + private _db: DBType; + private _objStore: ObjectStore; + + constructor(db: DBType, objStore: ObjectStore) { + this._db = db; + this._permissionCore = new PermissionCore(db); + this._objStore = objStore; + } + + public fileDownloadController = (req: Request, res: Response) => { + (async () => { + const requester = req.user as IUserWithoutToken; + const requestedFile = req.params['fileId']; + const token = req.headers.authorization || ''; + let associatedUser = requester; + + const log: ILog = { + id: uuid(), + requester: req.user?.username ?? 'NA', + type: enumEventType.API_LOG, + apiResolver: enumAPIResolver.FILE, + event: 'FileDownload', + parameters: { + fileId: requestedFile + }, + status: enumEventStatus.FAIL, + errors: '', + timeConsumed: null, + life: { + createdTime: Date.now(), + createdUser: requester?.id || 'anonymous', + deletedTime: null, + deletedUser: null + }, + metadata: { + } + }; + + // Handle authentication first, before any file operations + if (!requester && token === '') { + res.status(403).json({ error: 'Please log in.' }); + await this._db.collections.log_collection.insertOne({ + ...log, + errors: 'Please log in.' + }); + return; + } + + const file = await this._db.collections.files_collection.findOne({ 'id': requestedFile, 'life.deletedTime': null }); + + if (!file || file.fileCategory !== enumFileCategories.DOMAIN_FILE) { + if ((token !== '') && (req.user === undefined)) { + // get the decoded payload ignoring signature, no symmetric secret or asymmetric key needed + const decodedPayload = jwt.decode(token); + // obtain the public-key of the robot user in the JWT payload + let pubkey: string; + if (decodedPayload !== null && !(typeof decodedPayload === 'string')) { + pubkey = decodedPayload['publicKey']; + } else { + await this._db.collections.log_collection.insertOne({ + ...log, + errors: 'JWT verification failed.' + }); + throw new CoreError( + enumCoreErrors.AUTHENTICATION_ERROR, + 'JWT verification failed.' + ); + } + // verify the JWT + try { + jwt.verify(token, pubkey); + } catch (__unused) { + await this._db.collections.log_collection.insertOne(log); + throw new CoreError( + enumCoreErrors.AUTHENTICATION_ERROR, + 'JWT verification failed.' + ); + } + associatedUser = await userRetrieval(this._db, pubkey); + } else if (!requester) { + res.status(403).json({ error: 'Please log in.' }); + await this._db.collections.log_collection.insertOne({ + ...log, + errors: 'Please log in.' + }); + return; + } + } + try { + /* download file */ + if (!file) { + res.status(404).json({ error: 'File not found' }); + await this._db.collections.log_collection.insertOne({ + ...log, + errors: 'File not found' + }); + return; + } + + // check target field exists + if (file.studyId) { + const roles = await this._permissionCore.getRolesOfUser(associatedUser, associatedUser.id, file.studyId); + if (!roles.length) { + res.status(404).json({ error: 'You do not have the necessary permission.' }); + await this._db.collections.log_collection.insertOne({ + ...log, + errors: 'You do not have the necessary permission.' + }); + return; + } + } + let buckerId = ''; + if (file.fileCategory === enumFileCategories.STUDY_DATA_FILE) { + buckerId = file.studyId || ''; + } else if (file.fileCategory === enumFileCategories.USER_DRIVE_FILE) { + buckerId = defaultSettings.userConfig.defaultFileBucketId; + } else if (file.fileCategory === enumFileCategories.CACHE) { + buckerId = defaultSettings.cacheConfig.defaultFileBucketId; + } else if (file.fileCategory === enumFileCategories.DOMAIN_FILE) { + buckerId = defaultSettings.domainConfig.defaultFileBucketId; + } else if (file.fileCategory === enumFileCategories.PROFILE_FILE) { + buckerId = defaultSettings.systemConfig.defaultProfileBucketId; + } + await this._db.collections.log_collection.insertOne({ + ...log, + status: enumEventStatus.SUCCESS, + errors: '' + }); + const stream = await this._objStore.downloadFile(buckerId, file.uri); + res.set('Content-Type', 'application/octet-stream'); + res.set('Content-Type', 'application/download'); + res.set('Content-Disposition', `attachment; filename="${file.fileName}"`); + stream.pipe(res, { end: true }); + return; + } catch (e) { + await this._db.collections.log_collection.insertOne({ + ...log, + status: enumEventStatus.FAIL, + errors: e instanceof Error ? e.message : 'Unknown error' + }); + res.status(500).json(e); + return; + } + })().catch(() => { return; }); + }; +} diff --git a/packages/itmat-cores/src/utils/cache.ts b/packages/itmat-cores/src/utils/cache.ts new file mode 100644 index 000000000..cac89d2dc --- /dev/null +++ b/packages/itmat-cores/src/utils/cache.ts @@ -0,0 +1,54 @@ +import { FileUpload, IUserWithoutToken, enumFileCategories, enumFileTypes } from '@itmat-broker/itmat-types'; +import { FileCore } from '../coreFunc/fileCore'; +import { v4 as uuid } from 'uuid'; +import { Readable } from 'stream'; +import { ObjectStore } from '@itmat-broker/itmat-commons'; + +export async function convertToBufferAndUpload(fileCore: FileCore, requester: IUserWithoutToken, jsonObject: Record | Record[]) { + + const fileUpload: FileUpload = { + filename: uuid(), + mimetype: 'application/json', + encoding: 'utf-8', + createReadStream: () => { + const jsonString = JSON.stringify(jsonObject); + const stream = new Readable(); + stream.push(jsonString); + stream.push(null); // Indicates the end of the stream + return stream; + } + + }; + + const res = await fileCore.uploadFile( + requester, + null, + null, + fileUpload, + enumFileTypes.JSON, + enumFileCategories.CACHE, + undefined, + undefined + ); + return res; +} + +export async function getJsonFileContents(objStore: ObjectStore, buckerId: string, uri: string): Promise> { + try { + // Get the Readable stream from the downloadFile method + const stream = await objStore.downloadFile(buckerId, uri); + + // Accumulate the stream data + const chunks: Buffer[] = []; + for await (const chunk of stream) { + chunks.push(Buffer.from(chunk)); + } + + // Convert the accumulated data to a string + const data = Buffer.concat(chunks).toString('utf-8'); + // Parse and return the JSON object + return JSON.parse(data); + } catch { + throw new Error('Failed to fetch and parse JSON file.'); + } +} diff --git a/packages/itmat-cores/src/utils/configManager.ts b/packages/itmat-cores/src/utils/configManager.ts new file mode 100644 index 000000000..f6cdb6e78 --- /dev/null +++ b/packages/itmat-cores/src/utils/configManager.ts @@ -0,0 +1,56 @@ +import merge from 'deepmerge'; +import fs from 'fs-extra'; +import path from 'path'; +import { IObjectStoreConfig, IDatabaseBaseConfig, Logger } from '@itmat-broker/itmat-commons'; +import configDefaults from '../../config/config.sample.json'; +import { IServerConfig } from './server.js'; +import chalk from 'chalk'; +import SMTPTransport from 'nodemailer/lib/smtp-transport'; + +export interface IConfiguration extends IServerConfig { + appName: string; + database: IDatabaseBaseConfig; + objectStore: IObjectStoreConfig; + nodemailer: SMTPTransport.Options & { auth: { user: string, pass: string } }; + aesSecret: string; + sessionsSecret: string; + adminEmail: string; + aeEndpoint: string; + useWebdav: boolean; + webdavPort: number; + webdavServer: string; + lxdEndpoint: string; + lxdPortRange: number[]; // the port range for lxd a list of numbers + lxdSquidProxy: string; // the squid proxy server for lxd + lxdStoragePool: string; + lxdProject: string; + systemKey: Record; + lxdCertFile: Record; + lxdRejectUnauthorized: boolean; + jupyterPort: number; + dmp: Record; +} + +export class ConfigurationManager { + + public static expand(configurationFiles: string[]): IConfiguration { + + let config = configDefaults; + Logger.log('Applied default configuration.'); + + configurationFiles.forEach((configurationFile) => { + try { + if (fs.existsSync(configurationFile)) { + const content = fs.readFileSync(configurationFile, 'utf8'); + config = merge(config, JSON.parse(content)); + Logger.log(`Applied configuration from ${path.resolve(configurationFile)}.`); + } + } catch (__unused__exception) { + Logger.error(chalk.red('Cannot parse configuration file.')); + } + }); + + return config; + } + +} diff --git a/packages/itmat-interface/src/utils/definition.ts b/packages/itmat-cores/src/utils/definition.ts similarity index 100% rename from packages/itmat-interface/src/utils/definition.ts rename to packages/itmat-cores/src/utils/definition.ts diff --git a/packages/itmat-interface/src/graphql/errors.ts b/packages/itmat-cores/src/utils/errors.ts similarity index 100% rename from packages/itmat-interface/src/graphql/errors.ts rename to packages/itmat-cores/src/utils/errors.ts diff --git a/packages/itmat-cores/src/utils/index.ts b/packages/itmat-cores/src/utils/index.ts new file mode 100644 index 000000000..e9ea9202b --- /dev/null +++ b/packages/itmat-cores/src/utils/index.ts @@ -0,0 +1,12 @@ +export * from './configManager'; +export * from './definition'; +export * from './errors'; +export * from './mfa'; +export * from './noop'; +export * from './pubkeycrypto'; +export * from './pubsub'; +export * from './query'; +export * from './responses'; +export * from './server'; +export * from './userLoginUtils'; +export * from './cache'; diff --git a/packages/itmat-interface/src/utils/mfa.ts b/packages/itmat-cores/src/utils/mfa.ts similarity index 100% rename from packages/itmat-interface/src/utils/mfa.ts rename to packages/itmat-cores/src/utils/mfa.ts diff --git a/packages/itmat-cores/src/utils/noop.ts b/packages/itmat-cores/src/utils/noop.ts new file mode 100644 index 000000000..5e791dcc8 --- /dev/null +++ b/packages/itmat-cores/src/utils/noop.ts @@ -0,0 +1 @@ +export default (): void => { return; }; diff --git a/packages/itmat-interface/src/utils/pubkeycrypto.ts b/packages/itmat-cores/src/utils/pubkeycrypto.ts similarity index 91% rename from packages/itmat-interface/src/utils/pubkeycrypto.ts rename to packages/itmat-cores/src/utils/pubkeycrypto.ts index 415629876..b1579f00a 100644 --- a/packages/itmat-interface/src/utils/pubkeycrypto.ts +++ b/packages/itmat-cores/src/utils/pubkeycrypto.ts @@ -15,8 +15,8 @@ export function rsasigner(privateKey: string, message: string, scheme = 'RSA-SHA signer.end(); return signature; } - catch (err: any) { - return err; + catch (err) { + return err instanceof Error ? err.message : String(err); } } @@ -42,8 +42,8 @@ export function reGenPkfromSk(privateKey: string, passphrase = 'idea-fast'): str }); return reGenPk.toString('base64'); } - catch (err: any) { - return err; + catch (err) { + return err instanceof Error ? err.message : String(err); } } @@ -142,11 +142,19 @@ export function eckeygen(curve = 'secp256k1') { return keyPair; } -export function tokengen(payload: string | Buffer | object, secret: string, passphrase = 'idea-fast', algorithm: Algorithm = 'RS256', life = 12000) { - // Asymmetric JWT is used by default by setting algorithm = RS256. +export function tokengen(payload: string | Buffer | object, secret: string, passphrase = 'idea-fast', algorithm: Algorithm = 'RS256', life = 120000) { + const options: jwt.SignOptions = { + algorithm: algorithm + }; + + // Only add expiresIn if life is specified + if (life !== undefined && life !== null) { + options.expiresIn = life; + } + return jwt.sign(payload, { key: secret, passphrase: passphrase }, - { algorithm: algorithm, expiresIn: life } + options ); } diff --git a/packages/itmat-interface/src/graphql/pubsub.ts b/packages/itmat-cores/src/utils/pubsub.ts similarity index 55% rename from packages/itmat-interface/src/graphql/pubsub.ts rename to packages/itmat-cores/src/utils/pubsub.ts index 92305d19b..62099a3cb 100644 --- a/packages/itmat-interface/src/graphql/pubsub.ts +++ b/packages/itmat-cores/src/utils/pubsub.ts @@ -1,7 +1,3 @@ -import { PubSub } from 'graphql-subscriptions'; - -export const pubsub = new PubSub(); - export const subscriptionEvents = { JOB_STATUS_CHANGE: 'JOB_STATUS_CHANGE', NEW_JOB: 'NEW_JOB' diff --git a/packages/itmat-cores/src/utils/query.ts b/packages/itmat-cores/src/utils/query.ts new file mode 100644 index 000000000..00d0187ed --- /dev/null +++ b/packages/itmat-cores/src/utils/query.ts @@ -0,0 +1,376 @@ +import { IStudy, IField, IStandardization, IQueryString, IGroupedData, StandardizationFilterOptionParameters, StandardizationFilterOptions, IRole } from '@itmat-broker/itmat-types'; +import { Filter } from 'mongodb'; +/* + queryString: + format: string # returned foramt: raw, standardized, grouped, summary + data_requested: array # returned fields + cohort: array[array] # filters + new_fields: array # new_fields + +*/ +// if has study-level permission, non versioned data will also be returned + +export function buildPipeline(studyId: string, roles: IRole[], fieldRecords: IField[], dataVersions: Array, fieldsRequested?: string[]) { + let fieldIds: string[] = fieldsRequested ? fieldsRequested : fieldRecords.map(el => el.fieldId); + fieldIds = fieldIds.filter(el => fieldRecords.map(el => el.fieldId).includes(el)); + + const facetFilters = {}; + for (const field of fieldRecords) { + facetFilters[field.fieldId] = [{ $match: { '_id.fieldId': field.fieldId } }]; + facetFilters[field.fieldId].push({ $unwind: '$docs' }); + facetFilters[field.fieldId].push({ + $group: { + _id: { + ...(field.properties ?? []).reduce((acc, curr) => { + acc[curr.name] = `$docs.properties.${curr.name}`; + return acc; + }, {}) + }, + doc: { $first: '$docs' } + } + }); + facetFilters[field.fieldId].push({ + $group: { + _id: '$_id', + doc: { $first: '$doc' } + } + }); + } + + const userPermissionFilters: Filter[] = []; + for (const role of roles) { + for (const dataPermission of role.dataPermissions) { + const obj: Filter = {}; + + // Combine regexes using $or + obj.$or = dataPermission.fields.map(regex => ({ fieldId: { $regex: regex } })); + + for (const property of Object.keys(dataPermission.dataProperties)) { + obj[`properties.${property}`] = { $in: dataPermission.dataProperties[property] }; + } + + obj.dataVersion = { $in: dataPermission.includeUnVersioned ? dataVersions : dataVersions.filter(el => el !== null) }; + userPermissionFilters.push(obj); + } + } + const pipeline = [{ + $match: { studyId: studyId, fieldId: { $in: fieldIds }, dataVersion: { $in: dataVersions } } + }, { + $sort: { 'life.createdTime': -1 } + }, { + $group: { + _id: { fieldId: '$fieldId' }, + docs: { $push: '$$ROOT' } + } + }, { + $facet: { + ...facetFilters + } + }, { + $project: { + docs: { + $setUnion: fieldIds.map(el => ({ + $ifNull: [`$${el}.doc`, []] + })) + } + } + }, { + $unwind: '$docs' + }, { + $replaceRoot: { newRoot: '$docs' } + }, { + $match: { 'life.deletedTime': { $eq: null } } + }, { + $match: { $or: userPermissionFilters } + }]; + + return pipeline; +} + +export function dataStandardization(study: IStudy, fields: IField[], data: IGroupedData, queryString: IQueryString, standardizations: IStandardization[] | null) { + if (!queryString['format'] || queryString['format'] === 'raw') { + return data; + } else if (queryString['format'] === 'grouped' || queryString['format'] === 'summary') { + return dataGrouping(data, queryString['format']); + } else if (standardizations && queryString['format'].startsWith('standardized')) { + return standardize(study, fields, data, standardizations); + } + return { error: 'Format not recognized.' }; +} + +// fields are obtained from called functions, providing the valid fields + + + +export function standardize(study: IStudy, fields: IField[], data: IGroupedData, standardizations: IStandardization[]) { + const records = {}; + const mergedFields: IField[] = [...fields]; + const seqNumMap: Map = new Map(); + for (const field of mergedFields) { + let fieldDef = {}; + for (let i = 0; i < mergedFields.length; i++) { + if (mergedFields[i].fieldId === field.fieldId) { + fieldDef = mergedFields[i]; + break; + } + } + // check if it is in the standardizations + let fieldIdentifier: string | string[] = field.fieldId; + if (!Array.isArray(field.fieldId)) { + fieldIdentifier = ['$' + field.fieldId]; + } + let standardization: IStandardization | undefined = undefined; + for (let i = 0; i < standardizations.length; i++) { + if (JSON.stringify(standardizations[i].field) === JSON.stringify(fieldIdentifier)) { + standardization = standardizations[i]; + break; + } + } + if (!standardization) { + continue; + } + if (!standardization.stdRules || standardization.stdRules.length === 0) { + continue; + } + for (const subjectId of Object.keys(data)) { + for (const visitId of Object.keys(data[subjectId])) { + if (data[subjectId][visitId][field.fieldId] === null || data[subjectId][visitId][field.fieldId] === undefined) { + continue; + } + const dataClip: Record = {}; + let isSkip = false; + if (!standardization.stdRules) { + continue; + } + for (const rule of standardization.stdRules) { + if (!rule.parameter) { + continue; + } + let filterKey = ''; + if (typeof dataClip[rule.entry] === 'string') { + filterKey = dataClip[rule.entry] as string; // Safe after type check + } else if (typeof dataClip[rule.entry] === 'number') { + filterKey = (dataClip[rule.entry] as number).toString(); + } + if (rule.filters !== undefined && rule.filters !== null && rule.filters[filterKey] !== undefined + && rule.filters[filterKey][0] === 'delete') { + continue; + } + switch (rule.source) { + case 'data': { + if (rule.parameter.length === 0) { + dataClip[rule.entry] = data[subjectId][visitId][field.fieldId] || ''; + } else { + const selectedFieldId = rule.parameter[0]; + const selectedVistId = rule.parameter.length === 2 ? rule.parameter[1] : visitId; + dataClip[rule.entry] = data[subjectId][selectedVistId][selectedFieldId] || ''; + } + break; + } + case 'fieldDef': { + dataClip[rule.entry] = fieldDef[rule.parameter[0]] || ''; + break; + } + case 'value': { + dataClip[rule.entry] = rule.parameter[0] || ''; + break; + } + // parameter is the path that start from + case 'inc': { + const value: number | undefined = incHelper(seqNumMap, rule.parameter, subjectId, visitId); + if (value === undefined) { + break; + } + dataClip[rule.entry] = value; + break; + } + case 'reserved': { + switch (rule.parameter[0]) { + case 'm_subjectId': { + dataClip[rule.entry] = subjectId; + break; + } + case 'm_visitId': { + dataClip[rule.entry] = visitId; + break; + } + case 'm_studyId': { + dataClip[rule.entry] = study.id; + break; + } + } + break; + } + default: { + isSkip = true; + break; + } + } + // deal with filters + // support two ways: convert to another value, delete this value; input should be [delete/convert, $value] + if (rule.filters) { + let filterKey = ''; + if (typeof dataClip[rule.entry] === 'string') { + filterKey = dataClip[rule.entry] as string; // Safe after type check + } else if (typeof dataClip[rule.entry] === 'number') { + filterKey = (dataClip[rule.entry] as number).toString(); + } + if (Object.keys(rule.filters).includes(filterKey)) { + if (rule.filters[filterKey].length !== 2) { + continue; + } + switch (rule.filters[filterKey][0]) { + // add patch to allow to convert to another field value + case 'convert': { + const options: StandardizationFilterOptionParameters | StandardizationFilterOptions = rule.filters[filterKey][1]; + if (typeof options !== 'object') { + break; + } + if ('source' in options) { + if (options.source === 'value') { + dataClip[rule.entry] = options.parameter; + } else if (options.source === 'data') { + dataClip[rule.entry] = data[subjectId][visitId][options.parameter]; + } + } + break; + } + case 'delete': { + isSkip = true; + break; + } + default: { + isSkip = true; + break; + } + } + } + } + } + if (isSkip) { + continue; + } + // deal with join + if (standardization.path) { + let pointer = insertInObj(records, standardization.path, undefined, true, subjectId, visitId); + if (pointer === undefined) { + pointer = insertInObj(records, standardization.path, [], true, subjectId, visitId); + } + if (standardization.joinByKeys && standardization.joinByKeys.length > 0) { + let isSame = true; + for (let i = 0; i < pointer.length; i++) { + isSame = true; + for (let j = 0; j < standardization.joinByKeys.length; j++) { + if (pointer[i][standardization.joinByKeys[j]] !== dataClip[standardization.joinByKeys[j]]) { + isSame = false; + break; + } + } + if (isSame) { + pointer[i] = { ...dataClip, ...pointer[i] }; + break; + } + } + if (isSame && pointer.length !== 0) { + insertInObj(records, standardization.path, pointer, true, subjectId, visitId); + } else { + insertInObj(records, standardization.path, dataClip, false, subjectId, visitId); + } + } else { + insertInObj(records, standardization.path, dataClip, false, subjectId, visitId); + } + } + } + } + } + return records; +} + +// ignore the subjectId, join values with same visitId and fieldId; with extra info +export function dataGrouping(data: IGroupedData, format: string) { + const joinedData: { + [key: string]: { + [key: string]: { + totalNumOfRecords: number, + validNumOfRecords: number, + data: unknown[] + } + } + } = {}; + for (const subjectId of Object.keys(data)) { + for (const visitId of Object.keys(data[subjectId])) { + for (const fieldId of Object.keys(data[subjectId][visitId])) { + if (['m_subjectId', 'm_visitId', 'm_versionId'].includes(fieldId)) { + continue; + } else { + if (joinedData[fieldId] === undefined) { + joinedData[fieldId] = {}; + } + if (joinedData[fieldId][visitId] === undefined) { + joinedData[fieldId][visitId] = { + totalNumOfRecords: 0, + validNumOfRecords: 0, + data: [] + }; + } + if (data[subjectId][visitId][fieldId] !== '99999') { + joinedData[fieldId][visitId]['validNumOfRecords'] += 1; + // if summary mode; donot return data + } + if (format !== 'summary') { + joinedData[fieldId][visitId]['data'].push(data[subjectId][visitId][fieldId]); + } + joinedData[fieldId][visitId]['totalNumOfRecords'] += 1; + } + } + } + } + return joinedData; +} + +function incHelper(map: Map, levels: string[], subjectId: string, visitId: string) { + if (levels.length < 1) { + return; + } + let key = levels[0] === 'm_subjectId' ? subjectId : levels[0] === 'm_visitId' ? visitId : levels[0]; + for (let i = 1; i < levels.length; i++) { + const modifiedLevel = levels[i] === 'm_subjectId' ? subjectId : levels[0] === 'm_visitId' ? visitId : levels[0]; + key += `-${modifiedLevel}`; + } + map.set(key, (map.get(key) || 0) + 1); + return map.get(key) || 0; +} + +// recursively create object structures, return the last pointer +function insertInObj(obj, levels: string[], lastValue: unknown, join: boolean, subjectId: string, visitId: string) { + let pointer = obj; + for (let i = 0; i < levels.length; i++) { + let modifiedLevel = levels[i]; + if (levels[i] === 'm_subjectId') { + modifiedLevel = subjectId; + } else if (levels[i] === 'm_visitId') { + modifiedLevel = visitId; + } + if (i === levels.length - 1) { + if (lastValue) { + if (Array.isArray(pointer[modifiedLevel])) { + if (join) { + pointer[modifiedLevel] = lastValue; + } else { + pointer[modifiedLevel].push(lastValue); + } + } else { + pointer[modifiedLevel] = lastValue; + } + } + pointer = pointer[modifiedLevel]; + break; + } + if (pointer[modifiedLevel] === undefined) { + pointer[modifiedLevel] = {}; + } + pointer = pointer[modifiedLevel]; + } + return pointer; +} + diff --git a/packages/itmat-interface/src/graphql/responses.ts b/packages/itmat-cores/src/utils/responses.ts similarity index 72% rename from packages/itmat-interface/src/graphql/responses.ts rename to packages/itmat-cores/src/utils/responses.ts index c342319ce..364dfe14e 100644 --- a/packages/itmat-interface/src/graphql/responses.ts +++ b/packages/itmat-cores/src/utils/responses.ts @@ -5,7 +5,7 @@ export interface IGenericResponse { description?: string; } -export function makeGenericReponse(id?: string, successful?: boolean, code?: string, description?: string): IGenericResponse { +export function makeGenericResponse(id?: string, successful?: boolean, code?: string, description?: string): IGenericResponse { const res: IGenericResponse = { id: id ?? undefined, successful: successful ?? true, diff --git a/packages/itmat-cores/src/utils/server.ts b/packages/itmat-cores/src/utils/server.ts new file mode 100644 index 000000000..41f6b5685 --- /dev/null +++ b/packages/itmat-cores/src/utils/server.ts @@ -0,0 +1,7 @@ +import { IServerBaseConfig } from '@itmat-broker/itmat-commons'; + +export interface IServerConfig extends IServerBaseConfig { + bcrypt: { + saltround: number + }; +} diff --git a/packages/itmat-cores/src/utils/userLoginUtils.ts b/packages/itmat-cores/src/utils/userLoginUtils.ts new file mode 100644 index 000000000..474593aac --- /dev/null +++ b/packages/itmat-cores/src/utils/userLoginUtils.ts @@ -0,0 +1,27 @@ +import { IUser, IUserWithoutToken } from '@itmat-broker/itmat-types'; +import { DBType } from '../database/database'; + +export class UserLoginUtils { + db: DBType; + constructor(db: DBType) { + this.db = db; + this.serialiseUser = this.serialiseUser.bind(this); + this.deserialiseUser = this.deserialiseUser.bind(this); + } + + public serialiseUser(user: Express.User, done: (__unused__err: unknown, __unused__id: string) => void) { + done(null, (user as IUser).username); + } + + public deserialiseUser(username: string, done: (__unused__err: unknown, __unused__id: IUserWithoutToken | null) => void) { + this._getUser(username) + .then(user => { + done(null, user); + }) + .catch(() => { return; }); + } + + private async _getUser(username: string): Promise { + return await this.db.collections.users_collection.findOne({ 'life.deletedTime': null, username }, { projection: { '_id': 0, 'life.deletedTime': 0, 'password': 0 } }); + } +} diff --git a/packages/itmat-cores/src/webdav/dmpWebDAV.ts b/packages/itmat-cores/src/webdav/dmpWebDAV.ts new file mode 100644 index 000000000..70ebdebc0 --- /dev/null +++ b/packages/itmat-cores/src/webdav/dmpWebDAV.ts @@ -0,0 +1,570 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { v2 as webdav } from 'webdav-server'; +import { Readable, Writable } from 'stream'; +import nodeFetch from 'node-fetch'; +import { IDrive, IUserWithoutToken, enumFileTypes } from '@itmat-broker/itmat-types'; +import { FileCore } from '../coreFunc/fileCore'; +import { DriveCore } from '../coreFunc/driveCore'; +import { DBType } from '../database/database'; +import { StudyCore } from '../coreFunc/studyCore'; +import { DataCore } from '../coreFunc/dataCore'; + +class DMPFileSystemSerializer implements webdav.FileSystemSerializer { + uid(): string { + return 'DMPFileSystem-1.0'; // Unique identifier with versioning + } + + serialize(fs: webdav.FileSystem, callback: webdav.ReturnCallback): void { + if (fs instanceof DMPFileSystem) { + // we won't actually serialize anything. + callback(undefined, {}); + } else { + callback(new Error('Invalid file system type'), undefined); + } + } + + unserialize(serializedData: unknown, callback: webdav.ReturnCallback): void { + callback(undefined, undefined); + } +} + + +export class DMPFileSystem extends webdav.FileSystem { + router: unknown; + isCopying: boolean; + getFileResults: unknown; + currentStudy: unknown; + fileCore: FileCore; + driveCore: DriveCore; + studyCore: StudyCore; + dataCore: DataCore; + db: DBType; + myDriveDirName: string; + studyDirName: string; + sharedDirName: string; + + constructor(db: DBType, fileCore: FileCore, driveCore: DriveCore, studyCore: StudyCore, dataCore: DataCore) { + super(new DMPFileSystemSerializer()); + this.db = db; + this.fileCore = fileCore; + this.driveCore = driveCore; + this.studyCore = studyCore; + this.dataCore = dataCore; + this.isCopying = false; + + // reserved key words + this.myDriveDirName = 'My Drive'; + this.studyDirName = 'Study'; + this.sharedDirName = 'Shared'; + } + + override async _fastExistCheck(ctx: webdav.RequestContext, path: webdav.Path, callback: (exists: boolean) => void): Promise { + const pathStr = path.toString(); + const pathArr = pathToArray(pathStr); + const user = ctx.user as any; + + if (pathStr === '/') { + callback(true); + return; + } else { + if (pathArr[0] === 'My Drive') { + if (this.isCopying) { + callback(true); + return; + } + const userFileDir: Record = await this.driveCore.getDrives(user); + const ownUserFiles = userFileDir[user.id]; + if (!ownUserFiles) { + callback(false); + return; + } + const allPaths = ownUserFiles.map((el: { path: string[]; }) => + el.path + .map((ek: string) => ownUserFiles.filter((es: { id: string; }) => es.id === ek)?.[0]?.name) + .filter((name: string | undefined) => name !== undefined) + ); + callback(isPathIncluded(pathArr, allPaths)); + return; + } else if (pathArr[0] === 'Shared') { + if (pathArr.length == 1) { + callback(true); + return; + } + const usersFileDir: Record = await this.driveCore.getDrives(user); + // query the database directly as the API call requires authorisation + const users = await this.db.collections.users_collection.find({ 'life.deletedTime': null }).toArray(); + + let allPaths: string[][] = []; + for (const userId of Object.keys(usersFileDir)) { + if (userId === user.id) { + continue; + } + const owner = users.filter((el: { id: string; }) => el.id === userId)[0]; + const ownUserFiles = usersFileDir[userId].filter(el => el.path); + const validFileIds: string[] = ownUserFiles.map(el => el.id); + for (const item of ownUserFiles) { + item.path = item.path.filter(el => validFileIds.includes(el)); + } + const partialPaths: string[][] = ownUserFiles.map((el: { path: string[]; }) => el.path.map((ek: string) => ownUserFiles.filter(es => es.id === ek)[0].name)); + partialPaths.forEach((element: string[]) => { + element.unshift(owner ? `${owner.firstname} ${owner.lastname}` : 'NA'); + element.unshift('Shared'); + }); + allPaths = [...allPaths, ...partialPaths]; + } + callback(isPathIncluded(pathArr, allPaths)); + return; + } else { + if (pathArr.length === 1 && pathArr[0] === 'Study') { + callback(true); + return; + } else { + const studies = await this.studyCore.getStudies(user); + const study = studies.filter(el => el.name === pathArr[1])[0]; + if (!study) { + callback(false); + return; + } else { + callback(true); + return; + } + } + } + } + } + + override _displayName(path: webdav.Path, ctx: webdav.DisplayNameInfo, callback: webdav.ReturnCallback): void { + callback(undefined, 'Display name'); + } + + override _lockManager(path: webdav.Path, ctx: webdav.LockManagerInfo, callback: webdav.ReturnCallback): void { + callback(undefined, new webdav.LocalLockManager()); // Use local lock manager for simplicity + } + + override _propertyManager(path: webdav.Path, ctx: webdav.PropertyManagerInfo, callback: webdav.ReturnCallback): void { + callback(undefined, new webdav.LocalPropertyManager()); // Use local property manager for simplicity + } + + override _type(path: webdav.Path, ctx: webdav.TypeInfo, callback: webdav.ReturnCallback): void { + // Determine type based on path (directory if ends with '/', file otherwise) + const fileExtensionRegex = /\.[^/]+$/; + const isFile = fileExtensionRegex.test(path.toString()); + callback(undefined, isFile ? webdav.ResourceType.File : webdav.ResourceType.Directory); + } + + override async _delete(path: webdav.Path, ctx: webdav.DeleteInfo, callback: webdav.SimpleCallback): Promise { + const user = ctx.context.user as any; + const pathStr = path.toString(); + const pathArr = pathToArray(pathStr); + if (pathArr.length <= 1) { + callback(new Error('You can not edit the root node')); + return; + } else if (pathArr[1] === 'Study') { + callback(new Error('You can not edit study data')); + return; + } else { + const userFileDir: Record = await this.driveCore.getDrives(user); + const ownUserFiles: IDrive[] = userFileDir[user.id]; + const node: IDrive = ownUserFiles.filter(el => el.path.length === pathArr.length && el.path.every((part, index) => ownUserFiles.filter(ek => ek.id === part)[0].name === pathArr[index]))[0]; + try { + await this.driveCore.deleteDrive(user, node.id); + callback(undefined); + return; + } catch { + callback(new Error('Failed to delete file')); + return; + } + } + } + + // TODO: upload file + override async _create(path: webdav.Path, ctx: webdav.CreateInfo, callback: webdav.SimpleCallback): Promise { + const user = ctx.context.user as any; + const pathStr = path.toString(); + const pathArr = pathToArray(pathStr); + const fileExtensionRegex = /\.[^/]+$/; + const isFile = fileExtensionRegex.test(pathStr); + if (isFile) { + this.isCopying = true; + callback(undefined); + } else { + if (pathArr.length <= 1) { + callback(new Error('You can not edit the root node')); + return; + } else if (pathArr[1] === 'Study') { + callback(new Error('You can not edit study data')); + return; + } else { + const parentPath = pathArr.slice(0, -1); + const userFileDir: Record = await this.driveCore.getDrives(user); + const ownUserFiles: IDrive[] = userFileDir[user.id]; + const parentNode = ownUserFiles.filter(el => el.path.every((part, index) => ownUserFiles.filter(ek => ek.id === part)[0].name === parentPath[index]) && el.path.length === parentPath.length)[0]; + if (!parentNode) { + callback(new Error('You need to create the parent folder first')); + } else { + try { + await this.driveCore.createDriveFolder( + user, + pathArr[pathArr.length - 1], + parentNode.id, + false, + undefined + ); + callback(undefined); + return; + } catch { + callback(new Error('Failed to create folder')); + return; + } + } + } + } + } + + override async _openReadStream(path: webdav.Path, ctx: webdav.OpenReadStreamInfo, callback: webdav.ReturnCallback): Promise { + const user = ctx.context.user as any; + const pathStr = path.toString(); + const pathArr = pathToArray(pathStr); + if (pathArr.length <= 1) { + callback(new Error('You can not edit the root node')); + } else { + if (pathArr[1] === 'Study') { + callback(new Error('You can not edit study data')); + } else if (pathArr[0] === this.myDriveDirName) { + const userFileDir: Record = await this.driveCore.getDrives(user); + const ownUserFiles: IDrive[] = userFileDir[user.id]; + const node: IDrive = ownUserFiles.filter(el => el.path.length === pathArr.length && el.path.every((part, index) => ownUserFiles.filter(ek => ek.id === part)[0].name === pathArr[index]))[0]; + const fileUri = `/file/${node.fileId}`; + try { + const response = await nodeFetch(fileUri); + if (!response.ok) { + throw new Error('Failed to fetch file.'); + } + callback(undefined, response.body as Readable); + return; + } catch { + callback(new Error('Failed to fetch file.')); + return; + } + } + } + } + + override async _openWriteStream(path: webdav.Path, ctx: webdav.OpenWriteStreamInfo, callback: webdav.ReturnCallback): Promise { + const user = ctx.context.user as any; + const pathStr = path.toString(); + const pathArr = pathToArray(pathStr); + // eslint-disable-next-line @typescript-eslint/no-this-alias + const _this = this; + const fileBuffer: Uint8Array[] = []; + if (pathArr.length <= 1) { + callback(new Error('You can not edit the root node')); + return; + } else if (pathArr[1] === 'Study') { + callback(new Error('You can not edit study data')); + } else if (pathArr[0] === this.myDriveDirName) { + const userFileDir: Record = await this.driveCore.getDrives(user); + const ownUserFiles: IDrive[] = userFileDir[user.id]; + pathArr.pop(); + const parentNode: IDrive = ownUserFiles.filter(el => el.path.length === pathArr.length && el.path.every((part, index) => ownUserFiles.filter(ek => ek.id === part)[0].name === pathArr[index]))[0]; + if (!parentNode) { + callback(new Error('Path does not exist.')); + } + let isFinalCalled = false; + const writeableStream = new Writable({ + write(chunk, encoding, callback) { + fileBuffer.push(chunk); + callback(); + }, + async final(callback) { + if (!isFinalCalled) { + isFinalCalled = true; + await _this.driveCore.createDriveFile( + user, + parentNode.id, + undefined, + enumFileTypes.UNKNOWN, + { + createReadStream: () => Readable.from(Buffer.concat(fileBuffer)), + filename: path.fileName(), + mimetype: 'application/octet-stream', // or a more accurate MIME type if known + encoding: 'utf-8' + } + ).then(() => callback(null)).catch((err: Error | null | undefined) => callback(err)); + } + } + }); + if (this.isCopying) { + this.isCopying = false; + } + callback(undefined, writeableStream); + } else { + callback(new Error('Path does not exist.')); + return; + } + } + + override async _readDir(path: webdav.Path, ctx: webdav.ReadDirInfo, callback: webdav.ReturnCallback): Promise { + const user = ctx.context.user as any; + const depth: number = ctx.context.headers.depth ?? 1; + const pathStr: string = path.toString(); + + + if (pathStr === '/') { + callback(undefined, [this.myDriveDirName, this.sharedDirName, this.studyDirName]); + return; + } else { + const pathArr = pathToArray(pathStr); + if (pathArr[0] === this.myDriveDirName) { + const userFileDir: Record = await this.driveCore.getDrives(user); + const ownUserFiles = userFileDir[user.id]; + callback(undefined, convertToWebDAVPaths(ownUserFiles, depth, pathStr)); + return; + } else if (pathArr[0] === this.sharedDirName) { + const usersFileDir: Record = await this.driveCore.getDrives(user); + const users = await this.db.collections.users_collection.find({ 'life.deletedTime': null }).toArray(); + if (pathArr.length === 1) { + callback(undefined, Object.keys(usersFileDir).filter(el => el !== user.id).map(el => { + const user = users.filter((es: { id: string; }) => es.id === el)[0]; + return user ? `${user.firstname} ${user.lastname}` : 'NA'; + })); + return; + } + let allPaths: string[] = []; + for (const userId of Object.keys(usersFileDir)) { + if (userId === user.id) { + continue; + } + const owner = users.filter((el: { id: string; }) => el.id === userId)[0]; + const ownUserFiles = usersFileDir[userId].filter(el => el.path); + const validFileIds: string[] = ownUserFiles.map(el => el.id); + for (const item of ownUserFiles) { + item.path = item.path.filter(el => validFileIds.includes(el)); + } + const partialPaths: string[] = convertToWebDAVPaths(ownUserFiles, depth, pathStr, [this.sharedDirName, `${owner.firstname} ${owner.lastname}`]); + const completePaths: string[] = partialPaths.map((el: string) => `${this.sharedDirName}/${owner.firstname} ${owner.lastname}/${el}`); + allPaths = [...allPaths, ...completePaths]; + } + } else if (pathArr[0] === this.studyDirName) { + const studies = await this.studyCore.getStudies(user); + if (pathArr.length === 1) { + callback(undefined, studies.map(el => el.name)); + return; + } else { + const study = studies.filter(el => el.name === pathArr[1])[0]; + if (!study) { + callback(new Error('Path not found.')); + return; + } else { + try { + const files = await this.dataCore.getStudyFiles(user, study.id); + callback(undefined, files.map(el => el.fileName ?? '')); + return; + } catch { + callback(new Error('Failed to get study files.')); + return; + } + } + + } + } else { + callback(new Error('Path not found.')); + return; + } + } + } + + override async _copy(pathFrom: webdav.Path, pathTo: webdav.Path, ctx: webdav.CopyInfo, callback: webdav.ReturnCallback): Promise { + const user = ctx.context.user as any; + function findNodeIdFromPath(pathArr: string[], allPathIds: string[][]) { + const filtered = allPathIds.filter(el => { + return el.length === pathArr.length && el.every((part, index) => pathArr[index] === part[0]); + })[0]; + return filtered[filtered.length - 1][1]; + } + const userFileDir: Record = await this.driveCore.getDrives(user); + const ownUserFiles = userFileDir[user.id]; + const allPathsIds: string[][] = ownUserFiles.map((el: { path: string[]; }) => el.path.map((ek: string) => { + return ownUserFiles.filter(es => es.id === ek)[0].id; + })); + const sourceNodeId = findNodeIdFromPath(pathToArray(pathFrom.toString()), allPathsIds); + const targetNodeId = findNodeIdFromPath(pathToArray(pathTo.toString()).slice(0, -1), allPathsIds); + try { + await this.driveCore.copyDrive( + user, + sourceNodeId, + targetNodeId + ); + } catch { + callback(new Error('Failed to copy file.')); + return; + } + callback(undefined, true); + } + + override async _move(pathFrom: webdav.Path, pathTo: webdav.Path, ctx: webdav.MoveInfo, callback: webdav.ReturnCallback): Promise { + const user = ctx.context.user as any; + + // const caller = this.router.createCaller({ user: ctx.context.user }); + function findNodeIdFromPath(pathArr: string[], allPathIds: string[][]) { + const filtered = allPathIds.filter(el => { + return el.length === pathArr.length && el.every((part, index) => pathArr[index] === part[0]); + })[0]; + return filtered[filtered.length - 1][1]; + } + const userFileDir: Record = await this.driveCore.getDrives(user); + const ownUserFiles = userFileDir[user.id]; + const allPathsIds: string[][] = ownUserFiles.map((el: { path: string[]; }) => el.path.map((ek: string) => { + return ownUserFiles.filter(es => es.id === ek)[0].id; + })); + const sourceNodeId = findNodeIdFromPath(pathToArray(pathFrom.toString()), allPathsIds); + const targetNodeId = findNodeIdFromPath(pathToArray(pathTo.toString()).slice(0, -1), allPathsIds); + try { + await this.driveCore.editDrive( + user, + sourceNodeId, + undefined, + undefined, + undefined, + targetNodeId + ); + } catch { + callback(new Error('Failed to move file.')); + return; + } + callback(undefined, true); + } + +} +// The DMPWebDAVAuthentication class implementing the above interface +interface HTTPAuthentication { + askForAuthentication(): { [headerName: string]: string; }; + getUser(ctx: webdav.HTTPRequestContext, callback: (error: Error | null, user?: IUserWithoutToken) => void): void; +} + +export class DMPWebDAVAuthentication implements HTTPAuthentication { + realm: string; + db: DBType; + constructor(db: DBType, realm?: string) { + this.realm = realm ?? 'realm'; + this.db = db; + } + + askForAuthentication(): { 'WWW-Authenticate': string; } { + return { + 'WWW-Authenticate': 'Basic realm="' + this.realm + '"' + }; + } + + getUser(ctx: webdav.HTTPRequestContext, callback: (error: Error | null, user?: IUserWithoutToken) => void): void { + try { + + const authHeader = ctx.request.headers['authorization']; + + if (authHeader) { + if (authHeader.startsWith('Basic ')) { + const base64Credentials = authHeader.slice(6); + + // Logic to retrieve the user from the database using username and password + // retrival the user according to the user_id + const credentials = Buffer.from(base64Credentials, 'base64').toString('ascii'); + const [userId] = credentials.split(':'); // Split the credentials into userId and token + + + this.db.collections.users_collection.findOne( + { 'life.deletedTime': null, 'id': userId }, + { projection: { password: 0, otpSecret: 0 } } + ) + .then((user) => { + if (!user) { + callback(new Error('Invalid credentials')); + return; + } + callback(null, user as IUserWithoutToken); // Pass the user object to the callback + }) + .catch(() => { + callback(new Error('Error retrieving user')); + }); + } else { + callback(new Error('Unsupported authentication method')); + } + } else { + callback(new Error('No authentication provided')); + } + // get the user from the request + // const user = ctx.user as any; + // if (!user) { + // callback(new Error('Invalid credentials')); + // return; + // } else { + // callback(null, user); + // return; + // } + // const token = ctx.headers.find('authorization') ?? ''; + // const decodedPayload = jwt.decode(token); + // let pubkey: string; + // if (decodedPayload !== null && !(typeof decodedPayload === 'string')) { + // pubkey = decodedPayload['publicKey']; + // } else { + // callback(new Error('JWT verification failed.')); + // return; + // } + // // verify the JWT + // jwt.verify(token, pubkey, function (error) { + // if (error) { + // callback(new Error('JWT verification failed.')); + // } + // }); + // try { + // userRetrieval(this.db, pubkey).then((associatedUser) => { + // callback(null, associatedUser); + // }).catch(() => { + // callback(new Error('Invalid credentials')); + // }); + // } catch { + // callback(new Error('Invalid credentials')); + // } + } catch { + callback(new Error('Invalid credentials')); + } + } +} + +function pathToArray(path: string) { + return path.split('/').filter(Boolean); +} + +function convertToWebDAVPaths(ownUserFiles: IDrive[], depth: number, pathStr: string, prefix?: string[]): string[] { + const allPaths = ownUserFiles.map((el: { path: string[]; }) => + el.path + .map((ek: string) => ownUserFiles.filter((es: { id: string; }) => es.id === ek)?.[0]?.name) + .filter((name: string | undefined) => name !== undefined) + ); + const basePath = pathToArray(pathStr); + if (prefix) { + const result = allPaths.map((el: ConcatArray) => prefix.concat(el)); + return getDirectChildren(result, basePath); + } + return getDirectChildren(allPaths, basePath); +} + +function getDirectChildren(allPaths: string[][], basePath: string[]): string[] { + const basePathLength = basePath.length; + const directChildren = new Set(); + + for (const path of allPaths) { + // Check if the current path is a direct child of the basePath + if (path.length > basePathLength && basePath.every((part, index) => part === path[index])) { + directChildren.add(path[basePathLength]); + } + } + + return Array.from(directChildren); +} + +function isPathIncluded(givenPath: string[], paths: string[][]): boolean { + return paths.some(path => + givenPath.every((part, index) => part === path[index]) + ); +} + diff --git a/packages/itmat-cores/tsconfig.json b/packages/itmat-cores/tsconfig.json new file mode 100644 index 000000000..781e6aac1 --- /dev/null +++ b/packages/itmat-cores/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "ES2022", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noImplicitAny": false, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} \ No newline at end of file diff --git a/packages/itmat-cores/tsconfig.lib.json b/packages/itmat-cores/tsconfig.lib.json new file mode 100644 index 000000000..472dc6121 --- /dev/null +++ b/packages/itmat-cores/tsconfig.lib.json @@ -0,0 +1,18 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": [ + "node" + ] + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "jest.config.ts", + "**/*.spec.ts", + "**/*.test.ts" + ] +} \ No newline at end of file diff --git a/packages/itmat-cores/tsconfig.spec.json b/packages/itmat-cores/tsconfig.spec.json new file mode 100644 index 000000000..c744cb73a --- /dev/null +++ b/packages/itmat-cores/tsconfig.spec.json @@ -0,0 +1,17 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "ES2022", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "jest.config.ts", + "**/*.test.ts", + "**/*.spec.ts", + "**/*.d.ts" + ] +} \ No newline at end of file diff --git a/packages/itmat-cores/webdav.d.ts b/packages/itmat-cores/webdav.d.ts new file mode 100644 index 000000000..63b94f575 --- /dev/null +++ b/packages/itmat-cores/webdav.d.ts @@ -0,0 +1,67 @@ +import { IUserWithoutToken } from '@itmat-broker/itmat-types'; + +// Augment the webdav-server module +declare module 'webdav-server' { + type IUser = IUserWithoutToken; + namespace v2 { + type IUser = IUserWithoutToken; + interface RequestContext { + user: IUserWithoutToken; + } + + interface DeleteInfo { + context: RequestContext; + } + + interface CreateInfo { + context: RequestContext; + } + + interface OpenReadStreamInfo { + context: RequestContext; + } + + interface OpenWriteStreamInfo { + context: RequestContext; + } + + interface ReadDirInfo { + context: { + user: IUserWithoutToken; + headers: { + depth?: number + } + }; + } + + interface CopyInfo { + context: RequestContext; + } + + interface MoveInfo { + context: RequestContext; + } + + abstract class FileSystemSerializer { + abstract uid(): string; + abstract serialize(fs: FileSystem, callback: ReturnCallback): void; + abstract unserialize(serializeData: unknown, callback: ReturnCallback): void; + } + + abstract class FileSystem { + constructor(FileSystemSerializer) + abstract _fastExistCheck(ctx: RequestContext, path: Path, callback: (exists: boolean) => void): Promise; + abstract _displayName(path: Path, ctx: DisplayNameInfo, callback: ReturnCallback): void; + abstract _lockManager(path: Path, ctx: LockManagerInfo, callback: ReturnCallback): void; + abstract _propertyManager(path: Path, ctx: PropertyManagerInfo, callback: ReturnCallback): void; + abstract _type(path: Path, ctx: TypeInfo, callback: ReturnCallback): void; + abstract _delete(path: Path, ctx: DeleteInfo, callback: SimpleCallback): Promise; + abstract _create(path: Path, ctx: CreateInfo, callback: SimpleCallback): Promise; + abstract _openReadStream(path: Path, ctx: OpenReadStreamInfo, callback: ReturnCallback): Promise; + abstract _openWriteStream(path: Path, ctx: OpenWriteStreamInfo, callback: ReturnCallback): Promise; + abstract _readDir(path: Path, ctx: ReadDirInfo, callback: ReturnCallback): Promise; + abstract _copy(pathFrom: Path, pathTo: Path, ctx: CopyInfo, callback: ReturnCallback): Promise; + abstract _move(pathFrom: Path, pathTo: Path, ctx: MoveInfo, callback: ReturnCallback): Promise; + } + } +} \ No newline at end of file diff --git a/packages/itmat-docker/config/config.sample.json b/packages/itmat-docker/config/config.sample.json index 26ae7bb0e..b0c5e91b1 100644 --- a/packages/itmat-docker/config/config.sample.json +++ b/packages/itmat-docker/config/config.sample.json @@ -17,7 +17,14 @@ "files_collection": "FILES_COLLECTION", "sessions_collection": "SESSIONS_COLLECTION", "pubkeys_collection": "PUBKEY_COLLECTION", - "standardizations_collection": "STANDARDIZATION_COLLECTION" + "standardizations_collection": "STANDARDIZATION_COLLECTION", + "configs_collection": "CONFIG_COLLECTION", + "ontologies_collection": "ONTOLOGY_COLLECTION", + "docs_collection": "DOC_COLLECTION", + "cache_collection": "CACHE_COLLECTION", + "drives_collection": "DRIVE_COLLECTION", + "colddata_collection": "COLDDATA_COLLECTION", + "domains_collection": "DOMAIN_COLLECTION" } }, "server": { @@ -36,6 +43,13 @@ }, "sessionsSecret": "Change_Me", "aesSecret": "change_this", - "nodemailer": {}, - "aeEndpoint": "http://localhost:9090" + "nodemailer": { + "auth": { + "user": "change_this", + "pass": "change_this" + } + }, + "aeEndpoint": "http://localhost:9090", + "useWebdav": true, + "webdavPort": 1900 } \ No newline at end of file diff --git a/packages/itmat-docker/launcher.js b/packages/itmat-docker/launcher.js index 5296c7118..432c7388b 100644 --- a/packages/itmat-docker/launcher.js +++ b/packages/itmat-docker/launcher.js @@ -28,6 +28,8 @@ Promise.all([ root.use('/', router.getApp()); }); + root.set('trust proxy', 1); + root.use(rateLimit({ windowMs: 1 * 60 * 1000, max: 500 @@ -62,6 +64,17 @@ Promise.all([ socket.setTimeout(0); socket.timeout = 0; }); + + // Add WebSocket upgrade handling + server.on('upgrade', (req, socket, head) => { + // Forward WebSocket upgrade to the interface + if (interface.router) { + interface.router.getServer().emit('upgrade', req, socket, head); + } else { + console.error('No WebSocket upgrade handler available for URL:', req.url); + socket.destroy(); + } + }); try { server.listen(config.server.port, () => { diff --git a/packages/itmat-docker/package.json b/packages/itmat-docker/package.json index ea859f6b1..8db19e9fc 100644 --- a/packages/itmat-docker/package.json +++ b/packages/itmat-docker/package.json @@ -1,6 +1,6 @@ { "name": "@itmat-broker/itmat-setup", - "version": "2.5.1", + "version": "3.1.0", "description": "ITMAT Broker", "keywords": [ "itmat", @@ -16,12 +16,12 @@ ], "dependencies": { "bcrypt": "5.1.1", - "compression": "1.7.4", - "express": "4.18.2", - "express-rate-limit": "7.1.2", + "compression": "1.8.0", + "express": "4.21.2", + "express-rate-limit": "7.5.0", "isobject": "4.0.0", - "minio": "7.1.3", - "mongodb": "6.2.0", + "minio": "8.0.4", + "mongodb": "6.14.1", "sanitize-filename": "1.6.3" } } \ No newline at end of file diff --git a/packages/itmat-docker/project.json b/packages/itmat-docker/project.json index e04dce9d9..3c9f0e892 100644 --- a/packages/itmat-docker/project.json +++ b/packages/itmat-docker/project.json @@ -3,6 +3,12 @@ "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "packages/itmat-docker/src", "projectType": "application", + "tags": [], + "implicitDependencies": [ + "itmat-interface", + "itmat-job-executor", + "itmat-ui-react" + ], "targets": { "build": { "dependsOn": [ @@ -18,11 +24,5 @@ "cwd": "packages/itmat-docker" } } - }, - "tags": [], - "implicitDependencies": [ - "itmat-interface", - "itmat-job-executor", - "itmat-ui-react" - ] + } } \ No newline at end of file diff --git a/packages/itmat-interface/config/config.sample.json b/packages/itmat-interface/config/config.sample.json index be4f3f017..6bddb50a3 100644 --- a/packages/itmat-interface/config/config.sample.json +++ b/packages/itmat-interface/config/config.sample.json @@ -8,8 +8,6 @@ "users_collection": "USER_COLLECTION", "organisations_collection": "ORGANISATION_COLLECTION", "studies_collection": "STUDY_COLLECTION", - "projects_collection": "PROJECT_COLLECTION", - "queries_collection": "QUERY_COLLECTION", "log_collection": "LOG_COLLECTION", "data_collection": "DATA_COLLECTION", "roles_collection": "ROLE_COLLECTION", @@ -17,7 +15,17 @@ "files_collection": "FILES_COLLECTION", "sessions_collection": "SESSIONS_COLLECTION", "pubkeys_collection": "PUBKEY_COLLECTION", - "standardizations_collection": "STANDARDIZATION_COLLECTION" + "standardizations_collection": "STANDARDIZATION_COLLECTION", + "configs_collection": "CONFIG_COLLECTION", + "ontologies_collection": "ONTOLOGY_COLLECTION", + "docs_collection": "DOC_COLLECTION", + "cache_collection": "CACHE_COLLECTION", + "drives_collection": "DRIVE_COLLECTION", + "colddata_collection": "COLDDATA_COLLECTION", + "domains_collection": "DOMAIN_COLLECTION", + "webauthn_collection": "WEBAUTHN_COLLECTION", + "instance_collection": "INSTANCE_COLLECTION" + } }, "server": { @@ -37,8 +45,33 @@ "sessionsSecret": "Change_Me", "aesSecret": "change_this", "nodemailer": { - "auth": {} + "auth": { + "user": "change_this", + "pass": "change_this" + } }, "adminEmail": "admin@example.com", - "aeEndpoint": "http://localhost:9090" + "useWebdav": true, + "webdavPort": 1900, + "aeEndpoint": "http://localhost:9090", + "lxdEndpoint": "https://localhost:8443", + "lxdPortRange": [ + 30000, + 40000 + ], + "lxdSquidProxy": "traget_ip:3128", + "systemKey":{ + "pubkey": "/path/system_key.pub", + "privkey": "/path/system_key.pem" + }, + "lxdCertFile": { + "cert": "/path/mylxd.crt", + "key": "/path/mylxd.key" + }, + "lxdRejectUnauthorized": false, + "jupyterPort": 8888, + "dmp": { + "host": "localhost", + "port": 3080 + } } \ No newline at end of file diff --git a/packages/itmat-interface/express-user.d.ts b/packages/itmat-interface/express-user.d.ts new file mode 100644 index 000000000..2fff509fe --- /dev/null +++ b/packages/itmat-interface/express-user.d.ts @@ -0,0 +1,27 @@ +import type { IUserWithoutToken } from '@itmat-broker/itmat-types'; + +declare global { + + namespace Express { + + // eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/no-empty-object-type + interface User extends IUserWithoutToken { } + + interface MulterFile { + fieldname: string; + originalname: string; + encoding: string; + mimetype: string; + buffer: Buffer; + size: number; + } + + interface Request { + user?: User; + files?: MulterFile[]; + headers: IncomingHttpHeaders; + login(user: User, done: (err: unknown) => void): void; + logout(done: (err: unknown) => void): void; + } + } +} \ No newline at end of file diff --git a/packages/itmat-interface/jest.config.ts b/packages/itmat-interface/jest.config.ts index 76a8a7392..6895a92fb 100644 --- a/packages/itmat-interface/jest.config.ts +++ b/packages/itmat-interface/jest.config.ts @@ -1,8 +1,8 @@ +console.log('uuid', require.resolve('uuid')); /* eslint-disable */ export default { displayName: 'itmat-interface', - preset: '../../jest.preset.js', - verbose: true, + preset: '../../jest.preset.cjs', transform: { '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json', @@ -19,5 +19,6 @@ export default { moduleNameMapper: { // Force module uuid to resolve with the CJS entry point, because Jest does not support package.json.exports. See https://github.com/uuidjs/uuid/issues/451 "uuid": require.resolve('uuid'), - } + }, + setupFilesAfterEnv: ["/test/setupTests.ts"] }; diff --git a/packages/itmat-interface/project.json b/packages/itmat-interface/project.json index cc74fe6c2..9e351f276 100644 --- a/packages/itmat-interface/project.json +++ b/packages/itmat-interface/project.json @@ -3,6 +3,7 @@ "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "packages/itmat-interface/src", "projectType": "application", + "tags": [], "targets": { "build": { "executor": "@nx/webpack:webpack", @@ -16,7 +17,7 @@ "main": "packages/itmat-interface/src/index.ts", "tsConfig": "packages/itmat-interface/tsconfig.app.json", "assets": [], - "webpackConfig": "packages/itmat-interface/webpack.config.js" + "webpackConfig": "packages/itmat-interface/webpack.config.cjs" }, "configurations": { "production": { @@ -59,6 +60,5 @@ "jestConfig": "packages/itmat-interface/jest.config.ts" } } - }, - "tags": [] + } } \ No newline at end of file diff --git a/packages/itmat-interface/src/authentication/pubkeyAuthentication.ts b/packages/itmat-interface/src/authentication/pubkeyAuthentication.ts deleted file mode 100644 index da9478df1..000000000 --- a/packages/itmat-interface/src/authentication/pubkeyAuthentication.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { GraphQLError } from 'graphql'; -import { ApolloServerErrorCode } from '@apollo/server/errors'; -import { db } from '../database/database'; -import { IUser } from '@itmat-broker/itmat-types'; - - -export async function userRetrieval(pubkey: string): Promise { - // retrieve userId associated with the token - const pubkeyrec = await db.collections!.pubkeys_collection.findOne({ jwtPubkey: pubkey, deleted: null }); - if (pubkeyrec === null || pubkeyrec === undefined) { - throw new GraphQLError('The public-key embedded in the JWT is not valid!', { extensions: { code: ApolloServerErrorCode.BAD_USER_INPUT } }); - } - if (!pubkeyrec.associatedUserId) { - throw new GraphQLError('The public-key embedded in the JWT is not associated with any user!', { extensions: { code: ApolloServerErrorCode.BAD_USER_INPUT } }); - } - - const associatedUser: IUser | null = await db.collections!.users_collection.findOne({ deleted: null, id: pubkeyrec.associatedUserId }, { projection: { _id: 0 } }); - if (!associatedUser) { - throw new GraphQLError('The user assciated with the public-key embedded in the JWT is not existed or already deleted!', { extensions: { code: ApolloServerErrorCode.BAD_USER_INPUT } }); - } - return associatedUser; -} diff --git a/packages/itmat-interface/src/database/database.ts b/packages/itmat-interface/src/database/database.ts index 763b0b950..89168519c 100644 --- a/packages/itmat-interface/src/database/database.ts +++ b/packages/itmat-interface/src/database/database.ts @@ -1,39 +1,4 @@ -import type { IDataEntry, IFieldEntry, IFile, IJobEntry, ILogEntry, IOrganisation, IProject, IPubkey, IQueryEntry, IRole, IStudy, IUser, IStandardization } from '@itmat-broker/itmat-types'; import { Database as DatabaseBase, IDatabaseBaseConfig } from '@itmat-broker/itmat-commons'; -import type { Collection } from 'mongodb'; - -export interface IDatabaseConfig extends IDatabaseBaseConfig { - collections: { - users_collection: string, - jobs_collection: string, - studies_collection: string, - projects_collection: string, - queries_collection: string, - field_dictionary_collection: string, - roles_collection: string, - files_collection: string, - organisations_collection: string, - log_collection: string, - pubkeys_collection: string, - data_collection: string, - standardizations_collection: string, - }; -} - -export interface IDatabaseCollectionConfig { - users_collection: Collection, - jobs_collection: Collection>, - studies_collection: Collection, - projects_collection: Collection, - queries_collection: Collection, - field_dictionary_collection: Collection, - roles_collection: Collection, - files_collection: Collection, - organisations_collection: Collection, - log_collection: Collection, - pubkeys_collection: Collection, - data_collection: Collection, - standardizations_collection: Collection, -} +import { IDatabaseCollectionConfig } from '@itmat-broker/itmat-cores'; export const db = new DatabaseBase(); diff --git a/packages/itmat-interface/src/emailer/emailer.ts b/packages/itmat-interface/src/emailer/emailer.ts index 24f54183b..7c0782d8a 100644 --- a/packages/itmat-interface/src/emailer/emailer.ts +++ b/packages/itmat-interface/src/emailer/emailer.ts @@ -1,25 +1,4 @@ -import nodemailer from 'nodemailer'; import appConfig from '../utils/configManager'; -import { Attachment } from 'nodemailer/lib/mailer'; - -export interface IMail { - from: string, - to: string, - subject: string, - html: string, - attachments?: Attachment[]; -} - -class Mailer { - private readonly _client: nodemailer.Transporter; - - constructor(config: any) { - this._client = nodemailer.createTransport(config); - } - - public async sendMail(mail: IMail): Promise { - await this._client.sendMail(mail); - } -} +import { Mailer } from '@itmat-broker/itmat-commons'; export const mailer = new Mailer(appConfig.nodemailer); diff --git a/packages/itmat-interface/src/graphql/core/fieldCore.ts b/packages/itmat-interface/src/graphql/core/fieldCore.ts deleted file mode 100644 index 69bb6659c..000000000 --- a/packages/itmat-interface/src/graphql/core/fieldCore.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { IFieldEntry, enumValueType, IUser } from '@itmat-broker/itmat-types'; -import { db } from '../../database/database'; -import { v4 as uuid } from 'uuid'; -export class FieldCore { - public async getFieldsOfStudy(studyId: string, detailed: boolean, getOnlyTheseFields?: string[]): Promise { - /* ASSUMING projectId and studyId match*/ - /* if detailed=false, only returns the fieldid in an array */ - /* constructing queryObj; if projectId is provided then only those in the approved fields are returned */ - let queryObj: any = { studyId }; - if (getOnlyTheseFields) { // if both study id and project id are provided then just make sure they belong to each other - queryObj = { studyId, fieldId: { $in: getOnlyTheseFields } }; - } - - const aggregatePipeline: any = [ - { $match: queryObj } - ]; - /* if detailed=false, only returns the fieldid in an array */ - if (detailed === false) { - aggregatePipeline.push({ $group: { _id: null, array: { $addToSet: '$fieldId' } } }); - } - - const cursor = db.collections!.field_dictionary_collection.aggregate(aggregatePipeline); - return cursor.toArray(); - } - -} - - -export function validateAndGenerateFieldEntry(fieldEntry: any, requester: IUser) { - // duplicates with existing fields are checked by caller function - const error: string[] = []; - const complusoryField = [ - 'fieldId', - 'fieldName', - 'dataType' - ]; - - // check missing field - for (const key of complusoryField) { - if (fieldEntry[key] === undefined && fieldEntry[key] === null) { - error.push(`${key} should not be empty.`); - } - } - // only english letters, numbers and _ are allowed in fieldIds - if (!/^[a-zA-Z0-9_]*$/.test(fieldEntry.fieldId || '')) { - error.push('FieldId should contain letters, numbers and _ only.'); - } - // data types - if (!Object.values(enumValueType).includes(fieldEntry.dataType)) { - error.push(`Data type shouldn't be ${fieldEntry.dataType}: use 'int' for integer, 'dec' for decimal, 'str' for string, 'bool' for boolean, 'date' for datetime, 'file' for FILE, 'json' for json.`); - } - // check possiblevalues to be not-empty if datatype is categorical - if (fieldEntry.dataType === enumValueType.CATEGORICAL) { - if (fieldEntry.possibleValues !== undefined && fieldEntry.possibleValues !== null) { - if (fieldEntry.possibleValues.length === 0) { - error.push(`${fieldEntry.fieldId}-${fieldEntry.fieldName}: possible values can't be empty if data type is categorical.`); - } - for (let i = 0; i < fieldEntry.possibleValues.length; i++) { - fieldEntry.possibleValues[i]['id'] = uuid(); - } - } else { - error.push(`${fieldEntry.fieldId}-${fieldEntry.fieldName}: possible values can't be empty if data type is categorical.`); - } - } - - const newField: any = { - fieldId: fieldEntry.fieldId, - fieldName: fieldEntry.fieldName, - tableName: fieldEntry.tableName, - dataType: fieldEntry.dataType, - possibleValues: fieldEntry.dataType === enumValueType.CATEGORICAL ? fieldEntry.possibleValues : null, - unit: fieldEntry.unit, - comments: fieldEntry.comments, - metadata: { - 'uploader:org': requester.organisation, - 'uploader:user': requester.id, - ...fieldEntry.metadata - } - }; - - return { fieldEntry: newField, error: error }; -} - -export const fieldCore = Object.freeze(new FieldCore()); diff --git a/packages/itmat-interface/src/graphql/core/jobCore.ts b/packages/itmat-interface/src/graphql/core/jobCore.ts deleted file mode 100644 index b69ce2bc9..000000000 --- a/packages/itmat-interface/src/graphql/core/jobCore.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { IJobEntry } from '@itmat-broker/itmat-types'; -import { v4 as uuid } from 'uuid'; -import { db } from '../../database/database'; - -export class JobCore { - public async createJob(userId: string, jobType: string, files: string[], studyId: string, projectId?: string, jobId?: string): Promise> { - const job: IJobEntry = { - requester: userId, - id: jobId || uuid(), - studyId, - jobType, - projectId, - requestTime: new Date().valueOf(), - receivedFiles: files, - status: 'QUEUED', - error: null, - cancelled: false - }; - await db.collections!.jobs_collection.insertOne(job); - return job; - } -} - -export const jobCore = Object.freeze(new JobCore()); diff --git a/packages/itmat-interface/src/graphql/core/permissionCore.ts b/packages/itmat-interface/src/graphql/core/permissionCore.ts deleted file mode 100644 index b62acd877..000000000 --- a/packages/itmat-interface/src/graphql/core/permissionCore.ts +++ /dev/null @@ -1,409 +0,0 @@ -import { GraphQLError } from 'graphql'; -import { atomicOperation, IPermissionManagementOptions, IRole, IUser, userTypes } from '@itmat-broker/itmat-types'; -import { BulkWriteResult } from 'mongodb'; -import { v4 as uuid } from 'uuid'; -import { db } from '../../database/database'; -import { errorCodes } from '../errors'; - -interface ICreateRoleInput { - studyId: string; - projectId?: string; - roleName: string; - createdBy: string; -} - -export class PermissionCore { - public async getAllRolesOfStudyOrProject(studyId: string, projectId?: string): Promise { - return db.collections!.roles_collection.find({ studyId, projectId }).toArray(); - } - - public async userHasTheNeccessaryManagementPermission(type: string, operation: string, user: IUser, studyId: string, projectId?: string): Promise { - if (user === undefined) { - return false; - } - - /* if user is an admin then return true if admin privileges includes needed permissions */ - if (user.type === userTypes.ADMIN) { - return true; - } - const tag = `permissions.manage.${type}`; - const roles = await db.collections!.roles_collection.aggregate([ - { $match: { studyId, projectId: { $in: [projectId, null] }, users: user.id, deleted: null } }, // matches all the role documents where the study and project matches and has the user inside - { $match: { [tag]: operation } } - ]).toArray(); - if (roles.length === 0) { - return false; - } - return true; - } - - public async combineUserDataPermissions(operation: string, user: IUser, studyId: string, projectId?: string): Promise | false> { - if (user.type === userTypes.ADMIN) { - const matchAnyString = '^.*$'; - return { - subjectIds: [matchAnyString], - visitIds: [matchAnyString], - fieldIds: [matchAnyString] - }; - } - const roles = await db.collections!.roles_collection.aggregate([ - { $match: { studyId, projectId: { $in: [projectId, null] }, users: user.id, deleted: null } }, // matches all the role documents where the study and project matches and has the user inside - { $match: { 'permissions.data.operations': operation } } - ]).toArray(); - if (roles.length === 0) { - return false; - } - const combined: Record = { - subjectIds: [], - visitIds: [], - fieldIds: [] - }; - for (const role of roles) { - combined.subjectIds.push(...new Set(combined.subjectIds.concat(role.permissions.data?.subjectIds || []))); - combined.visitIds.push(...new Set(combined.visitIds.concat(role.permissions.data?.visitIds || []))); - combined.fieldIds.push(...new Set(combined.fieldIds.concat(role.permissions.data?.fieldIds || []))); - } - return combined; - } - - public checkDataEntryValid(combinedDataPermissions: Record | false, fieldId: string, subjectId?: string, visitId?: string): boolean { - if (!combinedDataPermissions) { - return false; - } - if (!subjectId || !visitId) { - return combinedDataPermissions.fieldIds.some((el: string) => (new RegExp(el)).test(fieldId)); - } else { - return ( - combinedDataPermissions.subjectIds.some((el: string) => (new RegExp(el)).test(subjectId)) - && combinedDataPermissions.visitIds.some((el: string) => (new RegExp(el)).test(visitId)) - && combinedDataPermissions.fieldIds.some((el: string) => (new RegExp(el)).test(fieldId)) - ); - } - } - - public async userHasTheNeccessaryDataPermission(operation: string, user: IUser, studyId: string, projectId?: string): Promise | false> { - if (user === undefined) { - return false; - } - const matchAnyString = '^.*$'; - /* if user is an admin then return true if admin privileges includes needed permissions */ - if (user.type === userTypes.ADMIN) { - return { - matchObj: { - - }, hasVersioned: true, raw: { - subjectIds: [matchAnyString], - visitIds: [matchAnyString], - fieldIds: [matchAnyString] - }, - uploaders: [], - roles: [] - }; - } - - const roles = await db.collections!.roles_collection.aggregate([ - { $match: { studyId, projectId: { $in: [projectId, null] }, users: user.id, deleted: null } }, // matches all the role documents where the study and project matches and has the user inside - { $match: { 'permissions.data.operations': operation } } - ]).toArray(); - let hasVersioned = false; - const roleObj: Array<{ key: string; op: string, parameter: boolean }>[] = []; - const raw: Record = { - subjectIds: [], - visitIds: [], - fieldIds: [] - }; - const roleraw = []; - let uploaders: string[] = []; - for (const role of roles) { - roleObj.push([{ - key: `role:${role.id}`, - op: '=', - parameter: true - }]); - raw.subjectIds = raw.subjectIds.concat(role.permissions.data?.subjectIds || []); - raw.visitIds = raw.visitIds.concat(role.permissions.data?.visitIds || []); - raw.fieldIds = raw.fieldIds.concat(role.permissions.data?.fieldIds || []); - if (role.permissions.data?.uploaders) { - uploaders = uploaders.concat(role.permissions.data?.uploaders || []); - } - if (role.permissions.data?.hasVersioned) { - hasVersioned = hasVersioned || role.permissions.data.hasVersioned; - } - roleraw.push({ - subjectIds: role.permissions.data?.subjectIds || [], - visitIds: role.permissions.data?.visitIds || [], - fieldIds: role.permissions.data?.fieldIds || [], - uploaders: role.permissions.data?.uploaders || [], - hasVersioned: role.permissions.data.hasVersioned - }); - } - if (Object.keys(roleObj).length === 0) { - return false; - } - return { matchObj: roleObj, hasVersioned: hasVersioned, uploaders: uploaders, raw: raw, roleraw: roleraw }; - } - - public combineMultiplePermissions(permissions: any[]): any { - const res = { - matchObj: [], - hasVersioned: false, - raw: { - subjectIds: [], - visitIds: [], - fieldIds: [] - } - }; - for (const permission of permissions) { - if (!permission) { - continue; - } - res.raw.subjectIds = res.raw.subjectIds.concat(permission.raw?.subjectIds || []); - res.raw.visitIds = res.raw.visitIds.concat(permission.raw?.visitIds || []); - res.raw.fieldIds = res.raw.fieldIds.concat(permission.raw?.fieldIds || []); - res.matchObj = res.matchObj.concat(permission.matchObj || []); - res.hasVersioned = res.hasVersioned || permission.hasVersioned; - } - return res; - } - - public async removeRole(roleId: string): Promise { - const updateResult = await db.collections!.roles_collection.findOneAndUpdate({ id: roleId, deleted: null }, { $set: { deleted: new Date().valueOf() } }); - if (updateResult) { - return; - } else { - throw new GraphQLError('Cannot delete role.', { extensions: { code: errorCodes.DATABASE_ERROR } }); - } - } - - public async removeRoleFromStudyOrProject({ studyId, projectId }: { studyId: string, projectId?: string } | { studyId?: string, projectId: string }): Promise { - if (studyId === undefined && projectId === undefined) { - throw new GraphQLError('Neither studyId nor projectId is provided'); - } - let queryObj = {}; - if (studyId !== undefined && projectId !== undefined) { - queryObj = { studyId, projectId, deleted: null }; - } else if (studyId !== undefined) { - queryObj = { studyId, deleted: null }; - } else if (projectId !== undefined) { - queryObj = { projectId, deleted: null }; - } - const updateResult = await db.collections!.roles_collection.updateMany(queryObj, { $set: { deleted: new Date().valueOf() } }); - if (updateResult.acknowledged) { - return; - } else { - throw new GraphQLError('Cannot delete role(s).', { extensions: { code: errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY } }); - } - } - - public async editRoleFromStudyOrProject(roleId: string, name?: string, description?: string, permissionChanges?: any, userChanges?: { add: string[], remove: string[] }): Promise { - if (permissionChanges === undefined) { - permissionChanges = { - data: { subjectIds: [], visitIds: [], fieldIds: [], uploaders: ['^.*$'], hasVersioned: false, operations: [] }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }; - } - if (userChanges === undefined) { userChanges = { add: [], remove: [] }; } - - const bulkop = db.collections!.roles_collection.initializeUnorderedBulkOp(); - bulkop.find({ id: roleId, deleted: null }).updateOne({ $set: { permissions: permissionChanges }, $addToSet: { users: { $each: userChanges.add } } }); - bulkop.find({ id: roleId, deleted: null }).updateOne({ $set: { permissions: permissionChanges }, $pullAll: { users: userChanges.remove } }); - if (name) { - bulkop.find({ id: roleId, deleted: null }).updateOne({ $set: { name } }); - } - if (description) { - bulkop.find({ id: roleId, deleted: null }).updateOne({ $set: { description } }); - } - const result: BulkWriteResult = await bulkop.execute(); - const resultingRole = await db.collections!.roles_collection.findOne({ id: roleId, deleted: null }); - if (!resultingRole) { - throw new GraphQLError('Role does not exist', { extensions: { code: errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY } }); - } - - // filters - // We send back the filtered fields values - let validSubjects: Array = []; - if (permissionChanges.data?.filters) { - if (permissionChanges.data.filters.length > 0) { - const subqueries = translateCohort(permissionChanges.data.filters); - validSubjects = (await db.collections!.data_collection.aggregate([{ - $match: { $and: subqueries } - }]).toArray()).map(el => el.m_subjectId); - } - } - - - // update the data and field records - const dataBulkOp = db.collections!.data_collection.initializeUnorderedBulkOp(); - const filters: Record = { - subjectIds: permissionChanges.data?.subjectIds || [], - visitIds: permissionChanges.data?.visitIds || [], - fieldIds: permissionChanges.data?.fieldIds || [] - }; - if (!validSubjects.length) { - validSubjects = filters.subjectIds.map((el: string) => new RegExp(el)); - } - const dataTag = `metadata.${'role:'.concat(roleId)}`; - dataBulkOp.find({ - m_studyId: resultingRole.studyId, - m_versionId: { $exists: true, $ne: null }, - $and: [{ - m_subjectId: { $in: filters.subjectIds.map((el: string) => new RegExp(el)) } - }, { - m_subjectId: { $in: validSubjects } - }], - m_visitId: { $in: filters.visitIds.map((el: string) => new RegExp(el)) }, - m_fieldId: { $in: filters.fieldIds.map((el: string) => new RegExp(el)) } - }).update({ - $set: { [dataTag]: true } - }); - dataBulkOp.find({ - m_studyId: resultingRole.studyId, - m_versionId: { $exists: true, $ne: null }, - $or: [ - { m_subjectId: { $nin: validSubjects } }, - { m_subjectId: { $nin: filters.subjectIds.map((el: string) => new RegExp(el)) } }, - { m_visitId: { $nin: filters.visitIds.map((el: string) => new RegExp(el)) } }, - { m_fieldId: { $nin: filters.fieldIds.map((el: string) => new RegExp(el)) } } - ] - }).update({ - $set: { [dataTag]: false } - }); - const fieldBulkOp = db.collections!.field_dictionary_collection.initializeUnorderedBulkOp(); - const fieldIds = permissionChanges.data?.fieldIds || []; - const fieldTag = `metadata.${'role:'.concat(roleId)}`; - fieldBulkOp.find({ - studyId: resultingRole.studyId, - dataVersion: { $exists: true, $ne: null }, - fieldId: { $in: fieldIds.map((el: string) => new RegExp(el)) } - }).update({ - $set: { [fieldTag]: true } - }); - fieldBulkOp.find({ - studyId: resultingRole.studyId, - dataVersion: { $exists: true, $ne: null }, - fieldId: { $nin: fieldIds.map((el: string) => new RegExp(el)) } - }).update({ - $set: { [fieldTag]: false } - }); - await dataBulkOp.execute(); - await fieldBulkOp.execute(); - if (result.ok === 1 && resultingRole) { - return resultingRole; - } else { - throw new GraphQLError('Cannot edit role.', { extensions: { code: errorCodes.DATABASE_ERROR } }); - } - } - - public async addRole(opt: ICreateRoleInput): Promise { - /* add user role */ - const role: IRole = { - id: uuid(), - name: opt.roleName, - permissions: { - data: { - subjectIds: [], - visitIds: [], - fieldIds: [], - hasVersioned: false, - operations: [], - uploaders: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - description: '', - users: [], - studyId: opt.studyId, - projectId: opt.projectId, - createdBy: opt.createdBy, - metadata: {}, - deleted: null - }; - const updateResult = await db.collections!.roles_collection.insertOne(role); - if (updateResult.acknowledged) { - return role; - } else { - throw new GraphQLError('Cannot create role.', { extensions: { code: errorCodes.DATABASE_ERROR } }); - } - } -} - -export const permissionCore = new PermissionCore(); - - -export function translateCohort(cohort: any) { - const queries: any[] = []; - cohort.forEach(function (select: any) { - const match: any = { - m_fieldId: select.field - }; - switch (select.op) { - case '=': - // select.value must be an array - match['value'] = { $in: [select.value] }; - break; - case '!=': - // select.value must be an array - match['value'] = { $nin: [select.value] }; - break; - case '<': - // select.value must be a float - match['value'] = { $lt: parseFloat(select.value) }; - break; - case '>': - // select.value must be a float - match['value'] = { $gt: parseFloat(select.value) }; - break; - case 'derived': { - // equation must only have + - * / - const derivedOperation = select.value.split(' '); - if (derivedOperation[0] === '=') { - match['value'] = { $eq: parseFloat(select.value) }; - } - if (derivedOperation[0] === '>') { - match['value'] = { $gt: parseFloat(select.value) }; - } - if (derivedOperation[0] === '<') { - match['value'] = { $lt: parseFloat(select.value) }; - } - break; - } - case 'exists': - // We check if the field exists. This is to be used for checking if a patient - // has an image - match['value'] = { $exists: true }; - break; - case 'count': { - // counts can only be positive. NB: > and < are inclusive e.g. < is <= - const countOperation = select.value.split(' '); - const countfield = select.field + '.count'; - if (countOperation[0] === '=') { - (match as any)[countfield] = { $eq: parseInt(countOperation[1], 10) }; - } - if (countOperation[0] === '>') { - (match as any)[countfield] = { $gt: parseInt(countOperation[1], 10) }; - } - if (countOperation[0] === '<') { - (match as any)[countfield] = { $lt: parseInt(countOperation[1], 10) }; - } - break; - } - default: - break; - } - queries.push(match); - } - ); - return queries; -} diff --git a/packages/itmat-interface/src/graphql/core/queryCore.ts b/packages/itmat-interface/src/graphql/core/queryCore.ts deleted file mode 100644 index e48785eeb..000000000 --- a/packages/itmat-interface/src/graphql/core/queryCore.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { IQueryEntry } from '@itmat-broker/itmat-types'; -import { v4 as uuid } from 'uuid'; -import { db } from '../../database/database'; - -export class QueryCore { - public async createQuery(args: any): Promise { - const query: IQueryEntry = { - requester: args.query.userId, - id: uuid(), - queryString: args.query.queryString, - studyId: args.query.studyId, - projectId: args.query.projectId, - status: 'QUEUED', - error: null, - cancelled: false, - data_requested: args.query.queryString.data_requested, - cohort: args.query.queryString.cohort, - new_fields: args.query.queryString.new_fields - }; - await db.collections!.queries_collection.insertOne(query); - return query; - } - - public async getUsersQuery_NoResult(userId: string): Promise { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore - return db.collections!.queries_collection.find({ requester: userId }, { projection: { _id: 0, claimedBy: 0, queryResult: 0 } }).toArray(); - } - -} - -export const queryCore = Object.freeze(new QueryCore()); diff --git a/packages/itmat-interface/src/graphql/core/studyCore.ts b/packages/itmat-interface/src/graphql/core/studyCore.ts deleted file mode 100644 index 45f2c58ec..000000000 --- a/packages/itmat-interface/src/graphql/core/studyCore.ts +++ /dev/null @@ -1,655 +0,0 @@ -import { GraphQLError } from 'graphql'; -import { IFile, IUser, IProject, IStudy, studyType, IStudyDataVersion, IDataEntry, IDataClip, IRole, IFieldEntry, deviceTypes, IOrganisation } from '@itmat-broker/itmat-types'; -import { v4 as uuid } from 'uuid'; -import { db } from '../../database/database'; -import { errorCodes } from '../errors'; -import { PermissionCore, permissionCore, translateCohort } from './permissionCore'; -import { validate } from '@ideafast/idgen'; -import type { MatchKeysAndValues } from 'mongodb'; -import { objStore } from '../../objStore/objStore'; -import { FileUpload } from 'graphql-upload-minimal'; -import crypto from 'crypto'; -import { fileSizeLimit } from '../../utils/definition'; -import { IGenericResponse } from '../responses'; -export class StudyCore { - constructor(private readonly localPermissionCore: PermissionCore) { } - - public async findOneStudy_throwErrorIfNotExist(studyId: string): Promise { - const studySearchResult = await db.collections!.studies_collection.findOne({ id: studyId, deleted: null })!; - if (studySearchResult === null || studySearchResult === undefined) { - throw new GraphQLError('Study does not exist.', { extensions: { code: errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY } }); - } - return studySearchResult; - } - - public async findOneProject_throwErrorIfNotExist(projectId: string): Promise { - const projectSearchResult = await db.collections!.projects_collection.findOne({ id: projectId, deleted: null })!; - if (projectSearchResult === null || projectSearchResult === undefined) { - throw new GraphQLError('Project does not exist.', { extensions: { code: errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY } }); - } - return projectSearchResult; - } - - public async createNewStudy(studyName: string, description: string, type: studyType, requestedBy: string): Promise { - /* check if study already exist (lowercase because S3 minio buckets cant be mixed case) */ - const existingStudies = await db.collections!.studies_collection.aggregate( - [ - { $match: { deleted: null } }, - { - $group: { - _id: '', - name: { - $push: { $toLower: '$name' } - } - } - }, - { $project: { name: 1 } } - ] - ).toArray(); - - if (existingStudies[0] && existingStudies[0].name.includes(studyName.toLowerCase())) { - throw new GraphQLError(`Study "${studyName}" already exists (duplicates are case-insensitive).`); - } - - const study: IStudy = { - id: uuid(), - name: studyName, - createdBy: requestedBy, - currentDataVersion: -1, - lastModified: new Date().valueOf(), - dataVersions: [], - deleted: null, - description: description, - type: type, - ontologyTrees: [], - metadata: {} - }; - await db.collections!.studies_collection.insertOne(study); - return study; - } - - public async editStudy(studyId: string, description: string): Promise { - const res = await db.collections!.studies_collection.findOneAndUpdate({ id: studyId }, { $set: { description: description } }, { returnDocument: 'after' }); - if (res) { - return res; - } else { - throw new GraphQLError('Edit study failed'); - } - } - - public async createNewDataVersion(studyId: string, tag: string, dataVersion: string): Promise { - const newDataVersionId = uuid(); - const newContentId = uuid(); - - // update data - const resData = await db.collections!.data_collection.updateMany({ - m_studyId: studyId, - m_versionId: null - }, { - $set: { - m_versionId: newDataVersionId - } - }); - // update field - const resField = await db.collections!.field_dictionary_collection.updateMany({ - studyId: studyId, - dataVersion: null - }, { - $set: { - dataVersion: newDataVersionId - } - }); - // update standardization - const resStandardization = await db.collections!.standardizations_collection.updateMany({ - studyId: studyId, - dataVersion: null - }, { - $set: { - dataVersion: newDataVersionId - } - }); - - // update ontology trees - const resOntologyTrees = await db.collections!.studies_collection.updateOne({ 'id': studyId, 'deleted': null, 'ontologyTrees.dataVersion': null }, { - $set: { - 'ontologyTrees.$.dataVersion': newDataVersionId - } - }); - - if (resData.modifiedCount === 0 && resField.modifiedCount === 0 && resStandardization.modifiedCount === 0 && resOntologyTrees.modifiedCount === 0) { - return null; - } - - // insert a new version into study - const newDataVersion: IStudyDataVersion = { - id: newDataVersionId, - contentId: newContentId, // same content = same id - used in reverting data, version control - version: dataVersion, - tag: tag, - updateDate: (new Date().valueOf()).toString() - }; - await db.collections!.studies_collection.updateOne({ id: studyId }, { - $push: { dataVersions: newDataVersion }, - $inc: { - currentDataVersion: 1 - } - }); - - // update permissions based on roles - const roles = await db.collections!.roles_collection.find({ studyId: studyId, deleted: null }).toArray(); - for (const role of roles) { - const filters: Record = { - subjectIds: role.permissions.data?.subjectIds || [], - visitIds: role.permissions.data?.visitIds || [], - fieldIds: role.permissions.data?.fieldIds || [] - }; - // deal with data filters - let validSubjects: Array | null = null; - if (role.permissions.data?.filters) { - if (role.permissions.data.filters.length > 0) { - validSubjects = []; - const subqueries = translateCohort(role.permissions.data.filters); - validSubjects = (await db.collections!.data_collection.aggregate([{ - $match: { m_fieldId: { $in: role.permissions.data.filters.map(el => el.field) } } - }, - { - $sort: { uploadedAt: -1 } - }, { - $group: { - _id: { m_subjectId: '$m_subjectId', m_visitId: '$m_visitId', m_fieldId: '$m_fieldId' }, - doc: { $first: '$$ROOT' } - } - }, { - $project: { - m_subjectId: '$doc.m_subjectId', - m_visitId: '$doc.m_visitId', - m_fieldId: '$doc.m_fieldId', - value: '$doc.value', - _id: 0 - } - }, { - $match: { $and: subqueries } - }], { allowDiskUse: true }).toArray()).map(el => el.m_subjectId); - } - } - if (validSubjects === null) { - validSubjects = [/^.*$/]; - } - const tag = `metadata.${'role:'.concat(role.id)}`; - await db.collections!.data_collection.updateMany({ - m_studyId: studyId, - m_versionId: newDataVersionId, - $and: [ - { m_subjectId: { $in: filters.subjectIds.map((el: string) => new RegExp(el)) } }, - { m_subjectId: { $in: validSubjects } } - ], - m_visitId: { $in: filters.visitIds.map((el: string) => new RegExp(el)) }, - m_fieldId: { $in: filters.fieldIds.map((el: string) => new RegExp(el)) } - }, { - $set: { [tag]: true } - }); - await db.collections!.data_collection.updateMany({ - m_studyId: studyId, - m_versionId: newDataVersionId, - $or: [ - { m_subjectId: { $nin: filters.subjectIds.map((el: string) => new RegExp(el)) } }, - { m_subjectId: { $nin: validSubjects } }, - { m_visitId: { $nin: filters.visitIds.map((el: string) => new RegExp(el)) } }, - { m_fieldId: { $nin: filters.fieldIds.map((el: string) => new RegExp(el)) } } - ] - }, { - $set: { [tag]: false } - }); - await db.collections!.field_dictionary_collection.updateMany({ - studyId: studyId, - dataVersion: newDataVersionId, - fieldId: { $in: filters.fieldIds.map((el: string) => new RegExp(el)) } - }, { - $set: { [tag as any]: true } - }); - await db.collections!.field_dictionary_collection.updateMany({ - studyId: studyId, - dataVersion: newDataVersionId, - fieldId: { $nin: filters.fieldIds.map((el: string) => new RegExp(el)) } - }, { - $set: { [tag as any]: false } - }); - } - return newDataVersion; - } - - public async uploadOneDataClip(studyId: string, permissions: any, fieldList: Partial[], data: IDataClip[], requester: IUser): Promise { - const response: IGenericResponse[] = []; - let bulk = db.collections!.data_collection.initializeUnorderedBulkOp(); - // remove duplicates by subjectId, visitId and fieldId - const keysToCheck: Array = ['visitId', 'subjectId', 'fieldId']; - const filteredData = data.filter( - (s => o => (k => !s.has(k) && s.add(k))(keysToCheck.map(k => o[k]).join('|')))(new Set()) - ); - for (const dataClip of filteredData) { - // remove the '-' if there exists - dataClip.subjectId = dataClip.subjectId.replace('-', ''); - const fieldInDb = fieldList.filter(el => el.fieldId === dataClip.fieldId)[0]; - if (!fieldInDb) { - response.push({ successful: false, code: errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, description: `Field ${dataClip.fieldId}: Field Not found` }); - continue; - } - // check subjectId - if (!validate(dataClip.subjectId.substr(1) ?? '')) { - response.push({ successful: false, code: errorCodes.CLIENT_MALFORMED_INPUT, description: `Subject ID ${dataClip.subjectId} is illegal.` }); - continue; - } - if (!(await permissionCore.checkDataEntryValid(permissions, dataClip.fieldId, dataClip.subjectId, dataClip.visitId))) { - response.push({ successful: false, code: errorCodes.NO_PERMISSION_ERROR, description: 'You do not have access to this field.' }); - continue; - } - // check value is valid - let error; - let parsedValue; - if (dataClip.value?.toString() === '99999') { // agreement with other WPs, 99999 refers to missing - parsedValue = '99999'; - } else { - switch (fieldInDb.dataType) { - case 'dec': {// decimal - if (typeof (dataClip.value) !== 'string') { - error = `Field ${dataClip.fieldId}: Cannot parse as decimal.`; - break; - } - if (!/^\d+(.\d+)?$/.test(dataClip.value)) { - error = `Field ${dataClip.fieldId}: Cannot parse as decimal.`; - break; - } - parsedValue = parseFloat(dataClip.value); - break; - } - case 'int': {// integer - if (typeof (dataClip.value) !== 'string') { - error = `Field ${dataClip.fieldId}: Cannot parse as integer.`; - break; - } - if (!/^-?\d+$/.test(dataClip.value)) { - error = `Field ${dataClip.fieldId}: Cannot parse as integer.`; - break; - } - parsedValue = parseInt(dataClip.value, 10); - break; - } - case 'bool': {// boolean - if (typeof (dataClip.value) !== 'string') { - error = `Field ${dataClip.fieldId}: Cannot parse as boolean.`; - break; - } - if (dataClip.value.toLowerCase() === 'true' || dataClip.value.toLowerCase() === 'false') { - parsedValue = dataClip.value.toLowerCase() === 'true'; - } else { - error = `Field ${dataClip.fieldId}: Cannot parse as boolean.`; - break; - } - break; - } - case 'str': { - if (typeof (dataClip.value) !== 'string') { - error = `Field ${dataClip.fieldId}: Cannot parse as string.`; - break; - } - parsedValue = dataClip.value.toString(); - break; - } - // 01/02/2021 00:00:00 - case 'date': { - if (typeof (dataClip.value) !== 'string') { - error = `Field ${dataClip.fieldId}: Cannot parse as data. Value for date type must be in ISO format.`; - break; - } - const matcher = /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?/; - if (!dataClip.value.match(matcher)) { - error = `Field ${dataClip.fieldId}: Cannot parse as data. Value for date type must be in ISO format.`; - break; - } - parsedValue = dataClip.value.toString(); - break; - } - case 'json': { - parsedValue = dataClip.value; - break; - } - case 'file': { - if (!dataClip.file || typeof (dataClip.file) === 'string') { - error = `Field ${dataClip.fieldId}: Cannot parse as file.`; - break; - } - // if old file exists, delete it first - const res = await this.uploadFile(studyId, dataClip, requester, {}); - if ('code' in res && 'description' in res) { - error = `Field ${dataClip.fieldId}: Cannot parse as file.`; - break; - } else { - parsedValue = res.id; - } - break; - } - case 'cat': { - if (!fieldInDb.possibleValues) { - error = `Field ${dataClip.fieldId}: Cannot parse as categorical, possible values not defined.`; - break; - } - if (!fieldInDb.possibleValues.map((el: any) => el.code).includes(dataClip.value?.toString())) { - error = `Field ${dataClip.fieldId}: Cannot parse as categorical, value not in value list.`; - break; - } else { - parsedValue = dataClip.value?.toString(); - } - break; - } - default: { - error = (`Field ${dataClip.fieldId}: Invalid data Type.`); - break; - } - } - } - if (error !== undefined) { - response.push({ successful: false, code: errorCodes.CLIENT_MALFORMED_INPUT, description: error }); - continue; - } else { - response.push({ successful: true, description: `${dataClip.subjectId}-${dataClip.visitId}-${dataClip.fieldId}` }); - } - const obj = { - m_studyId: studyId, - m_versionId: null, - m_subjectId: dataClip.subjectId, - m_visitId: dataClip.visitId, - m_fieldId: dataClip.fieldId - }; - let objWithData: Partial>; - // update the file data differently - if (fieldInDb.dataType === 'file') { - const existing = await db.collections!.data_collection.findOne(obj); - if (!existing) { - await db.collections!.data_collection.insertOne({ - ...obj, - id: uuid(), - uploadedAt: (new Date()).valueOf(), - value: '', - metadata: { - add: [], - remove: [] - } - }); - } - - objWithData = { - ...obj, - id: uuid(), - value: '', - uploadedAt: (new Date()).valueOf(), - metadata: { - ...dataClip.metadata, - participantId: dataClip.subjectId, - add: ((existing?.metadata as any)?.add || []).concat(parsedValue), - uploader: requester.id - }, - uploadedBy: requester.id - }; - bulk.find(obj).updateOne({ $set: objWithData }); - } else { - objWithData = { - ...obj, - id: uuid(), - value: parsedValue, - uploadedAt: (new Date()).valueOf(), - metadata: { - ...dataClip.metadata, - uploader: requester.id - }, - uploadedBy: requester.id - }; - bulk.insert(objWithData); - } - if (bulk.batches.length > 999) { - await bulk.execute(); - bulk = db.collections!.data_collection.initializeUnorderedBulkOp(); - } - } - bulk.batches.length !== 0 && await bulk.execute(); - return response; - } - - // This file uploading function will not check any metadate of the file - public async uploadFile(studyId: string, data: IDataClip, uploader: IUser, args: { fileLength?: number, fileHash?: string }): Promise { - if (!data.file || typeof (data.file) === 'string') { - return { code: errorCodes.CLIENT_MALFORMED_INPUT, description: 'Invalid File Stream' }; - } - const study = await db.collections!.studies_collection.findOne({ id: studyId }); - if (!study) { - return { code: errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY, description: 'Study does not exist.' }; - } - const sitesIDMarkers = (await db.collections!.organisations_collection.find({ deleted: null }).toArray()).reduce((acc, curr) => { - if (curr.metadata?.siteIDMarker) { - acc[curr.metadata.siteIDMarker] = curr.shortname; - } - return acc; - }, {}); - // check file metadata - if (data.metadata) { - let parsedDescription: Record; - let startDate; - let endDate; - let deviceId; - let participantId; - try { - parsedDescription = data.metadata; - startDate = parseInt(parsedDescription.startDate); - endDate = parseInt(parsedDescription.endDate); - participantId = data.subjectId.toString(); - deviceId = parsedDescription.deviceId.toString(); - } catch (e) { - return { code: errorCodes.CLIENT_MALFORMED_INPUT, description: 'File description is invalid' }; - } - if ( - !Object.keys(sitesIDMarkers).includes(participantId.substr(0, 1)?.toUpperCase()) || - !Object.keys(deviceTypes).includes(deviceId.substr(0, 3)?.toUpperCase()) || - !validate(participantId.substr(1) ?? '') || - !validate(deviceId.substr(3) ?? '') || - !startDate || !endDate || - (new Date(endDate).setHours(0, 0, 0, 0).valueOf()) > (new Date().setHours(0, 0, 0, 0).valueOf()) - ) { - return { code: errorCodes.CLIENT_MALFORMED_INPUT, description: 'File description is invalid' }; - } - } else { - return { code: errorCodes.CLIENT_MALFORMED_INPUT, description: 'File description is invalid' }; - } - - - const file: FileUpload = await data.file; - - // check if old files exist; if so, denote it as deleted - const dataEntry = await db.collections!.data_collection.findOne({ m_studyId: studyId, m_visitId: data.visitId, m_subjectId: data.subjectId, m_versionId: null, m_fieldId: data.fieldId }); - const oldFileId = dataEntry ? dataEntry.value : null; - return new Promise((resolve, reject) => { - - (async () => { - try { - const fileEntry: Partial = { - id: uuid(), - fileName: file.filename, - studyId: studyId, - description: JSON.stringify({}), - uploadTime: `${Date.now()}`, - uploadedBy: uploader.id, - deleted: null, - metadata: (data.metadata as Record) - }; - - if (args.fileLength !== undefined && args.fileLength > fileSizeLimit) { - reject(new GraphQLError('File should not be larger than 8GB', { extensions: { code: errorCodes.CLIENT_MALFORMED_INPUT } })); - return; - } - - const stream = file.createReadStream(); - const fileUri = uuid(); - const hash = crypto.createHash('sha256'); - let readBytes = 0; - - stream.pause(); - - /* if the client cancelled the request mid-stream it will throw an error */ - stream.on('error', (e) => { - reject(new GraphQLError('Upload resolver file stream failure', { extensions: { code: errorCodes.FILE_STREAM_ERROR, error: e } })); - return; - }); - - stream.on('data', (chunk) => { - readBytes += chunk.length; - if (readBytes > fileSizeLimit) { - stream.destroy(); - reject(new GraphQLError('File should not be larger than 8GB', { extensions: { code: errorCodes.CLIENT_MALFORMED_INPUT } })); - return; - } - hash.update(chunk); - }); - - - await objStore.uploadFile(stream, studyId, fileUri); - - // hash is optional, but should be correct if provided - const hashString = hash.digest('hex'); - if (args.fileHash && args.fileHash !== hashString) { - reject(new GraphQLError('File hash not match', { extensions: { code: errorCodes.CLIENT_MALFORMED_INPUT } })); - return; - } - - // check if readbytes equal to filelength in parameters - if (args.fileLength !== undefined && args.fileLength.toString() !== readBytes.toString()) { - reject(new GraphQLError('File size mismatch', { extensions: { code: errorCodes.CLIENT_MALFORMED_INPUT } })); - return; - } - - fileEntry.fileSize = readBytes.toString(); - fileEntry.uri = fileUri; - fileEntry.hash = hashString; - const insertResult = await db.collections!.files_collection.insertOne(fileEntry as IFile); - if (insertResult.acknowledged) { - // delete old file if existing - await db.collections!.files_collection.findOneAndUpdate({ studyId: studyId, id: oldFileId }, { $set: { deleted: Date.now().valueOf() } }); - resolve(fileEntry as IFile); - } else { - throw new GraphQLError(errorCodes.DATABASE_ERROR); - } - } - catch (error) { - reject({ code: errorCodes.CLIENT_MALFORMED_INPUT, description: 'Missing file metadata.', error }); - return; - } - })(); - }); - } - - public async createProjectForStudy(studyId: string, projectName: string, requestedBy: string): Promise { - const project: IProject = { - id: uuid(), - studyId, - createdBy: requestedBy, - name: projectName, - patientMapping: {}, - lastModified: new Date().valueOf(), - deleted: null, - metadata: {} - }; - - const getListOfPatientsResult = await db.collections!.data_collection.aggregate([ - { $match: { m_studyId: studyId } }, - { $group: { _id: null, array: { $addToSet: '$m_subjectId' } } }, - { $project: { array: 1 } } - ]).toArray(); - - if (getListOfPatientsResult === null || getListOfPatientsResult === undefined) { - throw new GraphQLError('Cannot get list of patients', { extensions: { code: errorCodes.DATABASE_ERROR } }); - } - - if (getListOfPatientsResult[0] !== undefined) { - project.patientMapping = this.createPatientIdMapping(getListOfPatientsResult[0].array); - } - - await db.collections!.projects_collection.insertOne(project); - return project; - } - - public async deleteStudy(studyId: string): Promise { - /* PRECONDITION: CHECKED THAT STUDY INDEED EXISTS */ - const session = db.client!.startSession(); - session.startTransaction(); - - const timestamp = new Date().valueOf(); - - try { - /* delete the study */ - await db.collections!.studies_collection.findOneAndUpdate({ id: studyId, deleted: null }, { $set: { lastModified: timestamp, deleted: timestamp } }); - - /* delete all projects related to the study */ - await db.collections!.projects_collection.updateMany({ studyId, deleted: null }, { $set: { lastModified: timestamp, deleted: timestamp } }); - - /* delete all roles related to the study */ - await this.localPermissionCore.removeRoleFromStudyOrProject({ studyId }); - - /* delete all files belong to the study*/ - await db.collections!.files_collection.updateMany({ studyId, deleted: null }, { $set: { deleted: timestamp } }); - - await session.commitTransaction(); - session.endSession(); - - } catch (error) { - // If an error occurred, abort the whole transaction and - // undo any changes that might have happened - await session.abortTransaction(); - session.endSession(); - throw error; // Rethrow so calling function sees error - } - } - - public async deleteProject(projectId: string): Promise { - const timestamp = new Date().valueOf(); - - /* delete all projects related to the study */ - await db.collections!.projects_collection.findOneAndUpdate({ id: projectId, deleted: null }, { $set: { lastModified: timestamp, deleted: timestamp } }, { returnDocument: 'after' }); - - /* delete all roles related to the study */ - await this.localPermissionCore.removeRoleFromStudyOrProject({ projectId }); - } - - private createPatientIdMapping(listOfPatientId: string[], prefix?: string): { [originalPatientId: string]: string } { - let rangeArray: Array = [...Array.from(listOfPatientId.keys())]; - if (prefix === undefined) { - prefix = uuid().substring(0, 10); - } - rangeArray = rangeArray.map((e) => `${prefix}${e} `); - rangeArray = this.shuffle(rangeArray); - const mapping: { [originalPatientId: string]: string } = {}; - for (let i = 0, length = listOfPatientId.length; i < length; i++) { - mapping[listOfPatientId[i]] = (rangeArray as string[])[i]; - } - return mapping; - } - - private shuffle(array: Array) { // source: Fisher–Yates Shuffle; https://bost.ocks.org/mike/shuffle/ - let currentIndex = array.length; - let temporaryValue: string | number; - let randomIndex: number; - - // While there remain elements to shuffle... - while (0 !== currentIndex) { - - // Pick a remaining element... - randomIndex = Math.floor(Math.random() * currentIndex); - currentIndex -= 1; - - // And swap it with the current element. - temporaryValue = array[currentIndex]; - array[currentIndex] = array[randomIndex]; - array[randomIndex] = temporaryValue; - } - - return array; - } -} - -export const studyCore = Object.freeze(new StudyCore(permissionCore)); diff --git a/packages/itmat-interface/src/graphql/core/userCore.ts b/packages/itmat-interface/src/graphql/core/userCore.ts deleted file mode 100644 index 822bbfab2..000000000 --- a/packages/itmat-interface/src/graphql/core/userCore.ts +++ /dev/null @@ -1,129 +0,0 @@ -import bcrypt from 'bcrypt'; -import { db } from '../../database/database'; -import config from '../../utils/configManager'; -import { GraphQLError } from 'graphql'; -import { IUser, IUserWithoutToken, userTypes, IOrganisation, IPubkey } from '@itmat-broker/itmat-types'; -import { v4 as uuid } from 'uuid'; -import { errorCodes } from '../errors'; -import { MarkOptional } from 'ts-essentials'; - -export class UserCore { - public async getOneUser_throwErrorIfNotExists(username: string): Promise { - const user = await db.collections!.users_collection.findOne({ deleted: null, username }); - if (user === undefined || user === null) { - throw new GraphQLError('User does not exist.', { extensions: { code: errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY } }); - } - return user; - } - - public async createUser(user: { password: string, otpSecret: string, username: string, organisation: string, type: userTypes, description: string, firstname: string, lastname: string, email: string, emailNotificationsActivated: boolean, metadata: any }): Promise { - const { password, otpSecret, organisation, username, type, description, firstname, lastname, email, emailNotificationsActivated, metadata } = user; - const hashedPassword: string = await bcrypt.hash(password, config.bcrypt.saltround); - const createdAt = Date.now(); - const expiredAt = Date.now() + 86400 * 1000 /* millisec per day */ * 90; - const entry: IUser = { - id: uuid(), - username, - otpSecret, - type, - description, - organisation, - firstname, - lastname, - password: hashedPassword, - email, - emailNotificationsActivated, - emailNotificationsStatus: { expiringNotification: false }, - createdAt, - expiredAt, - resetPasswordRequests: [], - metadata, - deleted: null - }; - - const result = await db.collections!.users_collection.insertOne(entry); - if (result.acknowledged) { - const cleared: MarkOptional = { ...entry }; - delete cleared['password']; - delete cleared['otpSecret']; - return cleared; - } else { - throw new GraphQLError('Database error', { extensions: { code: errorCodes.DATABASE_ERROR } }); - } - } - - public async deleteUser(userId: string): Promise { - const session = db.client!.startSession(); - session.startTransaction(); - try { - /* delete the user */ - await db.collections!.users_collection.findOneAndUpdate({ id: userId, deleted: null }, { $set: { deleted: new Date().valueOf(), password: 'DeletedUserDummyPassword' } }, { returnDocument: 'after', projection: { deleted: 1 } }); - - /* delete all user records in roles related to the study */ - await db.collections!.roles_collection.updateMany( - { - deleted: null, - users: userId - }, - { - $pull: { users: { _id: userId } } - } - ); - - await session.commitTransaction(); - session.endSession(); - } catch (error) { - // If an error occurred, abort the whole transaction and - // undo any changes that might have happened - await session.abortTransaction(); - session.endSession(); - throw new GraphQLError(`Database error: ${JSON.stringify(error)}`); - } - } - - public async createOrganisation(org: { name: string, shortname: string | null, containOrg: string | null, metadata: any }): Promise { - const { name, shortname, containOrg, metadata } = org; - const entry: IOrganisation = { - id: uuid(), - name, - shortname, - containOrg, - deleted: null, - metadata: metadata?.siteIDMarker ? { - siteIDMarker: metadata.siteIDMarker - } : {} - }; - const result = await db.collections!.organisations_collection.findOneAndUpdate({ name: name, deleted: null }, { - $set: entry - }, { - upsert: true - }); - if (result) { - return entry; - } else { - throw new GraphQLError('Database error', { extensions: { code: errorCodes.DATABASE_ERROR } }); - } - } - - public async registerPubkey(pubkeyobj: { pubkey: string, associatedUserId: string | null, jwtPubkey: string, jwtSeckey: string }): Promise { - const { pubkey, associatedUserId, jwtPubkey, jwtSeckey } = pubkeyobj; - const entry: IPubkey = { - id: uuid(), - pubkey, - associatedUserId, - jwtPubkey, - jwtSeckey, - refreshCounter: 0, - deleted: null - }; - - const result = await db.collections!.pubkeys_collection.insertOne(entry); - if (result.acknowledged) { - return entry; - } else { - throw new GraphQLError('Database error', { extensions: { code: errorCodes.DATABASE_ERROR } }); - } - } -} - -export const userCore = Object.freeze(new UserCore()); diff --git a/packages/itmat-interface/src/graphql/declare.d.ts b/packages/itmat-interface/src/graphql/declare.d.ts deleted file mode 100644 index 744ba13ec..000000000 --- a/packages/itmat-interface/src/graphql/declare.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'graphql-type-json'; diff --git a/packages/itmat-interface/src/graphql/resolvers/fileResolvers.ts b/packages/itmat-interface/src/graphql/resolvers/fileResolvers.ts deleted file mode 100644 index b737f4a3a..000000000 --- a/packages/itmat-interface/src/graphql/resolvers/fileResolvers.ts +++ /dev/null @@ -1,310 +0,0 @@ -import { GraphQLError } from 'graphql'; -import { IFile, IOrganisation, IUser, atomicOperation, IPermissionManagementOptions, IDataEntry } from '@itmat-broker/itmat-types'; -import { v4 as uuid } from 'uuid'; -import { FileUpload } from 'graphql-upload-minimal'; -import { db } from '../../database/database'; -import { objStore } from '../../objStore/objStore'; -import { permissionCore } from '../core/permissionCore'; -import { errorCodes } from '../errors'; -import { IGenericResponse, makeGenericReponse } from '../responses'; -import crypto from 'crypto'; -import { validate } from '@ideafast/idgen'; -import { deviceTypes } from '@itmat-broker/itmat-types'; -import { fileSizeLimit } from '../../utils/definition'; -import type { MatchKeysAndValues } from 'mongodb'; -import { studyCore } from '../core/studyCore'; - -// default visitId for file data -const targetVisitId = '0'; -export const fileResolvers = { - Query: { - }, - Mutation: { - // this API has the same functions as uploading file data via clinical APIs - uploadFile: async (__unused__parent: Record, args: { fileLength?: bigint, studyId: string, file: Promise, description: string, hash?: string }, context: any): Promise => { - - const requester: IUser = context.req.user; - // get the target fieldId of this file - const study = await studyCore.findOneStudy_throwErrorIfNotExist(args.studyId); - - const hasStudyLevelSubjectPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.WRITE, - requester, - args.studyId - ); - const hasStudyLevelStudyDataPermission = await permissionCore.userHasTheNeccessaryManagementPermission( - IPermissionManagementOptions.own, - atomicOperation.WRITE, - requester, - args.studyId - ); - if (!hasStudyLevelSubjectPermission && !hasStudyLevelStudyDataPermission) { throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); } - - let targetFieldId: string; - let isStudyLevel = false; - // obtain sitesIDMarker from db - const sitesIDMarkers = (await db.collections!.organisations_collection.find({ deleted: null }).toArray()).reduce((acc, curr) => { - if (curr.metadata?.siteIDMarker) { - acc[curr.metadata.siteIDMarker] = curr.shortname; - } - return acc; - }, {}); - // if the description object is empty, then the file is study-level data - // otherwise, a subjectId must be provided in the description object - // we will check other properties in the decription object (deviceId, startDate, endDate) - const parsedDescription = JSON.parse(args.description); - if (!parsedDescription) { - throw new GraphQLError('File description is invalid', { extensions: { code: errorCodes.CLIENT_MALFORMED_INPUT } }); - } - if (!parsedDescription.participantId) { - isStudyLevel = true; - } else { - isStudyLevel = false; - if (!Object.keys(sitesIDMarkers).includes(parsedDescription.participantId?.substr(0, 1)?.toUpperCase())) { - throw new GraphQLError('File description is invalid', { extensions: { code: errorCodes.CLIENT_MALFORMED_INPUT } }); - } - // check deviceId, startDate, endDate if necessary - if (parsedDescription.deviceId && parsedDescription.startDate && parsedDescription.endDate) { - if (!Object.keys(deviceTypes).includes(parsedDescription.deviceId?.substr(0, 3)?.toUpperCase()) || - !validate(parsedDescription.participantId?.substr(1) ?? '') || - !validate(parsedDescription.deviceId.substr(3) ?? '')) { - throw new GraphQLError('File description is invalid', { extensions: { code: errorCodes.CLIENT_MALFORMED_INPUT } }); - } - } else { - throw new GraphQLError('File description is invalid', { extensions: { code: errorCodes.CLIENT_MALFORMED_INPUT } }); - } - // if the targetFieldId is in the description object; then use the fieldId, otherwise, infer it from the device types - if (parsedDescription.fieldId) { - targetFieldId = parsedDescription.fieldId; - } else { - const device = parsedDescription.deviceId?.slice(0, 3); - targetFieldId = `Device_${deviceTypes[device].replace(/ /g, '_')}`; - } - // check fieldId exists - if ((await db.collections!.field_dictionary_collection.find({ studyId: study.id, fieldId: targetFieldId, dateDeleted: null }).sort({ dateAdded: -1 }).limit(1).toArray()).length === 0) { - throw new GraphQLError('File description is invalid', { extensions: { code: errorCodes.CLIENT_MALFORMED_INPUT } }); - } - // check field permission - if (!permissionCore.checkDataEntryValid(await permissionCore.combineUserDataPermissions(atomicOperation.WRITE, requester, args.studyId, undefined), targetFieldId, parsedDescription.participantId, targetVisitId)) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - } - - const file = await args.file; - const fileNameParts = file.filename.split('.'); - - return new Promise((resolve, reject) => { - (async () => { - try { - const fileEntry: Partial = { - id: uuid(), - fileName: file.filename, - studyId: args.studyId, - description: args.description, - uploadTime: `${Date.now()}`, - uploadedBy: requester.id, - deleted: null, - metadata: {} - }; - if (!isStudyLevel) { - const matcher = /(.{1})(.{6})-(.{3})(.{6})-(\d{8})-(\d{8})\.(.*)/; - let startDate; - let endDate; - let participantId; - let deviceId; - // check description first, then filename - if (args.description) { - const parsedDescription = JSON.parse(args.description); - startDate = parseInt(parsedDescription.startDate); - endDate = parseInt(parsedDescription.endDate); - participantId = parsedDescription.participantId.toString(); - deviceId = parsedDescription.deviceId.toString(); - } else if (matcher.test(file.filename)) { - const particles = file.filename.split('-'); - participantId = particles[0]; - deviceId = particles[1]; - startDate = particles[2]; - endDate = particles[3]; - } else { - reject(new GraphQLError('Missing file description', { extensions: { code: errorCodes.CLIENT_MALFORMED_INPUT } })); - return; - } - - - try { - if ( - !Object.keys(sitesIDMarkers).includes(participantId.substr(0, 1)?.toUpperCase()) || - !Object.keys(deviceTypes).includes(deviceId.substr(0, 3)?.toUpperCase()) || - !validate(participantId.substr(1) ?? '') || - !validate(deviceId.substr(3) ?? '') || - !startDate || !endDate || - (new Date(endDate).setHours(0, 0, 0, 0).valueOf()) > (new Date().setHours(0, 0, 0, 0).valueOf()) - ) { - reject(new GraphQLError('File description is invalid', { extensions: { code: errorCodes.CLIENT_MALFORMED_INPUT } })); - return; - } - } catch (e) { - reject(new GraphQLError('Missing file description', { extensions: { code: errorCodes.CLIENT_MALFORMED_INPUT } })); - return; - } - - const typedStartDate = new Date(startDate); - const formattedStartDate = typedStartDate.getFullYear() + `${typedStartDate.getMonth() + 1}`.padStart(2, '0') + `${typedStartDate.getDate()}`.padStart(2, '0'); - const typedEndDate = new Date(endDate); - const formattedEndDate = typedEndDate.getFullYear() + `${typedEndDate.getMonth() + 1}`.padStart(2, '0') + `${typedEndDate.getDate()}`.padStart(2, '0'); - fileEntry.fileName = `${parsedDescription.participantId.toUpperCase()}-${parsedDescription.deviceId.toUpperCase()}-${formattedStartDate}-${formattedEndDate}.${fileNameParts[fileNameParts.length - 1]}`; - fileEntry.metadata = { - participantId: parsedDescription.participantId, - deviceId: parsedDescription.deviceId, - startDate: parsedDescription.startDate, // should be in milliseconds - endDate: parsedDescription.endDate, - tup: parsedDescription.tup - }; - } - - if (args.fileLength !== undefined && args.fileLength > fileSizeLimit) { - reject(new GraphQLError('File should not be larger than 8GB', { extensions: { code: errorCodes.CLIENT_MALFORMED_INPUT } })); - return; - } - - const stream = file.createReadStream(); - const fileUri = uuid(); - const hash = crypto.createHash('sha256'); - let readBytes = 0; - - stream.pause(); - - /* if the client cancelled the request mid-stream it will throw an error */ - stream.on('error', (e) => { - reject(new GraphQLError('Upload resolver file stream failure', { extensions: { code: errorCodes.FILE_STREAM_ERROR, error: e } })); - return; - }); - - stream.on('data', (chunk) => { - readBytes += chunk.length; - if (readBytes > fileSizeLimit) { - stream.destroy(); - reject(new GraphQLError('File should not be larger than 8GB', { extensions: { code: errorCodes.CLIENT_MALFORMED_INPUT } })); - return; - } - hash.update(chunk); - }); - - await objStore.uploadFile(stream, args.studyId, fileUri); - - const hashString = hash.digest('hex'); - if (args.hash && args.hash !== hashString) { - reject(new GraphQLError('File hash not match', { extensions: { code: errorCodes.CLIENT_MALFORMED_INPUT } })); - return; - } - - // check if readbytes equal to filelength in parameters - if (args.fileLength !== undefined && args.fileLength.toString() !== readBytes.toString()) { - reject(new GraphQLError('File size mismatch', { extensions: { code: errorCodes.CLIENT_MALFORMED_INPUT } })); - return; - } - - fileEntry.fileSize = readBytes.toString(); - fileEntry.uri = fileUri; - fileEntry.hash = hashString; - if (!isStudyLevel) { - await db.collections!.data_collection.insertOne({ - id: uuid(), - m_studyId: args.studyId, - m_subjectId: parsedDescription.participantId, - m_versionId: null, - m_visitId: targetVisitId, - m_fieldId: targetFieldId, - value: '', - uploadedAt: (new Date()).valueOf(), - metadata: { - 'uploader:user': requester.id, - 'add': [fileEntry.id], - 'remove': [] - } - }); - } - const insertResult = await db.collections!.files_collection.insertOne(fileEntry as IFile); - if (insertResult.acknowledged) { - resolve(fileEntry as IFile); - } else { - throw new GraphQLError(errorCodes.DATABASE_ERROR); - } - - } catch (error) { - reject(new GraphQLError('General upload error', { extensions: { code: errorCodes.UNQUALIFIED_ERROR, error } })); - } - })(); - }); - }, - deleteFile: async (__unused__parent: Record, args: { fileId: string }, context: any): Promise => { - const requester: IUser = context.req.user; - - const file = await db.collections!.files_collection.findOne({ deleted: null, id: args.fileId }); - - if (!file) { - throw new GraphQLError(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - } - const hasStudyLevelPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.WRITE, - requester, - file.studyId - ); - if (!hasStudyLevelPermission) { throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); } - const parsedDescription = JSON.parse(file.description); - if (Object.keys(parsedDescription).length === 0) { - await db.collections!.files_collection.findOneAndUpdate({ deleted: null, id: args.fileId }, { $set: { deleted: Date.now().valueOf() } }); - return makeGenericReponse(); - } - const device = parsedDescription.deviceId.slice(0, 3); - if (!Object.keys(deviceTypes).includes(device)) { - throw new GraphQLError('File description is invalid', { extensions: { code: errorCodes.CLIENT_MALFORMED_INPUT } }); - } - const targetFieldId = `Device_${(deviceTypes[device] as string).replace(/ /g, '_')}`; - if (!permissionCore.checkDataEntryValid(hasStudyLevelPermission.raw, targetFieldId, parsedDescription.participantId, targetVisitId)) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - // update data record - const obj = { - m_studyId: file.studyId, - m_subjectId: parsedDescription.participantId, - m_versionId: null, - m_visitId: targetVisitId, - m_fieldId: targetFieldId - }; - const existing = await db.collections!.data_collection.findOne(obj); - if (!existing) { - await db.collections!.data_collection.insertOne({ - ...obj, - id: uuid(), - uploadedAt: (new Date()).valueOf(), - value: '', - metadata: { - add: [], - remove: [] - } - }); - } - const objWithData: Partial> = { - ...obj, - id: uuid(), - value: '', - uploadedAt: (new Date()).valueOf(), - metadata: { - 'uploader:user': requester.id, - 'add': existing?.metadata?.add ?? [], - 'remove': ((existing?.metadata as any)?.remove || []).concat(args.fileId) - } - }; - const updateResult = await db.collections!.data_collection.updateOne(obj, { $set: objWithData }, { upsert: true }); - - // const updateResult = await db.collections!.files_collection.updateOne({ deleted: null, id: args.fileId }, { $set: { deleted: new Date().valueOf() } }); - if (updateResult.modifiedCount === 1 || updateResult.upsertedCount === 1) { - return makeGenericReponse(); - } else { - throw new GraphQLError(errorCodes.DATABASE_ERROR); - } - } - }, - Subscription: {} -}; diff --git a/packages/itmat-interface/src/graphql/resolvers/index.ts b/packages/itmat-interface/src/graphql/resolvers/index.ts deleted file mode 100644 index 6a64f99c2..000000000 --- a/packages/itmat-interface/src/graphql/resolvers/index.ts +++ /dev/null @@ -1,66 +0,0 @@ -import GraphQLJSON from 'graphql-type-json'; -import { fileResolvers } from './fileResolvers'; -import { jobResolvers } from './jobResolvers'; -import { permissionResolvers } from './permissionResolvers'; -import { queryResolvers } from './queryResolvers'; -import { studyResolvers } from './studyResolvers'; -import { userResolvers } from './userResolvers'; -import { organisationResolvers } from './organisationResolvers'; -import { pubkeyResolvers } from './pubkeyResolvers'; -import { GraphQLError } from 'graphql'; -import { errorCodes } from '../errors'; -import { IUser } from '@itmat-broker/itmat-types'; -import { logResolvers } from './logResolvers'; -import { standardizationResolvers } from './standardizationResolvers'; - -const modules = [ - studyResolvers, - userResolvers, - queryResolvers, - permissionResolvers, - jobResolvers, - fileResolvers, - organisationResolvers, - pubkeyResolvers, - logResolvers, - standardizationResolvers -]; - -// const loggingDecorator = (reducerFunction: Function) => { -// return async (parent: any, args: any, context: any, info: any) => { -// return await reducerFunction(parent, args, context, info); -// }; -// }; - -const bounceNotLoggedInDecorator = (reducerFunction: any) => { - return async (parent: any, args: any, context: any, info: any) => { - const uncheckedFunctionWhitelist = ['login', 'rsaSigner', 'keyPairGenwSignature', 'issueAccessToken', 'whoAmI', 'getOrganisations', 'requestUsernameOrResetPassword', 'resetPassword', 'createUser', 'writeLog', 'validateResetPassword']; - const requester: IUser = context.req.user; - - if (!requester) { - if (!(uncheckedFunctionWhitelist as any).includes(reducerFunction.name)) { - throw new GraphQLError(errorCodes.NOT_LOGGED_IN); - } - } - return await reducerFunction(parent, args, context, info); - }; -}; - - -const reduceInit: any = { JSON: GraphQLJSON }; -export const resolvers = modules.reduce((a, e) => { - const types = Object.keys(e); - for (const each of types) { // types can be Subscription | Query | Mutation | {{TYPE}} - if (a[each] === undefined) { // if a doesnt have types then create a empty obj - a[each] = {}; - } - for (const funcName of Object.keys((e as any)[each])) { - if (each === 'Subscription') { - (a as any)[each][funcName] = (e as any)[each][funcName]; - } else { - (a as any)[each][funcName] = bounceNotLoggedInDecorator((e as any)[each][funcName]); - } - } - } - return a; -}, reduceInit); diff --git a/packages/itmat-interface/src/graphql/resolvers/jobResolvers.ts b/packages/itmat-interface/src/graphql/resolvers/jobResolvers.ts deleted file mode 100644 index be6d26131..000000000 --- a/packages/itmat-interface/src/graphql/resolvers/jobResolvers.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { GraphQLError } from 'graphql'; -import { withFilter } from 'graphql-subscriptions'; -import { IJobEntryForDataCuration, IJobEntryForFieldCuration, IJobEntryForQueryCuration, IUser, atomicOperation, IPermissionManagementOptions } from '@itmat-broker/itmat-types'; -import { v4 as uuid } from 'uuid'; -import { db } from '../../database/database'; -import { errorCodes } from '../errors'; -import { pubsub, subscriptionEvents } from '../pubsub'; -import { permissionCore } from '../core/permissionCore'; -import { studyCore } from '../core/studyCore'; - -enum JOB_TYPE { - FIELD_INFO_UPLOAD = 'FIELD_INFO_UPLOAD', - DATA_UPLOAD_CSV = 'DATA_UPLOAD_CSV', - DATA_UPLOAD_JSON = 'DATA_UPLOAD_JSON', - QUERY_EXECUTION = 'QUERY_EXECUTION', - DATA_EXPORT = 'DATA_EXPORT' -} - -export const jobResolvers = { - Query: {}, - Mutation: { - createDataCurationJob: async (__unused__parent: Record, args: { file: string[], studyId: string }, context: any): Promise => { - const requester: IUser = context.req.user; - - /* check permission */ - const hasPermission = await permissionCore.userHasTheNeccessaryManagementPermission( - IPermissionManagementOptions.job, - atomicOperation.WRITE, - requester, - args.studyId - ); - if (!hasPermission) { throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); } - - const dataFormatToJobType = { - json: JOB_TYPE.DATA_UPLOAD_JSON, - csv: JOB_TYPE.DATA_UPLOAD_CSV - // tsv: JOB_TYPE.DATA_UPLOAD_CSV - }; - - const jobList: IJobEntryForDataCuration[] = []; - for (const oneFile of args.file) { - /* check if the file exists */ - const file = await db.collections!.files_collection.findOne({ deleted: null, id: oneFile }); - if (!file) { - throw new GraphQLError(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - } - - /* check study exists */ - await studyCore.findOneStudy_throwErrorIfNotExist(args.studyId); - - /* create job */ - const parts = file.fileName.split('.'); - const dataFormat = parts[parts.length - 1] as keyof typeof dataFormatToJobType; - - if (!dataFormatToJobType[dataFormat]) { - throw new GraphQLError(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - } - const job: IJobEntryForDataCuration = { - id: uuid(), - jobType: dataFormatToJobType[dataFormat], - studyId: args.studyId, - requester: requester.id, - requestTime: new Date().valueOf(), - receivedFiles: [oneFile], - error: null, - status: 'QUEUED', - cancelled: false - }; - - const result = await db.collections!.jobs_collection.insertOne(job); - jobList.push(job); - if (!result.acknowledged) { - throw new GraphQLError(errorCodes.DATABASE_ERROR); - } - } - return jobList; - }, - createFieldCurationJob: async (__unused__parent: Record, args: { file: string, studyId: string, tag: string }, context: any): Promise => { - const requester: IUser = context.req.user; - - /* check permission */ - const hasPermission = await permissionCore.userHasTheNeccessaryManagementPermission( - IPermissionManagementOptions.job, - atomicOperation.WRITE, - requester, - args.studyId - ); - if (!hasPermission) { throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); } - - /* check if the file exists */ - const file = await db.collections!.files_collection.findOne({ deleted: null, id: args.file }); - if (!file) { - throw new GraphQLError(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - } - - /* check study exists */ - await studyCore.findOneStudy_throwErrorIfNotExist(args.studyId); - - /* check tag not undefined */ - if (args.tag === undefined) { - throw new GraphQLError('Tag is not provided', { extensions: { code: errorCodes.CLIENT_MALFORMED_INPUT } }); - } - - /* create job */ - const job: IJobEntryForFieldCuration = { - id: uuid(), - jobType: JOB_TYPE.FIELD_INFO_UPLOAD, - studyId: args.studyId, - requester: requester.id, - requestTime: new Date().valueOf(), - receivedFiles: [args.file], - error: null, - status: 'QUEUED', - cancelled: false, - data: { - tag: args.tag - } - }; - - const result = await db.collections!.jobs_collection.insertOne(job); - if (!result.acknowledged) { - throw new GraphQLError(errorCodes.DATABASE_ERROR); - } - return job; - }, - createQueryCurationJob: async (__unused__parent: Record, args: { queryId: string[], studyId: string, projectId: string }, context: any): Promise => { - const requester: IUser = context.req.user; - - /* check permission */ - const hasStudyLevelPermission = await permissionCore.userHasTheNeccessaryManagementPermission( - IPermissionManagementOptions.job, - atomicOperation.WRITE, - requester, - args.studyId - ); - const hasProjectLevelPermission = await permissionCore.userHasTheNeccessaryManagementPermission( - IPermissionManagementOptions.job, - atomicOperation.WRITE, - requester, - args.studyId, - args.projectId - ); - if (!hasStudyLevelPermission && !hasProjectLevelPermission) { throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); } - - /* check study exists */ - await studyCore.findOneStudy_throwErrorIfNotExist(args.studyId); - - /* check if project exists */ - const projectExist = await db.collections!.projects_collection.findOne({ id: args.projectId }); - if (!projectExist) { - throw new GraphQLError('Project does not exist.', { extensions: { code: errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY } }); - } - - /* check if the query exists */ - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const queryExist = await db.collections!.queries_collection.findOne({ id: args.queryId[0] }); - if (!queryExist) { - throw new GraphQLError('Query does not exist.', { extensions: { code: errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY } }); - } - - const job: IJobEntryForQueryCuration = { - id: uuid(), - jobType: JOB_TYPE.QUERY_EXECUTION, - studyId: args.studyId, - requester: requester.id, - requestTime: new Date().valueOf(), - receivedFiles: [], - error: null, - status: 'QUEUED', - cancelled: false, - data: { - queryId: args.queryId, - projectId: args.projectId, - studyId: args.studyId - } - }; - const result = await db.collections!.jobs_collection.insertOne(job); - if (!result.acknowledged) { - throw new GraphQLError(errorCodes.DATABASE_ERROR); - } - return job; - } - }, - Subscription: { - subscribeToJobStatusChange: { - subscribe: withFilter( - () => pubsub.asyncIterator(subscriptionEvents.JOB_STATUS_CHANGE), - (incoming, variables) => incoming.subscribeToJobStatusChange.studyId === variables.studyId - ) - } - } -}; diff --git a/packages/itmat-interface/src/graphql/resolvers/logResolvers.ts b/packages/itmat-interface/src/graphql/resolvers/logResolvers.ts deleted file mode 100644 index c00b027b7..000000000 --- a/packages/itmat-interface/src/graphql/resolvers/logResolvers.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { db } from '../../database/database'; -import { ILogEntry, IUser, LOG_ACTION, userTypes } from '@itmat-broker/itmat-types'; -import { GraphQLError } from 'graphql'; -import { errorCodes } from '../errors'; - -export const logResolvers = { - Query: { - getLogs: async (__unused_parent: Record, args: any, context: any): Promise => { - const requester: IUser = context.req.user; - - /* only admin can access this field */ - if (!(requester.type === userTypes.ADMIN) && !(requester.metadata?.logPermission === true)) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - const queryObj: Record = {}; - for (const prop in args) { - if (args.prop !== undefined) { - queryObj[prop] = args.prop; - } - } - const logData = await db.collections!.log_collection.find(queryObj, { projection: { _id: 0 } }).limit(1000).sort('time', -1).toArray(); - // log information decoration - for (const i in logData) { - logData[i].actionData = JSON.stringify(await logDecorationHelper(logData[i].actionData, logData[i].actionType)); - } - - return logData; - } - } -}; - -// fields that carry sensitive information will be hidden -export const hiddenFields = { - LOGIN_USER: ['password', 'totp'], - UPLOAD_FILE: ['file', 'description'] -}; - -async function logDecorationHelper(actionData: any, actionType: string) { - const obj = JSON.parse(actionData) ?? {}; - if (Object.keys(hiddenFields).includes(actionType)) { - for (let i = 0; i < hiddenFields[actionType as keyof typeof hiddenFields].length; i++) { - delete obj[hiddenFields[actionType as keyof typeof hiddenFields][i]]; - } - } - if (actionType === LOG_ACTION.getStudy) { - const studyId = obj['studyId']; - const study = await db.collections!.studies_collection.findOne({ id: studyId, deleted: null })!; - if (study === null || study === undefined) { - obj['name'] = ''; - } - else { - obj['name'] = study.name; - } - } - return obj; -} diff --git a/packages/itmat-interface/src/graphql/resolvers/organisationResolvers.ts b/packages/itmat-interface/src/graphql/resolvers/organisationResolvers.ts deleted file mode 100644 index 4cf37abb5..000000000 --- a/packages/itmat-interface/src/graphql/resolvers/organisationResolvers.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { IOrganisation } from '@itmat-broker/itmat-types'; -import { db } from '../../database/database'; - -export const organisationResolvers = { - Query: { - getOrganisations: async (__unused__parent: Record, args: any): Promise => { - // everyone is allowed to see all organisations in the app. - const queryObj = args.organisationId === undefined ? { deleted: null } : { deleted: null, id: args.organisationId }; - const cursor = db.collections!.organisations_collection.find(queryObj, { projection: { _id: 0 } }); - return cursor.toArray(); - } - }, - Mutation: {}, - Subscription: {} -}; diff --git a/packages/itmat-interface/src/graphql/resolvers/permissionResolvers.ts b/packages/itmat-interface/src/graphql/resolvers/permissionResolvers.ts deleted file mode 100644 index f6e1f33f1..000000000 --- a/packages/itmat-interface/src/graphql/resolvers/permissionResolvers.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { GraphQLError } from 'graphql'; -import { IUser, IRole, atomicOperation, IPermissionManagementOptions } from '@itmat-broker/itmat-types'; -import { db } from '../../database/database'; -import { permissionCore } from '../core/permissionCore'; -import { studyCore } from '../core/studyCore'; -import { errorCodes } from '../errors'; -import { IGenericResponse, makeGenericReponse } from '../responses'; - -export const permissionResolvers = { - Query: { - getGrantedPermissions: async (__unused__parent: Record, { studyId, projectId }: { studyId?: string, projectId?: string }, context: any): Promise<{ - studies: unknown[]; - projects: unknown[]; - }> => { - const requester: IUser = context.req.user; - const matchClause: Record = { users: requester.id }; - if (studyId) - matchClause.studyId = studyId; - if (projectId) - matchClause.projectId = { $in: [projectId, null] }; - const aggregationPipeline = [ - { $match: matchClause } - // { $group: { _id: requester.id, arrArrPrivileges: { $addToSet: '$permissions' } } }, - // { $project: { arrPrivileges: { $reduce: { input: '$arrArrPrivileges', initialValue: [], in: { $setUnion: ['$$this', '$$value'] } } } } } - ]; - - const grantedPermissions = { - studies: await db.collections!.roles_collection.aggregate(aggregationPipeline).toArray(), - projects: await db.collections!.roles_collection.aggregate(aggregationPipeline).toArray() - }; - return grantedPermissions; - } - }, - StudyOrProjectUserRole: { - users: async (role: IRole): Promise => { - const listOfUsers = role.users; - return await (db.collections!.users_collection.find({ id: { $in: listOfUsers } }, { projection: { _id: 0, password: 0, email: 0 } }).toArray()); - } - }, - Mutation: { - addRole: async (__unused__parent: Record, args: { studyId: string, projectId?: string, roleName: string }, context: any): Promise => { - const requester: IUser = context.req.user; - const { studyId, projectId, roleName } = args; - - /* check whether user has at least provided one id */ - if (studyId === undefined && projectId === undefined) { - throw new GraphQLError('Please provide either study id or project id.', { extensions: { code: errorCodes.CLIENT_MALFORMED_INPUT } }); - } - - /* check the requester has privilege */ - const hasPermission = await permissionCore.userHasTheNeccessaryManagementPermission( - IPermissionManagementOptions.role, - atomicOperation.WRITE, - requester, - args.studyId, - args.projectId - ); - if (!hasPermission) { throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); } - - /* check whether the target study or project exists */ - if (studyId && projectId) { // if both study id and project id are provided then just make sure they belong to each other - const result = await studyCore.findOneProject_throwErrorIfNotExist(projectId); - if (result.studyId !== studyId) { - throw new GraphQLError('The project provided does not belong to the study provided', { extensions: { code: errorCodes.CLIENT_MALFORMED_INPUT } }); - } - } else if (studyId) { // if only study id is provided - await studyCore.findOneStudy_throwErrorIfNotExist(studyId); - } else if (projectId) { - await studyCore.findOneProject_throwErrorIfNotExist(projectId); - } - - const result = await permissionCore.addRole({ createdBy: requester.id, studyId: studyId!, projectId, roleName }); - return result; - }, - editRole: async (__unused__parent: Record, args: { roleId: string, name?: string, description?: string, userChanges?: { add: string[], remove: string[] }, permissionChanges?: any }, context: any): Promise => { - const requester: IUser = context.req.user; - const { roleId, name, permissionChanges, userChanges } = args; - - const role = await db.collections!.roles_collection.findOne({ id: roleId, deleted: null })!; - if (role === null) { - throw new GraphQLError(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - } - - /* check the requester has privilege */ - const hasPermission = await permissionCore.userHasTheNeccessaryManagementPermission( - IPermissionManagementOptions.role, - atomicOperation.WRITE, - requester, - role.studyId, - role.projectId - ); - if (!hasPermission) { throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); } - - /* check whether all the permissions are valid in terms of regular expressions */ - if (permissionChanges) { - if (permissionChanges.data) { - if (permissionChanges.data.subjectIds) { - for (const subjectId of permissionChanges.data.subjectIds) { - checkReExpIsValid(subjectId); - } - } - if (permissionChanges.data.visitIds) { - for (const visitId of permissionChanges.data.visitIds) { - checkReExpIsValid(visitId); - } - } - if (permissionChanges.data.fieldIds) { - for (const fieldId of permissionChanges.data.fieldIds) { - checkReExpIsValid(fieldId); - } - } - } - } - - /* check whether all the users exists */ - if (userChanges) { - const allRequestedUserChanges: string[] = [...userChanges.add, ...userChanges.remove]; - const testedUser: string[] = []; - for (const each of allRequestedUserChanges) { - if (!testedUser.includes(each)) { - const user = await db.collections!.users_collection.findOne({ id: each, deleted: null }); - if (user === null) { - throw new GraphQLError(errorCodes.CLIENT_MALFORMED_INPUT); - } else { - testedUser.push(each); - } - } - } - } - - /* edit the role */ - const modifiedRole = await permissionCore.editRoleFromStudyOrProject(roleId, name, args.description, permissionChanges, userChanges); - return modifiedRole; - }, - removeRole: async (__unused__parent: Record, args: { roleId: string }, context: any): Promise => { - const requester: IUser = context.req.user; - const { roleId } = args; - - const role = await db.collections!.roles_collection.findOne({ id: roleId, deleted: null })!; - if (role === null) { - throw new GraphQLError(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - } - - /* check permission */ - const hasPermission = await permissionCore.userHasTheNeccessaryManagementPermission( - IPermissionManagementOptions.role, - atomicOperation.WRITE, - requester, - role.studyId, - role.projectId - ); - if (!hasPermission) { throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); } - - /* remove the role */ - await permissionCore.removeRole(roleId); - return makeGenericReponse(roleId); - } - }, - Subscription: {} -}; - -function checkReExpIsValid(pattern: string) { - try { - new RegExp(pattern); - } catch { - throw new GraphQLError(`${pattern} is not a valid regular expression.`, { extensions: { code: errorCodes.CLIENT_MALFORMED_INPUT } }); - } -} diff --git a/packages/itmat-interface/src/graphql/resolvers/pubkeyResolvers.ts b/packages/itmat-interface/src/graphql/resolvers/pubkeyResolvers.ts deleted file mode 100644 index 039ae3eaa..000000000 --- a/packages/itmat-interface/src/graphql/resolvers/pubkeyResolvers.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { ApolloServerErrorCode } from '@apollo/server/errors'; -import { GraphQLError } from 'graphql'; -import { mailer } from '../../emailer/emailer'; -import { IUser, IPubkey, AccessToken, KeyPairwSignature, Signature } from '@itmat-broker/itmat-types'; -//import { v4 as uuid } from 'uuid'; -import { db } from '../../database/database'; -import config from '../../utils/configManager'; -import { userCore } from '../core/userCore'; -import { errorCodes } from '../errors'; -//import { makeGenericReponse, IGenericResponse } from '../responses'; -import * as pubkeycrypto from '../../utils/pubkeycrypto'; -export const pubkeyResolvers = { - Query: { - getPubkeys: async (__unused__parent: Record, args: any): Promise => { - // a user is allowed to obtain his/her registered public key. - let queryObj; - if (args.pubkeyId === undefined) { - if (args.associatedUserId === undefined) { - queryObj = { deleted: null }; - } else { - queryObj = { deleted: null, associatedUserId: args.associatedUserId }; - } - } else { - queryObj = { deleted: null, id: args.pubkeyId }; - } - const cursor = db.collections!.pubkeys_collection.find(queryObj, { projection: { _id: 0 } }); - return cursor.toArray(); - } - }, - - Mutation: { - keyPairGenwSignature: async (): Promise => { - // Generate RSA key-pair with Signature for robot user - const keyPair = pubkeycrypto.rsakeygen(); - //default message = hash of the public key (SHA256) - const messageToBeSigned = pubkeycrypto.hashdigest(keyPair.publicKey); - const signature = pubkeycrypto.rsasigner(keyPair.privateKey, messageToBeSigned); - - return { privateKey: keyPair.privateKey, publicKey: keyPair.publicKey, signature: signature }; - }, - - rsaSigner: async (__unused__parent: Record, { privateKey, message }: { privateKey: string, message: string }): Promise => { - let messageToBeSigned; - privateKey = privateKey.replace(/\\n/g, '\n'); - if (message === undefined) { - //default message = hash of the public key (SHA256) - try { - const reGenPubkey = pubkeycrypto.reGenPkfromSk(privateKey); - messageToBeSigned = pubkeycrypto.hashdigest(reGenPubkey); - } catch (error) { - throw new GraphQLError('Error: private-key incorrect!', { extensions: { code: ApolloServerErrorCode.BAD_USER_INPUT, error } }); - } - - } else { - messageToBeSigned = message; - } - const signature = pubkeycrypto.rsasigner(privateKey, messageToBeSigned); - return { signature: signature }; - }, - - issueAccessToken: async (__unused__parent: Record, { pubkey, signature }: { pubkey: string, signature: string }): Promise => { - // refine the public-key parameter from browser - pubkey = pubkey.replace(/\\n/g, '\n'); - - /* Validate the signature with the public key */ - if (!await pubkeycrypto.rsaverifier(pubkey, signature)) { - throw new GraphQLError('Signature vs Public key mismatched.', { extensions: { code: ApolloServerErrorCode.BAD_USER_INPUT } }); - } - - const pubkeyrec = await db.collections!.pubkeys_collection.findOne({ pubkey, deleted: null }); - if (pubkeyrec === null || pubkeyrec === undefined) { - throw new GraphQLError('This public-key has not been registered yet!', { extensions: { code: ApolloServerErrorCode.BAD_USER_INPUT } }); - } - - // payload of the JWT for storing user information - const payload = { - publicKey: pubkeyrec.jwtPubkey, - associatedUserId: pubkeyrec.associatedUserId, - refreshCounter: pubkeyrec.refreshCounter, - Issuer: 'IDEA-FAST DMP' - }; - - // update the counter - const fieldsToUpdate = { - refreshCounter: (pubkeyrec.refreshCounter + 1) - }; - const updateResult = await db.collections!.pubkeys_collection.findOneAndUpdate({ pubkey, deleted: null }, { $set: fieldsToUpdate }, { returnDocument: 'after' }); - if (updateResult === null) { - throw new GraphQLError('Server error; cannot fulfil the JWT request.'); - } - // return the acccess token - const accessToken = { - accessToken: pubkeycrypto.tokengen(payload, pubkeyrec.jwtSeckey) - }; - - return accessToken; - }, - - registerPubkey: async (__unused__parent: Record, { pubkey, signature, associatedUserId }: { pubkey: string, signature: string, associatedUserId: string }, context: any): Promise => { - // refine the public-key parameter from browser - pubkey = pubkey.replace(/\\n/g, '\n'); - const alreadyExist = await db.collections!.pubkeys_collection.findOne({ pubkey, deleted: null }); - if (alreadyExist !== null && alreadyExist !== undefined) { - throw new GraphQLError('This public-key has already been registered.', { extensions: { code: ApolloServerErrorCode.BAD_USER_INPUT } }); - } - - const requester: IUser = context.req.user; - /* Check whether requester is the same as the associated user*/ - if (associatedUserId && (requester.id !== associatedUserId)) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - /* Validate the signature with the public key */ - try { - const signature_verifier = await pubkeycrypto.rsaverifier(pubkey, signature); - if (!signature_verifier) { - throw new GraphQLError('Signature vs Public-key mismatched.', { extensions: { code: ApolloServerErrorCode.BAD_USER_INPUT } }); - } - } catch (error) { - throw new GraphQLError('Error: Signature or Public-key is incorrect.', { extensions: { code: ApolloServerErrorCode.BAD_USER_INPUT } }); - } - - /* Generate a public key-pair for generating and authenticating JWT access token later */ - const keypair = pubkeycrypto.rsakeygen(); - - /* Update the new public key if the user is already associated with another public key*/ - if (associatedUserId) { - const alreadyRegistered = await db.collections!.pubkeys_collection.findOne({ associatedUserId, deleted: null }); - if (alreadyRegistered !== null && alreadyRegistered !== undefined) { - //updating the new public key. - const fieldsToUpdate = { - pubkey, - jwtPubkey: keypair.publicKey, - jwtSeckey: keypair.privateKey - }; - const updateResult = await db.collections!.pubkeys_collection.findOneAndUpdate({ associatedUserId, deleted: null }, { $set: fieldsToUpdate }, { returnDocument: 'after' }); - if (updateResult) { - await mailer.sendMail({ - from: `${config.appName} <${config.nodemailer.auth.user}>`, - to: requester.email, - subject: `[${config.appName}] New public-key has sucessfully registered!`, - html: ` -

- Dear ${requester.firstname}, -

-

- Your new public-key "${pubkey}" on ${config.appName} has successfully registered !
- The old one is already wiped out! - You will need to keep your new private key secretly.
- You will also need to sign a message (using this new public-key) to authenticate the owner of the public key.
-

- -
-

- The ${config.appName} Team. -

- ` - }); - - return updateResult; - } else { - throw new GraphQLError('Server error; no entry or more than one entry has been updated.'); - } - } - } - - /* Register new public key (either associated with an user or not) */ - const registeredPubkey = await userCore.registerPubkey({ - pubkey, - jwtPubkey: keypair.publicKey, - jwtSeckey: keypair.privateKey, - associatedUserId: associatedUserId ?? null - }); - - await mailer.sendMail({ - from: `${config.appName} <${config.nodemailer.auth.user}>`, - to: requester.email, - subject: `[${config.appName}] Public-key Registration!`, - html: ` -

- Dear ${requester.firstname}, -

-

- You have successfully registered your public-key "${pubkey}" on ${config.appName}!
- You will need to keep your private key secretly.
- You will also need to sign a message (using your public-key) to authenticate the owner of the public key.
-

- -
-

- The ${config.appName} Team. -

- ` - }); - - return registeredPubkey; - } - }, - - Subscription: {} -}; diff --git a/packages/itmat-interface/src/graphql/resolvers/queryResolvers.ts b/packages/itmat-interface/src/graphql/resolvers/queryResolvers.ts deleted file mode 100644 index a670177db..000000000 --- a/packages/itmat-interface/src/graphql/resolvers/queryResolvers.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { IQueryEntry, IUser, IProject, atomicOperation, IPermissionManagementOptions } from '@itmat-broker/itmat-types'; -import { queryCore } from '../core/queryCore'; -import { permissionCore } from '../core/permissionCore'; -import { GraphQLError } from 'graphql'; -import { errorCodes } from '../errors'; -import { db } from '../../database/database'; - -export const queryResolvers = { - Query: { - getQueryById: async (__unused__parent: Record, args: { queryId: string }, context: any): Promise => { - const queryId = args.queryId; - const requester: IUser = context.req.user; - /* check query exists */ - const queryEntry = await db.collections!.queries_collection.findOne({ id: queryId }, { projection: { _id: 0, claimedBy: 0 } }); - if (queryEntry === null || queryEntry === undefined) { - throw new GraphQLError('Query does not exist.', { extensions: { code: errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY } }); - } - /* check permission */ - const hasProjectLevelPermission = await permissionCore.userHasTheNeccessaryManagementPermission( - IPermissionManagementOptions.query, - atomicOperation.READ, - requester, - queryEntry.studyId, - queryEntry.projectId - ); - - const hasStudyLevelPermission = await permissionCore.userHasTheNeccessaryManagementPermission( - IPermissionManagementOptions.query, - atomicOperation.READ, - requester, - queryEntry.studyId - ); - if (!hasProjectLevelPermission && !hasStudyLevelPermission) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - return queryEntry; - }, - getQueries: async (__unused__parent: Record, args: { studyId: string, projectId: string }, context: any): Promise => { - const requester: IUser = context.req.user; - - /* check permission */ - const hasProjectLevelPermission = await permissionCore.userHasTheNeccessaryManagementPermission( - IPermissionManagementOptions.query, - atomicOperation.READ, - requester, - args.studyId, - args.projectId - ); - - const hasStudyLevelPermission = await permissionCore.userHasTheNeccessaryManagementPermission( - IPermissionManagementOptions.query, - atomicOperation.READ, - requester, - args.studyId - ); - if (!hasStudyLevelPermission && !hasProjectLevelPermission) { throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); } - const entries = await db.collections!.queries_collection.find({ studyId: args.studyId, projectId: args.projectId }).toArray(); - return entries; - } - }, - Mutation: { - createQuery: async (__unused__parent: Record, args: { query: { userId: string, queryString: any, studyId: string, projectId?: string } }): Promise => { - /* check study exists */ - const studySearchResult = await db.collections!.studies_collection.findOne({ id: args.query.studyId, deleted: null }); - if (studySearchResult === null || studySearchResult === undefined) { - throw new GraphQLError('Study does not exist.', { extensions: { code: errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY } }); - } - /* check project exists */ - const project = await db.collections!.projects_collection.findOne>({ id: args.query.projectId, deleted: null }, { projection: { patientMapping: 0 } })!; - if (project === null) { - throw new GraphQLError(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - } - /* check project matches study */ - if (studySearchResult.id !== project.studyId) { - throw new GraphQLError('Study and project mismatch.', { extensions: { code: errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY } }); - } - - const entry = await queryCore.createQuery(args); - return entry; - } - }, - Subscription: {} -}; diff --git a/packages/itmat-interface/src/graphql/resolvers/standardizationResolvers.ts b/packages/itmat-interface/src/graphql/resolvers/standardizationResolvers.ts deleted file mode 100644 index fac379d43..000000000 --- a/packages/itmat-interface/src/graphql/resolvers/standardizationResolvers.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { IProject, IStandardization, IUser, atomicOperation } from '@itmat-broker/itmat-types'; -import { permissionCore } from '../core/permissionCore'; -import { studyCore } from '../core/studyCore'; -import { GraphQLError } from 'graphql'; -import { errorCodes } from '../errors'; -import { db } from '../../database/database'; -import { v4 as uuid } from 'uuid'; -import { IGenericResponse, makeGenericReponse } from '../responses'; - -export const standardizationResolvers = { - Query: { - getStandardization: async (__unused__parent: Record, { studyId, projectId, type, versionId }: { studyId: string, projectId: string, type?: string, versionId: string }, context: any): Promise => { - const requester: IUser = context.req.user; - let modifiedStudyId = studyId; - /* check study exists */ - if (!studyId && !projectId) { - throw new GraphQLError('Either studyId or projectId should be provided.', { extensions: { code: errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY } }); - } - if (studyId) { - await studyCore.findOneStudy_throwErrorIfNotExist(studyId); - } - if (projectId) { - const project: IProject = await studyCore.findOneProject_throwErrorIfNotExist(projectId); - modifiedStudyId = project.studyId; - } - - /* check permission */ - const hasStudyLevelPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.READ, - requester, - studyId - ); - const hasProjectLevelPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.READ, - requester, - studyId, - projectId - ); - if (!hasStudyLevelPermission && !hasProjectLevelPermission) { throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); } - - const study = await studyCore.findOneStudy_throwErrorIfNotExist(modifiedStudyId); - // get all dataVersions that are valid (before/equal the current version) - const availableDataVersions: Array = (study.currentDataVersion === -1 ? [] : study.dataVersions.filter((__unused__el, index) => index <= study.currentDataVersion)).map(el => el.id); - if (hasStudyLevelPermission && hasStudyLevelPermission.hasVersioned && versionId === null) { - availableDataVersions.push(null); - } - const standardizations = await db.collections!.standardizations_collection.aggregate([{ - $sort: { uploadedAt: -1 } - }, { - $match: { dataVersion: { $in: availableDataVersions } } - }, { - $match: { studyId: studyId, type: type ?? /^.*$/ } - }, { - $group: { - _id: { - type: '$type', - field: '$field' - }, - doc: { $first: '$$ROOT' } - } - }, { - $replaceRoot: { newRoot: '$doc' } - }, { - $match: { deleted: null } - } - ]).toArray(); - return standardizations as IStandardization[]; - } - }, - Mutation: { - createStandardization: async (__unused__parent: Record, { studyId, standardization }: { studyId: string, standardization: any }, context: any): Promise => { - const requester: IUser = context.req.user; - /* check permission */ - const hasPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.WRITE, - requester, - studyId - ); - if (!hasPermission) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - /* check study exists */ - const studySearchResult = await db.collections!.studies_collection.findOne({ id: studyId, deleted: null }); - if (studySearchResult === null || studySearchResult === undefined) { - throw new GraphQLError('Study does not exist.', { extensions: { code: errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY } }); - } - const stdRulesWithId: any[] = [...standardization.stdRules]; - stdRulesWithId.forEach(el => { - el.id = uuid(); - }); - if (!(permissionCore.checkDataEntryValid(hasPermission.raw, standardization.field[0].slice(1)))) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - const standardizationEntry: IStandardization = { - id: uuid(), - studyId: studyId, - type: standardization.type, - field: standardization.field, - path: standardization.path, - stdRules: stdRulesWithId || [], - joinByKeys: standardization.joinByKeys || [], - dataVersion: null, - metadata: standardization.metadata, - uploadedAt: Date.now(), - deleted: null - }; - - await db.collections!.standardizations_collection.findOneAndUpdate({ studyId: studyId, type: standardization.type, field: standardization.field, dataVersion: null }, { - $set: { ...standardizationEntry } - }, { - upsert: true - }); - return standardizationEntry; - }, - deleteStandardization: async (__unused__parent: Record, { studyId, type, field }: { studyId: string, type: string, field: string[] }, context: any): Promise => { - const requester: IUser = context.req.user; - /* check permission */ - const hasPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.WRITE, - requester, - studyId - ); - if (!hasPermission) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - if (!(permissionCore.checkDataEntryValid(hasPermission.raw, field[0].slice(1)))) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - /* check study exists */ - const studySearchResult = await db.collections!.studies_collection.findOne({ id: studyId, deleted: null }); - if (studySearchResult === null || studySearchResult === undefined) { - throw new GraphQLError('Study does not exist.', { extensions: { code: errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY } }); - } - - // check type exists - const types: string[] = await db.collections!.standardizations_collection.distinct('type', { studyId: studyId, deleted: null }); - if (!types.includes(type)) { - throw new GraphQLError('Type does not exist.', { extensions: { code: errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY } }); - } - const result = await db.collections!.standardizations_collection.findOneAndUpdate({ studyId: studyId, field: field, type: type, dataVersion: null }, { - $set: { - id: uuid(), - studyId: studyId, - field: field, - type: type, - dataVersion: null, - uploadedAt: Date.now(), - deleted: Date.now() - } - }, { - upsert: true - }); - return makeGenericReponse(result?.id || ''); - } - }, - Subscription: {} -}; - -// function checkFieldEqual(fieldA: string[], fieldB: string[]) { -// return fieldA.length === fieldB.length && fieldA.every((value, index) => { -// return value === fieldB[index]; -// }); -// } diff --git a/packages/itmat-interface/src/graphql/resolvers/studyResolvers.ts b/packages/itmat-interface/src/graphql/resolvers/studyResolvers.ts deleted file mode 100644 index 02eafc0bd..000000000 --- a/packages/itmat-interface/src/graphql/resolvers/studyResolvers.ts +++ /dev/null @@ -1,1438 +0,0 @@ -import { GraphQLError } from 'graphql'; -import { - IProject, - IStudy, - IStudyDataVersion, - IFieldEntry, - IUser, - IFile, - IJobEntry, - studyType, - IDataClip, - ISubjectDataRecordSummary, - IRole, - IOntologyTree, - userTypes, - atomicOperation, - IPermissionManagementOptions, - IDataEntry, - enumValueType -} from '@itmat-broker/itmat-types'; -import { v4 as uuid } from 'uuid'; -import { db } from '../../database/database'; -import { permissionCore } from '../core/permissionCore'; -import { validateAndGenerateFieldEntry } from '../core/fieldCore'; -import { studyCore } from '../core/studyCore'; -import { errorCodes } from '../errors'; -import { IGenericResponse, makeGenericReponse } from '../responses'; -import { buildPipeline, translateMetadata } from '../../utils/query'; -import { dataStandardization } from '../../utils/query'; - -export const studyResolvers = { - Query: { - getStudy: async (__unused__parent: Record, args: Record, context: any): Promise => { - const requester: IUser = context.req.user; - const studyId: string = args.studyId; - - /* user can get study if he has readonly permission */ - const hasPermission = await permissionCore.userHasTheNeccessaryManagementPermission( - IPermissionManagementOptions.own, - atomicOperation.READ, - requester, - studyId - ); - if (!hasPermission) { throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); } - - const study = await db.collections!.studies_collection.findOne({ id: studyId, deleted: null })!; - if (study === null || study === undefined) { - throw new GraphQLError(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - } - return study; - }, - getProject: async (__unused__parent: Record, args: any, context: any): Promise | null> => { - const requester: IUser = context.req.user; - const projectId: string = args.projectId; - - /* get project */ // defer patientMapping since it's costly and not available to all users - const project = await db.collections!.projects_collection.findOne({ id: projectId, deleted: null }, { projection: { patientMapping: 0 } })!; - if (!project) - throw new GraphQLError(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - - /* check if user has permission */ - const hasProjectLevelPermission = await permissionCore.userHasTheNeccessaryManagementPermission( - IPermissionManagementOptions.own, - atomicOperation.READ, - requester, - project.studyId, - projectId - ); - - const hasStudyLevelPermission = await permissionCore.userHasTheNeccessaryManagementPermission( - IPermissionManagementOptions.own, - atomicOperation.READ, - requester, - project.studyId - ); - if (!hasStudyLevelPermission && !hasProjectLevelPermission) { throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); } - - return project; - }, - getStudyFields: async (__unused__parent: Record, { studyId, projectId, versionId }: { studyId: string, projectId?: string, versionId?: string | null }, context: any): Promise => { - const requester: IUser = context.req.user; - /* user can get study if he has readonly permission */ - const hasStudyLevelPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.READ, - requester, - studyId - ); - const hasProjectLevelPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.READ, - requester, - studyId, - projectId - ); - if (!hasStudyLevelPermission && !hasProjectLevelPermission) { throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); } - const study = await studyCore.findOneStudy_throwErrorIfNotExist(studyId); - const aggregatedPermissions: any = permissionCore.combineMultiplePermissions([hasStudyLevelPermission, hasProjectLevelPermission]); - - // the processes of requiring versioned data and unversioned data are different - // check the metadata:role:**** for versioned data directly - const availableDataVersions: Array = (study.currentDataVersion === -1 ? [] : study.dataVersions.filter((__unused__el, index) => index <= study.currentDataVersion)).map(el => el.id); - // check the regular expressions for unversioned data - if (requester.type === userTypes.ADMIN) { - if (versionId === null) { - availableDataVersions.push(null); - } - const fieldRecords: any[] = await db.collections!.field_dictionary_collection.aggregate([{ - $match: { studyId: studyId, dataVersion: { $in: availableDataVersions } } - }, { - $sort: { dateAdded: -1 } - }, { - $group: { - _id: '$fieldId', - doc: { $first: '$$ROOT' } - } - }, { - $replaceRoot: { - newRoot: '$doc' - } - }, { - $sort: { fieldId: 1 } - }]).toArray(); - return fieldRecords.filter(el => el.dateDeleted === null); - } - // unversioned data could not be returned by metadata filters - if (versionId === null && aggregatedPermissions.hasVersioned) { - availableDataVersions.push(null); - const fieldRecords: any[] = await db.collections!.field_dictionary_collection.aggregate([{ - $match: { studyId: studyId, dataVersion: { $in: availableDataVersions } } - }, { - $sort: { dateAdded: -1 } - }, { - $match: { - fieldId: { $in: aggregatedPermissions.raw.fieldIds.map((el: string) => new RegExp(el)) } - } - }, { - $group: { - _id: '$fieldId', - doc: { $first: '$$ROOT' } - } - }, { - $replaceRoot: { - newRoot: '$doc' - } - }, { - $sort: { fieldId: 1 } - }]).toArray(); - return fieldRecords.filter(el => el.dateDeleted === null); - } else { - // metadata filter - const subqueries: any = []; - aggregatedPermissions.matchObj.forEach((subMetadata: any) => { - subqueries.push(translateMetadata(subMetadata)); - }); - const metadataFilter = { $or: subqueries }; - const fieldRecords: any[] = await db.collections!.field_dictionary_collection.aggregate([{ - $match: { studyId: studyId, dataVersion: { $in: availableDataVersions } } - }, { - $sort: { dateAdded: -1 } - }, { $match: metadataFilter }, { - $group: { - _id: '$fieldId', - doc: { $first: '$$ROOT' } - } - }, { - $replaceRoot: { - newRoot: '$doc' - } - }, { - $sort: { fieldId: 1 } - }, { - $set: { metadata: null } - }]).toArray(); - return fieldRecords.filter(el => el.dateDeleted === null); - } - }, - getOntologyTree: async (__unused__parent: Record, { studyId, projectId, treeName, versionId }: { studyId: string, projectId?: string, treeName?: string, versionId?: string }, context: any): Promise => { - /* get studyId by parameter or project */ - const study = await studyCore.findOneStudy_throwErrorIfNotExist(studyId); - if (projectId) { - await studyCore.findOneProject_throwErrorIfNotExist(projectId); - } - - const requester: IUser = context.req.user; - - // we dont filters fields of an ontology tree by fieldIds - const hasProjectLevelPermission = await permissionCore.userHasTheNeccessaryManagementPermission( - IPermissionManagementOptions.ontologyTrees, - atomicOperation.READ, - requester, - studyId, - projectId - ); - - const hasStudyLevelPermission = await permissionCore.userHasTheNeccessaryManagementPermission( - IPermissionManagementOptions.ontologyTrees, - atomicOperation.READ, - requester, - studyId - ); - if (!hasStudyLevelPermission && !hasProjectLevelPermission) { throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); } - - const availableDataVersions = (study.currentDataVersion === -1 ? [] : study.dataVersions.filter((__unused__el, index) => index <= study.currentDataVersion)).map(el => el.id); - // if versionId is null, we will only return trees whose data version is null - // this is a different behavior from getting fields or data - if (study.ontologyTrees === undefined) { - return []; - } else { - const trees: IOntologyTree[] = study.ontologyTrees; - if (hasStudyLevelPermission && versionId === null) { - const availableTrees: IOntologyTree[] = []; - for (let i = trees.length - 1; i >= 0; i--) { - if (trees[i].dataVersion === null - && availableTrees.filter(el => el.name === trees[i].name).length === 0) { - availableTrees.push(trees[i]); - } else { - continue; - } - } - if (treeName) { - return availableTrees.filter(el => el.name === treeName); - } else { - return availableTrees; - } - } else { - const availableTrees: IOntologyTree[] = []; - for (let i = trees.length - 1; i >= 0; i--) { - if (availableDataVersions.includes(trees[i].dataVersion || '') - && availableTrees.filter(el => el.name === trees[i].name).length === 0) { - availableTrees.push(trees[i]); - } else { - continue; - } - } - if (treeName) { - return availableTrees.filter(el => el.name === treeName); - } else { - return availableTrees; - } - } - } - }, - checkDataComplete: async (__unused__parent: Record, { studyId }: { studyId: string }, context: any): Promise => { - const requester: IUser = context.req.user; - /* user can get study if he has readonly permission */ - const hasPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.READ, - requester, - studyId - ); - if (!hasPermission) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - // we only check data that hasnt been pushed to a new data version - const data: IDataEntry[] = await db.collections!.data_collection.find({ - m_studyId: studyId, - m_versionId: null, - m_subjectId: { $in: hasPermission.raw.subjectIds.map((el: string) => new RegExp(el)) }, - m_visitId: { $in: hasPermission.raw.visitIds.map((el: string) => new RegExp(el)) }, - m_fieldId: { $in: hasPermission.raw.fieldIds.map((el: string) => new RegExp(el)) } - }).toArray(); - const fieldMapping = (await db.collections!.field_dictionary_collection.aggregate([{ - $match: { studyId: studyId } - }, { - $match: { fieldId: { $in: hasPermission.raw.fieldIds.map((el: string) => new RegExp(el)) } } - }, { - $sort: { dateAdded: -1 } - }, { - $group: { - _id: '$fieldId', - doc: { $last: '$$ROOT' } - } - } - ]).toArray()).map(el => el.doc).filter(eh => eh.dateDeleted === null).reduce((acc, curr) => { - acc[curr.fieldId] = curr; - return acc; - }, {}); - const summary: ISubjectDataRecordSummary[] = []; - // we will not check data whose fields are not defined, because data that the associated fields are undefined will not be returned while querying data - for (const record of data) { - let error: string | null = null; - if (fieldMapping[record.m_fieldId] !== undefined && fieldMapping[record.m_fieldId] !== null) { - switch (fieldMapping[record.m_fieldId].dataType) { - case 'dec': {// decimal - if (!/^\d+(.\d+)?$/.test(record.value)) { - error = `Field ${record.m_fieldId}: Cannot parse as decimal.`; - break; - } - break; - } - case 'int': {// integer - if (!/^-?\d+$/.test(record.value)) { - error = `Field ${record.m_fieldId}: Cannot parse as integer.`; - break; - } - break; - } - case 'bool': {// boolean - if (record.value.toLowerCase() !== 'true' && record.value.toLowerCase() !== 'false') { - error = `Field ${record.m_fieldId}: Cannot parse as boolean.`; - break; - } - break; - } - case 'str': { - break; - } - // 01/02/2021 00:00:00 - case 'date': { - const matcher = /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?/; - if (!record.value.match(matcher)) { - error = `Field ${record.m_fieldId}: Cannot parse as data. Value for date type must be in ISO format.`; - break; - } - break; - } - case 'json': { - break; - } - case 'file': { - const file = await db.collections!.files_collection.findOne({ id: record.value }); - if (!file) { - error = `Field ${record.m_fieldId}: Cannot parse as file or file does not exist.`; - break; - } - break; - } - case 'cat': { - if (!fieldMapping[record.m_fieldId].possibleValues.map((el: any) => el.code).includes(record.value.toString())) { - error = `Field ${record.m_fieldId}: Cannot parse as categorical, value not in value list.`; - break; - } - break; - } - default: { - error = `Field ${record.m_fieldId}: Invalid data Type.`; - break; - } - } - } - error && summary.push({ - subjectId: record.m_subjectId, - visitId: record.m_visitId, - fieldId: record.m_fieldId, - error: error - }); - } - - return summary; - }, - getDataRecords: async (__unused__parent: Record, { studyId, queryString, versionId, projectId }: { queryString: any, studyId: string, versionId: string | null | undefined, projectId?: string }, context: any): Promise => { - const requester: IUser = context.req.user; - /* user can get study if he has readonly permission */ - const hasStudyLevelPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.READ, - requester, - studyId - ); - const hasProjectLevelPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.READ, - requester, - studyId, - projectId - ); - if (!hasStudyLevelPermission && !hasProjectLevelPermission) { throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); } - - const study = await studyCore.findOneStudy_throwErrorIfNotExist(studyId); - const aggregatedPermissions: any = permissionCore.combineMultiplePermissions([hasStudyLevelPermission, hasProjectLevelPermission]); - - let availableDataVersions: Array = (study.currentDataVersion === -1 ? [] : study.dataVersions.filter((__unused__el, index) => index <= study.currentDataVersion)).map(el => el.id); - let fieldRecords: any[] = []; - let result: any; - let metadataFilter: any = undefined; - // we obtain the data by different requests - // admin used will not filtered by metadata filters - if (requester.type === userTypes.ADMIN) { - if (versionId === null) { - availableDataVersions.push(null); - } - if (versionId === '-1') { - availableDataVersions = availableDataVersions.length !== 0 ? [availableDataVersions[availableDataVersions.length - 1]] : []; - } - - fieldRecords = await db.collections!.field_dictionary_collection.aggregate([{ - $match: { studyId: studyId, dateDeleted: null, dataVersion: { $in: availableDataVersions } } - }, { - $sort: { dateAdded: -1 } - }, { - $group: { - _id: '$fieldId', - doc: { $first: '$$ROOT' } - } - }, { - $replaceRoot: { - newRoot: '$doc' - } - }, { - $sort: { fieldId: 1 } - }]).toArray(); - if (queryString.data_requested?.length > 0) { - fieldRecords = fieldRecords.filter(el => queryString.data_requested.includes(el.fieldId)); - } - const pipeline = buildPipeline(queryString, studyId, availableDataVersions, fieldRecords, undefined, true, versionId === null); - result = await db.collections!.data_collection.aggregate(pipeline, { allowDiskUse: true }).toArray(); - } else { - const subqueries: any = []; - aggregatedPermissions.matchObj.forEach((subMetadata: any) => { - subqueries.push(translateMetadata(subMetadata)); - }); - metadataFilter = { $or: subqueries }; - // unversioned data: metadatafilter for versioned data and all unversioned tags - if (versionId === null && aggregatedPermissions.hasVersioned) { - availableDataVersions.push(null); - fieldRecords = await db.collections!.field_dictionary_collection.aggregate([{ - $match: { studyId: studyId, dateDeleted: null, dataVersion: { $in: availableDataVersions } } - }, { - $sort: { dateAdded: -1 } - }, { - $match: { - $or: [ - metadataFilter, - { m_versionId: null } - ] - } - }, { - $group: { - _id: '$fieldId', - doc: { $first: '$$ROOT' } - } - }, { - $replaceRoot: { - newRoot: '$doc' - } - }, { - $sort: { fieldId: 1 } - }]).toArray(); - if (queryString.data_requested?.length > 0) { - fieldRecords = fieldRecords.filter(el => queryString.data_requested.includes(el.fieldId)); - } - } else if (versionId === undefined || versionId === '-1') { - if (versionId === '-1') { - availableDataVersions = availableDataVersions.length !== 0 ? [availableDataVersions[availableDataVersions.length - 1]] : []; - } - fieldRecords = await db.collections!.field_dictionary_collection.aggregate([{ - $match: { studyId: studyId, dateDeleted: null, dataVersion: { $in: availableDataVersions } } - }, { - $sort: { dateAdded: -1 } - }, { - $match: { - $or: [ - metadataFilter - ] - } - }, { - $group: { - _id: '$fieldId', - doc: { $first: '$$ROOT' } - } - }, { - $replaceRoot: { - newRoot: '$doc' - } - }, { - $sort: { fieldId: 1 } - }]).toArray(); - if (queryString.data_requested?.length > 0) { - fieldRecords = fieldRecords.filter(el => queryString.data_requested.includes(el.fieldId)); - } - } - - // TODO: placeholder for metadata filter - // if (queryString.metadata) { - // metadataFilter = { $and: queryString.metadata.map((el: any) => translateMetadata(el)) }; - // } - const pipeline = buildPipeline(queryString, studyId, availableDataVersions, fieldRecords, metadataFilter, false, versionId === null && aggregatedPermissions.hasVersioned); - result = await db.collections!.data_collection.aggregate(pipeline, { allowDiskUse: true }).toArray(); - } - // post processing the data - // 2. update to the latest data; start from first record - const groupedResult: any = {}; - for (let i = 0; i < result.length; i++) { - const { m_subjectId, m_visitId, m_fieldId, value } = result[i]; - if (!groupedResult[m_subjectId]) { - groupedResult[m_subjectId] = {}; - } - if (!groupedResult[m_subjectId][m_visitId]) { - groupedResult[m_subjectId][m_visitId] = {}; - } - groupedResult[m_subjectId][m_visitId][m_fieldId] = value; - } - - // 2. adjust format: 1) original(exists) 2) standardized - $name 3) grouped - // when standardized data, versionId should not be specified - const standardizations = versionId === null ? null : await db.collections!.standardizations_collection.find({ studyId: studyId, type: queryString['format'].split('-')[1], delete: null, dataVersion: { $in: availableDataVersions } }).toArray(); - const formattedData = dataStandardization(study, fieldRecords, - groupedResult, queryString, standardizations); - return { data: formattedData }; - } - }, - Study: { - projects: async (study: IStudy): Promise> => { - return await db.collections!.projects_collection.find({ studyId: study.id, deleted: null }).toArray(); - }, - jobs: async (study: IStudy): Promise>> => { - return await db.collections!.jobs_collection.find({ studyId: study.id }).toArray(); - }, - roles: async (study: IStudy): Promise> => { - return await db.collections!.roles_collection.find({ studyId: study.id, projectId: undefined, deleted: null }).toArray(); - }, - files: async (study: IStudy, __unused__args: never, context: any): Promise> => { - const requester: IUser = context.req.user; - const hasPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.READ, - requester, - study.id - ); - - if (!hasPermission) { - return []; - } - const availableDataVersions: Array = (study.currentDataVersion === -1 ? [] : study.dataVersions.filter((__unused__el, index) => index <= study.currentDataVersion)).map(el => el.id); - const fileFieldIds: string[] = (await db.collections!.field_dictionary_collection.aggregate([{ - $match: { studyId: study.id, dateDeleted: null, dataVersion: { $in: availableDataVersions }, dataType: enumValueType.FILE } - }, { $match: { fieldId: { $in: hasPermission.raw.fieldIds.map((el: string) => new RegExp(el)) } } }, { - $sort: { dateAdded: -1 } - }, { - $group: { - _id: '$fieldId', - doc: { $first: '$$ROOT' } - } - }, { - $replaceRoot: { - newRoot: '$doc' - } - }, { - $sort: { fieldId: 1 } - }]).toArray()).map(el => el.fieldId); - let adds = []; - let removes = []; - // versioned data - if (requester.type === userTypes.ADMIN) { - const fileRecords = await db.collections!.data_collection.aggregate([{ - $match: { m_studyId: study.id, m_fieldId: { $in: fileFieldIds } } - }]).toArray(); - adds = fileRecords.map(el => el.metadata?.add || []).flat(); - removes = fileRecords.map(el => el.metadata?.remove || []).flat(); - } else { - const subqueries: any = []; - hasPermission.matchObj.forEach((subMetadata: any) => { - subqueries.push(translateMetadata(subMetadata)); - }); - const metadataFilter = { $or: subqueries }; - const versionedFileRecors = await db.collections!.data_collection.aggregate([{ - $match: { m_studyId: study.id, m_versionId: { $in: availableDataVersions }, m_fieldId: { $in: fileFieldIds } } - }, { - $match: metadataFilter - }]).toArray(); - - const filters = []; - for (const role of hasPermission.roleraw) { - if (!(role.hasVersioned)) { - continue; - } - filters.push({ - m_subjectId: { $in: role.subjectIds.map((el: string) => new RegExp(el)) }, - m_visitId: { $in: role.visitIds.map((el: string) => new RegExp(el)) }, - m_fieldId: { $in: role.fieldIds.map((el: string) => new RegExp(el)) }, - m_versionId: null - }); - } - let unversionedFileRecords: any[] = []; - if (filters.length !== 0) { - unversionedFileRecords = await db.collections!.data_collection.aggregate([{ - $match: { m_studyId: study.id, m_versionId: null, m_fieldId: { $in: fileFieldIds } } - }, { - $match: { $or: filters } - }]).toArray(); - } - adds = versionedFileRecors.map(el => el.metadata?.add || []).flat(); - removes = versionedFileRecors.map(el => el.metadata?.remove || []).flat(); - adds = adds.concat(unversionedFileRecords.map(el => el.metadata?.add || []).flat()); - removes = removes.concat(unversionedFileRecords.map(el => el.metadata?.remove || []).flat()); - } - return await db.collections!.files_collection.find({ studyId: study.id, deleted: null, $or: [{ id: { $in: adds, $nin: removes } }, { description: JSON.stringify({}) }] }).sort({ uploadTime: -1 }).toArray(); - }, - subjects: async (study: IStudy, __unused__args: never, context: any): Promise>> => { - const requester: IUser = context.req.user; - const hasPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.READ, - requester, - study.id - ); - if (!hasPermission) { - return [[], []]; - } - const availableDataVersions: Array = (study.currentDataVersion === -1 ? [] : study.dataVersions.filter((__unused__el, index) => index <= study.currentDataVersion)).map(el => el.id); - const versionedSubjects = (await db.collections!.data_collection.distinct('m_subjectId', { - m_studyId: study.id, - m_versionId: availableDataVersions[availableDataVersions.length - 1], - m_subjectId: { $in: hasPermission.raw.subjectIds.map((el: string) => new RegExp(el)) }, - m_visitId: { $in: hasPermission.raw.visitIds.map((el: string) => new RegExp(el)) }, - m_fieldId: { $in: hasPermission.raw.fieldIds.map((el: string) => new RegExp(el)) }, - value: { $ne: null } - })).sort() || []; - const unVersionedSubjects = hasPermission.hasVersioned ? (await db.collections!.data_collection.distinct('m_subjectId', { - m_studyId: study.id, - m_versionId: null, - m_subjectId: { $in: hasPermission.raw.subjectIds.map((el: string) => new RegExp(el)) }, - m_visitId: { $in: hasPermission.raw.visitIds.map((el: string) => new RegExp(el)) }, - m_fieldId: { $in: hasPermission.raw.fieldIds.map((el: string) => new RegExp(el)) }, - value: { $ne: null } - })).sort() || [] : []; - return [versionedSubjects, unVersionedSubjects]; - }, - visits: async (study: IStudy, __unused__args: never, context: any): Promise>> => { - const requester: IUser = context.req.user; - const hasPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.READ, - requester, - study.id - ); - if (!hasPermission) { - return [[], []]; - } - const availableDataVersions: Array = (study.currentDataVersion === -1 ? [] : study.dataVersions.filter((__unused__el, index) => index <= study.currentDataVersion)).map(el => el.id); - const versionedVisits = (await db.collections!.data_collection.distinct('m_visitId', { - m_studyId: study.id, - m_versionId: availableDataVersions[availableDataVersions.length - 1], - m_subjectId: { $in: hasPermission.raw.subjectIds.map((el: string) => new RegExp(el)) }, - m_visitId: { $in: hasPermission.raw.visitIds.map((el: string) => new RegExp(el)) }, - m_fieldId: { $in: hasPermission.raw.fieldIds.map((el: string) => new RegExp(el)) }, - value: { $ne: null } - })).sort((a, b) => parseFloat(a) - parseFloat(b)); - const unVersionedVisits = hasPermission.hasVersioned ? (await db.collections!.data_collection.distinct('m_visitId', { - m_studyId: study.id, - m_versionId: null, - m_subjectId: { $in: hasPermission.raw.subjectIds.map((el: string) => new RegExp(el)) }, - m_visitId: { $in: hasPermission.raw.visitIds.map((el: string) => new RegExp(el)) }, - m_fieldId: { $in: hasPermission.raw.fieldIds.map((el: string) => new RegExp(el)) }, - value: { $ne: null } - })).sort((a, b) => parseFloat(a) - parseFloat(b)) : []; - return [versionedVisits, unVersionedVisits]; - }, - numOfRecords: async (study: IStudy, __unused__args: never, context: any): Promise => { - const requester: IUser = context.req.user; - const hasPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.READ, - requester, - study.id - ); - if (!hasPermission) { - return [0, 0]; - } - const availableDataVersions: Array = (study.currentDataVersion === -1 ? [] : study.dataVersions.filter((__unused__el, index) => index <= study.currentDataVersion)).map(el => el.id); - const numberOfVersioned: number = (await db.collections!.data_collection.aggregate([{ - $match: { m_studyId: study.id, m_versionId: availableDataVersions[availableDataVersions.length - 1], value: { $ne: null } } - }, { - $match: { - m_subjectId: { $in: hasPermission.raw.subjectIds.map((el: string) => new RegExp(el)) }, - m_visitId: { $in: hasPermission.raw.visitIds.map((el: string) => new RegExp(el)) }, - m_fieldId: { $in: hasPermission.raw.fieldIds.map((el: string) => new RegExp(el)) } - } - }, { - $count: 'count' - }]).toArray())[0]?.count || 0; - const numberOfUnVersioned: number = hasPermission.hasVersioned ? (await db.collections!.data_collection.aggregate([{ - $match: { m_studyId: study.id, m_versionId: null, value: { $ne: null } } - }, { - $match: { - m_subjectId: { $in: hasPermission.raw.subjectIds.map((el: string) => new RegExp(el)) }, - m_visitId: { $in: hasPermission.raw.visitIds.map((el: string) => new RegExp(el)) }, - m_fieldId: { $in: hasPermission.raw.fieldIds.map((el: string) => new RegExp(el)) } - } - }, { - $count: 'count' - }]).toArray())[0]?.count || 0 : 0; - return [numberOfVersioned, numberOfUnVersioned]; - }, - currentDataVersion: async (study: IStudy): Promise => { - return study.currentDataVersion === -1 ? null : study.currentDataVersion; - } - }, - Project: { - fields: async (project: Omit, __unused__args: never, context: any): Promise>> => { - const requester: IUser = context.req.user; - const hasProjectLevelPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.READ, - requester, - project.studyId, - project.id - ); - if (!hasProjectLevelPermission) { return []; } - // get all dataVersions that are valid (before the current version) - const study = await studyCore.findOneStudy_throwErrorIfNotExist(project.studyId); - - // the processes of requiring versioned data and unversioned data are different - // check the metadata:role:**** for versioned data directly - // check the regular expressions for unversioned data - const availableDataVersions: string[] = (study.currentDataVersion === -1 ? [] : study.dataVersions.filter((__unused__el, index) => index <= study.currentDataVersion)).map(el => el.id); - const availableTrees: IOntologyTree[] = []; - const trees: IOntologyTree[] = study.ontologyTrees || []; - for (let i = trees.length - 1; i >= 0; i--) { - if (availableDataVersions.includes(trees[i].dataVersion || '') - && availableTrees.filter(el => el.name === trees[i].name).length === 0) { - availableTrees.push(trees[i]); - } else { - continue; - } - } - if (availableTrees.length === 0) { - return []; - } - const ontologyTreeFieldIds: string[] = (availableTrees[0].routes || []).map(el => el.field[0].replace('$', '')); - let fieldRecords: any[] = []; - if (requester.type === userTypes.ADMIN) { - fieldRecords = await db.collections!.field_dictionary_collection.aggregate([{ - $match: { studyId: project.studyId, dateDeleted: null, dataVersion: { $in: availableDataVersions }, fieldId: { $in: ontologyTreeFieldIds } } - }, { - $group: { - _id: '$fieldId', - doc: { $last: '$$ROOT' } - } - }, { - $replaceRoot: { - newRoot: '$doc' - } - }, { - $sort: { fieldId: 1 } - }, { - $set: { metadata: null } - }]).toArray(); - } else { - // metadata filter - const subqueries: any = []; - hasProjectLevelPermission.matchObj.forEach((subMetadata: any) => { - subqueries.push(translateMetadata(subMetadata)); - }); - const metadataFilter = { $or: subqueries }; - fieldRecords = await db.collections!.field_dictionary_collection.aggregate([{ - $match: { studyId: project.studyId, dateDeleted: null, dataVersion: { $in: availableDataVersions }, fieldId: { $in: ontologyTreeFieldIds } } - }, { $match: metadataFilter }, { - $group: { - _id: '$fieldId', - doc: { $last: '$$ROOT' } - } - }, { - $replaceRoot: { - newRoot: '$doc' - } - }, { - $sort: { fieldId: 1 } - }, { - $set: { metadata: null } - }]).toArray(); - } - return fieldRecords; - }, - jobs: async (project: Omit): Promise>> => { - return await db.collections!.jobs_collection.find({ studyId: project.studyId, projectId: project.id }).toArray(); - }, - files: async (project: Omit, __unused__args: never, context: any): Promise> => { - const requester: IUser = context.req.user; - const hasPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.READ, - requester, - project.studyId, - project.id - ); - if (!hasPermission) { - return []; - } - const study = await studyCore.findOneStudy_throwErrorIfNotExist(project.studyId); - const availableDataVersions = (study.currentDataVersion === -1 ? [] : study.dataVersions.filter((__unused__el, index) => index <= study.currentDataVersion)).map(el => el.id); - const availableTrees: IOntologyTree[] = []; - const trees: IOntologyTree[] = study.ontologyTrees || []; - for (let i = trees.length - 1; i >= 0; i--) { - if (availableDataVersions.includes(trees[i].dataVersion || '') - && availableTrees.filter(el => el.name === trees[i].name).length === 0) { - availableTrees.push(trees[i]); - } else { - continue; - } - } - if (availableTrees.length === 0) { - return []; - } - const ontologyTreeFieldIds: string[] = (availableTrees[0].routes || []).map(el => el.field[0].replace('$', '')); - const fileFieldIds: string[] = (await db.collections!.field_dictionary_collection.aggregate([{ - $match: { studyId: study.id, dateDeleted: null, dataVersion: { $in: availableDataVersions }, dataType: enumValueType.FILE } - }, { $match: { $and: [{ fieldId: { $in: hasPermission.raw.fieldIds.map((el: string) => new RegExp(el)) } }, { fieldId: { $in: ontologyTreeFieldIds } }] } }, { - $group: { - _id: '$fieldId', - doc: { $last: '$$ROOT' } - } - }, { - $replaceRoot: { - newRoot: '$doc' - } - }, { - $sort: { fieldId: 1 } - }]).toArray()).map(el => el.fieldId); - let add: string[] = []; - let remove: string[] = []; - if (Object.keys(hasPermission.matchObj).length === 0) { - (await db.collections!.data_collection.aggregate([{ - $match: { m_studyId: study.id, m_versionId: { $in: availableDataVersions }, m_fieldId: { $in: fileFieldIds } } - }]).toArray()).forEach(element => { - add = add.concat(element.metadata?.add || []); - remove = remove.concat(element.metadata?.remove || []); - }); - } else { - const subqueries: any = []; - hasPermission.matchObj.forEach((subMetadata: any) => { - subqueries.push(translateMetadata(subMetadata)); - }); - const metadataFilter = { $or: subqueries }; - (await db.collections!.data_collection.aggregate([{ - $match: { m_studyId: study.id, m_versionId: { $in: availableDataVersions }, m_fieldId: { $in: fileFieldIds } } - }, { - $match: metadataFilter - }]).toArray()).forEach(element => { - add = add.concat(element.metadata?.add || []); - remove = remove.concat(element.metadata?.remove || []); - }); - } - return await db.collections!.files_collection.find({ $and: [{ id: { $in: add } }, { id: { $nin: remove } }] }).toArray(); - }, - dataVersion: async (project: IProject): Promise => { - const study = await db.collections!.studies_collection.findOne({ id: project.studyId, deleted: null }); - if (study === undefined || study === null) { - return null; - } - if (study.currentDataVersion === -1) { - return null; - } - return study.dataVersions[study?.currentDataVersion]; - }, - summary: async (project: IProject, __unused__args: never, context: any): Promise => { - const summary: any = {}; - const study = await db.collections!.studies_collection.findOne({ id: project.studyId }); - if (study === undefined || study === null || study.currentDataVersion === -1) { - return summary; - } - const requester: IUser = context.req.user; - /* user can get study if he has readonly permission */ - const hasStudyLevelPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.READ, - requester, - project.studyId - ); - const hasProjectLevelPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.READ, - requester, - project.studyId, - project.id - ); - if (!hasStudyLevelPermission && !hasProjectLevelPermission) { throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); } - // get all dataVersions that are valid (before the current version) - const aggregatedPermissions: any = permissionCore.combineMultiplePermissions([hasStudyLevelPermission, hasProjectLevelPermission]); - - let metadataFilter: any = undefined; - - const availableDataVersions = (study.currentDataVersion === -1 ? [] : study.dataVersions.filter((__unused__el, index) => index <= study.currentDataVersion)).map(el => el.id); - // ontology trees - const availableTrees: IOntologyTree[] = []; - const trees: IOntologyTree[] = study.ontologyTrees || []; - for (let i = trees.length - 1; i >= 0; i--) { - if (availableDataVersions.includes(trees[i].dataVersion || '') - && availableTrees.filter(el => el.name === trees[i].name).length === 0) { - availableTrees.push(trees[i]); - } else { - continue; - } - } - // const ontologyTreeFieldIds = (availableTrees[0]?.routes || []).map(el => el.field[0].replace('$', '')); - - let fieldRecords; - if (requester.type === userTypes.ADMIN) { - fieldRecords = await db.collections!.field_dictionary_collection.aggregate([{ - $match: { studyId: project.studyId, dateDeleted: null, dataVersion: { $in: availableDataVersions } } - }, { - $group: { - _id: '$fieldId', - doc: { $last: '$$ROOT' } - } - }, { - $replaceRoot: { - newRoot: '$doc' - } - }]).toArray(); - } else { - const subqueries: any = []; - aggregatedPermissions.matchObj.forEach((subMetadata: any) => { - subqueries.push(translateMetadata(subMetadata)); - }); - metadataFilter = { $or: subqueries }; - fieldRecords = await db.collections!.field_dictionary_collection.aggregate([{ - $match: { studyId: project.studyId, dateDeleted: null, dataVersion: { $in: availableDataVersions } } - }, { $match: metadataFilter }, { - $group: { - _id: '$fieldId', - doc: { $last: '$$ROOT' } - } - }, { - $replaceRoot: { - newRoot: '$doc' - } - }]).toArray(); - } - // fieldRecords = fieldRecords.filter(el => ontologyTreeFieldIds.includes(el.fieldId)); - const pipeline = buildPipeline({}, project.studyId, [availableDataVersions[availableDataVersions.length - 1]], fieldRecords as IFieldEntry[], metadataFilter, requester.type === userTypes.ADMIN, false); - const result = await db.collections!.data_collection.aggregate(pipeline, { allowDiskUse: true }).toArray(); - summary['subjects'] = Array.from(new Set(result.map((el: any) => el.m_subjectId))).sort(); - summary['visits'] = Array.from(new Set(result.map((el: any) => el.m_visitId))).sort((a, b) => parseFloat(a) - parseFloat(b)).sort(); - summary['standardizationTypes'] = (await db.collections!.standardizations_collection.distinct('type', { studyId: study.id, deleted: null })).sort(); - return summary; - }, - patientMapping: async (project: Omit, __unused__args: never, context: any): Promise => { - const requester: IUser = context.req.user; - /* check privileges */ - if (!(await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.READ, // patientMapping is not visible to project users; only to study users. - requester, - project.studyId - ))) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - /* returning */ - const result = - await db.collections!.projects_collection.findOne( - { id: project.id, deleted: null }, - { projection: { patientMapping: 1 } } - ); - if (result && result.patientMapping) { - return result.patientMapping; - } else { - return null; - } - }, - roles: async (project: IProject): Promise> => { - return await db.collections!.roles_collection.find({ studyId: project.studyId, projectId: project.id, deleted: null }).toArray(); - }, - iCanEdit: async (project: IProject): Promise => { // TO_DO - await db.collections!.roles_collection.findOne({ - studyId: project.studyId, - projectId: project.id - // permissions: permissions.specific_project.specifi - }); - return true; - } - }, - Mutation: { - createStudy: async (__unused__parent: Record, { name, description, type }: { name: string, description: string, type: studyType }, context: any): Promise => { - const requester: IUser = context.req.user; - - /* check privileges */ - if (requester.type !== userTypes.ADMIN) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - /* create study */ - const study = await studyCore.createNewStudy(name, description, type, requester.id); - return study; - }, - editStudy: async (__unused__parent: Record, { studyId, description }: { studyId: string, description: string }, context: any): Promise => { - const requester: IUser = context.req.user; - - /* check privileges */ - if (requester.type !== userTypes.ADMIN) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - /* create study */ - const study = await studyCore.editStudy(studyId, description); - return study; - }, - createNewField: async (__unused__parent: Record, { studyId, fieldInput }: { studyId: string, fieldInput: any[] }, context: any): Promise => { - const requester: IUser = context.req.user; - /* check privileges */ - /* user can get study if he has readonly permission */ - const hasPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.WRITE, - requester, - studyId - ); - if (!hasPermission) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - // check study exists - await studyCore.findOneStudy_throwErrorIfNotExist(studyId); - - const response: IGenericResponse[] = []; - let isError = false; - const bulk = db.collections!.field_dictionary_collection.initializeUnorderedBulkOp(); - // remove duplicates by fieldId - const keysToCheck = ['fieldId']; - const filteredFieldInput = fieldInput.filter( - (s => o => (k => !s.has(k) && s.add(k))(keysToCheck.map(k => o[k]).join('|')))(new Set()) - ); - // check fieldId duplicate - for (const oneFieldInput of filteredFieldInput) { - isError = false; - // check data valid - if (!(permissionCore.checkDataEntryValid(hasPermission.raw, oneFieldInput.fieldId))) { - isError = true; - response.push({ successful: false, code: errorCodes.NO_PERMISSION_ERROR, description: 'You do not have permissions to create this field.' }); - continue; - } - const { fieldEntry, error: thisError } = validateAndGenerateFieldEntry(oneFieldInput, requester); - if (thisError.length !== 0) { - response.push({ successful: false, code: errorCodes.CLIENT_MALFORMED_INPUT, description: `Field ${oneFieldInput.fieldId || 'fieldId not defined'}-${oneFieldInput.fieldName || 'fieldName not defined'}: ${JSON.stringify(thisError)}` }); - isError = true; - } else { - response.push({ successful: true, description: `Field ${oneFieldInput.fieldId}-${oneFieldInput.fieldName} is created successfully.` }); - } - // // construct the rest of the fields - if (!isError) { - fieldEntry.id = uuid(); - fieldEntry.studyId = studyId; - fieldEntry.dataVersion = null; - fieldEntry.dateAdded = (new Date()).valueOf(); - fieldEntry.dateDeleted = null; - fieldEntry.metadata = { - uploader: requester.id - }; - bulk.find({ - fieldId: fieldEntry.fieldId, - studyId: studyId, - dataVersion: null - }).upsert().updateOne({ $set: fieldEntry }); - } - } - if (bulk.batches.length > 0) { - await bulk.execute(); - } - return response; - }, - editField: async (__unused__parent: Record, { studyId, fieldInput }: { studyId: string, fieldInput: any }, context: any): Promise => { - const requester: IUser = context.req.user; - /* check privileges */ - if (requester.type !== userTypes.ADMIN) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - // check fieldId exist - const searchField = await db.collections!.field_dictionary_collection.findOne({ studyId: studyId, fieldId: fieldInput.fieldId, dateDeleted: null }); - if (!searchField) { - throw new GraphQLError('Field does not exist.', { extensions: { code: errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY } }); - } - for (const each of Object.keys(fieldInput) as Array) { - searchField[each] = fieldInput[each]; - } - const { fieldEntry, error } = validateAndGenerateFieldEntry(searchField, requester); - if (error.length !== 0) { - throw new GraphQLError(JSON.stringify(error), { extensions: { code: errorCodes.CLIENT_MALFORMED_INPUT } }); - } - const newFieldEntry = { ...fieldEntry, id: searchField.id, dateAdded: searchField.dateAdded, deleted: searchField.dateDeleted, studyId: searchField.studyId }; - await db.collections!.field_dictionary_collection.findOneAndUpdate({ studyId: studyId, fieldId: newFieldEntry.fieldId }, { $set: newFieldEntry }); - - return newFieldEntry; - - }, - deleteField: async (__unused__parent: Record, { studyId, fieldId }: { studyId: string, fieldId: string }, context: any): Promise => { - const requester: IUser = context.req.user; - /* check privileges */ - const hasPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.WRITE, - requester, - studyId - ); - if (!hasPermission) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - if (!(await permissionCore.checkDataEntryValid(hasPermission.raw, fieldId))) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - // check fieldId exist - const searchField = await db.collections!.field_dictionary_collection.find({ studyId: studyId, fieldId: fieldId, dateDeleted: null }).limit(1).sort({ dateAdded: -1 }).toArray(); - if (searchField.length === 0 || searchField[0].dateDeleted !== null) { - throw new GraphQLError('Field does not exist.', { extensions: { code: errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY } }); - } - - const fieldEntry: any = { - id: uuid(), - studyId: studyId, - fieldId: searchField[0].fieldId, - fieldName: searchField[0].fieldName, - tableName: searchField[0].tableName, - dataType: searchField[0].dataType, - possibleValues: searchField[0].possibleValues, - unit: searchField[0].unit, - comments: searchField[0].comments, - dataVersion: null, - dateAdded: (new Date()).valueOf(), - dateDeleted: (new Date()).valueOf() - }; - await db.collections!.field_dictionary_collection.insertOne(fieldEntry); - return searchField[0]; - - }, - uploadDataInArray: async (__unused__parent: Record, { studyId, data }: { studyId: string, data: IDataClip[] }, context: any): Promise => { - // check study exists - const study = await studyCore.findOneStudy_throwErrorIfNotExist(studyId); - - const requester: IUser = context.req.user; - /* check privileges */ - /* user can get study if he has readonly permission */ - const hasPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.WRITE, - requester, - studyId - ); - if (!hasPermission) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - // find the fieldsList, including those that have not been versioned, same method as getStudyFields - // get all dataVersions that are valid (before/equal the current version) - const availableDataVersions = (study.currentDataVersion === -1 ? [] : study.dataVersions.filter((__unused__el, index) => index <= study.currentDataVersion)).map(el => el.id); - const fieldRecords = await db.collections!.field_dictionary_collection.aggregate([{ - $sort: { dateAdded: -1 } - }, { - $match: { $or: [{ dataVersion: null }, { dataVersion: { $in: availableDataVersions } }] } - }, { - $match: { studyId: studyId } - }, { - $group: { - _id: '$fieldId', - doc: { $first: '$$ROOT' } - } - } - ]).toArray(); - // filter those that have been deleted - const fieldsList = fieldRecords.map(el => el.doc).filter(eh => eh.dateDeleted === null); - const response = (await studyCore.uploadOneDataClip(studyId, hasPermission.raw, fieldsList, data, requester)); - - return response; - }, - deleteDataRecords: async (__unused__parent: Record, { studyId, subjectIds, visitIds, fieldIds }: { studyId: string, subjectIds: string[], visitIds: string[], fieldIds: string[] }, context: any): Promise => { - // check study exists - await studyCore.findOneStudy_throwErrorIfNotExist(studyId); - const response: IGenericResponse[] = []; - const requester: IUser = context.req.user; - /* check privileges */ - const hasPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.WRITE, - requester, - studyId - ); - if (!hasPermission) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - let validSubjects: string[]; - let validVisits: string[]; - let validFields: any; - // filter - if (subjectIds === undefined || subjectIds === null || subjectIds.length === 0) { - validSubjects = (await db.collections!.data_collection.distinct('m_subjectId', { m_studyId: studyId })); - } else { - validSubjects = subjectIds; - } - if (visitIds === undefined || visitIds === null || visitIds.length === 0) { - validVisits = (await db.collections!.data_collection.distinct('m_visitId', { m_studyId: studyId })); - } else { - validVisits = visitIds; - } - if (fieldIds === undefined || fieldIds === null || fieldIds.length === 0) { - validFields = (await db.collections!.field_dictionary_collection.distinct('fieldId', { studyId: studyId })); - } else { - validFields = fieldIds; - } - - const bulk = db.collections!.data_collection.initializeUnorderedBulkOp(); - for (const subjectId of validSubjects) { - for (const visitId of validVisits) { - for (const fieldId of validFields) { - if (!(await permissionCore.checkDataEntryValid(hasPermission.raw, fieldId, subjectId, visitId))) { - continue; - } - bulk.find({ m_studyId: studyId, m_subjectId: subjectId, m_visitId: visitId, m_fieldId: fieldId, m_versionId: null }).upsert().updateOne({ - $set: { - m_studyId: studyId, - m_subjectId: subjectId, - m_visitId: visitId, - m_versionId: null, - m_fieldId: fieldId, - value: null, - uploadedAt: (new Date()).valueOf(), - id: uuid() - } - }); - response.push({ successful: true, description: `SubjectId-${subjectId}:visitId-${visitId}:fieldId-${fieldId} is deleted.` }); - } - } - } - if (bulk.batches.length > 0) { - await bulk.execute(); - } - return response; - }, - createNewDataVersion: async (__unused__parent: Record, { studyId, dataVersion, tag }: { studyId: string, dataVersion: string, tag: string }, context: any): Promise => { - // check study exists - await studyCore.findOneStudy_throwErrorIfNotExist(studyId); - - const requester: IUser = context.req.user; - - /* check privileges */ - if (requester.type !== userTypes.ADMIN) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - // check dataVersion name valid - if (!/^\d{1,3}(\.\d{1,2}){0,2}$/.test(dataVersion)) { - throw new GraphQLError(errorCodes.CLIENT_MALFORMED_INPUT); - } - - const created = await studyCore.createNewDataVersion(studyId, tag, dataVersion); - if (created === null) { - throw new GraphQLError('No matched or modified records', { extensions: { code: errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY } }); - } - return created; - }, - createOntologyTree: async (__unused__parent: Record, { studyId, ontologyTree }: { studyId: string, ontologyTree: Pick }, context: any): Promise => { - /* check study exists */ - const study = await studyCore.findOneStudy_throwErrorIfNotExist(studyId); - - const requester: IUser = context.req.user; - - /* user can get study if he has readonly permission */ - const hasPermission = await permissionCore.userHasTheNeccessaryManagementPermission( - IPermissionManagementOptions.ontologyTrees, - atomicOperation.WRITE, - requester, - studyId - ); - if (!hasPermission) { throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); } - - // in case of old documents whose ontologyTrees are invalid - if (study.ontologyTrees === undefined || study.ontologyTrees === null) { - await db.collections!.studies_collection.findOneAndUpdate({ id: studyId, deleted: null }, { - $set: { - ontologyTrees: [] - } - }); - } - const ontologyTreeWithId: Partial = { ...ontologyTree }; - ontologyTreeWithId.id = uuid(); - ontologyTreeWithId.routes = ontologyTreeWithId.routes || []; - ontologyTreeWithId.routes.forEach(el => { - el.id = uuid(); - el.visitRange = el.visitRange || []; - }); - await db.collections!.studies_collection.findOneAndUpdate({ - id: studyId, deleted: null, ontologyTrees: { - $not: { - $elemMatch: { - name: ontologyTree.name, - dataVersion: null - } - } - } - }, { - $addToSet: { - ontologyTrees: ontologyTreeWithId - } - }); - await db.collections!.studies_collection.findOneAndUpdate({ id: studyId, deleted: null, ontologyTrees: { $elemMatch: { name: ontologyTreeWithId.name, dataVersion: null } } }, { - $set: { - 'ontologyTrees.$.routes': ontologyTreeWithId.routes, - 'ontologyTrees.$.dataVersion': null, - 'ontologyTrees.$.deleted': null - } - }); - return ontologyTreeWithId as IOntologyTree; - }, - deleteOntologyTree: async (__unused__parent: Record, { studyId, treeName }: { studyId: string, treeName: string }, context: any): Promise => { - /* check study exists */ - await studyCore.findOneStudy_throwErrorIfNotExist(studyId); - - const requester: IUser = context.req.user; - - /* user can get study if he has readonly permission */ - const hasPermission = await permissionCore.userHasTheNeccessaryManagementPermission( - IPermissionManagementOptions.ontologyTrees, - atomicOperation.WRITE, - requester, - studyId - ); - if (!hasPermission) { throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); } - - const resultAdd = await db.collections!.studies_collection.findOneAndUpdate({ - id: studyId, deleted: null, ontologyTrees: { - $not: { - $elemMatch: { - name: treeName, - dataVersion: null - } - } - } - }, { - $addToSet: { - ontologyTrees: { - id: uuid(), - name: treeName, - dataVersion: null, - deleted: Date.now().valueOf() - } - } - }); - const resultUpdate = await db.collections!.studies_collection.findOneAndUpdate({ - id: studyId, deleted: null, ontologyTrees: { $elemMatch: { name: treeName, dataVersion: null } } - }, { - $set: { - 'ontologyTrees.$.deleted': Date.now().valueOf(), - 'ontologyTrees.$.routes': undefined - } - }); - if (resultAdd || resultUpdate) { - return makeGenericReponse(treeName); - } else { - throw new GraphQLError(errorCodes.DATABASE_ERROR); - } - - }, - createProject: async (__unused__parent: Record, { studyId, projectName }: { studyId: string, projectName: string }, context: any): Promise => { - const requester: IUser = context.req.user; - - /* check privileges */ - if (!(await permissionCore.userHasTheNeccessaryManagementPermission( - IPermissionManagementOptions.own, - atomicOperation.WRITE, - requester, - studyId - ))) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - /* making sure that the study exists first */ - await studyCore.findOneStudy_throwErrorIfNotExist(studyId); - - /* create project */ - const project = await studyCore.createProjectForStudy(studyId, projectName, requester.id); - return project; - }, - deleteProject: async (__unused__parent: Record, { projectId }: { projectId: string }, context: any): Promise => { - const requester: IUser = context.req.user; - - const project = await studyCore.findOneProject_throwErrorIfNotExist(projectId); - - /* check privileges */ - if (!(await permissionCore.userHasTheNeccessaryManagementPermission( - IPermissionManagementOptions.own, - atomicOperation.WRITE, - requester, - project.studyId - ))) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - /* delete project */ - await studyCore.deleteProject(projectId); - return makeGenericReponse(projectId); - }, - deleteStudy: async (__unused__parent: Record, { studyId }: { studyId: string }, context: any): Promise => { - const requester: IUser = context.req.user; - - /* check privileges */ - if (requester.type !== userTypes.ADMIN) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - const study = await db.collections!.studies_collection.findOne({ id: studyId, deleted: null }); - - if (study) { - /* delete study */ - await studyCore.deleteStudy(studyId); - } else { - throw new GraphQLError(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - } - - return makeGenericReponse(studyId); - }, - setDataversionAsCurrent: async (__unused__parent: Record, { studyId, dataVersionId }: { studyId: string, dataVersionId: string }, context: any): Promise => { - const requester: IUser = context.req.user; - - /* check privileges */ - if (!(await permissionCore.userHasTheNeccessaryManagementPermission( - IPermissionManagementOptions.own, - atomicOperation.WRITE, - requester, - studyId - ))) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - const study = await studyCore.findOneStudy_throwErrorIfNotExist(studyId); - - /* check whether the dataversion exists */ - const selectedataVersionFiltered = study.dataVersions.filter((el) => el.id === dataVersionId); - if (selectedataVersionFiltered.length !== 1) { - throw new GraphQLError(errorCodes.CLIENT_MALFORMED_INPUT); - } - - /* create a new dataversion with the same contentId */ - // const newDataVersion: IStudyDataVersion = { - // ...selectedataVersionFiltered[0], - // id: uuid() - // }; - - /* add this to the database */ - // const result = await db.collections!.studies_collection.findOneAndUpdate({ id: studyId, deleted: null }, { - // $push: { dataVersions: newDataVersion }, $inc: { currentDataVersion: 1 } - // }, { returnDocument: 'after' }); - - /* update the currentversion field in database */ - const versionIdsList = study.dataVersions.map((el) => el.id); - const result = await db.collections!.studies_collection.findOneAndUpdate({ id: studyId, deleted: null }, { - $set: { currentDataVersion: versionIdsList.indexOf(dataVersionId) } - }, { - returnDocument: 'after' - }); - - if (result) { - return result; - } else { - throw new GraphQLError(errorCodes.DATABASE_ERROR); - } - - - - } - }, - Subscription: {} -}; diff --git a/packages/itmat-interface/src/graphql/resolvers/userResolvers.ts b/packages/itmat-interface/src/graphql/resolvers/userResolvers.ts deleted file mode 100644 index 4b8d5bffd..000000000 --- a/packages/itmat-interface/src/graphql/resolvers/userResolvers.ts +++ /dev/null @@ -1,774 +0,0 @@ -import { ApolloServerErrorCode } from '@apollo/server/errors'; -import { GraphQLError } from 'graphql'; -import bcrypt from 'bcrypt'; -import crypto from 'crypto'; -import { mailer } from '../../emailer/emailer'; -import { IProject, IStudy, IUser, IUserWithoutToken, IResetPasswordRequest, userTypes, IOrganisation } from '@itmat-broker/itmat-types'; -import { Logger } from '@itmat-broker/itmat-commons'; -import { v4 as uuid } from 'uuid'; -import { db } from '../../database/database'; -import config from '../../utils/configManager'; -import { userCore } from '../core/userCore'; -import { errorCodes } from '../errors'; -import { makeGenericReponse, IGenericResponse } from '../responses'; -import * as mfa from '../../utils/mfa'; -import QRCode from 'qrcode'; -import tmp from 'tmp'; - -export const userResolvers = { - Query: { - whoAmI(parent: Record, __unused__args: any, context: any): Record { - return context.req.user; - }, - getUsers: async (__unused__parent: Record, args: any): Promise => { - // everyone is allowed to see all the users in the app. But only admin can access certain fields, like emails, etc - see resolvers for User type. - const queryObj = args.userId === undefined ? { deleted: null } : { deleted: null, id: args.userId }; - const cursor = db.collections!.users_collection.find(queryObj, { projection: { _id: 0 } }); - return cursor.toArray(); - }, - validateResetPassword: async (__unused__parent: Record, args: any): Promise => { - /* decrypt email */ - const salt = makeAESKeySalt(args.token); - const iv = makeAESIv(args.token); - let email; - try { - email = await decryptEmail(args.encryptedEmail, salt, iv); - } catch (e) { - throw new GraphQLError('Token is not valid.'); - } - - /* check whether username and token is valid */ - /* not changing password too in one step (using findOneAndUpdate) because bcrypt is costly */ - const TIME_NOW = new Date().valueOf(); - const ONE_HOUR_IN_MILLISEC = 60 * 60 * 1000; - const user: IUserWithoutToken | null = await db.collections!.users_collection.findOne({ - email, - resetPasswordRequests: { - $elemMatch: { - id: args.token, - timeOfRequest: { $gt: TIME_NOW - ONE_HOUR_IN_MILLISEC }, - used: false - } - }, - deleted: null - }); - if (!user) { - throw new GraphQLError(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - } - return makeGenericReponse(); - }, - recoverSessionExpireTime: async (): Promise => { - return makeGenericReponse(); - } - }, - User: { - access: async (user: IUser, __unused__arg: any, context: any): Promise<{ projects: IProject[], studies: IStudy[], id: string }> => { - const requester: IUser = context.req.user; - - /* only admin can access this field */ - if (requester.type !== userTypes.ADMIN && user.id !== requester.id) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - /* if requested user is admin, then he has access to all studies */ - if (user.type === userTypes.ADMIN) { - const allprojects: IProject[] = await db.collections!.projects_collection.find({ deleted: null }).toArray(); - const allstudies: IStudy[] = await db.collections!.studies_collection.find({ deleted: null }).toArray(); - return { id: `user_access_obj_user_id_${user.id}`, projects: allprojects, studies: allstudies }; - } - - /* if requested user is not admin, find all the roles a user has */ - const roles = await db.collections!.roles_collection.find({ users: user.id, deleted: null }).toArray(); - const init: { projects: string[], studies: string[] } = { projects: [], studies: [] }; - const studiesAndProjectThatUserCanSee: { projects: string[], studies: string[] } = roles.reduce( - (a, e) => { - if (e.projectId) { - a.projects.push(e.projectId); - } else { - a.studies.push(e.studyId); - } - return a; - }, init - ); - - const projects = await db.collections!.projects_collection.find({ - $or: [ - { id: { $in: studiesAndProjectThatUserCanSee.projects }, deleted: null }, - { studyId: { $in: studiesAndProjectThatUserCanSee.studies }, deleted: null } - ] - }).toArray(); - const studies = await db.collections!.studies_collection.find({ id: { $in: studiesAndProjectThatUserCanSee.studies }, deleted: null }).toArray(); - return { id: `user_access_obj_user_id_${user.id}`, projects, studies }; - }, - username: async (user: IUser, __unused__arg: any, context: any): Promise => { - const requester: IUser = context.req.user; - /* only admin can access this field */ - if (context.req.user.type !== userTypes.ADMIN && user.id !== requester.id) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - return user.username; - }, - description: async (user: IUser, __unused__arg: any, context: any): Promise => { - const requester: IUser = context.req.user; - /* only admin can access this field */ - if (context.req.user.type !== userTypes.ADMIN && user.id !== requester.id) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - return user.description; - }, - email: async (user: IUser, __unused__arg: any, context: any): Promise => { - const requester: IUser = context.req.user; - /* only admin can access this field */ - if (context.req.user.type !== userTypes.ADMIN && user.id !== requester.id) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - return user.email; - } - }, - Mutation: { - requestExpiryDate: async (__unused__parent: Record, { username, email }: { username?: string, email?: string }): Promise => { - /* double-check user existence */ - const queryObj = email ? { deleted: null, email } : { deleted: null, username }; - const user: IUser | null = await db.collections!.users_collection.findOne(queryObj); - if (!user) { - /* even user is null. send successful response: they should know that a user doesn't exist */ - await new Promise(resolve => setTimeout(resolve, Math.random() * 6000)); - return makeGenericReponse(); - } - /* send email to the DMP admin mailing-list */ - await mailer.sendMail(formatEmailRequestExpiryDatetoAdmin({ - userEmail: user.email, - username: user.username - })); - - /* send email to client */ - await mailer.sendMail(formatEmailRequestExpiryDatetoClient({ - to: user.email, - username: user.username - })); - - return makeGenericReponse(); - }, - requestUsernameOrResetPassword: async (__unused__parent: Record, { forgotUsername, forgotPassword, email, username }: { forgotUsername: boolean, forgotPassword: boolean, email?: string, username?: string }, context: any): Promise => { - /* checking the args are right */ - if ((forgotUsername && !email) // should provide email if no username - || (forgotUsername && username) // should not provide username if it's forgotten.. - || (!email && !username)) { - throw new GraphQLError(errorCodes.CLIENT_MALFORMED_INPUT); - } else if (email && username) { - // TO_DO : better client erro - /* only provide email if no username */ - throw new GraphQLError(errorCodes.CLIENT_MALFORMED_INPUT); - } - - /* check user existence */ - const queryObj = email ? { deleted: null, email } : { deleted: null, username }; - const user = await db.collections!.users_collection.findOne(queryObj); - if (!user) { - /* even user is null. send successful response: they should know that a user doesn't exist */ - await new Promise(resolve => setTimeout(resolve, Math.random() * 6000)); - return makeGenericReponse(); - } - - if (forgotPassword) { - /* make link to change password */ - const passwordResetToken = uuid(); - const resetPasswordRequest: IResetPasswordRequest = { - id: passwordResetToken, - timeOfRequest: new Date().valueOf(), - used: false - }; - const invalidateAllTokens = await db.collections!.users_collection.findOneAndUpdate( - queryObj, - { - $set: { - 'resetPasswordRequests.$[].used': true - } - } - ); - if (invalidateAllTokens === null) { - throw new GraphQLError(errorCodes.DATABASE_ERROR); - } - const updateResult = await db.collections!.users_collection.findOneAndUpdate( - queryObj, - { - $push: { - resetPasswordRequests: resetPasswordRequest - } - } - ); - if (updateResult === null) { - throw new GraphQLError(errorCodes.DATABASE_ERROR); - } - - /* send email to client */ - await mailer.sendMail(await formatEmailForForgottenPassword({ - to: user.email, - resetPasswordToken: passwordResetToken, - username: user.username, - firstname: user.firstname, - origin: context.req.headers.origin - })); - } else { - /* send email to client */ - await mailer.sendMail(formatEmailForFogettenUsername({ - to: user.email, - username: user.username - })); - } - return makeGenericReponse(); - }, - login: async (parent: Record, args: any, context: any): Promise> => { - const { req }: { req: Express.Request } = context; - const result = await db.collections!.users_collection.findOne({ deleted: null, username: args.username }); - if (!result) { - throw new GraphQLError('User does not exist.', { extensions: { code: ApolloServerErrorCode.BAD_USER_INPUT } }); - } - - const passwordMatched = await bcrypt.compare(args.password, result.password); - if (!passwordMatched) { - throw new GraphQLError('Incorrect password.', { extensions: { code: ApolloServerErrorCode.BAD_USER_INPUT } }); - } - - // validate the TOTP - const totpValidated = mfa.verifyTOTP(args.totp, result.otpSecret); - if (!totpValidated) { - if (process.env.NODE_ENV === 'development') - console.warn('Incorrect One-Time password. Continuing in development ...'); - else - throw new GraphQLError('Incorrect One-Time password.', { extensions: { code: ApolloServerErrorCode.BAD_USER_INPUT } }); - } - - /* validate if account expired */ - if (result.expiredAt < Date.now() && result.type === userTypes.STANDARD) { - if (args.requestexpirydate) { - /* send email to the DMP admin mailing-list */ - await mailer.sendMail(formatEmailRequestExpiryDatetoAdmin({ - userEmail: result.email, - username: result.username - })); - /* send email to client */ - await mailer.sendMail(formatEmailRequestExpiryDatetoClient({ - to: result.email, - username: result.username - })); - throw new GraphQLError('New expiry date has been requested! Wait for ADMIN to approve.', { extensions: { code: ApolloServerErrorCode.BAD_USER_INPUT } }); - } - - throw new GraphQLError('Account Expired. Please request a new expiry date!', { extensions: { code: ApolloServerErrorCode.BAD_USER_INPUT } }); - } - - const filteredResult: Partial = { ...result }; - delete filteredResult.password; - delete filteredResult.deleted; - - return new Promise((resolve) => { - req.login(filteredResult, (err: any) => { - if (err) { - Logger.error(err); - throw new GraphQLError('Cannot log in. Please try again later.'); - } - resolve(filteredResult); - }); - }); - }, - logout: async (parent: Record, __unused__args: any, context: any): Promise => { - const requester: IUser = context.req.user; - const req: Express.Request = context.req; - if (requester === undefined || requester === null) { - return makeGenericReponse(context.req.user); - } - return new Promise((resolve) => { - req.logout((err) => { - if (err) { - Logger.error(err); - throw new GraphQLError('Cannot log out'); - } else { - resolve(makeGenericReponse(context.req.user)); - } - }); - }); - }, - createUser: async (__unused__parent: Record, args: any): Promise => { - const { username, firstname, lastname, email, emailNotificationsActivated, password, description, organisation, metadata }: { - username: string, firstname: string, lastname: string, email: string, emailNotificationsActivated?: boolean, password: string, description?: string, organisation: string, metadata: any - } = args.user; - - /* check email is valid form */ - if (!/^([a-zA-Z0-9_\-.]+)@([a-zA-Z0-9_\-.]+)\.([a-zA-Z]{2,5})$/.test(email)) { - throw new GraphQLError('Email is not the right format.', { extensions: { code: ApolloServerErrorCode.BAD_USER_INPUT } }); - } - - /* check password validity */ - if (password && !passwordIsGoodEnough(password)) { - throw new GraphQLError('Password has to be at least 8 character long.', { extensions: { code: ApolloServerErrorCode.BAD_USER_INPUT } }); - } - - /* check that username and password dont have space */ - if (username.indexOf(' ') !== -1 || password.indexOf(' ') !== -1) { - throw new GraphQLError('Username or password cannot have spaces.', { extensions: { code: ApolloServerErrorCode.BAD_USER_INPUT } }); - } - - const alreadyExist = await db.collections!.users_collection.findOne({ username, deleted: null }); // since bycrypt is CPU expensive let's check the username is not taken first - if (alreadyExist !== null && alreadyExist !== undefined) { - throw new GraphQLError('User already exists.', { extensions: { code: ApolloServerErrorCode.BAD_USER_INPUT } }); - } - - /* check if email has been used to register */ - const emailExist = await db.collections!.users_collection.findOne({ email, deleted: null }); - if (emailExist !== null && emailExist !== undefined) { - throw new GraphQLError('This email has been registered. Please sign-in or register with another email!', { extensions: { code: ApolloServerErrorCode.BAD_USER_INPUT } }); - } - - /* randomly generate a secret for Time-based One Time Password*/ - const otpSecret = mfa.generateSecret(); - - await userCore.createUser({ - password, - otpSecret, - username, - type: userTypes.STANDARD, - description: description ?? '', - firstname, - lastname, - email, - organisation, - emailNotificationsActivated: !!emailNotificationsActivated, - metadata - }); - - /* send email to the registered user */ - // get QR Code for the otpSecret. - const oauth_uri = `otpauth://totp/${config.appName}:${username}?secret=${otpSecret}&issuer=Data%20Science%20Institute`; - const tmpobj = tmp.fileSync({ mode: 0o644, prefix: 'qrcodeimg-', postfix: '.png' }); - - QRCode.toFile(tmpobj.name, oauth_uri, {}, function (err) { - if (err) throw new GraphQLError(err.message); - }); - - const attachments = [{ filename: 'qrcode.png', path: tmpobj.name, cid: 'qrcode_cid' }]; - await mailer.sendMail({ - from: `${config.appName} <${config.nodemailer.auth.user}>`, - to: email, - subject: `[${config.appName}] Registration Successful`, - html: ` -

- Dear ${firstname}, -

-

- Welcome to the ${config.appName} data portal!
- Your username is ${username}.
-

-

- To login you will need to use a MFA authenticator app for one time passcode (TOTP).
- Scan the QRCode below in your MFA application of choice to configure it:
- QR code
- If you need to type the token in use ${otpSecret.toLowerCase()} -

-
-

- The ${config.appName} Team. -

- `, - attachments: attachments - }); - tmpobj.removeCallback(); - return makeGenericReponse(); - }, - deleteUser: async (__unused__parent: Record, args: any, context: any): Promise => { - /* only admin can delete users */ - const requester: IUser = context.req.user; - - // user (admin type) cannot delete itself - if (requester.id === args.userId) { - throw new GraphQLError('User cannot delete itself'); - } - - if (requester.type !== userTypes.ADMIN) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - await userCore.deleteUser(args.userId); - return makeGenericReponse(args.userId); - }, - resetPassword: async (__unused__parent: Record, { encryptedEmail, token, newPassword }: { encryptedEmail: string, token: string, newPassword: string }): Promise => { - /* check password validity */ - if (!passwordIsGoodEnough(newPassword)) { - throw new GraphQLError('Password has to be at least 8 character long.'); - } - - /* check that username and password dont have space */ - if (newPassword.indexOf(' ') !== -1) { - throw new GraphQLError('Password cannot have spaces.'); - } - - /* decrypt email */ - if (token.length < 16) { - throw new GraphQLError(errorCodes.CLIENT_MALFORMED_INPUT); - } - const salt = makeAESKeySalt(token); - const iv = makeAESIv(token); - let email; - try { - email = await decryptEmail(encryptedEmail, salt, iv); - } catch (e) { - throw new GraphQLError('Token is not valid.'); - } - - /* check whether username and token is valid */ - /* not changing password too in one step (using findOneAndUpdate) because bcrypt is costly */ - const TIME_NOW = new Date().valueOf(); - const ONE_HOUR_IN_MILLISEC = 60 * 60 * 1000; - const user: IUserWithoutToken | null = await db.collections!.users_collection.findOne({ - email, - resetPasswordRequests: { - $elemMatch: { - id: token, - timeOfRequest: { $gt: TIME_NOW - ONE_HOUR_IN_MILLISEC }, - used: false - } - }, - deleted: null - }); - if (!user) { - throw new GraphQLError(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - } - - /* randomly generate a secret for Time-based One Time Password*/ - const otpSecret = mfa.generateSecret(); - - /* all ok; change the user's password */ - const hashedPw = await bcrypt.hash(newPassword, config.bcrypt.saltround); - const updateResult = await db.collections!.users_collection.findOneAndUpdate( - { - id: user.id, - resetPasswordRequests: { - $elemMatch: { - id: token, - timeOfRequest: { $gt: TIME_NOW - ONE_HOUR_IN_MILLISEC }, - used: false - } - } - }, - { $set: { 'password': hashedPw, 'otpSecret': otpSecret, 'resetPasswordRequests.$.used': true } }); - if (updateResult === null) { - throw new GraphQLError(errorCodes.DATABASE_ERROR); - } - - /* need to log user out of all sessions */ - // TO_DO - - /* send email to the registered user */ - // get QR Code for the otpSecret. - const oauth_uri = `otpauth://totp/${config.appName}:${user.username}?secret=${otpSecret}&issuer=Data%20Science%20Institute`; - const tmpobj = tmp.fileSync({ mode: 0o644, prefix: 'qrcodeimg-', postfix: '.png' }); - - QRCode.toFile(tmpobj.name, oauth_uri, {}, function (err) { - if (err) throw new GraphQLError(err.message); - }); - - const attachments = [{ filename: 'qrcode.png', path: tmpobj.name, cid: 'qrcode_cid' }]; - await mailer.sendMail({ - from: `${config.appName} <${config.nodemailer.auth.user}>`, - to: email, - subject: `[${config.appName}] Password reset`, - html: ` -

- Dear ${user.firstname}, -

-

- Your password on ${config.appName} is now reset!
- You will need to update your MFA application for one-time passcode.
-

-

- To update your MFA authenticator app you can scan the QRCode below to configure it:
- QR code
- If you need to type the token in use ${otpSecret.toLowerCase()} -

-
-

- The ${config.appName} Team. -

- `, - attachments: attachments - }); - tmpobj.removeCallback(); - return makeGenericReponse(); - }, - editUser: async (__unused__parent: Record, args: any, context: any): Promise> => { - const requester: IUser = context.req.user; - const { id, username, type, firstname, lastname, email, emailNotificationsActivated, emailNotificationsStatus, password, description, organisation, expiredAt, metadata }: { - id: string, username?: string, type?: userTypes, firstname?: string, lastname?: string, email?: string, emailNotificationsActivated?: boolean, emailNotificationsStatus?: any, password?: string, description?: string, organisation?: string, expiredAt?: number, metadata?: any - } = args.user; - if (password !== undefined && requester.id !== id) { // only the user themself can reset password - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - if (password && !passwordIsGoodEnough(password)) { - throw new GraphQLError('Password has to be at least 8 character long.'); - } - if (requester.type !== userTypes.ADMIN && requester.id !== id) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - let result; - if (requester.type === userTypes.ADMIN) { - result = await db.collections!.users_collection.findOne({ id, deleted: null }); // just an extra guard before going to bcrypt cause bcrypt is CPU intensive. - if (result === null || result === undefined) { - throw new GraphQLError('User not found'); - } - } - - const fieldsToUpdate = { - type, - firstname, - lastname, - username, - email, - emailNotificationsActivated, - emailNotificationsStatus, - password, - description, - organisation, - expiredAt, - metadata - }; - - /* check email is valid form */ - if (email && !/^([a-zA-Z0-9_\-.]+)@([a-zA-Z0-9_\-.]+)\.([a-zA-Z]{2,5})$/.test(email)) { - throw new GraphQLError('User not updated: Email is not the right format.', { extensions: { code: ApolloServerErrorCode.BAD_USER_INPUT } }); - } - if (requester.type !== userTypes.ADMIN && ( - type || firstname || lastname || username || description || organisation - )) { - throw new GraphQLError('User not updated: Non-admin users are only authorised to change their password, email or email notification.'); - } - - if (password) { fieldsToUpdate.password = await bcrypt.hash(password, config.bcrypt.saltround); } - for (const each of (Object.keys(fieldsToUpdate) as Array)) { - if (fieldsToUpdate[each] === undefined) { - delete fieldsToUpdate[each]; - } - } - if (expiredAt) { - fieldsToUpdate['emailNotificationsStatus'] = { - expiringNotification: false - }; - } - const updateResult = await db.collections!.users_collection.findOneAndUpdate({ id, deleted: null }, { $set: fieldsToUpdate }, { returnDocument: 'after' }); - if (updateResult) { - // New expiry date has been updated successfully. - if (expiredAt && result) { - /* send email to client */ - await mailer.sendMail(formatEmailRequestExpiryDateNotification({ - to: result.email, - username: result.username - })); - } - return updateResult; - } else { - throw new GraphQLError('Server error; no entry or more than one entry has been updated.'); - } - }, - createOrganisation: async (__unused__parent: Record, { name, shortname, containOrg, metadata }: { name: string, shortname: string, containOrg: string, metadata: any }, context: any): Promise => { - const requester: IUser = context.req.user; - - /* check privileges */ - if (requester.type !== userTypes.ADMIN) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - // if the org already exists, update it; the existence is checked by the name - const createdOrganisation = await userCore.createOrganisation({ - name, - shortname: shortname ?? null, - containOrg: containOrg ?? null, - metadata: metadata ?? null - }); - - return createdOrganisation; - }, - deleteOrganisation: async (__unused__parent: Record, { id }: { id: string }, context: any): Promise => { - const requester: IUser = context.req.user; - - /* check privileges */ - if (requester.type !== userTypes.ADMIN) { - throw new GraphQLError(errorCodes.NO_PERMISSION_ERROR); - } - - const res = await db.collections!.organisations_collection.findOneAndUpdate({ id: id }, { - $set: { - deleted: Date.now() - } - }, { - returnDocument: 'after' - }); - - if (res) { - return res; - } else { - throw new GraphQLError('Delete organisation failed.'); - } - } - }, - Subscription: {} -}; - -export function makeAESKeySalt(str: string): string { - return str; -} - -export function makeAESIv(str: string): string { - if (str.length < 16) { throw new Error('IV cannot be less than 16 bytes long.'); } - return str.slice(0, 16); -} - -export async function encryptEmail(email: string, keySalt: string, iv: string): Promise { - const algorithm = 'aes-256-cbc'; - return new Promise((resolve, reject) => { - crypto.scrypt(config.aesSecret, keySalt, 32, (err, derivedKey) => { - if (err) reject(err); - const cipher = crypto.createCipheriv(algorithm, derivedKey, iv); - let encoded = cipher.update(email, 'utf8', 'hex'); - encoded += cipher.final('hex'); - resolve(encoded); - }); - }); - -} - -export async function decryptEmail(encryptedEmail: string, keySalt: string, iv: string): Promise { - const algorithm = 'aes-256-cbc'; - return new Promise((resolve, reject) => { - crypto.scrypt(config.aesSecret, keySalt, 32, (err, derivedKey) => { - if (err) reject(err); - try { - const decipher = crypto.createDecipheriv(algorithm, derivedKey, iv); - let decoded = decipher.update(encryptedEmail, 'hex', 'utf8'); - decoded += decipher.final('utf-8'); - resolve(decoded); - } catch (e) { - reject(e); - } - }); - }); -} - -async function formatEmailForForgottenPassword({ username, firstname, to, resetPasswordToken, origin }: { resetPasswordToken: string, to: string, username: string, firstname: string, origin: any }) { - const keySalt = makeAESKeySalt(resetPasswordToken); - const iv = makeAESIv(resetPasswordToken); - const encryptedEmail = await encryptEmail(to, keySalt, iv); - - const link = `${origin}/reset/${encryptedEmail}/${resetPasswordToken}`; - return ({ - from: `${config.appName} <${config.nodemailer.auth.user}>`, - to, - subject: `[${config.appName}] password reset`, - html: ` -

- Dear ${firstname}, -

-

- Your username is ${username}. -

-

- You can reset you password by click the following link (active for 1 hour):
- ${link} -

-
-

- The ${config.appName} Team. -

- ` - }); -} - -function formatEmailForFogettenUsername({ username, to }: { username: string, to: string }) { - return ({ - from: `${config.appName} <${config.nodemailer.auth.user}>`, - to, - subject: `[${config.appName}] password reset`, - html: ` -

- Dear user, -

-

- Your username is ${username}. -

-
-

- The ${config.appName} Team. -

- ` - }); -} - -function formatEmailRequestExpiryDatetoClient({ username, to }: { username: string, to: string }) { - return ({ - from: `${config.appName} <${config.nodemailer.auth.user}>`, - to, - subject: `[${config.appName}] New expiry date has been requested!`, - html: ` -

- Dear user, -

-

- New expiry date for your ${username} account has been requested. - You will get a notification email once the request is approved. -

-
-

- The ${config.appName} Team. -

- ` - }); -} - -function formatEmailRequestExpiryDatetoAdmin({ username, userEmail }: { username: string, userEmail: string }) { - return ({ - from: `${config.appName} <${config.nodemailer.auth.user}>`, - to: `${config.adminEmail}`, - subject: `[${config.appName}] New expiry date has been requested from ${username} account!`, - html: ` -

- Dear ADMINs, -

-

- A expiry date request from the ${username} account (whose email address is ${userEmail}) has been submitted. - Please approve or deny the request ASAP. -

-
-

- The ${config.appName} Team. -

- ` - }); -} - -function formatEmailRequestExpiryDateNotification({ username, to }: { username: string, to: string }) { - return ({ - from: `${config.appName} <${config.nodemailer.auth.user}>`, - to, - subject: `[${config.appName}] New expiry date has been updated!`, - html: ` -

- Dear user, -

-

- New expiry date for your ${username} account has been updated. - You now can log in as normal. -

-
-

- The ${config.appName} Team. -

- ` - }); -} - -function passwordIsGoodEnough(pw: string): boolean { - if (pw.length < 8) { - return false; - } - return true; -} diff --git a/packages/itmat-interface/src/graphql/typeDefs.ts b/packages/itmat-interface/src/graphql/typeDefs.ts deleted file mode 100644 index f1427d040..000000000 --- a/packages/itmat-interface/src/graphql/typeDefs.ts +++ /dev/null @@ -1,626 +0,0 @@ -import gql from 'graphql-tag'; - -export const typeDefs = gql` -scalar JSON -scalar BigInt -scalar Upload - -enum USERTYPE { - ADMIN - STANDARD - SYSTEM -} - -enum FIELD_ITEM_TYPE { - I #image - C #clinical -} - -enum FIELD_VALUE_TYPE { - int # integer - dec # decimal - str # characters/string - bool # boolean - date # datetime, temporaily save as string - file # file id - json # JSON: array & object - cat # CATEGORICAL -} - -enum STUDYTYPE { - SENSOR - CLINICAL - ANY -} - -type ValueCategory { - id: String! - code: String! - description: String -} - -enum StandardizationRuleSource { - value # from a given value - data # from the value of the field - fieldDef # from the definitions of the field - reserved # from the related value entries - inc # increase by 1 -} - -# rules for sources -# value: parameter is the value itself -# data: parameter is the key of the data, or paths joined by - for JSON data type; -# note the input is a whole data reocrd including the subjectId & visitId -# fieldDef: parameter is the name of the attribute of the field definition, e.g., unit -# inc: no parameter needed - -type Standardization { - id: String! - studyId: String! - type: String! - field: [String]! - path: [String] - joinByKeys: [String] - stdRules: [StandardizationRule] - dataVersion: String - uploadedAt: Float - metadata: JSON - deleted: String -} - -type StandardizationRule { - id: String! - entry: String! - source: StandardizationRuleSource! - parameter: [String]! - joinByKeys: [String] - filters: JSON -} - -input StandardizationInput { - type: String! - field: [String]! - path: [String]! - joinByKeys: [String] - stdRules: [StandardizationRuleInput] -} - -input StandardizationRuleInput { - entry: String! - source: StandardizationRuleSource! - parameter: [String] - filters: JSON -} - -type Field { - id: String! - studyId: String! - fieldId: String! # start - fieldName: String! - tableName: String - dataType: FIELD_VALUE_TYPE! - possibleValues: [ValueCategory] - metadata: JSON - unit: String - comments: String - dataVersion: String - dateAdded: String! - dateDeleted: String -} - -input DataClip { - fieldId: String! - value: String - subjectId: String! - visitId: String! - file: Upload - metadata: JSON -} - -type UserAccess { - id: String! - projects: [Project]! - studies: [Study]! -} - -type UserPermissions { - projects: [StudyOrProjectUserRole]! - studies: [StudyOrProjectUserRole]! -} - -type User { - id: String! - username: String! # admin only - type: USERTYPE! - firstname: String - lastname: String - organisation: String - email: String # admin only - description: String # admin only - emailNotificationsActivated: Boolean! - emailNotificationsStatus: JSON - createdBy: String - createdAt: Float! - expiredAt: Float! - metadata: JSON - # external to mongo documents: - access: UserAccess # admin or self only -} - -type Pubkey { - id: String! - pubkey: String! - associatedUserId: String - jwtPubkey: String! - jwtSeckey: String! - refreshCounter: Int! - deleted: String -} - -type KeyPairwSignature { - privateKey: String! - publicKey: String! - signature: String -} - -type Signature { - signature: String -} - -type AccessToken { - accessToken: String -} - -type OrganisationMetadata { - siteIDMarker: String -} - -input OrganisationMetadataInput { - siteIDMarker: String -} - -type Organisation { - id: String! - name: String! - shortname: String - containOrg: String - deleted: String - metadata: OrganisationMetadata -} - -type StudyOrProjectUserRole { - id: String! - name: String! - studyId: String - projectId: String - description: String - permissions: JSON - users: [User]! - metadata: JSON -} - -type File { - id: String! - uri: String! - fileName: String! - studyId: String! - projectId: String - fileSize: String - description: String! - uploadTime: String! - uploadedBy: String! - hash: String! - metadata: JSON -} - -type DataVersion { - id: String! - version: String! - contentId: String! - tag: String - updateDate: String! -} - -type OntologyTree { - id: String! - name: String! - routes: [OntologyRoute] - dataVersion: String! - metadata: JSON - deleted: Float -} - -type OntologyRoute { - id: String! - path: [String]! - name: String! - field: [String]! - visitRange: [String] -} - -input OntologyTreeInput { - name: String! - routes: [OntologyRouteInput] - metadata: JSON -} - -input OntologyRouteInput { - path: [String]! - name: String! - field: [String]! - visitRange: [String] -} - -type Study { - id: String! - name: String! - createdBy: String! - lastModified: Int! - currentDataVersion: Int - dataVersions: [DataVersion]! - description: String - type: STUDYTYPE - ontologyTrees: [OntologyTree] - # external to mongo documents: - jobs: [Job]! - projects: [Project]! - roles: [StudyOrProjectUserRole]! - # fields: [Field]! - files: [File]! - subjects: JSON! - visits: JSON! - numOfRecords: [Int]! - metadata: JSON -} - -type Project { - id: String! - studyId: String! - name: String! - dataVersion: DataVersion - summary: JSON - - #only admin - patientMapping: JSON! - - #external to mongo documents: - jobs: [Job]! - roles: [StudyOrProjectUserRole]! - iCanEdit: Boolean - fields: [Field]! - files: [File]! - metadata: JSON -} - -type Job { - id: String! - studyId: String! - projectId: String - jobType: String! - requester: String! - receivedFiles: [String]! - requestTime: Float! - status: String! - error: [String] - cancelled: Boolean - cancelledTime: Int - data: JSON -} - -enum LOG_TYPE { - SYSTEM_LOG - REQUEST_LOG -} - -enum USER_AGENT { - MOZILLA - OTHER -} - -enum LOG_STATUS { - SUCCESS - FAIL -} - -enum LOG_ACTION { - # SYSTEM - START_SERVER - STOP_SERVER - - # USER - GET_USERS - EDIT_USER - DELETE_USER - CREATE_USER - LOGIN_USER - WHO_AM_I - LOGOUT - REQUEST_USERNAME_OR_RESET_PASSWORD - RESET_PASSWORD - REQUEST_EXPIRY_DATE - - # KEY - REGISTER_PUBKEY - ISSUE_ACCESS_TOKEN - KEYPAIRGEN_SIGNATURE - RSA_SIGNER - LINK_USER_PUBKEY - - # ORGANISATION - GET_ORGANISATIONS - CREATE_ORGANISATION - DELETE_ORGANISATION - - # PROJECT - GET_PROJECT - # GET_PROJECT_PATIENT_MAPPING = 'GET_PROJECT_PATIENT_MAPPING', - EDIT_PROJECT_APPROVED_FIELDS - EDIT_PROJECT_APPROVED_FILES - CREATE_PROJECT - DELETE_PROJECT - SET_DATAVERSION_AS_CURRENT - SUBSCRIBE_TO_JOB_STATUS - - # STUDY | DATASET - DELETE_STUDY - GET_STUDY - GET_STUDY_FIELDS - CREATE_STUDY - EDIT_STUDY - CREATE_DATA_CURATION_JOB - CREATE_FIELD_CURATION_JOB - GET_DATA_RECORDS - GET_ONTOLOGY_TREE - CHECK_DATA_COMPLETE - CREATE_NEW_DATA_VERSION - UPLOAD_DATA_IN_ARRAY - DELETE_DATA_RECORDS - CREATE_NEW_FIELD - EDIT_FIELD - DELETE_FIELD - ADD_ONTOLOGY_TREE - DELETE_ONTOLOGY_TREE - - # STUDY & PROJECT - EDIT_ROLE - ADD_NEW_ROLE - REMOVE_ROLE - - # FILE - UPLOAD_FILE - DOWNLOAD_FILE - DELETE_FILE - - # QUERY - GET_QUERY - CREATE_QUERY - GET_QUERY_BY_ID - CREATE_QUERY_CURATION_JOB -} - -type Log { - id: String! - requesterName: String - requesterType: USERTYPE - userAgent: USER_AGENT - logType: LOG_TYPE - actionType: LOG_ACTION - actionData: JSON - time: Float! - status: LOG_STATUS - error: String -} - -type QueryEntry { - id: String! - queryString: JSON! - studyId: String! - projectId: String - requester: String! - status: String! - error: JSON - cancelled: Boolean - cancelledTime: Int - queryResult: String - data_requested: [String] - cohort: JSON - new_fields: JSON -} - -type GenericResponse { - successful: Boolean! - id: String, - code: String, - description: String -} - -enum JOB_STATUS { - finished - error - QUEUED - PROCESSING - CANCELLED -} - -type JobStatusChange_Subscription { - jobId: String! - studyId: String! - newStatus: JOB_STATUS! - errors: [String] -} - -input QueryObjInput { - queryString: JSON! - userId: String! - studyId: String! - projectId: String -} - -input CreateUserInput { - username: String! - type: USERTYPE - firstname: String! - lastname: String! - email: String! - description: String - organisation: String! - emailNotificationsActivated: Boolean - password: String!, - metadata: JSON -} - -input EditUserInput { - id: String! - username: String - type: USERTYPE - firstname: String - lastname: String - email: String - description: String - organisation: String - emailNotificationsActivated: Boolean - emailNotificationsStatus: JSON - password: String - expiredAt: Float - metadata: JSON -} - -input IntArrayChangesInput { - add: [Int]! - remove: [Int]! -} - -input StringArrayChangesInput { - add: [String]! - remove: [String]! -} - -input ValueCategoryInput { - code: String! - description: String -} - -input FieldInput { - fieldId: String! # start - fieldName: String! - tableName: String - dataType: FIELD_VALUE_TYPE! - possibleValues: [ValueCategoryInput] - unit: String - comments: String - metadata: JSON -} - -type SubjectDataRecordSummary { - subjectId: String! - visitId: String! - fieldId: String! - error: String! -} - -type Query { - # USER - whoAmI: User - getUsers(userId: String): [User] - validateResetPassword(encryptedEmail: String!, token: String!): GenericResponse - recoverSessionExpireTime: GenericResponse - - # ORGANISATION - getOrganisations(organisationId: String): [Organisation] - - # PUBLIC KEY AUTHENTICATION - getPubkeys(pubkeyId: String, associatedUserId: String): [Pubkey] - - # STUDY - getStudy(studyId: String!): Study - getProject(projectId: String!): Project - getStudyFields(studyId: String!, projectId: String, versionId: String): [Field] - getDataRecords(studyId: String!, queryString: JSON, versionId: String, projectId: String): JSON - getOntologyTree(studyId: String!, projectId: String, treeName: String, versionId: String): [OntologyTree] - getStandardization(studyId: String, projectId: String, type: String, versionId: String): [Standardization] - checkDataComplete(studyId: String!): [SubjectDataRecordSummary] - - # QUERY - getQueries(studyId: String, projectId: String): [QueryEntry] # only returns the queries that the user has access to. - getQueryById(queryId: String!): QueryEntry - - # PERMISSION - getGrantedPermissions(studyId: String, projectId: String): UserPermissions - - # LOG - getLogs(requesterName: String, requesterType: USERTYPE, logType: LOG_TYPE, actionType: LOG_ACTION, status: LOG_STATUS): [Log] -} - -type Mutation { - # USER - login(username: String!, password: String!, totp: String!, requestexpirydate: Boolean): User - logout: GenericResponse - requestUsernameOrResetPassword( - forgotUsername: Boolean!, - forgotPassword: Boolean!, - email: String, # only provide email if forgotUsername - username: String - ): GenericResponse - resetPassword(encryptedEmail: String!, token: String!, newPassword: String!): GenericResponse - createUser(user: CreateUserInput!): GenericResponse - requestExpiryDate(username: String, email: String): GenericResponse - - # PUBLIC KEY AUTHENTICATION - registerPubkey(pubkey: String!, signature: String!, associatedUserId: String): Pubkey - issueAccessToken(pubkey: String!, signature: String!): AccessToken - keyPairGenwSignature: KeyPairwSignature - rsaSigner(privateKey: String!, message: String): Signature - - # ORGANISATION - createOrganisation(name: String!, shortname: String, containOrg: String, metadata: OrganisationMetadataInput): Organisation - deleteOrganisation(id: String!): Organisation - - # APP USERS - editUser(user: EditUserInput!): User - deleteUser(userId: String!): GenericResponse - - # STUDY - createStudy(name: String!, description: String, type: STUDYTYPE!): Study - deleteStudy(studyId: String!): GenericResponse - editStudy(studyId: String!, description: String): Study - createNewDataVersion(studyId: String!, dataVersion: String!, tag: String): DataVersion - uploadDataInArray(studyId: String!, data: [DataClip]): [GenericResponse] - deleteDataRecords(studyId: String!, subjectIds: [String], visitIds: [String], fieldIds: [String]): [GenericResponse] - createNewField(studyId: String!, fieldInput: [FieldInput]!): [GenericResponse] - editField(studyId: String!, fieldInput: FieldInput!): Field - deleteField(studyId: String!, fieldId: String!): Field - createOntologyTree(studyId: String!, ontologyTree: OntologyTreeInput!): OntologyTree - deleteOntologyTree(studyId: String!, treeName: String!): GenericResponse - - # STANDARDIZATION - createStandardization(studyId: String!, standardization: StandardizationInput): Standardization - deleteStandardization(studyId: String!, type: String, field: [String]!): GenericResponse - - # PROJECT - createProject(studyId: String!, projectName: String!): Project - deleteProject(projectId: String!): GenericResponse - - # ACCESS MANAGEMENT - addRole(studyId: String!, projectId: String, roleName: String!): StudyOrProjectUserRole - editRole(roleId: String!, name: String, description: String, permissionChanges: JSON, userChanges: StringArrayChangesInput): StudyOrProjectUserRole - removeRole(roleId: String!): GenericResponse - - # FILES - uploadFile(studyId: String!, description: String!, file: Upload!, fileLength: BigInt, hash: String): File - deleteFile(fileId: String!): GenericResponse - - # QUERY - createQuery(query: QueryObjInput!): QueryEntry - - # CURATION - createDataCurationJob(file: [String]!, studyId: String!): [Job] - createFieldCurationJob(file: String!, studyId: String!, tag: String!): Job - createQueryCurationJob(queryId: [String], studyId: String, projectId: String): Job - setDataversionAsCurrent(studyId: String!, dataVersionId: String!): Study - -} - -type Subscription { - subscribeToJobStatusChange(studyId: String!): JobStatusChange_Subscription -} -`; diff --git a/packages/itmat-interface/src/index.ts b/packages/itmat-interface/src/index.ts index e37cde715..40012f178 100644 --- a/packages/itmat-interface/src/index.ts +++ b/packages/itmat-interface/src/index.ts @@ -5,13 +5,13 @@ import http from 'http'; import ITMATInterfaceRunner from './interfaceRunner'; import config from './utils/configManager'; import { db } from './database/database'; -import { IUser, userTypes } from '@itmat-broker/itmat-types'; +import { IUser, enumUserTypes } from '@itmat-broker/itmat-types'; import { mailer } from './emailer/emailer'; let interfaceRunner = new ITMATInterfaceRunner(config); let interfaceSockets: Socket[] = []; let interfaceServer: http.Server; -let interfaceRouter: Express; +let interfaceRouter: Express | undefined; function serverStart() { console.info(`Starting api server ${process.pid} ...`); @@ -29,7 +29,7 @@ function serverStart() { socket.setKeepAlive(true); socket.setNoDelay(true); socket.setTimeout(0); - (socket as any).timeout = 0; + (socket as unknown as Record)['timeout'] = 0; interfaceSockets.push(socket); }) .on('error', (error) => { @@ -42,9 +42,9 @@ function serverStart() { // notice users of expiration await emailNotification(); - const interfaceRouterProxy = itmatRouter.getProxy(); - if (interfaceRouterProxy?.upgrade) - interfaceServer.on('upgrade', interfaceRouterProxy?.upgrade); + // const interfaceRouterProxy = itmatRouter.getProxy(); + // if (interfaceRouterProxy?.upgrade) + // interfaceServer.on('upgrade', interfaceRouterProxy?.upgrade); }).catch((error) => { console.error('An error occurred while starting the ITMAT core.', error); @@ -67,9 +67,7 @@ function serverSpinning() { interfaceSockets = []; interfaceServer.close(() => { console.info(`Shuting down api server ${process.pid} ...`); - interfaceRouter?.on('close', () => { - serverStart(); - }) || serverStart(); + serverStart(); }); } else { serverStart(); @@ -78,6 +76,7 @@ function serverSpinning() { serverSpinning(); +// eslint-disable-next-line @typescript-eslint/no-explicit-any declare const module: any; if (module.hot) { module.hot.accept('./index', serverSpinning); @@ -89,19 +88,15 @@ if (module.hot) { async function emailNotification() { const now = Date.now().valueOf(); const threshold = now + 7 * 24 * 60 * 60 * 1000; - // update info if not set before - await db.collections!.users_collection.updateMany({ deleted: null, emailNotificationsStatus: null }, { - $set: { emailNotificationsStatus: { expiringNotification: false } } - }); - const users = await db.collections!.users_collection.find({ + const users = await db.collections.users_collection.find({ 'expiredAt': { $lte: threshold, $gt: now }, - 'type': { $ne: userTypes.ADMIN }, + 'type': { $ne: enumUserTypes.ADMIN }, 'emailNotificationsActivated': true, 'emailNotificationsStatus.expiringNotification': false, - 'deleted': null + 'life.deletedTime': null }).toArray(); for (const user of users) { await mailer.sendMail({ @@ -123,9 +118,9 @@ async function emailNotification() {

` }); - await db.collections!.users_collection.findOneAndUpdate({ id: user.id }, { + await db.collections.users_collection.findOneAndUpdate({ id: user.id }, { $set: { emailNotificationsStatus: { expiringNotification: true } } }); } - setInterval(emailNotification, 24 * 60 * 60 * 1000); + setInterval(() => { emailNotification().catch(() => { return; }); }, 24 * 60 * 60 * 1000); } diff --git a/packages/itmat-interface/src/interfaceRunner.ts b/packages/itmat-interface/src/interfaceRunner.ts index 112a8edcc..02d6a6f86 100644 --- a/packages/itmat-interface/src/interfaceRunner.ts +++ b/packages/itmat-interface/src/interfaceRunner.ts @@ -4,7 +4,7 @@ import { objStore } from './objStore/objStore'; import { MongoClient } from 'mongodb'; import { Router } from './server/router'; import { Runner } from './server/server'; -import { pubsub, subscriptionEvents } from './graphql/pubsub'; +import { dmpWebDav } from './webdav/webdav'; class ITMATInterfaceRunner extends Runner { @@ -17,34 +17,16 @@ class ITMATInterfaceRunner extends Runner { * @return {Promise} Resolve to a native Express.js router ready to use on success. * In case of error, an ErrorStack is rejected. */ - public start(): Promise { + public async start(): Promise { + // eslint-disable-next-line @typescript-eslint/no-this-alias const _this = this; return new Promise((resolve, reject) => { // Operate database migration if necessary db.connect(this.config.database, MongoClient) - .then(() => objStore.connect(this.config.objectStore)) + .then(async () => objStore.connect(this.config.objectStore)) + .then(async () => this.config?.useWebdav && dmpWebDav.connect(this.config)) .then(async () => { - - const jobChangestream = db.collections!.jobs_collection.watch([ - { $match: { operationType: { $in: ['update', 'insert'] } } } - ], { fullDocument: 'updateLookup' }); - jobChangestream.on('change', data => { - if (data.operationType === 'update' && - data.updateDescription && - data.updateDescription.updatedFields && - data.updateDescription.updatedFields.status - ) { - pubsub.publish(subscriptionEvents.JOB_STATUS_CHANGE, { - subscribeToJobStatusChange: { - jobId: data.fullDocument?.id, - studyId: data.fullDocument?.studyId, - newStatus: data.fullDocument?.status, - errors: data.fullDocument?.status === 'error' ? data.fullDocument.error : null - } - }); - } - }); _this.router = new Router(this.config); await _this.router.init(); diff --git a/packages/itmat-interface/src/log/logPlugin.ts b/packages/itmat-interface/src/log/logPlugin.ts deleted file mode 100644 index 3d66d9f9f..000000000 --- a/packages/itmat-interface/src/log/logPlugin.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { db } from '../database/database'; -import { v4 as uuid } from 'uuid'; -import { LOG_TYPE, LOG_ACTION, LOG_STATUS, USER_AGENT, userTypes } from '@itmat-broker/itmat-types'; - -// only requests in white list will be recorded -export const logActionRecordWhiteList = Object.keys(LOG_ACTION); - -// only requests in white list will be recorded -export const logActionShowWhiteList = Object.keys(LOG_ACTION); - -export class LogPlugin { - public async serverWillStartLogPlugin(): Promise { - await db.collections!.log_collection.insertOne({ - id: uuid(), - requesterName: userTypes.SYSTEM, - requesterType: userTypes.SYSTEM, - logType: LOG_TYPE.SYSTEM_LOG, - actionType: LOG_ACTION.startSERVER, - actionData: JSON.stringify({}), - time: Date.now(), - status: LOG_STATUS.SUCCESS, - errors: '', - userAgent: USER_AGENT.OTHER - }); - return null; - } - - public async requestDidStartLogPlugin(requestContext: any): Promise { - if (!logActionRecordWhiteList.includes(requestContext.operationName)) { - return null; - } - if ((LOG_ACTION as any)[requestContext.operationName] === undefined || (LOG_ACTION as any)[requestContext.operationName] === null) { - return null; - } - await db.collections!.log_collection.insertOne({ - id: uuid(), - requesterName: requestContext.contextValue?.req?.user?.username ?? 'NA', - requesterType: requestContext.contextValue?.req?.user?.type ?? userTypes.SYSTEM, - userAgent: (requestContext.contextValue.req.headers['user-agent'] as string)?.startsWith('Mozilla') ? USER_AGENT.MOZILLA : USER_AGENT.OTHER, - logType: LOG_TYPE.REQUEST_LOG, - actionType: (LOG_ACTION as any)[requestContext.operationName], - actionData: JSON.stringify(ignoreFieldsHelper(requestContext.request.variables, requestContext.operationName)), - time: Date.now(), - status: requestContext.errors === undefined ? LOG_STATUS.SUCCESS : LOG_STATUS.FAIL, - errors: requestContext.errors === undefined ? '' : requestContext.errors[0].message - }); - return null; - } -} - -function ignoreFieldsHelper(dataObj: any, operationName: string) { - if (operationName === 'login') { - delete dataObj['password']; - delete dataObj['totp']; - } else if (operationName === 'createUser') { - delete dataObj['user']['password']; - } else if (operationName === 'registerPubkey') { - delete dataObj['signature']; - } else if (operationName === 'issueAccessToken') { - delete dataObj['signature']; - } else if (operationName === 'editUser') { - delete dataObj['user']['password']; - } else if (operationName === 'uploadDataInArray') { - if (Array.isArray(dataObj['data'])) { - for (let i = 0; i < dataObj['data'].length; i++) { - // only keep the fieldId - delete dataObj['data'][i].value; - delete dataObj['data'][i].file; - delete dataObj['data'][i].metadata; - } - } - } else if (operationName === 'uploadFile') { - delete dataObj['file']; - } - return dataObj; -} - -export const logPlugin = Object.freeze(new LogPlugin()); diff --git a/packages/itmat-interface/src/lxd/index.ts b/packages/itmat-interface/src/lxd/index.ts new file mode 100644 index 000000000..5c878912c --- /dev/null +++ b/packages/itmat-interface/src/lxd/index.ts @@ -0,0 +1,863 @@ +import httpProxy from 'http-proxy'; +import { WebSocketServer, WebSocket, RawData} from 'ws'; +import { NextFunction, Request, Response} from 'express'; +import http from 'node:http'; +import * as net from 'net'; // For WebSocket handling (net.Socket) +import * as url from 'url'; // For handling target URLs +import qs from 'qs'; +import {LxdManager, InstanceCore} from '@itmat-broker/itmat-cores'; +import { Logger } from '@itmat-broker/itmat-commons'; + + +// const textDecoder = new TextDecoder('utf-8'); + +export const registerContainSocketServer = (server: WebSocketServer, lxdManager: LxdManager) => { + + server.on('connection', (clientSocket, req) => { + clientSocket.pause(); + let containerSocket: WebSocket | undefined; + const query = qs.parse(req.url?.split('?')[1] || ''); + const operationId = query['o']?.toString() || ''; + const operationSecret = query['s']?.toString() || ''; + const clientMessageBuffers: Array<[Buffer, boolean]> = []; + const containerMessageBuffers: Array<[Buffer, boolean]> = []; + + const flushClientMessageBuffers = () => { + if (containerSocket && containerSocket.readyState === WebSocket.OPEN) { + + const curr = clientMessageBuffers[0]; + if (curr) { + containerSocket.send(curr[0], { binary: curr[1] }, (err) => { + if (err) { + Logger.error('Error sending message to container' + err); + } else { + clientMessageBuffers.shift(); + if (clientMessageBuffers.length > 0) + flushClientMessageBuffers(); + } + }); + } + } + }; + + const flushContainerMessageBuffers = () => { + if (clientSocket.readyState === WebSocket.OPEN) { + const curr = containerMessageBuffers[0]; + if (curr) { + clientSocket.send(curr[0], { binary: curr[1] }, (err) => { + if (err) { + Logger.error(`Error sending message to client ${JSON.stringify(err)}`); + } else { + containerMessageBuffers.shift(); + if (containerMessageBuffers.length > 0) + flushContainerMessageBuffers(); + } + }); + } + } + }; + // send test message to client + clientSocket.on('open', () => { + clientSocket.send('test message'); + }); + + clientSocket.on('message', (message, isBinary) => { + const tuple: [Buffer, boolean] = [Buffer.from(message as ArrayBuffer), isBinary]; + clientMessageBuffers.push(tuple); + flushClientMessageBuffers(); + }); + + clientSocket.on('close', (code, reason) => { + if (containerSocket?.readyState === WebSocket.OPEN) + containerSocket?.close(4110, `The client socket was closed with code ${code}: ${reason.toString()}`); + }); + + clientSocket.on('error', () => { + if (containerSocket?.readyState === WebSocket.OPEN) + containerSocket?.close(4115, 'The client socket errored'); + }); + + try { + containerSocket = lxdManager.getOperationSocket(operationId, operationSecret); + + containerSocket.pause(); + containerSocket.on('open', () => { + flushClientMessageBuffers(); + }); + + containerSocket.on('message', (message: ArrayBuffer | Uint8Array[], isBinary: boolean) => { + const tuple: [Buffer, boolean] = [Buffer.from(message as ArrayBuffer), isBinary]; + containerMessageBuffers.push(tuple); + flushContainerMessageBuffers(); + }); + + containerSocket.on('close', (code, reason) => { + flushContainerMessageBuffers(); + if (clientSocket?.readyState === WebSocket.OPEN) + clientSocket?.close(4010, `The container socket was closed with code${code}: ${reason.toString()}`); + }); + + containerSocket.on('error', () => { + if (clientSocket?.readyState === WebSocket.OPEN) + clientSocket?.close(4015, 'The container socket errored'); + }); + + containerSocket.resume(); + clientSocket.resume(); + + } catch (e) { + Logger.error(`Failed to create container WebSocket: ${JSON.stringify(e)}`); + if (clientSocket?.readyState === WebSocket.OPEN) + clientSocket?.close(4015, 'The container socket failed to open'); + } + }); + server.on('error', (err) => { + Logger.error(`LXD socket broker errored: ${JSON.stringify(err)}`); + }); +}; + + + +export const registerJupyterSocketServer = (server: WebSocketServer, instanceCore: InstanceCore) => { + + server.on('connection', (clientSocket, req) => { + const handleConnection = async () => { + + clientSocket.pause(); + clientSocket.binaryType = 'arraybuffer'; + + // Extract subprotocol from the client's WebSocket request + const clientSubprotocol = req.headers['sec-websocket-protocol'] || ''; + + let containerSocket: WebSocket | undefined; + + if ( !req.url ||!req.url?.startsWith('/jupyter')){ + Logger.log(`Error request ${req.url}`); + clientSocket.close(4004, 'Invalid Jupyter path'); + return; + } + + // get the instance_id from the url + const instance_id = req.url?.split('/')[2] ?? ''; + if (!instance_id) { + clientSocket.close(4004, 'Missing instance ID'); + return; + } + + const clientMessageBuffers: Array<[RawData, boolean]> = []; + const containerMessageBuffers: Array<[RawData, boolean]> = []; + + let ip = ''; // Initialize with a default value + let port = 0; // Initialize with a default value + try { + // Get the container IP according to the instance_id + const result = await getInstanceTarget(instance_id, instanceCore); + ({ ip, port } = result); // Wrap destructuring in parentheses + } catch (error) { + Logger.error(`Failed to retrieve container IP: ${JSON.stringify(error)}`); + clientSocket.close(4013, 'Failed to get container IP'); + return; + } + + const targetUrl = `ws://${ip}:${port}${req.url}`; + const flushClientMessageBuffers = () => { + + if (containerSocket && containerSocket.readyState === WebSocket.OPEN) { + + if (clientMessageBuffers.length > 0) { + const buffer = clientMessageBuffers.shift(); + if (buffer !== undefined) { + const [message, isBinary] = buffer; + containerSocket.send(message, { binary: isBinary }, (err) => { + if (err) { + console.error('Error sending message to container:', err); + } + }); + if (clientMessageBuffers.length > 0) + flushClientMessageBuffers(); + } else { + console.error('Unexpected undefined buffer'); + } + } + } + }; + + const flushContainerMessageBuffers = () => { + if (clientSocket.readyState === WebSocket.OPEN) { + + if (containerMessageBuffers.length > 0) { + const buffer = containerMessageBuffers.shift(); + if (buffer !== undefined) { + const [message, isBinary] = buffer; + clientSocket.send(message, { binary: isBinary }, (err) => { + if (err) { + console.error('Error sending message to client:', err); + } + }); + if (containerMessageBuffers.length > 0) + flushContainerMessageBuffers(); + } + } + + } + }; + clientSocket.on('message', (message, isBinary) => { + // Buffer the message and send it to the containerSocket + clientMessageBuffers.push([message, isBinary]); + flushClientMessageBuffers(); + }); + clientSocket.on('open', () => { + clientSocket.ping(); + } ); + + clientSocket.on('error', (error) => { + Logger.error(`clientSocket error: ${JSON.stringify(error, null, 2)}`); + if (containerSocket && containerSocket.readyState === WebSocket.OPEN) { + containerSocket.close(4000, 'Client WebSocket encountered an error.'); + } + }); + + clientSocket.on('close', (code, reason) => { + console.log('clientSocket closed. Code:', code, 'Reason:', reason.toString()); + if (containerSocket && containerSocket.readyState === WebSocket.OPEN) { + containerSocket.close(4001, `Client WebSocket closed. Code: ${code}`); + } + // if is connecting, then set a interval to close the container socket + else if (containerSocket && containerSocket.readyState === WebSocket.CONNECTING) { + const interval = setInterval(() => { + if (containerSocket?.readyState === WebSocket.OPEN) { + containerSocket?.close(4001, `Client WebSocket closed. Code: ${code}`); + clearInterval(interval); + } + }, 1000); + } + }); + + + try { + + // Determine the protocol for the Origin header + const originProtocol = req.headers['x-forwarded-proto'] === 'https' ? 'wss' : 'ws'; + + // Create WebSocket connection to the backend (container) + const headers = { + 'Sec-WebSocket-Protocol': clientSubprotocol, // Pass client's subprotocol if provided + 'Sec-WebSocket-Key': req.headers['sec-websocket-key'], + 'Sec-WebSocket-Version': req.headers['sec-websocket-version'], + 'Sec-WebSocket-Extensions': req.headers['sec-websocket-extensions'], + 'pragma': req.headers['pragma'], + 'cache-control': req.headers['cache-control'], + 'upgrade': req.headers['upgrade'], + 'connection': req.headers['connection'], + 'Accept-Encoding': req.headers['accept-encoding'], + 'Accept-Language': req.headers['accept-language'], + 'User-Agent': req.headers['user-agent'], + 'Host': `${ip}:${port}`, // Set the Host header + 'Origin': `${originProtocol}://${ip}:${port}` // Set the Origin header + }; + + // Create WebSocket connection to the backend (container) + containerSocket = new WebSocket(targetUrl, + clientSubprotocol ? [clientSubprotocol] : [], + { headers }); + containerSocket.binaryType = 'arraybuffer'; + + containerSocket.pause(); + + + // Set maximum listeners to prevent memory leaks + containerSocket.setMaxListeners(20); + clientSocket.setMaxListeners(20); + + containerSocket.on('message', (message, isBinary) => { + containerMessageBuffers.push([message, isBinary]); + flushContainerMessageBuffers(); + }); + + containerSocket.on('open', () => { + flushClientMessageBuffers(); + }); + + containerSocket.on('error', (error) => { + Logger.error(`Container WebSocket error: ${JSON.stringify(error, null, 2)}`); + if (clientSocket && clientSocket.readyState === WebSocket.OPEN) { + clientSocket.close(4002, 'Container WebSocket encountered an error.'); + } + }); + + containerSocket.on('close', (code, reason) => { + flushContainerMessageBuffers(); + if (clientSocket?.readyState === WebSocket.OPEN) + clientSocket?.close(4010, `The container socket was closed with code${code}: ${reason.toString()}`); + }); + + containerSocket.resume(); + clientSocket.resume(); + + + } catch (error) { + console.error('Failed to create container WebSocket:', error); + if (clientSocket?.readyState === WebSocket.OPEN) + clientSocket?.close(4015, 'The container socket failed to open'); + if (containerSocket?.readyState === WebSocket.OPEN) + containerSocket?.close(4015, 'The container socket failed to open'); + } + }; + // Call the async function + handleConnection().catch((error) => { + Logger.error(`Error handling jupyter connection: ${JSON.stringify(error)}`); + clientSocket.close(); + }); + }); + server.on('error', (err) => { + Logger.error(`LXD Jupyter socket broker errored: ${JSON.stringify(err)}`); + }); +}; + + + +// Enhanced ProxyCache interface with lastUsed timestamp +interface ProxyCache { + [instance_id: string]: { + proxy: httpProxy; + ip: string; + port: number; + lastUsed: number; // timestamp when last used + }; +} + +// How long a proxy should remain cached without being used (30 minutes) +const PROXY_CACHE_TTL = 30 * 60 * 1000; + +const proxyCache: ProxyCache = {}; +const vncProxyCache: ProxyCache = {}; + +// Track our cleanup interval ID to prevent duplicate intervals +let cleanupIntervalId: NodeJS.Timeout | null = null; + +/** + * Cleanup function for proxy caches + * Removes entries that haven't been used for PROXY_CACHE_TTL milliseconds + */ +function cleanupProxyCaches() { + const now = Date.now(); + + // Clean HTTP proxy cache + Object.entries(proxyCache).forEach(([instanceId, cacheEntry]) => { + if (now - cacheEntry.lastUsed > PROXY_CACHE_TTL) { + Logger.log(`Cleaning up stale proxy cache for instance ${instanceId}`); + // Close the proxy to clean up any open connections + cacheEntry.proxy.close(); + delete proxyCache[instanceId]; + } + }); + + // Clean VNC proxy cache + Object.entries(vncProxyCache).forEach(([instanceId, cacheEntry]) => { + if (now - cacheEntry.lastUsed > PROXY_CACHE_TTL) { + Logger.log(`Cleaning up stale VNC proxy cache for instance ${instanceId}`); + cacheEntry.proxy.close(); + delete vncProxyCache[instanceId]; + } + }); +} + +/** + * Initialize the cleanup interval if it hasn't been initialized yet. + * This function is idempotent and can be called multiple times safely. + */ +export function initializeProxyCacheCleanup() { + if (!cleanupIntervalId) { + cleanupIntervalId = setInterval(cleanupProxyCaches, 30 * 60 * 1000); + Logger.log('Proxy cache cleanup interval initialized'); + + // Make sure we clear the interval when the process exits + process.once('SIGTERM', stopProxyCacheCleanup); + process.once('SIGINT', stopProxyCacheCleanup); + } +} + +/** + * Stop the cleanup interval explicitly if needed + */ +export function stopProxyCacheCleanup() { + if (cleanupIntervalId) { + clearInterval(cleanupIntervalId); + cleanupIntervalId = null; + Logger.log('Proxy cache cleanup interval stopped'); + + // Close all active proxies + Object.entries(proxyCache).forEach(([instanceId, entry]) => { + entry.proxy.close(); + Logger.log(`Closed Jupyter proxy for ${instanceId} on shutdown`); + }); + + Object.entries(vncProxyCache).forEach(([instanceId, entry]) => { + entry.proxy.close(); + Logger.log(`Closed VNC proxy for ${instanceId} on shutdown`); + }); + } +} + +// Helper functions to update cache entries with current timestamp +function updateProxyCache(instanceId: string, proxy: httpProxy, ip: string, port: number) { + proxyCache[instanceId] = { proxy, ip, port, lastUsed: Date.now() }; +} + +function updateVncProxyCache(instanceId: string, proxy: httpProxy, ip: string, port: number) { + vncProxyCache[instanceId] = { proxy, ip, port, lastUsed: Date.now() }; +} + +// Verify if the user has access to the specified instance +async function verifyInstanceAccess(instance_id: string, user_id: string | undefined, instanceCore: InstanceCore): Promise { + if (!user_id) { + return false; + } + + try { + // Get the instance information from the database + const instance = await instanceCore.getInstanceById(instance_id); + + // Verify that the requesting user owns this instance + if (instance.userId !== user_id) { + Logger.warn(`User ${user_id} attempted to access instance ${instance_id} owned by ${instance.userId}`); + return false; + } + + return true; + } catch (error) { + Logger.error(`Error verifying instance ownership: ${error instanceof Error ? error.message : String(error)}`); + return false; + } +} + +// Replace the existing getInstanceTarget function +const getInstanceTarget = async (instance_id: string, instanceCore: InstanceCore, user_id?: string | undefined) => { + // Get the instance information from the database + const instance = await instanceCore.getInstanceById(instance_id); + + if (!instance) { + throw new Error('Failed to retrieve instance'); + } + + // If we have both hostIp and hostMapPort, use those directly + if (instance.lxdState?.hostIp && instance.hostMapPort) { + + return { + ip: instance.lxdState.hostIp, + port: instance.hostMapPort + }; + } + + + + // Fall back to the previous approach if no host IP or mapped port is available + const containerIP = await instanceCore.getContainerIP(instance_id, user_id); + if (!containerIP || !containerIP.ip) { + throw new Error('Failed to retrieve container IP'); + } + + return { + ip: containerIP.ip, + port: containerIP.port || 8888 + }; +}; + +// Middleware to handle HTTP and WebSocket proxying for Jupyter instances +export const jupyterProxyMiddleware = async (req: Request & { user?: { id: string } }, res: Response, next: NextFunction, instanceCore: InstanceCore) => { + + const instance_id = req.params['instance_id']; + + if (!instance_id) { + if (!res.headersSent) { + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('Instance not found'); + } + return; + } + + // Verify instance ownership + const hasAccess = await verifyInstanceAccess(instance_id, req.user?.id, instanceCore); + if (!hasAccess) { + if (!res.headersSent) { + res.writeHead(403, { 'Content-Type': 'text/plain' }); + res.end('Access denied'); + } + return; + } + + try { + // Retrieve the instance-specific target if not already cached + if (!proxyCache[instance_id]) { + const { ip, port } = await getInstanceTarget(instance_id, instanceCore, req.user?.id); + if (!ip) { + throw new Error(`Could not determine IP for instance ${instance_id}`); + } + + const target = `http://${ip}:${port}`; + Logger.log(`Creating new proxy for instance ${instance_id} to ${target}`); + + // Create proxy instance + const proxy = httpProxy.createProxyServer({ + target, + xfwd: true, + autoRewrite: true, + changeOrigin: true + }); + + // Add event listeners (same as before) + proxy.on('proxyReq', (proxyReq: http.ClientRequest, req: http.IncomingMessage & { body?: unknown }) => { + if (req.headers.upgrade && req.headers.upgrade.toLowerCase() === 'websocket') { + return; + } + + const instancePrefix = `/jupyter/${instance_id}`; + proxyReq.path = (req.url || '').startsWith(instancePrefix) + ? (req.url || '') + : `${instancePrefix}${req.url || ''}`; + + // Set the request headers + proxyReq.setHeader('Host', `${ip}:${port}`); + proxyReq.setHeader('Origin', `http://${ip}:${port}`); + + if (req.body && Object.keys(req.body).length) { + const contentType = proxyReq.getHeader('Content-Type'); + + const writeBody = (bodyData: string) => { + proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData)); + proxyReq.write(bodyData); + proxyReq.end(); + }; + + if (contentType === 'application/json') { + writeBody(JSON.stringify(req.body)); + } else if (contentType === 'application/x-www-form-urlencoded') { + writeBody(qs.stringify(req.body)); + } + } + }); + + proxy.on('error', (err: Error, req: http.IncomingMessage, res: http.ServerResponse | net.Socket, target?: string | url.UrlObject) => { + Logger.error(`Proxy error for target ${JSON.stringify(target)}: error ${err.message}`); + + if (res instanceof http.ServerResponse && !res.headersSent) { + res.writeHead(500, { 'Content-Type': 'text/plain' }); + res.end('Proxy Error'); + } else if (res instanceof net.Socket) { + res.end('Proxy Error'); + } + }); + + // Store in cache with timestamp using helper function + updateProxyCache(instance_id, proxy, ip, port); + + // Make sure cleanup is initialized + initializeProxyCacheCleanup(); + } else { + // Update the lastUsed timestamp + proxyCache[instance_id].lastUsed = Date.now(); + } + + // Handle the request + proxyCache[instance_id].proxy.web(req, res); + return; // Add explicit return for consistency with vncProxyMiddleware + } catch (error) { + Logger.error(`Jupyter proxy error: ${JSON.stringify(error)}`); + if (!res.headersSent) { + res.status(500).send('Proxy setup error occurred. Please contact an administrator.'); + } + next(error); // Consider if you want to call next with the error or just return + } + +}; + + +export const vncProxyMiddleware = async ( + req: Request & { user?: { id: string } }, + res: Response, + next: NextFunction, + instanceCore: InstanceCore +) => { + const instance_id = req.params['instance_id']; + + if (!instance_id) { + if (!res.headersSent) { + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('Instance not found'); + } + return; + } + + // Verify instance ownership + const hasAccess = await verifyInstanceAccess(instance_id, req.user?.id, instanceCore); + if (!hasAccess) { + if (!res.headersSent) { + res.writeHead(403, { 'Content-Type': 'text/plain' }); + res.end('Access denied: You do not have permission to access this instance'); + } + return; + } + + try { + // Get or create proxy instance + if (!vncProxyCache[instance_id]) { + const { ip, port } = await getInstanceTarget(instance_id, instanceCore, req.user?.id); + if (!ip) { + throw new Error(`Could not determine IP for instance ${instance_id}`); + } + + const target = `http://${ip}:${port}`; + Logger.log(`Creating new VNC proxy for instance ${instance_id} to ${target}`); + + const proxy = httpProxy.createProxyServer({ + target: target, + xfwd: true, + autoRewrite: true, + changeOrigin: true, + secure: false + }); + + // Add event listeners (same as before) + proxy.on('proxyReq', (proxyReq: http.ClientRequest, req: http.IncomingMessage & { body?: unknown }) => { + const originalUrl = req.url || ''; + const instancePrefix = `/matlab/${instance_id}`; + + // If `originalUrl` is missing that prefix, add it + if (!originalUrl.startsWith(instancePrefix)) { + proxyReq.path = `${instancePrefix}${originalUrl}`; + } else { + proxyReq.path = originalUrl; + } + + // Set the request headers + proxyReq.setHeader('Host', `${ip}:${port}`); + proxyReq.setHeader('Origin', `http://${ip}:${port}`); + + if (req.body && Object.keys(req.body).length) { + const contentType = proxyReq.getHeader('Content-Type'); + + const writeBody = (bodyData: string) => { + proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData)); + proxyReq.write(bodyData); + proxyReq.end(); + }; + + if (contentType === 'application/json') { + writeBody(JSON.stringify(req.body)); + } else if (contentType === 'application/x-www-form-urlencoded') { + writeBody(qs.stringify(req.body)); + } + } + }); + + proxy.on('error', (err: Error) => { + Logger.error(`VNC Proxy error: ${err.message}`); + if (!res.headersSent) { + res.status(502).send('VNC Proxy Error'); + } + }); + + // Store with timestamp using helper function + updateVncProxyCache(instance_id, proxy, ip, port); + + // Make sure cleanup is initialized + initializeProxyCacheCleanup(); + } else { + // Update timestamp on use + vncProxyCache[instance_id].lastUsed = Date.now(); + } + + // Handle the request + vncProxyCache[instance_id].proxy.web(req, res); + return; + } catch (error) { + Logger.error(`VNC proxy error: ${error}`); + if (!res.headersSent) { + res.status(500).send('Proxy setup error occurred. Please contact an administrator.'); + } + next(error); + return; + } +}; + +/** + * Function to explicitly clear caches when instances are terminated + */ +export function clearProxyCacheForInstance(instanceId: string): void { + if (proxyCache[instanceId]) { + proxyCache[instanceId].proxy.close(); + delete proxyCache[instanceId]; + Logger.log(`Explicitly cleared Jupyter proxy cache for instance ${instanceId}`); + } + + if (vncProxyCache[instanceId]) { + vncProxyCache[instanceId].proxy.close(); + delete vncProxyCache[instanceId]; + Logger.log(`Explicitly cleared VNC proxy cache for instance ${instanceId}`); + } +} + +// WebSocket handler for VNC +export const registerVNCSocketServer = (server: WebSocketServer, instanceCore: InstanceCore) => { + server.on('connection', (clientSocket, req) => { + const handleConnection = async () => { + clientSocket.pause(); + clientSocket.binaryType = 'arraybuffer'; + + // Extract subprotocol from the client's WebSocket request + const clientSubprotocol = req.headers['sec-websocket-protocol'] || ''; + + + let containerSocket: WebSocket | undefined; + + if (!req.url?.startsWith('/matlab')) { + Logger.log(`error request ${req.url}`); + clientSocket.close(4004, 'Invalid VNC path'); + return; + } + + const instance_id = req.url.split('/')[2]; + if (!instance_id) { + clientSocket.close(4004, 'Missing instance ID'); + return; + } + + const clientMessageBuffers: Array<[RawData, boolean]> = []; + const containerMessageBuffers: Array<[RawData, boolean]> = []; + + + let ip = ''; // Initialize with a default value + let port = 0; // Initialize with a default value + try { + const result = await getInstanceTarget(instance_id, instanceCore); + + // ip = 'localhost'; + ({ ip, port } = result); + } catch (error) { + Logger.error(`Failed to retrieve container IP: ${JSON.stringify(error)}`); + clientSocket.close(4013, 'Failed to get container IP'); + return; + } + + // const targetUrl = `ws://${ip}:8888`; + // also need to combine the path of the original request + const targetUrl = `ws://${ip}:${port}${req.url}`; + + const flushClientMessageBuffers = () => { + if (containerSocket?.readyState === WebSocket.OPEN && clientMessageBuffers.length > 0) { + const buffer = clientMessageBuffers.shift(); + if (buffer) { + const [message, isBinary] = buffer; + containerSocket.send(message, { binary: isBinary }, (err) => { + if (err) console.error('Error sending to container:', err); + }); + if (clientMessageBuffers.length > 0) flushClientMessageBuffers(); + } + } + }; + + const flushContainerMessageBuffers = () => { + if (clientSocket.readyState === WebSocket.OPEN && containerMessageBuffers.length > 0) { + const buffer = containerMessageBuffers.shift(); + if (buffer) { + const [message, isBinary] = buffer; + clientSocket.send(message, { binary: isBinary }, (err) => { + if (err) console.error('Error sending to client:', err); + }); + if (containerMessageBuffers.length > 0) flushContainerMessageBuffers(); + } + } + }; + + try { + + + // Determine the protocol for the Origin header + const originProtocol = req.headers['x-forwarded-proto'] === 'https' ? 'wss' : 'ws'; + + // Create WebSocket connection to the backend (container) + const headers = { + 'Sec-WebSocket-Protocol': clientSubprotocol, // Pass client's subprotocol if provided + 'Sec-WebSocket-Key': req.headers['sec-websocket-key'], + 'Sec-WebSocket-Version': req.headers['sec-websocket-version'], + 'Sec-WebSocket-Extensions': req.headers['sec-websocket-extensions'], + 'pragma': req.headers['pragma'], + 'cache-control': req.headers['cache-control'], + 'upgrade': req.headers['upgrade'], + 'connection': req.headers['connection'], + 'Accept-Encoding': req.headers['accept-encoding'], + 'Accept-Language': req.headers['accept-language'], + 'User-Agent': req.headers['user-agent'], + 'Host': `${ip}:${port}`, // Set the Host header + 'Origin': `${originProtocol}://${ip}:${port}` // Set the Origin header + }; + + + // containerSocket = new WebSocket(targetUrl, { headers }); + containerSocket = new WebSocket(targetUrl, + clientSubprotocol ? [clientSubprotocol] : [], + { headers }); + + containerSocket.binaryType = 'arraybuffer'; + containerSocket.pause(); + + containerSocket.setMaxListeners(20); + clientSocket.setMaxListeners(20); + + clientSocket.on('message', (message, isBinary) => { + clientMessageBuffers.push([message, isBinary]); + flushClientMessageBuffers(); + }); + + containerSocket.on('message', (message, isBinary) => { + containerMessageBuffers.push([message, isBinary]); + flushContainerMessageBuffers(); + }); + + clientSocket.on('close', (code, reason) => { + console.log('VNC client closed:', code, reason.toString()); + if (containerSocket?.readyState === WebSocket.OPEN) { + containerSocket.close(4001, `Client closed: ${code}`); + } + }); + + containerSocket.on('close', (code, reason) => { + flushContainerMessageBuffers(); + if (clientSocket?.readyState === WebSocket.OPEN) { + clientSocket.close(4010, `Container closed: ${code}: ${reason}`); + } + }); + + containerSocket.on('error', (error) => { + Logger.error(`VNC container error: ${JSON.stringify(error, null, 2)}`); + clientSocket.close(4011, 'Container error'); + }); + + clientSocket.on('error', (error) => { + Logger.error(`VNC client error: ${JSON.stringify(error, null, 2)}`); + containerSocket?.close(4012, 'Client error'); + }); + + containerSocket.on('open', () => { + flushClientMessageBuffers(); + containerSocket?.resume(); + clientSocket.resume(); + }); + + } catch (error) { + Logger.error(`VNC connection error: ${error}`); + clientSocket.close(4015, 'Failed to establish connection'); + containerSocket?.close(4015, 'Failed to establish connection'); + } + }; + + handleConnection().catch((error) => { + Logger.error(`Error handling VNC connection: ${JSON.stringify(error)}`); + clientSocket.close(4013, 'Connection error'); + }); + }); + + server.on('error', (err) => { + Logger.error(`VNC socket broker error: ${JSON.stringify(err)}`); + }); +}; \ No newline at end of file diff --git a/packages/itmat-interface/src/rest/fileDownload.ts b/packages/itmat-interface/src/rest/fileDownload.ts index 866f558c9..edc295586 100644 --- a/packages/itmat-interface/src/rest/fileDownload.ts +++ b/packages/itmat-interface/src/rest/fileDownload.ts @@ -1,61 +1,6 @@ -import { Request, Response } from 'express'; import { db } from '../database/database'; +import { FileDownloadController } from '@itmat-broker/itmat-cores'; import { objStore } from '../objStore/objStore'; -import { permissionCore } from '../graphql/core/permissionCore'; -import { atomicOperation, IUser } from '@itmat-broker/itmat-types'; -import jwt from 'jsonwebtoken'; -import { userRetrieval } from '../authentication/pubkeyAuthentication'; -import { ApolloServerErrorCode } from '@apollo/server/errors'; -import { GraphQLError } from 'graphql'; -export const fileDownloadController = async (req: Request, res: Response): Promise => { - const requester = req.user as IUser; - const requestedFile = req.params.fileId; - const token = req.headers.authorization || ''; - let associatedUser = requester; - if ((token !== '') && (req.user === undefined)) { - // get the decoded payload ignoring signature, no symmetric secret or asymmetric key needed - const decodedPayload = jwt.decode(token); - // obtain the public-key of the robot user in the JWT payload - const pubkey = (decodedPayload as any).publicKey; - // verify the JWT - jwt.verify(token, pubkey, function (error: any) { - if (error) { - throw new GraphQLError('JWT verification failed.', { extensions: { code: ApolloServerErrorCode.BAD_USER_INPUT, error } }); - } - }); - associatedUser = await userRetrieval(pubkey); - } else if (!requester) { - res.status(403).json({ error: 'Please log in.' }); - return; - } - try { - /* download file */ - const file = await db.collections!.files_collection.findOne({ id: requestedFile, deleted: null })!; - if (!file) { - res.status(404).json({ error: 'File not found or you do not have the necessary permission.' }); - return; - } - // check target field exists - const hasStudyLevelPermission = await permissionCore.userHasTheNeccessaryDataPermission( - atomicOperation.READ, - associatedUser, - file.studyId - ); - if (!hasStudyLevelPermission) { - res.status(404).json({ error: 'File not found or you do not have the necessary permission.' }); - return; - } - - const stream = await objStore.downloadFile(file.studyId, file.uri); - res.set('Content-Type', 'application/octet-stream'); - res.set('Content-Type', 'application/download'); - res.set('Content-Disposition', `attachment; filename="${file.fileName}"`); - stream.pipe(res, { end: true }); - return; - } catch (e) { - res.status(500).json(e); - return; - } -}; +export const fileDownloadControllerInstance = new FileDownloadController(db, objStore); \ No newline at end of file diff --git a/packages/itmat-interface/src/server/commonMiddleware.ts b/packages/itmat-interface/src/server/commonMiddleware.ts new file mode 100644 index 000000000..506b6ca86 --- /dev/null +++ b/packages/itmat-interface/src/server/commonMiddleware.ts @@ -0,0 +1,166 @@ +import jwt from 'jsonwebtoken'; +import { DataCore, DataTransformationCore, FileCore, PermissionCore, userRetrieval, UtilsCore } from '@itmat-broker/itmat-cores'; +import { db } from '../database/database'; +import Busboy from 'busboy'; +import crypto from 'crypto'; +import { v4 as uuid } from 'uuid'; +import { enumConfigType, IStudyConfig, defaultSettings, enumFileTypes, IFile, enumFileCategories, CoreError } from '@itmat-broker/itmat-types'; +import { objStore } from '../objStore/objStore'; +import { PassThrough } from 'stream'; + +const fileCore = new FileCore(db, objStore); +const permissionCore = new PermissionCore(db); +const utilsCore = new UtilsCore(); +const dataTransformationCore = new DataTransformationCore(utilsCore); +const dataCore = new DataCore(db, objStore, fileCore, permissionCore, utilsCore, dataTransformationCore); + +export const tokenAuthentication = async (token: string) => { + if (token !== '') { + const decodedPayload = jwt.decode(token); + + if (decodedPayload !== null && typeof decodedPayload === 'object') { + // Check if it's a system token + if (decodedPayload['isSystemToken'] === true) { + + try { + jwt.verify(token, decodedPayload['publicKey']); + return await userRetrieval(db, decodedPayload['publicKey'], true, decodedPayload['userId']); + } catch { + return false; + } + } + + // Handle regular user token + const pubkey = decodedPayload['publicKey']; + if (!pubkey) { + return false; + } + + try { + jwt.verify(token, pubkey); + return await userRetrieval(db, pubkey); + } catch { + return false; + } + } + return false; + } + return null; +}; + +export async function uploadFileData(req, res) { + const busboy = new Busboy({ headers: req.headers }); + const variables: Record = {}; // To hold form fields + let fileName: string; + let fileSize = 0; + const config = await db.collections.configs_collection.findOne({ type: enumConfigType.STUDYCONFIG, key: variables['studyId'] }); + const fileConfig = config ? config.properties : defaultSettings.studyConfig; + const fileSizeLimit = (fileConfig as IStudyConfig).defaultMaximumFileSize; + const hash_ = crypto.createHash('sha256'); + + busboy.on('field', (fieldname, val) => { + variables[fieldname] = val; // Capture fields + }); + let passThrough: PassThrough; + // Capture file stream and upload it immediately + busboy.on('file', (fieldname, file, filename) => { + fileName = filename; // Store the filename + const fileUri = uuid(); // Generate unique file identifier + + passThrough = new PassThrough(); // Create a passthrough stream + + // Start the MinIO upload using the PassThrough stream + // const minioUploadPromise = objStore.uploadFile(passThrough, variables['studyId'], fileUri, fileSize); + + // Listen for data chunks to calculate size and hash while piping to MinIO + file.on('data', (chunk) => { + fileSize += chunk.length; // Increment file size + hash_.update(chunk); // Update the hash with the chunk of data + + if (fileSize > fileSizeLimit) { + file.destroy(); // Stop the stream if the file size exceeds the limit + passThrough.end(); // End the passThrough to stop MinIO upload + res.status(400).json({ message: 'File size exceeds the limit' }); + return; + } + + // Pass the chunk to MinIO via the PassThrough stream + passThrough.write(chunk); + }); + + // When the file stream ends + file.on('end', async () => { + try { + // Upload file to MinIO + const minioUploadPromise = objStore.uploadFile(passThrough, variables['studyId'], fileUri, fileSize); + passThrough.end(); // Signal the end of the PassThrough stream + await minioUploadPromise; + } catch (err: unknown) { + // Return a response with the error message + return res.status(500).json({ message: 'Error uploading file to MinIO', error: { message: (err as CoreError).message } }); + } + + // Hash the file and proceed with the file entry creation + const hashString = hash_.digest('hex'); + const fileType = (filename.split('.').pop() as string).toUpperCase(); + if (!Object.keys(enumFileTypes).includes(fileType)) { + return res.status(400).json({ error: { message: `File type ${fileType} not supported.` } }); + } + + // Create the file entry object + const fileEntry: IFile = { + id: uuid(), + studyId: variables['studyId'], + userId: null, + fileName: fileName, + fileSize: fileSize, + description: variables['description'], + uri: fileUri, + hash: hashString, + fileType: fileType as enumFileTypes, + fileCategory: enumFileCategories.STUDY_DATA_FILE, + properties: variables['properties'] ? JSON.parse(variables['properties']) : {}, + sharedUsers: [], + life: { + createdTime: Date.now(), + createdUser: req.user.id, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + try { + + // Perform any additional processing and insert file data into the database + const response = await dataCore.uploadFileDataWithFileEntry(req.user, + variables['studyId'], + variables['fieldId'], + fileEntry, + JSON.stringify({ + ...JSON.parse(variables['properties'] ?? '{}'), + FileName: fileName + }) + ); + + await db.collections.files_collection.insertOne(fileEntry); + // Send success response + res.status(200).json({ result: { data: response } }); + } catch (err: unknown) { + // Handle any error during processing or insertion + return res.status(400).json({ message: 'Failed to upload file.', error: { message: (err as CoreError).message } }); + } + }); + + file.on('error', (err) => { + return res.status(400).json({ message: 'Failed to upload file.', error: { message: (err as CoreError).message } }); + }); + }); + + // When Busboy finishes processing + busboy.on('finish', () => { + // No need to respond here; we already send the response after upload completion + }); + + // Pipe the request into Busboy to handle the file stream + req.pipe(busboy); +} diff --git a/packages/itmat-interface/src/server/helper.ts b/packages/itmat-interface/src/server/helper.ts new file mode 100644 index 000000000..58587b93a --- /dev/null +++ b/packages/itmat-interface/src/server/helper.ts @@ -0,0 +1,82 @@ +import { ConfigRouter, DataRouter, DomainRouter, DriveRouter, LogRouter, OrganisationRouter, RoleRouter, StudyRouter, TRPCAggRouter, UserRouter, tRPCBaseProcedureMilldeware, WebAuthnRouter, InstanceRouter, LXDRouter } from '@itmat-broker/itmat-apis'; +import { db } from '../database/database'; +import { ConfigCore, DataCore, DataTransformationCore, DomainCore, DriveCore, FileCore, LogCore, OrganisationCore, PermissionCore, StandarizationCore, StudyCore, UserCore, UtilsCore, WebauthnCore, JobCore, InstanceCore, LxdManager } from '@itmat-broker/itmat-cores'; +import { objStore } from '../objStore/objStore'; +import { mailer } from '../emailer/emailer'; +import configManager from '../utils/configManager'; +import { CreateNextContextOptions } from '@trpc/server/adapters/next'; +import { inferAsyncReturnType, initTRPC } from '@trpc/server'; +import { guestProtectionMiddleware } from '../utils/guestProtection'; +export class APICalls { + permissionCore: PermissionCore; + fileCore: FileCore; + utilsCore: UtilsCore; + dataTransformationCore: DataTransformationCore; + studyCore: StudyCore; + dataCore: DataCore; + userCore: UserCore; + organisationCore: OrganisationCore; + logCore: LogCore; + standardizationCore: StandarizationCore; + driveCore: DriveCore; + configCore: ConfigCore; + domainCore: DomainCore; + webauthnCore: WebauthnCore; + jobCore: JobCore; + instanceCore: InstanceCore; + lxdManager: LxdManager; + constructor() { + this.permissionCore = new PermissionCore(db); + this.fileCore = new FileCore(db, objStore); + this.utilsCore = new UtilsCore(); + this.dataTransformationCore = new DataTransformationCore(this.utilsCore); + this.studyCore = new StudyCore(db, objStore, this.permissionCore, this.fileCore); + this.dataCore = new DataCore(db, objStore, this.fileCore, this.permissionCore, this.utilsCore, this.dataTransformationCore); + this.userCore = new UserCore(db, mailer, configManager, objStore); + this.organisationCore = new OrganisationCore(db, this.fileCore); + this.logCore = new LogCore(db); + this.standardizationCore = new StandarizationCore(db, objStore, this.permissionCore, this.studyCore); + this.driveCore = new DriveCore(db, this.fileCore, objStore); + this.configCore = new ConfigCore(db); + this.domainCore = new DomainCore(db, this.fileCore); + this.webauthnCore = new WebauthnCore(db, mailer, configManager, objStore); + this.jobCore = new JobCore(db); + this.instanceCore = new InstanceCore(db, mailer, configManager, this.jobCore, this.userCore); + this.lxdManager = new LxdManager(configManager); + } + + _listOfTRPCRouters() { + const __unusedCreatetRPCContext = async (opts: CreateNextContextOptions) => { + return { + user: opts.req.user, + req: opts.req, + res: opts.res + }; + }; + + type Context = inferAsyncReturnType; + const t = initTRPC.context().create(); + const protectedProcedure = t.procedure.use(async (opts) => { + return await guestProtectionMiddleware(opts); + }); + const baseProcedure = protectedProcedure.use(async (opts) => { + return await tRPCBaseProcedureMilldeware(db, opts); + }); + const router = t.router; + return (new TRPCAggRouter( + router, + new UserRouter(baseProcedure, router, this.userCore), + new DriveRouter(baseProcedure, router, this.driveCore), + new StudyRouter(baseProcedure, router, this.studyCore), + new DataRouter(baseProcedure, router, this.dataCore), + new RoleRouter(baseProcedure, router, this.permissionCore), + new ConfigRouter(baseProcedure, router, this.configCore), + new LogRouter(baseProcedure, router, this.logCore), + new DomainRouter(baseProcedure, router, this.domainCore), + new OrganisationRouter(baseProcedure, router, this.organisationCore), + new WebAuthnRouter(baseProcedure, router, this.webauthnCore), + new InstanceRouter(baseProcedure, router, this.instanceCore), + new LXDRouter(baseProcedure, router, this.lxdManager) + ))._routers(); + } +} diff --git a/packages/itmat-interface/src/server/router.ts b/packages/itmat-interface/src/server/router.ts index 72100b6b5..c8d83833c 100644 --- a/packages/itmat-interface/src/server/router.ts +++ b/packages/itmat-interface/src/server/router.ts @@ -1,70 +1,80 @@ -import { ApolloServer } from '@apollo/server'; -import { ApolloServerErrorCode } from '@apollo/server/errors'; -import { expressMiddleware } from '@apollo/server/express4'; -import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer'; -import { GraphQLError } from 'graphql'; -import { graphqlUploadExpress, GraphQLUpload } from 'graphql-upload-minimal'; -import { execute, subscribe } from 'graphql'; import { WebSocketServer } from 'ws'; -import { useServer } from 'graphql-ws/lib/use/ws'; -import { makeExecutableSchema } from '@graphql-tools/schema'; import MongoStore from 'connect-mongo'; -// import cors from 'cors'; import express from 'express'; import { Express } from 'express'; +import { Request, Response, RequestHandler as NativeRequestHandler } from 'express-serve-static-core'; import session from 'express-session'; import rateLimit from 'express-rate-limit'; import http from 'node:http'; import passport from 'passport'; import { db } from '../database/database'; -import { resolvers } from '../graphql/resolvers'; -import { typeDefs } from '../graphql/typeDefs'; -import { fileDownloadController } from '../rest/fileDownload'; +import { fileDownloadControllerInstance } from '../rest/fileDownload'; +import 'json-bigint-patch'; +import { createProxyMiddleware } from 'http-proxy-middleware'; +import { FileUploadSchema, IUserConfig, enumConfigType, enumUserTypes } from '@itmat-broker/itmat-types'; +import { IConfiguration } from '@itmat-broker/itmat-cores'; import { userLoginUtils } from '../utils/userLoginUtils'; -import { IConfiguration } from '../utils/configManager'; -import { logPlugin } from '../log/logPlugin'; -import { spaceFixing } from '../utils/regrex'; -import { BigIntResolver as scalarResolvers } from 'graphql-scalars'; -import jwt from 'jsonwebtoken'; -import { userRetrieval } from '../authentication/pubkeyAuthentication'; -import { createProxyMiddleware, RequestHandler } from 'http-proxy-middleware'; -import qs from 'qs'; -import { IUser } from '@itmat-broker/itmat-types'; - -interface ApolloServerContext { - token?: string; -} +import * as trpcExpress from '@trpc/server/adapters/express'; +import { tokenAuthentication, uploadFileData } from './commonMiddleware'; +import multer from 'multer'; +import { Readable } from 'stream'; +import { z } from 'zod'; +import { createtRPCContext } from '@itmat-broker/itmat-apis'; +import { APICalls } from './helper'; +import { registerContainSocketServer, registerJupyterSocketServer, jupyterProxyMiddleware, vncProxyMiddleware, registerVNCSocketServer, initializeProxyCacheCleanup } from '../lxd'; +import { Socket } from 'node:net'; +import { Logger } from '@itmat-broker/itmat-commons'; export class Router { private readonly app: Express; private readonly server: http.Server; private readonly config: IConfiguration; - public readonly proxies: Array = []; + // public readonly proxies: Array> = []; constructor(config: IConfiguration) { this.config = config; this.app = express(); - this.app.use(rateLimit({ - windowMs: 1 * 60 * 1000, - max: 500 - })); - - // if (process.env.NODE_ENV === 'development') - // this.app.use(cors({ credentials: true })); - this.app.use(express.json({ limit: '50mb' })); this.app.use(express.urlencoded({ extended: true })); + this.app.use((req, res, next) => { + db.collections.configs_collection.findOne({ + type: enumConfigType.SYSTEMCONFIG + }) + .then(async () => { + const availablePaths: string[] = (await db.collections.domains_collection.find({ 'life.deletedTime': null }).toArray()).map(el => el.domainPath); + // Use a regular expression to match the first path segment after the initial '/' + const pathMatch = req.url.match(/^\/([^/]+)/); + // If there's a match and it's one of the known base paths, remove it from req.url + if (pathMatch && availablePaths.includes(pathMatch[1])) { + // Remove the matched segment from req.url + req.url = req.url.substring(pathMatch[0].length); + // Handle the special case where req.url becomes empty, which should default to '/' + if (req.url === '') { + req.url = '/'; + } + } + next(); + }) + .catch(err => { + next(err); + }); + }); + + this.app.set('trust proxy', 1); /* save persistent sessions in mongo */ + type MongoStoreOptions = Parameters[0]; + const sessionClient = db.client as unknown as NonNullable; // align with connect-mongo's MongoClient declaration + this.app.use( session({ - store: process.env.NODE_ENV === 'test' ? undefined : MongoStore.create({ - client: db.client, - collectionName: config.database.collections.sessions_collection + store: process.env['NODE_ENV'] === 'test' ? undefined : MongoStore.create({ + client: sessionClient, + collectionName: config.database.collections['sessions_collection'] }), secret: this.config.sessionsSecret, saveUninitialized: false, @@ -84,14 +94,50 @@ export class Router { passport.serializeUser(userLoginUtils.serialiseUser); passport.deserializeUser(userLoginUtils.deserialiseUser); + this.app.use(rateLimit({ + windowMs: 1 * 60 * 1000, + max: async function (req) { + const minimumQPS = 5000; + let qps = minimumQPS; + if (req.user) { + const userConfig = await db.collections.configs_collection.findOne({ type: enumConfigType.USERCONFIG, key: req.user.id }); + if (!userConfig) { + qps = minimumQPS; + } else { + qps = (userConfig.properties as IUserConfig).defaultMaximumQPS ?? minimumQPS; + } + } + if (req.user?.type === enumUserTypes.ADMIN) { + qps = Math.max(qps, 2000); + } + return qps; + } + })); + + // authentication middleware + this.app.use((req, res, next) => { + let token: string = req.headers.authorization || ''; + if (token.startsWith('Bearer ') || token.startsWith('BEARER')) { + token = token.slice(7); + } + tokenAuthentication(token) + .then((associatedUser) => { + if (associatedUser) { + req.user = associatedUser; + } + next(); + }) + .catch(() => { + next(); + }); + }); + this.server = http.createServer({ - allowHTTP1: true, keepAlive: true, keepAliveInitialDelay: 0, requestTimeout: 0, - headersTimeout: 0, noDelay: true - } as any, this.app); + }, this.app); this.server.timeout = 0; this.server.headersTimeout = 0; @@ -101,178 +147,240 @@ export class Router { socket.setKeepAlive(true); socket.setNoDelay(true); socket.setTimeout(0); - (socket as any).timeout = 0; + (socket as unknown as Record)['timeout'] = 0; }); } async init() { - const _this = this; - - /* putting schema together */ - const schema = makeExecutableSchema({ - typeDefs, - resolvers: { - ...resolvers, - BigInt: scalarResolvers, - // This maps the `Upload` scalar to the implementation provided - // by the `graphql-upload` package. - Upload: GraphQLUpload - } - }); + // eslint-disable-next-line @typescript-eslint/no-this-alias + // const _this = this; - /* register apolloserver for graphql requests */ - const gqlServer = new ApolloServer({ - schema, - csrfPrevention: false, - allowBatchedHttpRequests: true, - plugins: [ - { - async serverWillStart() { - logPlugin.serverWillStartLogPlugin(); - return { - async drainServer() { - serverCleanup.dispose(); - } - }; - }, - async requestDidStart() { - return { - async executionDidStart(requestContext) { - const operation = requestContext.operationName; - const actionData = requestContext.request.variables; - (requestContext as any).request.variables = spaceFixing(operation as any, actionData); - }, - async willSendResponse(requestContext) { - logPlugin.requestDidStartLogPlugin(requestContext); - } - }; - } - }, - ApolloServerPluginDrainHttpServer({ httpServer: this.server }) - ], - formatError: (error) => { - // TO_DO: generate a ref uuid for errors so the clients can contact admin - // TO_DO: check if the error is not thrown my me manually then switch to generic error to client and log - // Logger().error(error); - return error; - } - }); + const apiCalls = new APICalls(); - /* AE proxy middleware */ - // initial this before graphqlUploadExpress middleware - const ae_proxy = createProxyMiddleware({ - target: _this.config.aeEndpoint, + // webdav + const webdav_target = `http://localhost:${this.config.webdavPort}`; + const webdav_proxy = createProxyMiddleware({ + target: webdav_target, ws: true, xfwd: true, - // logLevel: 'debug', autoRewrite: true, changeOrigin: true, - onProxyReq: function (preq, req, res) { - if (!req.user) - return res.status(403).redirect('/'); - res.cookie('ae_proxy', req.headers['host']); - const data = (req.user as IUser).username + ':token'; - preq.setHeader('authorization', `Basic ${Buffer.from(data).toString('base64')}`); - if (req.body && Object.keys(req.body).length) { - const contentType = preq.getHeader('Content-Type'); - preq.setHeader('origin', _this.config.aeEndpoint); - const writeBody = (bodyData: string) => { - preq.setHeader('Content-Length', Buffer.byteLength(bodyData)); - preq.write(bodyData); - preq.end(); - }; - - if (contentType === 'application/json') { // contentType.includes('application/json') - writeBody(JSON.stringify(req.body)); + pathRewrite: (path) => { + const rewrittenPath = path.replace(/^\/webdav/, '/'); + return rewrittenPath; + }, + on: { + //TODO local test, Change 'ProxyReq' to 'proxyReq' to match the correct event name + proxyReq: function (preq, req: Request, res: Response) { + + preq.setHeader('Host', req.headers['host'] || 'localhost'); + preq.setHeader('Origin', req.headers['origin'] || webdav_target); + preq.setHeader('Accept', '*/*'); + preq.setHeader('Connection', 'keep-alive'); + preq.setHeader('Keep-Alive', 'timeout=120'); + + + // Handle authentication + if (req.user) { + // If user info is available, create Basic auth header + const data = `${req.user.id}:token`; + preq.setHeader('Authorization', `Basic ${Buffer.from(data).toString('base64')}`); + } else { + Logger.log('Unauthorized request, webdav_proxy redirecting...'); + res.status(403).redirect('/'); + return; } - - if (contentType === 'application/x-www-form-urlencoded') { - writeBody(qs.stringify(req.body)); + // Special handling for OPTIONS request + if (req.method === 'OPTIONS') { + preq.setHeader('MS-Author-Via', 'DAV'); + res.setHeader('DAV', '1,2'); + res.setHeader('Allow', 'OPTIONS, GET, HEAD, POST, PUT, DELETE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK'); } + }, + proxyRes: function (proxyRes) { + if (proxyRes.statusCode === 500) { + console.error('WebDAV Server Error:', proxyRes.statusMessage); + let body = ''; + proxyRes.on('data', chunk => { + body += chunk.toString(); + }); + proxyRes.on('end', () => { + Logger.error(`Response Body: ${JSON.stringify(body)}`); + }); + } } - }, - onProxyReqWs: function (preq) { - const data = 'username:token'; - preq.setHeader('authorization', `Basic ${Buffer.from(data).toString('base64')}`); - }, - onError: function (err, req, res, target) { - console.error(err, target); } }); - this.proxies.push(ae_proxy); - - /* AE routers */ - // pun for AE portal - // node and rnode for AE application - // public for public resource like favicon and logo - const proxy_routers = ['/pun', '/node', '/rnode', '/public']; + this.app.use('/webdav', webdav_proxy as NativeRequestHandler); - proxy_routers.forEach(router => { - this.app.use(router, ae_proxy); + this.app.use('/trpc/data.uploadStudyFileData', (req, res, next) => { + uploadFileData(req, res).catch(next); // Ensure any error is passed to next() }); - await gqlServer.start(); - + // trpc + const upload = multer(); this.app.use( - '/graphql', - express.json(), - graphqlUploadExpress(), - expressMiddleware(gqlServer, { - // context: async({ req }) => ({ token: req.headers.token }) - context: async ({ req, res }) => { - /* Bounce all unauthenticated graphql requests */ - // if (req.user === undefined && req.body.operationName !== 'login' && req.body.operationName !== 'IntrospectionQuery' ) { // login and schema introspection doesn't need authentication - // throw new ForbiddenError('not logged in'); - // } - const token: string = req.headers.authorization || ''; - if ((token !== '') && (req.user === undefined)) { - // get the decoded payload ignoring signature, no symmetric secret or asymmetric key needed - const decodedPayload = jwt.decode(token); - // obtain the public-key of the robot user in the JWT payload - const pubkey = (decodedPayload as any).publicKey; - - // verify the JWT - jwt.verify(token, pubkey, function (error: any) { - if (error) { - throw new GraphQLError('JWT verification failed. ' + error, { extensions: { code: ApolloServerErrorCode.BAD_USER_INPUT, error } }); + '/trpc', + upload.any(), // Accept any field name for file uploads + (req, _res, next) => { + (async () => { + try { + if (req.files && req.files.length > 0) { + const files = req.files || []; + const transformedFiles: Record[]> = {}; + + for (const file of files) { + if (!transformedFiles[file.fieldname]) { + transformedFiles[file.fieldname] = []; + } + + transformedFiles[file.fieldname].push({ + createReadStream: () => { + const readableStream = new Readable(); + // eslint-disable-next-line @typescript-eslint/no-empty-function + readableStream._read = () => { }; // No-op _read method + readableStream.push(file.buffer); + readableStream.push(null); // Signify the end of the stream + return readableStream; + }, + filename: file.originalname, + mimetype: file.mimetype, + encoding: file.encoding, + fieldName: file.fieldname + }); } - }); - // store the associated user with the JWT to context - const associatedUser = await userRetrieval(pubkey); - req.user = associatedUser; + req.body.files = transformedFiles; // Attach the transformed files to the request body for later use + } + next(); + } catch (error) { + next(error); } - return ({ req, res }); + })().catch(next); + }, trpcExpress.createExpressMiddleware({ + router: apiCalls._listOfTRPCRouters(), + createContext: createtRPCContext + })); + + this.app.get('/file/:fileId', fileDownloadControllerInstance.fileDownloadController); + + + // set up the proxy and register directly, combine the grapgql's websocket and lxd's websocket + + // Rate limiting for proxy routes (more restrictive due to expensive operations) + const proxyRateLimiter = rateLimit({ + windowMs: 1 * 60 * 1000, // 1 minute + max: async function (req) { + // More restrictive limits for proxy operations + const baseLimit = 200; // Base limit of 200 requests per minute + if (req.user?.type === enumUserTypes.ADMIN) { + return 500; // Admins get higher limit } - }) - ); + if (req.user) { + const userConfig = await db.collections.configs_collection.findOne({ type: enumConfigType.USERCONFIG, key: req.user.id }); + if (userConfig) { + const userQPS = (userConfig.properties as IUserConfig).defaultMaximumQPS; + // Convert QPS to requests per minute, but cap at reasonable proxy limit + return Math.min(Math.floor((userQPS ?? baseLimit) * 60), 1000); + } + } + return baseLimit; + }, + standardHeaders: true, + legacyHeaders: false + }); + + // Setup HTTP route handlers for Jupyter proxy requests + const proxyRoutes = ['/jupyter/:instance_id', '/jupyter/:instance_id/*']; + proxyRoutes.forEach((route) => { + this.app.use(route, proxyRateLimiter, (req, res, next) => { + jupyterProxyMiddleware(req, res, next, apiCalls.instanceCore).catch(next); + }); + }); + + // Add VNC proxy routes + const vncProxyRoutes = ['/matlab/:instance_id', '/matlab/:instance_id/*']; + vncProxyRoutes.forEach((route) => { + this.app.use(route, proxyRateLimiter, (req, res, next) => { + vncProxyMiddleware(req, res, next, apiCalls.instanceCore).catch(next); + }); + }); + - /* register the graphql subscription functionalities */ - // Creating the WebSocket subscription server - const wsServer = new WebSocketServer({ - // This is the `httpServer` returned by createServer(app); - server: this.server, - // Pass a different path here if your ApolloServer serves at - // a different path. - path: '/graphql' + // Create WebSocket server for RTC connections + const containerWsServer = new WebSocketServer({ + noServer: true }); - // Passing in an instance of a GraphQLSchema and - // telling the WebSocketServer to start listening - const serverCleanup = useServer({ schema: schema, execute: execute, subscribe: subscribe }, wsServer); + registerContainSocketServer(containerWsServer, apiCalls.lxdManager); - /* Bounce all unauthenticated non-graphql HTTP requests */ - // this.app.use((req: Request, res: Response, next: NextFunction) => { - // if (req.user === undefined || req.user.username === undefined) { - // res.status(401).json(new CustomError('Please log in first.')); - // return; - // } - // next(); - // }); + // Create WebSocket server for connections + const targetWsServer = new WebSocketServer({ + noServer: true + }); - this.app.get('/file/:fileId', fileDownloadController); + registerJupyterSocketServer(targetWsServer, apiCalls.instanceCore); + + // Create WebSocket server for VNC connections + const vncWsServer = new WebSocketServer({ + noServer: true + }); + + registerVNCSocketServer(vncWsServer, apiCalls.instanceCore); + + // Upgrade handler for WebSocket requests + this.server.on('upgrade', (req, socket: Socket, head: Buffer) => { + + if (req.url?.startsWith('/jupyter')) { + const instance_id = req.url.split('/')[2]; // Extract instance ID from the URL + + if (!instance_id) { + Logger.error('No instance ID found in URL.'); + socket.end(); + return; + } + try { + targetWsServer.handleUpgrade(req, socket, head, (ws) => { + + targetWsServer.emit('connection', ws, req); // Forward the upgrade to the container WebSocket server + } + ); + } catch (error) { + Logger.log(`error ${JSON.stringify(error)}`); + socket.end(); // End socket if there's an error creating the proxy + return; + } + } + // handle VNC websocket connections + else if (req.url?.startsWith('/matlab')) { + + if (req.headers.upgrade?.toLowerCase() === 'websocket') { + + vncWsServer.handleUpgrade(req, socket, head, (ws) => { + vncWsServer.emit('connection', ws, req); + }); + } else { + socket.destroy(); + } + } + // Handle RTC WebSocket connections + else if (req.url?.startsWith('/rtc')) { + + containerWsServer.handleUpgrade(req, socket, head, (ws) => { + containerWsServer.emit('connection', ws, req); // Forward the upgrade to the container WebSocket server + }); + } + // Invalid WebSocket request + else { + Logger.error(`Invalid WebSocket upgrade request: ${req.url}`); + socket.destroy(); // Close socket if the request is not valid + } + }); + + // Initialize the proxy cache cleanup system + initializeProxyCacheCleanup(); } @@ -280,11 +388,12 @@ export class Router { return this.app; } - public getProxy(): RequestHandler { - return this.proxies[0]; - } + // public getProxy(): RequestHandler { + // return this.proxies[0]; + // } public getServer(): http.Server { return this.server; } + } diff --git a/packages/itmat-interface/src/server/server.ts b/packages/itmat-interface/src/server/server.ts index cfa1099d4..88dff204d 100644 --- a/packages/itmat-interface/src/server/server.ts +++ b/packages/itmat-interface/src/server/server.ts @@ -1,5 +1,5 @@ -import { CustomError, IServerBaseConfig, ServerBase } from '@itmat-broker/itmat-commons'; -import { IConfiguration } from '../utils/configManager'; +import { CustomError, IServerBaseConfig, Logger, ServerBase } from '@itmat-broker/itmat-commons'; +import { IConfiguration } from '@itmat-broker/itmat-cores'; export interface IServerConfig extends IServerBaseConfig { bcrypt: { @@ -8,12 +8,12 @@ export interface IServerConfig extends IServerBaseConfig { } export class Runner extends ServerBase { - constructor(protected config: IConfiguration) { + constructor(protected override config: IConfiguration) { super(config); } protected async additionalChecksAndActions(): Promise { if (isNaN(parseInt(`${this.config.bcrypt.saltround}`, 10))) { - console.log(new CustomError('Salt round must be a number')); + Logger.log(new CustomError('Salt round must be a number')); process.exit(1); } } diff --git a/packages/itmat-interface/src/utils/configManager.ts b/packages/itmat-interface/src/utils/configManager.ts index 4af6cc91c..bb8a90166 100644 --- a/packages/itmat-interface/src/utils/configManager.ts +++ b/packages/itmat-interface/src/utils/configManager.ts @@ -1,44 +1,4 @@ -import merge from 'deepmerge'; -import fs from 'fs-extra'; import path from 'path'; -import { IObjectStoreConfig, IDatabaseBaseConfig } from '@itmat-broker/itmat-commons'; -import configDefaults from '../../config/config.sample.json'; -import { IServerConfig } from '../server/server.js'; -import chalk from 'chalk'; +import { ConfigurationManager } from '@itmat-broker/itmat-cores'; -export interface IConfiguration extends IServerConfig { - appName: string; - database: IDatabaseBaseConfig; - objectStore: IObjectStoreConfig; - nodemailer: any; - aesSecret: string; - sessionsSecret: string; - adminEmail: string; - aeEndpoint: string; -} - -class ConfigurationManager { - - public static expand(configurationFiles: string[]): IConfiguration { - - let config = configDefaults; - console.log('Applied default configuration.'); - - configurationFiles.forEach((configurationFile) => { - try { - if (fs.existsSync(configurationFile)) { - const content = fs.readFileSync(configurationFile, 'utf8'); - config = merge(config, JSON.parse(content)); - console.log(`Applied configuration from ${path.resolve(configurationFile)}.`); - } - } catch (e) { - console.error(chalk.red('Cannot parse configuration file.')); - } - }); - - return config; - } - -} - -export default ConfigurationManager.expand((process.env.NODE_ENV === 'development' ? [path.join(__dirname.replace('dist', ''), 'config/config.json')] : []).concat(['config/config.json'])); +export default ConfigurationManager.expand((process.env['NODE_ENV'] === 'development' ? [path.join(__dirname.replace('dist', ''), 'config/config.json')] : []).concat(['config/config.json'])); diff --git a/packages/itmat-interface/src/utils/guestProtection.ts b/packages/itmat-interface/src/utils/guestProtection.ts new file mode 100644 index 000000000..b21167a82 --- /dev/null +++ b/packages/itmat-interface/src/utils/guestProtection.ts @@ -0,0 +1,55 @@ +import { TRPCError } from '@trpc/server'; +import { IUserWithoutToken } from '@itmat-broker/itmat-types'; + +const GUEST_ALLOWED_ROUTERS = [ + 'user', + 'webauthn', + 'role' +]; +const GUEST_ALLOWED_OPERATIONS = [ + 'study.getStudies', + 'data.getStudyData', + 'data.getStudyDataLatest', + 'data.getStudyFiles', + 'data.getStudyFields', + 'data.getStudyData', + 'data.getDataLatest', + 'data.getStudyFiles', + 'config.getConfig', + 'domain.getCurrentDomain', + 'domain.getCurrentSubPath', + 'organisation.getOrganisations' + +]; + + +// Check if user is guest +function isGuestUser(user: IUserWithoutToken): boolean { + return user.type === 'GUEST'; +} + +// The middleware +export const guestProtectionMiddleware = async (opts) => { + // Skip if no user + if (!opts.ctx.req?.user) { + return opts.next(); + } + + // Check if user is a guest + if (isGuestUser(opts.ctx.req.user)) { + const routerPath = opts.path.split('.')[0]; + const isAllowedRouter = GUEST_ALLOWED_ROUTERS.includes(routerPath); + const isAllowedOperation = GUEST_ALLOWED_OPERATIONS.includes(opts.path); + + // Grant access only if it's an allowed operation or router + if (!isAllowedOperation && !isAllowedRouter) { + throw new TRPCError({ + code: 'FORBIDDEN', + message: 'Guest users cannot access this feature' + }); + } + + } + + return opts.next(); +}; diff --git a/packages/itmat-interface/src/utils/noop.ts b/packages/itmat-interface/src/utils/noop.ts deleted file mode 100644 index 65d573907..000000000 --- a/packages/itmat-interface/src/utils/noop.ts +++ /dev/null @@ -1,2 +0,0 @@ -/* eslint-disable import/no-anonymous-default-export */ -export default (): void => { return; }; diff --git a/packages/itmat-interface/src/utils/query.ts b/packages/itmat-interface/src/utils/query.ts deleted file mode 100644 index bfee2a417..000000000 --- a/packages/itmat-interface/src/utils/query.ts +++ /dev/null @@ -1,592 +0,0 @@ -import { IStudy, IFieldEntry, IStandardization } from '@itmat-broker/itmat-types'; -/* - queryString: - format: string # returned foramt: raw, standardized, grouped, summary - data_requested: array # returned fields - cohort: array[array] # filters - new_fields: array # new_fields - -*/ -// if has study-level permission, non versioned data will also be returned - - -export function buildPipeline(query: any, studyId: string, permittedVersions: Array, permittedFields: IFieldEntry[], metadataFilter: any, isAdmin: boolean, includeUnversioned: boolean) { - let fieldIds: string[] = permittedFields.map(el => el.fieldId); - const fields = { _id: 0, m_subjectId: 1, m_visitId: 1 }; - // We send back the requested fields, by default send all fields - if (query['data_requested'] !== undefined && query['data_requested'] !== null) { - fieldIds = permittedFields.filter(el => query['data_requested'].includes(el.fieldId)).map(el => el.fieldId); - query.data_requested.forEach((field: string) => { - if (fieldIds.includes(field)) { - (fields as any)[field] = 1; - } - }); - } else if (query['table_requested'] !== undefined && query['table_requested'] !== undefined) { - fieldIds = permittedFields.filter(el => el.tableName === query['table_requested']).map(el => el.fieldId); - fieldIds.forEach((field: string) => { - (fields as any)[field] = 1; - }); - } else { - fieldIds.forEach((field: string) => { - (fields as any)[field] = 1; - }); - } - let match = {}; - // We send back the filtered fields values - if (query['cohort'] !== undefined && query['cohort'] !== null) { - if (query.cohort.length > 1) { - const subqueries: any = []; - query.cohort.forEach((subcohort: any) => { - subqueries.push(translateCohort(subcohort)); - }); - match = { $or: subqueries }; - } else { - match = translateCohort(query.cohort[0]); - } - } - - const groupFilter: any = [{ - $match: { m_fieldId: { $in: fieldIds } } - }, { - $sort: { uploadedAt: -1 } - }, { - $group: { - _id: { m_subjectId: '$m_subjectId', m_visitId: '$m_visitId', m_fieldId: '$m_fieldId' }, - doc: { $first: '$$ROOT' } - } - }, { - $project: { - m_subjectId: '$doc.m_subjectId', - m_visitId: '$doc.m_visitId', - m_fieldId: '$doc.m_fieldId', - value: '$doc.value', - _id: 0 - } - } - ]; - if (isAdmin) { - return [ - { $match: { m_fieldId: { $regex: /^(?!Device)\w+$/ }, m_versionId: { $in: permittedVersions }, m_studyId: studyId } }, - ...groupFilter, - { $match: match } - // { $project: fields } - ]; - } else { - if (includeUnversioned) { - return [ - { $match: { m_fieldId: { $regex: /^(?!Device)\w+$/ }, m_versionId: { $in: permittedVersions }, m_studyId: studyId } }, - { $match: { $or: [metadataFilter, { m_versionId: null }] } }, - ...groupFilter, - { $match: match } - // { $project: fields } - ]; - } else { - return [ - { $match: { m_fieldId: { $regex: /^(?!Device)\w+$/ }, m_versionId: { $in: permittedVersions }, m_studyId: studyId } }, - { $match: metadataFilter }, - ...groupFilter, - { $match: match } - // { $project: fields } - ]; - } - } -} - -// function createNewField(expression: any) { -// let newField = {}; -// // if any parameters === '99999', then ignore this calculation -// switch (expression.op) { -// case '*': -// newField = { -// $cond: [ -// { -// $or: [ -// { $eq: [{ $type: createNewField(expression.left) }, 'string'] }, -// { $eq: [{ $type: createNewField(expression.right) }, 'string'] } -// ] -// }, -// '99999', -// { -// $multiply: [createNewField(expression.left), createNewField(expression.right)] -// } -// ] -// }; -// break; -// case '/': -// newField = { -// $cond: [ -// { -// $or: [ -// { $eq: [{ $type: createNewField(expression.left) }, 'string'] }, -// { $eq: [{ $type: createNewField(expression.right) }, 'string'] } -// ] -// }, -// '99999', -// { -// $divide: [createNewField(expression.left), createNewField(expression.right)] -// } -// ] -// }; -// break; -// case '-': -// newField = { -// $cond: [ -// { -// $or: [ -// { $eq: [{ $type: createNewField(expression.left) }, 'string'] }, -// { $eq: [{ $type: createNewField(expression.right) }, 'string'] } -// ] -// }, -// '99999', -// { -// $subtract: [createNewField(expression.left), createNewField(expression.right)] -// } -// ] -// }; -// break; -// case '+': -// newField = { -// $cond: [ -// { -// $or: [ -// { $eq: [{ $type: createNewField(expression.left) }, 'string'] }, -// { $eq: [{ $type: createNewField(expression.right) }, 'string'] } -// ] -// }, -// '99999', -// { -// $add: [createNewField(expression.left), createNewField(expression.right)] -// } -// ] -// }; -// break; -// case '^': -// newField = { -// $cond: [ -// { $eq: [{ $type: createNewField(expression.left) }, 'string'] }, -// '99999', -// { -// $pow: [createNewField(expression.left), createNewField(expression.right)] -// } -// ] -// }; -// break; -// case 'val': -// newField = parseFloat(expression.left); -// break; -// case 'field': -// newField = { -// $cond: [ -// { $eq: [{ $type: createNewField(expression.left) }, 'string'] }, -// '99999', -// '$' + expression.left -// ] -// }; -// break; -// default: -// break; -// } - -// return newField; -// } - - -// function isEmptyObject(obj: any) { -// return !Object.keys(obj).length; -// } - - -function translateCohort(cohort: any) { - const match = {}; - cohort.forEach(function (select: any) { - - switch (select.op) { - case '=': - // select.value must be an array - (match as any)[select.field] = { $in: [select.value] }; - break; - case '!=': - // select.value must be an array - (match as any)[select.field] = { $ne: [select.value] }; - break; - case '<': - // select.value must be a float - (match as any)[select.field] = { $lt: parseFloat(select.value) }; - break; - case '>': - // select.value must be a float - (match as any)[select.field] = { $gt: parseFloat(select.value) }; - break; - case 'derived': { - // equation must only have + - * / - const derivedOperation = select.value.split(' '); - if (derivedOperation[0] === '=') { - (match as any)[select.field] = { $eq: parseFloat(select.value) }; - } - if (derivedOperation[0] === '>') { - (match as any)[select.field] = { $gt: parseFloat(select.value) }; - } - if (derivedOperation[0] === '<') { - (match as any)[select.field] = { $lt: parseFloat(select.value) }; - } - break; - } - case 'exists': - // We check if the field exists. This is to be used for checking if a patient - // has an image - (match as any)[select.field] = { $exists: true }; - break; - case 'count': { - // counts can only be positive. NB: > and < are inclusive e.g. < is <= - const countOperation = select.value.split(' '); - const countfield = select.field + '.count'; - if (countOperation[0] === '=') { - (match as any)[countfield] = { $eq: parseInt(countOperation[1], 10) }; - } - if (countOperation[0] === '>') { - (match as any)[countfield] = { $gt: parseInt(countOperation[1], 10) }; - } - if (countOperation[0] === '<') { - (match as any)[countfield] = { $lt: parseInt(countOperation[1], 10) }; - } - break; - } - default: - break; - } - } - ); - return match; -} - -export function translateMetadata(metadata: any) { - const match = {}; - metadata.forEach(function (select: any) { - switch (select.op) { - case '=': - // select.parameter must be an array - (match as any)['metadata.'.concat(select.key)] = { $in: [select.parameter] }; - break; - case '!=': - // select.parameter must be an array - (match as any)['metadata.'.concat(select.key)] = { $ne: [select.parameter] }; - break; - case '<': - // select.parameter must be a float - (match as any)['metadata.'.concat(select.key)] = { $lt: parseFloat(select.parameter) }; - break; - case '>': - // select.parameter must be a float - (match as any)['metadata.'.concat(select.key)] = { $gt: parseFloat(select.parameter) }; - break; - case 'exists': - // We check if the field exists. This is to be used for checking if a patient - // has an image - (match as any)['metadata.'.concat(select.key)] = { $exists: true }; - break; - default: - break; - } - } - ); - return match; -} - -export function dataStandardization(study: IStudy, fields: IFieldEntry[], data: any, queryString: any, standardizations: IStandardization[] | null) { - if (!queryString['format'] || queryString['format'] === 'raw') { - return data; - } else if (queryString['format'] === 'grouped' || queryString['format'] === 'summary') { - return dataGrouping(data, queryString['format']); - } else if (standardizations && queryString['format'].startsWith('standardized')) { - return standardize(study, fields, data, standardizations, queryString['new_fields'] || []); - } - return { error: 'Format not recognized.' }; -} - -// fields are obtained from called functions, providing the valid fields -export function standardize(study: IStudy, fields: IFieldEntry[], data: any, standardizations: IStandardization[], newFields: any) { - const records: any = {}; - const mergedFields: any[] = [...fields]; - [...newFields].forEach(el => { - const emptyArr: string[] = []; - preOrderTraversal(el, emptyArr); - mergedFields.push({ fieldId: emptyArr }); - }); - const seqNumMap: Map = new Map(); - for (const field of mergedFields) { - let fieldDef: any = {}; - for (let i = 0; i < mergedFields.length; i++) { - if (mergedFields[i].fieldId === field.fieldId) { - fieldDef = mergedFields[i]; - break; - } - } - // check if it is in the standardizations - let fieldIdentifier: string | string[] = field.fieldId; - if (!Array.isArray(field.fieldId)) { - fieldIdentifier = ['$' + field.fieldId]; - } - let standardization: IStandardization | undefined = undefined; - for (let i = 0; i < standardizations.length; i++) { - if (JSON.stringify(standardizations[i].field) === JSON.stringify(fieldIdentifier)) { - standardization = standardizations[i]; - break; - } - } - if (!standardization) { - continue; - } - if (!standardization.stdRules || standardization.stdRules.length === 0) { - continue; - } - for (const subjectId of Object.keys(data)) { - for (const visitId of Object.keys(data[subjectId])) { - if (data[subjectId][visitId][field.fieldId] === null) { - continue; - } - const dataClip: any = {}; - let isSkip = false; - if (!standardization.stdRules) { - continue; - } - for (const rule of (standardization.stdRules as any)) { - if (!rule.parameter) { - continue; - } - if (rule.filters !== undefined && rule.filters !== null && rule.filters[dataClip[rule.entry]] !== undefined - && rule.filters[dataClip[rule.entry]][0] === 'delete') { - continue; - } - switch (rule.source) { - case 'data': { - if (rule.parameter.length === 0) { - dataClip[rule.entry] = data[subjectId][visitId][field.fieldId] || ''; - } else { - const selectedFieldId = rule.parameter[0]; - const selectedVistId = rule.parameter.length === 2 ? rule.parameter[1] : visitId; - dataClip[rule.entry] = data[subjectId][selectedVistId][selectedFieldId] || ''; - } - break; - } - case 'fieldDef': { - dataClip[rule.entry] = fieldDef[rule.parameter[0] as keyof IFieldEntry] as any || ''; - break; - } - case 'value': { - dataClip[rule.entry] = rule.parameter[0] || ''; - break; - } - // parameter is the path that start from - case 'inc': { - const value: number | undefined = incHelper(seqNumMap, rule.parameter, subjectId, visitId); - if (value === undefined) { - break; - } - dataClip[rule.entry] = value; - break; - } - case 'reserved': { - switch (rule.parameter[0]) { - case 'm_subjectId': { - dataClip[rule.entry] = subjectId; - break; - } - case 'm_visitId': { - dataClip[rule.entry] = visitId; - break; - } - case 'm_studyId': { - dataClip[rule.entry] = study.id; - break; - } - } - break; - } - default: { - isSkip = true; - break; - } - } - // deal with filters - // support two ways: convert to another value, delete this value; input should be [delete/convert, $value] - if (rule.filters) { - if (Object.keys(rule.filters).includes(dataClip[rule.entry].toString())) { - if (rule.filters[dataClip[rule.entry]].length !== 2) { - continue; - } - switch (rule.filters[dataClip[rule.entry]][0]) { - // add patch to allow to convert to another field value - case 'convert': { - const options: Record = rule.filters[dataClip[rule.entry]][1]; - if (options.source === 'value') { - dataClip[rule.entry] = options.parameter; - } else if (options.source === 'data') { - const tmpData = data[subjectId][visitId][options.parameter]; - // the replaced value can be converted again - if (options.filters && Object.keys(options.filters).includes(tmpData)) { - dataClip[rule.entry] = options.filters[tmpData]; - } else { - dataClip[rule.entry] = tmpData; - } - } - break; - } - case 'delete': { - isSkip = true; - break; - } - default: { - isSkip = true; - break; - } - } - } - } - } - if (isSkip) { - continue; - } - // if (Object.keys(dataClip).includes('CMTRT') && dataClip['CMTRT'] === '') { - // console.log(field.fieldId, subjectId, visitId, console.log(data[subjectId][visitId][field.fieldId])); - // } - // deal with join - if (standardization.path) { - let pointer = insertInObj(records, standardization.path, undefined, true, subjectId, visitId); - if (pointer === undefined) { - pointer = insertInObj(records, standardization.path, [], true, subjectId, visitId); - } - if (standardization.joinByKeys && standardization.joinByKeys.length > 0) { - let isSame = true; - for (let i = 0; i < pointer.length; i++) { - isSame = true; - for (let j = 0; j < standardization.joinByKeys.length; j++) { - if (pointer[i][standardization.joinByKeys[j]] !== dataClip[standardization.joinByKeys[j]]) { - isSame = false; - break; - } - } - if (isSame) { - pointer[i] = { ...dataClip, ...pointer[i] }; - break; - } - } - if (isSame && pointer.length !== 0) { - insertInObj(records, standardization.path, pointer, true, subjectId, visitId); - } else { - insertInObj(records, standardization.path, dataClip, false, subjectId, visitId); - } - } else { - insertInObj(records, standardization.path, dataClip, false, subjectId, visitId); - } - } - } - } - } - return records; -} - -// ignore the subjectId, join values with same visitId and fieldId; with extra info -export function dataGrouping(data: any, format: string) { - const joinedData: any = {}; - for (const subjectId of Object.keys(data)) { - for (const visitId of Object.keys(data[subjectId])) { - for (const fieldId of Object.keys(data[subjectId][visitId])) { - if (['m_subjectId', 'm_visitId', 'm_versionId'].includes(fieldId)) { - continue; - } else { - if (joinedData[fieldId] === undefined) { - joinedData[fieldId] = {}; - } - if (joinedData[fieldId][visitId] === undefined) { - joinedData[fieldId][visitId] = { - totalNumOfRecords: 0, - validNumOfRecords: 0, - data: [] - }; - } - if (data[subjectId][visitId][fieldId] !== '99999') { - joinedData[fieldId][visitId]['validNumOfRecords'] += 1; - // if summary mode; donot return data - } - if (format !== 'summary') { - joinedData[fieldId][visitId]['data'].push(data[subjectId][visitId][fieldId]); - } - joinedData[fieldId][visitId]['totalNumOfRecords'] += 1; - } - } - } - } - return joinedData; -} - -function incHelper(map: Map, levels: string[], subjectId: string, visitId: string) { - if (levels.length < 1) { - return; - } - let key = levels[0] === 'm_subjectId' ? subjectId : levels[0] === 'm_visitId' ? visitId : levels[0]; - for (let i = 1; i < levels.length; i++) { - const modifiedLevel = levels[i] === 'm_subjectId' ? subjectId : levels[0] === 'm_visitId' ? visitId : levels[0]; - key += `-${modifiedLevel}`; - } - map.set(key, (map.get(key) || 0) + 1); - return map.get(key) || 0; -} - -// recursively create object structures, return the last pointer -function insertInObj(obj: any, levels: string[], lastValue: any, join: boolean, subjectId: string, visitId: string) { - let pointer: any = obj; - for (let i = 0; i < levels.length; i++) { - let modifiedLevel = levels[i]; - if (levels[i] === 'm_subjectId') { - modifiedLevel = subjectId; - } else if (levels[i] === 'm_visitId') { - modifiedLevel = visitId; - } - if (i === levels.length - 1) { - if (lastValue) { - if (Array.isArray(pointer[modifiedLevel])) { - if (join) { - pointer[modifiedLevel] = lastValue; - } else { - pointer[modifiedLevel].push(lastValue); - } - } else { - pointer[modifiedLevel] = lastValue; - } - } - pointer = pointer[modifiedLevel]; - break; - } - if (pointer[modifiedLevel] === undefined) { - pointer[modifiedLevel] = {}; - } - pointer = pointer[modifiedLevel]; - } - return pointer; -} - -// array[0] should be the name of the new field; array[-1] should be 'derived' -function preOrderTraversal(node: any, array: string[]) { - if (!node) { - return false; - } - if (node.name) { - // first level - array.push(node.name); - preOrderTraversal(node.value, array); - } - // node must have a value of left/right children - if (node instanceof Object) { - array.push(node.op); - if (node.op === 'field') { - array.push('$' + node.left); - array.push(''); - } else { - preOrderTraversal(node.left, array); - preOrderTraversal(node.right, array); - } - } else { - array.push(node); - } - return true; -} diff --git a/packages/itmat-interface/src/utils/regrex.ts b/packages/itmat-interface/src/utils/regrex.ts deleted file mode 100644 index 9e8ffc741..000000000 --- a/packages/itmat-interface/src/utils/regrex.ts +++ /dev/null @@ -1,50 +0,0 @@ -// whether passwords can contain spaces remains to be determined by the resolver functions -// the format doesnt allow extra spaces for any kinds of names, but whether spaces are not allowed is -// determined by the resolver functions -const omitOperationsAndFields = { - login: [], - CreateUser: ['password'], - EditUser: ['password'], - resetPassword: ['newPassword'] -}; - -// By default, only one space is allowed between adjacent letters -// this function only format the parameters, other requirement, such as duplicate check -// relies on the resolver functions -export function spaceFixing(operation: keyof typeof omitOperationsAndFields, actionData: any) { - if (Object.keys(omitOperationsAndFields).includes(operation)) { - if (!omitOperationsAndFields[operation].length) { - return actionData; - } - } - recursiveFix(operation, actionData); - return actionData; -} - -export function recursiveFix(operation: keyof typeof omitOperationsAndFields, obj: any): void { - if (obj !== null && obj !== undefined) { - // keep it original if it is a promise - if (obj instanceof Promise) { - return; - } else { - const fields = Object.keys(omitOperationsAndFields).includes(operation) ? omitOperationsAndFields[operation] : []; - for (const key in obj) { - switch (typeof (obj[key])) { - case 'object': { - return recursiveFix(operation, obj[key]); - } - case 'string': { - if (!fields.includes(key)) { - obj[key] = obj[key].replace(/\s+/g, ' ').trim(); - } - break; - } - default: { - break; - } - } - } - } - } - return; -} diff --git a/packages/itmat-interface/src/utils/userLoginUtils.ts b/packages/itmat-interface/src/utils/userLoginUtils.ts index bac987ef0..bd227874b 100644 --- a/packages/itmat-interface/src/utils/userLoginUtils.ts +++ b/packages/itmat-interface/src/utils/userLoginUtils.ts @@ -1,24 +1,4 @@ -import { IUser, IUserWithoutToken } from '@itmat-broker/itmat-types'; +import { UserLoginUtils } from '@itmat-broker/itmat-cores'; import { db } from '../database/database'; -export class UserLoginUtils { - constructor() { - this.serialiseUser = this.serialiseUser.bind(this); - this.deserialiseUser = this.deserialiseUser.bind(this); - } - - public serialiseUser(user: Express.User, done: (__unused__err: any, __unused__id?: any) => void): void { - done(null, (user as IUser).username); - } - - public async deserialiseUser(username: string, done: (__unused__err: any, __unused__id?: any) => void): Promise { - const user = await this._getUser(username); - done(null, user); - } - - private async _getUser(username: string): Promise { - return await db.collections!.users_collection.findOne({ deleted: null, username }, { projection: { _id: 0, deleted: 0, password: 0 } })!; - } -} - -export const userLoginUtils = Object.freeze(new UserLoginUtils()); +export const userLoginUtils = Object.freeze(new UserLoginUtils(db)); diff --git a/packages/itmat-interface/src/webdav/webdav.ts b/packages/itmat-interface/src/webdav/webdav.ts new file mode 100644 index 000000000..7dc8e28ba --- /dev/null +++ b/packages/itmat-interface/src/webdav/webdav.ts @@ -0,0 +1,32 @@ +import { v2 as webdav } from 'webdav-server'; +import { DMPFileSystem, DMPWebDAVAuthentication, IConfiguration, DataCore, DataTransformationCore, DriveCore, FileCore, PermissionCore, StudyCore, UtilsCore } from '@itmat-broker/itmat-cores'; +import { objStore } from '../objStore/objStore'; +import { db } from '../database/database'; +import { Logger } from '@itmat-broker/itmat-commons'; + +class DMPWebDav { + public async connect(config: IConfiguration) { + const httpAuthentication = new DMPWebDAVAuthentication(db, 'realem'); + const webServer = new webdav.WebDAVServer({ + port: config.webdavPort, + httpAuthentication: httpAuthentication as webdav.HTTPAuthentication + }); + + const fileCore = new FileCore(db, objStore); + const driveCore = new DriveCore(db, fileCore, objStore); + const premissionCore = new PermissionCore(db); + const studyCore = new StudyCore(db, objStore, premissionCore, fileCore); + const utilsCore = new UtilsCore(); + const dataTransformatonCore = new DataTransformationCore(utilsCore); + const dataCore = new DataCore(db, objStore, fileCore, premissionCore, utilsCore, dataTransformatonCore); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + webServer.setFileSystem('/DMP', new DMPFileSystem(db, fileCore, driveCore, studyCore, dataCore) as any, (success) => { + webServer.start(() => Logger.log('DMP file system attached: ' + (success ? 'success' : 'failed'))); + }); + + Logger.log('Webdav is starting...'); + } +} + +export const dmpWebDav = new DMPWebDav(); \ No newline at end of file diff --git a/packages/itmat-interface/test/filesForTests/RandomFile.random b/packages/itmat-interface/test/filesForTests/RandomFile.random new file mode 100644 index 000000000..41bc2ceff --- /dev/null +++ b/packages/itmat-interface/test/filesForTests/RandomFile.random @@ -0,0 +1 @@ +just testing Random. \ No newline at end of file diff --git a/packages/itmat-interface/test/filesForTests/dsi.jpeg b/packages/itmat-interface/test/filesForTests/dsi.jpeg new file mode 100644 index 000000000..f78e06878 Binary files /dev/null and b/packages/itmat-interface/test/filesForTests/dsi.jpeg differ diff --git a/packages/itmat-interface/test/serverTests/_loginHelper.ts b/packages/itmat-interface/test/serverTests/_loginHelper.ts deleted file mode 100644 index 2c2d00cfb..000000000 --- a/packages/itmat-interface/test/serverTests/_loginHelper.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { print } from 'graphql'; -import { LOGIN, LOGOUT } from '@itmat-broker/itmat-models'; -import * as mfa from '../../src/utils/mfa'; -import { SuperTest, Test } from 'supertest'; - -export function connectAdmin(agent: SuperTest): Promise { - const adminSecret = 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA'; - return connectAgent(agent, 'admin', 'admin', adminSecret); -} - -export function connectUser(agent: SuperTest): Promise { - const userSecret = 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA'; - return connectAgent(agent, 'standardUser', 'admin', userSecret); -} - -export function connectAgent(agent: SuperTest, user: string, pw: string, secret: string): Promise { - const otp = mfa.generateTOTP(secret).toString(); - return new Promise((resolve, reject) => agent.post('/graphql') - .set('Content-type', 'application/json') - .send({ - query: print(LOGIN), - variables: { username: user, password: pw, totp: otp } - }) - .then(res => { - if (res.status === 200) - return resolve(); - return reject(); - }).catch(() => null)); -} - -export function disconnectAgent(agent: SuperTest): Promise { - return new Promise((resolve, reject) => agent.post('/graphql') - .send({ - query: print(LOGOUT) - }) - .then(res => { - if (res.status === 200) - return resolve(); - return reject(); - }).catch(() => null)); -} diff --git a/packages/itmat-interface/test/serverTests/file.test.ts b/packages/itmat-interface/test/serverTests/file.test.ts deleted file mode 100644 index d364d8fb5..000000000 --- a/packages/itmat-interface/test/serverTests/file.test.ts +++ /dev/null @@ -1,883 +0,0 @@ -/** - * @with Minio - */ -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-nocheck - -import request from 'supertest'; -import { print } from 'graphql'; -import { connectAdmin, connectUser, connectAgent } from './_loginHelper'; -import { db } from '../../src/database/database'; -import { objStore } from '../../src/objStore/objStore'; -import { Router } from '../../src/server/router'; -import path from 'path'; -import { v4 as uuid } from 'uuid'; -import { errorCodes } from '../../src/graphql/errors'; -import { Db, MongoClient } from 'mongodb'; -import { studyType, IStudy, IUser, IRole, IFile, IFieldEntry, atomicOperation, IPermissionManagementOptions } from '@itmat-broker/itmat-types'; -import { UPLOAD_FILE, CREATE_STUDY, DELETE_FILE } from '@itmat-broker/itmat-models'; -import { MongoMemoryServer } from 'mongodb-memory-server'; -import { setupDatabase } from '@itmat-broker/itmat-setup'; -import config from '../../config/config.sample.json'; - -if (global.hasMinio) { - let app: Express; - let mongodb: MongoMemoryServer; - let admin: request.SuperTest; - let user: request.SuperTest; - let mongoConnection: MongoClient; - let mongoClient: Db; - - afterAll(async () => { - await db.closeConnection(); - await mongoConnection?.close(); - await mongodb.stop(); - }); - - beforeAll(async () => { // eslint-disable-line no-undef - - /* Creating a in-memory MongoDB instance for testing */ - const dbName = uuid(); - mongodb = await MongoMemoryServer.create({ instance: { dbName } }); - const connectionString = mongodb.getUri(); - await setupDatabase(connectionString, dbName); - - /* Wiring up the backend server */ - config.objectStore.port = global.minioContainerPort; - config.database.mongo_url = connectionString; - config.database.database = dbName; - await db.connect(config.database, MongoClient); - await objStore.connect(config.objectStore); - const router = new Router(config); - await router.init(); - - /* Connect mongo client (for test setup later / retrieve info later) */ - mongoConnection = await MongoClient.connect(connectionString); - mongoClient = mongoConnection.db(dbName); - - /* Connecting clients for testing later */ - app = router.getApp(); - admin = request.agent(app); - user = request.agent(app); - await connectAdmin(admin); - await connectUser(user); - }, 10000); - - describe('FILE API', () => { - let adminId: any; - - beforeAll(async () => { - /* setup: first retrieve the generated user id */ - const result = await mongoClient.collection(config.database.collections.users_collection).find({}, { projection: { id: 1, username: 1 } }).toArray(); - adminId = result.filter((e: { username: string; }) => e.username === 'admin')[0].id; - }); - - describe('UPLOAD AND DOWNLOAD FILE', () => { - describe('UPLOAD FILE', () => { - /* note: a new study is created and a special authorised user for study permissions */ - let createdStudy: { id: any; }; - let createdStudyClinical; - let createdStudyAny: { id: any; }; - let authorisedUser: request.SuperTest; - let authorisedUserProfile: { id: any; otpSecret: any; username?: string; type?: string; firstname?: string; lastname?: string; password?: string; email?: string; description?: string; emailNotificationsActivated?: boolean; organisation?: string; deleted?: null; }; - beforeEach(async () => { - /* setup: create a study to upload file to */ - const studyname = uuid(); - const createStudyRes = await admin.post('/graphql').send({ - query: print(CREATE_STUDY), - variables: { name: studyname, description: 'test description', type: studyType.SENSOR } - }); - expect(createStudyRes.status).toBe(200); - expect(createStudyRes.body.errors).toBeUndefined(); - - const studynameCLINICAL = uuid(); - const createCLINICALStudyRes = await admin.post('/graphql').send({ - query: print(CREATE_STUDY), - variables: { name: studynameCLINICAL, description: 'test description', type: studyType.CLINICAL } - }); - expect(createCLINICALStudyRes.status).toBe(200); - expect(createCLINICALStudyRes.body.errors).toBeUndefined(); - - const studynameANY = uuid(); - const createANYStudyRes = await admin.post('/graphql').send({ - query: print(CREATE_STUDY), - variables: { name: studynameANY, description: 'test description', type: studyType.ANY } - }); - expect(createANYStudyRes.status).toBe(200); - expect(createANYStudyRes.body.errors).toBeUndefined(); - - - /* setup: getting the created study Id */ - createdStudy = await mongoClient.collection(config.database.collections.studies_collection).findOne({ name: studyname }); - expect(createStudyRes.body.data.createStudy).toEqual({ - id: createdStudy.id, - name: studyname, - description: 'test description', - type: studyType.SENSOR - }); - - createdStudyClinical = await mongoClient.collection(config.database.collections.studies_collection).findOne({ name: studynameCLINICAL }); - expect(createCLINICALStudyRes.body.data.createStudy).toEqual({ - id: createdStudyClinical.id, - name: studynameCLINICAL, - description: 'test description', - type: studyType.CLINICAL - }); - - createdStudyAny = await mongoClient.collection(config.database.collections.studies_collection).findOne({ name: studynameANY }); - expect(createANYStudyRes.body.data.createStudy).toEqual({ - id: createdStudyAny.id, - name: studynameANY, - description: 'test description', - type: studyType.ANY - }); - - // create field for both studies - await mongoClient.collection(config.database.collections.field_dictionary_collection).insertMany([{ - fieldId: 'Device_McRoberts', - fieldName: 'Device_McRoberts', - dataType: 'file', - possibleValues: null, - unit: '', - comments: 'Field for deviceid MMM', - id: '8e3fac1d-fa19-44fc-8250-7709d7af5524', - studyId: createdStudy.id, - dateAdded: 1679419198219.61, - dateDeleted: null, - dataVersion: null, - metadata: { - } - }, { - fieldId: 'Device_Axivity', - fieldName: 'Device_Axivity', - dataType: 'file', - possibleValues: null, - unit: '', - comments: 'Field for deviceid AX6', - id: 'ff0c2e60-5086-4111-91ca-475168977ae6', - studyId: createdStudy.id, - dateAdded: 1679419198219.57, - dateDeleted: null, - dataVersion: null, - metadata: { - } - }]); - await mongoClient.collection(config.database.collections.field_dictionary_collection).insertMany([{ - fieldId: 'Device_McRoberts', - fieldName: 'Device_McRoberts', - dataType: 'file', - possibleValues: null, - unit: '', - comments: 'Field for deviceid MMM', - id: '8e3fac1d-fa19-44fc-8250-7709d7af5525', - studyId: createANYStudyRes.id, - dateAdded: 1679419198219.61, - dateDeleted: null, - dataVersion: null, - metadata: { - } - }, { - fieldId: 'Device_Axivity', - fieldName: 'Device_Axivity', - dataType: 'file', - possibleValues: null, - unit: '', - comments: 'Field for deviceid AX6', - id: 'ff0c2e60-5086-4111-91ca-475168977ae5', - studyId: createANYStudyRes.id, - dateAdded: 1679419198219.57, - dateDeleted: null, - dataVersion: null, - metadata: { - } - }]); - /* setup: creating a privileged user */ - const username = uuid(); - authorisedUserProfile = { - username, - type: 'STANDARD', - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}@example.com`, - description: 'I am a new user.', - emailNotificationsActivated: true, - organisation: 'organisation_system', - deleted: null, - id: `new_user_id_${username}` - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(authorisedUserProfile); - - const roleId = uuid(); - const newRole = { - id: roleId, - projectId: null, - studyId: createdStudy.id, - name: `${roleId}_rolename`, - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: true, - uploaders: ['^.*$'], - operations: [atomicOperation.READ, atomicOperation.WRITE], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - users: [authorisedUserProfile.id], - deleted: null - }; - await mongoClient.collection(config.database.collections.roles_collection).insertOne(newRole); - - authorisedUser = request.agent(app); - await connectAgent(authorisedUser, username, 'admin', authorisedUserProfile.otpSecret); - }); - - afterEach(async () => { - await mongoClient.collection(config.database.collections.studies_collection).deleteMany({}); - await mongoClient.collection(config.database.collections.field_dictionary_collection).deleteMany({}); - await mongoClient.collection(config.database.collections.files_collection).deleteMany({}); - }); - - test('Upload file to SENSOR study (admin)', async () => { - /* test: upload file */ - const res = await admin.post('/graphql') - .field('operations', JSON.stringify({ - query: print(UPLOAD_FILE), - variables: { - studyId: createdStudy.id, - file: null, - description: JSON.stringify({ participantId: 'I7N3G6G', deviceId: 'MMM7N3G6G', startDate: 1593827200000, endDate: 1595296000000 }), - fileLength: 21, - hash: 'b0dc2ae76cdea04dcf4be7fcfbe36e2ce8d864fe70a1895c993ce695274ba7a0' - } - })) - .field('map', JSON.stringify({ 1: ['variables.file'] })) - .attach('1', path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt')); - /* setup: geting the created file Id */ - const createdFile = await mongoClient.collection(config.database.collections.files_collection).findOne({ fileName: 'I7N3G6G-MMM7N3G6G-20200704-20200721.txt', studyId: createdStudy.id }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - const { uploadTime, uri, ...uploadFile } = res.body.data.uploadFile; - expect(uri).toBeDefined(); - expect(uploadTime).toBeDefined(); - expect(uploadFile).toEqual({ - id: createdFile.id, - fileName: 'I7N3G6G-MMM7N3G6G-20200704-20200721.txt', - studyId: createdStudy.id, - projectId: null, - fileSize: '21', - description: JSON.stringify({ participantId: 'I7N3G6G', deviceId: 'MMM7N3G6G', startDate: 1593827200000, endDate: 1595296000000 }), - uploadedBy: adminId, - hash: 'b0dc2ae76cdea04dcf4be7fcfbe36e2ce8d864fe70a1895c993ce695274ba7a0', - metadata: { - deviceId: 'MMM7N3G6G', - endDate: 1595296000000, - participantId: 'I7N3G6G', - startDate: 1593827200000 - } - }); - }); - - test('Upload file to study ANY (admin)', async () => { - /* test: upload file */ - const res = await admin.post('/graphql') - .field('operations', JSON.stringify({ - query: print(UPLOAD_FILE), - variables: { - studyId: createdStudyAny.id, - file: null, - description: JSON.stringify({}), - fileLength: 20, - hash: 'f98cfd74547ce10259e94fe12e20168f9c3b59864db6097d95be25dcdf6012c8' - } - })) - .field('map', JSON.stringify({ 1: ['variables.file'] })) - .attach('1', path.join(__dirname, '../filesForTests/RandomFile.txt')); - - /* setup: geting the created file Id */ - const createdFile = await mongoClient.collection(config.database.collections.files_collection).findOne({ fileName: 'RandomFile.txt', studyId: createdStudyAny.id }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - const { uploadTime, uri, ...uploadFile } = res.body.data.uploadFile; - expect(uri).toBeDefined(); - expect(uploadTime).toBeDefined(); - expect(uploadFile).toEqual({ - id: createdFile.id, - fileName: 'RandomFile.txt', - studyId: createdStudyAny.id, - projectId: null, - fileSize: '20', - description: JSON.stringify({}), - uploadedBy: adminId, - hash: 'f98cfd74547ce10259e94fe12e20168f9c3b59864db6097d95be25dcdf6012c8', - metadata: {} - }); - }); - - test('Upload file to study (user with no privilege) (should fail)', async () => { - /* test: upload file */ - const res = await user.post('/graphql') - .field('operations', JSON.stringify({ - query: print(UPLOAD_FILE), - variables: { - studyId: createdStudy.id, - file: null, - description: JSON.stringify({ participantId: 'I7N3G6G', deviceId: 'MMM7N3G6G', startDate: 1593827200000, endDate: 1595296000000 }), - fileLength: 21, - hash: 'b0dc2ae76cdea04dcf4be7fcfbe36e2ce8d864fe70a1895c993ce695274ba7a0' - } - })) - .field('map', JSON.stringify({ 1: ['variables.file'] })) - .attach('1', path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt')); - - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.uploadFile).toEqual(null); - }); - - test('Upload file to study (user with privilege)', async () => { - /* test: upload file */ - const res = await authorisedUser.post('/graphql') - .field('operations', JSON.stringify({ - query: print(UPLOAD_FILE), - variables: { - studyId: createdStudy.id, - file: null, - description: JSON.stringify({ participantId: 'I7N3G6G', deviceId: 'MMM7N3G6G', startDate: 1593827200000, endDate: 1595296000000 }), - fileLength: 21, - hash: 'b0dc2ae76cdea04dcf4be7fcfbe36e2ce8d864fe70a1895c993ce695274ba7a0' - } - })) - .field('map', JSON.stringify({ 1: ['variables.file'] })) - .attach('1', path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt')); - - /* setup: geting the created file Id */ - const createdFile = await mongoClient.collection(config.database.collections.files_collection).findOne({ fileName: 'I7N3G6G-MMM7N3G6G-20200704-20200721.txt', studyId: createdStudy.id }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - const { uploadTime, uri, ...uploadFile } = res.body.data.uploadFile; - expect(uri).toBeDefined(); - expect(uploadTime).toBeDefined(); - expect(uploadFile).toEqual({ - id: createdFile.id, - fileName: 'I7N3G6G-MMM7N3G6G-20200704-20200721.txt', - studyId: createdStudy.id, - projectId: null, - fileSize: '21', - description: JSON.stringify({ participantId: 'I7N3G6G', deviceId: 'MMM7N3G6G', startDate: 1593827200000, endDate: 1595296000000 }), - uploadedBy: authorisedUserProfile.id, - hash: 'b0dc2ae76cdea04dcf4be7fcfbe36e2ce8d864fe70a1895c993ce695274ba7a0', - metadata: { - deviceId: 'MMM7N3G6G', - endDate: 1595296000000, - participantId: 'I7N3G6G', - startDate: 1593827200000 - } - }); - }); - - test('Upload a empty file (admin)', async () => { - /* test: upload file */ - const res = await admin.post('/graphql') - .field('operations', JSON.stringify({ - query: print(UPLOAD_FILE), - variables: { - studyId: createdStudy.id, - file: null, - description: JSON.stringify({ participantId: 'IR6R4AR', deviceId: 'AX6VJH6F6', startDate: 1590976000000, endDate: 1593740800000 }), - fileLength: 0, - hash: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' - } - })) - .field('map', JSON.stringify({ 1: ['variables.file'] })) - .attach('1', path.join(__dirname, '../filesForTests/IR6R4AR-AX6VJH6F6-20200601-20200703.txt')); - - /* setup: geting the created file Id */ - const createdFile = await mongoClient.collection(config.database.collections.files_collection).findOne({ fileName: 'IR6R4AR-AX6VJH6F6-20200601-20200703.txt', studyId: createdStudy.id }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - const { uploadTime, uri, ...uploadFile } = res.body.data.uploadFile; - expect(uri).toBeDefined(); - expect(uploadTime).toBeDefined(); - expect(uploadFile).toEqual({ - id: createdFile.id, - fileName: 'IR6R4AR-AX6VJH6F6-20200601-20200703.txt', - studyId: createdStudy.id, - projectId: null, - fileSize: '0', - description: JSON.stringify({ participantId: 'IR6R4AR', deviceId: 'AX6VJH6F6', startDate: 1590976000000, endDate: 1593740800000 }), - uploadedBy: adminId, - hash: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', - metadata: { - deviceId: 'AX6VJH6F6', - endDate: 1593740800000, - participantId: 'IR6R4AR', - startDate: 1590976000000 - } - }); - }); - - test('Upload a file with incorrect hash', async () => { - /* test: upload file */ - const res = await admin.post('/graphql') - .field('operations', JSON.stringify({ - query: print(UPLOAD_FILE), - variables: { - studyId: createdStudy.id, - file: null, - description: JSON.stringify({ participantId: 'IR6R4AR', deviceId: 'AX6VJH6F6', startDate: 1590976000000, endDate: 1593740800000 }), - fileLength: 21, - hash: '4ae25be36354ee0aec8dc8deac3f279d2e9d6415361da996cf57eb6142cfb1a4' - } - })) - .field('map', JSON.stringify({ 1: ['variables.file'] })) - .attach('1', path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt')); - - /* setup: geting the created file Id */ - expect(res.status).toBe(200); - - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe('File hash not match'); - expect(res.body.data.uploadFile).toEqual(null); - }); - - test('File size mismatch with actual read bytes', async () => { - /* test: upload file */ - const res = await admin.post('/graphql') - .field('operations', JSON.stringify({ - query: print(UPLOAD_FILE), - variables: { - studyId: createdStudy.id, - file: null, - description: JSON.stringify({ participantId: 'I7N3G6G', deviceId: 'MMM7N3G6G', startDate: 1593827200000, endDate: 1595296000000 }), - fileLength: 10 - } - })) - .field('map', JSON.stringify({ 1: ['variables.file'] })) - .attach('1', path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt')); - - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe('File size mismatch'); - expect(res.body.data.uploadFile).toEqual(null); - }); - - }); - - describe('DOWNLOAD FILES', () => { - /* note: a new study is created and a non-empty text file is uploaded before each test */ - let createdStudy; - let createdFile: { id: any; }; - let authorisedUser: request.SuperTest; - let authorisedUserProfile; - - beforeEach(async () => { - /* setup: create studies to upload file to */ - const studyname = uuid(); - const createStudyRes = await admin.post('/graphql').send({ - query: print(CREATE_STUDY), - variables: { name: studyname, description: 'test description', type: studyType.SENSOR } - }); - expect(createStudyRes.status).toBe(200); - expect(createStudyRes.body.errors).toBeUndefined(); - - /* setup: getting the created study Id */ - createdStudy = await mongoClient.collection(config.database.collections.studies_collection).findOne({ name: studyname }); - expect(createStudyRes.body.data.createStudy).toEqual({ - id: createdStudy.id, - name: studyname, - description: 'test description', - type: studyType.SENSOR - }); - await mongoClient.collection(config.database.collections.field_dictionary_collection).insertMany([{ - fieldId: 'Device_McRoberts', - fieldName: 'Device_McRoberts', - dataType: 'file', - possibleValues: null, - unit: '', - comments: 'Field for deviceid MMM', - id: '8e3fac1d-fa19-44fc-8250-7709d7af5524', - studyId: createdStudy.id, - dateAdded: 1679419198219.61, - dateDeleted: null, - dataVersion: null, - metadata: { - } - }, { - fieldId: 'Device_Axivity', - fieldName: 'Device_Axivity', - dataType: 'file', - possibleValues: null, - unit: '', - comments: 'Field for deviceid AX6', - id: 'ff0c2e60-5086-4111-91ca-475168977ae6', - studyId: createdStudy.id, - dateAdded: 1679419198219.57, - dateDeleted: null, - dataVersion: null, - metadata: { - } - }]); - /* setup: upload file (would be better to upload not via app api but will do for now) */ - const res = await admin.post('/graphql') - .field('operations', JSON.stringify({ - query: print(UPLOAD_FILE), - variables: { - studyId: createdStudy.id, - file: null, - description: JSON.stringify({ participantId: 'I7N3G6G', deviceId: 'MMM7N3G6G', startDate: 1593827200000, endDate: 1595296000000 }), - fileLength: 21, - hash: 'b0dc2ae76cdea04dcf4be7fcfbe36e2ce8d864fe70a1895c993ce695274ba7a0' - } - })) - .field('map', JSON.stringify({ 1: ['variables.file'] })) - .attach('1', path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt')); - - /* setup: geting the created file Id */ - createdFile = res.body.data.uploadFile; - if (!createdFile) - throw 'Test file could not be retreived.'; - - /* setup: creating a privileged user */ - const username = uuid(); - authorisedUserProfile = { - username, - type: 'STANDARD', - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}@example.com`, - description: 'I am a new user.', - emailNotificationsActivated: true, - organisation: 'organisation_system', - deleted: null, - id: `new_user_id_${username}` - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(authorisedUserProfile); - - const roleId = uuid(); - const newRole = { - id: roleId, - projectId: null, - studyId: createdStudy.id, - name: `${roleId}_rolename`, - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - users: [authorisedUserProfile.id], - deleted: null - }; - await mongoClient.collection(config.database.collections.roles_collection).insertOne(newRole); - - authorisedUser = request.agent(app); - await connectAgent(authorisedUser, username, 'admin', authorisedUserProfile.otpSecret); - }); - - afterEach(async () => { - await mongoClient.collection(config.database.collections.studies_collection).deleteMany({}); - await mongoClient.collection(config.database.collections.field_dictionary_collection).deleteMany({}); - await mongoClient.collection(config.database.collections.files_collection).deleteMany({}); - }); - - test('Download file from study (admin)', async () => { - const res = await admin.get(`/file/${createdFile.id}`); - expect(res.status).toBe(200); - expect(res.headers['content-type']).toBe('application/download'); - expect(res.headers['content-disposition']).toBe('attachment; filename="I7N3G6G-MMM7N3G6G-20200704-20200721.txt"'); - expect(res.text).toBe('just testing I7N3G6G.'); - }); - - test('Download file from study (user with privilege)', async () => { - const res = await authorisedUser.get(`/file/${createdFile.id}`); - expect(res.status).toBe(200); - expect(res.headers['content-type']).toBe('application/download'); - expect(res.headers['content-disposition']).toBe('attachment; filename="I7N3G6G-MMM7N3G6G-20200704-20200721.txt"'); - expect(res.text).toBe('just testing I7N3G6G.'); - }); - - test('Download file from study (not logged in)', async () => { - const loggedoutUser = request.agent(app); - const res = await loggedoutUser.get(`/file/${createdFile.id}`); - expect(res.status).toBe(403); - expect(res.body).toEqual({ error: 'Please log in.' }); - }); - - test('Download file from study (user with no privilege) (should fail)', async () => { - const res = await user.get(`/file/${createdFile.id}`); - expect(res.status).toBe(404); - expect(res.body).toEqual({ error: 'File not found or you do not have the necessary permission.' }); - }); - - test('Download an non-existent file from study (admin) (should fail)', async () => { - const res = await admin.get('/file/fakefileid'); - expect(res.status).toBe(404); - expect(res.body).toEqual({ error: 'File not found or you do not have the necessary permission.' }); - }); - - test('Download an non-existent file from study (not logged in)', async () => { - const loggedoutUser = request.agent(app); - const res = await loggedoutUser.get('/file/fakefileid'); - expect(res.status).toBe(403); - expect(res.body).toEqual({ error: 'Please log in.' }); - }); - - test('Download an non-existent file from study (user without privilege) (should fail)', async () => { - const res = await user.get('/file/fakefileid'); - expect(res.status).toBe(404); - expect(res.body).toEqual({ error: 'File not found or you do not have the necessary permission.' }); - }); - - test('Download an non-existent file from study (user with privilege) (should fail)', async () => { - const res = await authorisedUser.get('/file/fakefileid'); - expect(res.status).toBe(404); - expect(res.body).toEqual({ error: 'File not found or you do not have the necessary permission.' }); - }); - }); - - describe('DELETE FILES', () => { - let createdStudy; - let createdFile: { id: any; }; - let authorisedUser: request.SuperTest; - let authorisedUserProfile; - beforeEach(async () => { - /* Clear old values */ - await db.collections!.roles_collection.deleteMany({}); - /* setup: create a study to upload file to */ - const studyname = uuid(); - const createStudyRes = await admin.post('/graphql').send({ - query: print(CREATE_STUDY), - variables: { name: studyname, description: 'test description', type: studyType.SENSOR } - }); - expect(createStudyRes.status).toBe(200); - expect(createStudyRes.body.errors).toBeUndefined(); - - /* setup: getting the created study Id */ - createdStudy = await mongoClient.collection(config.database.collections.studies_collection).findOne({ name: studyname }); - expect(createStudyRes.body.data.createStudy).toEqual({ - id: createdStudy.id, - name: studyname, - description: 'test description', - type: studyType.SENSOR - }); - await mongoClient.collection(config.database.collections.field_dictionary_collection).insertMany([{ - fieldId: 'Device_McRoberts', - fieldName: 'Device_McRoberts', - dataType: 'file', - possibleValues: null, - unit: '', - comments: 'Field for deviceid MMM', - id: '8e3fac1d-fa19-44fc-8250-7709d7af5524', - studyId: createdStudy.id, - dateAdded: 1679419198219.61, - dateDeleted: null, - dataVersion: null, - metadata: { - } - }, { - fieldId: 'Device_Axivity', - fieldName: 'Device_Axivity', - dataType: 'file', - possibleValues: null, - unit: '', - comments: 'Field for deviceid AX6', - id: 'ff0c2e60-5086-4111-91ca-475168977ae6', - studyId: createdStudy.id, - dateAdded: 1679419198219.57, - dateDeleted: null, - dataVersion: null, - metadata: { - } - }]); - /* setup: upload file (would be better to upload not via app api but will do for now) */ - await admin.post('/graphql') - .field('operations', JSON.stringify({ - query: print(UPLOAD_FILE), - variables: { - studyId: createdStudy.id, - file: null, - description: JSON.stringify({ participantId: 'I7N3G6G', deviceId: 'MMM7N3G6G', startDate: 1593827200000, endDate: 1595296000000 }), - fileLength: 21, - hash: 'b0dc2ae76cdea04dcf4be7fcfbe36e2ce8d864fe70a1895c993ce695274ba7a0' - } - })) - .field('map', JSON.stringify({ 1: ['variables.file'] })) - .attach('1', path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt')); - - /* setup: geting the created file Id */ - createdFile = await mongoClient.collection(config.database.collections.files_collection).findOne({ fileName: 'I7N3G6G-MMM7N3G6G-20200704-20200721.txt', studyId: createdStudy.id, deleted: null }); - - /* before test: can download file */ - const res = await admin.get(`/file/${createdFile.id}`); - expect(res.status).toBe(200); - expect(res.headers['content-type']).toBe('application/download'); - expect(res.headers['content-disposition']).toBe('attachment; filename="I7N3G6G-MMM7N3G6G-20200704-20200721.txt"'); - expect(res.text).toBe('just testing I7N3G6G.'); - - /* setup: creating a privileged user */ - const username = uuid(); - authorisedUserProfile = { - username, - type: 'STANDARD', - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}@example.com`, - description: 'I am a new user.', - emailNotificationsActivated: true, - organisation: 'organisation_system', - deleted: null, - id: `new_user_id_${username}` - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(authorisedUserProfile); - - const roleId = uuid(); - const newRole = { - id: roleId, - projectId: null, - studyId: createdStudy.id, - name: `${roleId}_rolename`, - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: true, - uploaders: ['^.*$'], - operations: [atomicOperation.READ, atomicOperation.WRITE], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - users: [authorisedUserProfile.id], - deleted: null - }; - await mongoClient.collection(config.database.collections.roles_collection).insertOne(newRole); - - authorisedUser = request.agent(app); - await connectAgent(authorisedUser, username, 'admin', authorisedUserProfile.otpSecret); - }); - - afterEach(async () => { - await mongoClient.collection(config.database.collections.studies_collection).deleteMany({}); - await mongoClient.collection(config.database.collections.field_dictionary_collection).deleteMany({}); - await mongoClient.collection(config.database.collections.files_collection).deleteMany({}); - }); - - test('Delete file from study (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(DELETE_FILE), - variables: { fileId: createdFile.id } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.deleteFile).toEqual({ successful: true }); - - // const downloadFileRes = await admin.get(`/file/${createdFile.id}`); - // expect(downloadFileRes.status).toBe(404); - // expect(downloadFileRes.body).toEqual({ error: 'File not found or you do not have the necessary permission.' }); - }); - - test('Delete file from study (user with privilege)', async () => { - const res = await authorisedUser.post('/graphql').send({ - query: print(DELETE_FILE), - variables: { fileId: createdFile.id } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.deleteFile).toEqual({ successful: true }); - - // const downloadFileRes = await authorisedUser.get(`/file/${createdFile.id}`); - // expect(downloadFileRes.status).toBe(404); - // expect(downloadFileRes.body).toEqual({ error: 'File not found or you do not have the necessary permission.' }); - }); - - test('Delete file from study (user with no privilege) (should fail)', async () => { - const res = await user.post('/graphql').send({ - query: print(DELETE_FILE), - variables: { fileId: createdFile.id } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.deleteFile).toBe(null); - - const downloadFileRes = await admin.get(`/file/${createdFile.id}`); - expect(downloadFileRes.status).toBe(200); - expect(downloadFileRes.headers['content-type']).toBe('application/download'); - expect(downloadFileRes.headers['content-disposition']).toBe('attachment; filename="I7N3G6G-MMM7N3G6G-20200704-20200721.txt"'); - expect(downloadFileRes.text).toBe('just testing I7N3G6G.'); - }); - - test('Delete an non-existent file from study (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(DELETE_FILE), - variables: { fileId: 'nosuchfile' } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - expect(res.body.data.deleteFile).toBe(null); - }); - - test('Delete an non-existent file from study (user with privilege)', async () => { - const res = await authorisedUser.post('/graphql').send({ - query: print(DELETE_FILE), - variables: { fileId: 'nosuchfile' } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - expect(res.body.data.deleteFile).toBe(null); - }); - - test('Delete an non-existent file from study (user with no privilege)', async () => { - const res = await user.post('/graphql').send({ - query: print(DELETE_FILE), - variables: { fileId: 'nosuchfile' } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - expect(res.body.data.deleteFile).toBe(null); - }); - }); - }); - - // describe('FILE PERMISSION FOR PROJECTS', () => { - // }); - }); -} else - test(`${__filename.split(/[\\/]/).pop()} skipped because it requires Minio on Docker`, () => { - expect(true).toBe(true); - }); diff --git a/packages/itmat-interface/test/serverTests/job.test.ts b/packages/itmat-interface/test/serverTests/job.test.ts deleted file mode 100644 index 7da568489..000000000 --- a/packages/itmat-interface/test/serverTests/job.test.ts +++ /dev/null @@ -1,776 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-nocheck - -import request from 'supertest'; -import { print } from 'graphql'; -import { connectAdmin, connectUser, connectAgent } from './_loginHelper'; -import { db } from '../../src/database/database'; -import { Router } from '../../src/server/router'; -import { v4 as uuid } from 'uuid'; -import { errorCodes } from '../../src/graphql/errors'; -import { Db, MongoClient } from 'mongodb'; -import { IJobEntry, IUser, IRole, IStudy, IProject, IQueryEntry, IFile, atomicOperation, IPermissionManagementOptions } from '@itmat-broker/itmat-types'; -import { CREATE_DATA_CURATION_JOB, CREATE_FIELD_CURATION_JOB, CREATE_QUERY_CURATION_JOB } from '@itmat-broker/itmat-models'; -import { MongoMemoryServer } from 'mongodb-memory-server'; -import { setupDatabase } from '@itmat-broker/itmat-setup'; -import config from '../../config/config.sample.json'; -import { Express } from 'express'; - -let app: Express; -let mongodb: MongoMemoryServer; -let admin: request.SuperTest; -let user: request.SuperTest; -let mongoConnection: MongoClient; -let mongoClient: Db; - -afterAll(async () => { - await db.closeConnection(); - await mongoConnection?.close(); - await mongodb.stop(); -}); - -beforeAll(async () => { // eslint-disable-line no-undef - /* Creating a in-memory MongoDB instance for testing */ - const dbName = uuid(); - mongodb = await MongoMemoryServer.create({ instance: { dbName } }); - const connectionString = mongodb.getUri(); - await setupDatabase(connectionString, dbName); - - /* Wiring up the backend server */ - config.database.mongo_url = connectionString; - config.database.database = dbName; - await db.connect(config.database, MongoClient); - const router = new Router(config); - await router.init(); - - /* Connect mongo client (for test setup later / retrieve info later) */ - mongoConnection = await MongoClient.connect(connectionString); - mongoClient = mongoConnection.db(dbName); - - /* Connecting clients for testing later */ - app = router.getApp(); - admin = request.agent(app); - user = request.agent(app); - await connectAdmin(admin); - await connectUser(user); -}); - -describe('JOB API', () => { - let adminId: any; - let createdStudy: { id: any; name?: string; createdBy?: string; lastModified?: number; deleted?: null; currentDataVersion?: number; dataVersions?: never[]; }; - let createdProject: { id: any; name?: string; createdBy?: string; lastModified?: number; studyId?: string; deleted?: null; patientMapping?: Record; }; - let createdQuery: { id: any; requester?: string; queryString?: { date_requested: string; }; studyId?: string; projectId?: string; status?: string; error?: null; cancelled?: boolean; data_requested?: never[]; cohort?: never[][]; new_fields?: never[]; queryResult?: never[]; }; - let createdFile: { id: any; fileName?: string; studyId?: string; fileSize?: string; description?: string; uploadedBy?: any; uri?: string; deleted?: null; }; - - beforeAll(async () => { - /* setup: first retrieve the generated user id */ - const result = await mongoClient.collection(config.database.collections.users_collection).find({}, { projection: { id: 1, username: 1 } }).toArray(); - adminId = result.filter((e: { username: string; }) => e.username === 'admin')[0].id; - }); - - describe('CREATE DATA CURATION API', () => { - beforeEach(async () => { - /* setup: create a study to upload file to */ - const studyname = uuid(); - createdStudy = { - id: `new_study_id_${studyname}`, - name: studyname, - createdBy: 'admin', - lastModified: 200000002, - deleted: null, - currentDataVersion: -1, - dataVersions: [] - }; - await mongoClient.collection(config.database.collections.studies_collection).insertOne(createdStudy); - - /* setup: created file entry in the database */ - const fileName = uuid(); - createdFile = { - id: `new_file_id_${fileName}`, - fileName: fileName + '.csv', - studyId: createdStudy.id, - fileSize: '1000', - description: 'just a test file here.', - uploadedBy: adminId, - uri: `new_file_uri_${fileName}`, - deleted: null - }; - await mongoClient.collection(config.database.collections.files_collection).insertOne(createdFile); - }); - - test('Create a data curation job with tag (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(CREATE_DATA_CURATION_JOB), - variables: { - file: [createdFile.id], - studyId: createdStudy.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - const job = await mongoClient.collection(config.database.collections.jobs_collection).findOne({ - receivedFiles: createdFile.id - }); - expect(res.body.data.createDataCurationJob).toEqual([{ - id: job.id, - jobType: 'DATA_UPLOAD_CSV', - projectId: null, - studyId: createdStudy.id, - requester: adminId, - requestTime: job.requestTime, - receivedFiles: [createdFile.id], - status: 'QUEUED', - error: null, - cancelled: false, - cancelledTime: null, - data: null - }]); - }); - - test('Create a data curation job without tag (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(CREATE_DATA_CURATION_JOB), - variables: { - file: [createdFile.id], - studyId: createdStudy.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - const job = await mongoClient.collection(config.database.collections.jobs_collection).findOne({ - receivedFiles: createdFile.id - }); - expect(res.body.data.createDataCurationJob).toEqual([{ - id: job.id, - jobType: 'DATA_UPLOAD_CSV', - projectId: null, - studyId: createdStudy.id, - requester: adminId, - requestTime: job.requestTime, - receivedFiles: [createdFile.id], - status: 'QUEUED', - error: null, - cancelled: false, - cancelledTime: null, - data: null - }]); - }); - - test('Create a data curation job (user with no privilege)', async () => { - const res = await user.post('/graphql').send({ - query: print(CREATE_DATA_CURATION_JOB), - variables: { - file: [createdFile.id], - studyId: createdStudy.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - const job = await mongoClient.collection(config.database.collections.jobs_collection).findOne({ - receivedFiles: createdFile.id - }); - expect(job).toBe(null); - }); - - test('Create a data curation job (user with privilege)', async () => { - /* setup: creating a privileged user */ - const username = uuid(); - const authorisedUserProfile = { - username, - type: 'STANDARD', - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}@example.com`, - description: 'I am a new user.', - emailNotificationsActivated: true, - organisation: 'organisation_system', - deleted: null, - id: `new_user_id_${username}` - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(authorisedUserProfile); - - const roleId = uuid(); - const newRole = { - id: roleId, - projectId: null, - studyId: createdStudy.id, - name: `${roleId}_rolename`, - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [atomicOperation.READ, atomicOperation.WRITE], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - users: [authorisedUserProfile.id], - deleted: null - }; - await mongoClient.collection(config.database.collections.roles_collection).insertOne(newRole); - - const authorisedUser = request.agent(app); - await connectAgent(authorisedUser, username, 'admin', authorisedUserProfile.otpSecret); - - /* test */ - const res = await authorisedUser.post('/graphql').send({ - query: print(CREATE_DATA_CURATION_JOB), - variables: { - file: [createdFile.id], - studyId: createdStudy.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - const job = await mongoClient.collection(config.database.collections.jobs_collection).findOne({ - receivedFiles: createdFile.id - }); - expect(res.body.data.createDataCurationJob).toEqual([{ - id: job.id, - jobType: 'DATA_UPLOAD_CSV', - projectId: null, - studyId: createdStudy.id, - requester: authorisedUserProfile.id, - requestTime: job.requestTime, - receivedFiles: [createdFile.id], - status: 'QUEUED', - error: null, - cancelled: false, - cancelledTime: null, - data: null - }]); - }); - - test('Create a data curation job with a non-existent file id (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(CREATE_DATA_CURATION_JOB), - variables: { - file: ['fakeFile'], - studyId: createdStudy.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - const job = await mongoClient.collection(config.database.collections.jobs_collection).findOne({ - receivedFiles: createdFile.id - }); - expect(job).toBe(null); - }); - - test('Create a data curation job with a non-existent study id (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(CREATE_DATA_CURATION_JOB), - variables: { - file: [createdFile.id], - studyId: 'fakeStudyId' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe('Study does not exist.'); - const job = await mongoClient.collection(config.database.collections.jobs_collection).findOne({ - receivedFiles: createdFile.id - }); - expect(job).toBe(null); - }); - - test('Create a data curation job with a non-existent study id (user)', async () => { - const res = await user.post('/graphql').send({ - query: print(CREATE_DATA_CURATION_JOB), - variables: { - file: [createdFile.id], - studyId: createdStudy.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - const job = await mongoClient.collection(config.database.collections.jobs_collection).findOne({ - receivedFiles: createdFile.id - }); - expect(job).toBe(null); - }); - }); - - describe('CREATE FIELD CURATION API', () => { - beforeEach(async () => { - /* setup: create a study to upload file to */ - const studyname = uuid(); - createdStudy = { - id: `new_study_id_${studyname}`, - name: studyname, - createdBy: 'admin', - lastModified: 200000002, - deleted: null, - currentDataVersion: -1, - dataVersions: [] - }; - await mongoClient.collection(config.database.collections.studies_collection).insertOne(createdStudy); - - /* setup: created file entry in the database */ - const fileName = uuid(); - createdFile = { - id: `new_file_id_${fileName}`, - fileName: fileName + '.csv', - studyId: createdStudy.id, - fileSize: '1000', - description: 'just a test file here.', - uploadedBy: adminId, - uri: `new_file_uri_${fileName}`, - deleted: null - }; - await mongoClient.collection(config.database.collections.files_collection).insertOne(createdFile); - }); - - test('Create a field curation job with tag (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(CREATE_FIELD_CURATION_JOB), - variables: { - file: createdFile.id, - studyId: createdStudy.id, - tag: 'mockTag' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - const job = await mongoClient.collection(config.database.collections.jobs_collection).findOne({ - receivedFiles: createdFile.id - }); - expect(res.body.data.createFieldCurationJob).toEqual({ - id: job.id, - jobType: 'FIELD_INFO_UPLOAD', - projectId: null, - studyId: createdStudy.id, - requester: adminId, - requestTime: job.requestTime, - receivedFiles: [createdFile.id], - status: 'QUEUED', - error: null, - cancelled: false, - cancelledTime: null, - data: { - tag: 'mockTag' - } - }); - }); - - test('Create a field curation job without tag (admin) (should fail)', async () => { - const res = await admin.post('/graphql').send({ - query: print(CREATE_FIELD_CURATION_JOB), - variables: { - file: createdFile.id, - studyId: createdStudy.id, - tag: undefined - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - }); - - test('Create a data curation job (user with no privilege)', async () => { - const res = await user.post('/graphql').send({ - query: print(CREATE_FIELD_CURATION_JOB), - variables: { - file: createdFile.id, - studyId: createdStudy.id, - tag: 'mockTag' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - const job = await mongoClient.collection(config.database.collections.jobs_collection).findOne({ - receivedFiles: createdFile.id - }); - expect(job).toBe(null); - }); - - test('Create a field curation job (user with privilege)', async () => { - /* setup: creating a privileged user */ - const username = uuid(); - const authorisedUserProfile = { - username, - type: 'STANDARD', - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}@example.com`, - description: 'I am a new user.', - emailNotificationsActivated: true, - organisation: 'organisation_system', - deleted: null, - id: `new_user_id_${username}` - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(authorisedUserProfile); - - const roleId = uuid(); - const newRole = { - id: roleId, - projectId: null, - studyId: createdStudy.id, - name: `${roleId}_rolename`, - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [atomicOperation.READ, atomicOperation.WRITE], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - users: [authorisedUserProfile.id], - deleted: null - }; - await mongoClient.collection(config.database.collections.roles_collection).insertOne(newRole); - - const authorisedUser = request.agent(app); - await connectAgent(authorisedUser, username, 'admin', authorisedUserProfile.otpSecret); - - /* test */ - const res = await authorisedUser.post('/graphql').send({ - query: print(CREATE_FIELD_CURATION_JOB), - variables: { - file: createdFile.id, - studyId: createdStudy.id, - tag: 'mockTag' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - const job = await mongoClient.collection(config.database.collections.jobs_collection).findOne({ - receivedFiles: createdFile.id - }); - expect(res.body.data.createFieldCurationJob).toEqual({ - id: job.id, - studyId: createdStudy.id, - projectId: null, - jobType: 'FIELD_INFO_UPLOAD', - requester: authorisedUserProfile.id, - requestTime: job.requestTime, - receivedFiles: [createdFile.id], - status: 'QUEUED', - error: null, - cancelled: false, - cancelledTime: null, - data: { - tag: 'mockTag' - } - }); - }); - - test('Create a field curation job with a non-existent file id (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(CREATE_FIELD_CURATION_JOB), - variables: { - file: 'fake_file_id', - studyId: createdStudy.id, - tag: 'just_a_tag' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - const job = await mongoClient.collection(config.database.collections.jobs_collection).findOne({ - receivedFiles: createdFile.id - }); - expect(job).toBe(null); - }); - - test('Create a field curation job with a non-existent study id (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(CREATE_FIELD_CURATION_JOB), - variables: { - file: createdFile.id, - studyId: 'fake_study_id', - tag: 'just_a_tag' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe('Study does not exist.'); - const job = await mongoClient.collection(config.database.collections.jobs_collection).findOne({ - receivedFiles: createdFile.id - }); - expect(job).toBe(null); - }); - - test('Create a field curation job with a non-existent study id (user)', async () => { - const res = await user.post('/graphql').send({ - query: print(CREATE_FIELD_CURATION_JOB), - variables: { - file: createdFile.id, - studyId: 'fake_study_id', - tag: 'just_a_tag' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - const job = await mongoClient.collection(config.database.collections.jobs_collection).findOne({ - receivedFiles: createdFile.id - }); - expect(job).toBe(null); - }); - }); - - describe('CREATE QUERY CURATION API', () => { - beforeEach(async () => { - /* setup: create a study to upload file to */ - const studyname = uuid(); - createdStudy = { - id: `new_study_id_${studyname}`, - name: studyname, - createdBy: 'admin', - lastModified: 200000002, - deleted: null, - currentDataVersion: -1, - dataVersions: [] - }; - await mongoClient.collection(config.database.collections.studies_collection).insertOne(createdStudy); - - /* setup: create a study to upload file to */ - const projectname = uuid(); - createdProject = { - id: `new_project_id_${projectname}`, - name: projectname, - createdBy: 'admin', - lastModified: 200000002, - studyId: createdStudy.id, - deleted: null, - patientMapping: {} - }; - await mongoClient.collection(config.database.collections.projects_collection).insertOne(createdProject); - - - /* setup: created query entry in the database */ - const queryId = uuid(); - createdQuery = { - id: `new_query_id_${queryId}`, - requester: 'admin', - queryString: { date_requested: 'test_query_string' }, - studyId: createdStudy.id, - projectId: createdProject.id, - status: 'QUEUED', - error: null, - cancelled: false, - data_requested: [], - cohort: [[]], - new_fields: [], - queryResult: [] - }; - await mongoClient.collection(config.database.collections.queries_collection).insertOne(createdQuery); - }); - - test('Create a query curation job (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(CREATE_QUERY_CURATION_JOB), - variables: { - queryId: createdQuery.id, - studyId: createdStudy.id, - projectId: createdProject.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - const job = await mongoClient.collection(config.database.collections.jobs_collection).findOne({ - 'data.queryId': createdQuery.id - }); - expect(res.body.data.createQueryCurationJob).toEqual({ - id: job.id, - studyId: createdStudy.id, - projectId: null, - jobType: 'QUERY_EXECUTION', - requester: adminId, - requestTime: job.requestTime, - receivedFiles: [], - status: 'QUEUED', - error: null, - cancelled: false, - cancelledTime: null, - data: { - queryId: [createdQuery.id], - projectId: createdProject.id, - studyId: createdStudy.id - } - }); - }); - - test('Create a query curation job (user with no privilege)', async () => { - const res = await user.post('/graphql').send({ - query: print(CREATE_QUERY_CURATION_JOB), - variables: { - queryId: createdQuery.id, - studyId: createdStudy.id, - projectId: createdProject.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - const job = await mongoClient.collection(config.database.collections.jobs_collection).findOne({ - 'data.queryId': createdQuery.id - }); - expect(job).toBe(null); - }); - - test('Create a query curation job (user with privilege)', async () => { - /* setup: creating a privileged user */ - const username = uuid(); - const authorisedUserProfile = { - username, - type: 'STANDARD', - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}@example.com`, - description: 'I am a new user.', - emailNotificationsActivated: true, - organisation: 'organisation_system', - deleted: null, - id: `new_user_id_${username}` - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(authorisedUserProfile); - - const roleId = uuid(); - const newRole = { - id: roleId, - projectId: createdProject.id, - studyId: createdStudy.id, - name: `${roleId}_rolename`, - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [atomicOperation.READ, atomicOperation.WRITE], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - users: [authorisedUserProfile.id], - deleted: null - }; - await mongoClient.collection(config.database.collections.roles_collection).insertOne(newRole); - - await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: roleId }); - const authorisedUser = request.agent(app); - await connectAgent(authorisedUser, username, 'admin', authorisedUserProfile.otpSecret); - - /* test */ - const res = await authorisedUser.post('/graphql').send({ - query: print(CREATE_QUERY_CURATION_JOB), - variables: { - queryId: createdQuery.id, - studyId: createdStudy.id, - projectId: createdProject.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - const job = await mongoClient.collection(config.database.collections.jobs_collection).findOne({ - 'data.queryId': createdQuery.id - }); - expect(res.body.data.createQueryCurationJob).toEqual({ - id: job.id, - studyId: createdStudy.id, - projectId: null, - jobType: 'QUERY_EXECUTION', - requester: authorisedUserProfile.id, - requestTime: job.requestTime, - receivedFiles: [], - status: 'QUEUED', - error: null, - cancelled: false, - cancelledTime: null, - data: { - queryId: [createdQuery.id], - projectId: createdProject.id, - studyId: createdStudy.id - } - }); - }); - - test('Create a query curation job with a non-existent study id (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(CREATE_QUERY_CURATION_JOB), - variables: { - queryId: createdQuery.id, - studyId: 'fake_study_id', - projectId: createdProject.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe('Study does not exist.'); - const job = await mongoClient.collection(config.database.collections.jobs_collection).findOne({ - 'data.queryId': createdQuery.id - }); - expect(job).toBe(null); - }); - - test('Create a query curation job with a non-existent project id (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(CREATE_QUERY_CURATION_JOB), - variables: { - queryId: createdQuery.id, - studyId: createdStudy.id, - projectId: 'fake_project_id' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe('Project does not exist.'); - const job = await mongoClient.collection(config.database.collections.jobs_collection).findOne({ - 'data.queryId': createdQuery.id - }); - expect(job).toBe(null); - }); - - test('Create a query curation job with a non-existent query id (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(CREATE_QUERY_CURATION_JOB), - variables: { - queryId: 'fake_query_id', - studyId: createdStudy.id, - projectId: createdProject.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe('Query does not exist.'); - const job = await mongoClient.collection(config.database.collections.jobs_collection).findOne({ - 'data.queryId': createdQuery.id - }); - expect(job).toBe(null); - }); - }); -}); diff --git a/packages/itmat-interface/test/serverTests/log.test.ts b/packages/itmat-interface/test/serverTests/log.test.ts deleted file mode 100644 index f138e510b..000000000 --- a/packages/itmat-interface/test/serverTests/log.test.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { v4 as uuid } from 'uuid'; -import request from 'supertest'; -import { print } from 'graphql'; -import { connectAdmin, connectUser } from './_loginHelper'; -import { db } from '../../src/database/database'; -import { Router } from '../../src/server/router'; -import { Db, MongoClient } from 'mongodb'; -import { MongoMemoryServer } from 'mongodb-memory-server'; -import { setupDatabase } from '@itmat-broker/itmat-setup'; -import config from '../../config/config.sample.json'; -import { errorCodes } from '../../src/graphql/errors'; -import * as mfa from '../../src/utils/mfa'; -import { GET_LOGS, LOGIN, DELETE_USER } from '@itmat-broker/itmat-models'; -import { userTypes, IUser, ILogEntry, LOG_STATUS, LOG_ACTION, LOG_TYPE, USER_AGENT } from '@itmat-broker/itmat-types'; -import { Express } from 'express'; - -let app: Express; -let mongodb: MongoMemoryServer; -let admin: request.SuperTest; -let user: request.SuperTest; -let mongoConnection: MongoClient; -let mongoClient: Db; - -afterAll(async () => { - await db.closeConnection(); - await mongoConnection?.close(); - await mongodb.stop(); - - /* claer all mocks */ - jest.clearAllMocks(); -}); - -beforeAll(async () => { // eslint-disable-line no-undef - /* Creating a in-memory MongoDB instance for testing */ - const dbName = uuid(); - mongodb = await MongoMemoryServer.create({ instance: { dbName } }); - const connectionString = mongodb.getUri(); - await setupDatabase(connectionString, dbName); - - /* Wiring up the backend server */ - config.database.mongo_url = connectionString; - config.database.database = dbName; - await db.connect(config.database, MongoClient); - const router = new Router(config); - await router.init(); - - /* Connect mongo client (for test setup later / retrieve info later) */ - mongoConnection = await MongoClient.connect(connectionString); - mongoClient = mongoConnection.db(dbName); - - /* Connecting clients for testing later */ - app = router.getApp(); - admin = request.agent(app); - user = request.agent(app); - await connectAdmin(admin); - await connectUser(user); - - /* Mock Date for testing */ - jest.spyOn(Date, 'now').mockImplementation(() => 1591134065000); -}); - -describe('LOG API', () => { - describe('Write logs', () => { - test('Write log (Login)', async () => { - const userSecret = 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA'; - const newUser: IUser = { - id: 'testuser0', - username: 'test_user', - email: 'test@test.com', - firstname: 'test', - lastname: 'user', - organisation: 'organisation_system', - type: userTypes.ADMIN, - description: 'I am an test user.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - deleted: null, - password: '$2b$04$ps9ownz6PqJFD/LExsmgR.ZLk11zhtRdcpUwypWVfWJ4ZW6/Zzok2', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - resetPasswordRequests: [], - createdAt: 1591134065000, - expiredAt: 2501134065000, - metadata: {} - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - const newloggedoutuser = request.agent(app); - const otp = mfa.generateTOTP(userSecret).toString(); - const res = await newloggedoutuser.post('/graphql').set('Content-type', 'application/json').send({ - query: print(LOGIN), - variables: { - username: 'test_user', - password: 'admin', - totp: otp - } - }); - expect(res.status).toBe(200); - const findLogInMongo = await db.collections!.log_collection.find({}).toArray(); - const lastLog = findLogInMongo.pop(); - expect(lastLog).toBeDefined(); - if (!lastLog) - return; - expect(lastLog.requesterName).toEqual('test_user'); - expect(lastLog.requesterType).toEqual(userTypes.ADMIN); - expect(lastLog.logType).toEqual(LOG_TYPE.REQUEST_LOG); - expect(lastLog.actionType).toEqual(LOG_ACTION.login); - expect(JSON.parse(lastLog.actionData)).toEqual({ - username: 'test_user' - }); - expect(lastLog.status).toEqual(LOG_STATUS.SUCCESS); - expect(lastLog.errors).toEqual(''); - - await admin.post('/graphql').send( - { - query: print(DELETE_USER), - variables: { - userId: newUser.id - } - } - ); - }, 30000); - }); - - describe('Get logs', () => { - beforeAll(async () => { - // write initial data for testing - const logSample = [{ - id: '001', - requesterName: userTypes.SYSTEM, - requesterType: userTypes.SYSTEM, - logType: LOG_TYPE.SYSTEM_LOG, - userAgent: USER_AGENT.MOZILLA, - actionType: LOG_ACTION.startSERVER, - actionData: JSON.stringify({}), - time: 100000000, - status: LOG_STATUS.SUCCESS, - errors: '' - }]; - await db.collections!.log_collection.insertMany(logSample); - }); - - afterAll(async () => { - await mongoClient.collection(config.database.collections.log_collection).deleteMany({}); - }); - - test('GET log (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(GET_LOGS), - variables: { - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.getLogs.length).toBeGreaterThanOrEqual(1); - }, 30000); - - test('GET log (user) should fail', async () => { - const res = await user.post('/graphql').send({ - query: print(GET_LOGS), - variables: { - requesterId: 'test_id1' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - }, 30000); - }); -}); diff --git a/packages/itmat-interface/test/serverTests/permission.test.ts b/packages/itmat-interface/test/serverTests/permission.test.ts deleted file mode 100644 index 103318e00..000000000 --- a/packages/itmat-interface/test/serverTests/permission.test.ts +++ /dev/null @@ -1,3257 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-nocheck - -import request from 'supertest'; -import { print } from 'graphql'; -import { connectAdmin, connectUser, connectAgent } from './_loginHelper'; -import { db } from '../../src/database/database'; -import { Router } from '../../src/server/router'; -import { errorCodes } from '../../src/graphql/errors'; -import { Db, MongoClient } from 'mongodb'; -import { IUser, IStudy, IProject, IRole, atomicOperation, IPermissionManagementOptions } from '@itmat-broker/itmat-types'; -import { ADD_NEW_ROLE, EDIT_ROLE, REMOVE_ROLE } from '@itmat-broker/itmat-models'; -import { MongoMemoryServer } from 'mongodb-memory-server'; -import { setupDatabase } from '@itmat-broker/itmat-setup'; -import config from '../../config/config.sample.json'; -import { v4 as uuid } from 'uuid'; -import { Express } from 'express'; - -let app: Express; -let mongodb: MongoMemoryServer; -let admin: request.SuperTest; -let user: request.SuperTest; -let mongoConnection: MongoClient; -let mongoClient: Db; - -afterAll(async () => { - await db.closeConnection(); - await mongoConnection?.close(); - await mongodb.stop(); -}); - -beforeAll(async () => { // eslint-disable-line no-undef - /* Creating a in-memory MongoDB instance for testing */ - const dbName = uuid(); - mongodb = await MongoMemoryServer.create({ instance: { dbName } }); - const connectionString = mongodb.getUri(); - await setupDatabase(connectionString, dbName); - - /* Wiring up the backend server */ - config.database.mongo_url = connectionString; - config.database.database = dbName; - await db.connect(config.database, MongoClient); - const router = new Router(config); - await router.init(); - - /* Connect mongo client (for test setup later / retrieve info later) */ - mongoConnection = await MongoClient.connect(connectionString); - mongoClient = mongoConnection.db(dbName); - - /* Connecting clients for testing later */ - app = router.getApp(); - admin = request.agent(app); - user = request.agent(app); - await connectAdmin(admin); - await connectUser(user); -}); - -describe('ROLE API', () => { - let adminId: any; - - beforeAll(async () => { - /* setup: first retrieve the generated user id */ - const result = await mongoClient.collection(config.database.collections.users_collection).find({}, { projection: { id: 1, username: 1 } }).toArray(); - adminId = result.filter((e: { username: string; }) => e.username === 'admin')[0].id; - }); - - describe('ADDING ROLE', () => { - let setupStudy: { id: any; name?: string; createdBy?: any; lastModified?: number; deleted?: null; currentDataVersion?: number; dataVersions?: never[]; }; - let setupProject: { id: any; studyId?: string; createdBy?: any; patientMapping?: Record; name?: string; lastModified?: number; deleted?: null; }; - let authorisedUser: request.SuperTest; - let authorisedUserProfile: { otpSecret: any; id: any; username?: string; type?: string; firstname?: string; lastname?: string; password?: string; email?: string; description?: string; emailNotificationsActivated?: boolean; organisation?: string; deleted?: null; }; - beforeEach(async () => { - const studyName = uuid(); - setupStudy = { - id: `id_${studyName}`, - name: studyName, - createdBy: adminId, - lastModified: 200000002, - deleted: null, - currentDataVersion: -1, - dataVersions: [] - }; - await mongoClient.collection(config.database.collections.studies_collection).insertOne(setupStudy); - - const projectName = uuid(); - setupProject = { - id: `id_${projectName}`, - studyId: setupStudy.id, - createdBy: adminId, - patientMapping: {}, - name: projectName, - lastModified: 20000002, - deleted: null - }; - await mongoClient.collection(config.database.collections.projects_collection).insertOne(setupProject); - - /* setup: creating a privileged user (not yet added roles) */ - const username = uuid(); - authorisedUserProfile = { - username, - type: 'STANDARD', - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}@example.com`, - description: 'I am a new user.', - emailNotificationsActivated: true, - organisation: 'organisation_system', - deleted: null, - id: `new_user_id_${username}` - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(authorisedUserProfile); - - authorisedUser = request.agent(app); - await connectAgent(authorisedUser, username, 'admin', authorisedUserProfile.otpSecret); - }); - - test('Creating a new role for study (admin)', async () => { - const roleName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(ADD_NEW_ROLE), - variables: { - roleName, - studyId: setupStudy.id, - projectId: null - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ name: roleName }); - expect(createdRole).toEqual({ - _id: createdRole._id, - id: createdRole.id, - projectId: null, - studyId: setupStudy.id, - name: roleName, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - createdBy: adminId, - users: [], - deleted: null, - metadata: {} - }); - expect(res.body.data.addRole).toEqual({ - id: createdRole.id, - name: roleName, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - studyId: setupStudy.id, - projectId: null, - users: [] - }); - - /* cleanup */ - await mongoClient.collection(config.database.collections.roles_collection).findOneAndUpdate({ name: roleName, deleted: null }, { $set: { deleted: new Date().valueOf() } }); - }); - - test('Creating a new role for study (user without privilege) (should fail)', async () => { - const roleName = uuid(); - const res = await user.post('/graphql').send({ - query: print(ADD_NEW_ROLE), - variables: { - roleName, - studyId: setupStudy.id, - projectId: null - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.addRole).toEqual(null); - - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ name: roleName }); - expect(createdRole).toBe(null); - }); - - test('Creating a new role for study (user with privilege)', async () => { - /* setup: adding privilege to authorised user */ - const roleId = uuid(); - const newRole = { - id: roleId, - projectId: null, - studyId: setupStudy.id, - name: `${roleId}_rolename`, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [atomicOperation.READ, atomicOperation.WRITE], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - users: [authorisedUserProfile.id], - deleted: null - }; - await mongoClient.collection(config.database.collections.roles_collection).insertOne(newRole); - - /* test */ - const roleName = uuid(); - const res = await authorisedUser.post('/graphql').send({ - query: print(ADD_NEW_ROLE), - variables: { - roleName, - studyId: setupStudy.id, - projectId: null - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ name: roleName }); - expect(createdRole).toEqual({ - _id: createdRole._id, - id: createdRole.id, - projectId: null, - studyId: setupStudy.id, - name: roleName, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - createdBy: authorisedUserProfile.id, - users: [], - deleted: null, - metadata: {} - }); - expect(res.body.data.addRole).toEqual({ - id: createdRole.id, - name: roleName, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - studyId: setupStudy.id, - projectId: null, - users: [] - }); - - /* cleanup */ - await mongoClient.collection(config.database.collections.roles_collection).findOneAndUpdate({ name: roleName, deleted: null }, { $set: { deleted: new Date().valueOf() } }); - }); - - test('Creating a new role for project (admin)', async () => { - const roleName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(ADD_NEW_ROLE), - variables: { - roleName, - studyId: setupStudy.id, - projectId: setupProject.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ name: roleName }); - expect(createdRole).toEqual({ - _id: createdRole._id, - id: createdRole.id, - projectId: setupProject.id, - studyId: setupStudy.id, - name: roleName, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - createdBy: adminId, - users: [], - deleted: null, - metadata: {} - }); - expect(res.body.data.addRole).toEqual({ - id: createdRole.id, - name: roleName, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - studyId: setupStudy.id, - projectId: setupProject.id, - users: [] - }); - - /* cleanup */ - await mongoClient.collection(config.database.collections.roles_collection).findOneAndUpdate({ name: roleName, deleted: null }, { $set: { deleted: new Date().valueOf() } }); - }); - - test('Creating a new role for project (user with privilege for another project in the same study) (should fail)', async () => { - /* setup: creating another project */ - const anotherProjectName = uuid(); - const anotherSetupProject = { - id: `id_${anotherProjectName}`, - studyId: setupStudy.id, - createdBy: adminId, - patientMapping: {}, - name: anotherProjectName, - lastModified: 20000002, - deleted: null - }; - await mongoClient.collection(config.database.collections.projects_collection).insertOne(anotherSetupProject); - - /* setup: giving authorised user privilege */ - const roleId = uuid(); - const newRole = { - id: roleId, - projectId: anotherSetupProject.id, - studyId: setupStudy.id, - name: `${roleId}_rolename`, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [atomicOperation.READ, atomicOperation.WRITE], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - users: [authorisedUserProfile.id], - deleted: null - }; - await mongoClient.collection(config.database.collections.roles_collection).insertOne(newRole); - - /* test */ - const roleName = uuid(); - const res = await authorisedUser.post('/graphql').send({ - query: print(ADD_NEW_ROLE), - variables: { - roleName, - studyId: setupStudy.id, - projectId: setupProject.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.addRole).toEqual(null); - - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ name: roleName }); - expect(createdRole).toBe(null); - }); - - test('Creating a new role for project (user with privilege for this project)', async () => { - /* setup: giving authorised user privilege */ - const roleId = uuid(); - const newRole = { - id: roleId, - projectId: setupProject.id, - studyId: setupStudy.id, - name: `${roleId}_rolename`, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [atomicOperation.READ, atomicOperation.WRITE], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - users: [authorisedUserProfile.id], - deleted: null - }; - await mongoClient.collection(config.database.collections.roles_collection).insertOne(newRole); - - /* test */ - const roleName = uuid(); - const res = await authorisedUser.post('/graphql').send({ - query: print(ADD_NEW_ROLE), - variables: { - roleName, - studyId: setupStudy.id, - projectId: setupProject.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ name: roleName }); - expect(createdRole).toEqual({ - _id: createdRole._id, - id: createdRole.id, - projectId: setupProject.id, - studyId: setupStudy.id, - name: roleName, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - createdBy: authorisedUserProfile.id, - users: [], - deleted: null, - metadata: {} - }); - expect(res.body.data.addRole).toEqual({ - id: createdRole.id, - name: roleName, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - studyId: setupStudy.id, - projectId: setupProject.id, - users: [] - }); - - /* cleanup */ - await mongoClient.collection(config.database.collections.roles_collection).findOneAndUpdate({ name: roleName, deleted: null }, { $set: { deleted: new Date().valueOf() } }); - }); - - test('Creating a new role for project (user with privilege for the study)', async () => { - /* setup: giving authorised user privilege */ - const roleId = uuid(); - const newRole = { - id: roleId, - projectId: null, - studyId: setupStudy.id, - name: `${roleId}_rolename`, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [atomicOperation.READ, atomicOperation.WRITE], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - users: [authorisedUserProfile.id], - deleted: null - }; - await mongoClient.collection(config.database.collections.roles_collection).insertOne(newRole); - - /* test */ - const roleName = uuid(); - const res = await authorisedUser.post('/graphql').send({ - query: print(ADD_NEW_ROLE), - variables: { - roleName, - studyId: setupStudy.id, - projectId: setupProject.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ name: roleName }); - expect(createdRole).toEqual({ - _id: createdRole._id, - id: createdRole.id, - projectId: setupProject.id, - studyId: setupStudy.id, - name: roleName, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - createdBy: authorisedUserProfile.id, - users: [], - deleted: null, - metadata: {} - }); - expect(res.body.data.addRole).toEqual({ - id: createdRole.id, - name: roleName, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - studyId: setupStudy.id, - projectId: setupProject.id, - users: [] - }); - - /* cleanup */ - await mongoClient.collection(config.database.collections.roles_collection).findOneAndUpdate({ name: roleName, deleted: null }, { $set: { deleted: new Date().valueOf() } }); - }); - - test('Creating a new role for project (user without privilege) (should fail)', async () => { - const roleName = uuid(); - const res = await user.post('/graphql').send({ - query: print(ADD_NEW_ROLE), - variables: { - roleName, - studyId: setupStudy.id, - projectId: setupProject.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.addRole).toEqual(null); - - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ name: roleName }); - expect(createdRole).toBe(null); - }); - }); - - describe('EDITING ROLE', () => { - describe('EDIT STUDY ROLE', () => { - let setupStudy: { id: any; name?: string; createdBy?: any; lastModified?: number; deleted?: null; currentDataVersion?: number; dataVersions?: never[]; }; - let setupRole: { id: any; _id?: any; name: any; studyId: any; projectId?: null; permissions?: never[]; createdBy?: any; users?: never[]; deleted?: null; }; - let authorisedUser: request.SuperTest; - let authorisedUserProfile; - beforeEach(async () => { - const studyName = uuid(); - setupStudy = { - id: `id_${studyName}`, - name: studyName, - createdBy: adminId, - lastModified: 200000002, - deleted: null, - currentDataVersion: -1, - dataVersions: [] - }; - await mongoClient.collection(config.database.collections.studies_collection).insertOne(setupStudy); - - /* setup: creating a privileged user (not yet added roles) */ - const username = uuid(); - authorisedUserProfile = { - username, - type: 'STANDARD', - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}@example.com`, - description: 'I am a new user.', - emailNotificationsActivated: true, - organisation: 'organisation_system', - deleted: null, - id: `new_user_id_${username}` - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(authorisedUserProfile); - - authorisedUser = request.agent(app); - await connectAgent(authorisedUser, username, 'admin', authorisedUserProfile.otpSecret); - - /* setup: giving authorised user privilege */ - const roleId = [uuid(), uuid()]; - const authorisedUserRole = { - id: roleId[0], - projectId: null, - studyId: setupStudy.id, - name: `${roleId[0]}_rolename`, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [atomicOperation.READ, atomicOperation.WRITE], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [authorisedUserProfile.id], - deleted: null - }; - - setupRole = { - id: roleId[1], - projectId: null, - studyId: setupStudy.id, - name: `${roleId[1]}_rolename`, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }; - await mongoClient.collection(config.database.collections.roles_collection).insertMany([setupRole, authorisedUserRole]); - }); - - test('Edit a non-existent role (admin)', async () => { - const newRoleName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: 'fake role id', - name: newRoleName - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - expect(res.body.data.editRole).toEqual(null); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - }); - - test('Edit a non-existent role (user)', async () => { - const newRoleName = uuid(); - const res = await user.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: 'fake role id', - name: newRoleName - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - expect(res.body.data.editRole).toEqual(null); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - }); - - test('Change role name (admin)', async () => { - const newRoleName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: setupRole.id, - name: newRoleName, - permissionChanges: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: setupRole.id, - name: newRoleName, - studyId: setupStudy.id, - projectId: null, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - users: [] - }); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: newRoleName, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - }); - - test('Change role name (privileged user)', async () => { - const newRoleName = uuid(); - const res = await authorisedUser.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: setupRole.id, - name: newRoleName, - permissionChanges: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: setupRole.id, - name: newRoleName, - studyId: setupStudy.id, - projectId: null, - description: null, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - users: [] - }); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: newRoleName, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - }); - - test('Change role name (user without privilege) (should fail)', async () => { - const newRoleName = uuid(); - const oldName = setupRole.name; - const res = await user.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: setupRole.id, - name: newRoleName, - permissionChanges: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.editRole).toEqual(null); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: oldName, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - }); - - test('Add a non-existent user to role (admin)', async () => { - /* setup: confirm that role has no user yet */ - const role = await mongoClient.collection(config.database.collections.roles_collection).findOne({ - id: setupRole.id, - deleted: null - }); - expect(role).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - - /* test */ - const res = await admin.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: setupRole.id, - userChanges: { - add: ['Iamafakeuser'], - remove: [] - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_MALFORMED_INPUT); - expect(res.body.data.editRole).toEqual(null); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - }); - - test('Add user to role (admin)', async () => { - /* setup: create a user to be added to role */ - const newUsername = uuid(); - const newUser = { - username: newUsername, - type: 'STANDARD', - firstname: `${newUsername}_firstname`, - lastname: `${newUsername}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${newUsername}@example.com`, - description: 'I am a new user.', - emailNotificationsActivated: true, - organisation: 'organisation_system', - deleted: null, - id: `new_user_id_${newUsername}` - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - - /* test */ - const res = await admin.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: setupRole.id, - userChanges: { - add: [newUser.id], - remove: [] - }, - permissionChanges: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: setupRole.id, - name: setupRole.name, - studyId: setupRole.studyId, - projectId: null, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - users: [{ - id: newUser.id, - organisation: newUser.organisation, - firstname: newUser.firstname, - lastname: newUser.lastname - }] - }); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [newUser.id], - deleted: null - }); - }); - - test('Add user to role (privileged user)', async () => { - /* setup: create a user to be added to role */ - const newUsername = uuid(); - const newUser = { - username: newUsername, - type: 'STANDARD', - firstname: `${newUsername}_firstname`, - lastname: `${newUsername}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${newUsername}@example.com`, - description: 'I am a new user.', - emailNotificationsActivated: true, - organisation: 'organisation_system', - deleted: null, - id: `new_user_id_${newUsername}` - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - - /* test */ - const res = await authorisedUser.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: setupRole.id, - userChanges: { - add: [newUser.id], - remove: [] - }, - permissionChanges: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: setupRole.id, - name: setupRole.name, - studyId: setupRole.studyId, - projectId: null, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - users: [{ - id: newUser.id, - organisation: newUser.organisation, - firstname: newUser.firstname, - lastname: newUser.lastname - }] - }); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [newUser.id], - deleted: null - }); - }); - - test('Add user to role (user without privilege) (should fail)', async () => { - /* setup: create a user to be added to role */ - const newUsername = uuid(); - const newUser = { - username: newUsername, - type: 'STANDARD', - firstname: `${newUsername}_firstname`, - lastname: `${newUsername}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${newUsername}@example.com`, - description: 'I am a new user.', - emailNotificationsActivated: true, - organisation: 'organisation_system', - deleted: null, - id: `new_user_id_${newUsername}` - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - - /* test */ - const res = await user.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: setupRole.id, - userChanges: { - add: [newUser.id], - remove: [] - }, - permissionChanges: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.editRole).toEqual(null); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - }); - - test('Remove user from role (admin)', async () => { - /* setup: create a user to be removed from role */ - const newUsername = uuid(); - const newUser = { - username: newUsername, - type: 'STANDARD', - firstname: `${newUsername}_firstname`, - lastname: `${newUsername}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${newUsername}@example.com`, - description: 'I am a new user.', - emailNotificationsActivated: true, - organisation: 'organisation_system', - deleted: null, - id: `new_user_id_${newUsername}` - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - const updatedRole = await mongoClient.collection(config.database.collections.roles_collection).findOneAndUpdate({ - id: setupRole.id, - deleted: null - }, { - $push: { - users: newUser.id - } - }, { returnDocument: 'after' }); - expect(updatedRole.users).toEqual([newUser.id]); - - /* test */ - const res = await admin.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: setupRole.id, - userChanges: { - add: [], - remove: [newUser.id] - }, - permissionChanges: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: setupRole.id, - name: setupRole.name, - studyId: setupRole.studyId, - projectId: null, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - users: [] - }); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - }); - - test('Add permission to role (admin)', async () => { - /* setup: confirm that role has no permissions yet */ - const role = await mongoClient.collection(config.database.collections.roles_collection).findOne({ - id: setupRole.id, - deleted: null - }); - expect(role).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - - /* test */ - const res = await admin.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: setupRole.id, - permissionChanges: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.WRITE], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: setupRole.id, - name: setupRole.name, - studyId: setupRole.studyId, - projectId: null, - description: '', - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.WRITE], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - users: [] - }); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.WRITE], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - }); - - test('Add a duplicated permission to role (admin)', async () => { - /* setup: confirm that role has one permissions yet */ - const role = await mongoClient.collection(config.database.collections.roles_collection).findOneAndUpdate({ - id: setupRole.id, - deleted: null - }, { - $set: { - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - } - } - }, { returnDocument: 'after' }); - expect(role).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - - /* test */ - const res = await admin.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: setupRole.id, - permissionChanges: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: setupRole.id, - name: setupRole.name, - studyId: setupRole.studyId, - projectId: null, - description: '', - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - users: [] - }); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - }); - - test('Add two of the same permissions to role (admin)', async () => { - /* setup: confirm that role has no permissions yet */ - const role = await mongoClient.collection(config.database.collections.roles_collection).findOne({ - id: setupRole.id, - deleted: null - }); - expect(role).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - - /* test */ - const res = await admin.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: setupRole.id, - permissionChanges: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: setupRole.id, - name: setupRole.name, - studyId: setupRole.studyId, - projectId: null, - description: '', - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - users: [] - }); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - }); - - test('Remove permission from role (admin)', async () => { - /* setup: confirm that role has one permissions */ - const role = await mongoClient.collection(config.database.collections.roles_collection).findOneAndUpdate({ - id: setupRole.id, - deleted: null - }, { - $set: { - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - } - } - }, { returnDocument: 'after' }); - expect(role).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - - /* test */ - const res = await admin.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: setupRole.id, - permissionChanges: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: setupRole.id, - name: setupRole.name, - studyId: setupRole.studyId, - projectId: null, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - users: [] - }); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - }); - - test('Remove permission which is not added from role (admin)', async () => { - /* setup: confirm that role has no permissions yet */ - const role = await mongoClient.collection(config.database.collections.roles_collection).findOne({ - id: setupRole.id, - deleted: null - }); - expect(role).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - - /* test */ - const res = await admin.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: setupRole.id, - permissionChanges: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: setupRole.id, - name: setupRole.name, - studyId: setupRole.studyId, - projectId: null, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - users: [] - }); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - }); - - test('Combination of edits (admin)', async () => { - /* setup: create a user to be removed from role */ - const newUsername = uuid(); - const newUser = { - username: newUsername, - type: 'STANDARD', - firstname: `${newUsername}_firstname`, - lastname: `${newUsername}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${newUsername}@example.com`, - description: 'I am a new user.', - emailNotificationsActivated: true, - organisation: 'organisation_system', - deleted: null, - id: `new_user_id_${newUsername}` - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - - /* setup role */ - const role = await mongoClient.collection(config.database.collections.roles_collection).findOneAndUpdate({ - id: setupRole.id, - deleted: null - }, { - $set: { - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, users: [newUser.id] - } - }, { returnDocument: 'after' }); - expect(role).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [newUser.id], - deleted: null - }); - - /* test */ - const res = await admin.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: setupRole.id, - permissionChanges: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ, atomicOperation.WRITE], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - userChanges: { - add: [adminId], - remove: [newUser.id] - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: setupRole.id, - name: setupRole.name, - studyId: setupRole.studyId, - projectId: null, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ, atomicOperation.WRITE], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - users: [{ - id: adminId, - organisation: 'organisation_system', - firstname: 'Fadmin', - lastname: 'Ladmin' - }] - }); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: null, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ, atomicOperation.WRITE], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - users: [adminId], - createdBy: adminId, - deleted: null - }); - }); - }); - - describe('EDIT PROJECT ROLE', () => { - let setupStudy: { id: any; name?: string; createdBy?: any; lastModified?: number; deleted?: null; currentDataVersion?: number; dataVersions?: never[]; }; - let setupProject: { id: any; studyId?: string; createdBy?: any; patientMapping?: Record; name?: string; lastModified?: number; deleted?: null; }; - let setupRole: { id: any; _id?: any; name: any; projectId?: string; studyId?: string; permissions?: never[]; createdBy?: any; users?: never[]; deleted?: null; }; - let authorisedUser: request.SuperTest; - let authorisedUserProfile; - beforeEach(async () => { - /* setup: creating a setup study */ - const studyName = uuid(); - setupStudy = { - id: `id_${studyName}`, - name: studyName, - createdBy: adminId, - lastModified: 200000002, - deleted: null, - currentDataVersion: -1, - dataVersions: [] - }; - await mongoClient.collection(config.database.collections.studies_collection).insertOne(setupStudy); - - /* setup: creating a project */ - const projectName = uuid(); - setupProject = { - id: `id_${projectName}`, - studyId: setupStudy.id, - createdBy: adminId, - patientMapping: {}, - name: projectName, - lastModified: 12103214, - deleted: null - }; - await mongoClient.collection(config.database.collections.projects_collection).insertOne(setupProject); - - /* setup: creating a privileged user (not yet added roles) */ - const username = uuid(); - authorisedUserProfile = { - username, - type: 'STANDARD', - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}@example.com`, - description: 'I am a new user.', - emailNotificationsActivated: true, - organisation: 'organisation_system', - deleted: null, - id: `new_user_id_${username}` - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(authorisedUserProfile); - - authorisedUser = request.agent(app); - await connectAgent(authorisedUser, username, 'admin', authorisedUserProfile.otpSecret); - - /* setup: giving authorised user privilege */ - const roleId = [uuid(), uuid()]; - const authorisedUserRole = { - id: roleId[0], - projectId: setupProject.id, - studyId: setupStudy.id, - name: `${roleId[0]}_rolename`, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [atomicOperation.READ, atomicOperation.WRITE], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [authorisedUserProfile.id], - deleted: null - }; - - setupRole = { - id: roleId[1], - projectId: setupProject.id, - studyId: setupStudy.id, - name: `${roleId[1]}_rolename`, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }; - await mongoClient.collection(config.database.collections.roles_collection).insertMany([setupRole, authorisedUserRole]); - }); - - test('Change role name (admin)', async () => { - const newRoleName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: setupRole.id, - name: newRoleName - }, - permissionChanges: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: setupRole.id, - name: newRoleName, - studyId: setupStudy.id, - projectId: setupProject.id, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - users: [] - }); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: setupProject.id, - studyId: setupStudy.id, - name: newRoleName, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - }); - - test('Change role name (privileged user)', async () => { - const newRoleName = uuid(); - const res = await authorisedUser.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: setupRole.id, - name: newRoleName - }, - permissionChanges: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: setupRole.id, - name: newRoleName, - studyId: setupStudy.id, - projectId: setupProject.id, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - users: [] - }); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: setupProject.id, - studyId: setupStudy.id, - name: newRoleName, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - }); - - test('Change role name (user without privilege) (should fail)', async () => { - const newRoleName = uuid(); - const oldName = setupRole.name; - const res = await user.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: setupRole.id, - name: newRoleName - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.editRole).toEqual(null); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: setupProject.id, - studyId: setupStudy.id, - name: oldName, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - }); - - test('Add a non-existent user to role (admin)', async () => { - /* setup: confirm that role has no user yet */ - const role = await mongoClient.collection(config.database.collections.roles_collection).findOne({ - id: setupRole.id, - deleted: null - }); - expect(role).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: setupProject.id, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - - /* test */ - const res = await admin.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: setupRole.id, - userChanges: { - add: ['Iamafakeuser'], - remove: [] - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_MALFORMED_INPUT); - expect(res.body.data.editRole).toEqual(null); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: setupProject.id, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - }); - - test('Add user to role (privileged user)', async () => { - /* setup: create a user to be added to role */ - const newUsername = uuid(); - const newUser = { - username: newUsername, - type: 'STANDARD', - firstname: `${newUsername}_firstname`, - lastname: `${newUsername}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${newUsername}@example.com`, - description: 'I am a new user.', - emailNotificationsActivated: true, - organisation: 'organisation_system', - deleted: null, - id: `new_user_id_${newUsername}` - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - - /* test */ - const res = await authorisedUser.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: setupRole.id, - userChanges: { - add: [newUser.id], - remove: [] - } - }, - permissionChanges: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: setupRole.id, - name: setupRole.name, - studyId: setupStudy.id, - projectId: setupProject.id, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - users: [{ - id: newUser.id, - organisation: newUser.organisation, - firstname: newUser.firstname, - lastname: newUser.lastname - }] - }); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: setupProject.id, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - createdBy: adminId, - users: [newUser.id], - deleted: null - }); - }); - - test('Add user to role (user without privilege) (should fail)', async () => { - /* setup: create a user to be added to role */ - const newUsername = uuid(); - const newUser = { - username: newUsername, - type: 'STANDARD', - firstname: `${newUsername}_firstname`, - lastname: `${newUsername}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${newUsername}@example.com`, - description: 'I am a new user.', - emailNotificationsActivated: true, - organisation: 'organisation_system', - deleted: null, - id: `new_user_id_${newUsername}` - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - - /* test */ - const res = await user.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: setupRole.id, - userChanges: { - add: [newUser.id], - remove: [] - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.editRole).toEqual(null); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole).toEqual({ - _id: setupRole._id, - id: setupRole.id, - projectId: setupProject.id, - studyId: setupStudy.id, - name: setupRole.name, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }); - }); - }); - }); - - describe('DELETING ROLE', () => { - describe('DELETE STUDY ROLE', () => { - let setupStudy; - let setupRole: { id: any; projectId?: null; studyId?: string; name?: string; permissions?: never[]; createdBy?: any; users?: never[]; deleted?: null; }; - let authorisedUser: request.SuperTest; - let authorisedUserProfile; - beforeEach(async () => { - const studyName = uuid(); - setupStudy = { - id: `id_${studyName}`, - name: studyName, - createdBy: adminId, - lastModified: 200000002, - deleted: null, - currentDataVersion: -1, - dataVersions: [] - }; - await mongoClient.collection(config.database.collections.studies_collection).insertOne(setupStudy); - - /* setup: creating a privileged user (not yet added roles) */ - const username = uuid(); - authorisedUserProfile = { - username, - type: 'STANDARD', - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}@example.com`, - description: 'I am a new user.', - emailNotificationsActivated: true, - organisation: 'organisation_system', - deleted: null, - id: `new_user_id_${username}` - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(authorisedUserProfile); - - authorisedUser = request.agent(app); - await connectAgent(authorisedUser, username, 'admin', authorisedUserProfile.otpSecret); - - /* setup: giving authorised user privilege */ - const roleId = [uuid(), uuid()]; - const authorisedUserRole = { - id: roleId[0], - projectId: null, - studyId: setupStudy.id, - name: `${roleId[0]}_rolename`, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [atomicOperation.READ, atomicOperation.WRITE], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [authorisedUserProfile.id], - deleted: null - }; - - setupRole = { - id: roleId[1], - projectId: null, - studyId: setupStudy.id, - name: `${roleId[1]}_rolename`, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }; - await mongoClient.collection(config.database.collections.roles_collection).insertMany([setupRole, authorisedUserRole]); - }); - - test('delete a study role (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(REMOVE_ROLE), - variables: { - roleId: setupRole.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.removeRole).toEqual({ successful: true }); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(typeof createdRole.deleted).toBe('number'); - }); - - test('delete a study role (privileged user)', async () => { - const res = await authorisedUser.post('/graphql').send({ - query: print(REMOVE_ROLE), - variables: { - roleId: setupRole.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.removeRole).toEqual({ successful: true }); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(typeof createdRole.deleted).toBe('number'); - }); - - test('delete a study role (user with no privilege) (should fail)', async () => { - const res = await user.post('/graphql').send({ - query: print(REMOVE_ROLE), - variables: { - roleId: setupRole.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.removeRole).toEqual(null); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole.deleted).toBe(null); - }); - - test('delete a non-existent role (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(REMOVE_ROLE), - variables: { - roleId: 'iamafakerole!' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - expect(res.body.data.removeRole).toEqual(null); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(createdRole.deleted).toBe(null); - }); - }); - - describe('DELETE PROJECT ROLE', () => { - let setupStudy; - let setupProject; - let setupRole: { id: any; projectId?: string; studyId?: string; name?: string; permissions?: never[]; createdBy?: any; users?: never[]; deleted?: null; }; - let authorisedUser: request.SuperTest; - let authorisedUserProfile; - beforeEach(async () => { - /* setup: creating a setup study */ - const studyName = uuid(); - setupStudy = { - id: `id_${studyName}`, - name: studyName, - createdBy: adminId, - lastModified: 200000002, - deleted: null, - currentDataVersion: -1, - dataVersions: [] - }; - await mongoClient.collection(config.database.collections.studies_collection).insertOne(setupStudy); - - /* setup: creating a project */ - const projectName = uuid(); - setupProject = { - id: `id_${projectName}`, - studyId: setupStudy.id, - createdBy: adminId, - patientMapping: {}, - name: projectName, - lastModified: 12103214, - deleted: null - }; - await mongoClient.collection(config.database.collections.projects_collection).insertOne(setupProject); - - /* setup: creating a privileged user (not yet added roles) */ - const username = uuid(); - authorisedUserProfile = { - username, - type: 'STANDARD', - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}@example.com`, - description: 'I am a new user.', - emailNotificationsActivated: true, - organisation: 'organisation_system', - deleted: null, - id: `new_user_id_${username}` - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(authorisedUserProfile); - - authorisedUser = request.agent(app); - await connectAgent(authorisedUser, username, 'admin', authorisedUserProfile.otpSecret); - - /* setup: giving authorised user privilege */ - const roleId = [uuid(), uuid()]; - const authorisedUserRole = { - id: roleId[0], - projectId: setupProject.id, - studyId: setupStudy.id, - name: `${roleId[0]}_rolename`, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [atomicOperation.READ, atomicOperation.WRITE], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [authorisedUserProfile.id], - deleted: null - }; - - setupRole = { - id: roleId[1], - projectId: setupProject.id, - studyId: setupStudy.id, - name: `${roleId[1]}_rolename`, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [] - } - }, - createdBy: adminId, - users: [], - deleted: null - }; - await mongoClient.collection(config.database.collections.roles_collection).insertMany([setupRole, authorisedUserRole]); - }); - - test('delete a project role (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(REMOVE_ROLE), - variables: { - roleId: setupRole.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.removeRole).toEqual({ successful: true }); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(typeof createdRole.deleted).toBe('number'); - }); - - test('delete a project role (privileged user)', async () => { - const res = await authorisedUser.post('/graphql').send({ - query: print(REMOVE_ROLE), - variables: { - roleId: setupRole.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.removeRole).toEqual({ successful: true }); - const createdRole = await mongoClient.collection(config.database.collections.roles_collection).findOne({ id: setupRole.id }); - expect(typeof createdRole.deleted).toBe('number'); - }); - }); - }); -}); diff --git a/packages/itmat-interface/test/serverTests/standardization.test.ts b/packages/itmat-interface/test/serverTests/standardization.test.ts deleted file mode 100644 index 5f6d13507..000000000 --- a/packages/itmat-interface/test/serverTests/standardization.test.ts +++ /dev/null @@ -1,1517 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-nocheck - -import request from 'supertest'; -import { print } from 'graphql'; -import { connectAdmin, connectUser, connectAgent } from './_loginHelper'; -import { db } from '../../src/database/database'; -import { Router } from '../../src/server/router'; -import { errorCodes } from '../../src/graphql/errors'; -import { Db, MongoClient } from 'mongodb'; -import { MongoMemoryServer } from 'mongodb-memory-server'; -import { setupDatabase } from '@itmat-broker/itmat-setup'; -import config from '../../config/config.sample.json'; -import { v4 as uuid } from 'uuid'; -import { - GET_STUDY, - GET_USERS, - EDIT_ROLE, - ADD_NEW_ROLE, - WHO_AM_I, - CREATE_PROJECT, - CREATE_STUDY, - DELETE_STUDY, - UPLOAD_DATA_IN_ARRAY, - GET_DATA_RECORDS, - CREATE_NEW_DATA_VERSION, - CREATE_NEW_FIELD, - CREATE_STANDARDIZATION, - GET_STANDARDIZATION, - DELETE_STANDARDIZATION, - CREATE_ONTOLOGY_TREE -} from '@itmat-broker/itmat-models'; -import { userTypes, IUser, studyType, IStudy, IProject, IRole, IPermissionManagementOptions, atomicOperation } from '@itmat-broker/itmat-types'; -import { Express } from 'express'; - - -let app: Express; -let mongodb: MongoMemoryServer; -let admin: request.SuperTest; -let user: request.SuperTest; -let mongoConnection: MongoClient; -let mongoClient: Db; - -afterAll(async () => { - await db.closeConnection(); - await mongoConnection?.close(); - await mongodb.stop(); - - /* claer all mocks */ - jest.clearAllMocks(); -}); - -beforeAll(async () => { // eslint-disable-line no-undef - /* Creating a in-memory MongoDB instance for testing */ - const dbName = uuid(); - mongodb = await MongoMemoryServer.create({ instance: { dbName } }); - const connectionString = mongodb.getUri(); - await setupDatabase(connectionString, dbName); - - /* Wiring up the backend server */ - config.database.mongo_url = connectionString; - config.database.database = dbName; - await db.connect(config.database, MongoClient); - const router = new Router(config); - await router.init(); - - /* Connect mongo client (for test setup later / retrieve info later) */ - mongoConnection = await MongoClient.connect(connectionString); - mongoClient = mongoConnection.db(dbName); - - /* Connecting clients for testing later */ - app = router.getApp(); - admin = request.agent(app); - user = request.agent(app); - await connectAdmin(admin); - await connectUser(user); - - /* Mock Date for testing */ - jest.spyOn(Date, 'now').mockImplementation(() => 1591134065000); -}); - -describe('STUDY API', () => { - let adminId: any; - - beforeAll(async () => { - /* setup: first retrieve the generated user id */ - const result = await mongoClient.collection(config.database.collections.users_collection).find({}, { projection: { id: 1, username: 1 } }).toArray(); - adminId = result.filter((e: { username: string; }) => e.username === 'admin')[0].id; - }); - - describe('MINI END-TO-END API TEST, NO UI, NO DATA', () => { - let createdProject; - let createdStudy: { id: any; name: any; }; - let createdRole_study; - let createdRole_study_manageProject; - let createdRole_project; - let createdUserAuthorised; // profile - let createdUserAuthorisedStudy; // profile - let createdUserAuthorisedStudyManageProjects; // profile - let authorisedUser: request.SuperTest; // client - let authorisedUserStudy: request.SuperTest; // client - let authorisedUserStudyManageProject; // client - - beforeAll(async () => { - /*** setup: create a setup study ***/ - /* 1. create study */ - { - const studyName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(CREATE_STUDY), - variables: { name: studyName, description: 'test description', type: studyType.SENSOR } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - createdStudy = await mongoClient.collection(config.database.collections.studies_collection).findOne({ name: studyName }); - expect(res.body.data.createStudy).toEqual({ - id: createdStudy.id, - name: studyName, - description: 'test description', - type: studyType.SENSOR - }); - } - - /* 2. create projects for the study */ - { - const projectName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(CREATE_PROJECT), - variables: { - studyId: createdStudy.id, - projectName: projectName - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - createdProject = await mongoClient.collection(config.database.collections.projects_collection).findOne({ name: projectName }); - expect(res.body.data.createProject).toEqual({ - id: createdProject.id, - studyId: createdStudy.id, - name: projectName - }); - } - - /* 3. create roles for study */ - { - const roleName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(ADD_NEW_ROLE), - variables: { - roleName, - studyId: createdStudy.id, - projectId: null - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - - createdRole_study = await mongoClient.collection(config.database.collections.roles_collection).findOne({ name: roleName }); - expect(createdRole_study).toEqual({ - _id: createdRole_study._id, - id: createdRole_study.id, - projectId: null, - studyId: createdStudy.id, - name: roleName, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - createdBy: adminId, - users: [], - deleted: null, - metadata: {} - }); - expect(res.body.data.addRole).toEqual({ - id: createdRole_study.id, - name: roleName, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - studyId: createdStudy.id, - projectId: null, - users: [] - }); - } - /* create another role for study (this time it will have "manage project" privilege - equivalent to PI */ - { - const roleName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(ADD_NEW_ROLE), - variables: { - roleName, - studyId: createdStudy.id, - projectId: null - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - - createdRole_study_manageProject = await mongoClient.collection(config.database.collections.roles_collection).findOne({ name: roleName }); - expect(createdRole_study_manageProject).toEqual({ - _id: createdRole_study_manageProject._id, - id: createdRole_study_manageProject.id, - projectId: null, - studyId: createdStudy.id, - name: roleName, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - createdBy: adminId, - users: [], - deleted: null, - metadata: {} - }); - expect(res.body.data.addRole).toEqual({ - id: createdRole_study_manageProject.id, - name: roleName, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - studyId: createdStudy.id, - projectId: null, - users: [] - }); - } - - /* 4. create roles for project */ - { - const roleName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(ADD_NEW_ROLE), - variables: { - roleName, - studyId: createdStudy.id, - projectId: createdProject.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - createdRole_project = await mongoClient.collection(config.database.collections.roles_collection).findOne({ name: roleName }); - expect(createdRole_project).toEqual({ - _id: createdRole_project._id, - id: createdRole_project.id, - projectId: createdProject.id, - studyId: createdStudy.id, - name: roleName, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - createdBy: adminId, - users: [], - deleted: null, - metadata: {} - }); - expect(res.body.data.addRole).toEqual({ - id: createdRole_project.id, - name: roleName, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - studyId: createdStudy.id, - projectId: createdProject.id, - users: [] - }); - } - - /* 5. create an authorised project user (no role yet) */ - { - const username = uuid(); - const newUser: IUser = { - username: username, - type: userTypes.STANDARD, - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}@user.io`, - resetPasswordRequests: [], - description: 'I am an authorised project user.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - deleted: null, - id: `AuthorisedProjectUser_${username}`, - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - createdUserAuthorised = await mongoClient.collection(config.database.collections.users_collection).findOne({ username }); - } - - /* 6. add authorised user to role */ - { - const res = await admin.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: createdRole_project.id, - userChanges: { - add: [createdUserAuthorised.id], - remove: [] - }, - permissionChanges: { - data: { - subjectIds: ['^.*$'], - visitIds: ['^.*$'], - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: createdRole_project.id, - name: createdRole_project.name, - studyId: createdStudy.id, - projectId: createdProject.id, - description: '', - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - users: [{ - id: createdUserAuthorised.id, - organisation: 'organisation_system', - firstname: createdUserAuthorised.firstname, - lastname: createdUserAuthorised.lastname - }] - }); - const resUser = await admin.post('/graphql').send({ - query: print(GET_USERS), - variables: { - fetchDetailsAdminOnly: false, - userId: createdUserAuthorised.id, - fetchAccessPrivileges: true - } - }); - expect(resUser.status).toBe(200); - expect(resUser.body.errors).toBeUndefined(); - expect(resUser.body.data.getUsers).toHaveLength(1); - expect(resUser.body.data.getUsers[0]).toEqual({ - id: createdUserAuthorised.id, - type: userTypes.STANDARD, - firstname: `${createdUserAuthorised.username}_firstname`, - lastname: `${createdUserAuthorised.username}_lastname`, - organisation: 'organisation_system', - access: { - id: `user_access_obj_user_id_${createdUserAuthorised.id}`, - projects: [{ - id: createdProject.id, - name: createdProject.name, - studyId: createdStudy.id - }], - studies: [] - } - }); - } - - /* 5. create an authorised study user (no role yet) */ - { - const username = uuid(); - const newUser: IUser = { - username: username, - type: userTypes.STANDARD, - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}@user.io`, - resetPasswordRequests: [], - description: 'I am an authorised study user.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - deleted: null, - id: `AuthorisedStudyUser_${username}`, - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - createdUserAuthorisedStudy = await mongoClient.collection(config.database.collections.users_collection).findOne({ username }); - } - - /* 6. add authorised user to role */ - { - const res = await admin.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: createdRole_study.id, - userChanges: { - add: [createdUserAuthorisedStudy.id], - remove: [] - }, - permissionChanges: { - data: { - fieldIds: ['^.*$'], - hasVersioned: true, - uploaders: ['^.*$'], - operations: [atomicOperation.READ, atomicOperation.WRITE], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: createdRole_study.id, - name: createdRole_study.name, - studyId: createdStudy.id, - projectId: null, - description: '', - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: true, - uploaders: ['^.*$'], - operations: [atomicOperation.READ, atomicOperation.WRITE], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - users: [{ - id: createdUserAuthorisedStudy.id, - organisation: 'organisation_system', - firstname: createdUserAuthorisedStudy.firstname, - lastname: createdUserAuthorisedStudy.lastname - }] - }); - const resUser = await admin.post('/graphql').send({ - query: print(GET_USERS), - variables: { - fetchDetailsAdminOnly: false, - userId: createdUserAuthorisedStudy.id, - fetchAccessPrivileges: true - } - }); - expect(resUser.status).toBe(200); - expect(resUser.body.errors).toBeUndefined(); - expect(resUser.body.data.getUsers).toHaveLength(1); - expect(resUser.body.data.getUsers[0]).toEqual({ - id: createdUserAuthorisedStudy.id, - type: userTypes.STANDARD, - firstname: `${createdUserAuthorisedStudy.username}_firstname`, - lastname: `${createdUserAuthorisedStudy.username}_lastname`, - organisation: 'organisation_system', - access: { - id: `user_access_obj_user_id_${createdUserAuthorisedStudy.id}`, - projects: [{ - id: createdProject.id, - name: createdProject.name, - studyId: createdStudy.id - }], - studies: [{ - id: createdStudy.id, - name: createdStudy.name - }] - } - }); - } - - /* 5. create an authorised study user that can manage projects (no role yet) */ - { - const username = uuid(); - const newUser: IUser = { - username: username, - type: userTypes.STANDARD, - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}'@user.io'`, - resetPasswordRequests: [], - description: 'I am an authorised study user managing project.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - deleted: null, - id: `AuthorisedStudyUserManageProject_${username}`, - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - createdUserAuthorisedStudyManageProjects = await mongoClient.collection(config.database.collections.users_collection).findOne({ username }); - } - - /* 6. add authorised user to role */ - { - const res = await admin.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: createdRole_study_manageProject.id, - userChanges: { - add: [createdUserAuthorisedStudyManageProjects.id], - remove: [] - }, - permissionChanges: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ, atomicOperation.WRITE], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: createdRole_study_manageProject.id, - name: createdRole_study_manageProject.name, - studyId: createdStudy.id, - projectId: null, - description: '', - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ, atomicOperation.WRITE], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - users: [{ - id: createdUserAuthorisedStudyManageProjects.id, - organisation: 'organisation_system', - firstname: createdUserAuthorisedStudyManageProjects.firstname, - lastname: createdUserAuthorisedStudyManageProjects.lastname - }] - }); - const resUser = await admin.post('/graphql').send({ - query: print(GET_USERS), - variables: { - fetchDetailsAdminOnly: false, - userId: createdUserAuthorisedStudyManageProjects.id, - fetchAccessPrivileges: true - } - }); - expect(resUser.status).toBe(200); - expect(resUser.body.errors).toBeUndefined(); - expect(resUser.body.data.getUsers).toHaveLength(1); - expect(resUser.body.data.getUsers[0]).toEqual({ - id: createdUserAuthorisedStudyManageProjects.id, - type: userTypes.STANDARD, - firstname: `${createdUserAuthorisedStudyManageProjects.username}_firstname`, - lastname: `${createdUserAuthorisedStudyManageProjects.username}_lastname`, - organisation: 'organisation_system', - access: { - id: `user_access_obj_user_id_${createdUserAuthorisedStudyManageProjects.id}`, - projects: [{ - id: createdProject.id, - name: createdProject.name, - studyId: createdStudy.id - }], - studies: [{ - id: createdStudy.id, - name: createdStudy.name - }] - } - }); - } - /* fsdafs: admin who am i */ - { - const res = await admin.post('/graphql').send({ query: print(WHO_AM_I) }); - expect(res.body.data.whoAmI).toStrictEqual({ - username: 'admin', - type: userTypes.ADMIN, - firstname: 'Fadmin', - lastname: 'Ladmin', - organisation: 'organisation_system', - email: 'admin@example.com', - description: 'I am an admin user.', - id: adminId, - access: { - id: `user_access_obj_user_id_${adminId}`, - projects: [{ - id: createdProject.id, - name: createdProject.name, - studyId: createdStudy.id - }], - studies: [{ - id: createdStudy.id, - name: createdStudy.name, - type: studyType.SENSOR - }] - }, - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - createdAt: 1591134065000, - expiredAt: 1991134065000, - metadata: null - }); - } - /* connecting users */ - authorisedUser = request.agent(app); - await connectAgent(authorisedUser, createdUserAuthorised.username, 'admin', createdUserAuthorised.otpSecret); - authorisedUserStudy = request.agent(app); - await connectAgent(authorisedUserStudy, createdUserAuthorisedStudy.username, 'admin', createdUserAuthorisedStudy.otpSecret); - authorisedUserStudyManageProject = request.agent(app); - await connectAgent(authorisedUserStudyManageProject, createdUserAuthorisedStudyManageProjects.username, 'admin', createdUserAuthorisedStudyManageProjects.otpSecret); - }); - - afterAll(async () => { - /* project user cannot delete study */ - { - const res = await authorisedUser.post('/graphql').send({ - query: print(DELETE_STUDY), - variables: { studyId: createdStudy.id } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.deleteStudy).toBe(null); - } - - /* delete values in db */ - await db.collections!.field_dictionary_collection.deleteMany({ studyId: createdStudy.id }); - await db.collections!.data_collection.deleteMany({ m_studyId: createdStudy.id }); - await db.collections!.files_collection.deleteMany({ studyId: createdStudy.id }); - - /* study user cannot delete study */ - { - const res = await authorisedUserStudy.post('/graphql').send({ - query: print(DELETE_STUDY), - variables: { studyId: createdStudy.id } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.deleteStudy).toBe(null); - } - - /* admin can delete study */ - { - const res = await admin.post('/graphql').send({ - query: print(DELETE_STUDY), - variables: { studyId: createdStudy.id } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.deleteStudy).toEqual({ - id: createdStudy.id, - successful: true - }); - } - - /* check projects and roles are also deleted */ - { - const res = await admin.post('/graphql').send({ query: print(WHO_AM_I) }); - expect(res.body.data.whoAmI).toEqual({ - username: 'admin', - type: userTypes.ADMIN, - firstname: 'Fadmin', - lastname: 'Ladmin', - organisation: 'organisation_system', - email: 'admin@example.com', - description: 'I am an admin user.', - id: adminId, - access: { - id: `user_access_obj_user_id_${adminId}`, - projects: [], - studies: [] - }, - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - createdAt: 1591134065000, - expiredAt: 1991134065000, - metadata: null - }); - - // study data is NOT deleted for audit purposes - unless explicitly requested separately - const roles = await db.collections!.roles_collection.find({ studyId: createdStudy.id, deleted: null }).toArray(); - const projects = await db.collections!.projects_collection.find({ studyId: createdStudy.id, deleted: null }).toArray(); - const study = await db.collections!.studies_collection.findOne({ id: createdStudy.id, deleted: null }); - expect(roles).toEqual([]); - expect(projects).toEqual([]); - expect(study).toBe(null); - } - - /* cannot get study from api anymore */ - { - const res = await admin.post('/graphql').send({ - query: print(GET_STUDY), - variables: { studyId: createdStudy.id } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - expect(res.body.data.getStudy).toBe(null); - } - }); - - afterEach(async () => { - await db.collections!.standardizations_collection.deleteMany({}); - await db.collections!.studies_collection.findOneAndUpdate({ id: createdStudy.id, deleted: null }, { $set: { dataVersions: [], currentDataVersion: -1 } }); - }); - - test('Create standardization (authorised user)', async () => { - const res = await authorisedUserStudy.post('/graphql').send({ - query: print(CREATE_STANDARDIZATION), - variables: { - studyId: createdStudy.id, - standardization: { - type: 'fakeType', - field: ['$testField'], - path: ['testPath'], - joinByKeys: [], - stdRules: [ - { - entry: 'fakeEntry', - source: 'value', - parameter: ['fakeValue'], - filters: null - } - ] - } - } - }); - const std = await db.collections!.standardizations_collection.findOne({}); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.createStandardization).toEqual({ - id: std?.id, - studyId: createdStudy.id, - type: 'fakeType', - field: ['$testField'], - path: ['testPath'], - joinByKeys: [], - stdRules: [ - { - id: std?.stdRules[0]?.id, - entry: 'fakeEntry', - source: 'value', - parameter: ['fakeValue'], - filters: null - } - ], - metadata: null, - uploadedAt: 1591134065000, - deleted: null - }); - }); - - test('Create standardization (authorised user)', async () => { - const res = await user.post('/graphql').send({ - query: print(CREATE_STANDARDIZATION), - variables: { - studyId: createdStudy.id, - standardization: { - type: 'fakeType', - field: ['$testField'], - path: ['testPath'], - joinByKeys: [], - stdRules: [ - { - entry: 'fakeEntry', - source: 'value', - parameter: ['fakeValue'], - filters: null - } - ] - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - }); - - test('Get standardization (authorised user)', async () => { - await authorisedUserStudy.post('/graphql').send({ - query: print(CREATE_STANDARDIZATION), - variables: { - studyId: createdStudy.id, - standardization: { - type: 'fakeType', - field: ['$testField'], - path: ['testPath'], - joinByKeys: [], - stdRules: [ - { - entry: 'fakeEntry', - source: 'value', - parameter: ['fakeValue'], - filters: null - } - ] - } - } - }); - await admin.post('/graphql').send({ - query: print(CREATE_NEW_DATA_VERSION), - variables: { studyId: createdStudy.id, dataVersion: '1', tag: 'testTag' } - }); - const res = await authorisedUserStudy.post('/graphql').send({ - query: print(GET_STANDARDIZATION), - variables: { - studyId: createdStudy.id, - type: 'fakeType' - } - }); - const std = await db.collections!.standardizations_collection.findOne({ studyId: createdStudy.id, type: 'fakeType', field: ['$testField'] }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.getStandardization[0]).toEqual({ - id: std?.id, - studyId: createdStudy.id, - type: 'fakeType', - field: ['$testField'], - path: ['testPath'], - joinByKeys: [], - stdRules: [ - { - id: std?.stdRules[0]?.id, - entry: 'fakeEntry', - source: 'value', - parameter: ['fakeValue'], - filters: null - } - ], - dataVersion: std?.dataVersion, - metadata: null, - uploadedAt: 1591134065000, - deleted: null - }); - }); - - test('Get standardization with versioning (authorised user)', async () => { - await authorisedUserStudy.post('/graphql').send({ - query: print(CREATE_STANDARDIZATION), - variables: { - studyId: createdStudy.id, - standardization: { - type: 'fakeType', - field: ['$testField'], - path: ['testPath'], - joinByKeys: [], - stdRules: [ - { - entry: 'fakeEntry', - source: 'value', - parameter: ['fakeValue'], - filters: null - } - ] - } - } - }); - await admin.post('/graphql').send({ - query: print(CREATE_NEW_DATA_VERSION), - variables: { studyId: createdStudy.id, dataVersion: '1', tag: 'testTag' } - }); - await authorisedUserStudy.post('/graphql').send({ - query: print(CREATE_STANDARDIZATION), - variables: { - studyId: createdStudy.id, - standardization: { - type: 'fakeType', - field: ['$testField'], - path: ['testPath'], - joinByKeys: [], - stdRules: [ - { - entry: 'fakeEntryNew', - source: 'value', - parameter: ['fakeValue'], - filters: null - } - ] - } - } - }); - // modify the uploadedAt of the second uploading as we use mock - await db.collections!.standardizations_collection.findOneAndUpdate({ stdRules: { $elemMatch: { entry: 'fakeEntryNew' } } }, { - $set: { - uploadedAt: 1591134065001 - } - }); - const withNullVersion = await authorisedUserStudy.post('/graphql').send({ - query: print(GET_STANDARDIZATION), - variables: { - studyId: createdStudy.id, - type: 'fakeType', - versionId: null - } - }); - expect(withNullVersion.status).toBe(200); - expect(withNullVersion.body.errors).toBeUndefined(); - expect(withNullVersion.body.data.getStandardization).toHaveLength(1); - expect(withNullVersion.body.data.getStandardization[0].stdRules[0].entry).toBe('fakeEntryNew'); - const withVersion = await authorisedUserStudy.post('/graphql').send({ - query: print(GET_STANDARDIZATION), - variables: { - studyId: createdStudy.id, - type: 'fakeType' - } - }); - expect(withVersion.status).toBe(200); - expect(withVersion.body.errors).toBeUndefined(); - expect(withVersion.body.data.getStandardization).toHaveLength(1); - expect(withVersion.body.data.getStandardization[0].stdRules[0].entry).toBe('fakeEntry'); - }); - - test('Get standardization (unauthorised user)', async () => { - await authorisedUserStudy.post('/graphql').send({ - query: print(CREATE_STANDARDIZATION), - variables: { - studyId: createdStudy.id, - standardization: { - type: 'fakeType', - field: ['$testField'], - path: ['testPath'], - joinByKeys: [], - stdRules: [ - { - entry: 'fakeEntry', - source: 'value', - parameter: ['fakeValue'], - filters: null - } - ] - } - } - }); - const res = await user.post('/graphql').send({ - query: print(GET_STANDARDIZATION), - variables: { - studyId: createdStudy.id, - type: 'fakeType' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - }); - - test('Delete standardization (authorised user)', async () => { - await authorisedUserStudy.post('/graphql').send({ - query: print(CREATE_STANDARDIZATION), - variables: { - studyId: createdStudy.id, - standardization: { - type: 'fakeType', - field: ['$testField'], - path: ['testPath'], - joinByKeys: [], - stdRules: [ - { - entry: 'fakeEntry', - source: 'value', - parameter: ['fakeValue'], - filters: null - } - ] - } - } - }); - const std = await db.collections!.standardizations_collection.findOne({}); - const res = await authorisedUserStudy.post('/graphql').send({ - query: print(DELETE_STANDARDIZATION), - variables: { - studyId: createdStudy.id, - type: 'fakeType', - field: ['$testField'] - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.deleteStandardization).toEqual({ - code: null, - description: null, - id: std?.id, - successful: true - }); - }); - - test('Delete standardization (unauthorised user)', async () => { - await authorisedUserStudy.post('/graphql').send({ - query: print(CREATE_STANDARDIZATION), - variables: { - studyId: createdStudy.id, - standardization: { - type: 'fakeType', - field: ['$testField'], - path: ['testPath'], - joinByKeys: [], - stdRules: [ - { - entry: 'fakeEntry', - source: 'value', - parameter: ['fakeValue'], - filters: null - } - ] - } - } - }); - const res = await user.post('/graphql').send({ - query: print(DELETE_STANDARDIZATION), - variables: { - studyId: createdStudy.id, - type: 'fakeType', - field: ['$testField'] - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - }); - - test('Get standardized data (authorised user)', async () => { - await authorisedUserStudy.post('/graphql').send({ - query: print(CREATE_NEW_FIELD), - variables: { - studyId: createdStudy.id, - fieldInput: [ - { - fieldId: '100', - fieldName: 'newField100', - tableName: 'test', - dataType: 'cat', - comments: 'test', - possibleValues: [ - { code: '1', description: 'NOW' }, - { code: '2', description: 'OLD' } - ] - }, - { - fieldId: '101', - fieldName: 'newField101', - tableName: 'test', - dataType: 'cat', - comments: 'test', - possibleValues: [ - { code: '1', description: 'TRUE' }, - { code: '2', description: 'FALSE' } - ] - }, - { - fieldId: '110', - fieldName: 'newField110', - tableName: 'test', - dataType: 'int', - comments: 'AGE' - }, - { - fieldId: '111', - fieldName: 'newField111', - tableName: 'test', - dataType: 'cat', - comments: 'GENDER', - possibleValues: [ - { code: '1', description: 'Male' }, - { code: '2', description: 'Female' } - ] - } - ] - } - }); - await authorisedUserStudy.post('/graphql').send({ - query: print(UPLOAD_DATA_IN_ARRAY), - variables: { - studyId: createdStudy.id, - data: [ - { fieldId: '100', value: '1', subjectId: 'I7N3G6G', visitId: '1' }, - { fieldId: '101', value: '2', subjectId: 'I7N3G6G', visitId: '1' }, - { fieldId: '110', value: '25', subjectId: 'I7N3G6G', visitId: '1' }, - { fieldId: '111', value: '1', subjectId: 'I7N3G6G', visitId: '1' }, - { fieldId: '100', value: '2', subjectId: 'GR6R4AR', visitId: '1' }, - { fieldId: '101', value: '1', subjectId: 'GR6R4AR', visitId: '1' }, - { fieldId: '110', value: '35', subjectId: 'GR6R4AR', visitId: '1' }, - { fieldId: '111', value: '2', subjectId: 'GR6R4AR', visitId: '1' }, - { fieldId: '100', value: '1', subjectId: 'I7N3G6G', visitId: '2' }, - { fieldId: '101', value: '2', subjectId: 'I7N3G6G', visitId: '2' }, - { fieldId: '100', value: '1', subjectId: 'GR6R4AR', visitId: '2' }, - { fieldId: '101', value: '2', subjectId: 'GR6R4AR', visitId: '2' } - - ] - } - }); - await admin.post('/graphql').send({ - query: print(CREATE_ONTOLOGY_TREE), - variables: { - studyId: createdStudy.id, - ontologyTree: { - name: 'fakeTree', - routes: [ - { - path: ['QS', 'MFI'], - name: 'AGE', - field: ['$100'], - visitRange: [] - }, - { - path: ['QS', 'MFI'], - name: '', - field: ['$101'], - visitRange: [] - }, - { - path: ['DM'], - name: '', - field: ['$110'], - visitRange: [] - }, - { - path: ['DM'], - name: '', - field: ['$111'], - visitRange: [] - } - ] - } - } - }); - await admin.post('/graphql').send({ - query: print(CREATE_STANDARDIZATION), - variables: { - studyId: createdStudy.id, - standardization: { - type: 'fakeType', - field: ['$100'], - path: ['QS'], - joinByKeys: [], - stdRules: [ - { - entry: 'entry_value', - source: 'value', - parameter: ['fakeValue100'], - filters: null - }, - { - entry: 'entry_reserved', - source: 'reserved', - parameter: ['m_subjectId'], - filters: null - }, - { - entry: 'entry_inc', - source: 'inc', - parameter: ['m_subjectId'], - filters: null - }, - { - entry: 'entry_data', - source: 'data', - parameter: [], - filters: null - }, - { - entry: 'entry_field', - source: 'fieldDef', - parameter: ['comments'], - filters: null - }, - { - entry: 'visit', - source: 'reserved', - parameter: ['m_visitId'], - filters: null - } - ] - } - } - }); - await admin.post('/graphql').send({ - query: print(CREATE_STANDARDIZATION), - variables: { - studyId: createdStudy.id, - standardization: { - type: 'fakeType', - field: ['$101'], - path: ['QS'], - joinByKeys: [], - stdRules: [ - { - entry: 'entry_value', - source: 'value', - parameter: ['fakeValue101'], - filters: null - }, - { - entry: 'entry_reserved', - source: 'reserved', - parameter: ['m_subjectId'], - filters: null - }, - { - entry: 'entry_inc', - source: 'inc', - parameter: ['m_subjectId'], - filters: null - }, - { - entry: 'entry_data', - source: 'data', - parameter: [], - filters: null - }, - { - entry: 'entry_field', - source: 'fieldDef', - parameter: ['comments'], - filters: null - }, - { - entry: 'visit', - source: 'reserved', - parameter: ['m_visitId'], - filters: null - } - ] - } - } - }); - await admin.post('/graphql').send({ - query: print(CREATE_STANDARDIZATION), - variables: { - studyId: createdStudy.id, - standardization: { - type: 'fakeType', - field: ['$110'], - path: ['DM'], - joinByKeys: ['entry_reserved'], - stdRules: [ - { - entry: 'entry_reserved', - source: 'reserved', - parameter: ['m_subjectId'], - filters: null - }, - { - entry: 'index', - source: 'inc', - parameter: ['DM'], - filters: null - }, - { - entry: 'entry_data_age', - source: 'data', - parameter: [], - filters: null - } - ] - } - } - }); - await admin.post('/graphql').send({ - query: print(CREATE_STANDARDIZATION), - variables: { - studyId: createdStudy.id, - standardization: { - type: 'fakeType', - field: ['$111'], - path: ['DM'], - joinByKeys: ['entry_reserved'], - stdRules: [ - { - entry: 'entry_reserved', - source: 'reserved', - parameter: ['m_subjectId'], - filters: null - }, - { - entry: 'index', - source: 'inc', - parameter: ['DM'], - filters: null - }, - { - entry: 'entry_data_gender', - source: 'data', - parameter: [], - filters: { - 1: ['convert', { source: 'value', parameter: 'M' }], - 2: ['convert', { source: 'value', parameter: 'F' }] - } - } - ] - } - } - }); - await admin.post('/graphql').send({ - query: print(CREATE_NEW_DATA_VERSION), - variables: { studyId: createdStudy.id, dataVersion: '1', tag: 'testTag' } - }); - const res = await authorisedUserStudy.post('/graphql').send({ - query: print(GET_DATA_RECORDS), - variables: { - studyId: createdStudy.id, - queryString: { - data_requested: ['100', '101', '110', '111'], - format: 'standardized-fakeType', - cohort: [[]], - new_fields: [] - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.getDataRecords.data.QS.sort((a: { entry_reserved: string; entry_inc: number; }, b: { entry_reserved: any; entry_inc: number; }) => { - return a.entry_reserved !== b.entry_reserved ? a.entry_reserved.localeCompare(b.entry_reserved) - : a.entry_inc - b.entry_inc; - })).toEqual([ - { - entry_value: 'fakeValue100', - entry_reserved: 'GR6R4AR', - entry_inc: 1, - entry_data: '2', - entry_field: 'test', - visit: '1' - }, - { - entry_value: 'fakeValue100', - entry_reserved: 'GR6R4AR', - entry_inc: 2, - entry_data: '1', - entry_field: 'test', - visit: '2' - }, - { - entry_value: 'fakeValue101', - entry_reserved: 'GR6R4AR', - entry_inc: 3, - entry_data: '1', - entry_field: 'test', - visit: '1' - }, - { - entry_value: 'fakeValue101', - entry_reserved: 'GR6R4AR', - entry_inc: 4, - entry_data: '2', - entry_field: 'test', - visit: '2' - }, - { - entry_value: 'fakeValue100', - entry_reserved: 'I7N3G6G', - entry_inc: 1, - entry_data: '1', - entry_field: 'test', - visit: '1' - }, - { - entry_value: 'fakeValue100', - entry_reserved: 'I7N3G6G', - entry_inc: 2, - entry_data: '1', - entry_field: 'test', - visit: '2' - }, - { - entry_value: 'fakeValue101', - entry_reserved: 'I7N3G6G', - entry_inc: 3, - entry_data: '2', - entry_field: 'test', - visit: '1' - }, - { - entry_value: 'fakeValue101', - entry_reserved: 'I7N3G6G', - entry_inc: 4, - entry_data: '2', - entry_field: 'test', - visit: '2' - } - ]); - expect(res.body.data.getDataRecords.data.DM.sort((a: { entry_reserved: string; entry_inc: number; }, b: { entry_reserved: any; entry_inc: number; }) => { - return a.entry_reserved !== b.entry_reserved ? a.entry_reserved.localeCompare(b.entry_reserved) - : a.entry_inc - b.entry_inc; - }).map(el => el.entry_reserved)).toEqual(['GR6R4AR', 'I7N3G6G']); - }); - }); -}); diff --git a/packages/itmat-interface/test/serverTests/study.test.ts b/packages/itmat-interface/test/serverTests/study.test.ts deleted file mode 100644 index bd19b9ee2..000000000 --- a/packages/itmat-interface/test/serverTests/study.test.ts +++ /dev/null @@ -1,4517 +0,0 @@ -/** - * @with Minio - */ -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-nocheck -import request from 'supertest'; -import { print } from 'graphql'; -import { connectAdmin, connectUser, connectAgent } from './_loginHelper'; -import { db } from '../../src/database/database'; -import { Router } from '../../src/server/router'; -import { errorCodes } from '../../src/graphql/errors'; -import { Db, MongoClient } from 'mongodb'; -import { MongoMemoryServer } from 'mongodb-memory-server'; -import { setupDatabase } from '@itmat-broker/itmat-setup'; -import config from '../../config/config.sample.json'; -import { v4 as uuid } from 'uuid'; -import { - GET_STUDY_FIELDS, - GET_PROJECT_PATIENT_MAPPING, - GET_STUDY, - GET_PROJECT, - GET_USERS, - EDIT_ROLE, - ADD_NEW_ROLE, - WHO_AM_I, - CREATE_PROJECT, - CREATE_STUDY, - DELETE_STUDY, - DELETE_PROJECT, - SET_DATAVERSION_AS_CURRENT, - EDIT_STUDY, - UPLOAD_DATA_IN_ARRAY, - DELETE_DATA_RECORDS, - GET_DATA_RECORDS, - CREATE_NEW_DATA_VERSION, - CHECK_DATA_COMPLETE, - CREATE_NEW_FIELD, - DELETE_FIELD, - CREATE_ONTOLOGY_TREE, - DELETE_ONTOLOGY_TREE, - GET_ONTOLOGY_TREE -} from '@itmat-broker/itmat-models'; -import { - userTypes, - studyType, - enumValueType, - IDataEntry, - IUser, - IFile, - IFieldEntry, - IStudyDataVersion, - IStudy, - IProject, - IRole, - IPermissionManagementOptions, - atomicOperation -} from '@itmat-broker/itmat-types'; -import { Express } from 'express'; -import path from 'path'; -import { objStore } from '../../src/objStore/objStore'; - -if (global.hasMinio) { - let app: Express; - let mongodb: MongoMemoryServer; - let admin: request.SuperTest; - let user: request.SuperTest; - let mongoConnection: MongoClient; - let mongoClient: Db; - - afterAll(async () => { - await db.closeConnection(); - await mongoConnection?.close(); - await mongodb.stop(); - - /* claer all mocks */ - jest.clearAllMocks(); - }); - - beforeAll(async () => { // eslint-disable-line no-undef - /* Creating a in-memory MongoDB instance for testing */ - const dbName = uuid(); - mongodb = await MongoMemoryServer.create({ instance: { dbName } }); - const connectionString = mongodb.getUri(); - await setupDatabase(connectionString, dbName); - - /* Wiring up the backend server */ - config.objectStore.port = global.minioContainerPort; - config.database.mongo_url = connectionString; - config.database.database = dbName; - await db.connect(config.database, MongoClient); - await objStore.connect(config.objectStore); - const router = new Router(config); - await router.init(); - - /* Connect mongo client (for test setup later / retrieve info later) */ - mongoConnection = await MongoClient.connect(connectionString); - mongoClient = mongoConnection.db(dbName); - - /* Connecting clients for testing later */ - app = router.getApp(); - admin = request.agent(app); - user = request.agent(app); - await connectAdmin(admin); - await connectUser(user); - - /* Mock Date for testing */ - jest.spyOn(Date, 'now').mockImplementation(() => 1591134065000); - }); - - describe('STUDY API', () => { - let adminId: any; - - beforeAll(async () => { - /* setup: first retrieve the generated user id */ - const result = await mongoClient.collection(config.database.collections.users_collection).find({}, { projection: { id: 1, username: 1 } }).toArray(); - adminId = result.filter((e: { username: string; }) => e.username === 'admin')[0].id; - }); - - describe('MANIPULATING STUDIES EXISTENCE', () => { - test('Create study (admin)', async () => { - const studyName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(CREATE_STUDY), - variables: { name: studyName, description: 'test description', type: studyType.SENSOR } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - - const createdStudy = await mongoClient.collection(config.database.collections.studies_collection).findOne({ name: studyName }); - expect(res.body.data.createStudy).toEqual({ - id: createdStudy.id, - name: studyName, - description: 'test description', - type: studyType.SENSOR - }); - - const resWhoAmI = await admin.post('/graphql').send({ query: print(WHO_AM_I) }); - expect(resWhoAmI.status).toBe(200); - expect(resWhoAmI.body.data.errors).toBeUndefined(); - expect(resWhoAmI.body.data.whoAmI).toEqual({ - username: 'admin', - type: userTypes.ADMIN, - firstname: 'Fadmin', - lastname: 'Ladmin', - organisation: 'organisation_system', - email: 'admin@example.com', - description: 'I am an admin user.', - id: adminId, - access: { - id: `user_access_obj_user_id_${adminId}`, - projects: [], - studies: [{ - id: createdStudy.id, - name: studyName, - type: studyType.SENSOR - }] - }, - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - createdAt: 1591134065000, - expiredAt: 1991134065000, - metadata: null - }); - - /* cleanup: delete study */ - await mongoClient.collection(config.database.collections.studies_collection).findOneAndUpdate({ name: studyName, deleted: null }, { $set: { deleted: new Date().valueOf() } }); - }); - - test('Edit study (admin)', async () => { - const studyName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(CREATE_STUDY), - variables: { name: studyName, description: 'test description', type: studyType.SENSOR } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - - const createdStudy = await mongoClient.collection(config.database.collections.studies_collection).findOne({ name: studyName }); - expect(res.body.data.createStudy).toEqual({ - id: createdStudy.id, - name: studyName, - description: 'test description', - type: studyType.SENSOR - }); - - const editStudy = await admin.post('/graphql').send({ - query: print(EDIT_STUDY), - variables: { studyId: createdStudy.id, description: 'edited description' } - }); - expect(editStudy.body.data.editStudy).toEqual({ - id: createdStudy.id, - name: studyName, - description: 'edited description', - type: studyType.SENSOR - }); - - /* cleanup: delete study */ - await mongoClient.collection(config.database.collections.studies_collection).findOneAndUpdate({ name: studyName, deleted: null }, { $set: { deleted: new Date().valueOf() } }); - }); - - test('Create study that violate unique name constraint (admin)', async () => { - const studyName = uuid(); - const newStudy = { - id: `id_${studyName}`, - name: studyName, - createdBy: 'admin', - lastModified: 200000002, - deleted: null, - currentDataVersion: -1, - dataVersions: [] - }; - await mongoClient.collection(config.database.collections.studies_collection).insertOne(newStudy); - - const res = await admin.post('/graphql').send({ - query: print(CREATE_STUDY), - variables: { name: studyName, description: 'test description', type: studyType.SENSOR } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(`Study "${studyName}" already exists (duplicates are case-insensitive).`); - expect(res.body.data.createStudy).toBe(null); - - /* should be only one study in database */ - const study = await mongoClient.collection(config.database.collections.studies_collection).find({ name: studyName }).toArray(); - expect(study).toEqual([newStudy]); - - /* cleanup: delete study */ - await mongoClient.collection(config.database.collections.studies_collection).findOneAndUpdate({ name: studyName, deleted: null }, { $set: { deleted: new Date().valueOf() } }); - }); - - test('Create study that violate unique name constraint (case insensitive) (admin)', async () => { - const studyName = uuid(); - const newStudy = { - id: `id_${studyName}`, - name: studyName, - createdBy: 'admin', - lastModified: 200000002, - deleted: null, - currentDataVersion: -1, - dataVersions: [] - }; - await mongoClient.collection(config.database.collections.studies_collection).insertOne(newStudy); - - const res = await admin.post('/graphql').send({ - query: print(CREATE_STUDY), - variables: { name: studyName.toUpperCase(), description: 'test description', type: studyType.SENSOR } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(`Study "${studyName.toUpperCase()}" already exists (duplicates are case-insensitive).`); - expect(res.body.data.createStudy).toBe(null); - - /* should be only one study in database */ - const study = await mongoClient.collection(config.database.collections.studies_collection).find({ name: { $in: [studyName, studyName.toUpperCase()] } }).toArray(); - expect(study).toEqual([newStudy]); - - /* cleanup: delete study */ - await mongoClient.collection(config.database.collections.studies_collection).findOneAndUpdate({ name: studyName, deleted: null }, { $set: { deleted: new Date().valueOf() } }); - }); - - test('Create study (user) (should fail)', async () => { - const studyName = uuid(); - const res = await user.post('/graphql').send({ - query: print(CREATE_STUDY), - variables: { name: studyName, description: 'test description', type: studyType.SENSOR } - }); - expect(res.status).toBe(200); - expect(res.body.data.createStudy).toBe(null); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - - const createdStudy = await mongoClient.collection(config.database.collections.studies_collection).findOne({ name: studyName }); - expect(createdStudy).toBe(null); - }); - - test('Edit study (user) (should fail)', async () => { - const studyName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(CREATE_STUDY), - variables: { name: studyName, description: 'test description', type: studyType.SENSOR } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - - const createdStudy = await mongoClient.collection(config.database.collections.studies_collection).findOne({ name: studyName }); - expect(res.body.data.createStudy).toEqual({ - id: createdStudy.id, - name: studyName, - description: 'test description', - type: studyType.SENSOR - }); - - const editStudy = await user.post('/graphql').send({ - query: print(EDIT_STUDY), - variables: { studyId: createdStudy.id, description: 'edited description' } - }); - expect(editStudy.status).toBe(200); - expect(editStudy.body.data.editStudy).toBe(null); - expect(editStudy.body.errors).toHaveLength(1); - expect(editStudy.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - - /* cleanup: delete study */ - await mongoClient.collection(config.database.collections.studies_collection).findOneAndUpdate({ name: studyName, deleted: null }, { $set: { deleted: new Date().valueOf() } }); - }); - - test('Delete study (no projects) (admin)', async () => { - /* setup: create a study to be deleted */ - const studyName = uuid(); - const newStudy = { - id: `id_${studyName}`, - name: studyName, - createdBy: 'admin', - lastModified: 200000002, - deleted: null, - currentDataVersion: -1, - dataVersions: [], - type: studyType.SENSOR - }; - await mongoClient.collection(config.database.collections.studies_collection).insertOne(newStudy); - - const resWhoAmI = await admin.post('/graphql').send({ query: print(WHO_AM_I) }); - expect(resWhoAmI.status).toBe(200); - expect(resWhoAmI.body.data.errors).toBeUndefined(); - expect(resWhoAmI.body.data.whoAmI).toEqual({ - username: 'admin', - type: userTypes.ADMIN, - firstname: 'Fadmin', - lastname: 'Ladmin', - organisation: 'organisation_system', - email: 'admin@example.com', - description: 'I am an admin user.', - id: adminId, - access: { - id: `user_access_obj_user_id_${adminId}`, - projects: [], - studies: [{ - id: newStudy.id, - name: studyName, - type: studyType.SENSOR - }] - }, - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - createdAt: 1591134065000, - expiredAt: 1991134065000, - metadata: null - }); - - /* test */ - const res = await admin.post('/graphql').send({ - query: print(DELETE_STUDY), - variables: { studyId: newStudy.id } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.deleteStudy).toEqual({ - id: newStudy.id, - successful: true - }); - - const study = await mongoClient.collection(config.database.collections.studies_collection).findOne({ id: newStudy.id }); - expect(typeof study.deleted).toBe('number'); - - const resWhoAmIAfter = await admin.post('/graphql').send({ query: print(WHO_AM_I) }); - expect(resWhoAmIAfter.status).toBe(200); - expect(resWhoAmIAfter.body.data.errors).toBeUndefined(); - expect(resWhoAmIAfter.body.data.whoAmI).toEqual({ - username: 'admin', - type: userTypes.ADMIN, - firstname: 'Fadmin', - lastname: 'Ladmin', - organisation: 'organisation_system', - email: 'admin@example.com', - description: 'I am an admin user.', - id: adminId, - access: { - id: `user_access_obj_user_id_${adminId}`, - projects: [], - studies: [] - }, - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - createdAt: 1591134065000, - expiredAt: 1991134065000, - metadata: null - }); - }); - - test('Delete study that has been deleted (no projects) (admin)', async () => { - /* setup: create a study to be deleted */ - const studyName = uuid(); - const newStudy = { - id: `id_${studyName}`, - name: studyName, - createdBy: 'admin', - lastModified: 200000002, - deleted: new Date().valueOf(), - currentDataVersion: -1, - dataVersions: [] - }; - await mongoClient.collection(config.database.collections.studies_collection).insertOne(newStudy); - - /* test */ - const res = await admin.post('/graphql').send({ - query: print(DELETE_STUDY), - variables: { studyId: newStudy.id } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - expect(res.body.data.deleteStudy).toEqual(null); - }); - - test('Delete study that never existed (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(DELETE_STUDY), - variables: { studyId: 'I_never_existed' } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - expect(res.body.data.deleteStudy).toEqual(null); - }); - - test('Delete study (user) (should fail)', async () => { - /* setup: create a study to be deleted */ - const studyName = uuid(); - const newStudy = { - id: `id_${studyName}`, - name: studyName, - createdBy: 'admin', - lastModified: 200000002, - deleted: null, - currentDataVersion: -1, - dataVersions: [] - }; - await mongoClient.collection(config.database.collections.studies_collection).insertOne(newStudy); - - const res = await user.post('/graphql').send({ - query: print(DELETE_STUDY), - variables: { studyId: studyName } - }); - expect(res.status).toBe(200); - expect(res.body.data.deleteStudy).toBe(null); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - - /* confirms that the created study is still alive */ - const createdStudy = await mongoClient.collection(config.database.collections.studies_collection).findOne({ name: studyName }); - expect(createdStudy.deleted).toBe(null); - - /* cleanup: delete study */ - await mongoClient.collection(config.database.collections.studies_collection).findOneAndUpdate({ name: studyName, deleted: null }, { $set: { deleted: new Date().valueOf() } }); - }); - }); - - describe('MANIPULATING PROJECTS EXISTENCE', () => { - let testCounter = 0; - let setupStudy: { id: any; name?: string; createdBy?: string; lastModified?: number; deleted?: null; currentDataVersion?: number; dataVersions?: { id: string; contentId: string; version: string; tag: string; updateDate: string; }[]; }; - let setupProject: { id: any; studyId?: string; createdBy?: string; name?: string; patientMapping?: { patient001: string; }; lastModified?: number; deleted?: null; }; - beforeEach(async () => { - testCounter++; - /* setup: creating a study */ - const studyName = uuid() + 'STUDYNAME_manipulating_project_existentce_' + testCounter; - setupStudy = { - id: `id_${studyName}`, - name: studyName, - createdBy: 'admin', - lastModified: 200000002, - deleted: null, - currentDataVersion: 0, - dataVersions: [ - { - id: 'dataVersionId', - contentId: 'dataVersionContentId', - version: '1', - tag: '1', - updateDate: '1628049066475' - } - ] - }; - await mongoClient.collection(config.database.collections.studies_collection).insertOne(setupStudy); - - /* setup: creating a project */ - const projectName = uuid() + 'PROJECTNAME_manipulating_project_existentce_' + testCounter; - setupProject = { - id: `id_${projectName}`, - studyId: setupStudy.id, - createdBy: 'admin', - name: projectName, - patientMapping: { patient001: 'patientA' }, - lastModified: 20000002, - deleted: null - }; - await mongoClient.collection(config.database.collections.projects_collection).insertOne(setupProject); - }); - - afterEach(async () => { - await mongoClient.collection(config.database.collections.studies_collection).updateOne({ id: setupStudy.id }, { $set: { deleted: 10000 } }); - await mongoClient.collection(config.database.collections.projects_collection).updateOne({ id: setupProject.id }, { $set: { deleted: 10000 } }); - }); - - test('Create project (no existing patients in study) (admin)', async () => { - const projectName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(CREATE_PROJECT), - variables: { - studyId: setupStudy.id, - projectName: projectName - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - - const createdProject = await mongoClient.collection(config.database.collections.projects_collection).findOne({ name: projectName }); - expect(createdProject).toEqual({ - _id: createdProject._id, - id: createdProject.id, - studyId: setupStudy.id, - createdBy: adminId, - name: projectName, - patientMapping: {}, - lastModified: createdProject.lastModified, - deleted: null, - metadata: {} - }); - expect(res.body.data.createProject).toEqual({ - id: createdProject.id, - studyId: setupStudy.id, - name: projectName - }); - - /* cleanup: delete project */ - await mongoClient.collection(config.database.collections.projects_collection).updateOne({ id: createdProject.id }, { $set: { deleted: 10000 } }); - }); - - test('Create project (user with no privilege) (should fail)', async () => { - const res = await user.post('/graphql').send({ - query: print(CREATE_PROJECT), - variables: { - studyId: setupStudy.id, - projectName: 'new_project_4' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.createProject).toBe(null); - }); - - test('Create project (user with privilege)', async () => { - /* setup: creating a privileged user */ - const username = uuid(); - const authorisedUserProfile: IUser = { - username, - type: userTypes.STANDARD, - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}@example.com`, - resetPasswordRequests: [], - description: 'I am a new user.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - deleted: null, - id: `new_user_id_${username}`, - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(authorisedUserProfile); - - const roleId = uuid(); - const newRole = { - id: roleId, - projectId: null, - studyId: setupStudy.id, - name: `${roleId}_rolename`, - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: ['^.*$'], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ, atomicOperation.WRITE], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - users: [authorisedUserProfile.id], - deleted: null - }; - await mongoClient.collection(config.database.collections.roles_collection).insertOne(newRole); - - const authorisedUser = request.agent(app); - await connectAgent(authorisedUser, username, 'admin', authorisedUserProfile.otpSecret); - - /* test */ - const projectName = uuid(); - const res = await authorisedUser.post('/graphql').send({ - query: print(CREATE_PROJECT), - variables: { - studyId: setupStudy.id, - projectName: projectName - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - const createdProject = await mongoClient.collection(config.database.collections.projects_collection).findOne({ name: projectName }); - expect(createdProject).toEqual({ - _id: createdProject._id, - id: createdProject.id, - studyId: setupStudy.id, - createdBy: authorisedUserProfile.id, - patientMapping: {}, - name: projectName, - lastModified: createdProject.lastModified, - deleted: null, - metadata: {} - }); - expect(res.body.data.createProject).toEqual({ - id: createdProject.id, - studyId: setupStudy.id, - name: projectName - }); - - /* cleanup: delete project */ - await mongoClient.collection(config.database.collections.projects_collection).updateOne({ id: createdProject.id }, { $set: { deleted: 10000 } }); - }); - - test('Delete project (user without privilege) (should fail)', async () => { - const res = await user.post('/graphql').send({ - query: print(DELETE_PROJECT), - variables: { - projectId: setupProject.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.deleteProject).toEqual(null); - /* TO_DO make sure api project is not gone */ - }); - - test('Delete project (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(DELETE_PROJECT), - variables: { - projectId: setupProject.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.deleteProject).toEqual({ - id: setupProject.id, - successful: true - }); - /* TO_DO make sure api project is gone */ - }); - - test('Delete project (user with privilege)', async () => { - /* setup: creating a privileged user */ - const username = uuid(); - const authorisedUserProfile: IUser = { - username, - type: userTypes.STANDARD, - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}@example.com`, - resetPasswordRequests: [], - description: 'I am a new user.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - deleted: null, - id: `new_user_id_${username}`, - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(authorisedUserProfile); - - const roleId = uuid(); - const newRole = { - id: roleId, - projectId: null, - studyId: setupStudy.id, - name: `${roleId}_rolename`, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ, atomicOperation.WRITE], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - users: [authorisedUserProfile.id], - deleted: null - }; - await mongoClient.collection(config.database.collections.roles_collection).insertOne(newRole); - - const authorisedUser = request.agent(app); - await connectAgent(authorisedUser, username, 'admin', authorisedUserProfile.otpSecret); - - /* test */ - const res = await authorisedUser.post('/graphql').send({ - query: print(DELETE_PROJECT), - variables: { - projectId: setupProject.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.deleteProject).toEqual({ - id: setupProject.id, - successful: true - }); - /* TO_DO make sure api project is gone */ - }); - - test('Delete a non-existent project (admin) (should fail)', async () => { - const res = await admin.post('/graphql').send({ - query: print(DELETE_PROJECT), - variables: { - projectId: 'I dont exist' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe('Project does not exist.'); - expect(res.body.data.deleteProject).toEqual(null); - }); - }); - - describe('MINI END-TO-END API TEST, NO UI, NO DATA', () => { - let createdProject: { id: any; name: any; patientMapping: { mock_patient1: any; mock_patient2: any; }; }; - let createdStudy: { id: any; name: any; }; - let createdRole_study: { _id: any; id: any; name: any; }; - let createdRole_study_manageProject: { _id: any; id: any; name: any; }; - let createdRole_study_self_access: { _id: any; id: any; name: any; }; - let createdRole_project: { _id: any; id: any; name: any; }; - let createdUserAuthorised: { id: any; firstname: any; lastname: any; username: string; otpSecret: string; }; // profile - let createdUserAuthorisedStudy: { id: any; firstname: any; lastname: any; username: string; otpSecret: string; }; // profile - let createdUserAuthorisedStudyManageProjects: { id: any; firstname: any; lastname: any; username: string; otpSecret: string; }; // profile - let createdUserAuthorisedToOneOrg: { id: any; firstname: any; lastname: any; username: string; otpSecret: string; }; // profile - let authorisedUser: request.SuperTest; // client - let authorisedUserStudy: request.SuperTest; // client - let authorisedUserStudyManageProject: request.SuperTest; // client - let authorisedUserToOneOrg: request.SuperTest; // client - let mockFields: IFieldEntry[]; - let mockFiles: IFile[]; - let mockDataVersion: IStudyDataVersion; - const newMockDataVersion: IStudyDataVersion = { // this is not added right away; but multiple tests uses this - id: 'mockDataVersionId2', - contentId: 'mockContentId2', - version: '0.0.2', - updateDate: '5000000', - tag: 'hey' - }; - - beforeAll(async () => { - /*** setup: create a setup study ***/ - /* 1. create study */ - { - const studyName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(CREATE_STUDY), - variables: { name: studyName, description: 'test description', type: studyType.SENSOR } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - createdStudy = await mongoClient.collection(config.database.collections.studies_collection).findOne({ name: studyName }); - expect(res.body.data.createStudy).toEqual({ - id: createdStudy.id, - name: studyName, - description: 'test description', - type: studyType.SENSOR - }); - } - - /* x. mock - add data to the study */ - { - mockDataVersion = { - id: 'mockDataVersionId', - contentId: 'mockContentId', - version: '0.0.1', - updateDate: '5000000' - }; - const mockData: IDataEntry[] = [ - { - id: 'mockData1_1', - m_subjectId: 'mock_patient1', - m_visitId: 'mockvisitId', - m_studyId: createdStudy.id, - m_versionId: mockDataVersion.id, - m_fieldId: '31', - value: 'male', - metadata: {}, - deleted: null - }, - { - id: 'mockData1_2', - m_subjectId: 'mock_patient1', - m_visitId: 'mockvisitId', - m_studyId: createdStudy.id, - m_versionId: mockDataVersion.id, - m_fieldId: '49', - value: 'England', - metadata: {}, - deleted: null - }, - { - id: 'mockData2_1', - m_subjectId: 'mock_patient2', - m_visitId: 'mockvisitId', - m_studyId: createdStudy.id, - m_versionId: mockDataVersion.id, - m_fieldId: '31', - value: 'female', - metadata: {}, - deleted: null - }, - { - id: 'mockData2_2', - m_subjectId: 'mock_patient2', - m_visitId: 'mockvisitId', - m_studyId: createdStudy.id, - m_versionId: mockDataVersion.id, - m_fieldId: '49', - value: 'France', - metadata: {}, - deleted: null - } - ]; - mockFields = [ - { - id: 'mockfield1', - studyId: createdStudy.id, - fieldId: '31', - fieldName: 'Sex', - dataType: enumValueType.STRING, - possibleValues: [], - unit: 'person', - comments: 'mockComments1', - dateAdded: '10000', - dateDeleted: null, - dataVersion: 'mockDataVersionId' - }, - { - id: 'mockfield2', - studyId: createdStudy.id, - fieldId: '32', - fieldName: 'Race', - dataType: enumValueType.STRING, - possibleValues: [], - unit: 'person', - comments: 'mockComments2', - dateAdded: '20000', - dateDeleted: null, - dataVersion: 'mockDataVersionId' - } - ]; - - mockFiles = [ - { - id: 'mockfile1_id', - fileName: 'I7N3G6G-MMM7N3G6G-20200704-20210429.txt', - studyId: createdStudy.id, - fileSize: '1000', - description: 'Just a test file1', - uploadTime: '1599345644000', - uploadedBy: adminId, - uri: 'fakeuri', - deleted: null, - hash: 'b0dc2ae76cdea04dcf4be7fcfbe36e2ce8d864fe70a1895c993ce695274ba7a0' - }, - { - id: 'mockfile2_id', - fileName: 'GR6R4AR-MMMS3JSPP-20200601-20200703.json', - studyId: createdStudy.id, - fileSize: '1000', - description: 'Just a test file2', - uploadTime: '1599345644000', - uploadedBy: adminId, - uri: 'fakeuri2', - deleted: null, - hash: '4ae25be36354ee0aec8dc8deac3f279d2e9d6415361da996cf57eb6142cfb1a3' - } - ]; - await db.collections!.studies_collection.updateOne({ id: createdStudy.id }, { - $push: { dataVersions: mockDataVersion }, - $inc: { currentDataVersion: 1 }, - $set: { - ontologyTrees: [{ - id: 'testOntology_id', - name: 'testOntology', - routes: [ - { - path: [ - 'MO', - 'MOCK' - ], - name: 'mockfield1', - field: [ - '$31' - ], - visitRange: [], - id: '036b7772-f239-4fef-b7f8-c3db883f51e3' - }, - { - path: [ - 'MO', - 'MOCK' - ], - name: 'mockfield2', - field: [ - '$32' - ], - visitRange: [], - id: 'f577023f-de54-446a-9bbe-1c346823e6bf' - } - ] - }] - } - }); - await db.collections!.data_collection.insertMany(mockData); - await db.collections!.field_dictionary_collection.insertMany(mockFields); - await db.collections!.files_collection.insertMany(mockFiles); - } - - /* 2. create projects for the study */ - { - const projectName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(CREATE_PROJECT), - variables: { - studyId: createdStudy.id, - projectName: projectName, - dataVersion: mockDataVersion.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - createdProject = await mongoClient.collection(config.database.collections.projects_collection).findOne({ name: projectName }); - expect(res.body.data.createProject).toEqual({ - id: createdProject.id, - studyId: createdStudy.id, - name: projectName - }); - } - - /* 3. create roles for study */ - { - const roleName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(ADD_NEW_ROLE), - variables: { - roleName, - studyId: createdStudy.id, - projectId: null - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - - createdRole_study = await mongoClient.collection(config.database.collections.roles_collection).findOne({ name: roleName }); - expect(createdRole_study).toEqual({ - _id: createdRole_study._id, - id: createdRole_study.id, - projectId: null, - studyId: createdStudy.id, - name: roleName, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - description: '', - createdBy: adminId, - users: [], - deleted: null, - metadata: {} - }); - expect(res.body.data.addRole).toEqual({ - id: createdRole_study.id, - name: roleName, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - studyId: createdStudy.id, - projectId: null, - users: [] - }); - } - /* create another role for study (this time it will have "manage project" privilege - equivalent to PI */ - { - const roleName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(ADD_NEW_ROLE), - variables: { - roleName, - studyId: createdStudy.id, - projectId: null - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - - createdRole_study_manageProject = await mongoClient.collection(config.database.collections.roles_collection).findOne({ name: roleName }); - expect(createdRole_study_manageProject).toEqual({ - _id: createdRole_study_manageProject._id, - id: createdRole_study_manageProject.id, - projectId: null, - studyId: createdStudy.id, - name: roleName, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - description: '', - createdBy: adminId, - users: [], - deleted: null, - metadata: {} - }); - expect(res.body.data.addRole).toEqual({ - id: createdRole_study_manageProject.id, - name: roleName, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - studyId: createdStudy.id, - projectId: null, - users: [] - }); - } - - /* create another role for access data with self organisation only */ - { - const roleName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(ADD_NEW_ROLE), - variables: { - roleName, - studyId: createdStudy.id, - projectId: null - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - - createdRole_study_self_access = await mongoClient.collection(config.database.collections.roles_collection).findOne({ name: roleName }); - expect(createdRole_study_self_access).toEqual({ - _id: createdRole_study_self_access._id, - id: createdRole_study_self_access.id, - projectId: null, - studyId: createdStudy.id, - name: roleName, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - description: '', - createdBy: adminId, - users: [], - deleted: null, - metadata: {} - }); - expect(res.body.data.addRole).toEqual({ - id: createdRole_study_self_access.id, - name: roleName, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - studyId: createdStudy.id, - projectId: null, - users: [] - }); - } - - /* 4. create roles for project */ - { - const roleName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(ADD_NEW_ROLE), - variables: { - roleName, - studyId: createdStudy.id, - projectId: createdProject.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - createdRole_project = await mongoClient.collection(config.database.collections.roles_collection).findOne({ name: roleName }); - expect(createdRole_project).toEqual({ - _id: createdRole_project._id, - id: createdRole_project.id, - projectId: createdProject.id, - studyId: createdStudy.id, - name: roleName, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - description: '', - createdBy: adminId, - users: [], - deleted: null, - metadata: {} - }); - expect(res.body.data.addRole).toEqual({ - id: createdRole_project.id, - name: roleName, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - studyId: createdStudy.id, - projectId: createdProject.id, - users: [] - }); - } - - /* 5. create an authorised project user (no role yet) */ - { - const username = uuid(); - const newUser: IUser = { - username: username, - type: userTypes.STANDARD, - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}@user.io`, - resetPasswordRequests: [], - description: 'I am an authorised project user.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - deleted: null, - id: `AuthorisedProjectUser_${username}`, - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - createdUserAuthorised = await mongoClient.collection(config.database.collections.users_collection).findOne({ username }); - } - - /* 6. add authorised user to role */ - { - const res = await admin.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: createdRole_project.id, - userChanges: { - add: [createdUserAuthorised.id], - remove: [] - }, - permissionChanges: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: createdRole_project.id, - name: createdRole_project.name, - studyId: createdStudy.id, - projectId: createdProject.id, - description: '', - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - users: [{ - id: createdUserAuthorised.id, - organisation: 'organisation_system', - firstname: createdUserAuthorised.firstname, - lastname: createdUserAuthorised.lastname - }] - }); - const resUser = await admin.post('/graphql').send({ - query: print(GET_USERS), - variables: { - fetchDetailsAdminOnly: false, - userId: createdUserAuthorised.id, - fetchAccessPrivileges: true - } - }); - expect(resUser.status).toBe(200); - expect(resUser.body.errors).toBeUndefined(); - expect(resUser.body.data.getUsers).toHaveLength(1); - expect(resUser.body.data.getUsers[0]).toEqual({ - id: createdUserAuthorised.id, - type: userTypes.STANDARD, - firstname: `${createdUserAuthorised.username}_firstname`, - lastname: `${createdUserAuthorised.username}_lastname`, - organisation: 'organisation_system', - access: { - id: `user_access_obj_user_id_${createdUserAuthorised.id}`, - projects: [{ - id: createdProject.id, - name: createdProject.name, - studyId: createdStudy.id - }], - studies: [] - } - }); - } - - /* 5. create an authorised study user (no role yet) */ - { - const username = uuid(); - const newUser: IUser = { - username: username, - type: userTypes.STANDARD, - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}@user.io`, - resetPasswordRequests: [], - description: 'I am an authorised study user.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - deleted: null, - id: `AuthorisedStudyUser_${username}`, - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - createdUserAuthorisedStudy = await mongoClient.collection(config.database.collections.users_collection).findOne({ username }); - } - - /* 6. add authorised user to role */ - { - const res = await admin.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: createdRole_study.id, - userChanges: { - add: [createdUserAuthorisedStudy.id], - remove: [] - }, - permissionChanges: { - data: { - fieldIds: ['^.*$'], - hasVersioned: true, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: createdRole_study.id, - name: createdRole_study.name, - studyId: createdStudy.id, - projectId: null, - description: '', - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: true, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - users: [{ - id: createdUserAuthorisedStudy.id, - organisation: 'organisation_system', - firstname: createdUserAuthorisedStudy.firstname, - lastname: createdUserAuthorisedStudy.lastname - }] - }); - const resUser = await admin.post('/graphql').send({ - query: print(GET_USERS), - variables: { - fetchDetailsAdminOnly: false, - userId: createdUserAuthorisedStudy.id, - fetchAccessPrivileges: true - } - }); - expect(resUser.status).toBe(200); - expect(resUser.body.errors).toBeUndefined(); - expect(resUser.body.data.getUsers).toHaveLength(1); - expect(resUser.body.data.getUsers[0]).toEqual({ - id: createdUserAuthorisedStudy.id, - type: userTypes.STANDARD, - firstname: `${createdUserAuthorisedStudy.username}_firstname`, - lastname: `${createdUserAuthorisedStudy.username}_lastname`, - organisation: 'organisation_system', - access: { - id: `user_access_obj_user_id_${createdUserAuthorisedStudy.id}`, - projects: [{ - id: createdProject.id, - name: createdProject.name, - studyId: createdStudy.id - }], - studies: [{ - id: createdStudy.id, - name: createdStudy.name - }] - } - }); - } - - /* 5. create an authorised study user that can manage projects (no role yet) */ - { - const username = uuid(); - const newUser: IUser = { - username: username, - type: userTypes.STANDARD, - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}'@user.io'`, - resetPasswordRequests: [], - description: 'I am an authorised study user managing project.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - deleted: null, - id: `AuthorisedStudyUserManageProject_${username}`, - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - createdUserAuthorisedStudyManageProjects = await mongoClient.collection(config.database.collections.users_collection).findOne({ username }); - } - - /* 5. create an authorised study user that can access data from self organisation only */ - { - const username = uuid(); - const newUser: IUser = { - username: username, - type: userTypes.STANDARD, - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}'@user.io'`, - resetPasswordRequests: [], - description: 'I am an authorised study user to access self org data.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_user', - deleted: null, - id: `AuthorisedStudyUserAccessSelfData_${username}`, - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - createdUserAuthorisedToOneOrg = await mongoClient.collection(config.database.collections.users_collection).findOne({ username }); - } - - /* 6. add authorised user to role */ - { - const res = await admin.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: createdRole_study_manageProject.id, - userChanges: { - add: [createdUserAuthorisedStudyManageProjects.id], - remove: [] - }, - permissionChanges: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ, atomicOperation.WRITE], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: createdRole_study_manageProject.id, - name: createdRole_study_manageProject.name, - studyId: createdStudy.id, - projectId: null, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ, atomicOperation.WRITE], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - users: [{ - id: createdUserAuthorisedStudyManageProjects.id, - organisation: 'organisation_system', - firstname: createdUserAuthorisedStudyManageProjects.firstname, - lastname: createdUserAuthorisedStudyManageProjects.lastname - }] - }); - const resUser = await admin.post('/graphql').send({ - query: print(GET_USERS), - variables: { - fetchDetailsAdminOnly: false, - userId: createdUserAuthorisedStudyManageProjects.id, - fetchAccessPrivileges: true - } - }); - expect(resUser.status).toBe(200); - expect(resUser.body.errors).toBeUndefined(); - expect(resUser.body.data.getUsers).toHaveLength(1); - expect(resUser.body.data.getUsers[0]).toEqual({ - id: createdUserAuthorisedStudyManageProjects.id, - type: userTypes.STANDARD, - firstname: `${createdUserAuthorisedStudyManageProjects.username}_firstname`, - lastname: `${createdUserAuthorisedStudyManageProjects.username}_lastname`, - organisation: 'organisation_system', - access: { - id: `user_access_obj_user_id_${createdUserAuthorisedStudyManageProjects.id}`, - projects: [{ - id: createdProject.id, - name: createdProject.name, - studyId: createdStudy.id - }], - studies: [{ - id: createdStudy.id, - name: createdStudy.name - }] - } - }); - } - /* 7. add authorised user to role */ - { - const res = await admin.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: createdRole_study_self_access.id, - userChanges: { - add: [createdUserAuthorisedToOneOrg.id], - remove: [] - }, - permissionChanges: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^K.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ, atomicOperation.WRITE], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: createdRole_study_self_access.id, - name: createdRole_study_self_access.name, - studyId: createdStudy.id, - projectId: null, - description: '', - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^K.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ, atomicOperation.WRITE], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - users: [{ - id: createdUserAuthorisedToOneOrg.id, - organisation: 'organisation_user', - firstname: createdUserAuthorisedToOneOrg.firstname, - lastname: createdUserAuthorisedToOneOrg.lastname - }] - }); - const resUser = await admin.post('/graphql').send({ - query: print(GET_USERS), - variables: { - fetchDetailsAdminOnly: false, - userId: createdUserAuthorisedToOneOrg.id, - fetchAccessPrivileges: true - } - }); - expect(resUser.status).toBe(200); - expect(resUser.body.errors).toBeUndefined(); - expect(resUser.body.data.getUsers).toHaveLength(1); - expect(resUser.body.data.getUsers[0]).toEqual({ - id: createdUserAuthorisedToOneOrg.id, - type: userTypes.STANDARD, - firstname: `${createdUserAuthorisedToOneOrg.username}_firstname`, - lastname: `${createdUserAuthorisedToOneOrg.username}_lastname`, - organisation: 'organisation_user', - access: { - id: `user_access_obj_user_id_${createdUserAuthorisedToOneOrg.id}`, - projects: [{ - id: createdProject.id, - name: createdProject.name, - studyId: createdStudy.id - }], - studies: [{ - id: createdStudy.id, - name: createdStudy.name - }] - } - }); - } - /* fsdafs: admin who am i */ - { - const res = await admin.post('/graphql').send({ query: print(WHO_AM_I) }); - expect(res.body.data.whoAmI).toStrictEqual({ - username: 'admin', - type: userTypes.ADMIN, - firstname: 'Fadmin', - lastname: 'Ladmin', - organisation: 'organisation_system', - email: 'admin@example.com', - description: 'I am an admin user.', - id: adminId, - access: { - id: `user_access_obj_user_id_${adminId}`, - projects: [{ - id: createdProject.id, - name: createdProject.name, - studyId: createdStudy.id - }], - studies: [{ - id: createdStudy.id, - name: createdStudy.name, - type: studyType.SENSOR - }] - }, - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - createdAt: 1591134065000, - expiredAt: 1991134065000, - metadata: null - }); - } - /* connecting users */ - authorisedUser = request.agent(app); - await connectAgent(authorisedUser, createdUserAuthorised.username, 'admin', createdUserAuthorised.otpSecret); - authorisedUserStudy = request.agent(app); - await connectAgent(authorisedUserStudy, createdUserAuthorisedStudy.username, 'admin', createdUserAuthorisedStudy.otpSecret); - authorisedUserStudyManageProject = request.agent(app); - await connectAgent(authorisedUserStudyManageProject, createdUserAuthorisedStudyManageProjects.username, 'admin', createdUserAuthorisedStudyManageProjects.otpSecret); - authorisedUserToOneOrg = request.agent(app); - await connectAgent(authorisedUserToOneOrg, createdUserAuthorisedToOneOrg.username, 'admin', createdUserAuthorisedToOneOrg.otpSecret); - }); - - afterAll(async () => { - /* project user cannot delete study */ - { - const res = await authorisedUser.post('/graphql').send({ - query: print(DELETE_STUDY), - variables: { studyId: createdStudy.id } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.deleteStudy).toBe(null); - } - - /* delete values in db */ - await db.collections!.field_dictionary_collection.deleteMany({ studyId: createdStudy.id }); - await db.collections!.data_collection.deleteMany({ m_studyId: createdStudy.id }); - await db.collections!.files_collection.deleteMany({ studyId: createdStudy.id }); - - /* study user cannot delete study */ - { - const res = await authorisedUserStudy.post('/graphql').send({ - query: print(DELETE_STUDY), - variables: { studyId: createdStudy.id } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.deleteStudy).toBe(null); - } - - /* admin can delete study */ - { - const res = await admin.post('/graphql').send({ - query: print(DELETE_STUDY), - variables: { studyId: createdStudy.id } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.deleteStudy).toEqual({ - id: createdStudy.id, - successful: true - }); - } - - /* check projects and roles are also deleted */ - { - const res = await admin.post('/graphql').send({ query: print(WHO_AM_I) }); - expect(res.body.data.whoAmI).toEqual({ - username: 'admin', - type: userTypes.ADMIN, - firstname: 'Fadmin', - lastname: 'Ladmin', - organisation: 'organisation_system', - email: 'admin@example.com', - description: 'I am an admin user.', - id: adminId, - access: { - id: `user_access_obj_user_id_${adminId}`, - projects: [], - studies: [] - }, - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - createdAt: 1591134065000, - expiredAt: 1991134065000, - metadata: null - }); - - // study data is NOT deleted for audit purposes - unless explicitly requested separately - const roles = await db.collections!.roles_collection.find({ studyId: createdStudy.id, deleted: null }).toArray(); - const projects = await db.collections!.projects_collection.find({ studyId: createdStudy.id, deleted: null }).toArray(); - const study = await db.collections!.studies_collection.findOne({ id: createdStudy.id, deleted: null }); - expect(roles).toEqual([]); - expect(projects).toEqual([]); - expect(study).toBe(null); - } - - /* cannot get study from api anymore */ - { - const res = await admin.post('/graphql').send({ - query: print(GET_STUDY), - variables: { studyId: createdStudy.id } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - expect(res.body.data.getStudy).toBe(null); - } - }); - - test('Get a non-existent study (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(GET_STUDY), - variables: { studyId: 'iamfake' } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - expect(res.body.data.getStudy).toBe(null); - }); - - test('Get a non-existent project (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(GET_PROJECT), - variables: { projectId: 'iamfake', admin: true } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - expect(res.body.data.getProject).toBe(null); - }); - - test('Get study (admin)', async () => { - { - const res = await admin.post('/graphql').send({ - query: print(GET_STUDY), - variables: { studyId: createdStudy.id } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.getStudy).toEqual({ - id: createdStudy.id, - name: createdStudy.name, - createdBy: adminId, - jobs: [], - description: 'test description', - type: studyType.SENSOR, - projects: [ - { - id: createdProject.id, - studyId: createdStudy.id, - name: createdProject.name - } - ], - roles: [ - { - id: createdRole_study.id, - name: createdRole_study.name, - description: '', - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: true, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - projectId: null, - studyId: createdStudy.id, - users: [{ - id: createdUserAuthorisedStudy.id, - organisation: 'organisation_system', - firstname: createdUserAuthorisedStudy.firstname, - lastname: createdUserAuthorisedStudy.lastname, - username: createdUserAuthorisedStudy.username - }] - }, - { - id: createdRole_study_manageProject.id, - name: createdRole_study_manageProject.name, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ, atomicOperation.WRITE], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - projectId: null, - studyId: createdStudy.id, - users: [{ - id: createdUserAuthorisedStudyManageProjects.id, - organisation: 'organisation_system', - firstname: createdUserAuthorisedStudyManageProjects.firstname, - lastname: createdUserAuthorisedStudyManageProjects.lastname, - username: createdUserAuthorisedStudyManageProjects.username - }] - }, - { - id: createdRole_study_self_access.id, - name: createdRole_study_self_access.name, - description: '', - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^K.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ, atomicOperation.WRITE], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - projectId: null, - studyId: createdStudy.id, - users: [{ - id: createdUserAuthorisedToOneOrg.id, - organisation: 'organisation_user', - firstname: createdUserAuthorisedToOneOrg.firstname, - lastname: createdUserAuthorisedToOneOrg.lastname, - username: createdUserAuthorisedToOneOrg.username - }] - } - ], - files: [], - numOfRecords: [4, 0], - subjects: [['mock_patient1', 'mock_patient2'], []], - visits: [['mockvisitId'], []], - currentDataVersion: 0, - dataVersions: [{ - id: 'mockDataVersionId', - contentId: 'mockContentId', - version: '0.0.1', - // fileSize: '10000', - updateDate: '5000000', - tag: null - }] - }); - } - { - const res = await admin.post('/graphql').send({ - query: print(GET_PROJECT), - variables: { projectId: createdProject.id, admin: true } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.getProject).toEqual({ - id: createdProject.id, - studyId: createdStudy.id, - name: createdProject.name, - dataVersion: { - contentId: 'mockContentId', - id: 'mockDataVersionId', - tag: null, - updateDate: '5000000', - version: '0.0.1' - }, - summary: { - subjects: [ - 'mock_patient1', - 'mock_patient2' - ], - visits: [ - 'mockvisitId' - ], - standardizationTypes: [] - }, - jobs: [], - roles: [ - { - id: createdRole_project.id, - name: createdRole_project.name, - description: '', - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - projectId: createdProject.id, - studyId: createdStudy.id, - users: [{ - id: createdUserAuthorised.id, - organisation: 'organisation_system', - firstname: createdUserAuthorised.firstname, - lastname: createdUserAuthorised.lastname, - username: createdUserAuthorised.username - }] - } - ], - iCanEdit: true, - fields: [], - files: [] - }); - } - }); - - test('Get study (user that can only access self org data)', async () => { - const res = await authorisedUserToOneOrg.post('/graphql').send({ - query: print(GET_STUDY), - variables: { studyId: createdStudy.id } - }); - expect(res.status).toBe(200); - // expect(res.body.errors).toBeUndefined(); - // expect(res.body.data.getStudy.files[0]).toEqual({ - // id: 'mockfile1_id', - // fileName: 'I7N3G6G-MMM7N3G6G-20200704-20210429.txt', - // studyId: createdStudy.id, - // projectId: null, - // fileSize: '1000', - // description: 'Just a test file1', - // uploadTime: '1599345644000', - // uploadedBy: adminId, - // hash: 'b0dc2ae76cdea04dcf4be7fcfbe36e2ce8d864fe70a1895c993ce695274ba7a0' - // } - // ); - }); - - test('Get patient mapping (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(GET_PROJECT_PATIENT_MAPPING), - variables: { projectId: createdProject.id } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.getProject).toEqual({ - id: createdProject.id, - patientMapping: { - mock_patient1: createdProject.patientMapping.mock_patient1, - mock_patient2: createdProject.patientMapping.mock_patient2 - } - }); - const { patientMapping } = res.body.data.getProject; - expect(typeof patientMapping.mock_patient1).toBe('string'); - expect(patientMapping.mock_patient1).not.toBe('mock_patient1'); // should not be the same as before mapped - expect(typeof patientMapping.mock_patient2).toBe('string'); - expect(patientMapping.mock_patient2).not.toBe('mock_patient2'); // should not be the same as before mapped - }); - - test('Get patient mapping (user without privilege) (should fail)', async () => { - const res = await user.post('/graphql').send({ - query: print(GET_PROJECT_PATIENT_MAPPING), - variables: { projectId: createdProject.id } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.getProject).toBe(null); - }); - - test('Get patient mapping (user with project data privilege) (should fail)', async () => { - // patient mapping is obscured from users that can only access project data. They should only see the mapped id - const res = await authorisedUser.post('/graphql').send({ - query: print(GET_PROJECT_PATIENT_MAPPING), - variables: { projectId: createdProject.id } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.getProject).toBe(null); - }); - - test('Get patient mapping (user with study data privilege)', async () => { - const res = await authorisedUserStudy.post('/graphql').send({ - query: print(GET_PROJECT_PATIENT_MAPPING), - variables: { projectId: createdProject.id } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.getProject).toEqual({ - id: createdProject.id, - patientMapping: { - mock_patient1: createdProject.patientMapping.mock_patient1, - mock_patient2: createdProject.patientMapping.mock_patient2 - } - }); - const { patientMapping } = res.body.data.getProject; - expect(typeof patientMapping.mock_patient1).toBe('string'); - expect(patientMapping.mock_patient1).not.toBe('mock_patient1'); // should not be the same as before mapped - expect(typeof patientMapping.mock_patient2).toBe('string'); - expect(patientMapping.mock_patient2).not.toBe('mock_patient2'); // should not be the same as before mapped - }); - - test('Get study (user without privilege)', async () => { - const res = await user.post('/graphql').send({ - query: print(GET_STUDY), - variables: { studyId: createdStudy.id } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.getStudy).toBe(null); - }); - - test('Get study (user with privilege)', async () => { - { - const res = await authorisedUser.post('/graphql').send({ - query: print(GET_STUDY), - variables: { studyId: createdStudy.id } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.getStudy).toEqual(null); - } - { - const res = await authorisedUser.post('/graphql').send({ - query: print(GET_PROJECT), - variables: { projectId: createdProject.id, admin: false } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.getProject).toEqual({ - id: createdProject.id, - studyId: createdStudy.id, - name: createdProject.name, - jobs: [], - iCanEdit: true, - fields: [], - files: [], - dataVersion: { - contentId: 'mockContentId', - id: 'mockDataVersionId', - tag: null, - updateDate: '5000000', - version: '0.0.1' - }, - summary: { - subjects: [ - 'mock_patient1', - 'mock_patient2' - ], - visits: [ - 'mockvisitId' - ], - standardizationTypes: [] - } - }); - } - }); - - test('Get study fields (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(GET_STUDY_FIELDS), - variables: { - studyId: createdStudy.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.getStudyFields.sort((a: { id: string; }, b: { id: any; }) => a.id.localeCompare(b.id))).toEqual([ // as the api will sort the results, the order is changed - { - id: 'mockfield2', - studyId: createdStudy.id, - fieldId: '32', - fieldName: 'Race', - tableName: null, - dataType: enumValueType.STRING, - possibleValues: [], - unit: 'person', - comments: 'mockComments2', - dateAdded: '20000', - dateDeleted: null, - dataVersion: 'mockDataVersionId', - metadata: { - [`role:${createdRole_study.id}`]: true, - [`role:${createdRole_project.id}`]: true, - [`role:${createdRole_study_manageProject.id}`]: false, - [`role:${createdRole_study_self_access.id}`]: true - } - }, - { - id: 'mockfield1', - studyId: createdStudy.id, - fieldId: '31', - fieldName: 'Sex', - tableName: null, - dataType: enumValueType.STRING, - possibleValues: [], - unit: 'person', - comments: 'mockComments1', - dateAdded: '10000', - dateDeleted: null, - dataVersion: 'mockDataVersionId', - metadata: { - [`role:${createdRole_study.id}`]: true, - [`role:${createdRole_project.id}`]: true, - [`role:${createdRole_study_manageProject.id}`]: false, - [`role:${createdRole_study_self_access.id}`]: true - } - } - ].sort((a, b) => a.id.localeCompare(b.id))); - }); - - test('Get study fields (user project privilege)', async () => { - const res = await authorisedUser.post('/graphql').send({ - query: print(GET_STUDY_FIELDS), - variables: { - studyId: createdStudy.id, - projectId: createdProject.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.getStudyFields.sort((a: { id: string; }, b: { id: any; }) => a.id.localeCompare(b.id))).toEqual([ // as the api will sort the results, the order is changed - { - id: 'mockfield1', - studyId: createdStudy.id, - fieldId: '31', - fieldName: 'Sex', - tableName: null, - dataType: enumValueType.STRING, - possibleValues: [], - unit: 'person', - comments: 'mockComments1', - dateAdded: '10000', - dateDeleted: null, - dataVersion: 'mockDataVersionId', - metadata: null - }, - { - id: 'mockfield2', - studyId: createdStudy.id, - fieldId: '32', - fieldName: 'Race', - tableName: null, - dataType: enumValueType.STRING, - possibleValues: [], - unit: 'person', - comments: 'mockComments2', - dateAdded: '20000', - dateDeleted: null, - dataVersion: 'mockDataVersionId', - metadata: null - } - ].sort((a, b) => a.id.localeCompare(b.id))); - }); - - test('Get study fields (user without project privilege nor study privilege) (should fail)', async () => { - const res = await user.post('/graphql').send({ - query: print(GET_STUDY_FIELDS), - variables: { - studyId: createdStudy.id, - projectId: createdProject.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.getStudyFields).toBe(null); - }); - - test('Get study fields, with unversioned fields', async () => { - // delete an exisiting field and add a new field - await db.collections!.field_dictionary_collection.insertOne({ - id: 'mockfield2_deleted', - studyId: createdStudy.id, - fieldId: '32', - fieldName: 'Race', - tableName: null, - dataType: enumValueType.STRING, - possibleValues: [], - unit: 'person', - comments: 'mockComments1', - dateAdded: '30000', - dateDeleted: '30000', - dataVersion: null - }); - - await db.collections!.field_dictionary_collection.insertOne({ - id: 'mockfield3', - studyId: createdStudy.id, - fieldId: '33', - fieldName: 'Weight', - tableName: null, - dataType: enumValueType.DECIMAL, - possibleValues: [], - unit: 'kg', - comments: 'mockComments3', - dateAdded: '30000', - dateDeleted: null, - dataVersion: null - }); - - // user with study privilege can access all latest field, including unversioned - const res = await authorisedUserStudy.post('/graphql').send({ - query: print(GET_STUDY_FIELDS), - variables: { - studyId: createdStudy.id, - projectId: createdProject.id, - versionId: null - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.getStudyFields.map(el => el.id).sort()).toEqual(['mockfield1', 'mockfield3']); - // user with project privilege can only access the latest fields that are versioned - const res2 = await authorisedUser.post('/graphql').send({ - query: print(GET_STUDY_FIELDS), - variables: { - studyId: createdStudy.id, - projectId: createdProject.id - } - }); - expect(res2.status).toBe(200); - expect(res2.body.errors).toBeUndefined(); - expect(res2.body.data.getStudyFields.sort((a: { id: string; }, b: { id: any; }) => a.id.localeCompare(b.id))).toEqual([ // as the api will sort the results, the order is changed - { - id: 'mockfield2', - studyId: createdStudy.id, - fieldId: '32', - fieldName: 'Race', - tableName: null, - dataType: enumValueType.STRING, - possibleValues: [], - unit: 'person', - comments: 'mockComments2', - dateAdded: '20000', - dateDeleted: null, - dataVersion: 'mockDataVersionId', - metadata: null - }, - { - id: 'mockfield1', - studyId: createdStudy.id, - fieldId: '31', - fieldName: 'Sex', - tableName: null, - dataType: enumValueType.STRING, - possibleValues: [], - unit: 'person', - comments: 'mockComments1', - dateAdded: '10000', - dateDeleted: null, - dataVersion: 'mockDataVersionId', - metadata: null - } - ].sort((a, b) => a.id.localeCompare(b.id))); - // clear database - await db.collections!.field_dictionary_collection.deleteMany({ dataVersion: null }); - }); - - test('Set a previous study dataversion as current (admin)', async () => { - /* setup: add an extra dataversion */ - await db.collections!.studies_collection.updateOne({ id: createdStudy.id }, { $push: { dataVersions: newMockDataVersion }, $inc: { currentDataVersion: 1 } }); - const res = await admin.post('/graphql').send({ - query: print(SET_DATAVERSION_AS_CURRENT), - variables: { - studyId: createdStudy.id, - dataVersionId: mockDataVersion.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - const study = await db.collections!.studies_collection.findOne({ id: createdStudy.id }, { projection: { dataVersions: 1 } }); - expect(study).toBeDefined(); - expect(res.body.data.setDataversionAsCurrent).toEqual({ - id: createdStudy.id, - currentDataVersion: 0, - dataVersions: [ - { ...mockDataVersion, tag: null }, - { ...newMockDataVersion } - ] - }); - - /* cleanup: reverse setting dataversion */ - await mongoClient.collection(config.database.collections.studies_collection) - .updateOne({ id: createdStudy.id }, { $set: { dataVersions: [mockDataVersion], currentDataVersion: 0 } }); - }); - - test('Set a previous study dataversion as current (user without privilege) (should fail)', async () => { - /* setup: add an extra dataversion */ - await db.collections!.studies_collection.updateOne({ id: createdStudy.id }, { $push: { dataVersions: newMockDataVersion }, $inc: { currentDataVersion: 1 } }); - - const res = await user.post('/graphql').send({ - query: print(SET_DATAVERSION_AS_CURRENT), - variables: { - studyId: createdStudy.id, - dataVersionId: mockDataVersion.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.data.setDataversionAsCurrent).toEqual(null); - - /* cleanup: reverse setting dataversion */ - await mongoClient.collection(config.database.collections.studies_collection) - .updateOne({ id: createdStudy.id }, { $set: { dataVersions: [mockDataVersion], currentDataVersion: 0 } }); - }); - - test('Set a previous study dataversion as current (user with project privilege) (should fail)', async () => { - /* setup: add an extra dataversion */ - await db.collections!.studies_collection.updateOne({ id: createdStudy.id }, { $push: { dataVersions: newMockDataVersion }, $inc: { currentDataVersion: 1 } }); - - const res = await authorisedUser.post('/graphql').send({ - query: print(SET_DATAVERSION_AS_CURRENT), - variables: { - studyId: createdStudy.id, - dataVersionId: mockDataVersion.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.data.setDataversionAsCurrent).toEqual(null); - - /* cleanup: reverse setting dataversion */ - await mongoClient.collection(config.database.collections.studies_collection) - .updateOne({ id: createdStudy.id }, { $set: { dataVersions: [mockDataVersion], currentDataVersion: 0 } }); - }); - - test('Set a previous study dataversion as current (user with study read-only privilege) (should fail)', async () => { - /* setup: add an extra dataversion */ - await db.collections!.studies_collection.updateOne({ id: createdStudy.id }, { $push: { dataVersions: newMockDataVersion }, $inc: { currentDataVersion: 1 } }); - - const res = await authorisedUserStudy.post('/graphql').send({ - query: print(SET_DATAVERSION_AS_CURRENT), - variables: { - studyId: createdStudy.id, - dataVersionId: mockDataVersion.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.data.setDataversionAsCurrent).toEqual(null); - - /* cleanup: reverse setting dataversion */ - await mongoClient.collection(config.database.collections.studies_collection) - .updateOne({ id: createdStudy.id }, { $set: { dataVersions: [mockDataVersion], currentDataVersion: 0 } }); - }); - - test('Set a previous study dataversion as current (user with study "manage project" privilege)', async () => { - /* setup: add an extra dataversion */ - await db.collections!.studies_collection.updateOne({ id: createdStudy.id }, { $push: { dataVersions: newMockDataVersion }, $inc: { currentDataVersion: 1 } }); - - const res = await authorisedUserStudyManageProject.post('/graphql').send({ - query: print(SET_DATAVERSION_AS_CURRENT), - variables: { - studyId: createdStudy.id, - dataVersionId: mockDataVersion.id - } - }); - expect(res.status).toBe(200); - expect(res.body.data.setDataversionAsCurrent.currentDataVersion).toBe(0); - - /* cleanup: reverse setting dataversion */ - await mongoClient.collection(config.database.collections.studies_collection) - .updateOne({ id: createdStudy.id }, { $set: { dataVersions: [mockDataVersion], currentDataVersion: 0 } }); - }); - - test('Set a previous study dataversion as current (user with study "manage data" privilege) (should fail)', async () => { - /* setup: create a new user */ - const userDataCurator: IUser = { - id: uuid(), - username: 'datacurator', - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: 'user@ic.ac.uk', - firstname: 'FDataCurator', - lastname: 'LDataCurator', - organisation: 'organisation_system', - type: userTypes.STANDARD, - description: 'just a data curator', - resetPasswordRequests: [], - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - deleted: null, - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - await db.collections!.users_collection.insertOne(userDataCurator); - - /* setup: create a new role with data management */ - const roleDataCurator: any = { - id: uuid(), - studyId: createdStudy.id, - name: 'Data Manager', - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: true, - uploaders: ['^.*$'], - operations: [atomicOperation.READ, atomicOperation.WRITE], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - users: [userDataCurator.id], - createdBy: adminId, - deleted: null - }; - await db.collections!.roles_collection.insertOne(roleDataCurator); - - /* setup: connect user */ - const dataCurator = request.agent(app); - await connectAgent(dataCurator, userDataCurator.username, 'admin', userDataCurator.otpSecret); - - /* setup: add an extra dataversion */ - await db.collections!.studies_collection.updateOne({ id: createdStudy.id }, { $push: { dataVersions: newMockDataVersion }, $inc: { currentDataVersion: 1 } }); - - /* test */ - const res = await dataCurator.post('/graphql').send({ - query: print(SET_DATAVERSION_AS_CURRENT), - variables: { - studyId: createdStudy.id, - dataVersionId: mockDataVersion.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - - /* cleanup: reverse setting dataversion */ - await mongoClient.collection(config.database.collections.studies_collection) - .updateOne({ id: createdStudy.id }, { $set: { dataVersions: [mockDataVersion], currentDataVersion: 0 } }); - - /* cleanup: delete user and role */ - await db.collections!.users_collection.deleteOne({ id: userDataCurator.id }); - await db.collections!.roles_collection.deleteOne({ id: roleDataCurator.id }); - }); - - test('Create New fields (admin)', async () => { - await db.collections!.field_dictionary_collection.deleteMany({ dataVersion: null }); - const res = await admin.post('/graphql').send({ - query: print(CREATE_NEW_FIELD), - variables: { - studyId: createdStudy.id, - fieldInput: [ - { - fieldId: '8', - fieldName: 'newField8', - tableName: 'test', - dataType: 'int', - comments: 'test', - possibleValues: [ - { code: '1', description: 'NOW' }, - { code: '2', description: 'OLD' } - ] - }, - { - fieldId: '9', - fieldName: 'newField9', - tableName: 'test', - dataType: 'cat', - comments: 'test', - possibleValues: [ - { code: '1', description: 'TRUE' }, - { code: '2', description: 'FALSE' } - ] - } - ] - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.createNewField).toEqual([ - { successful: true, code: null, id: null, description: 'Field 8-newField8 is created successfully.' }, - { successful: true, code: null, id: null, description: 'Field 9-newField9 is created successfully.' } - ]); - const fieldsInDb = await db.collections!.field_dictionary_collection.find({ studyId: createdStudy.id, dataVersion: null }).toArray(); - expect(fieldsInDb).toHaveLength(2); - }); - - test('Create New field with unsupported characters (admin)', async () => { - const res = await admin.post('/graphql').send({ - query: print(CREATE_NEW_FIELD), - variables: { - studyId: createdStudy.id, - fieldInput: [ - { - fieldId: '8.2', - fieldName: 'newField8', - tableName: 'test', - dataType: 'int', - comments: 'test', - possibleValues: [ - { code: '1', description: 'NOW' }, - { code: '2', description: 'OLD' } - ] - } - ] - } - }); - expect(res.status).toBe(200); - expect(res.body.data.createNewField[0]).toEqual({ - id: null, - successful: false, - code: 'CLIENT_MALFORMED_INPUT', - description: 'Field 8.2-newField8: ["FieldId should contain letters, numbers and _ only."]' - }); - }); - - test('Create New fields (user, should fail)', async () => { - const res = await user.post('/graphql').send({ - query: print(CREATE_NEW_FIELD), - variables: { - studyId: createdStudy.id, - fieldInput: [ - { - fieldId: '8', - fieldName: 'newField8', - tableName: 'test', - dataType: 'int', - comments: 'test', - possibleValues: [ - { code: '1', description: 'NOW' }, - { code: '2', description: 'OLD' } - ] - }, - { - fieldId: '9', - fieldName: 'newField9', - tableName: 'test', - dataType: 'cat', - comments: 'test', - possibleValues: [ - { code: '1', description: 'TRUE' }, - { code: '2', description: 'FALSE' } - ] - } - ] - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - }); - - test('Delete an unversioned field (admin)', async () => { - await admin.post('/graphql').send({ - query: print(CREATE_NEW_FIELD), - variables: { - studyId: createdStudy.id, - fieldInput: [ - { - fieldId: '8', - fieldName: 'newField8', - tableName: 'test', - dataType: 'int', - comments: 'test', - possibleValues: [ - { code: '1', description: 'NOW' }, - { code: '2', description: 'OLD' } - ] - }, - { - fieldId: '9', - fieldName: 'newField9', - tableName: 'test', - dataType: 'cat', - comments: 'test', - possibleValues: [ - { code: '1', description: 'TRUE' }, - { code: '2', description: 'FALSE' } - ] - } - ] - } - }); - const res = await admin.post('/graphql').send({ - query: print(DELETE_FIELD), - variables: { - studyId: createdStudy.id, - fieldId: '8' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.deleteField.fieldId).toBe('8'); - const fieldsInDb = await db.collections!.field_dictionary_collection.find({ studyId: createdStudy.id, dateDeleted: { $ne: null } }).toArray(); - expect(fieldsInDb).toHaveLength(1); - expect(fieldsInDb[0].fieldId).toBe('8'); - // clear database - await db.collections!.field_dictionary_collection.deleteMany({ studyId: createdStudy.id, fieldId: { $in: ['8', '9'] } }); - }); - - test('Delete a versioned field (admin)', async () => { - await admin.post('/graphql').send({ - query: print(CREATE_NEW_FIELD), - variables: { - studyId: createdStudy.id, - fieldInput: [ - { - fieldId: '8', - fieldName: 'newField8', - tableName: 'test', - dataType: 'int', - comments: 'test', - possibleValues: [ - { code: '1', description: 'NOW' }, - { code: '2', description: 'OLD' } - ] - }, - { - fieldId: '9', - fieldName: 'newField9', - tableName: 'test', - dataType: 'cat', - comments: 'test', - possibleValues: [ - { code: '1', description: 'TRUE' }, - { code: '2', description: 'FALSE' } - ] - } - ] - } - }); - await admin.post('/graphql').send({ - query: print(CREATE_NEW_DATA_VERSION), - variables: { studyId: createdStudy.id, dataVersion: '1', tag: 'testTag' } - }); - const res = await admin.post('/graphql').send({ - query: print(DELETE_FIELD), - variables: { - studyId: createdStudy.id, - fieldId: '8' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.deleteField.fieldId).toBe('8'); - const fieldsInDb = await db.collections!.field_dictionary_collection.find({ studyId: createdStudy.id, fieldId: '8' }).toArray(); - expect(fieldsInDb).toHaveLength(2); - expect(fieldsInDb[0].fieldId).toBe('8'); - expect(fieldsInDb[1].fieldId).toBe('8'); - expect(fieldsInDb[1].dateDeleted).not.toBe(null); - // clear database - await db.collections!.field_dictionary_collection.deleteMany({ studyId: createdStudy.id, fieldId: { $in: ['8', '9'] } }); - }); - }); - - describe('UPLOAD/DELETE DATA RECORDS DIRECTLY VIA API', () => { - let createdStudy: { id: any; name: any; }; - let createdProject: { id: any; name: any; studyId: any; }; - let createdRole_study_accessData; - let createdRole_project; - let createdUserAuthorisedProject; // profile - let createdUserNoAuthorisedProfile; - let createdUserAuthorisedProfile; - let authorisedUser: request.SuperTest; - let authorisedProjectUser: request.SuperTest; - let unauthorisedUser: request.SuperTest; - let mockFields: any[]; - let mockDataVersion: IStudyDataVersion; - const fieldTreeId = uuid(); - const oneRecord = [{ - fieldId: '31', - value: '10', - subjectId: 'I7N3G6G', - visitId: '1' - }]; - const multipleRecords = [ - { - fieldId: '31', - value: '10', - subjectId: 'I7N3G6G', - visitId: '1' - }, - { - fieldId: '32', - value: 'AAA', - subjectId: 'I7N3G6G', - visitId: '1' - }, - { - fieldId: '31', - value: '102', - subjectId: 'I7N3G6G', - visitId: '2' - }, - { - fieldId: '32', - value: 'AAAA', - subjectId: 'I7N3G6G', - visitId: '2' - }, - { - fieldId: '31', - value: '11', - subjectId: 'GR6R4AR', - visitId: '2' - }, - { - fieldId: '32', - value: 'BBB', - subjectId: 'GR6R4AR', - visitId: '2' - } - ]; - - beforeAll(async () => { - /*** setup: create a setup study ***/ - /* 1. create study */ - { - const studyName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(CREATE_STUDY), - variables: { name: studyName, description: 'test description', type: studyType.SENSOR } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - createdStudy = await mongoClient.collection(config.database.collections.studies_collection).findOne({ name: studyName }); - expect(res.body.data.createStudy).toEqual({ - id: createdStudy.id, - name: studyName, - description: 'test description', - type: studyType.SENSOR - }); - } - - /* 2. create a role for study (it will have 'access study data' privilege - equivalent to PI */ - { - const roleName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(ADD_NEW_ROLE), - variables: { - roleName, - studyId: createdStudy.id, - projectId: null - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - - createdRole_study_accessData = await mongoClient.collection(config.database.collections.roles_collection).findOne({ name: roleName }); - expect(createdRole_study_accessData).toEqual({ - _id: createdRole_study_accessData._id, - id: createdRole_study_accessData.id, - projectId: null, - studyId: createdStudy.id, - name: roleName, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - createdBy: adminId, - users: [], - deleted: null, - metadata: {}, - description: '' - }); - expect(res.body.data.addRole).toEqual({ - id: createdRole_study_accessData.id, - name: roleName, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - studyId: createdStudy.id, - projectId: null, - users: [] - }); - } - - /* 3. create an general study user that cannot manage projects (no role yet) */ - { - const username = uuid(); - const newUser: IUser = { - username: username, - type: userTypes.STANDARD, - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}'@user.io'`, - resetPasswordRequests: [], - description: 'I am an authorised study user managing project.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - deleted: null, - id: `AuthorisedStudyUserManageProject_${username}`, - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - createdUserNoAuthorisedProfile = await mongoClient.collection(config.database.collections.users_collection).findOne({ username }); - } - - /* 3. create an authorised study user that can manage projects (no role yet) */ - { - const username = uuid(); - const newUser: IUser = { - username: username, - type: userTypes.STANDARD, - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}'@user.io'`, - resetPasswordRequests: [], - description: 'I am an authorised study user managing project.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - metadata: { - wp: 'wp5.1' - }, - deleted: null, - id: `AuthorisedStudyUserManageProject_${username}`, - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - createdUserAuthorisedProfile = await mongoClient.collection(config.database.collections.users_collection).findOne({ username }); - } - - /* 4. add authorised user to role */ - { - const res = await admin.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: createdRole_study_accessData.id, - userChanges: { - add: [createdUserAuthorisedProfile.id], - remove: [] - }, - permissionChanges: { - data: { - fieldIds: ['^.*$'], - hasVersioned: true, - uploaders: ['^.*$'], - operations: [atomicOperation.READ, atomicOperation.WRITE], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ, atomicOperation.WRITE] - } - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: createdRole_study_accessData.id, - name: createdRole_study_accessData.name, - studyId: createdStudy.id, - projectId: null, - description: '', - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: true, - uploaders: ['^.*$'], - operations: [atomicOperation.READ, atomicOperation.WRITE], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ, atomicOperation.WRITE] - } - }, - users: [{ - id: createdUserAuthorisedProfile.id, - organisation: 'organisation_system', - firstname: createdUserAuthorisedProfile.firstname, - lastname: createdUserAuthorisedProfile.lastname - }] - }); - const resUser = await admin.post('/graphql').send({ - query: print(GET_USERS), - variables: { - fetchDetailsAdminOnly: false, - userId: createdUserAuthorisedProfile.id, - fetchAccessPrivileges: true - } - }); - expect(resUser.status).toBe(200); - expect(resUser.body.errors).toBeUndefined(); - expect(resUser.body.data.getUsers).toHaveLength(1); - expect(resUser.body.data.getUsers[0]).toEqual({ - id: createdUserAuthorisedProfile.id, - type: userTypes.STANDARD, - firstname: `${createdUserAuthorisedProfile.username}_firstname`, - lastname: `${createdUserAuthorisedProfile.username}_lastname`, - organisation: 'organisation_system', - access: { - id: `user_access_obj_user_id_${createdUserAuthorisedProfile.id}`, - projects: [], - studies: [{ - id: createdStudy.id, - name: createdStudy.name - }] - } - }); - } - - /* 5. Insert field for data uploading later */ - mockDataVersion = { - id: 'mockDataVersionId', - contentId: 'mockContentId', - version: '0.0.1', - updateDate: '5000000' - }; - mockFields = [ - { - id: 'mockfield1', - studyId: createdStudy.id, - fieldId: '31', - fieldName: 'Age', - dataType: enumValueType.INTEGER, - possibleValues: [], - unit: 'person', - comments: 'mockComments1', - dateAdded: 100000000, - dateDeleted: null, - dataVersion: 'mockDataVersionId', - metadata: {} - }, - { - id: 'mockfield2', - studyId: createdStudy.id, - fieldId: '32', - fieldName: 'Sex', - dataType: enumValueType.STRING, - possibleValues: [], - unit: 'person', - comments: 'mockComments2', - dateAdded: 100000000, - dateDeleted: null, - dataVersion: 'mockDataVersionId', - metadata: {} - }, - { - id: 'mockfield3', - studyId: createdStudy.id, - fieldId: '33', - fieldName: 'DeviceTest', - dataType: enumValueType.FILE, - possibleValues: [], - unit: 'person', - comments: 'mockComments3', - dateAdded: 100000000, - dateDeleted: null, - dataVersion: 'mockDataVersionId', - metadata: {} - } - ]; - await db.collections!.field_dictionary_collection.insertMany(mockFields); - await db.collections!.studies_collection.updateOne({ id: createdStudy.id }, { - $push: { dataVersions: mockDataVersion }, - $inc: { currentDataVersion: 1 }, - $set: { - ontologyTrees: [{ - id: 'testOntology_id', - name: 'testOntology', - routes: [ - { - path: [ - 'MO', - 'MOCK' - ], - name: 'mockfield1', - field: [ - '$31' - ], - visitRange: [], - id: '036b7772-f239-4fef-b7f8-c3db883f51e3' - }, - { - path: [ - 'MO', - 'MOCK' - ], - name: 'mockfield2', - field: [ - '$32' - ], - visitRange: [], - id: 'f577023f-de54-446a-9bbe-1c346823e6bf' - } - ] - }] - } - }); - - /* 2. create projects for the study */ - { - const projectName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(CREATE_PROJECT), - variables: { - studyId: createdStudy.id, - projectName: projectName, - dataVersion: mockDataVersion.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - createdProject = await mongoClient.collection(config.database.collections.projects_collection).findOne({ name: projectName }); - expect(res.body.data.createProject).toEqual({ - id: createdProject.id, - studyId: createdStudy.id, - name: projectName - }); - } - - /* 5. create an authorised project user (no role yet) */ - { - const username = uuid(); - const newUser: IUser = { - username: username, - type: userTypes.STANDARD, - firstname: `${username}_firstname`, - lastname: `${username}_lastname`, - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: `${username}@user.io`, - resetPasswordRequests: [], - description: 'I am an authorised project user.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - metadata: { - wp: 'wp5.2' - }, - deleted: null, - id: `AuthorisedProjectUser_${username}`, - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - createdUserAuthorisedProject = await mongoClient.collection(config.database.collections.users_collection).findOne({ username }); - } - - /* 4. create roles for project */ - { - const roleName = uuid(); - const res = await admin.post('/graphql').send({ - query: print(ADD_NEW_ROLE), - variables: { - roleName, - studyId: createdStudy.id, - projectId: createdProject.id - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - createdRole_project = await mongoClient.collection(config.database.collections.roles_collection).findOne({ name: roleName }); - expect(createdRole_project).toEqual({ - _id: createdRole_project._id, - id: createdRole_project.id, - projectId: createdProject.id, - studyId: createdStudy.id, - name: roleName, - description: '', - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - createdBy: adminId, - users: [], - deleted: null, - metadata: {} - }); - expect(res.body.data.addRole).toEqual({ - id: createdRole_project.id, - name: roleName, - permissions: { - data: { - fieldIds: [], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [], - subjectIds: [], - visitIds: [] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - studyId: createdStudy.id, - projectId: createdProject.id, - users: [] - }); - } - - /* 6. add authorised user to role */ - { - const res = await admin.post('/graphql').send({ - query: print(EDIT_ROLE), - variables: { - roleId: createdRole_project.id, - userChanges: { - add: [createdUserAuthorisedProject.id], - remove: [] - }, - permissionChanges: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editRole).toEqual({ - id: createdRole_project.id, - name: createdRole_project.name, - studyId: createdStudy.id, - projectId: createdProject.id, - description: '', - permissions: { - data: { - fieldIds: ['^.*$'], - hasVersioned: false, - uploaders: ['^.*$'], - operations: [atomicOperation.READ], - subjectIds: ['^.*$'], - visitIds: ['^.*$'] - }, - manage: { - [IPermissionManagementOptions.own]: [atomicOperation.READ], - [IPermissionManagementOptions.role]: [], - [IPermissionManagementOptions.job]: [], - [IPermissionManagementOptions.query]: [], - [IPermissionManagementOptions.ontologyTrees]: [atomicOperation.READ] - } - }, - users: [{ - id: createdUserAuthorisedProject.id, - organisation: 'organisation_system', - firstname: createdUserAuthorisedProject.firstname, - lastname: createdUserAuthorisedProject.lastname - }] - }); - const resUser = await admin.post('/graphql').send({ - query: print(GET_USERS), - variables: { - fetchDetailsAdminOnly: false, - userId: createdUserAuthorisedProject.id, - fetchAccessPrivileges: true - } - }); - expect(resUser.status).toBe(200); - expect(resUser.body.errors).toBeUndefined(); - expect(resUser.body.data.getUsers).toHaveLength(1); - expect(resUser.body.data.getUsers[0]).toEqual({ - id: createdUserAuthorisedProject.id, - type: userTypes.STANDARD, - firstname: `${createdUserAuthorisedProject.username}_firstname`, - lastname: `${createdUserAuthorisedProject.username}_lastname`, - organisation: 'organisation_system', - access: { - id: `user_access_obj_user_id_${createdUserAuthorisedProject.id}`, - projects: [{ - id: createdProject.id, - name: createdProject.name, - studyId: createdStudy.id - }], - studies: [] - } - }); - } - - /* 7. Create an ontologytree */ - { - await db.collections!.studies_collection.findOneAndUpdate({ id: createdStudy.id }, { - $set: { - ontologyTrees: [{ - id: uuid(), - name: 'testOntologyTree', - routes: [ - { path: ['DM'], name: 'AGE', field: ['$31'], visitRange: [], id: uuid() }, - { path: ['DM'], name: 'SEX', field: ['$32'], visitRange: [], id: uuid() } - ], - dataVersion: mockDataVersion.id, - deleted: null - }] - } - }); - } - const study_role_id = `metadata.${'role:'.concat(createdRole_study_accessData?.id)}`; - const project_role_id = `metadata.${'role:'.concat(createdRole_project?.id)}`; - await db.collections!.field_dictionary_collection.updateMany({ studyId: createdStudy.id }, { - $set: { - [study_role_id]: true, - [project_role_id]: true - } - }); - - /* Connect users */ - authorisedUser = request.agent(app); - await connectAgent(authorisedUser, createdUserAuthorisedProfile.username, 'admin', createdUserAuthorisedProfile.otpSecret); - unauthorisedUser = request.agent(app); - await connectAgent(unauthorisedUser, createdUserNoAuthorisedProfile.username, 'admin', createdUserNoAuthorisedProfile.otpSecret); - authorisedProjectUser = request.agent(app); - await connectAgent(authorisedProjectUser, createdUserAuthorisedProject.username, 'admin', createdUserAuthorisedProject.otpSecret); - - }); - - afterAll(async () => { - { - const res = await admin.post('/graphql').send({ - query: print(DELETE_STUDY), - variables: { studyId: createdStudy.id } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.deleteStudy).toEqual({ - id: createdStudy.id, - successful: true - }); - } - - { - const res = await admin.post('/graphql').send({ query: print(WHO_AM_I) }); - expect(res.body.data.whoAmI).toEqual({ - username: 'admin', - type: userTypes.ADMIN, - firstname: 'Fadmin', - lastname: 'Ladmin', - organisation: 'organisation_system', - email: 'admin@example.com', - description: 'I am an admin user.', - id: adminId, - access: { - id: `user_access_obj_user_id_${adminId}`, - projects: [], - studies: [] - }, - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - createdAt: 1591134065000, - expiredAt: 1991134065000, - metadata: null - }); - - // study data is NOT deleted for audit purposes - unless explicitly requested separately - const roles = await db.collections!.roles_collection.find({ studyId: createdStudy.id, deleted: null }).toArray(); - const projects = await db.collections!.projects_collection.find({ studyId: createdStudy.id, deleted: null }).toArray(); - const study = await db.collections!.studies_collection.findOne({ id: createdStudy.id, deleted: null }); - expect(roles).toEqual([]); - expect(projects).toEqual([]); - expect(study).toBe(null); - } - - /* cannot get study from api anymore */ - { - const res = await admin.post('/graphql').send({ - query: print(GET_STUDY), - variables: { studyId: createdStudy.id } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - expect(res.body.data.getStudy).toBe(null); - } - - await db.collections!.field_dictionary_collection.deleteMany({ studyId: createdStudy.id }); - }); - - afterEach(async () => { - await db.collections!.data_collection.deleteMany({}); - await db.collections!.studies_collection.findOneAndUpdate({ id: createdStudy.id }, { - $set: { - dataVersions: [{ - id: 'mockDataVersionId', - contentId: 'mockContentId', - version: '0.0.1', - updateDate: '5000000' - }], currentDataVersion: 0 - } - }); - await db.collections!.field_dictionary_collection.deleteMany({ studyId: createdStudy.id, fieldId: { $nin: ['31', '32'] }, dataVersion: null }); - }); - - test('Upload a data record to study (authorised user)', async () => { - const recordList = [ - { - fieldId: '31', - value: '10', - subjectId: 'I7N3G6G', - visitId: '1' - }, - { - fieldId: '32', - value: 'FAKE1', - subjectId: 'I7N3G6G', - visitId: '1' - }, - { - fieldId: '31', - value: '11', - subjectId: 'GR6R4AR', - visitId: '1' - }, - // non-existing field - { - fieldId: '34', - value: '10', - subjectId: 'I7N3G6G', - visitId: '1' - }, - // illegal value - { - fieldId: '31', - value: 'wrong', - subjectId: 'I7N3G6G', - visitId: '2' - }, - // illegal subject id - { - fieldId: '31', - value: '10', - subjectId: 'I777770', - visitId: '1' - } - ]; - const res = await authorisedUser.post('/graphql').send({ - query: print(UPLOAD_DATA_IN_ARRAY), - variables: { studyId: createdStudy.id, data: recordList } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.uploadDataInArray).toEqual([ - { code: null, description: 'I7N3G6G-1-31', id: null, successful: true }, - { code: null, description: 'I7N3G6G-1-32', id: null, successful: true }, - { code: null, description: 'GR6R4AR-1-31', id: null, successful: true }, - { code: 'CLIENT_ACTION_ON_NON_EXISTENT_ENTRY', description: 'Field 34: Field Not found', id: null, successful: false }, - { code: 'CLIENT_MALFORMED_INPUT', description: 'Field 31: Cannot parse as integer.', id: null, successful: false }, - { code: 'CLIENT_MALFORMED_INPUT', description: 'Subject ID I777770 is illegal.', id: null, successful: false } - ]); - - const dataInDb = await db.collections!.data_collection.find({ deleted: null }).toArray(); - expect(dataInDb).toHaveLength(3); - }); - - test('Upload a data record to study (unauthorised user)', async () => { - const res = await unauthorisedUser.post('/graphql').send({ - query: print(UPLOAD_DATA_IN_ARRAY), - variables: { studyId: createdStudy.id, data: oneRecord } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - }); - - test('Upload a data record with incorrect studyId', async () => { - const res = await authorisedUser.post('/graphql').send({ - query: print(UPLOAD_DATA_IN_ARRAY), - variables: { studyId: 'fakeStudyId', fieldTreeId: fieldTreeId, data: oneRecord } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe('Study does not exist.'); - }); - - test('Upload a file with the data API', async () => { - const res = await authorisedUser.post('/graphql') - .field('operations', JSON.stringify({ - query: print(UPLOAD_DATA_IN_ARRAY), - variables: { - studyId: createdStudy.id, - data: [ - { - fieldId: '33', - subjectId: 'I7N3G6G', - visitId: '1', - file: null, - metadata: { - deviceId: 'MMM7N3G6G', - startDate: '1590966000000', - endDate: '1593730800000', - postFix: 'txt' - } - } - ] - } - })) - .field('map', JSON.stringify({ 1: ['variables.data.0.file'] })) - .attach('1', path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt')); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - // check both data collection and file collection - const fileFirst = await db.collections!.files_collection.findOne({ studyId: createdStudy.id, deleted: null }); - const dataFirst = await db.collections!.data_collection.findOne({ m_studyId: createdStudy.id, m_visitId: '1', m_fieldId: '33' }); - expect(dataFirst?.metadata?.add[0]).toBe(fileFirst.id); - - // upload again and check whether the old file has been deleted - const resSecond = await authorisedUser.post('/graphql') - .field('operations', JSON.stringify({ - query: print(UPLOAD_DATA_IN_ARRAY), - variables: { - studyId: createdStudy.id, - data: [ - { - fieldId: '33', - subjectId: 'I7N3G6G', - visitId: '1', - file: null, - metadata: { - deviceId: 'MMM7N3G6G', - startDate: '1590966000000', - endDate: '1593730800000', - postFix: 'test' - } - } - ] - } - })) - .field('map', JSON.stringify({ 1: ['variables.data.0.file'] })) - .attach('1', path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt')); - expect(resSecond.status).toBe(200); - expect(resSecond.body.errors).toBeUndefined(); - const fileSecond = await db.collections!.files_collection.find({ studyId: createdStudy.id, deleted: null }).toArray(); - const dataSecond = await db.collections!.data_collection.findOne({ m_studyId: createdStudy.id, m_visitId: '1', m_fieldId: '33' }); - expect(fileSecond).toHaveLength(2); - expect(dataSecond?.metadata?.add).toEqual([fileSecond[0].id, fileSecond[1].id]); - }); - - test('Create New data version with data only (user with study privilege)', async () => { - const res = await admin.post('/graphql').send({ - query: print(UPLOAD_DATA_IN_ARRAY), - variables: { studyId: createdStudy.id, data: multipleRecords } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - const createRes = await admin.post('/graphql').send({ - query: print(CREATE_NEW_DATA_VERSION), - variables: { studyId: createdStudy.id, dataVersion: '1', tag: 'testTag' } - }); - expect(createRes.status).toBe(200); - expect(createRes.body.errors).toBeUndefined(); - expect(createRes.body.data.createNewDataVersion.version).toBe('1'); - expect(createRes.body.data.createNewDataVersion.tag).toBe('testTag'); - const studyInDb = await db.collections!.studies_collection.findOne({ id: createdStudy.id }); - expect(studyInDb.dataVersions).toHaveLength(2); - expect(studyInDb.dataVersions[1].version).toBe('1'); - expect(studyInDb.dataVersions[1].tag).toBe('testTag'); - const dataInDb = await db.collections!.data_collection.find({ m_studyId: createdStudy.id, m_versionId: createRes.body.data.createNewDataVersion.id }).toArray(); - expect(dataInDb).toHaveLength(6); - }); - - test('Create New data version with field only (user with study privilege)', async () => { - const res = await admin.post('/graphql').send({ - query: print(CREATE_NEW_FIELD), - variables: { - studyId: createdStudy.id, fieldInput: { - fieldId: '34', - fieldName: 'Height', - dataType: enumValueType.DECIMAL, - unit: 'cm' - } - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - const createRes = await admin.post('/graphql').send({ - query: print(CREATE_NEW_DATA_VERSION), - variables: { studyId: createdStudy.id, dataVersion: '1', tag: 'testTag' } - }); - expect(createRes.status).toBe(200); - expect(createRes.body.errors).toBeUndefined(); - expect(createRes.body.data.createNewDataVersion.version).toBe('1'); - expect(createRes.body.data.createNewDataVersion.tag).toBe('testTag'); - const studyInDb = await db.collections!.studies_collection.findOne({ id: createdStudy.id }); - expect(studyInDb.dataVersions).toHaveLength(2); - expect(studyInDb.dataVersions[1].version).toBe('1'); - expect(studyInDb.dataVersions[1].tag).toBe('testTag'); - const fieldIndb = await db.collections!.field_dictionary_collection.find({ studyId: createdStudy.id, dataVersion: { $in: [createRes.body.data.createNewDataVersion.id, 'mockDataVersionId'] } }).toArray(); - expect(fieldIndb).toHaveLength(4); - }); - - test('Create New data version with field and data (user with study privilege)', async () => { - await admin.post('/graphql').send({ - query: print(CREATE_NEW_FIELD), - variables: { - studyId: createdStudy.id, fieldInput: { - fieldId: '34', - fieldName: 'Height', - dataType: enumValueType.DECIMAL, - unit: 'cm' - } - } - }); - await admin.post('/graphql').send({ - query: print(UPLOAD_DATA_IN_ARRAY), - variables: { - studyId: createdStudy.id, - data: [{ - fieldId: '34', - value: '163.4', - subjectId: 'I7N3G6G', - visitId: '1' - }, ...multipleRecords] - } - }); - const createRes = await admin.post('/graphql').send({ - query: print(CREATE_NEW_DATA_VERSION), - variables: { studyId: createdStudy.id, dataVersion: '1', tag: 'testTag' } - }); - expect(createRes.status).toBe(200); - expect(createRes.body.errors).toBeUndefined(); - expect(createRes.body.data.createNewDataVersion.version).toBe('1'); - expect(createRes.body.data.createNewDataVersion.tag).toBe('testTag'); - const studyInDb = await db.collections!.studies_collection.findOne({ id: createdStudy.id }); - expect(studyInDb.dataVersions).toHaveLength(2); - expect(studyInDb.dataVersions[1].version).toBe('1'); - expect(studyInDb.dataVersions[1].tag).toBe('testTag'); - const dataInDb = await db.collections!.data_collection.find({ m_studyId: createdStudy.id, m_versionId: createRes.body.data.createNewDataVersion.id }).toArray(); - expect(dataInDb).toHaveLength(7); - const fieldsInDb = await db.collections!.field_dictionary_collection.find({ studyId: createdStudy.id, dataVersion: { $in: [createRes.body.data.createNewDataVersion.id, 'mockDataVersionId'] } }).toArray(); - expect(fieldsInDb).toHaveLength(4); - }); - - test('Create New data version (authorised user) should fail', async () => { - const res = await authorisedUser.post('/graphql').send({ - query: print(UPLOAD_DATA_IN_ARRAY), - variables: { studyId: createdStudy.id, data: multipleRecords } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - - const createRes = await authorisedUser.post('/graphql').send({ - query: print(CREATE_NEW_DATA_VERSION), - variables: { studyId: createdStudy.id, dataVersion: '1', tag: 'testTag', baseVersions: [], subjectIds: [], visitIds: [], withUnversionedData: true } - }); - expect(createRes.status).toBe(200); - expect(createRes.body.errors).toHaveLength(1); - expect(createRes.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - }); - - test('Delete data records: (unauthorised user) should fail', async () => { - const res = await authorisedUser.post('/graphql').send({ - query: print(UPLOAD_DATA_IN_ARRAY), - variables: { studyId: createdStudy.id, data: multipleRecords } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - - const deleteRes = await user.post('/graphql').send({ - query: print(DELETE_DATA_RECORDS), - variables: { studyId: createdStudy.id, subjectId: 'I7N3G6G' } - }); - expect(deleteRes.status).toBe(200); - expect(deleteRes.body.errors).toHaveLength(1); - expect(deleteRes.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - }); - - test('Delete data records: subjectId (user with study privilege)', async () => { - const res = await authorisedUser.post('/graphql').send({ - query: print(UPLOAD_DATA_IN_ARRAY), - variables: { studyId: createdStudy.id, data: multipleRecords } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - const deleteRes = await authorisedUser.post('/graphql').send({ - query: print(DELETE_DATA_RECORDS), - variables: { studyId: createdStudy.id, subjectIds: ['I7N3G6G'] } - }); - expect(deleteRes.status).toBe(200); - expect(deleteRes.body.errors).toBeUndefined(); - expect(deleteRes.body.data.deleteDataRecords).toEqual([ - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-1:fieldId-31 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-1:fieldId-32 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-1:fieldId-33 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-1:fieldId-34 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-2:fieldId-31 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-2:fieldId-32 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-2:fieldId-33 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-2:fieldId-34 is deleted.' }]); - }); - - test('Delete data records: visitId (admin)', async () => { - const res = await authorisedUser.post('/graphql').send({ - query: print(UPLOAD_DATA_IN_ARRAY), - variables: { studyId: createdStudy.id, data: multipleRecords } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - - const deleteRes = await authorisedUser.post('/graphql').send({ - query: print(DELETE_DATA_RECORDS), - variables: { studyId: createdStudy.id, visitIds: ['2'] } - }); - expect(deleteRes.status).toBe(200); - expect(deleteRes.body.errors).toBeUndefined(); - expect(deleteRes.body.data.deleteDataRecords).toEqual([ - { successful: true, id: null, code: null, description: 'SubjectId-GR6R4AR:visitId-2:fieldId-31 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-GR6R4AR:visitId-2:fieldId-32 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-GR6R4AR:visitId-2:fieldId-33 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-GR6R4AR:visitId-2:fieldId-34 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-2:fieldId-31 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-2:fieldId-32 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-2:fieldId-33 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-2:fieldId-34 is deleted.' } - ]); - }); - - test('Delete data records: studyId (admin)', async () => { - const res = await authorisedUser.post('/graphql').send({ - query: print(UPLOAD_DATA_IN_ARRAY), - variables: { studyId: createdStudy.id, data: multipleRecords } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.uploadDataInArray).toEqual([ - { code: null, description: 'I7N3G6G-1-31', id: null, successful: true }, - { code: null, description: 'I7N3G6G-1-32', id: null, successful: true }, - { code: null, description: 'I7N3G6G-2-31', id: null, successful: true }, - { code: null, description: 'I7N3G6G-2-32', id: null, successful: true }, - { code: null, description: 'GR6R4AR-2-31', id: null, successful: true }, - { code: null, description: 'GR6R4AR-2-32', id: null, successful: true } - ]); - - const deleteRes = await admin.post('/graphql').send({ - query: print(DELETE_DATA_RECORDS), - variables: { studyId: createdStudy.id } - }); - expect(deleteRes.status).toBe(200); - expect(deleteRes.body.errors).toBeUndefined(); - expect(deleteRes.body.data.deleteDataRecords).toEqual([ - { successful: true, id: null, code: null, description: 'SubjectId-GR6R4AR:visitId-1:fieldId-31 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-GR6R4AR:visitId-1:fieldId-32 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-GR6R4AR:visitId-1:fieldId-33 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-GR6R4AR:visitId-1:fieldId-34 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-GR6R4AR:visitId-2:fieldId-31 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-GR6R4AR:visitId-2:fieldId-32 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-GR6R4AR:visitId-2:fieldId-33 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-GR6R4AR:visitId-2:fieldId-34 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-1:fieldId-31 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-1:fieldId-32 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-1:fieldId-33 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-1:fieldId-34 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-2:fieldId-31 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-2:fieldId-32 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-2:fieldId-33 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-2:fieldId-34 is deleted.' }]); - const dataInDb = await db.collections!.data_collection.find({ 31: null }).sort({ uploadedAt: -1 }).toArray(); - expect(dataInDb).toHaveLength(16); // 2 visits * 2 subjects * 2 fields * 2 (delete or not) = 16 records - }); - - test('Delete data records: records not exist', async () => { - const res = await authorisedUser.post('/graphql').send({ - query: print(UPLOAD_DATA_IN_ARRAY), - variables: { studyId: createdStudy.id, data: multipleRecords } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.uploadDataInArray).toEqual([ - { code: null, description: 'I7N3G6G-1-31', id: null, successful: true }, - { code: null, description: 'I7N3G6G-1-32', id: null, successful: true }, - { code: null, description: 'I7N3G6G-2-31', id: null, successful: true }, - { code: null, description: 'I7N3G6G-2-32', id: null, successful: true }, - { code: null, description: 'GR6R4AR-2-31', id: null, successful: true }, - { code: null, description: 'GR6R4AR-2-32', id: null, successful: true } - ]); - - const deleteRes = await admin.post('/graphql').send({ - query: print(DELETE_DATA_RECORDS), - variables: { studyId: createdStudy.id, subjectIds: ['I7N3G6G'], visitIds: ['1', '2'] } - }); - expect(deleteRes.status).toBe(200); - expect(deleteRes.body.errors).toBeUndefined(); - expect(deleteRes.body.data.deleteDataRecords).toEqual([ - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-1:fieldId-31 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-1:fieldId-32 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-1:fieldId-33 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-1:fieldId-34 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-2:fieldId-31 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-2:fieldId-32 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-2:fieldId-33 is deleted.' }, - { successful: true, id: null, code: null, description: 'SubjectId-I7N3G6G:visitId-2:fieldId-34 is deleted.' }]); - const dataInDb = await db.collections!.data_collection.find({ m_subjectId: 'I7N3G6G' }).sort({ uploadedAt: -1 }).toArray(); - expect(dataInDb).toHaveLength(8); // two data records, two deleted records - }); - - test('Get data records (user with study privilege)', async () => { - await authorisedUser.post('/graphql').send({ - query: print(UPLOAD_DATA_IN_ARRAY), - variables: { studyId: createdStudy.id, data: multipleRecords } - }); - await admin.post('/graphql').send({ - query: print(CREATE_NEW_DATA_VERSION), - variables: { studyId: createdStudy.id, dataVersion: '1', tag: 'testTag' } - }); - await authorisedUser.post('/graphql').send({ - query: print(UPLOAD_DATA_IN_ARRAY), - variables: { - studyId: createdStudy.id, data: [ - { - fieldId: '31', - value: '10', - subjectId: 'I7N3G6G', - visitId: '3' - } - ] - } - }); - const getRes = await authorisedUser.post('/graphql').send({ - query: print(GET_DATA_RECORDS), - variables: { - studyId: createdStudy.id, - queryString: { - data_requested: ['31', '32'], - format: 'raw', - cohort: [[]], - new_fields: [] - } - } - }); - expect(getRes.status).toBe(200); - expect(getRes.body.errors).toBeUndefined(); - expect(Object.keys(getRes.body.data.getDataRecords.data)).toHaveLength(2); - }); - - // test('Get data records (user with project privilege)', async () => { - // await authorisedUser.post('/graphql').send({ - // query: print(UPLOAD_DATA_IN_ARRAY), - // variables: { studyId: createdStudy.id, data: multipleRecords } - // }); - // await admin.post('/graphql').send({ - // query: print(CREATE_NEW_DATA_VERSION), - // variables: { studyId: createdStudy.id, dataVersion: '1', tag: 'testTag' } - // }); - // await authorisedUser.post('/graphql').send({ - // query: print(UPLOAD_DATA_IN_ARRAY), - // variables: { - // studyId: createdStudy.id, - // data: [ - // { - // fieldId: '31', - // value: '10', - // subjectId: 'I7N3G6G', - // visitId: '3' - // } - // ] - // } - // }); - // const getRes = await authorisedProjectUser.post('/graphql').send({ - // query: print(GET_DATA_RECORDS), - // variables: { - // studyId: createdStudy.id, - // projectId: createdProject.id, - // queryString: { - // data_requested: ['31', '32'], - // format: 'raw', - // cohort: [[]], - // new_fields: [] - // } - // } - // }); - // expect(getRes.status).toBe(200); - // expect(getRes.body.errors).toBeUndefined(); - // expect(Object.keys(getRes.body.data.getDataRecords.data)).toHaveLength(2); - // }); - - // test('Get data records with meta data filters', async () => { - // const study = await db.collections!.studies_collection.findOne({ id: createdStudy.id }); - // await db.collections!.field_dictionary_collection.updateMany({ fieldId: { $in: ['31', '32'] } }, { - // $set: { - // [`metadata.role:${createdRole_study_accessData.id}`]: true - // } - // }); - // await db.collections!.data_collection.insertMany([{ - // m_studyId: createdStudy.id, - // m_fieldId: '31', - // m_subjectId: 'I7N3G6G', - // m_versionId: study.dataVersions[study?.currentDataVersion].id, - // m_visitId: '1', - // id: '0001', - // metadata: { - // 'uploader:org': createdUserAuthorisedProfile.organisation, - // 'uploader:user': createdUserAuthorisedProfile.id, - // 'uploader:wp': 'wp5.1', - // [`role:${createdRole_study_accessData.id}`]: true - // }, - // uploadedAt: 10000000, - // value: '1' - // }, { - // m_studyId: createdStudy.id, - // m_fieldId: '32', - // m_subjectId: 'I7N3G6G', - // m_versionId: study.dataVersions[study?.currentDataVersion].id, - // m_visitId: '1', - // id: '0002', - // metadata: { - // 'uploader:org': createdUserAuthorisedProject.organisation, - // 'uploader:user': createdUserAuthorisedProject.id, - // 'uploader:wp': 'wp5.2', - // [`role:${createdRole_study_accessData.id}`]: true - // }, - // uploadedAt: 10000000, - // value: '2' - // }]); - // const resMetadataOrg = await authorisedUser.post('/graphql').send({ - // query: print(GET_DATA_RECORDS), - // variables: { - // studyId: createdStudy.id, - // queryString: { - // data_requested: ['31', '32'], - // format: 'raw', - // cohort: [[]], - // new_fields: [], - // metadata: [ - // [ - // { - // key: 'uploader:wp', - // op: '=', - // parameter: 'wp5.1' - // } - // ] - // ] - // } - // } - // }); - // expect(resMetadataOrg.status).toBe(200); - // expect(resMetadataOrg.body.errors).toBeUndefined(); - // expect(resMetadataOrg.body.data.getDataRecords.data).toEqual({ - // I7N3G6G: { 1: { 31: '1', m_subjectId: 'I7N3G6G', m_visitId: '1' } } - // }); - // await db.collections!.studies_collection.findOneAndUpdate({ id: createdStudy.id }, { $set: { ontologyTrees: [] } }); - // }); - - test('Create an ontology tree (authorised user)', async () => { - const res = await authorisedUser.post('/graphql').send({ - query: print(CREATE_ONTOLOGY_TREE), - variables: { - studyId: createdStudy.id, - ontologyTree: { - name: 'fakeTree', - routes: [ - { - path: ['DM', 'm_subjectId', 'm_visitId'], - name: 'AGE', - field: ['$100'], - visitRange: [] - }, - { - path: ['QS', 'MFI', 'm_subjectId', 'm_visitId'], - name: 'Q1', - field: ['$200'], - visitRange: [] - } - ] - } - } - }); - const study = await db.collections!.studies_collection.findOne({ id: createdStudy.id, deleted: null }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.createOntologyTree).toEqual({ - id: study.ontologyTrees[1].id, - name: 'fakeTree', - routes: [ - { - id: study.ontologyTrees[1].routes[0].id, - path: ['DM', 'm_subjectId', 'm_visitId'], - name: 'AGE', - field: ['$100'], - visitRange: [] - }, - { - id: study.ontologyTrees[1].routes[1].id, - path: ['QS', 'MFI', 'm_subjectId', 'm_visitId'], - name: 'Q1', - field: ['$200'], - visitRange: [] - } - ], - metadata: null - }); - // clear ontologyTrees - await db.collections!.studies_collection.findOneAndUpdate({ id: createdStudy.id, deleted: null }, { - $set: { - ontologyTrees: [] - } - }); - }); - - test('Create an ontology tree (unauthorised user), should fail', async () => { - const res = await user.post('/graphql').send({ - query: print(CREATE_ONTOLOGY_TREE), - variables: { - studyId: createdStudy.id, - ontologyTree: { - name: 'fakeTree', - routes: [ - { - path: ['DM', 'm_subjectId', 'm_visitId'], - name: 'AGE', - field: ['$100'], - visitRange: [] - }, - { - path: ['QS', 'MFI', 'm_subjectId', 'm_visitId'], - name: '', - field: ['$200'], - visitRange: [] - } - ] - } - } - }); - expect(res.status).toBe(200); - expect(res.body.data.createOntologyTree).toBe(null); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - }); - - test('Delete an ontology tree (authorised user)', async () => { - await authorisedUser.post('/graphql').send({ - query: print(CREATE_ONTOLOGY_TREE), - variables: { - studyId: createdStudy.id, - ontologyTree: { - name: 'fakeTree', - routes: [ - { - path: ['DM', 'm_subjectId', 'm_visitId'], - name: 'AGE', - field: ['$100'], - visitRange: [] - }, - { - path: ['QS', 'MFI', 'm_subjectId', 'm_visitId'], - name: '', - field: ['$200'], - visitRange: [] - } - ] - } - } - }); - await admin.post('/graphql').send({ - query: print(CREATE_NEW_DATA_VERSION), - variables: { studyId: createdStudy.id, dataVersion: '1', tag: 'testTag' } - }); - const study: IStudy = await db.collections!.studies_collection.findOne({ id: createdStudy.id, deleted: null }); - const res = await authorisedUser.post('/graphql').send({ - query: print(DELETE_ONTOLOGY_TREE), - variables: { - studyId: createdStudy.id, - treeName: study.ontologyTrees[0].name - } - }); - expect(res.status).toBe(200); - expect(res.body.data.deleteOntologyTree).toEqual({ - id: study.ontologyTrees[0].name, - successful: true - }); - const updatedStudy: IStudy = await db.collections!.studies_collection.findOne({ id: createdStudy.id, deleted: null }); - expect(updatedStudy.ontologyTrees.length).toBe(2); // both records - // clear study - await db.collections!.studies_collection.findOneAndUpdate({ id: createdStudy.id, deleted: null }, { - $set: { - dataVersions: [], - currentDataVersion: -1, - ontologyTrees: [] - } - }); - }); - - test('Delete an ontology tree (unauthorised user), should fail', async () => { - await authorisedUser.post('/graphql').send({ - query: print(CREATE_ONTOLOGY_TREE), - variables: { - studyId: createdStudy.id, - ontologyTree: { - name: 'fakeTree', - routes: [ - { - path: ['DM', 'm_subjectId', 'm_visitId'], - name: 'AGE', - field: ['$100'], - visitRange: [] - }, - { - path: ['QS', 'MFI', 'm_subjectId', 'm_visitId'], - name: '', - field: ['$200'], - visitRange: [] - } - ] - } - } - }); - const study: IStudy = await db.collections!.studies_collection.findOne({ id: createdStudy.id, deleted: null }); - const res = await user.post('/graphql').send({ - query: print(DELETE_ONTOLOGY_TREE), - variables: { - studyId: createdStudy.id, - treeName: study.ontologyTrees[0].name - } - }); - expect(res.status).toBe(200); - expect(res.body.data.deleteOntologyTree).toBe(null); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - await db.collections!.studies_collection.findOneAndUpdate({ studyId: createdStudy.id, deleted: null }, { - $set: { - ontologyTrees: [] - } - }); - }); - - test('Get an ontology tree with versioning (authorised user)', async () => { - await authorisedUser.post('/graphql').send({ - query: print(CREATE_ONTOLOGY_TREE), - variables: { - studyId: createdStudy.id, - ontologyTree: { - name: 'fakeTree', - routes: [ - { - path: ['DM', 'm_subjectId', 'm_visitId'], - name: 'AGE', - field: ['$100'], - visitRange: [] - }, - { - path: ['QS', 'MFI', 'm_subjectId', 'm_visitId'], - name: '', - field: ['$200'], - visitRange: [] - } - ] - } - } - }); - await admin.post('/graphql').send({ - query: print(CREATE_NEW_DATA_VERSION), - variables: { studyId: createdStudy.id, dataVersion: '1', tag: 'testTag' } - }); - await authorisedUser.post('/graphql').send({ - query: print(CREATE_ONTOLOGY_TREE), - variables: { - studyId: createdStudy.id, - ontologyTree: { - name: 'fakeTree', - routes: [ - { - path: ['DM', 'm_subjectId', 'm_visitId'], - name: 'AGEnew', - field: ['$100'], - visitRange: [] - }, - { - path: ['QS', 'MFI', 'm_subjectId', 'm_visitId'], - name: '', - field: ['$200'], - visitRange: [] - } - ] - } - } - }); - const study: IStudy = await db.collections!.studies_collection.findOne({ id: createdStudy.id, deleted: null }); - const resWithoutVersion = await authorisedUser.post('/graphql').send({ - query: print(GET_ONTOLOGY_TREE), - variables: { - studyId: createdStudy.id, - treeName: null - } - }); - expect(resWithoutVersion.status).toBe(200); - expect(resWithoutVersion.body.data.getOntologyTree).toHaveLength(1); - expect(resWithoutVersion.body.data.getOntologyTree[0]).toEqual({ - id: study.ontologyTrees[0].id, - name: 'fakeTree', - routes: [ - { - id: study.ontologyTrees[0].routes[0].id, - path: ['DM', 'm_subjectId', 'm_visitId'], - name: 'AGE', - field: ['$100'], - visitRange: [] - }, - { - id: study.ontologyTrees[0].routes[1].id, - path: ['QS', 'MFI', 'm_subjectId', 'm_visitId'], - name: '', - field: ['$200'], - visitRange: [] - } - ], - metadata: null - }); - const resWithVersion = await authorisedUser.post('/graphql').send({ - query: print(GET_ONTOLOGY_TREE), - variables: { - studyId: createdStudy.id, - treeName: null, - versionId: null - } - }); - expect(resWithVersion.status).toBe(200); - expect(resWithVersion.body.data.getOntologyTree).toHaveLength(1); - expect(resWithVersion.body.data.getOntologyTree[0]).toEqual({ - id: study.ontologyTrees[1].id, - name: 'fakeTree', - routes: [ - { - id: study.ontologyTrees[1].routes[0].id, - path: ['DM', 'm_subjectId', 'm_visitId'], - name: 'AGEnew', - field: ['$100'], - visitRange: [] - }, - { - id: study.ontologyTrees[1].routes[1].id, - path: ['QS', 'MFI', 'm_subjectId', 'm_visitId'], - name: '', - field: ['$200'], - visitRange: [] - } - ], - metadata: null - }); - // clear study - await db.collections!.studies_collection.findOneAndUpdate({ id: createdStudy.id, deleted: null }, { - $set: { - dataVersions: [], - currentDataVersion: -1, - ontologyTrees: [] - } - }); - }); - - test('Get an ontology tree from project (authorised user)', async () => { - await authorisedUser.post('/graphql').send({ - query: print(CREATE_ONTOLOGY_TREE), - variables: { - studyId: createdStudy.id, - ontologyTree: { - name: 'fakeTree', - routes: [ - { - path: ['DM', 'm_subjectId', 'm_visitId'], - name: 'AGE', - field: ['$100'], - visitRange: [] - }, - { - path: ['QS', 'MFI', 'm_subjectId', 'm_visitId'], - name: '', - field: ['$200'], - visitRange: [] - } - ] - } - } - }); - await admin.post('/graphql').send({ - query: print(CREATE_NEW_DATA_VERSION), - variables: { studyId: createdStudy.id, dataVersion: '1', tag: 'testTag' } - }); - const res = await user.post('/graphql').send({ - query: print(GET_ONTOLOGY_TREE), - variables: { - studyId: createdProject.studyId, - projectId: createdProject.id, - treeName: null - } - }); - expect(res.status).toBe(200); - expect(res.body.data.getOntologyTree).toBe(null); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - // clear study - await db.collections!.studies_collection.findOneAndUpdate({ id: createdStudy.id, deleted: null }, { - $set: { - dataVersions: [], - currentDataVersion: -1, - ontologyTrees: [] - } - }); - }); - - test('Get an ontology tree (unauthorised user), should fail', async () => { - await authorisedUser.post('/graphql').send({ - query: print(CREATE_ONTOLOGY_TREE), - variables: { - studyId: createdStudy.id, - ontologyTree: { - name: 'fakeTree', - routes: [ - { - path: ['DM', 'm_subjectId', 'm_visitId'], - name: 'AGE', - field: ['$100'], - visitRange: [] - }, - { - path: ['QS', 'MFI', 'm_subjectId', 'm_visitId'], - name: '', - field: ['$200'], - visitRange: [] - } - ] - } - } - }); - await admin.post('/graphql').send({ - query: print(CREATE_NEW_DATA_VERSION), - variables: { studyId: createdStudy.id, dataVersion: '1', tag: 'testTag' } - }); - const res = await user.post('/graphql').send({ - query: print(GET_ONTOLOGY_TREE), - variables: { - studyId: createdProject.studyId, - projectId: createdProject.id, - treeName: null - } - }); - expect(res.status).toBe(200); - expect(res.body.data.getOntologyTree).toBe(null); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - // clear study - await db.collections!.studies_collection.findOneAndUpdate({ id: createdStudy.id, deleted: null }, { - $set: { - dataVersions: [], - currentDataVersion: -1, - ontologyTrees: [] - } - }); - }); - - test('Check data complete (admin)', async () => { - await admin.post('/graphql').send({ - query: print(UPLOAD_DATA_IN_ARRAY), - variables: { studyId: createdStudy.id, data: multipleRecords } - }); - // edit a field so that the datatype mismatched with the exisiting value - // this happens when a field is deleted first and then modified and added, while some data has been uploaded before deleting, causing conflicts - await db.collections!.field_dictionary_collection.findOneAndUpdate({ - studyId: createdStudy.id, - fieldId: '32' - }, { - $set: { - dataType: enumValueType.DECIMAL - } - }); - - const checkRes = await admin.post('/graphql').send({ - query: print(CHECK_DATA_COMPLETE), - variables: { studyId: createdStudy.id } - }); - expect(checkRes.status).toBe(200); - expect(checkRes.body.errors).toBeUndefined(); - expect(checkRes.body.data.checkDataComplete.sort((a, b) => { - return a.subjectId === b.subjectId ? a.visitId.localeCompare(b.visitId) : a.subjectId.localeCompare(b.subjectId); - })).toEqual([ - { - subjectId: 'GR6R4AR', - visitId: '2', - fieldId: '32', - error: 'Field 32: Cannot parse as decimal.' - }, - { - subjectId: 'I7N3G6G', - visitId: '1', - fieldId: '32', - error: 'Field 32: Cannot parse as decimal.' - }, - { - subjectId: 'I7N3G6G', - visitId: '2', - fieldId: '32', - error: 'Field 32: Cannot parse as decimal.' - } - ]); - }); - }); - }); -} else - test(`${__filename.split(/[\\/]/).pop()} skipped because it requires Minio on Docker`, () => { - expect(true).toBe(true); - }); diff --git a/packages/itmat-interface/test/serverTests/users.test.ts b/packages/itmat-interface/test/serverTests/users.test.ts deleted file mode 100644 index 03840df39..000000000 --- a/packages/itmat-interface/test/serverTests/users.test.ts +++ /dev/null @@ -1,1745 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-nocheck - -import 'nodemailer'; -import request, { SuperAgentTest } from 'supertest'; -import { print } from 'graphql'; -import { connectAdmin, connectUser, connectAgent } from './_loginHelper'; -import { db } from '../../src/database/database'; -import { makeAESIv, makeAESKeySalt, encryptEmail } from '../../src/graphql/resolvers/userResolvers'; -import { v4 as uuid } from 'uuid'; -import { Router } from '../../src/server/router'; -import { errorCodes } from '../../src/graphql/errors'; -import { MongoClient, Db } from 'mongodb'; -import { MongoMemoryServer } from 'mongodb-memory-server'; -import { setupDatabase } from '@itmat-broker/itmat-setup'; -import config from '../../config/config.sample.json'; -import * as mfa from '../../src/utils/mfa'; -import { - WHO_AM_I, - GET_USERS, - CREATE_USER, - EDIT_USER, - DELETE_USER, - REQUEST_USERNAME_OR_RESET_PASSWORD, - RESET_PASSWORD, - LOGIN -} from '@itmat-broker/itmat-models'; -import { IResetPasswordRequest, IUser, userTypes } from '@itmat-broker/itmat-types'; -import type { Express } from 'express'; - -let app: Express; -let mongodb: MongoMemoryServer; -let admin: SuperAgentTest; -let user: SuperAgentTest; -let mongoConnection: MongoClient; -let mongoClient: Db; - -const SEED_STANDARD_USER_USERNAME = 'standardUser'; -const SEED_STANDARD_USER_EMAIL = 'standard@example.com'; -const { TEST_SMTP_CRED, TEST_SMTP_USERNAME, TEST_RECEIVER_EMAIL_ADDR } = process.env; -const TEMP_USER_TEST_EMAIL = TEST_RECEIVER_EMAIL_ADDR || SEED_STANDARD_USER_EMAIL; - -jest.mock('nodemailer', () => { - const { TEST_SMTP_CRED, TEST_SMTP_USERNAME } = process.env; - if (!TEST_SMTP_CRED || !TEST_SMTP_USERNAME || !config?.nodemailer?.auth?.pass || !config?.nodemailer?.auth?.user) - return { - createTransport: jest.fn().mockImplementation(() => ({ - sendMail: jest.fn() - })) - }; - return jest.requireActual('nodemailer'); -}); - -afterAll(async () => { - await db.closeConnection(); - await mongoConnection?.close(); - await mongodb.stop(); - - /* claer all mocks */ - jest.clearAllMocks(); -}); - -beforeAll(async () => { // eslint-disable-line no-undef - /* Creating a in-memory MongoDB instance for testing */ - const dbName = uuid(); - mongodb = await MongoMemoryServer.create({ instance: { dbName } }); - const connectionString = mongodb.getUri(); - await setupDatabase(connectionString, dbName); - - /* Wiring up the backend server */ - config.database.mongo_url = connectionString; - config.database.database = dbName; - await db.connect(config.database, MongoClient); - const router = new Router(config); - await router.init(); - - /* Connect mongo client (for test setup later / retrieve info later) */ - mongoConnection = await MongoClient.connect(connectionString); - mongoClient = mongoConnection.db(dbName); - - /* Connecting clients for testing later */ - app = router.getApp(); - admin = request.agent(app); - user = request.agent(app); - await connectAdmin(admin); - await connectUser(user); - - /* Mock Date for testing */ - jest.spyOn(Date, 'now').mockImplementation(() => 1591134065000); - - /* Mock emailing interface */ - if (config.nodemailer.auth === undefined) - config.nodemailer.auth = { - auth: {} - } as any; - if (TEST_SMTP_CRED) - config.nodemailer.auth.pass = TEST_SMTP_CRED; - if (TEST_SMTP_USERNAME) - config.nodemailer.auth.user = TEST_SMTP_USERNAME; -}); - -describe('USERS API', () => { - describe('RESET PASSWORD FUNCTION', () => { - let loggedoutUser; - const presetToken = uuid(); - let encryptedEmailForStandardUser; - - - beforeAll(async () => { - loggedoutUser = request.agent(app); - encryptedEmailForStandardUser = - await encryptEmail(SEED_STANDARD_USER_EMAIL, makeAESKeySalt(presetToken), makeAESIv(presetToken)); - }); - - test('Request reset password with non-existent user providing username', async () => { - const res = await loggedoutUser - .post('/graphql') - .send({ - query: print(REQUEST_USERNAME_OR_RESET_PASSWORD), - variables: { - forgotUsername: false, - forgotPassword: true, - username: 'Idontexist' - } - }); - expect(res.status).toBe(200); // even though user doesnt exist. This should pass so people dont know the registered users - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.requestUsernameOrResetPassword).toEqual({ successful: true }); - }, 6050); - - test('Request reset password with non-existent user providing email', async () => { - const res = await loggedoutUser - .post('/graphql') - .send({ - query: print(REQUEST_USERNAME_OR_RESET_PASSWORD), - variables: { - forgotUsername: true, - forgotPassword: true, - email: 'email@email.io' - } - }); - expect(res.status).toBe(200); // even though user doesnt exist. This should pass so people dont know the registered users - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.requestUsernameOrResetPassword).toEqual({ successful: true }); - }, 6050); - - test('Request reset password with non-existent user but provide email as well as username (should fail)', async () => { - const res = await loggedoutUser - .post('/graphql') - .send({ - query: print(REQUEST_USERNAME_OR_RESET_PASSWORD), - variables: { - forgotUsername: false, - forgotPassword: true, - username: 'fakeuser', - email: 'email@email.io' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_MALFORMED_INPUT); - expect(res.body.data.requestUsernameOrResetPassword).toBe(null); - }); - - test('Request reset password and username but do not provide any email nor username (should fail)', async () => { - const res = await loggedoutUser - .post('/graphql') - .send({ - query: print(REQUEST_USERNAME_OR_RESET_PASSWORD), - variables: { - forgotUsername: true, - forgotPassword: true - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_MALFORMED_INPUT); - }); - - test('Request reset password and username but provide username (should fail)', async () => { - const res = await loggedoutUser - .post('/graphql') - .send({ - query: print(REQUEST_USERNAME_OR_RESET_PASSWORD), - variables: { - forgotUsername: true, - forgotPassword: true, - username: 'Iamauser', - email: 'email@email.io' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_MALFORMED_INPUT); - }); - - test('Request reset password with existing user providing email', async () => { - /* setup: replacing the seed user's email with slurp test email */ - const updateResult = await db.collections!.users_collection.findOneAndUpdate({ - username: SEED_STANDARD_USER_USERNAME - }, { $set: { email: TEMP_USER_TEST_EMAIL } }); - expect(updateResult).not.toBeNull(); - - /* test */ - const res = await loggedoutUser - .post('/graphql') - .send({ - query: print(REQUEST_USERNAME_OR_RESET_PASSWORD), - variables: { - forgotUsername: true, - forgotPassword: true, - email: TEMP_USER_TEST_EMAIL - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.requestUsernameOrResetPassword).toEqual({ successful: true }); - const modifiedUser = await db.collections!.users_collection.findOne({ username: SEED_STANDARD_USER_USERNAME }); - expect(modifiedUser).toBeDefined(); - expect(modifiedUser.resetPasswordRequests).toHaveLength(1); - expect(typeof modifiedUser.resetPasswordRequests[0].id).toBe('string'); - expect(typeof modifiedUser.resetPasswordRequests[0].timeOfRequest).toBe('number'); - expect(modifiedUser.resetPasswordRequests[0].used).toBe(false); - expect(new Date().valueOf() - modifiedUser.resetPasswordRequests[0].timeOfRequest).toBeLessThan(15000); // less then 5 seconds - - /* cleanup: changing the user's email back */ - const cleanupResult = await db.collections!.users_collection.findOneAndUpdate({ username: SEED_STANDARD_USER_USERNAME }, { $set: { email: SEED_STANDARD_USER_EMAIL, resetPasswordRequests: [] } }, { returnDocument: 'after' }); - expect(cleanupResult).not.toBeNull(); - expect(cleanupResult.email).toBe(SEED_STANDARD_USER_EMAIL); - }, 30000); - - test('Request reset password with existing user providing username', async () => { - /* setup: replacing the seed user's email with test email */ - const updateResult = await db.collections!.users_collection.findOneAndUpdate({ - username: SEED_STANDARD_USER_USERNAME - }, { $set: { email: TEMP_USER_TEST_EMAIL } }); - expect(updateResult).not.toBeNull(); - - /* test */ - const res = await loggedoutUser - .post('/graphql') - .send({ - query: print(REQUEST_USERNAME_OR_RESET_PASSWORD), - variables: { - forgotUsername: false, - forgotPassword: true, - username: SEED_STANDARD_USER_USERNAME - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.requestUsernameOrResetPassword).toEqual({ successful: true }); - const modifiedUser = await db.collections!.users_collection.findOne({ username: SEED_STANDARD_USER_USERNAME }); - expect(modifiedUser).toBeDefined(); - expect(modifiedUser.resetPasswordRequests).toHaveLength(1); - expect(typeof modifiedUser.resetPasswordRequests[0].id).toBe('string'); - expect(typeof modifiedUser.resetPasswordRequests[0].timeOfRequest).toBe('number'); - expect(modifiedUser.resetPasswordRequests[0].used).toBe(false); - expect(new Date().valueOf() - modifiedUser.resetPasswordRequests[0].timeOfRequest).toBeLessThan(15000); // less then 5 seconds - - /* cleanup: changing the user's email back */ - const cleanupResult = await db.collections!.users_collection.findOneAndUpdate({ username: SEED_STANDARD_USER_USERNAME }, { $set: { email: SEED_STANDARD_USER_EMAIL, resetPasswordRequests: [] } }, { returnDocument: 'after' }); - expect(cleanupResult).not.toBeNull(); - expect(cleanupResult.email).toBe(SEED_STANDARD_USER_EMAIL); - }, 30000); - - test('Reset password with password length < 8', async () => { - const res = await loggedoutUser - .post('/graphql') - .send({ - query: print(RESET_PASSWORD), - variables: { - encryptedEmail: encryptedEmailForStandardUser, - token: 'token', - newPassword: 'admin' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe('Password has to be at least 8 character long.'); - expect(res.body.data.resetPassword).toBe(null); - }); - - test('Reset password with incorrect token (should fail)', async () => { - /* setup: add request entry to user */ - const resetPWrequest: IResetPasswordRequest = { - id: presetToken, - timeOfRequest: new Date().valueOf(), - used: false - }; - const updateResult = await db.collections!.users_collection.findOneAndUpdate( - { username: SEED_STANDARD_USER_USERNAME }, - { $set: { resetPasswordRequests: [resetPWrequest] } } - ); - expect(updateResult).not.toBeNull(); - - /* test */ - const res = await loggedoutUser - .post('/graphql') - .send({ - query: print(RESET_PASSWORD), - variables: { - encryptedEmail: encryptedEmailForStandardUser, - token: 'wrongtoken_wrong_token_wrong_token', - newPassword: 'securepasswordrighthere' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe('Token is not valid.'); - expect(res.body.data.resetPassword).toBe(null); - - /* cleanup */ - const updateResult2 = await db.collections!.users_collection.findOneAndUpdate( - { username: SEED_STANDARD_USER_USERNAME }, - { $set: { resetPasswordRequests: [] } } - ); - expect(updateResult2).not.toBeNull(); - }); - - test('Reset password with incorrect token length < 16 (should fail)', async () => { - /* NOTE: token length < 16 is a constraint needed because of the way makeAESIv() works; */ - /* NOTE: if makeAESIv() implementation changes, remove this */ - /* setup: add request entry to user */ - const resetPWrequest: IResetPasswordRequest = { - id: presetToken, - timeOfRequest: new Date().valueOf(), - used: false - }; - const updateResult = await db.collections!.users_collection.findOneAndUpdate( - { username: SEED_STANDARD_USER_USERNAME }, - { $set: { resetPasswordRequests: [resetPWrequest] } } - ); - expect(updateResult).not.toBeNull(); - - /* test */ - const res = await loggedoutUser - .post('/graphql') - .send({ - query: print(RESET_PASSWORD), - variables: { - encryptedEmail: encryptedEmailForStandardUser, - token: 'shorttoken', - newPassword: 'securepasswordrighthere' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_MALFORMED_INPUT); - expect(res.body.data.resetPassword).toBe(null); - - /* cleanup */ - const updateResult2 = await db.collections!.users_collection.findOneAndUpdate( - { username: SEED_STANDARD_USER_USERNAME }, - { $set: { resetPasswordRequests: [] } } - ); - expect(updateResult2).not.toBeNull(); - }); - - test('Reset password with expired token (should fail)', async () => { - /* setup: add request entry to user */ - const resetPWrequest: IResetPasswordRequest = { - id: presetToken, - timeOfRequest: new Date().valueOf() - 60 * 60 * 1000 /* (default expiry: 1hr) */ - 1, - used: false - }; - const updateResult = await db.collections!.users_collection.findOneAndUpdate( - { username: SEED_STANDARD_USER_USERNAME }, - { $set: { resetPasswordRequests: [resetPWrequest] } } - ); - expect(updateResult).not.toBeNull(); - - /* test */ - const res = await loggedoutUser - .post('/graphql') - .send({ - query: print(RESET_PASSWORD), - variables: { - encryptedEmail: encryptedEmailForStandardUser, - token: presetToken, - newPassword: 'securepasswordrighthere' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - expect(res.body.data.resetPassword).toBe(null); - - /* cleanup */ - const updateResult2 = await db.collections!.users_collection.findOneAndUpdate( - { username: SEED_STANDARD_USER_USERNAME }, - { $set: { resetPasswordRequests: [] } } - ); - expect(updateResult2).not.toBeNull(); - }); - - test('Reset password with expired token (making sure id and expiry date belong to the same token) (should fail)', async () => { - /* test whether a existent token that is not expired will be selected even if providing a expired token id (mongo array selection is a bit weird) */ - /* setup: add request entry to user */ - const resetPWrequests: IResetPasswordRequest[] = [ - { - id: presetToken, - timeOfRequest: new Date().valueOf() - 60 * 60 * 1000 /* (default expiry: 1hr) */ - 1, - used: false - }, - { - id: 'still_not_expired_token', - timeOfRequest: new Date().valueOf(), - used: false - } - ]; - const updateResult = await db.collections!.users_collection.findOneAndUpdate( - { username: SEED_STANDARD_USER_USERNAME }, - { $set: { resetPasswordRequests: resetPWrequests } } - ); - expect(updateResult).not.toBeNull(); - - /* test */ - const res = await loggedoutUser - .post('/graphql') - .send({ - query: print(RESET_PASSWORD), - variables: { - encryptedEmail: encryptedEmailForStandardUser, - token: presetToken, - newPassword: 'securepasswordrighthere' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - expect(res.body.data.resetPassword).toBe(null); - - /* cleanup */ - const updateResult2 = await db.collections!.users_collection.findOneAndUpdate( - { username: SEED_STANDARD_USER_USERNAME }, - { $set: { resetPasswordRequests: [] } } - ); - expect(updateResult2).not.toBeNull(); - }); - - test('Reset password with valid token', async () => { - /* setup: add request entry to user */ - const resetPWrequest: IResetPasswordRequest = { - id: presetToken, - timeOfRequest: new Date().valueOf(), - used: false - }; - const updateResult = await db.collections!.users_collection.findOneAndUpdate( - { username: SEED_STANDARD_USER_USERNAME }, - { $set: { resetPasswordRequests: [resetPWrequest] } } - ); - expect(updateResult).not.toBeNull(); - - /* test */ - const newloggedoutuser = request.agent(app); - const res = await newloggedoutuser - .post('/graphql') - .send({ - query: print(RESET_PASSWORD), - variables: { - encryptedEmail: encryptedEmailForStandardUser, - token: presetToken, - newPassword: 'securepasswordrighthere' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.resetPassword).toEqual({ successful: true }); - const checkedUser = await db.collections!.users_collection.findOne({ username: SEED_STANDARD_USER_USERNAME }); - await connectAgent(newloggedoutuser, SEED_STANDARD_USER_USERNAME, 'securepasswordrighthere', checkedUser.otpSecret); - const whoami = await newloggedoutuser.post('/graphql').send({ query: print(WHO_AM_I) }); - expect(whoami.status).toBe(200); - expect(whoami.body.error).toBeUndefined(); - expect(whoami.body.data.whoAmI.id).toBeDefined(); - expect(whoami.body.data.whoAmI).toEqual({ - username: 'standardUser', - type: userTypes.STANDARD, - firstname: 'Tai Man', - lastname: 'Chan', - organisation: 'organisation_system', - email: 'standard@example.com', - description: 'I am a standard user.', - id: whoami.body.data.whoAmI.id, - access: { - id: `user_access_obj_user_id_${whoami.body.data.whoAmI.id}`, - projects: [], - studies: [] - }, - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - createdAt: 1591134065000, - expiredAt: 1991134065000, - metadata: null - }); - - /* cleanup */ - const updateResult2 = await db.collections!.users_collection.findOneAndUpdate( - { username: SEED_STANDARD_USER_USERNAME }, - { $set: { resetPasswordRequests: [], password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi' } } - ); - expect(updateResult2).not.toBeNull(); - }); - - test('Reset password with used token (should fail)', async () => { - /* setup: add request entry to user */ - const resetPWrequest: IResetPasswordRequest[] = [ - { - id: 'will-not-be-used-token', - timeOfRequest: new Date().valueOf(), - used: false - }, - { - id: presetToken, - timeOfRequest: new Date().valueOf(), - used: false - } - ]; - const updateResult = await db.collections!.users_collection.findOneAndUpdate( - { username: SEED_STANDARD_USER_USERNAME }, - { $set: { resetPasswordRequests: resetPWrequest } } - ); - expect(updateResult).not.toBeNull(); - const newloggedoutuser = request.agent(app); - const res = await newloggedoutuser - .post('/graphql') - .send({ - query: print(RESET_PASSWORD), - variables: { - encryptedEmail: encryptedEmailForStandardUser, - token: presetToken, - newPassword: 'securepasswordrighthere' - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.resetPassword).toEqual({ successful: true }); - const checkedUser = await db.collections!.users_collection.findOne({ username: SEED_STANDARD_USER_USERNAME }); - await connectAgent(newloggedoutuser, SEED_STANDARD_USER_USERNAME, 'securepasswordrighthere', checkedUser.otpSecret); - const whoami = await newloggedoutuser.post('/graphql').send({ query: print(WHO_AM_I) }); - expect(whoami.status).toBe(200); - expect(whoami.body.error).toBeUndefined(); - expect(whoami.body.data.whoAmI.id).toBeDefined(); - expect(whoami.body.data.whoAmI).toEqual({ - username: 'standardUser', - type: userTypes.STANDARD, - firstname: 'Tai Man', - lastname: 'Chan', - organisation: 'organisation_system', - email: 'standard@example.com', - description: 'I am a standard user.', - id: whoami.body.data.whoAmI.id, - access: { - id: `user_access_obj_user_id_${whoami.body.data.whoAmI.id}`, - projects: [], - studies: [] - }, - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - createdAt: 1591134065000, - expiredAt: 1991134065000, - metadata: null - }); - - /* test */ - const resAgain = await newloggedoutuser - .post('/graphql') - .send({ - query: print(RESET_PASSWORD), - variables: { - encryptedEmail: encryptedEmailForStandardUser, - token: presetToken, - newPassword: 'securepasswordrighthere' - } - }); - expect(resAgain.status).toBe(200); - expect(resAgain.body.errors).toHaveLength(1); - expect(resAgain.body.errors[0].message).toBe(errorCodes.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY); - expect(resAgain.body.data.resetPassword).toEqual(null); - - /* cleanup */ - const updateResult2 = await db.collections!.users_collection.findOneAndUpdate( - { username: SEED_STANDARD_USER_USERNAME }, - { $set: { resetPasswordRequests: [], password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi' } } - ); - expect(updateResult2).not.toBeNull(); - }); - }); - - describe('END USERS API', () => { - let adminId; - let userId; - - beforeAll(async () => { - /* setup: first retrieve the generated user id */ - const result = await mongoClient.collection(config.database.collections.users_collection).find({}, { projection: { id: 1, username: 1 } }).toArray(); - adminId = result.filter(e => e.username === 'admin')[0].id; - userId = result.filter(e => e.username === 'standardUser')[0].id; - }); - - test('If someone not logged in made a request', async () => { - const client_not_logged_in = request.agent(app); - const res = await client_not_logged_in.post('/graphql').send({ query: print(GET_USERS), variables: { fetchDetailsAdminOnly: false, fetchAccessPrivileges: false } }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NOT_LOGGED_IN); - expect(res.body.data.getUsers).toBe(null); - }); - - test('who am I (not logged in)', async () => { - const client_not_logged_in = request.agent(app); - const res = await client_not_logged_in.post('/graphql').send({ query: print(WHO_AM_I) }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.whoAmI).toBe(null); - }); - - test('Who am I (admin)', async () => { - const res = await admin.post('/graphql').send({ query: print(WHO_AM_I) }); - expect(res.status).toBe(200); - expect(res.body.data.whoAmI.id).toBeDefined(); - adminId = res.body.data.whoAmI.id; - expect(res.body.data.whoAmI).toEqual({ - username: 'admin', - type: userTypes.ADMIN, - firstname: 'Fadmin', - lastname: 'Ladmin', - organisation: 'organisation_system', - email: 'admin@example.com', - description: 'I am an admin user.', - id: adminId, - access: { - id: `user_access_obj_user_id_${adminId}`, - projects: [], - studies: [] - }, - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - createdAt: 1591134065000, - expiredAt: 1991134065000, - metadata: null - }); - }); - - test('Who am I (user)', async () => { - const res = await user.post('/graphql').send({ query: print(WHO_AM_I) }); - expect(res.status).toBe(200); - expect(res.body.error).toBeUndefined(); - expect(res.body.data.whoAmI.id).toBeDefined(); - userId = res.body.data.whoAmI.id; - expect(res.body.data.whoAmI).toEqual({ - username: 'standardUser', - type: userTypes.STANDARD, - firstname: 'Tai Man', - lastname: 'Chan', - organisation: 'organisation_system', - email: 'standard@example.com', - description: 'I am a standard user.', - id: userId, - access: { - id: `user_access_obj_user_id_${userId}`, - projects: [], - studies: [] - }, - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - createdAt: 1591134065000, - expiredAt: 1991134065000, - metadata: null - }); - }); - - test('Expired standard user log in (should fail)', async () => { - const userSecret = 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA'; - const newUser: IUser = { - username: 'expired_user', - type: userTypes.STANDARD, - firstname: 'Fexpired user', - lastname: 'Lexpired user', - password: '$2b$04$ps9ownz6PqJFD/LExsmgR.ZLk11zhtRdcpUwypWVfWJ4ZW6/Zzok2', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: 'expire@example.com', - resetPasswordRequests: [], - description: 'I am an expired user.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - deleted: null, - id: 'expiredId0', - createdAt: 1591134065000, - expiredAt: 1501134065000 - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - const newloggedoutuser = request.agent(app); - const otp = mfa.generateTOTP(userSecret).toString(); - const res = await newloggedoutuser.post('/graphql').set('Content-type', 'application/json').send({ - query: print(LOGIN), - variables: { - username: 'expired_user', - password: 'admin', - totp: otp - } - }); - expect(res.status).toBe(200); - expect(res.body.data.login).toBeNull(); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe('Account Expired. Please request a new expiry date!'); - await admin.post('/graphql').send( - { - query: print(DELETE_USER), - variables: { - userId: newUser.id - } - } - ); - }); - - test('Expired admin user log in', async () => { - const adminSecret = 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA'; - const newUser: IUser = { - username: 'expired_admin', - type: userTypes.ADMIN, - firstname: 'Fexpired admin', - lastname: 'Lexpired admin', - password: '$2b$04$ps9ownz6PqJFD/LExsmgR.ZLk11zhtRdcpUwypWVfWJ4ZW6/Zzok2', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: 'admine@example.com', - resetPasswordRequests: [], - description: 'I am an expired admin.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - deleted: null, - id: 'expiredId1', - createdAt: 1591134065000, - expiredAt: 1501134065000 - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - const newloggedoutuser = request.agent(app); - const otp = mfa.generateTOTP(adminSecret).toString(); - const res = await newloggedoutuser.post('/graphql').set('Content-type', 'application/json').send({ - query: print(LOGIN), - variables: { - username: 'expired_admin', - password: 'admin', - totp: otp - } - }); - expect(res.status).toBe(200); - expect(res.body.data.login).toEqual({ - id: 'expiredId1', - username: 'expired_admin', - type: 'ADMIN', - firstname: 'Fexpired admin', - lastname: 'Lexpired admin', - email: 'admine@example.com', - organisation: 'organisation_system', - description: 'I am an expired admin.', - access: { - id: 'user_access_obj_user_id_expiredId1', - projects: [], - studies: [] - }, - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - createdAt: 1591134065000, - expiredAt: 1501134065000, - metadata: null - }); - expect(res.body.errors).toBeUndefined(); - await admin.post('/graphql').send( - { - query: print(DELETE_USER), - variables: { - userId: newUser.id - } - } - ); - }); - }); - - describe('APP USERS QUERY API', () => { - let adminId; - let userId; - - beforeAll(async () => { - /* setup: first retrieve the generated user id */ - const result = await mongoClient.collection(config.database.collections.users_collection).find({}, { projection: { id: 1, username: 1 } }).toArray(); - adminId = result.filter(e => e.username === 'admin')[0].id; - userId = result.filter(e => e.username === 'standardUser')[0].id; - }); - - test('Get all users list with detail (no access info) (admin)', async () => { - const res = await admin.post('/graphql').send({ query: print(GET_USERS), variables: { fetchDetailsAdminOnly: true, fetchAccessPrivileges: false } }); - expect(res.status).toBe(200); - expect(res.body.data.getUsers).toEqual([ - { - username: 'admin', - type: userTypes.ADMIN, - firstname: 'Fadmin', - lastname: 'Ladmin', - organisation: 'organisation_system', - email: 'admin@example.com', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - description: 'I am an admin user.', - id: adminId, - createdAt: 1591134065000, - expiredAt: 1991134065000, - metadata: null - }, - { - username: 'standardUser', - type: userTypes.STANDARD, - firstname: 'Tai Man', - lastname: 'Chan', - organisation: 'organisation_system', - email: 'standard@example.com', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - description: 'I am a standard user.', - id: userId, - createdAt: 1591134065000, - expiredAt: 1991134065000, - metadata: null - } - ]); - }); - - test('Get all users list with detail (w/ access info) (admin)', async () => { - const res = await admin.post('/graphql').send({ query: print(GET_USERS), variables: { fetchDetailsAdminOnly: true, fetchAccessPrivileges: true } }); - expect(res.status).toBe(200); - expect(res.body.data.getUsers).toEqual([ - { - username: 'admin', - type: userTypes.ADMIN, - firstname: 'Fadmin', - lastname: 'Ladmin', - organisation: 'organisation_system', - email: 'admin@example.com', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - description: 'I am an admin user.', - id: adminId, - access: { - id: `user_access_obj_user_id_${adminId}`, - projects: [], - studies: [] - }, - createdAt: 1591134065000, - expiredAt: 1991134065000, - metadata: null - }, - { - username: 'standardUser', - type: userTypes.STANDARD, - firstname: 'Tai Man', - lastname: 'Chan', - organisation: 'organisation_system', - email: 'standard@example.com', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - description: 'I am a standard user.', - id: userId, - access: { - id: `user_access_obj_user_id_${userId}`, - projects: [], - studies: [] - }, - createdAt: 1591134065000, - expiredAt: 1991134065000, - metadata: null - } - ]); - }); - - test('Get all users list with detail (no access info) (user) (should fail)', async () => { - const res = await user.post('/graphql').send({ query: print(GET_USERS), variables: { fetchDetailsAdminOnly: true, fetchAccessPrivileges: false } }); - expect(res.status).toBe(200); //graphql returns 200 for application layer errors - expect(res.body.errors).toHaveLength(3); - expect(res.body.errors[0].message).toBe('NO_PERMISSION_ERROR'); - expect(res.body.data.getUsers).toEqual([ // user still has permission to his own data - null, - { - username: 'standardUser', - type: userTypes.STANDARD, - firstname: 'Tai Man', - lastname: 'Chan', - organisation: 'organisation_system', - email: 'standard@example.com', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - description: 'I am a standard user.', - id: userId, - createdAt: 1591134065000, - expiredAt: 1991134065000, - metadata: null - } - ]); - }); - - test('Get all users list with detail (w/ access info) (user) (should fail)', async () => { - const res = await user.post('/graphql').send({ query: print(GET_USERS), variables: { fetchDetailsAdminOnly: true, fetchAccessPrivileges: true } }); - expect(res.status).toBe(200); // graphql returns 200 for application layer errors - expect(res.body.errors).toHaveLength(4); - expect(res.body.errors[0].message).toBe('NO_PERMISSION_ERROR'); - expect(res.body.errors[1].message).toBe('NO_PERMISSION_ERROR'); - expect(res.body.errors[2].message).toBe('NO_PERMISSION_ERROR'); - expect(res.body.errors[3].message).toBe('NO_PERMISSION_ERROR'); - expect(res.body.data.getUsers).toEqual([ // user still has permission to his own data - null, - { - username: 'standardUser', - type: userTypes.STANDARD, - firstname: 'Tai Man', - lastname: 'Chan', - organisation: 'organisation_system', - email: 'standard@example.com', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - description: 'I am a standard user.', - id: userId, - access: { - id: `user_access_obj_user_id_${userId}`, - projects: [], - studies: [] - }, - createdAt: 1591134065000, - expiredAt: 1991134065000, - metadata: null - } - ]); - }); - - test('Get all users without details (admin)', async () => { - const res = await admin.post('/graphql').send({ query: print(GET_USERS), variables: { fetchDetailsAdminOnly: false, fetchAccessPrivileges: false } }); - expect(res.status).toBe(200); - expect(res.body.error).toBeUndefined(); - expect(res.body.data.getUsers).toEqual([ - { - type: userTypes.ADMIN, - firstname: 'Fadmin', - lastname: 'Ladmin', - organisation: 'organisation_system', - id: adminId - }, - { - type: userTypes.STANDARD, - firstname: 'Tai Man', - lastname: 'Chan', - organisation: 'organisation_system', - id: userId - } - ]); - }); - - test('Get all users without details (user)', async () => { - const res = await user.post('/graphql').send({ query: print(GET_USERS), variables: { fetchDetailsAdminOnly: false, fetchAccessPrivileges: false } }); - expect(res.status).toBe(200); - expect(res.body.error).toBeUndefined(); - expect(res.body.data.getUsers).toEqual([ - { - type: userTypes.ADMIN, - firstname: 'Fadmin', - lastname: 'Ladmin', - organisation: 'organisation_system', - id: adminId - }, - { - type: userTypes.STANDARD, - firstname: 'Tai Man', - lastname: 'Chan', - organisation: 'organisation_system', - id: userId - } - ]); - }); - - test('Get a specific user with details (admin)', async () => { - const res = await admin.post('/graphql').send({ query: print(GET_USERS), variables: { userId, fetchDetailsAdminOnly: true, fetchAccessPrivileges: true } }); - expect(res.status).toBe(200); - expect(res.body.data.getUsers instanceof Array).toBe(true); - expect(res.body.data.getUsers).toEqual([ - { - username: 'standardUser', - type: userTypes.STANDARD, - firstname: 'Tai Man', - lastname: 'Chan', - organisation: 'organisation_system', - email: 'standard@example.com', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - description: 'I am a standard user.', - id: userId, - access: { - id: `user_access_obj_user_id_${userId}`, - projects: [], - studies: [] - }, - createdAt: 1591134065000, - expiredAt: 1991134065000, - metadata: null - } - ]); - }); - - test('Get a specific non-self user with details (user) (should fail)', async () => { - const res = await user.post('/graphql').send({ query: print(GET_USERS), variables: { userId: adminId, fetchDetailsAdminOnly: true, fetchAccessPrivileges: true } }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(4); - expect(res.body.errors[0].message).toBe('NO_PERMISSION_ERROR'); - expect(res.body.errors[1].message).toBe('NO_PERMISSION_ERROR'); - expect(res.body.errors[2].message).toBe('NO_PERMISSION_ERROR'); - expect(res.body.errors[3].message).toBe('NO_PERMISSION_ERROR'); - expect(res.body.data.getUsers).toEqual([ - null - ]); - }); - - test('Get a specific non-self user without details (user) (should fail)', async () => { - const res = await user.post('/graphql').send({ query: print(GET_USERS), variables: { userId: adminId, fetchDetailsAdminOnly: false, fetchAccessPrivileges: false } }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.getUsers).toEqual([ - { - type: userTypes.ADMIN, - firstname: 'Fadmin', - lastname: 'Ladmin', - organisation: 'organisation_system', - id: adminId - } - ]); - }); - - test('Get a specific self user with details (user)', async () => { - const res = await user.post('/graphql').send({ query: print(GET_USERS), variables: { userId, fetchDetailsAdminOnly: true, fetchAccessPrivileges: true } }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.getUsers).toEqual([ - { - username: 'standardUser', - type: userTypes.STANDARD, - firstname: 'Tai Man', - lastname: 'Chan', - organisation: 'organisation_system', - email: 'standard@example.com', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - description: 'I am a standard user.', - id: userId, - access: { - id: `user_access_obj_user_id_${userId}`, - projects: [], - studies: [] - }, - createdAt: 1591134065000, - expiredAt: 1991134065000, - metadata: null - } - ]); - }); - - test('Get a specific self user without details (w/ access info) (user)', async () => { - const res = await user.post('/graphql').send({ query: print(GET_USERS), variables: { userId, fetchDetailsAdminOnly: false, fetchAccessPrivileges: true } }); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.getUsers).toEqual([ - { - type: userTypes.STANDARD, - organisation: 'organisation_system', - firstname: 'Tai Man', - lastname: 'Chan', - id: userId, - access: { - id: `user_access_obj_user_id_${userId}`, - projects: [], - studies: [] - } - } - ]); - }); - }); - - describe('APP USER MUTATION API', () => { - - test('log in with incorrect totp (user)', async () => { - await admin.post('/graphql').send({ - query: print(CREATE_USER), - variables: { - username: 'testuser0', - password: 'testpassword0', - firstname: 'FUser Testing', - lastname: 'LUser Testing', - description: 'I am fake!', - organisation: 'DSI-ICL', - emailNotificationsActivated: false, - email: 'user0email@email.io', - type: userTypes.STANDARD - } - }); - - /* getting the created user from mongo */ - const createdUser = (await mongoClient - .collection(config.database.collections.users_collection) - .findOne({ username: 'testuser0' })); - - const incorrectTotp = mfa.generateTOTP(createdUser.otpSecret) + 1; - const res_login = await admin.post('/graphql') - .set('Content-type', 'application/json') - .send({ - query: print(LOGIN), - variables: { username: 'testuser0', password: 'testpassword0', totp: incorrectTotp.toString() } - }); - - expect(res_login.status).toBe(200); - expect(res_login.body.errors).toHaveLength(1); - expect(res_login.body.errors[0].message).toBe('Incorrect One-Time password.'); - }, 30000); - - test('create user', async () => { - const res = await admin.post('/graphql').send({ - query: print(CREATE_USER), - variables: { - username: 'testuser1', - password: 'testpassword', - firstname: 'FUser Testing', - lastname: 'LUser Testing', - description: 'I am fake!', - organisation: 'DSI-ICL', - emailNotificationsActivated: false, - email: 'fake@email.io', - type: userTypes.STANDARD - } - }); - - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.createUser).toStrictEqual( - { - successful: true - } - ); - }, 30000); - - test('create user with wrong email format', async () => { - const res = await admin.post('/graphql').send({ - query: print(CREATE_USER), - variables: { - username: 'testuser2', - password: 'testpassword', - firstname: 'FUser Testing2', - lastname: 'LUser Testing2', - description: 'I am fake!', - organisation: 'DSI-ICL', - emailNotificationsActivated: false, - email: 'fak@e@semail.io', - type: userTypes.STANDARD - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe('Email is not the right format.'); - expect(res.body.data.createUser).toBe(null); - }); - - test('create user with space in password and username', async () => { - const res = await admin.post('/graphql').send({ - query: print(CREATE_USER), - variables: { - username: 'test user1', - password: 'test password', - firstname: 'FUser Testing', - lastname: 'LUser Testing', - description: 'I am fake!', - organisation: 'DSI-ICL', - emailNotificationsActivated: false, - email: 'fake@email.io', - type: userTypes.STANDARD - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe('Username or password cannot have spaces.'); - expect(res.body.data.createUser).toBe(null); - }); - - test('create user that already exists', async () => { - /* setup: getting the id of the created user from mongo */ - const newUser: IUser = { - username: 'new_user', - type: userTypes.STANDARD, - firstname: 'FChan Siu Man', - lastname: 'LChan Siu Man', - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: 'new@example.com', - resetPasswordRequests: [], - description: 'I am a new user.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - deleted: null, - id: 'replaced_at_runtime1', - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - - /* assertions */ - const res = await admin.post('/graphql').send({ - query: print(CREATE_USER), - variables: { - username: 'new_user', - password: 'testpassword', - firstname: 'FUser Testing', - lastname: 'LUser Testing', - description: 'I am fake!', - organisation: 'DSI-ICL', - emailNotificationsActivated: false, - email: 'fake@email.io', - type: userTypes.STANDARD - } - }); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe('User already exists.'); - expect(res.body.data.createUser).toBe(null); - }); - - test('edit user password (admin) (should fail)', async () => { - /* setup: getting the id of the created user from mongo */ - const newUser: IUser = { - username: 'new_user_333333', - type: userTypes.STANDARD, - firstname: 'FChan Ming Ming', - lastname: 'LChan Ming Ming', - password: 'fakepassword', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: 'new3333@example.com', - resetPasswordRequests: [], - description: 'I am a new user 33333.', - emailNotificationsActivated: true, - organisation: 'organisation_system', - deleted: null, - id: 'fakeid2', - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - - /* assertion */ - const res = await admin.post('/graphql').send( - { - query: print(EDIT_USER), - variables: { - id: 'fakeid2', - password: 'ishouldfail' - } - } - ); - const result = await mongoClient - .collection(config.database.collections.users_collection) - .findOne({ id: 'fakeid2' }); - expect(result.password).toBe('fakepassword'); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.editUser).toEqual(null); - - }); - - - test('edit user without password (admin)', async () => { - /* setup: getting the id of the created user from mongo */ - const newUser: IUser = { - username: 'new_user_3', - type: userTypes.STANDARD, - firstname: 'FChan Ming Man', - lastname: 'LChan Ming Man', - password: 'fakepassword', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: 'new3@example.com', - resetPasswordRequests: [], - description: 'I am a new user 3.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - deleted: null, - id: 'fakeid2222', - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - - /* assertion */ - const res = await admin.post('/graphql').send( - { - query: print(EDIT_USER), - variables: { - id: 'fakeid2222', - username: 'fakeusername', - type: userTypes.ADMIN, - firstname: 'FMan', - lastname: 'LMan', - email: 'hey@uk.io', - description: 'DSI director', - organisation: 'DSI-ICL' - } - } - ); - const result = await mongoClient - .collection(config.database.collections.users_collection) - .findOne({ id: 'fakeid2222' }); - expect(result.password).toBe('fakepassword'); - expect(res.status).toBe(200); - expect(res.body.data.editUser).toEqual( - { - username: 'fakeusername', - type: userTypes.ADMIN, - firstname: 'FMan', - lastname: 'LMan', - organisation: 'DSI-ICL', - email: 'hey@uk.io', - description: 'DSI director', - id: 'fakeid2222', - access: { - id: 'user_access_obj_user_id_fakeid2222', - projects: [], - studies: [] - }, - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - createdAt: 1591134065000, - expiredAt: 1991134065000, - metadata: null - } - ); - }); - - test('edit own password with length < 8 (user) (should fail)', async () => { - /* setup: getting the id of the created user from mongo */ - const newUser: IUser = { - username: 'new_user_4444', - type: userTypes.STANDARD, - firstname: 'FMing Man San', - lastname: 'LMing Man San', - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: 'new4444@example.com', - resetPasswordRequests: [], - description: 'I am a new user 44444.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - deleted: null, - id: 'fakeid44444', - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - const createdUser = request.agent(app); - await connectAgent(createdUser, 'new_user_4444', 'admin', newUser.otpSecret); - - /* assertion */ - const res = await createdUser.post('/graphql').send( - { - query: print(EDIT_USER), - variables: { - id: 'fakeid44444', - password: 'admin', - email: 'new_email@ic.ac.uk' - } - } - ); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe('Password has to be at least 8 character long.'); - expect(res.body.data.editUser).toEqual(null); - }); - - test('edit own password (user)', async () => { - /* setup: getting the id of the created user from mongo */ - const newUser: IUser = { - username: 'new_user_4', - type: userTypes.STANDARD, - firstname: 'FMing Man', - lastname: 'LMing Man', - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - resetPasswordRequests: [], - description: 'I am a new user 4.', - email: 'new4@example.com', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - deleted: null, - id: 'fakeid4', - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - const createdUser = request.agent(app); - await connectAgent(createdUser, 'new_user_4', 'admin', newUser.otpSecret); - - /* assertion */ - const res = await createdUser.post('/graphql').send( - { - query: print(EDIT_USER), - variables: { - id: 'fakeid4', - password: 'securepasswordhere', - email: 'new_email@ic.ac.uk' - } - } - ); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.editUser).toEqual({ - username: 'new_user_4', - type: userTypes.STANDARD, - firstname: 'FMing Man', - lastname: 'LMing Man', - organisation: 'organisation_system', - email: 'new_email@ic.ac.uk', - description: 'I am a new user 4.', - id: 'fakeid4', - access: { - id: 'user_access_obj_user_id_fakeid4', - projects: [], - studies: [] - }, - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - createdAt: 1591134065000, - expiredAt: 1991134065000, - metadata: null - }); - const modifieduser = await mongoClient.collection(config.database.collections.users_collection).findOne({ username: 'new_user_4' }); - expect(modifieduser.password).not.toBe(newUser.password); - expect(modifieduser.password).toHaveLength(60); - }); - - test('edit own non-password fields (user) (should fail)', async () => { - /* setup: getting the id of the created user from mongo */ - const newUser: IUser = { - username: 'new_user_5', - type: userTypes.STANDARD, - firstname: 'FMing Man Chon', - lastname: 'LMing Man Chon', - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: 'new5@example.com', - description: 'I am a new user 5.', - resetPasswordRequests: [], - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - deleted: null, - id: 'fakeid5', - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - const createdUser = request.agent(app); - await connectAgent(createdUser, 'new_user_5', 'admin', newUser.otpSecret); - - /* assertion */ - const res = await createdUser.post('/graphql').send( - { - query: print(EDIT_USER), - variables: { - id: 'fakeid5', - username: 'new_username', - type: 'ADMIN', - firstname: 'FMing Man Chon', - lastname: 'LMing Man Chon', - description: 'I am a new user 5.' - } - } - ); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe('User not updated: Non-admin users are only authorised to change their password, email or email notification.'); - expect(res.body.data.editUser).toEqual(null); - }); - - test('edit own email with malformed email (user) (should fail)', async () => { - /* setup: getting the id of the created user from mongo */ - const newUser: IUser = { - username: 'new_user_6', - type: userTypes.STANDARD, - firstname: 'FMing Man', - lastname: 'LMing Man', - password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: 'new6@example.com', - resetPasswordRequests: [], - description: 'I am a new user 6.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - deleted: null, - id: 'fakeid6', - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - const createdUser = request.agent(app); - await connectAgent(createdUser, 'new_user_6', 'admin', newUser.otpSecret); - - /* assertion */ - const res = await createdUser.post('/graphql').send( - { - query: print(EDIT_USER), - variables: { - id: 'fakeid6', - email: 'new_@email@ic.ac.uk' - } - } - ); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe('User not updated: Email is not the right format.'); - expect(res.body.data.editUser).toBe(null); - }); - - test('edit other user (user)', async () => { - /* setup: getting the id of the created user from mongo */ - const newUser: IUser = { - username: 'new_user_7', - type: userTypes.STANDARD, - firstname: 'FMing Man Tai', - lastname: 'LMing Man Tai', - password: 'fakepassword', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: 'new7@example.com', - resetPasswordRequests: [], - description: 'I am a new user 7.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - deleted: null, - id: 'fakeid7', - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - - /* assertion */ - const res = await user.post('/graphql').send( - { - query: print(EDIT_USER), - variables: { - id: 'fakeid7', - password: 'email' - } - } - ); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.editUser).toEqual(null); - }); - - test('delete user (admin)', async () => { - /* setup: create a new user to be deleted */ - const newUser: IUser = { - username: 'new_user_8', - type: userTypes.STANDARD, - firstname: 'FChan Mei', - lastname: 'LChan Mei', - password: 'fakepassword', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: 'new8@example.com', - resetPasswordRequests: [], - description: 'I am a new user 8.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - deleted: null, - id: 'fakeid8', - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - - /* assertion */ - const getUserRes = await admin.post('/graphql').send({ - query: print(GET_USERS), - variables: { userId: newUser.id, fetchDetailsAdminOnly: false, fetchAccessPrivileges: false } - }); - - expect(getUserRes.body.data.getUsers).toEqual([{ - firstname: 'FChan Mei', - lastname: 'LChan Mei', - type: userTypes.STANDARD, - organisation: 'organisation_system', - id: newUser.id - }]); - - - const res = await admin.post('/graphql').send( - { - query: print(DELETE_USER), - variables: { - userId: newUser.id - } - } - ); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.deleteUser).toEqual({ - successful: true, - id: newUser.id - }); - - const getUserResAfter = await admin.post('/graphql').send({ - query: print(GET_USERS), - variables: { userId: newUser.id, fetchDetailsAdminOnly: false, fetchAccessPrivileges: false } - }); - - expect(getUserResAfter.body.data.getUsers).toEqual([]); - }); - - test('delete user that has been deleted (admin)', async () => { - /* setup: create a "deleted" new user to be deleted */ - const newUser: IUser = { - username: 'new_user_9', - type: userTypes.STANDARD, - firstname: 'FChan Mei Fong', - lastname: 'LChan Mei Fong', - password: 'fakepassword', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: 'new9@example.com', - resetPasswordRequests: [], - description: 'I am a new user 9.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - deleted: (new Date()).valueOf(), - id: 'fakeid9', - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - - /* assertions */ - const res = await admin.post('/graphql').send( - { - query: print(DELETE_USER), - variables: { - userId: newUser.id - } - } - ); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.deleteUser).toEqual({ - successful: true, - id: newUser.id - }); - }); - - test('delete user that has never existed (admin)', async () => { - const res = await admin.post('/graphql').send( - { - query: print(DELETE_USER), - variables: { - userId: 'I never existed' - } - } - ); - expect(res.status).toBe(200); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data.deleteUser).toEqual({ - successful: true, - id: 'I never existed' - }); - }); - - test('delete user (user)', async () => { - /* setup: create a new user to be deleted */ - const newUser: IUser = { - username: 'new_user_10', - type: userTypes.STANDARD, - firstname: 'FChan Mei Yi', - lastname: 'LChan Mei Yi', - password: 'fakepassword', - otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', - email: 'new10@example.com', - resetPasswordRequests: [], - description: 'I am a new user 10.', - emailNotificationsActivated: true, - emailNotificationsStatus: { expiringNotification: false }, - organisation: 'organisation_system', - deleted: null, - id: 'fakeid10', - createdAt: 1591134065000, - expiredAt: 1991134065000 - }; - await mongoClient.collection(config.database.collections.users_collection).insertOne(newUser); - - /* assertion */ - const getUserRes = await user.post('/graphql').send({ - query: print(GET_USERS), - variables: { userId: newUser.id, fetchDetailsAdminOnly: false, fetchAccessPrivileges: false } - }); - - expect(getUserRes.body.data.getUsers).toEqual([{ - firstname: 'FChan Mei Yi', - lastname: 'LChan Mei Yi', - type: userTypes.STANDARD, - organisation: 'organisation_system', - id: newUser.id - }]); - - - const res = await user.post('/graphql').send( - { - query: print(DELETE_USER), - variables: { - userId: newUser.id - } - } - ); - expect(res.status).toBe(200); - expect(res.body.errors).toHaveLength(1); - expect(res.body.errors[0].message).toBe(errorCodes.NO_PERMISSION_ERROR); - expect(res.body.data.deleteUser).toEqual(null); - - const getUserResAfter = await user.post('/graphql').send({ - query: print(GET_USERS), - variables: { userId: newUser.id, fetchDetailsAdminOnly: false, fetchAccessPrivileges: false } - }); - - expect(getUserResAfter.body.data.getUsers).toEqual([{ - firstname: 'FChan Mei Yi', - lastname: 'LChan Mei Yi', - type: userTypes.STANDARD, - organisation: 'organisation_system', - id: newUser.id - }]); - }); - }); -}); diff --git a/packages/itmat-interface/test/setupTests.ts b/packages/itmat-interface/test/setupTests.ts new file mode 100644 index 000000000..b5e1bfa1b --- /dev/null +++ b/packages/itmat-interface/test/setupTests.ts @@ -0,0 +1,106 @@ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck + + + +// Mock the @simplewebauthn/browser module +jest.mock('@simplewebauthn/browser', () => ({ + startRegistration: jest.fn().mockResolvedValue({ + id: 'mock_id', + rawId: 'mock_rawId', + response: { + clientDataJSON: 'mock_clientDataJSON', + attestationObject: 'mock_attestationObject', + transports: ['internal'] // Ensure transports is included if expected + }, + type: 'public-key', + clientExtensionResults: {} + }), + startAuthentication: jest.fn().mockResolvedValue({ + id: 'mock_id', // Use the same id + rawId: 'mock_rawId', // Use the same rawId as in startRegistration + response: { + clientDataJSON: 'mock_clientDataJSON', + authenticatorData: 'mock_authenticatorData', + signature: 'mock_signature' + }, + type: 'public-key', + clientExtensionResults: {} + }) +})); + +jest.mock('@simplewebauthn/server', () => ({ + verifyRegistrationResponse: jest.fn().mockResolvedValue({ + verified: true, + registrationInfo: { + credentialPublicKey: new Uint8Array([1, 2, 3]), // Mocked public key + credentialID: 'mock_rawId', // Should match the rawId in startRegistration mock + counter: 0 + } + }), + verifyAuthenticationResponse: jest.fn().mockResolvedValue({ + verified: true, + authenticationInfo: { + newCounter: 1 + } + }), + generateRegistrationOptions: jest.fn().mockReturnValue({ + rp: { + name: 'mock_rpName', + id: 'mock_rpID' + }, + user: { + id: new Uint8Array([1, 2, 3, 4]), + name: 'mock_username', + displayName: 'mock_displayName' + }, + challenge: new Uint8Array([1, 2, 3, 4, 5]), + pubKeyCredParams: [ + { type: 'public-key', alg: -7 }, + { type: 'public-key', alg: -257 } + ], + timeout: 60000, + attestation: 'none', + excludeCredentials: [ + { + id: new Uint8Array([1, 2, 3]), + type: 'public-key', + transports: ['internal'] + } + ], + authenticatorSelection: { + residentKey: 'discouraged' + } + }), + generateAuthenticationOptions: jest.fn().mockReturnValue({ + challenge: 'mock_challenge', + rpId: 'mock_rpId', + allowCredentials: [ + { + id: 'mock_credential_id', + type: 'public-key', + transports: ['internal'] + } + ] + }) +})); + + + +import { LxdManager, UserCore } from '@itmat-broker/itmat-cores'; + +// Stub out the LXD client initialization to bypass certificate parsing for tests +jest.spyOn(LxdManager.prototype as LxdManager, 'initializeLXDClient').mockResolvedValue(undefined); +jest.spyOn(UserCore.prototype as UserCore,'initialize').mockResolvedValue(undefined); + +jest.mock('nodemailer', () => { + const { TEST_SMTP_CRED, TEST_SMTP_USERNAME } = process.env; + if (!TEST_SMTP_CRED || !TEST_SMTP_USERNAME || !config?.nodemailer?.auth?.pass || !config?.nodemailer?.auth?.user) + return { + createTransport: jest.fn().mockImplementation(() => ({ + sendMail: jest.fn() + })) + }; + return jest.requireActual('nodemailer'); +}); \ No newline at end of file diff --git a/packages/itmat-interface/test/trpcTests/_loginHelper.ts b/packages/itmat-interface/test/trpcTests/_loginHelper.ts new file mode 100644 index 000000000..78468bfc6 --- /dev/null +++ b/packages/itmat-interface/test/trpcTests/_loginHelper.ts @@ -0,0 +1,42 @@ +import { generateTOTP } from '@itmat-broker/itmat-cores'; +import { SuperTest, Test } from 'supertest'; + +export async function connectAdmin(agent: SuperTest) { + const adminSecret = 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA'; + return connectAgent(agent, 'admin', 'admin', adminSecret); +} + +export async function connectUser(agent: SuperTest) { + const userSecret = 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA'; + return connectAgent(agent, 'standardUser', 'admin', userSecret); +} + +export async function connectAgent(agent: SuperTest, user: string, pw: string, secret: string): Promise { + const otp = generateTOTP(secret).toString(); + try { + const res = await agent.post('/trpc/user.login') + .set('Content-type', 'application/json') + .send({ + username: user, + password: pw, + totp: otp + }); + if (res.status !== 200) { + throw new Error('Login failed'); + } + } catch (__unused__exception) { + throw new Error('Login request failed'); + } +} + +export async function disconnectAgent(agent: SuperTest): Promise { + try { + const res = await agent.post('/trpc/user.logout'); + if (res.status !== 200) { + throw new Error('Logout failed'); + } + } catch (__unused__exception) { + throw new Error('Logout request failed'); + } +} + diff --git a/packages/itmat-interface/test/trpcTests/config.test.ts b/packages/itmat-interface/test/trpcTests/config.test.ts new file mode 100644 index 000000000..a9284190f --- /dev/null +++ b/packages/itmat-interface/test/trpcTests/config.test.ts @@ -0,0 +1,317 @@ +/** + * @with Minio + */ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +import { MongoMemoryServer } from 'mongodb-memory-server'; +import { db } from '../../src/database/database'; +import { Express } from 'express'; +import { objStore } from '../../src/objStore/objStore'; +import request from 'supertest'; +import { connectAdmin, connectUser, connectAgent } from './_loginHelper'; +import { Router } from '../../src/server/router'; +import { MongoClient } from 'mongodb'; +import { setupDatabase } from '@itmat-broker/itmat-setup'; +import config from '../../config/config.sample.json'; +import { v4 as uuid } from 'uuid'; +import { enumUserTypes, enumStudyRoles, enumCoreErrors, enumConfigType } from '@itmat-broker/itmat-types'; +import { encodeQueryParams } from './helper'; +import { seedConfigs } from 'packages/itmat-setup/src/databaseSetup/seed/config'; +import { getRequiredMinioPort } from './types'; + +if (global.hasMinio) { + let app: Express; + let mongodb: MongoMemoryServer; + let admin: request.SuperTest; + let user: request.SuperTest; + let authorisedUser: request.SuperTest; + let mongoConnection: MongoClient; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let mongoClient: Db; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let adminProfile; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let userProfile; + let authorisedUserProfile; + let study; + let fullPermissionRole: IRole; + + afterAll(async () => { + await db.closeConnection(); + await mongoConnection?.close(); + await mongodb.stop(); + + /* claer all mocks */ + jest.clearAllMocks(); + }); + + beforeAll(async () => { // eslint-disable-line no-undef + /* Creating a in-memory MongoDB instance for testing */ + const dbName = uuid(); + mongodb = await MongoMemoryServer.create({ instance: { dbName } }); + const connectionString = mongodb.getUri(); + await setupDatabase(connectionString, dbName); + /* Wiring up the backend server */ + config.objectStore.port = getRequiredMinioPort(); + config.database.mongo_url = connectionString; + config.database.database = dbName; + await db.connect(config.database, MongoClient); + await objStore.connect(config.objectStore); + const router = new Router(config); + await router.init(); + /* Connect mongo client (for test setup later / retrieve info later) */ + mongoConnection = await MongoClient.connect(connectionString); + mongoClient = mongoConnection.db(dbName); + + /* Connecting clients for testing later */ + app = router.getApp(); + admin = request.agent(app); + user = request.agent(app); + await connectAdmin(admin); + await connectUser(user); + + // add the root node for each user + const users = await db.collections.users_collection.find({}).toArray(); + adminProfile = users.filter(el => el.type === enumUserTypes.ADMIN)[0]; + userProfile = users.filter(el => el.type === enumUserTypes.STANDARD)[0]; + + const username = uuid(); + authorisedUserProfile = { + username, + type: enumUserTypes.STANDARD, + firstname: `${username}_firstname`, + lastname: `${username}_lastname`, + password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', + otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', + email: `${username}@example.com`, + description: 'I am a new user.', + emailNotificationsActivated: true, + organisation: 'organisation_system', + id: `new_user_id_${username}`, + emailNotificationsStatus: { expiringNotification: false }, + resetPasswordRequests: [], + expiredAt: 1991134065000, + life: { + createdTime: 1591134065000, + createdUser: 'admin', + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + await mongoClient.collection(config.database.collections.users_collection).insertOne(authorisedUserProfile); + authorisedUser = request.agent(app); + await connectAgent(authorisedUser, username, 'admin', authorisedUserProfile.otpSecret); + }); + + beforeEach(async () => { + study = { + id: uuid(), + name: 'Test Study', + currentDataVersion: -1, // index; dataVersions[currentDataVersion] gives current version; // -1 if no data + dataVersions: [], + description: null, + profile: null, + webLayout: [], + life: { + createdTime: 0, + createdUser: enumUserTypes.SYSTEM, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + await db.collections.studies_collection.insertOne(study); + fullPermissionRole = { + id: 'full_permission_role_id', + studyId: study.id, + name: 'Full Permissison Role', + description: '', + // data permissions for studyId + dataPermissions: [{ + fields: ['^1.*$'], + dataProperties: { + }, + permission: parseInt('111', 2), + includeUnVersioned: true + }], + studyRole: enumStudyRoles.STUDY_MANAGER, + users: [authorisedUserProfile.id], + groups: [], + life: { + createdTime: 0, + createdUser: 'admin', + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + await db.collections.roles_collection.insertOne(fullPermissionRole); + }); + + afterEach(async () => { + await db.collections.studies_collection.deleteMany({}); + await db.collections.field_dictionary_collection.deleteMany({}); + await db.collections.data_collection.deleteMany({}); + await db.collections.roles_collection.deleteMany({}); + await db.collections.files_collection.deleteMany({}); + await db.collections.cache_collection.deleteMany({}); + await db.collections.configs_collection.deleteMany({}); + await db.collections.configs_collection.insertMany(seedConfigs); + }); + + describe('tRPC config APIs', () => { + test('Get system config (admin)', async () => { + const paramteres = { + configType: enumConfigType.SYSTEMCONFIG, + key: null, + useDefault: false + }; + const response = await admin.get('/trpc/config.getConfig?input=' + encodeQueryParams(paramteres)) + .query({ + }); + expect(response.status).toBe(200); + expect(response.body.result.data.id).toBe('root_system_config_protected'); + }); + test('Get system config default setting (admin)', async () => { + const paramteres = { + configType: enumConfigType.SYSTEMCONFIG, + key: null, + useDefault: true + }; + const response = await admin.get('/trpc/config.getConfig?input=' + encodeQueryParams(paramteres)) + .query({ + }); + expect(response.status).toBe(200); + expect(response.body.result.data.properties.defaultMaximumFileSize).toBe(1 * 1024 * 1024 * 1024); + }); + test('Edit system config (admin)', async () => { + const response = await admin.post('/trpc/config.editConfig') + .send({ + configType: enumConfigType.SYSTEMCONFIG, + key: null, + properties: { + id: 'root_system_config_protected', + type: enumConfigType.SYSTEMCONFIG, + key: null, + id: 'root_system_config', + life: { + createdTime: Date.now(), + createdUser: 'SYSTEM', + deletedTime: null, + deletedUser: null + }, + metadata: {}, + defaultBackgroundColor: '#FFFFFF', + defaultMaximumFileSize: 1 * 1024 * 1024 * 1024, // 1 GB + defaultFileBucketId: 'system', + defaultProfileBucketId: 'profile', + logoLink: null, + logoSize: ['24px', '24px'], + archiveAddress: '', + defaultEventTimeConsumptionBar: [50, 100], + defaultUserExpireDays: 120 + } + }); + expect(response.status).toBe(200); + expect(response.body.result.data.successful).toBe(true); + const config = await db.collections.configs_collection.findOne({ 'type': enumConfigType.SYSTEMCONFIG, 'key': null, 'life.deletedTime': null }); + expect(config).toBeDefined(); + expect(config.properties.defaultUserExpireDays).toBe(120); + }); + test('Edit system config (user)', async () => { + const response = await user.post('/trpc/config.editConfig') + .send({ + configType: enumConfigType.SYSTEMCONFIG, + key: null, + properties: { + id: 'root_system_config_protected', + type: enumConfigType.SYSTEMCONFIG, + key: null, + id: 'root_system_config', + life: { + createdTime: Date.now(), + createdUser: 'SYSTEM', + deletedTime: null, + deletedUser: null + }, + metadata: {}, + defaultBackgroundColor: '#FFFFFF', + defaultMaximumFileSize: 1 * 1024 * 1024 * 1024, // 1 GB + defaultFileBucketId: 'system', + defaultProfileBucketId: 'profile', + logoLink: null, + logoSize: ['24px', '24px'], + archiveAddress: '', + defaultEventTimeConsumptionBar: [50, 100], + defaultUserExpireDays: 120 + } + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('Edit system config (mismatch)', async () => { + const response = await admin.post('/trpc/config.editConfig') + .send({ + configType: enumConfigType.STUDYCONFIG, + key: null, + properties: { + id: 'root_system_config_protected', + type: enumConfigType.SYSTEMCONFIG, + key: null, + id: 'root_system_config', + life: { + createdTime: Date.now(), + createdUser: 'SYSTEM', + deletedTime: null, + deletedUser: null + }, + metadata: {}, + defaultBackgroundColor: '#FFFFFF', + defaultMaximumFileSize: 1 * 1024 * 1024 * 1024, // 1 GB + defaultFileBucketId: 'system', + defaultProfileBucketId: 'profile', + logoLink: null, + logoSize: ['24px', '24px'], + archiveAddress: '', + defaultEventTimeConsumptionBar: [50, 100], + defaultUserExpireDays: 120 + } + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Config type and properties do not match.'); + }); + test('Edit system config (not existing)', async () => { + const response = await admin.post('/trpc/config.editConfig') + .send({ + configType: enumConfigType.STUDYCONFIG, + key: 'random', + properties: { + id: 'root_system_config_protected', + type: enumConfigType.STUDYCONFIG, + key: 'random', + id: 'root_system_config', + life: { + createdTime: Date.now(), + createdUser: 'SYSTEM', + deletedTime: null, + deletedUser: null + }, + defaultFileBucketId: 'system', + metadata: {}, + defaultStudyProfile: null, + defaultMaximumFileSize: 8 * 1024 * 1024 * 1024, // 8 GB, + defaultRepresentationForMissingValue: '99999', + defaultFileBlocks: [], + defaultVersioningKeys: [] + } + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Config does not exist.'); + }); + }); +} else { + test(`${__filename.split(/[\\/]/).pop()} skipped because it requires Minio on Docker`, () => { + expect(true).toBe(true); + }); +} diff --git a/packages/itmat-interface/test/trpcTests/data.test.ts b/packages/itmat-interface/test/trpcTests/data.test.ts new file mode 100644 index 000000000..942a56d13 --- /dev/null +++ b/packages/itmat-interface/test/trpcTests/data.test.ts @@ -0,0 +1,2868 @@ +/** + * @with Minio + */ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +import { MongoMemoryServer } from 'mongodb-memory-server'; +import { db } from '../../src/database/database'; +import { Express } from 'express'; +import { objStore } from '../../src/objStore/objStore'; +import request from 'supertest'; +import { connectAdmin, connectUser, connectAgent } from './_loginHelper'; +import { Router } from '../../src/server/router'; +import { MongoClient } from 'mongodb'; +import { setupDatabase } from '@itmat-broker/itmat-setup'; +import config from '../../config/config.sample.json'; +import { v4 as uuid } from 'uuid'; +import { enumUserTypes, enumStudyRoles, enumDataTypes, enumASTNodeTypes, enumConditionOps, enumConfigType, enumCoreErrors } from '@itmat-broker/itmat-types'; +import { encodeQueryParams } from './helper'; +import path from 'path'; +import { getRequiredMinioPort } from './types'; + +if (global.hasMinio) { + let app: Express; + let mongodb: MongoMemoryServer; + let admin: request.SuperTest; + let user: request.SuperTest; + let authorisedUser: request.SuperTest; + let mongoConnection: MongoClient; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let mongoClient: Db; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let adminProfile; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let userProfile; + let authorisedUserProfile; + let study; + let fullPermissionRole: IRole; + + afterAll(async () => { + await db.closeConnection(); + await mongoConnection?.close(); + await mongodb.stop(); + + /* claer all mocks */ + jest.clearAllMocks(); + }); + + beforeAll(async () => { // eslint-disable-line no-undef + /* Creating a in-memory MongoDB instance for testing */ + const dbName = uuid(); + mongodb = await MongoMemoryServer.create({ instance: { dbName } }); + const connectionString = mongodb.getUri(); + await setupDatabase(connectionString, dbName); + /* Wiring up the backend server */ + config.objectStore.port = getRequiredMinioPort(); + config.database.mongo_url = connectionString; + config.database.database = dbName; + await db.connect(config.database, MongoClient); + await objStore.connect(config.objectStore); + const router = new Router(config); + await router.init(); + /* Connect mongo client (for test setup later / retrieve info later) */ + mongoConnection = await MongoClient.connect(connectionString); + mongoClient = mongoConnection.db(dbName); + + /* Connecting clients for testing later */ + app = router.getApp(); + admin = request.agent(app); + user = request.agent(app); + await connectAdmin(admin); + await connectUser(user); + + // add the root node for each user + const users = await db.collections.users_collection.find({}).toArray(); + adminProfile = users.filter(el => el.type === enumUserTypes.ADMIN)[0]; + userProfile = users.filter(el => el.type === enumUserTypes.STANDARD)[0]; + + const username = uuid(); + authorisedUserProfile = { + username, + type: enumUserTypes.STANDARD, + firstname: `${username}_firstname`, + lastname: `${username}_lastname`, + password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', + otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', + email: `${username}@example.com`, + description: 'I am a new user.', + emailNotificationsActivated: true, + organisation: 'organisation_system', + id: `new_user_id_${username}`, + emailNotificationsStatus: { expiringNotification: false }, + resetPasswordRequests: [], + expiredAt: 1991134065000, + life: { + createdTime: 1591134065000, + createdUser: 'admin', + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + await mongoClient.collection(config.database.collections.users_collection).insertOne(authorisedUserProfile); + authorisedUser = request.agent(app); + await connectAgent(authorisedUser, username, 'admin', authorisedUserProfile.otpSecret); + }); + + beforeEach(async () => { + study = { + id: uuid(), + name: 'Test Study', + currentDataVersion: -1, // index; dataVersions[currentDataVersion] gives current version; // -1 if no data + dataVersions: [], + description: null, + profile: null, + webLayout: [], + life: { + createdTime: 0, + createdUser: enumUserTypes.SYSTEM, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + await db.collections.configs_collection.insertOne({ + id: uuid(), + type: enumConfigType.STUDYCONFIG, + key: study.id, + properties: { + id: uuid(), + life: { + createdTime: 1648060684215, + createdUser: '8a51bda7-64b8-46af-b087-43caad743a81', + deletedTime: null, + deletedUser: null + }, + metadata: { + + }, + defaultStudyProfile: null, + defaultMaximumFileSize: 8589934592, + defaultMaximumProfileSize: 10485760, + defaultRepresentationForMissingValue: '99999', + defaultFileColumns: [ + { + title: 'Participant ID', + type: 'string' + }, + { + title: 'Device ID', + type: 'string' + }, + { + title: 'Device Type', + type: 'string' + }, + { + title: 'Start Date', + type: 'UNIX timestamps' + }, + { + title: 'End Date', + type: 'UNIX timestamps' + } + ], + defaultFileColumnsPropertyColor: 'orange', + defaultFileDirectoryStructure: { + pathLabels: [ + 'SubjectId', + 'VisitId' + ], + description: null + }, + defaultVersioningKeys: ['fieldId', 'properties.SubjectId', 'properties.VisitId'] + } + }); + await db.collections.studies_collection.insertOne(study); + fullPermissionRole = { + id: 'full_permission_role_id', + studyId: study.id, + name: 'Full Permissison Role', + description: '', + // data permissions for studyId + dataPermissions: [{ + fields: ['^1.*$'], + dataProperties: { + }, + permission: parseInt('111', 2), + includeUnVersioned: true + }], + studyRole: enumStudyRoles.STUDY_MANAGER, + users: [authorisedUserProfile.id], + groups: [], + life: { + createdTime: 0, + createdUser: 'admin', + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + await db.collections.roles_collection.insertOne(fullPermissionRole); + }); + + afterEach(async () => { + await db.collections.studies_collection.deleteMany({}); + await db.collections.field_dictionary_collection.deleteMany({}); + await db.collections.data_collection.deleteMany({}); + await db.collections.roles_collection.deleteMany({}); + await db.collections.files_collection.deleteMany({}); + await db.collections.cache_collection.deleteMany({}); + await db.collections.configs_collection.deleteMany({}); + }); + + describe('tRPC data APIs', () => { + test('Create a field', async () => { + const response = await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER, + verifier: [[{ + formula: { + type: enumASTNodeTypes.SELF, + operator: null, + value: null, + parameters: {}, + children: [] + }, + condition: enumConditionOps.NUMERICALGREATERTHAN, + value: 10, + parameters: {} + }, { + formula: { + type: enumASTNodeTypes.SELF, + operator: null, + value: null, + parameters: {}, + children: [] + }, + condition: enumConditionOps.NUMERICALLESSTHAN, + value: 50, + parameters: {} + }]], + properties: [{ + name: 'SubjectId', + verifier: [[{ + formula: { + type: enumASTNodeTypes.SELF, + operator: null, + value: null, + parameters: {}, + children: [] + }, + condition: enumConditionOps.STRINGREGEXMATCH, + value: '^I.*$', + parameters: {} + }]], + required: true + }] + }); + expect(response.status).toBe(200); + expect(response.body.result.data.fieldId).toBe('1'); + const field = await db.collections.field_dictionary_collection.findOne({ 'studyId': study.id, 'fieldId': '1', 'life.deletedTime': null }); + expect(field).toBeDefined(); + expect(field?.fieldId).toBe('1'); + }); + test('Create a field (study does not exist)', async () => { + const response = await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: 'random', + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + const field = await db.collections.field_dictionary_collection.findOne({ 'studyId': study.id, 'fieldId': '1', 'life.deletedTime': null }); + expect(field).toBe(null); + }); + test('Create a field (no permission)', async () => { + const response = await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '2', + description: '', + dataType: enumDataTypes.INTEGER + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('No permission to create this field.'); + const field = await db.collections.field_dictionary_collection.findOne({ 'studyId': study.id, 'fieldId': '1', 'life.deletedTime': null }); + expect(field).toBe(null); + }); + test('Create a field (multiple times)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + const response = await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + expect(response.status).toBe(200); + const field = await db.collections.field_dictionary_collection.find({ 'studyId': study.id, 'fieldId': '1', 'life.deletedTime': null }).toArray(); + expect(field).toHaveLength(2); + }); + test('Create a field (undefined categorical options for categorical field)', async () => { + const response = await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.CATEGORICAL + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('1-Test Field 1: possible values can\'t be empty if data type is categorical.'); + const field = await db.collections.field_dictionary_collection.find({ 'studyId': study.id, 'fieldId': '1', 'life.deletedTime': null }).toArray(); + expect(field).toHaveLength(0); + }); + test('Create a field (unauthorised user)', async () => { + const response = await user.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.CATEGORICAL + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('Create a field (authorised user but no data permission)', async () => { + const response = await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '2', + description: '', + dataType: enumDataTypes.CATEGORICAL + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('No permission to create this field.'); + }); + test('Get fields (unversioned)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + const paramteres = { + studyId: study.id, + versionId: null + }; + const response = await authorisedUser.get('/trpc/data.getStudyFields?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(200); + expect(response.body.result.data).toHaveLength(1); + expect(response.body.result.data[0].fieldId).toBe('1'); + }); + test('Get fields (unauthorised user)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + const paramteres = { + studyId: study.id, + versionId: null + }; + const response = await user.get('/trpc/data.getStudyFields?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('Get fields (authorised user on unauthorised fields)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + await db.collections.field_dictionary_collection.insertOne({ + studyId: study.id, + fieldId: '2', + fieldName: 'Test Field 2', + description: '', + dataType: enumDataTypes.INTEGER, + life: { + createdTime: 0, + createdUser: enumUserTypes.SYSTEM, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }); + const paramteres = { + studyId: study.id, + versionId: null + }; + const response = await authorisedUser.get('/trpc/data.getStudyFields?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(200); + expect(response.body.result.data).toHaveLength(1); + expect(response.body.result.data[0].fieldId).toBe('1'); + }); + test('Get fields (versioned)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + await db.collections.roles_collection.updateMany({}, { + $set: { + studyRole: enumStudyRoles.STUDY_MANAGER + } + }); + await authorisedUser.post('/trpc/study.createDataVersion') + .send({ + studyId: study.id, + dataVersion: '1.0', + tag: '1.0' + }); + const paramteres = { + studyId: study.id + }; + const response = await authorisedUser.get('/trpc/data.getStudyFields?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(200); + expect(response.body.result.data).toHaveLength(1); + expect(response.body.result.data[0].fieldId).toBe('1'); + }); + test('Delete a field', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + const response = await authorisedUser.post('/trpc/data.deleteStudyField') + .send({ + studyId: study.id, + fieldId: '1' + }); + expect(response.status).toBe(200); + expect(response.body.result.data.id).toBe('1'); + const field = await db.collections.field_dictionary_collection.find({ studyId: study.id, fieldId: '1' }).toArray(); + expect(field).toHaveLength(2); + const paramteres = { + studyId: study.id, + versionId: null + }; + const response2 = await authorisedUser.get('/trpc/data.getStudyFields?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response2.body.result.data).toHaveLength(0); + }); + test('Delete a field (unauthorised user)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + const response = await user.post('/trpc/data.deleteStudyField') + .send({ + studyId: study.id, + fieldId: '1' + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('Delete a field (authorised user but unauthorised field)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + await db.collections.field_dictionary_collection.insertOne({ + studyId: study.id, + fieldId: '2', + fieldName: 'Test Field 2', + description: '', + dataType: enumDataTypes.INTEGER, + life: { + createdTime: 0, + createdUser: enumUserTypes.SYSTEM, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }); + const response = await authorisedUser.post('/trpc/data.deleteStudyField') + .send({ + studyId: study.id, + fieldId: '2' + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('No permission to delete this field.'); + }); + test('Delete a field (study does not exist)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + const response = await authorisedUser.post('/trpc/data.deleteStudyField') + .send({ + studyId: 'random', + fieldId: '1' + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + const fields = await db.collections.field_dictionary_collection.find({ fieldId: '1' }).toArray(); + expect(fields).toHaveLength(1); + }); + test('Delete a field (field does not exist)', async () => { + const response = await authorisedUser.post('/trpc/data.deleteStudyField') + .send({ + studyId: study.id, + fieldId: '1' + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Field does not exist.'); + const fields = await db.collections.field_dictionary_collection.find({ fieldId: '1' }).toArray(); + expect(fields).toHaveLength(0); + }); + test('Upload a data clip (integer)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + const respone = await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '10' + }] + }); + expect(respone.status).toBe(200); + expect(respone.body.result.data[0].successful).toBe(true); + const data = await db.collections.data_collection.find({ studyId: study.id }).toArray(); + expect(data).toHaveLength(1); + expect(data[0].fieldId).toBe('1'); + }); + test('Upload a data clip (decimal)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.DECIMAL + }); + const respone = await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '1.1' + }] + }); + expect(respone.status).toBe(200); + expect(respone.body.result.data[0].successful).toBe(true); + const data = await db.collections.data_collection.find({ studyId: study.id }).toArray(); + expect(data).toHaveLength(1); + expect(data[0].fieldId).toBe('1'); + }); + test('Upload a data clip (boolean)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.BOOLEAN + }); + const respone = await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: 'true' + }] + }); + expect(respone.status).toBe(200); + expect(respone.body.result.data[0].successful).toBe(true); + const data = await db.collections.data_collection.find({ studyId: study.id }).toArray(); + expect(data).toHaveLength(1); + expect(data[0].fieldId).toBe('1'); + }); + test('Upload a data clip (date)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.DATETIME + }); + const respone = await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '2023-12-06T17:28:32.397Z' + }] + }); + expect(respone.status).toBe(200); + expect(respone.body.result.data[0].successful).toBe(true); + const data = await db.collections.data_collection.find({ studyId: study.id }).toArray(); + expect(data).toHaveLength(1); + expect(data[0].fieldId).toBe('1'); + }); + test('Upload a data clip (categorical)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.CATEGORICAL, + categoricalOptions: [ + { code: '1', description: '1' }, + { code: '2', description: '2' } + ] + }); + const respone = await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '1' + }] + }); + expect(respone.status).toBe(200); + expect(respone.body.result.data[0].successful).toBe(true); + const data = await db.collections.data_collection.find({ studyId: study.id }).toArray(); + expect(data).toHaveLength(1); + expect(data[0].fieldId).toBe('1'); + }); + test('Upload a data clip (study does not exist)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + const respone = await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: 'random', + data: [{ + fieldId: '1', + value: '10' + }] + }); + expect(respone.status).toBe(400); + expect(respone.body.error.message).toBe('Study does not exist.'); + const data = await db.collections.data_collection.find({ studyId: study.id }).toArray(); + expect(data).toHaveLength(0); + }); + test('Upload a data clip (unauthorised user)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + const respone = await user.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '2', + value: '10' + }] + }); + expect(respone.status).toBe(400); + expect(respone.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('Upload a data clip (authorised user with unauthorised field)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + await db.collections.field_dictionary_collection.insertOne({ + studyId: study.id, + fieldId: '2', + fieldName: 'Test Field 2', + description: '', + dataType: enumDataTypes.INTEGER, + life: { + createdTime: 0, + createdUser: enumUserTypes.SYSTEM, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }); + const respone = await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '2', + value: '10' + }] + }); + expect(respone.status).toBe(200); + expect(respone.body.result.data[0].description).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('Upload a data clip (field does not exist)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + const respone = await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '11', + value: '10' + }] + }); + expect(respone.status).toBe(200); + expect(respone.body.result.data[0].successful).toBe(false); + expect(respone.body.result.data[0].description).toBe('Field 11: Field not found'); + const data = await db.collections.data_collection.find({ studyId: study.id }).toArray(); + expect(data).toHaveLength(0); + }); + test('Upload a data clip (value is invalid as integer)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + const respone = await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: 'random' + }] + }); + expect(respone.status).toBe(200); + expect(respone.body.result.data[0].successful).toBe(false); + expect(respone.body.result.data[0].description).toBe('Field 1: Cannot parse as integer.'); + const data = await db.collections.data_collection.find({ studyId: study.id }).toArray(); + expect(data).toHaveLength(0); + }); + test('Upload a data clip (value is invalid as decimal)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.DECIMAL + }); + const respone = await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: 'random' + }] + }); + expect(respone.status).toBe(200); + expect(respone.body.result.data[0].successful).toBe(false); + expect(respone.body.result.data[0].description).toBe('Field 1: Cannot parse as decimal.'); + const data = await db.collections.data_collection.find({ studyId: study.id }).toArray(); + expect(data).toHaveLength(0); + }); + test('Upload a data clip (value is invalid as boolean)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.BOOLEAN + }); + const respone = await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: 'random' + }] + }); + expect(respone.status).toBe(200); + expect(respone.body.result.data[0].successful).toBe(false); + expect(respone.body.result.data[0].description).toBe('Field 1: Cannot parse as boolean.'); + const data = await db.collections.data_collection.find({ studyId: study.id }).toArray(); + expect(data).toHaveLength(0); + }); + test('Upload a data clip (value is invalid as date)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.DATETIME + }); + const respone = await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: 'random' + }] + }); + expect(respone.status).toBe(200); + expect(respone.body.result.data[0].successful).toBe(false); + expect(respone.body.result.data[0].description).toBe('Field 1: Cannot parse as date. Value for date type must be in ISO format.'); + const data = await db.collections.data_collection.find({ studyId: study.id }).toArray(); + expect(data).toHaveLength(0); + }); + test('Upload a data clip (value is invalid as categorical)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.CATEGORICAL, + categoricalOptions: [ + { code: '1', description: '1' }, + { code: '2', description: '2' } + ] + }); + const respone = await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: 'random' + }] + }); + expect(respone.status).toBe(200); + expect(respone.body.result.data[0].successful).toBe(false); + expect(respone.body.result.data[0].description).toBe('Field 1: Cannot parse as categorical, value not in value list.'); + const data = await db.collections.data_collection.find({ studyId: study.id }).toArray(); + expect(data).toHaveLength(0); + }); + test('Upload a data clip (duplicate data clips)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '1' + }] + }); + const respone = await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '1' + }] + }); + expect(respone.status).toBe(200); + expect(respone.body.result.data[0].successful).toBe(true); + const data = await db.collections.data_collection.find({ studyId: study.id }).toArray(); + expect(data).toHaveLength(2); + expect(data[0].fieldId).toBe(data[1].fieldId); + }); + test('Upload a data clip (pass the verifier)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER, + verifier: [[{ + formula: { + type: enumASTNodeTypes.SELF, + operator: null, + value: null, + parameters: {}, + children: [] + }, + condition: enumConditionOps.NUMERICALGREATERTHAN, + value: 10, + parameters: {} + }, { + formula: { + type: enumASTNodeTypes.SELF, + operator: null, + value: null, + parameters: {}, + children: [] + }, + condition: enumConditionOps.NUMERICALLESSTHAN, + value: 50, + parameters: {} + }]] + }); + const respone = await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '25' + }] + }); + expect(respone.status).toBe(200); + expect(respone.body.result.data[0].successful).toBe(true); + const data = await db.collections.data_collection.find({ studyId: study.id }).toArray(); + expect(data).toHaveLength(1); + }); + test('Upload a data clip (failed the verifier)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER, + verifier: [[{ + formula: { + type: enumASTNodeTypes.SELF, + operator: null, + value: null, + parameters: {}, + children: [] + }, + condition: enumConditionOps.NUMERICALGREATERTHAN, + value: 10, + parameters: {} + }, { + formula: { + type: enumASTNodeTypes.SELF, + operator: null, + value: null, + parameters: {}, + children: [] + }, + condition: enumConditionOps.NUMERICALLESSTHAN, + value: 50, + parameters: {} + }]] + }); + const respone = await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '5' + }] + }); + expect(respone.status).toBe(200); + expect(respone.body.result.data[0].successful).toBe(false); + expect(respone.body.result.data[0].description).toBe('Field 1 value 5: Failed to pass the verifier.'); + const data = await db.collections.data_collection.find({ studyId: study.id }).toArray(); + expect(data).toHaveLength(0); + }); + test('Upload a data clip (pass the property verifier)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER, + verifier: [[{ + formula: { + type: enumASTNodeTypes.SELF, + operator: null, + value: null, + parameters: {}, + children: [] + }, + condition: enumConditionOps.NUMERICALGREATERTHAN, + value: 10, + parameters: {} + }, { + formula: { + type: enumASTNodeTypes.SELF, + operator: null, + value: null, + parameters: {}, + children: [] + }, + condition: enumConditionOps.NUMERICALLESSTHAN, + value: 50, + parameters: {} + }]], + properties: [{ + name: 'SubjectId', + verifier: [[{ + formula: { + type: enumASTNodeTypes.SELF, + operator: null, + value: null, + parameters: {}, + children: [] + }, + condition: enumConditionOps.STRINGREGEXMATCH, + value: '^I.*$', + parameters: {} + }]], + required: true + }] + }); + const respone = await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '25', + properties: { + SubjectId: 'I7' + } + }] + }); + expect(respone.status).toBe(200); + expect(respone.body.result.data[0].successful).toBe(true); + const data = await db.collections.data_collection.find({ studyId: study.id }).toArray(); + expect(data).toHaveLength(1); + }); + test('Upload a data clip (failed the property verifier, required not satisfy)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER, + verifier: [[{ + formula: { + type: enumASTNodeTypes.SELF, + operator: null, + value: null, + parameters: {}, + children: [] + }, + condition: enumConditionOps.NUMERICALGREATERTHAN, + value: 10, + parameters: {} + }, { + formula: { + type: enumASTNodeTypes.SELF, + operator: null, + value: null, + parameters: {}, + children: [] + }, + condition: enumConditionOps.NUMERICALLESSTHAN, + value: 50, + parameters: {} + }]], + properties: [{ + name: 'SubjectId', + verifier: [[{ + formula: { + type: enumASTNodeTypes.SELF, + operator: null, + value: null, + parameters: {}, + children: [] + }, + condition: enumConditionOps.STRINGREGEXMATCH, + value: '^I.*$', + parameters: {} + }]], + required: true + }] + }); + const respone = await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '25' + }], + properties: { + random: 'random' + } + }); + expect(respone.status).toBe(200); + expect(respone.body.result.data[0].successful).toBe(false); + expect(respone.body.result.data[0].description).toBe('Field 1: Property SubjectId is required.'); + const data = await db.collections.data_collection.find({ studyId: study.id }).toArray(); + expect(data).toHaveLength(0); + //Field 1 value 25: Property SubjectId failed to pass the verifier. + }); + test('Upload a data clip (failed the property verifier, value not satisfy)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER, + verifier: [[{ + formula: { + type: enumASTNodeTypes.SELF, + operator: null, + value: null, + parameters: {}, + children: [] + }, + condition: enumConditionOps.NUMERICALGREATERTHAN, + value: 10, + parameters: {} + }, { + formula: { + type: enumASTNodeTypes.SELF, + operator: null, + value: null, + parameters: {}, + children: [] + }, + condition: enumConditionOps.NUMERICALLESSTHAN, + value: 50, + parameters: {} + }]], + properties: [{ + name: 'SubjectId', + verifier: [[{ + formula: { + type: enumASTNodeTypes.SELF, + operator: null, + value: null, + parameters: {}, + children: [] + }, + condition: enumConditionOps.STRINGREGEXMATCH, + value: '^I.*$', + parameters: {} + }]], + required: true + }] + }); + const respone = await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '25', + properties: { + SubjectId: 'K7' + } + }] + }); + expect(respone.status).toBe(200); + expect(respone.body.result.data[0].successful).toBe(false); + expect(respone.body.result.data[0].description).toBe('Field 1 value K7: Property SubjectId failed to pass the verifier.'); + const data = await db.collections.data_collection.find({ studyId: study.id }).toArray(); + expect(data).toHaveLength(0); + }); + test('Delete a data clip', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '10' + }] + }); + const response2 = await authorisedUser.post('/trpc/data.deleteStudyData') + .send({ + studyId: study.id, + fieldId: '1' + }); + expect(response2.status).toBe(200); + expect(response2.body.result.data.successful).toBe(true); + const data = await db.collections.data_collection.find({ studyId: study.id }).toArray(); + expect(data).toHaveLength(2); + expect(data[0].life.deletedTime).toBeNull(); + expect(data[1].life.deletedTime).not.toBeNull(); + }); + test('Delete a data clip (study does not exist)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '10' + }] + }); + const response2 = await authorisedUser.post('/trpc/data.deleteStudyData') + .send({ + studyId: 'random', + fieldId: '1' + }); + expect(response2.status).toBe(400); + expect(response2.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('Delete a data clip (delete twice)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '10' + }] + }); + await authorisedUser.post('/trpc/data.deleteStudyData') + .send({ + studyId: study.id, + fieldId: '1' + }); + const response2 = await authorisedUser.post('/trpc/data.deleteStudyData') + .send({ + studyId: study.id, + fieldId: '1' + }); + expect(response2.status).toBe(200); + const data = await db.collections.data_collection.find({ studyId: study.id, fieldId: '1' }, { sort: { 'life.createdTime': -1 } }).toArray(); + expect(data).toHaveLength(3); + expect(data[2].value).toBe(10); + expect(data[0].value).toBeNull(); + expect(data[1].value).toBeNull(); + }); + test('Upload a file data', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.FILE + }); + const filePath = path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt'); + const request = authorisedUser.post('/trpc/data.uploadStudyFileData') + .attach('file', filePath) + .field('studyId', study.id) + .field('fieldId', '1') + .field('properties', JSON.stringify({ + SubjectId: '1A', + VisitId: '1' + })); + const response = await request; + expect(response.status).toBe(200); + const fileObj = await db.collections.files_collection.findOne({}); + const dataObj = await db.collections.data_collection.findOne({}); + expect(response.body.result.data.id).toBe(fileObj.id); + expect(dataObj?.value).toBe(fileObj.id); + + }); + test('Upload a file data (study does not exist)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.FILE + }); + const filePath = path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt'); + const request = authorisedUser.post('/trpc/data.uploadStudyFileData') + .attach('file', filePath) + .field('studyId', 'random') + .field('fieldId', '1'); + const response = await request; + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('Upload a file data (field does not exist)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.FILE + }); + const filePath = path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt'); + const request = authorisedUser.post('/trpc/data.uploadStudyFileData') + .attach('file', filePath) + .field('studyId', study.id) + .field('fieldId', 'random'); + const response = await request; + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('Upload a file data (file format not supported)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.FILE + }); + const filePath = path.join(__dirname, '../filesForTests/RandomFile.random'); + const request = authorisedUser.post('/trpc/data.uploadStudyFileData') + .attach('file', filePath) + .field('studyId', study.id) + .field('fieldId', '1'); + const response = await request; + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('File type RANDOM not supported.'); + }); + test('Upload a file data (with properties)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.FILE, + properties: [{ + name: 'SubjectId', + verifier: [[{ + formula: { + type: enumASTNodeTypes.SELF, + operator: null, + value: null, + parameters: {}, + children: [] + }, + condition: enumConditionOps.STRINGREGEXMATCH, + value: '^I.*$', + parameters: {} + }]], + required: true + }] + }); + const filePath = path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt'); + const request = authorisedUser.post('/trpc/data.uploadStudyFileData') + .attach('file', filePath) + .field('studyId', study.id) + .field('fieldId', '1') + .field('properties', JSON.stringify({ + SubjectId: 'I7' + })); + const response = await request; + expect(response.status).toBe(200); + const fileObj = await db.collections.files_collection.findOne({}); + const dataObj = await db.collections.data_collection.findOne({}); + expect(response.body.result.data.id).toBe(fileObj.id); + expect(dataObj?.value).toBe(fileObj.id); + }); + test('Upload a file data (missing properties)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.FILE, + properties: [{ + name: 'SubjectId', + verifier: [[{ + formula: { + type: enumASTNodeTypes.SELF, + operator: null, + value: null, + parameters: {}, + children: [] + }, + condition: enumConditionOps.STRINGREGEXMATCH, + value: '^I.*$', + parameters: {} + }]], + required: true + }] + }); + const filePath = path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt'); + const request = authorisedUser.post('/trpc/data.uploadStudyFileData') + .attach('file', filePath) + .field('studyId', study.id) + .field('fieldId', '1'); + const response = await request; + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Field 1: Property SubjectId is required.'); + }); + test('Upload a file data (incorrect properties)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.FILE, + properties: [{ + name: 'SubjectId', + verifier: [[{ + formula: { + type: enumASTNodeTypes.SELF, + operator: null, + value: null, + parameters: {}, + children: [] + }, + condition: enumConditionOps.STRINGREGEXMATCH, + value: '^I.*$', + parameters: {} + }]], + required: true + }] + }); + const filePath = path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt'); + const request = authorisedUser.post('/trpc/data.uploadStudyFileData') + .attach('file', filePath) + .field('studyId', study.id) + .field('fieldId', '1') + .field('properties', JSON.stringify({ + SubjectId: 'K7' + })); + const response = await request; + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Field 1 value K7: Property SubjectId failed to pass the verifier.'); + }); + test('Get data', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '10', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }] + }); + const paramteres = { + studyId: study.id, + versionId: null, + aggregation: undefined, + fieldIds: undefined, + useCache: undefined, + forceUpdate: undefined + }; + const response2 = await authorisedUser.get('/trpc/data.getStudyData?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response2.status).toBe(200); + expect(response2.body.result.data).toHaveLength(1); + expect(response2.body.result.data[0].fieldId).toBe('1'); + }); + test('Get data (aggregation with group)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 11', + fieldId: '11', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '1', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }, { + fieldId: '11', + value: '2', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }, { + fieldId: '1', + value: '11', + properties: { + SubjectId: '2B', + VisitId: '1' + } + }, { + fieldId: '11', + value: '22', + properties: { + SubjectId: '2B', + VisitId: '1' + } + }] + }); + const paramteres = { + studyId: study.id, + versionId: null, + aggregation: { + clinical: [{ + operationName: 'GROUP', + params: { + keys: [ + 'properties.SubjectId' + ], + skipUnmatch: false + } + }] + }, + fieldIds: undefined, + useCache: undefined, + forceUpdate: undefined + }; + const response = await authorisedUser.get('/trpc/data.getStudyData?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(200); + expect(response.body.result.data.clinical).toHaveLength(2); + expect(response.body.result.data.clinical[0]).toHaveLength(2); + expect(response.body.result.data.clinical[1]).toHaveLength(2); + expect(response.body.result.data.clinical[0][0].properties.SubjectId).toBe(response.body.result.data.clinical[0][1].properties.SubjectId); + expect(response.body.result.data.clinical[1][0].properties.SubjectId).toBe(response.body.result.data.clinical[1][1].properties.SubjectId); + }); + test('Get data (aggregation with affine)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 11', + fieldId: '11', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '1', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }, { + fieldId: '11', + value: '2', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }, { + fieldId: '1', + value: '11', + properties: { + SubjectId: '2B', + VisitId: '1' + } + }, { + fieldId: '11', + value: '22', + properties: { + SubjectId: '2B', + VisitId: '1' + } + }] + }); + const paramteres = { + studyId: study.id, + versionId: null, + aggregation: { + clinical: [{ + operationName: 'AFFINE', + params: { + removedKeys: [ + '_id', + 'id', + 'studyId', + 'dataVersion', + 'life', + 'metadata' + ], + addedKeyRules: [{ + key: { + type: enumASTNodeTypes.VALUE, + operator: null, + value: 'randomKey', + parameters: {}, + children: null + }, + value: { + type: enumASTNodeTypes.VALUE, + operator: null, + value: 'randomValue', + parameters: {}, + children: null + } + }], + rules: {} + } + }] + }, + fieldIds: undefined, + useCache: undefined, + forceUpdate: undefined + }; + const response = await authorisedUser.get('/trpc/data.getStudyData?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(200); + expect(Object.keys(response.body.result.data.clinical[0])).toHaveLength(4); + expect(Object.keys(response.body.result.data.clinical[1])).toHaveLength(4); + expect(Object.keys(response.body.result.data.clinical[2])).toHaveLength(4); + expect(Object.keys(response.body.result.data.clinical[3])).toHaveLength(4); + expect(response.body.result.data.clinical[0].randomKey).toBe('randomValue'); + expect(response.body.result.data.clinical[1].randomKey).toBe('randomValue'); + expect(response.body.result.data.clinical[2].randomKey).toBe('randomValue'); + expect(response.body.result.data.clinical[3].randomKey).toBe('randomValue'); + }); + test('Get data (aggregation with leaveOne)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 11', + fieldId: '11', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '1', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }, { + fieldId: '11', + value: '2', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }, { + fieldId: '1', + value: '11', + properties: { + SubjectId: '2B', + VisitId: '1' + } + }, { + fieldId: '11', + value: '22', + properties: { + SubjectId: '2B', + VisitId: '1' + } + }] + }); + const paramteres = { + studyId: study.id, + versionId: null, + aggregation: { + clinical: [{ + operationName: 'GROUP', + params: { + keys: [ + 'properties.SubjectId' + ], + skipUnmatch: false + } + }, { + operationName: 'LEAVEONE', + params: { + scoreFormula: { + operator: null, + type: 'VARIABLE', + value: 'life.createdTime', + children: null, + parameters: {} + }, + isDescend: true + } + }] + }, + fieldIds: undefined, + useCache: undefined, + forceUpdate: undefined + }; + const response = await authorisedUser.get('/trpc/data.getStudyData?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(200); + expect(response.body.result.data.clinical).toHaveLength(2); + }); + test('Get data (aggregation with concat)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 11', + fieldId: '11', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '1', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }, { + fieldId: '11', + value: '2', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }, { + fieldId: '1', + value: '11', + properties: { + SubjectId: '2B', + VisitId: '1' + } + }, { + fieldId: '11', + value: '22', + properties: { + SubjectId: '2B', + VisitId: '1' + } + }] + }); + const paramteres = { + studyId: study.id, + versionId: null, + aggregation: { + clinical: [{ + operationName: 'GROUP', + params: { + keys: [ + 'properties.SubjectId' + ], + skipUnmatch: false + } + }, { + operationName: 'CONCAT', + params: { + concatKeys: ['value'] + } + }] + }, + fieldIds: undefined, + useCache: undefined, + forceUpdate: undefined + }; + const response = await authorisedUser.get('/trpc/data.getStudyData?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(200); + expect(response.body.result.data.clinical).toHaveLength(2); + expect(response.body.result.data.clinical[0].value).toHaveLength(2); + expect(response.body.result.data.clinical[0].value).toHaveLength(2); + }); + test('Get data (aggregation with deconcat)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.JSON, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: JSON.stringify(['1', '2', '3']), + properties: { + SubjectId: '1A', + VisitId: '1' + } + }] + }); + const paramteres = { + studyId: study.id, + versionId: null, + aggregation: { + clinical: [{ + operationName: 'DECONCAT', + params: { + deconcatKeys: [ + 'value' + ], + matchMode: 'combinations' + } + }] + }, + fieldIds: undefined, + useCache: undefined, + forceUpdate: undefined + }; + const response = await authorisedUser.get('/trpc/data.getStudyData?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(200); + expect(response.body.result.data.clinical).toHaveLength(1); + expect(response.body.result.data.clinical[0]).toHaveLength(3); + expect(response.body.result.data.clinical[0][0].value).toBe('1'); + expect(response.body.result.data.clinical[0][1].value).toBe('2'); + expect(response.body.result.data.clinical[0][2].value).toBe('3'); + }); + test('Get data (aggregation with join)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 11', + fieldId: '11', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '1', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }, { + fieldId: '11', + value: '2', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }, { + fieldId: '1', + value: '11', + properties: { + SubjectId: '2B', + VisitId: '1' + } + }, { + fieldId: '11', + value: '22', + properties: { + SubjectId: '2B', + VisitId: '1' + } + }] + }); + const paramteres = { + studyId: study.id, + versionId: null, + aggregation: { + clinical: [{ + operationName: 'AFFINE', + params: { + removedKeys: [ + '_id', + 'id', + 'studyId', + 'dataVersion', + 'life', + 'metadata', + 'properties', + 'fieldId', + 'value' + ], + addedKeyRules: [{ + key: { + type: enumASTNodeTypes.VALUE, + operator: null, + value: 'SubjectId', + parameters: {}, + children: null + }, + value: { + type: enumASTNodeTypes.VARIABLE, + operator: null, + value: 'properties.SubjectId', + parameters: {}, + children: null + } + }, { + key: { + type: enumASTNodeTypes.VARIABLE, + operator: null, + value: 'fieldId', + parameters: {}, + children: null + }, + value: { + type: enumASTNodeTypes.VARIABLE, + operator: null, + value: 'value', + parameters: {}, + children: null + } + }], + rules: {} + } + }, { + operationName: 'GROUP', + params: { + keys: [ + 'SubjectId' + ], + skipUnmatch: false + } + }, { + operationName: 'JOIN', + params: { + reservedKeys: [ + 'SubjectId' + ] + } + }] + }, + fieldIds: undefined, + useCache: undefined, + forceUpdate: undefined + }; + const response = await authorisedUser.get('/trpc/data.getStudyData?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(200); + expect(response.body.result.data.clinical).toHaveLength(2); + }); + test('Get data (aggregation with degroup)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 11', + fieldId: '11', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '1', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }, { + fieldId: '11', + value: '2', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }, { + fieldId: '1', + value: '11', + properties: { + SubjectId: '2B', + VisitId: '1' + } + }, { + fieldId: '11', + value: '22', + properties: { + SubjectId: '2B', + VisitId: '1' + } + }] + }); + const paramteres = { + studyId: study.id, + versionId: null, + aggregation: { + clinical: [{ + operationName: 'AFFINE', + params: { + removedKeys: [ + '_id', + 'id', + 'studyId', + 'dataVersion', + 'life', + 'metadata', + 'properties', + 'fieldId', + 'value' + ], + addedKeyRules: [{ + key: { + type: enumASTNodeTypes.VALUE, + operator: null, + value: 'SubjectId', + parameters: {}, + children: null + }, + value: { + type: enumASTNodeTypes.VARIABLE, + operator: null, + value: 'properties.SubjectId', + parameters: {}, + children: null + } + }, { + key: { + type: enumASTNodeTypes.VARIABLE, + operator: null, + value: 'fieldId', + parameters: {}, + children: null + }, + value: { + type: enumASTNodeTypes.VARIABLE, + operator: null, + value: 'value', + parameters: {}, + children: null + } + }], + rules: {} + } + }, { + operationName: 'GROUP', + params: { + keys: [ + 'SubjectId' + ], + skipUnmatch: false + } + }, { + operationName: 'JOIN', + params: { + reservedKeys: [ + 'SubjectId' + ] + } + }, { + operationName: 'DEGROUP', + params: { + sharedKeys: ['SubjectId'], + targetKeyGroups: [['1'], ['11']] + } + }] + }, + fieldIds: undefined, + useCache: undefined, + forceUpdate: undefined + }; + const response = await authorisedUser.get('/trpc/data.getStudyData?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(200); + expect(response.body.result.data.clinical).toHaveLength(2); + expect(response.body.result.data.clinical[0]).toHaveLength(2); + expect(response.body.result.data.clinical[1]).toHaveLength(2); + }); + test('Get data (aggregation with filter)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 11', + fieldId: '11', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '1', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }, { + fieldId: '11', + value: '2', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }, { + fieldId: '1', + value: '11', + properties: { + SubjectId: '2B', + VisitId: '1' + } + }, { + fieldId: '11', + value: '22', + properties: { + SubjectId: '2B', + VisitId: '1' + } + }] + }); + const paramteres = { + studyId: study.id, + versionId: null, + aggregation: { + clinical: [{ + operationName: 'FILTER', + params: { + filters: { + tag: [{ + formula: { + value: 'fieldId', + operation: null, + type: 'VARIABLE', + parameter: {}, + children: null + }, + value: '11', + condition: enumConditionOps.STRINGEQUAL, + parameters: {} + }] + } + } + }] + }, + fieldIds: undefined, + useCache: undefined, + forceUpdate: undefined + }; + const response = await authorisedUser.get('/trpc/data.getStudyData?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(200); + expect(response.body.result.data.clinical).toHaveLength(2); + expect(response.body.result.data.clinical[0].fieldId).toBe('11'); + expect(response.body.result.data.clinical[1].fieldId).toBe('11'); + }); + test('Get data (aggregation with flatten)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 11', + fieldId: '11', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '1', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }, { + fieldId: '11', + value: '2', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }, { + fieldId: '1', + value: '11', + properties: { + SubjectId: '2B', + VisitId: '1' + } + }, { + fieldId: '11', + value: '22', + properties: { + SubjectId: '2B', + VisitId: '1' + } + }] + }); + const paramteres = { + studyId: study.id, + versionId: null, + aggregation: { + clinical: [{ + operationName: 'FLATTEN', + params: { + keepFlattened: true, + flattenedKey: 'properties', + keepFlattenedKey: false + } + }] + }, + fieldIds: undefined, + useCache: undefined, + forceUpdate: undefined + }; + const response = await authorisedUser.get('/trpc/data.getStudyData?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(200); + expect(response.body.result.data.clinical).toHaveLength(4); + expect(response.body.result.data.clinical[0].SubjectId).toBeDefined(); + expect(response.body.result.data.clinical[1].SubjectId).toBeDefined(); + expect(response.body.result.data.clinical[2].SubjectId).toBeDefined(); + expect(response.body.result.data.clinical[3].SubjectId).toBeDefined(); + }); + test('Get data (aggregation with count)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 11', + fieldId: '11', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '1', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }, { + fieldId: '11', + value: '2', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }, { + fieldId: '1', + value: '11', + properties: { + SubjectId: '2B', + VisitId: '1' + } + }, { + fieldId: '11', + value: '22', + properties: { + SubjectId: '2B', + VisitId: '1' + } + }] + }); + const paramteres = { + studyId: study.id, + versionId: null, + aggregation: { + clinical: [{ + operationName: 'GROUP', + params: { + keys: [ + 'properties.SubjectId' + ], + skipUnmatch: false + } + }, { + operationName: 'COUNT', + params: { + + } + }] + }, + fieldIds: undefined, + useCache: undefined, + forceUpdate: undefined + }; + const response = await authorisedUser.get('/trpc/data.getStudyData?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(200); + expect(response.body.result.data.clinical).toHaveLength(2); + expect(response.body.result.data.clinical[0].count).toBe(2); + expect(response.body.result.data.clinical[1].count).toBe(2); + }); + test('Get data (with fieldIds)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 11', + fieldId: '11', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '1', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }, { + fieldId: '11', + value: '2', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }, { + fieldId: '1', + value: '11', + properties: { + SubjectId: '2B', + VisitId: '1' + } + }, { + fieldId: '11', + value: '22', + properties: { + SubjectId: '2B', + VisitId: '1' + } + }] + }); + const paramteres = { + studyId: study.id, + versionId: null, + aggregation: undefined, + fieldIds: ['1'], + useCache: undefined, + forceUpdate: undefined + }; + const response = await authorisedUser.get('/trpc/data.getStudyData?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(200); + expect(response.body.result.data).toHaveLength(2); + expect(response.body.result.data[0].fieldId).toBe('1'); + expect(response.body.result.data[1].fieldId).toBe('1'); + }); + test('Get data (cache initialized)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '1', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }] + }); + const paramteres = { + studyId: study.id, + versionId: null, + aggregation: undefined, + fieldIds: undefined, + useCache: true, + forceUpdate: undefined + }; + const response = await authorisedUser.get('/trpc/data.getStudyData?input=' + encodeQueryParams(paramteres)) + .query({}); + const cache = await db.collections.cache_collection.findOne({}); + const file = await db.collections.files_collection.findOne({}); + expect(response.status).toBe(200); + expect(cache?.uri).toBe(file?.uri); + }); + test('Get data (cache existing)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + description: null, + required: true + }, { + name: 'VisitId', + description: null, + required: true + }] + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '1', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }] + }); + const paramteres = { + studyId: study.id, + versionId: null, + aggregation: undefined, + fieldIds: undefined, + useCache: true, + forceUpdate: undefined + }; + await authorisedUser.get('/trpc/data.getStudyData?input=' + encodeQueryParams(paramteres)) + .query({}); + + const response = await authorisedUser.get('/trpc/data.getStudyData?input=' + encodeQueryParams(paramteres)) + .query({}); + const cache = await db.collections.cache_collection.find({}).toArray(); + const file = await db.collections.files_collection.find({}).toArray(); + expect(cache).toHaveLength(1); + expect(file).toHaveLength(1); + expect(response.status).toBe(200); + expect(cache[0]?.uri).toBe(file[0]?.uri); + }); + test('Get data (cache existing but force update)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + required: true + }, { + name: 'VisitId', + required: true + }] + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '1', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }] + }); + const paramteres = { + studyId: study.id, + versionId: null, + aggregation: undefined, + fieldIds: undefined, + useCache: true, + forceUpdate: true + }; + await authorisedUser.get('/trpc/data.getStudyData?input=' + encodeQueryParams(paramteres)) + .query({}); + const cache1 = await db.collections.cache_collection.find({}).toArray(); + expect(cache1).toHaveLength(1); + const file1 = await db.collections.files_collection.find({}).toArray(); + expect(file1).toHaveLength(1); + const response = await authorisedUser.get('/trpc/data.getStudyData?input=' + encodeQueryParams(paramteres)) + .query({}); + const cache = await db.collections.cache_collection.find({}).toArray(); + const file = await db.collections.files_collection.find({}).toArray(); + expect(cache).toHaveLength(2); + expect(file).toHaveLength(2); + expect(response.status).toBe(200); + expect(cache[0]?.uri).not.toBe(cache[1]?.uri); + }); + test('Get data (study does not exist)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + description: null, + required: true + }, { + name: 'VisitId', + description: null, + required: true + }] + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '1', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }] + }); + const paramteres = { + studyId: 'random', + versionId: null, + aggregation: undefined, + fieldIds: undefined, + useCache: undefined, + forceUpdate: undefined + }; + const response = await authorisedUser.get('/trpc/data.getStudyData?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('Get data (no permission)', async () => { + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: study.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER, + properties: [{ + name: 'SubjectId', + description: null, + required: true + }, { + name: 'VisitId', + description: null, + required: true + }] + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: study.id, + data: [{ + fieldId: '1', + value: '1', + properties: { + SubjectId: '1A', + VisitId: '1' + } + }] + }); + const paramteres = { + studyId: study.id, + versionId: null, + aggregation: undefined, + fieldIds: ['2'], + useCache: undefined, + forceUpdate: undefined + }; + const response = await authorisedUser.get('/trpc/data.getStudyData?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(200); + expect(response.body.result.data).toHaveLength(0); + }); + }); +} else { + test(`${__filename.split(/[\\/]/).pop()} skipped because it requires Minio on Docker`, () => { + expect(true).toBe(true); + }); +} diff --git a/packages/itmat-interface/test/trpcTests/domain.test.ts b/packages/itmat-interface/test/trpcTests/domain.test.ts new file mode 100644 index 000000000..8ec505ba4 --- /dev/null +++ b/packages/itmat-interface/test/trpcTests/domain.test.ts @@ -0,0 +1,182 @@ +/** + * @with Minio + */ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +import { v4 as uuid } from 'uuid'; +import request from 'supertest'; +import { objStore } from '../../src/objStore/objStore'; +import { connectAdmin, connectUser } from './_loginHelper'; +import { db } from '../../src/database/database'; +import { Router } from '../../src/server/router'; +import { Db, MongoClient } from 'mongodb'; +import { MongoMemoryServer } from 'mongodb-memory-server'; +import config from '../../config/config.sample.json'; +import { IDomain, enumCoreErrors } from '@itmat-broker/itmat-types'; +import { Express } from 'express'; +import { setupDatabase } from '@itmat-broker/itmat-setup'; +import { encodeQueryParams } from './helper'; +import path from 'path'; +import { getRequiredMinioPort } from './types'; + +if (global.hasMinio) { + let app: Express; + let mongodb: MongoMemoryServer; + let admin: request.SuperTest; + let user: request.SuperTest; + let mongoConnection: MongoClient; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let mongoClient: Db; + let test_domain: IDomain; + + afterAll(async () => { + await db.closeConnection(); + await mongoConnection?.close(); + await mongodb.stop(); + + /* claer all mocks */ + jest.clearAllMocks(); + }); + + beforeAll(async () => { // eslint-disable-line no-undef + /* Creating a in-memory MongoDB instance for testing */ + const dbName = uuid(); + mongodb = await MongoMemoryServer.create({ instance: { dbName } }); + const connectionString = mongodb.getUri(); + await setupDatabase(connectionString, dbName); + /* Wiring up the backend server */ + config.objectStore.port = getRequiredMinioPort(); + config.database.mongo_url = connectionString; + config.database.database = dbName; + await db.connect(config.database, MongoClient); + await objStore.connect(config.objectStore); + const router = new Router(config); + await router.init(); + /* Connect mongo client (for test setup later / retrieve info later) */ + mongoConnection = await MongoClient.connect(connectionString); + mongoClient = mongoConnection.db(dbName); + + /* Connecting clients for testing later */ + app = router.getApp(); + admin = request.agent(app); + user = request.agent(app); + await connectAdmin(admin); + await connectUser(user); + + /* Mock Date for testing */ + jest.spyOn(Date, 'now').mockImplementation(() => 1591134065000); + + test_domain = { + id: 'test_domain', + domainPath: 'test_domain', + name: 'test', + logo: 'test_logo', + color: '#000000', + life: { + createdTime: 1591134065000, + createdUser: 'admin', + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + await db.collections.domains_collection.insertOne(test_domain); + }); + + afterEach(async () => { + await db.collections.domains_collection.deleteMany({}); + await db.collections.domains_collection.insertOne(test_domain); + }); + + describe('tRPC domain APIs', () => { + test('Get domains (admin)', async () => { + // query API + const paramteres = { + }; + const response = await admin.get('/trpc/domain.getDomains?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(200); + expect(response.body.result.data).toHaveLength(1); + expect(response.body.result.data[0].domainPath).toBe('test_domain'); + }); + test('Get domains (user)', async () => { + // query API + const paramteres = { + }; + const response = await user.get('/trpc/domain.getDomains?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('Create domain (admin)', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const response = await admin.post('/trpc/domain.createDomain') + .attach('logo', filePath) + .field('domainName', 'new_test') + .field('domainPath', 'new_test_domain') + .field('color', '#000000'); + expect(response.status).toBe(200); + expect(response.body.result.data.domainPath).toBe('new_test_domain'); + }); + test('Create domain (user)', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const response = await user.post('/trpc/domain.createDomain') + .attach('logo', filePath) + .field('domainName', 'new_test') + .field('domainPath', 'new_test_domain') + .field('color', '#000000'); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('Create domain on existing domain path', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const response = await admin.post('/trpc/domain.createDomain') + .attach('logo', filePath) + .field('domainName', 'new_test') + .field('domainPath', 'test_domain') + .field('color', '#000000'); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Domain already exists.'); + }); + test('Edit domain (admin)', async () => { + const response = await admin.post('/trpc/domain.editDomain') + .field('domainId', 'test_domain') + .field('domainName', 'new_test') + .field('color', '#000000'); + expect(response.status).toBe(200); + expect(response.body.result.data.successful).toBe(true); + }); + test('Edit domain (user)', async () => { + const response = await user.post('/trpc/domain.editDomain') + .field('domainId', 'test_domain') + .field('domainName', 'new_test') + .field('color', '#000000'); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('Delete domain (admin)', async () => { + const response = await admin.post('/trpc/domain.deleteDomain') + .field('domainId', 'test_domain'); + expect(response.status).toBe(200); + expect(response.body.result.data.successful).toBe(true); + const domain = await db.collections.domains_collection.findOne({ id: 'test_domain' }); + expect(domain.life.deletedTime).not.toBe(null); + }); + test('Delete domain (user)', async () => { + const response = await user.post('/trpc/domain.deleteDomain') + .field('domainId', 'test_domain'); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('Delete non-existing domain', async () => { + const response = await admin.post('/trpc/domain.deleteDomain') + .field('domainId', 'non_existing_domain'); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Domain does not exist.'); + }); + }); +} else { + test(`${__filename.split(/[\\/]/).pop()} skipped because it requires Minio on Docker`, () => { + expect(true).toBe(true); + }); +} diff --git a/packages/itmat-interface/test/trpcTests/drive.test.ts b/packages/itmat-interface/test/trpcTests/drive.test.ts new file mode 100644 index 000000000..2dd0c8282 --- /dev/null +++ b/packages/itmat-interface/test/trpcTests/drive.test.ts @@ -0,0 +1,836 @@ +/** + * @with Minio + */ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +import { MongoMemoryServer } from 'mongodb-memory-server'; +import { db } from '../../src/database/database'; +import { Express } from 'express'; +import { objStore } from '../../src/objStore/objStore'; +import request from 'supertest'; +import { connectAdmin, connectUser } from './_loginHelper'; +import { Router } from '../../src/server/router'; +import { Db, MongoClient } from 'mongodb'; +import { setupDatabase } from '@itmat-broker/itmat-setup'; +import config from '../../config/config.sample.json'; +import { v4 as uuid } from 'uuid'; +import { enumUserTypes, enumDriveNodeTypes, enumCoreErrors } from '@itmat-broker/itmat-types'; +import { encodeQueryParams } from './helper'; +import { errorCodes } from '@itmat-broker/itmat-cores'; +import path from 'path'; +import { getRequiredMinioPort } from './types'; + +if (global.hasMinio) { + let app: Express; + let mongodb: MongoMemoryServer; + let admin: request.SuperTest; + let user: request.SuperTest; + let mongoConnection: MongoClient; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let mongoClient: Db; + let adminProfile; + let userProfile; + + afterAll(async () => { + await db.closeConnection(); + await mongoConnection?.close(); + await mongodb.stop(); + + /* claer all mocks */ + jest.clearAllMocks(); + }); + + beforeAll(async () => { // eslint-disable-line no-undef + /* Creating a in-memory MongoDB instance for testing */ + const dbName = uuid(); + mongodb = await MongoMemoryServer.create({ instance: { dbName } }); + const connectionString = mongodb.getUri(); + await setupDatabase(connectionString, dbName); + /* Wiring up the backend server */ + config.objectStore.port = getRequiredMinioPort(); + config.database.mongo_url = connectionString; + config.database.database = dbName; + await db.connect(config.database, MongoClient); + await objStore.connect(config.objectStore); + const router = new Router(config); + await router.init(); + /* Connect mongo client (for test setup later / retrieve info later) */ + mongoConnection = await MongoClient.connect(connectionString); + mongoClient = mongoConnection.db(dbName); + + /* Connecting clients for testing later */ + app = router.getApp(); + admin = request.agent(app); + user = request.agent(app); + await connectAdmin(admin); + await connectUser(user); + + // add the root node for each user + const users = await db.collections.users_collection.find({}).toArray(); + adminProfile = users.filter(el => el.type === enumUserTypes.ADMIN)[0]; + userProfile = users.filter(el => el.type === enumUserTypes.STANDARD)[0]; + }); + + beforeEach(async () => { + const users = await db.collections.users_collection.find({}).toArray(); + for (const user of users) { + const uid = uuid(); + await db.collections.drives_collection.insertOne({ + id: uid, + managerId: user.id, + path: [uid], + restricted: true, + name: 'My Drive', + description: null, + fileId: null, + type: enumDriveNodeTypes.FOLDER, + parent: null, + children: [], + sharedUsers: [], + life: { + createdTime: 0, + createdUser: enumUserTypes.SYSTEM, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }); + } + }); + + afterEach(async () => { + await db.collections.drives_collection.deleteMany({}); + }); + + describe('tRPC drive APIs', () => { + test('Get drives of a user', async () => { + const paramteres = { + userId: userProfile.id + }; + const response = await user.get('/trpc/drive.getDrives?input=' + encodeQueryParams(paramteres)) + .query({ + + }); + expect(response.status).toBe(200); + expect(response.body.errors).toBeUndefined(); + expect(Object.keys(response.body.result.data)).toHaveLength(1); + const key = Object.keys(response.body.result.data)[0]; + expect(response.body.result.data[key]).toHaveLength(1); + }); + test('Get drives of a user (with shared files)', async () => { + const adminDrive = await db.collections.drives_collection.findOne({ managerId: adminProfile.id }); + const childDriveId = uuid(); + await db.collections.drives_collection.insertOne({ + id: childDriveId, + managerId: adminProfile.id, + path: [uuid()], + restricted: true, + name: 'Shared Drive', + description: null, + fileId: null, + type: enumDriveNodeTypes.FOLDER, + parent: adminDrive, + children: [], + sharedUsers: [{ iid: userProfile.id, read: true, write: false, delete: false }], + life: { + createdTime: 0, + createdUser: enumUserTypes.SYSTEM, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }); + await db.collections.drives_collection.findOneAndUpdate({ id: adminDrive.id }, { + $push: { + children: childDriveId.id, sharedUsers: { + iid: userProfile.id, + read: true, + write: false, + delete: false + } + } + }); + const paramteres = { + userId: userProfile.id + }; + const response = await user.get('/trpc/drive.getDrives?input=' + encodeQueryParams(paramteres)) + .query({ + + }); + expect(response.status).toBe(200); + expect(response.body.errors).toBeUndefined(); + expect(Object.keys(response.body.result.data)).toHaveLength(2); + expect(response.body.result.data[userProfile.id]).toHaveLength(1); + expect(response.body.result.data[adminProfile.id]).toHaveLength(2); + }); + test('Get drives of a user (Invalid root id)', async () => { + const paramteres = { + rootId: 'random' + }; + const response = await user.get('/trpc/drive.getDrives?input=' + encodeQueryParams(paramteres)) + .query({ + + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Root drive does not exist.'); + }); + test('Get drives of a user (non-owned drives)', async () => { + const drive = await db.collections.drives_collection.findOne({ managerId: adminProfile.id }); + const paramteres = { + rootId: drive.id + }; + const response = await user.get('/trpc/drive.getDrives?input=' + encodeQueryParams(paramteres)) + .query({ + + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('No permission to get drives.'); + }); + test('Create a Drive Folder', async () => { + const root = await db.collections.drives_collection.findOne({ managerId: userProfile.id }); + const response = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: root.id + }); + expect(response.status).toBe(200); + expect(response.body.result.data.name).toBe('Test'); + const drives = await db.collections.drives_collection.find({ managerId: userProfile.id }).toArray(); + expect(drives[1].parent).toBe(drives[0].id); + }); + test('Create a Drive Folder (in default root)', async () => { + const response = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: null + }); + expect(response.status).toBe(200); + expect(response.body.result.data.name).toBe('Test'); + const drives = await db.collections.drives_collection.find({ managerId: userProfile.id }).toArray(); + expect(drives[1].parent).toBe(drives[0].id); + }); + test('Create a Drive Folder (non-owned parent)', async () => { + const root = (await db.collections.drives_collection.find({ managerId: adminProfile.id }).toArray())[0]; + const response = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: root.id + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('No permission to create folder.'); + }); + test('Create a Drive Folder (non-existing parent)', async () => { + const response = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: 'random' + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('No permission to create folder.'); + }); + test('Create a Drive Folder (folder already exist)', async () => { + await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: null + }); + const response = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: null + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Folder already exists.'); + }); + test('Create a Drive Folder (Parent is not a folder)', async () => { + const childDriveId = uuid(); + await db.collections.drives_collection.insertOne({ + id: childDriveId, + managerId: userProfile.id, + path: [uuid()], + restricted: true, + name: 'Shared File', + description: null, + fileId: null, + type: enumDriveNodeTypes.FILE, + parent: null, + children: [], + sharedUsers: [], + life: { + createdTime: 0, + createdUser: enumUserTypes.SYSTEM, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }); + const userRootDrive = await db.collections.drives_collection.findOne({ managerId: userProfile.id }); + await db.collections.drives_collection.findOneAndUpdate({ id: userRootDrive.id }, { $push: { children: childDriveId.id } }); + const response = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: childDriveId + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Parent is not a folder.'); + }); + test('Create a Drive Folder (under another users account with permission)', async () => { + await db.collections.drives_collection.findOneAndUpdate({ managerId: adminProfile.id }, { + $push: { sharedUsers: { iid: userProfile.id, read: true, write: true, delete: false } } + }); + const adminDrive = await db.collections.drives_collection.findOne({ managerId: adminProfile.id }); + const response = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: adminDrive?.id + }); + expect(response.status).toBe(200); + expect(response.body.errors).toBeUndefined(); + expect(response.body.result.data.name).toBe('Test'); + const newDrive = await db.collections.drives_collection.findOne({ name: 'Test' }); + expect(newDrive).toBeDefined(); + expect(newDrive.parent).toBe(adminDrive.id); + expect(newDrive?.name).toBe('Test'); + }); + test('Create a Drive File', async () => { + const root = (await db.collections.drives_collection.find({ managerId: userProfile.id }).toArray())[0]; + const filePath = path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt'); + const request = user.post('/trpc/drive.createDriveFile') + .attach('file', filePath) + .field('parentId', root.id) + .field('description', ''); + const response = await request; + expect(response.status).toBe(200); + expect(response.body.result.data.name).toBe('I7N3G6G-MMM7N3G6G-20200704-20200721.txt'); + const drives = await db.collections.drives_collection.find({ managerId: userProfile.id }).toArray(); + expect(drives).toHaveLength(2); + expect(response.body.result.data.parent).toBe(drives[0].id); + + }); + test('Create a Drive File īŧˆin default rootīŧ‰', async () => { + const filePath = path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt'); + const request = user.post('/trpc/drive.createDriveFile') + .attach('file', filePath) + .field('parentId', '') + .field('description', ''); + const response = await request; + + expect(response.status).toBe(200); + expect(response.body.result.data.name).toBe('I7N3G6G-MMM7N3G6G-20200704-20200721.txt'); + const drives = await db.collections.drives_collection.find({ managerId: userProfile.id }).toArray(); + expect(drives).toHaveLength(2); + expect(response.body.result.data.parent).toBe(drives[0].id); + }); + test('Create a Drive File īŧˆfor unpermitted parentīŧ‰', async () => { + const filePath = path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt'); + const request = user.post('/trpc/drive.createDriveFile') + .attach('file', filePath) + .field('parentId', 'random') + .field('description', ''); + const response = await request; + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + const drives = await db.collections.drives_collection.find({ managerId: userProfile.id }).toArray(); + expect(drives).toHaveLength(1); + }); + test('Create a Drive File īŧˆnot owned driveīŧ‰', async () => { + const adminDrive = await db.collections.drives_collection.findOne({ managerId: adminProfile.id }); + const filePath = path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt'); + const request = user.post('/trpc/drive.createDriveFile') + .attach('file', filePath) + .field('parentId', adminDrive?.id) + .field('description', ''); + const response = await request; + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + const drives = await db.collections.drives_collection.find({ managerId: userProfile.id }).toArray(); + expect(drives).toHaveLength(1); + }); + test('Create a Drive File shared driveīŧ‰', async () => { + const adminDrive = await db.collections.drives_collection.findOne({ managerId: adminProfile.id }); + const filePath = path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt'); + const request = user.post('/trpc/drive.createDriveFile') + .attach('file', filePath) + .field('parentId', adminDrive?.id) + .field('description', ''); + const response = await request; + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + const drives = await db.collections.drives_collection.find({ managerId: userProfile.id }).toArray(); + expect(drives).toHaveLength(1); + }); + test('Create a Drive File īŧˆfile already existīŧ‰', async () => { + const rootDrive = await db.collections.drives_collection.findOne({ managerId: userProfile.id, parent: null }); + function createDriveFile(filePath, parentId) { + const request = parentId ? user.post('/trpc/drive.createDriveFile') + .attach('file', filePath) + .field('parentId', parentId) : + user.post('/trpc/drive.createDriveFile') + .attach('file', filePath); + return request; + } + const filePath = path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt'); + await createDriveFile(filePath, rootDrive.id); + const response = await createDriveFile(filePath, rootDrive.id); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('File already exists.'); + }); + test('Create a Drive File īŧˆto a file parentīŧ‰', async () => { + const rootDrive = await db.collections.drives_collection.findOne({ managerId: userProfile.id, parent: null }); + function createDriveFile(filePath, parentId) { + const request = parentId ? user.post('/trpc/drive.createDriveFile') + .attach('file', filePath) + .field('parentId', parentId) : + user.post('/trpc/drive.createDriveFile') + .attach('file', filePath); + return request; + } + const filePath = path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt'); + await createDriveFile(filePath, rootDrive.id); + const fileDrive = await db.collections.drives_collection.findOne({ managerId: userProfile.id, parent: rootDrive.id }); + const response = await createDriveFile(filePath, fileDrive.id); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Parent is not a folder.'); + }); + test('Delete a drive node', async () => { + const response1 = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: null + }); + const drives = await db.collections.drives_collection.find({ 'managerId': userProfile.id, 'life.deletedTime': null }).toArray(); + expect(drives).toHaveLength(2); + const response2 = await user.post('/trpc/drive.deleteDrive') + .send({ + driveId: response1.body.result.data.id + }); + expect(response2.status).toBe(200); + expect(response2.body.result.data.id).toBe(response1.body.result.data.id); + const drives2 = await db.collections.drives_collection.find({ 'managerId': userProfile.id, 'life.deletedTime': null }).toArray(); + expect(drives2).toHaveLength(1); + }); + test('Delete a drive node (with recursive children)', async () => { + const response1 = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: null + }); + const drives = await db.collections.drives_collection.find({ 'managerId': userProfile.id, 'life.deletedTime': null }).toArray(); + expect(drives).toHaveLength(2); + await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test2', + parentId: response1.body.result.data.id + }); + const drives2 = await db.collections.drives_collection.find({ 'managerId': userProfile.id, 'life.deletedTime': null }).toArray(); + expect(drives2).toHaveLength(3); + const response3 = await user.post('/trpc/drive.deleteDrive') + .send({ + driveId: response1.body.result.data.id + }); + expect(response3.status).toBe(200); + expect(response3.body.result.data.id).toBe(response1.body.result.data.id); + const drives3 = await db.collections.drives_collection.find({ 'managerId': userProfile.id, 'life.deletedTime': null }).toArray(); + expect(drives3).toHaveLength(1); + }); + test('Delete a drive node (node does not exist)', async () => { + await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: null + }); + const response = await user.post('/trpc/drive.deleteDrive') + .send({ + driveId: 'random' + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Drive does not exist.'); + const drives = await db.collections.drives_collection.find({ 'managerId': userProfile.id, 'life.deletedTime': null }).toArray(); + expect(drives).toHaveLength(2); + }); + test('Share folders/files to another user via email', async () => { + const response1 = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: null + }); + const response2 = await user.post('/trpc/drive.shareDriveToUserViaEmail') + .send({ + userEmails: [adminProfile.email], + driveId: response1.body.result.data.parent, + permissions: { + read: true, + write: false, + delete: false + } + }); + expect(response2.status).toBe(200); + expect(response2.body.result.data).toEqual([response1.body.result.data.parent, response1.body.result.data.id]); + const drives = await db.collections.drives_collection.find({ managerId: userProfile.id }).toArray(); + expect(drives[0].sharedUsers).toContainEqual({ iid: adminProfile.id, read: true, write: false, delete: false }); + expect(drives[1].sharedUsers).toContainEqual({ iid: adminProfile.id, read: true, write: false, delete: false }); + const paramteres: any = { + userId: adminProfile.id + }; + const response3 = await user.get('/trpc/drive.getDrives?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response3.status).toBe(200); + expect(response3.body.result.data[userProfile.id]).toBeDefined(); + expect(response3.body.result.data[userProfile.id]).toHaveLength(2); + }); + test('Share folders/files to another user via email (Email does not exist)', async () => { + const response1 = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: null + }); + const response2 = await user.post('/trpc/drive.shareDriveToUserViaEmail') + .send({ + userEmails: ['random'], + driveId: response1.body.result.data.parent, + permissions: { + read: true, + write: false, + delete: false + } + }); + expect(response2.status).toBe(200); + const drive = await db.collections.drives_collection.findOne({ id: response1.body.result.data.id }); + expect(drive.sharedUsers).toHaveLength(0); + }); + test('Edit a drive', async () => { + const response1 = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: null + }); + const response2 = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: response1.body.result.data.id + }); + const response3 = await user.post('/trpc/drive.editDrive') + .send({ + driveId: response1.body.result.data.id, + sharedUsers: [{ + iid: adminProfile.id, + read: true, + write: false, + delete: false + }] + }); + expect(response3.status).toBe(200); + expect(response3.body.result.data.driveIds).toEqual([response1.body.result.data.id, response2.body.result.data.id]); + const drives = await db.collections.drives_collection.find({ id: { $in: [response1.body.result.data.id, response2.body.result.data.id] } }).toArray(); + expect(drives[0].sharedUsers[0]).toEqual({ iid: adminProfile.id, read: true, write: false, delete: false }); + expect(drives[1].sharedUsers[0]).toEqual({ iid: adminProfile.id, read: true, write: false, delete: false }); + }); + test('Edit a drive (drive does not exist)', async () => { + const response1 = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: null + }); + await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: response1.body.result.data.id + }); + const response3 = await user.post('/trpc/drive.editDrive') + .send({ + driveId: 'random', + sharedUsers: [{ + iid: adminProfile.id, + read: true, + write: false, + delete: false + }] + }); + expect(response3.status).toBe(400); + expect(response3.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('Edit a drive (not owned drive)', async () => { + const response1 = await admin.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: null + }); + await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: response1.body.result.data.id + }); + const response3 = await user.post('/trpc/drive.editDrive') + .send({ + driveId: response1.body.result.data.id, + sharedUsers: [{ + iid: adminProfile.id, + read: true, + write: false, + delete: false + }] + }); + expect(response3.status).toBe(400); + expect(response3.body.error.message).toBe(errorCodes.NO_PERMISSION_ERROR); + }); + test('Edit a drive (manager does not exist)', async () => { + const response1 = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: null + }); + await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: response1.body.result.data.id + }); + const response3 = await user.post('/trpc/drive.editDrive') + .send({ + driveId: response1.body.result.data.id, + managerId: 'random' + }); + expect(response3.status).toBe(400); + expect(response3.body.error.message).toBe('Manager does not exist.'); + }); + test('Edit a drive (name already exists)', async () => { + const response1 = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: null + }); + await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: response1.body.result.data.id + }); + const response3 = await user.post('/trpc/drive.editDrive') + .send({ + driveId: response1.body.result.data.id, + name: 'Test' + }); + expect(response3.status).toBe(400); + expect(response3.body.error.message).toBe('Name already exists.'); + }); + test('Edit a drive (parent does not exist)', async () => { + const response1 = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: null + }); + await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: response1.body.result.data.id + }); + const response3 = await user.post('/trpc/drive.editDrive') + .send({ + driveId: response1.body.result.data.id, + parentId: 'random' + }); + expect(response3.status).toBe(400); + expect(response3.body.error.message).toBe('Parent does not exist.'); + }); + test('Edit a drive (children does not exist)', async () => { + const response1 = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: null + }); + await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: response1.body.result.data.id + }); + const response3 = await user.post('/trpc/drive.editDrive') + .send({ + driveId: response1.body.result.data.id, + children: ['random'] + }); + expect(response3.status).toBe(400); + expect(response3.body.error.message).toBe('Children do not exist.'); + }); + test('Edit a drive (shared users do not exist)', async () => { + const response1 = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: null + }); + await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: response1.body.result.data.id + }); + const response3 = await user.post('/trpc/drive.editDrive') + .send({ + driveId: response1.body.result.data.id, + sharedUsers: [{ + iid: 'random', + read: true, + write: false, + delete: false + }] + }); + expect(response3.status).toBe(400); + expect(response3.body.error.message).toBe('Shared users do not exist.'); + }); + test('Edit a drive (empty update)', async () => { + const response1 = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: null + }); + await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: response1.body.result.data.id + }); + const response3 = await user.post('/trpc/drive.editDrive') + .send({ + driveId: response1.body.result.data.id + }); + expect(response3.status).toBe(400); + expect(response3.body.error.message).toBe('You have to edit at lease one property.'); + }); + test('Copy a drive node (self)', async () => { + function createDriveFile(filePath, parentId) { + const request = parentId ? user.post('/trpc/drive.createDriveFile') + .attach('file', filePath) + .field('parentId', parentId) : + user.post('/trpc/drive.createDriveFile') + .attach('file', filePath); + return request; + } + const response1 = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: null + }); + const filePath = path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt'); + await createDriveFile(filePath, response1.body.result.data.id); + const reponse2 = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test2', + parentId: null + }); + const response = await user.post('/trpc/drive.copyDrive') + .send({ + driveId: response1.body.result.data.id, + targetParentId: reponse2.body.result.data.id + }); + expect(response.status).toBe(200); + expect(response.body.result.data.successful).toBe(true); + const drives = await db.collections.drives_collection.find({ managerId: userProfile.id }).toArray(); + expect(drives).toHaveLength(6); + expect(drives.filter(el => el.name === 'Test')).toHaveLength(2); + expect(drives.filter(el => el.type === enumDriveNodeTypes.FILE)).toHaveLength(2); + expect(drives.filter(el => el.type === enumDriveNodeTypes.FOLDER)).toHaveLength(4); // including one root + }); + test('Copy a drive node (unauthorised permission)', async () => { + function createDriveFile(filePath, parentId) { + const request = parentId ? admin.post('/trpc/drive.createDriveFile') + .attach('file', filePath) + .field('parentId', parentId) : + admin.post('/trpc/drive.createDriveFile') + .attach('file', filePath); + return request; + } + const response1 = await admin.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: null + }); + const filePath = path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt'); + await createDriveFile(filePath, response1.body.result.data.id); + const userRootDrive = await db.collections.drives_collection.findOne({ managerId: userProfile.id }); + const response = await user.post('/trpc/drive.copyDrive') + .send({ + driveId: response1.body.result.data.id, + targetParentId: userRootDrive?.id + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('Copy a drive node (target parent does not exist)', async () => { + function createDriveFile(filePath, parentId) { + const request = parentId ? user.post('/trpc/drive.createDriveFile') + .attach('file', filePath) + .field('parentId', parentId) : + user.post('/trpc/drive.createDriveFile') + .attach('file', filePath); + return request; + } + const response1 = await user.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: null + }); + const filePath = path.join(__dirname, '../filesForTests/I7N3G6G-MMM7N3G6G-20200704-20200721.txt'); + await createDriveFile(filePath, response1.body.result.data.id); + const response = await user.post('/trpc/drive.copyDrive') + .send({ + driveId: response1.body.result.data.id, + targetParentId: 'random' + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('Copy a drive node (drive does not exist)', async () => { + const userRootDrive = await db.collections.drives_collection.findOne({ managerId: userProfile.id }); + const response = await user.post('/trpc/drive.copyDrive') + .send({ + driveId: 'random', + targetParentId: userRootDrive?.id + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('Copy drive from other users', async () => { + const adminDrive = await db.collections.drives_collection.findOne({ managerId: adminProfile.id }); + const response = await admin.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: adminDrive.id + }); + await db.collections.drives_collection.findOneAndUpdate({ id: response.body.result.data.id }, { + $push: { + sharedUsers: { + iid: userProfile.id, + read: true, + write: false, + delete: false + } + } + }); + const userRootDrive = await db.collections.drives_collection.findOne({ managerId: userProfile.id }); + const response2 = await user.post('/trpc/drive.copyDrive') + .send({ + driveId: response.body.result.data.id, + targetParentId: userRootDrive.id + }); + expect(response2.status).toBe(200); + expect(response2.body.result.data.successful).toBe(true); + const drives = await db.collections.drives_collection.find({ managerId: userProfile.id }).toArray(); + expect(drives).toHaveLength(2); + }); + test('Copy drive from other users (no permission)', async () => { + const adminDrive = await db.collections.drives_collection.findOne({ managerId: adminProfile.id }); + const response = await admin.post('/trpc/drive.createDriveFolder') + .send({ + folderName: 'Test', + parentId: adminDrive.id + }); + const userRootDrive = await db.collections.drives_collection.findOne({ managerId: userProfile.id }); + const response2 = await user.post('/trpc/drive.copyDrive') + .send({ + driveId: response.body.result.data.id, + targetParentId: userRootDrive.id + }); + expect(response2.status).toBe(400); + expect(response2.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + }); +} else { + test(`${__filename.split(/[\\/]/).pop()} skipped because it requires Minio on Docker`, () => { + expect(true).toBe(true); + }); +} diff --git a/packages/itmat-interface/test/trpcTests/helper.ts b/packages/itmat-interface/test/trpcTests/helper.ts new file mode 100644 index 000000000..3e8af6297 --- /dev/null +++ b/packages/itmat-interface/test/trpcTests/helper.ts @@ -0,0 +1,3 @@ +export function encodeQueryParams(s: unknown): string { + return encodeURIComponent(JSON.stringify(s)); +} diff --git a/packages/itmat-interface/test/trpcTests/instance.test.ts b/packages/itmat-interface/test/trpcTests/instance.test.ts new file mode 100644 index 000000000..9e32b895f --- /dev/null +++ b/packages/itmat-interface/test/trpcTests/instance.test.ts @@ -0,0 +1,290 @@ +/** + * @with Minio + */ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck + + +import { MongoMemoryServer } from 'mongodb-memory-server'; +import { db } from '../../src/database/database'; +import { objStore } from '../../src/objStore/objStore'; +import request from 'supertest'; +import { connectAdmin, connectUser } from './_loginHelper'; +import { Router } from '../../src/server/router'; +import { setupDatabase } from '@itmat-broker/itmat-setup'; +import config from '../../config/config.sample.json'; +import { v4 as uuid } from 'uuid'; +import { Express } from 'express'; +import { IUser, enumUserTypes, LXDInstanceTypeEnum, enumAppType, enumOpeType, enumInstanceStatus } from '@itmat-broker/itmat-types'; +import { Db, MongoClient } from 'mongodb'; +import { JobCore, UserCore } from '@itmat-broker/itmat-cores'; +import { getRequiredMinioPort } from './types'; + + +jest.mock('nodemailer', () => { + const { TEST_SMTP_CRED, TEST_SMTP_USERNAME } = process.env; + if (!TEST_SMTP_CRED || !TEST_SMTP_USERNAME || !config?.nodemailer?.auth?.pass || !config?.nodemailer?.auth?.user) + return { + createTransport: jest.fn().mockImplementation(() => ({ + sendMail: jest.fn() + })) + }; + return jest.requireActual('nodemailer'); +}); + + +if (global.hasMinio) { // eslint-disable-line no-undef + let app: Express; + let mongodb: MongoMemoryServer; + let admin: request.SuperTest; + let user: request.SuperTest; + let mongoConnection: MongoClient; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let mongoClient: Db; + let adminProfile: IUser; + let userProfile: IUser; + + const setupDatabaseAndApp = async () => { + const dbName = uuid(); + mongodb = await MongoMemoryServer.create({ instance: { dbName } }); + const connectionString = mongodb.getUri(); + await setupDatabase(connectionString, dbName); + + config.objectStore.port = getRequiredMinioPort(); + config.database.mongo_url = connectionString; + config.database.database = dbName; + + await db.connect(config.database, MongoClient); + + await objStore.connect(config.objectStore); + + // Initialize the router and application + const router = new Router(config); + await router.init(); + + mongoConnection = await MongoClient.connect(connectionString); + mongoClient = mongoConnection.db(dbName); + + app = router.getApp(); + admin = request.agent(app); + user = request.agent(app); + await connectAdmin(admin); + await connectUser(user); + + // Connect admin and user (mock login) + const users = await db.collections.users_collection.find({}).toArray(); + adminProfile = users.find(el => el.type === enumUserTypes.ADMIN); + userProfile = users.find(el => el.type === enumUserTypes.STANDARD); + }; + + + let userCoreMock: jest.SpyInstance; + let jobCoreMock: jest.SpyInstance; + + beforeAll(async () => { + await setupDatabaseAndApp(); + + // Mock specific methods inside UserCore and JobCore + userCoreMock = jest.spyOn(UserCore.prototype, 'issueSystemAccessToken').mockResolvedValue({ accessToken: 'mocked-token' }); + jobCoreMock = jest.spyOn(JobCore.prototype, 'createJob').mockResolvedValue(true); + }); + + afterAll(async () => { + await db.closeConnection(); + await mongoConnection.close(); + await mongodb.stop(); + userCoreMock.mockRestore(); + jobCoreMock.mockRestore(); + }); + afterEach(async () => { + await db.collections.instance_collection.deleteMany({}); // Clean up all instances after each test + }); + + describe('tRPC Instance APIs', () => { + + describe('createInstance', () => { + test('Should create an instance successfully', async () => { + const response = await user.post('/trpc/instance.createInstance').send({ + name: 'test-instance', + type: LXDInstanceTypeEnum.CONTAINER, + appType: enumAppType.JUPYTER, + cpuLimit: 2, + memoryLimit: '4GB', + diskLimit: '10GB', + lifeSpan: 3600 + }); + + + expect(response.status).toBe(200); + expect(response.body.result.data.name).toBe('test-instance'); + + const instance = await db.collections.instance_collection.findOne({ name: 'test-instance' }); + expect(instance).toBeDefined(); + expect(instance?.name).toBe('test-instance'); + }); + + test('Should throw error when user is not logged in', async () => { + const response = await request(app).post('/trpc/instance.createInstance').send({ + name: 'test-instance', + type: LXDInstanceTypeEnum.CONTAINER, + appType: enumAppType.JUPYTER, + lifeSpan: 3600 + }); + + expect(response.status).toBe(400); // User is not authenticated + expect(response.body.error).toBeDefined(); + }); + }); + + describe('getQuotaAndFlavors', () => { + test('Should return quota and flavors for logged-in user', async () => { + + const response = await user.get('/trpc/instance.getQuotaAndFlavors').send(); + expect(response.status).toBe(200); + expect(response.body.result?.data?.userQuota).toBeDefined(); + expect(response.body.result?.data?.userFlavors).toBeDefined(); + }); + + test('Should throw error when user is not logged in', async () => { + const response = await request(app).get('/trpc/instance.getQuotaAndFlavors').send(); + expect(response.status).toBe(400); + expect(response.body.error).toBeDefined(); + expect(response.body.error.message).toBe('User must be authenticated.'); + }); + }); + + describe('startStopInstance', () => { + test('Should start an instance successfully', async () => { + const instanceId = uuid(); + await db.collections.instance_collection.insertOne({ + id: instanceId, + name: 'test-instance', + type: LXDInstanceTypeEnum.CONTAINER, + appType: enumAppType.JUPYTER, + userId: userProfile.id, + status: enumInstanceStatus.PENDING + }); + + const response = await user.post('/trpc/instance.startStopInstance').send({ + instanceId, + action: enumOpeType.START + }); + + expect(response.status).toBe(200); + + const instance = await db.collections.instance_collection.findOne({ id: instanceId }); + expect(instance?.status).toBe(enumInstanceStatus.STARTING); + }); + + test('Should return error when unauthorized user tries to start an instance', async () => { + // Step 1: Create an instance assigned to another user + const instanceId = uuid(); // Generate a unique instance ID + await db.collections.instance_collection.insertOne({ + id: instanceId, + name: 'test-instance', + type: LXDInstanceTypeEnum.CONTAINER, + appType: enumAppType.JUPYTER, + userId: 'some-other-user-id', // Assign this instance to another user (not the logged-in user) + status: enumInstanceStatus.PENDING // Set initial status to PENDING + }); + + // Step 2: Make the request without a logged-in user (using `request(app)` directly) + const response = await request(app).post('/trpc/instance.startStopInstance').send({ + instanceId: instanceId, + action: enumOpeType.START + }); + + // Step 3: Ensure the user gets a 400 Forbidden error since they don't own the instance + expect(response.status).toBe(400); // Expecting a Forbidden status code + expect(response.body.error).toBeDefined(); // Ensure the response contains an error + }); + }); + + describe('restartInstance', () => { + test('Should restart an instance successfully', async () => { + const instanceId = uuid(); + await db.collections.instance_collection.insertOne({ + id: instanceId, + name: 'test-instance', + type: LXDInstanceTypeEnum.CONTAINER, + appType: enumAppType.JUPYTER, + userId: userProfile.id, + status: enumInstanceStatus.STOPPED + }); + + const response = await user.post('/trpc/instance.restartInstance').send({ + instanceId, + lifeSpan: 7200 + }); + + expect(response.status).toBe(200); + + const instance = await db.collections.instance_collection.findOne({ id: instanceId }); + expect(instance?.status).toBe(enumInstanceStatus.STARTING); + expect(instance?.lifeSpan).toBe(7200); + }); + }); + + describe('getInstances', () => { + test('Logged-in user should be able to retrieve their own instances', async () => { + // Insert an instance for the logged-in user + await db.collections.instance_collection.insertOne({ + id: uuid(), + name: 'user-instance', + type: LXDInstanceTypeEnum.CONTAINER, + appType: enumAppType.JUPYTER, + userId: userProfile.id, // Belongs to the logged-in user + status: enumInstanceStatus.RUNNING, + config: { + 'limits.cpu': '2', + 'limits.memory': '4GB' + } + }); + + // The logged-in user should retrieve their own instance + const response = await user.get('/trpc/instance.getInstances').send(); + expect(response.status).toBe(200); + expect(response.body.result.data).toHaveLength(1); // Only the user's instance + expect(response.body.result.data[0].name).toBe('user-instance'); + }); + + test('Non-logged-in user should not be able to retrieve any instances', async () => { + // Try to retrieve instances without being logged in + const response = await request(app).get('/trpc/instance.getInstances').send(); + + // Ensure the user gets a 400 Unauthorized error + expect(response.status).toBe(400); // Unauthorized + expect(response.body.error).toBeDefined(); + expect(response.body.error.message).toBe('Insufficient permissions.'); + }); + }); + + describe('deleteInstance', () => { + test('Admin should delete an instance successfully', async () => { + const instanceId = uuid(); + await db.collections.instance_collection.insertOne({ + id: instanceId, + name: 'instance-to-delete', + type: LXDInstanceTypeEnum.CONTAINER, + appType: enumAppType.JUPYTER, + userId: adminProfile.id, + status: enumInstanceStatus.RUNNING + }); + + const response = await admin.post('/trpc/instance.deleteInstance').send({ + instanceId + }); + + expect(response.status).toBe(200); + + const instance = await db.collections.instance_collection.findOne({ id: instanceId }); + expect(instance?.status).toBe(enumInstanceStatus.DELETED); + }); + }); + + }); +} else { + test(`${__filename.split(/[\\/]/).pop()} skipped because it requires Minio on Docker`, () => { + expect(true).toBe(true); + }); +} diff --git a/packages/itmat-interface/test/trpcTests/log.test.ts b/packages/itmat-interface/test/trpcTests/log.test.ts new file mode 100644 index 000000000..35d7a05ac --- /dev/null +++ b/packages/itmat-interface/test/trpcTests/log.test.ts @@ -0,0 +1,135 @@ +/** + * @with Minio + */ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +import { v4 as uuid } from 'uuid'; +import request from 'supertest'; +import { connectAdmin, connectUser } from './_loginHelper'; +import { db } from '../../src/database/database'; +import { Router } from '../../src/server/router'; +import { Db, MongoClient } from 'mongodb'; +import { MongoMemoryServer } from 'mongodb-memory-server'; +import config from '../../config/config.sample.json'; +import { enumCoreErrors, enumEventStatus } from '@itmat-broker/itmat-types'; +import { Express } from 'express'; +import { setupDatabase } from '@itmat-broker/itmat-setup'; +import { encodeQueryParams } from './helper'; +import path from 'path'; + +if (global.hasMinio) { + let app: Express; + let mongodb: MongoMemoryServer; + let admin: request.SuperTest; + let user: request.SuperTest; + let mongoConnection: MongoClient; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let mongoClient: Db; + + afterAll(async () => { + await db.closeConnection(); + await mongoConnection?.close(); + await mongodb.stop(); + + /* claer all mocks */ + jest.clearAllMocks(); + }); + + beforeAll(async () => { // eslint-disable-line no-undef + /* Creating a in-memory MongoDB instance for testing */ + const dbName = uuid(); + mongodb = await MongoMemoryServer.create({ instance: { dbName } }); + const connectionString = mongodb.getUri(); + await setupDatabase(connectionString, dbName); + + /* Wiring up the backend server */ + config.database.mongo_url = connectionString; + config.database.database = dbName; + await db.connect(config.database, MongoClient); + const router = new Router(config); + await router.init(); + + /* Connect mongo client (for test setup later / retrieve info later) */ + mongoConnection = await MongoClient.connect(connectionString); + mongoClient = mongoConnection.db(dbName); + + /* Connecting clients for testing later */ + app = router.getApp(); + admin = request.agent(app); + user = request.agent(app); + await connectAdmin(admin); + await connectUser(user); + + /* Mock Date for testing */ + jest.spyOn(Date, 'now').mockImplementation(() => 1591134065000); + }); + + afterEach(async () => { + await db.collections.log_collection.deleteMany({}); + }); + + describe('tRPC log APIs', () => { + test('Write log (query)', async () => { + // query API + const paramteres = { + }; + await user.get('/trpc/study.getStudies?input=' + encodeQueryParams(paramteres)) + .query({}); + const log = await db.collections.log_collection.find({}).toArray(); + // start server; two login logs, and this new log + expect(log).toHaveLength(3); + expect(log[2].event).toBe('study.getStudies'); + }); + test('Write log (mutation)', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + await request; + const log = await db.collections.log_collection.find({}).toArray(); + expect(log).toHaveLength(1); + expect(log[0].event).toBe('study.createStudy'); + }); + test('Write log (with errors)', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = user.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + await request; + const log = await db.collections.log_collection.find({}).toArray(); + expect(log).toHaveLength(1); + expect(log[0].event).toBe('study.createStudy'); + expect(log[0].status).toBe(enumEventStatus.FAIL); + expect(log[0].errors).toBe('Only admin can create a study.'); + }); + test('Get log (admin)', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + await request; + + const paramteres = { + }; + const response = await admin.get('/trpc/log.getLogs?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(200); + expect(response.body.result.data).toHaveLength(1); + }); + test('Get log (user)', async () => { + const paramteres = { + }; + const response = await user.get('/trpc/log.getLogs?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + }); +} else { + test(`${__filename.split(/[\\/]/).pop()} skipped because it requires Minio on Docker`, () => { + expect(true).toBe(true); + }); +} diff --git a/packages/itmat-interface/test/trpcTests/organisation.test.ts b/packages/itmat-interface/test/trpcTests/organisation.test.ts new file mode 100644 index 000000000..95c475e93 --- /dev/null +++ b/packages/itmat-interface/test/trpcTests/organisation.test.ts @@ -0,0 +1,198 @@ +/** + * @with Minio + */ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +import { v4 as uuid } from 'uuid'; +import request from 'supertest'; +import { connectAdmin, connectUser } from './_loginHelper'; +import { db } from '../../src/database/database'; +import { Router } from '../../src/server/router'; +import { Db, MongoClient } from 'mongodb'; +import { MongoMemoryServer } from 'mongodb-memory-server'; +import config from '../../config/config.sample.json'; +import { objStore } from '../../src/objStore/objStore'; +import { enumCoreErrors } from '@itmat-broker/itmat-types'; +import { Express } from 'express'; +import { setupDatabase } from '@itmat-broker/itmat-setup'; +import { encodeQueryParams } from './helper'; +import path from 'path'; +import { seedOrganisations } from 'packages/itmat-setup/src/databaseSetup/seed/organisations'; +import { getRequiredMinioPort } from './types'; + +if (global.hasMinio) { + let app: Express; + let mongodb: MongoMemoryServer; + let admin: request.SuperTest; + let user: request.SuperTest; + let mongoConnection: MongoClient; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let mongoClient: Db; + + afterAll(async () => { + await db.closeConnection(); + await mongoConnection?.close(); + await mongodb.stop(); + + /* claer all mocks */ + jest.clearAllMocks(); + }); + + beforeAll(async () => { // eslint-disable-line no-undef + /* Creating a in-memory MongoDB instance for testing */ + const dbName = uuid(); + mongodb = await MongoMemoryServer.create({ instance: { dbName } }); + const connectionString = mongodb.getUri(); + await setupDatabase(connectionString, dbName); + /* Wiring up the backend server */ + config.objectStore.port = getRequiredMinioPort(); + config.database.mongo_url = connectionString; + config.database.database = dbName; + await db.connect(config.database, MongoClient); + await objStore.connect(config.objectStore); + const router = new Router(config); + await router.init(); + /* Connect mongo client (for test setup later / retrieve info later) */ + mongoConnection = await MongoClient.connect(connectionString); + mongoClient = mongoConnection.db(dbName); + + /* Connecting clients for testing later */ + app = router.getApp(); + admin = request.agent(app); + user = request.agent(app); + await connectAdmin(admin); + await connectUser(user); + + /* Mock Date for testing */ + jest.spyOn(Date, 'now').mockImplementation(() => 1591134065000); + }); + + afterEach(async () => { + await db.collections.organisations_collection.deleteMany({}); + await db.collections.organisations_collection.insertMany(seedOrganisations); + await db.collections.files_collection.deleteMany({}); + }); + + describe('tRPC organisation APIs', () => { + test('Get organisations (admin)', async () => { + const paramteres = { + }; + const response = await admin.get('/trpc/organisation.getOrganisations?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(200); + expect(response.body.result.data).toHaveLength(2); + }); + test('Get organisations with organisationId (admin)', async () => { + const paramteres = { + organisationId: 'organisation_system' + }; + const response = await admin.get('/trpc/organisation.getOrganisations?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(200); + expect(response.body.result.data).toHaveLength(1); + expect(response.body.result.data[0].id).toBe('organisation_system'); + }); + test('Get organisations (user)', async () => { + const paramteres = { + }; + const response = await user.get('/trpc/organisation.getOrganisations?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(200); + expect(response.body.result.data).toHaveLength(2); + }); + test('Get organisations with organisationId (user)', async () => { + const paramteres = { + organisationId: 'organisation_system' + }; + const response = await user.get('/trpc/organisation.getOrganisations?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response.status).toBe(200); + expect(response.body.result.data).toHaveLength(1); + expect(response.body.result.data[0].id).toBe('organisation_system'); + }); + test('Create organisation (admin)', async () => { + const response = await admin.post('/trpc/organisation.createOrganisation') + .field('name', 'test') + .field('shortname', 'tst'); + expect(response.status).toBe(200); + expect(response.body.result.data.name).toBe('test'); + expect(response.body.result.data.shortname).toBe('tst'); + const orgs = await db.collections.organisations_collection.find({}).toArray(); + expect(orgs).toHaveLength(3); + }); + test('Create organisation (user)', async () => { + const response = await user.post('/trpc/organisation.createOrganisation') + .field('name', 'test') + .field('shortname', 'tst'); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('Create organisation (admin) with files', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const response = await admin.post('/trpc/organisation.createOrganisation') + .attach('profile', filePath) + .field('name', 'test') + .field('shortname', 'tst'); + expect(response.status).toBe(200); + expect(response.body.result.data.name).toBe('test'); + expect(response.body.result.data.shortname).toBe('tst'); + expect(response.body.result.data.profile).toBeDefined(); + const orgs = await db.collections.organisations_collection.find({}).toArray(); + expect(orgs).toHaveLength(3); + const files = await db.collections.files_collection.find({}).toArray(); + expect(files).toHaveLength(1); + expect(files[0].id).toBe(response.body.result.data.profile); + }); + test('Create organisation (admin) duplicate name', async () => { + const response = await admin.post('/trpc/organisation.createOrganisation') + .field('name', 'System') + .field('shortname', 'tst'); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Organisation already exists.'); + }); + test('Edit organisation (admin)', async () => { + const response = await admin.post('/trpc/organisation.editOrganisation') + .field('organisationId', 'organisation_system') + .field('name', 'test') + .field('shortname', 'tst'); + expect(response.status).toBe(200); + expect(response.body.result.data.successful).toBe(true); + const org = await db.collections.organisations_collection.findOne({ id: 'organisation_system' }); + expect(org.name).toBe('test'); + }); + test('Edit organisation (user)', async () => { + const response = await user.post('/trpc/organisation.editOrganisation') + .field('organisationId', 'organisation_system') + .field('name', 'test') + .field('shortname', 'tst'); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('Edit organisation (admin) duplicate name', async () => { + const response = await admin.post('/trpc/organisation.editOrganisation') + .field('organisationId', 'organisation_system') + .field('name', 'user') + .field('shortname', 'tst'); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Organisation already exists.'); + }); + test('Delete organisation (admin)', async () => { + const response = await admin.post('/trpc/organisation.deleteOrganisation') + .field('organisationId', 'organisation_system'); + expect(response.status).toBe(200); + expect(response.body.result.data.successful).toBe(true); + const org = await db.collections.organisations_collection.findOne({ id: 'organisation_system' }); + expect(org.life.deletedTime).toBeDefined(); + }); + test('Delete organisation (user)', async () => { + const response = await user.post('/trpc/organisation.deleteOrganisation') + .field('organisationId', 'organisation_system'); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + }); +} else { + test(`${__filename.split(/[\\/]/).pop()} skipped because it requires Minio on Docker`, () => { + expect(true).toBe(true); + }); +} diff --git a/packages/itmat-interface/test/trpcTests/role.test.ts b/packages/itmat-interface/test/trpcTests/role.test.ts new file mode 100644 index 000000000..3b0c5e410 --- /dev/null +++ b/packages/itmat-interface/test/trpcTests/role.test.ts @@ -0,0 +1,337 @@ +/** + * @with Minio + */ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +import { MongoMemoryServer } from 'mongodb-memory-server'; +import { db } from '../../src/database/database'; +import { Express } from 'express'; +import { objStore } from '../../src/objStore/objStore'; +import request from 'supertest'; +import { connectAdmin, connectUser, connectAgent } from './_loginHelper'; +import { Router } from '../../src/server/router'; +import { MongoClient } from 'mongodb'; +import { setupDatabase } from '@itmat-broker/itmat-setup'; +import config from '../../config/config.sample.json'; +import { v4 as uuid } from 'uuid'; +import { enumUserTypes, enumStudyRoles, enumCoreErrors } from '@itmat-broker/itmat-types'; +import { encodeQueryParams } from './helper'; +import { getRequiredMinioPort } from './types'; + +if (global.hasMinio) { + let app: Express; + let mongodb: MongoMemoryServer; + let admin: request.SuperTest; + let user: request.SuperTest; + let authorisedUser: request.SuperTest; + let mongoConnection: MongoClient; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let mongoClient: Db; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let adminProfile; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let userProfile; + let authorisedUserProfile; + let study; + let fullPermissionRole: IRole; + + afterAll(async () => { + await db.closeConnection(); + await mongoConnection?.close(); + await mongodb.stop(); + + /* claer all mocks */ + jest.clearAllMocks(); + }); + + beforeAll(async () => { // eslint-disable-line no-undef + /* Creating a in-memory MongoDB instance for testing */ + const dbName = uuid(); + mongodb = await MongoMemoryServer.create({ instance: { dbName } }); + const connectionString = mongodb.getUri(); + await setupDatabase(connectionString, dbName); + /* Wiring up the backend server */ + config.objectStore.port = getRequiredMinioPort(); + config.database.mongo_url = connectionString; + config.database.database = dbName; + await db.connect(config.database, MongoClient); + await objStore.connect(config.objectStore); + const router = new Router(config); + await router.init(); + /* Connect mongo client (for test setup later / retrieve info later) */ + mongoConnection = await MongoClient.connect(connectionString); + mongoClient = mongoConnection.db(dbName); + + /* Connecting clients for testing later */ + app = router.getApp(); + admin = request.agent(app); + user = request.agent(app); + await connectAdmin(admin); + await connectUser(user); + + // add the root node for each user + const users = await db.collections.users_collection.find({}).toArray(); + adminProfile = users.filter(el => el.type === enumUserTypes.ADMIN)[0]; + userProfile = users.filter(el => el.type === enumUserTypes.STANDARD)[0]; + + const username = uuid(); + authorisedUserProfile = { + username, + type: enumUserTypes.STANDARD, + firstname: `${username}_firstname`, + lastname: `${username}_lastname`, + password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', + otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', + email: `${username}@example.com`, + description: 'I am a new user.', + emailNotificationsActivated: true, + organisation: 'organisation_system', + id: `new_user_id_${username}`, + emailNotificationsStatus: { expiringNotification: false }, + resetPasswordRequests: [], + expiredAt: 1991134065000, + life: { + createdTime: 1591134065000, + createdUser: 'admin', + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + await mongoClient.collection(config.database.collections.users_collection).insertOne(authorisedUserProfile); + authorisedUser = request.agent(app); + await connectAgent(authorisedUser, username, 'admin', authorisedUserProfile.otpSecret); + }); + + beforeEach(async () => { + study = { + id: uuid(), + name: 'Test Study', + currentDataVersion: -1, // index; dataVersions[currentDataVersion] gives current version; // -1 if no data + dataVersions: [], + description: null, + profile: null, + webLayout: [], + life: { + createdTime: 0, + createdUser: enumUserTypes.SYSTEM, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + await db.collections.studies_collection.insertOne(study); + fullPermissionRole = { + id: 'full_permission_role_id', + studyId: study.id, + name: 'Full Permissison Role', + description: '', + // data permissions for studyId + dataPermissions: [{ + fields: ['^1.*$'], + dataProperties: { + }, + permission: parseInt('111', 2), + includeUnVersioned: true + }], + studyRole: enumStudyRoles.STUDY_MANAGER, + users: [authorisedUserProfile.id], + groups: [], + life: { + createdTime: 0, + createdUser: 'admin', + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + await db.collections.roles_collection.insertOne(fullPermissionRole); + }); + + afterEach(async () => { + await db.collections.studies_collection.deleteMany({}); + await db.collections.field_dictionary_collection.deleteMany({}); + await db.collections.data_collection.deleteMany({}); + await db.collections.roles_collection.deleteMany({}); + await db.collections.files_collection.deleteMany({}); + await db.collections.cache_collection.deleteMany({}); + await db.collections.configs_collection.deleteMany({}); + }); + + describe('tRPC data APIs', () => { + test('getUserRoles (admin)', async () => { + const parameters = { userId: authorisedUserProfile.id }; + const response = await admin.get('/trpc/role.getUserRoles?input=' + encodeQueryParams(parameters)); + expect(response.status).toBe(200); + expect(response.body.result.data).toHaveLength(1); + expect(response.body.result.data[0].id).toBe(fullPermissionRole.id); + }); + test('getUserRoles (authorised user)', async () => { + const parameters = { userId: authorisedUserProfile.id }; + const response = await authorisedUser.get('/trpc/role.getUserRoles?input=' + encodeQueryParams(parameters)); + expect(response.status).toBe(200); + expect(response.body.result.data).toHaveLength(1); + expect(response.body.result.data[0].id).toBe(fullPermissionRole.id); + }); + test('getUserRoles (with studyId)', async () => { + const parameters = { userId: authorisedUserProfile.id, studyId: study.id }; + const response = await authorisedUser.get('/trpc/role.getUserRoles?input=' + encodeQueryParams(parameters)); + expect(response.status).toBe(200); + expect(response.body.result.data).toHaveLength(1); + expect(response.body.result.data[0].id).toBe(fullPermissionRole.id); + }); + test('getUserRoles (with incorrect study Id)', async () => { + const parameters = { userId: authorisedUserProfile.id, studyId: 'incorrect' }; + const response = await authorisedUser.get('/trpc/role.getUserRoles?input=' + encodeQueryParams(parameters)); + expect(response.status).toBe(200); + expect(response.body.result.data).toHaveLength(0); + }); + test('getUserRoles (unauthorised user)', async () => { + const parameters = { userId: authorisedUserProfile.id }; + const response = await user.get('/trpc/role.getUserRoles?input=' + encodeQueryParams(parameters)); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('getStudyRoles (admin)', async () => { + const parameters = { studyId: study.id }; + const response = await admin.get('/trpc/role.getStudyRoles?input=' + encodeQueryParams(parameters)); + expect(response.status).toBe(200); + expect(response.body.result.data).toHaveLength(1); + expect(response.body.result.data[0].id).toBe(fullPermissionRole.id); + }); + test('getStudyRoles (authorised user)', async () => { + const parameters = { studyId: study.id }; + const response = await authorisedUser.get('/trpc/role.getStudyRoles?input=' + encodeQueryParams(parameters)); + expect(response.status).toBe(200); + expect(response.body.result.data).toHaveLength(1); + expect(response.body.result.data[0].id).toBe(fullPermissionRole.id); + }); + test('getStudyRoles (unauthorised user)', async () => { + const parameters = { studyId: study.id }; + const response = await user.get('/trpc/role.getStudyRoles?input=' + encodeQueryParams(parameters)); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('createStudyRole (admin)', async () => { + const response = await admin.post('/trpc/role.createStudyRole').send({ + studyId: study.id, + name: 'New Role', + description: 'New Role Description', + dataPermissions: [{ + fields: ['^1.*$'], + dataProperties: {}, + permission: parseInt('111', 2), + includeUnVersioned: true + }], + studyRole: enumStudyRoles.STUDY_MANAGER, + users: [authorisedUserProfile.id] + }); + expect(response.status).toBe(200); + const roleInDb = await db.collections.roles_collection.findOne({ name: 'New Role' }); + expect(roleInDb?.id).toBe(response.body.result.data.id); + }); + test('createStudyRole (authorised user)', async () => { + const response = await authorisedUser.post('/trpc/role.createStudyRole').send({ + studyId: study.id, + name: 'New Role', + description: 'New Role Description', + dataPermissions: [{ + fields: ['^1.*$'], + dataProperties: {}, + permission: parseInt('111', 2), + includeUnVersioned: true + }], + studyRole: enumStudyRoles.STUDY_MANAGER, + users: [authorisedUserProfile.id] + }); + expect(response.status).toBe(200); + const roleInDb = await db.collections.roles_collection.findOne({ name: 'New Role' }); + expect(roleInDb?.id).toBe(response.body.result.data.id); + }); + test('createStudyRole (unauthorised user)', async () => { + const response = await user.post('/trpc/role.createStudyRole').send({ + studyId: study.id, + name: 'New Role', + description: 'New Role Description', + dataPermissions: [{ + fields: ['^1.*$'], + dataProperties: {}, + permission: parseInt('111', 2), + includeUnVersioned: true + }], + studyRole: enumStudyRoles.STUDY_MANAGER, + users: [authorisedUserProfile.id] + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('editStudyRole (admin)', async () => { + const response = await admin.post('/trpc/role.editStudyRole').send({ + roleId: fullPermissionRole.id, + name: 'Edited Role Name' + }); + expect(response.status).toBe(200); + const roleInDb = await db.collections.roles_collection.findOne({ id: fullPermissionRole.id }); + expect(roleInDb?.name).toBe('Edited Role Name'); + }); + test('editStudyRole (authorised user)', async () => { + const response = await authorisedUser.post('/trpc/role.editStudyRole').send({ + roleId: fullPermissionRole.id, + name: 'Edited Role Name' + }); + expect(response.status).toBe(200); + const roleInDb = await db.collections.roles_collection.findOne({ id: fullPermissionRole.id }); + expect(roleInDb?.name).toBe('Edited Role Name'); + }); + test('editStudyRole (unauthorised user)', async () => { + const response = await user.post('/trpc/role.editStudyRole').send({ + roleId: fullPermissionRole.id, + name: 'Edited Role Name' + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('editStudyRole (invalid roleId)', async () => { + const response = await user.post('/trpc/role.editStudyRole').send({ + roleId: 'random', + name: 'Edited Role Name' + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('deleteStudyRole (admin)', async () => { + const response = await admin.post('/trpc/role.deleteStudyRole').send({ + roleId: fullPermissionRole.id + }); + expect(response.status).toBe(200); + const roleInDb = await db.collections.roles_collection.findOne({ id: fullPermissionRole.id }); + expect(roleInDb?.life.deletedTime).not.toBeNull(); + }); + test('deleteStudyRole (authorised user)', async () => { + const response = await authorisedUser.post('/trpc/role.deleteStudyRole').send({ + roleId: fullPermissionRole.id + }); + expect(response.status).toBe(200); + const roleInDb = await db.collections.roles_collection.findOne({ id: fullPermissionRole.id }); + expect(roleInDb?.life.deletedTime).not.toBeNull(); + }); + test('deleteStudyRole (unauthorised user)', async () => { + const response = await user.post('/trpc/role.deleteStudyRole').send({ + roleId: fullPermissionRole.id + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + test('deleteStudyRole (invalid roleId)', async () => { + const response = await user.post('/trpc/role.deleteStudyRole').send({ + roleId: 'random' + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(enumCoreErrors.NO_PERMISSION_ERROR); + }); + }); +} else { + test(`${__filename.split(/[\\/]/).pop()} skipped because it requires Minio on Docker`, () => { + expect(true).toBe(true); + }); +} diff --git a/packages/itmat-interface/test/trpcTests/study.test.ts b/packages/itmat-interface/test/trpcTests/study.test.ts new file mode 100644 index 000000000..203624f15 --- /dev/null +++ b/packages/itmat-interface/test/trpcTests/study.test.ts @@ -0,0 +1,887 @@ +/** + * @with Minio + */ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +import { MongoMemoryServer } from 'mongodb-memory-server'; +import { db } from '../../src/database/database'; +import { Express } from 'express'; +import { objStore } from '../../src/objStore/objStore'; +import request from 'supertest'; +import { connectAdmin, connectUser, connectAgent } from './_loginHelper'; +import { Router } from '../../src/server/router'; +import { Db, MongoClient } from 'mongodb'; +import { setupDatabase } from '@itmat-broker/itmat-setup'; +import config from '../../config/config.sample.json'; +import { v4 as uuid } from 'uuid'; +import { enumUserTypes, enumStudyRoles, IUser, IRole, enumDataTypes } from '@itmat-broker/itmat-types'; +import path from 'path'; +import { encodeQueryParams } from './helper'; +import { getRequiredMinioPort } from './types'; +if (global.hasMinio) { + let app: Express; + let mongodb: MongoMemoryServer; + let admin: request.SuperTest; + let user: request.SuperTest; + let authorisedUser: request.SuperTest; + let mongoConnection: MongoClient; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let mongoClient: Db; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let adminProfile: IUser; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let userProfile: IUser; + let authorisedUserProfile: IUser; + + afterAll(async () => { + await db.closeConnection(); + await mongoConnection?.close(); + await mongodb.stop(); + + /* claer all mocks */ + jest.clearAllMocks(); + }); + + beforeAll(async () => { // eslint-disable-line no-undef + /* Creating a in-memory MongoDB instance for testing */ + const dbName = uuid(); + mongodb = await MongoMemoryServer.create({ instance: { dbName } }); + const connectionString = mongodb.getUri(); + await setupDatabase(connectionString, dbName); + /* Wiring up the backend server */ + config.objectStore.port = getRequiredMinioPort(); + config.database.mongo_url = connectionString; + config.database.database = dbName; + await db.connect(config.database, MongoClient); + await objStore.connect(config.objectStore); + const router = new Router(config); + await router.init(); + /* Connect mongo client (for test setup later / retrieve info later) */ + mongoConnection = await MongoClient.connect(connectionString); + mongoClient = mongoConnection.db(dbName); + + /* Connecting clients for testing later */ + app = router.getApp(); + admin = request.agent(app); + user = request.agent(app); + await connectAdmin(admin); + await connectUser(user); + + // add the root node for each user + const users = await db.collections.users_collection.find({}).toArray(); + adminProfile = users.filter(el => el.type === enumUserTypes.ADMIN)[0]; + userProfile = users.filter(el => el.type === enumUserTypes.STANDARD)[0]; + + /* setup: creating a privileged user */ + const username = uuid(); + authorisedUserProfile = { + username, + type: enumUserTypes.STANDARD, + firstname: `${username}_firstname`, + lastname: `${username}_lastname`, + password: '$2b$04$j0aSK.Dyq7Q9N.r6d0uIaOGrOe7sI4rGUn0JNcaXcPCv.49Otjwpi', + otpSecret: 'H6BNKKO27DPLCATGEJAZNWQV4LWOTMRA', + email: `${username}@example.com`, + description: 'I am a new user.', + emailNotificationsActivated: true, + organisation: 'organisation_system', + id: `new_user_id_${username}`, + emailNotificationsStatus: { expiringNotification: false }, + resetPasswordRequests: [], + expiredAt: 1991134065000, + life: { + createdTime: 1591134065000, + createdUser: 'admin', + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + await mongoClient.collection(config.database.collections.users_collection).insertOne(authorisedUserProfile); + + const roleId = uuid(); + const newRole = { + id: roleId, + studyId: '', + name: `${roleId}_rolename`, + dataPermissions: [{ + fields: ['^.*$'], + dataProperties: {}, + includeUnVersioned: true, + permission: 7 + }], + studyRole: enumStudyRoles.STUDY_MANAGER, + users: [authorisedUserProfile.id], + life: { + createdTime: 1591134065000, + createdUser: 'admin', + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + await mongoClient.collection(config.database.collections.roles_collection).insertOne(newRole); + + authorisedUser = request.agent(app); + await connectAgent(authorisedUser, username, 'admin', authorisedUserProfile.otpSecret); + }); + + afterEach(async () => { + await db.collections.studies_collection.deleteMany({}); + await db.collections.files_collection.deleteMany({}); + await db.collections.field_dictionary_collection.deleteMany({}); + await db.collections.data_collection.deleteMany({}); + }); + + describe('tRPC Study APIs', () => { + test('Create a study', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + const response = await request; + expect(response.status).toBe(200); + expect(response.body.result.data.name).toBe('Test Study'); + const file = await db.collections.files_collection.findOne({}); + expect(file?.id).toBe(response.body.result.data.profile); + const study = await db.collections.studies_collection.findOne({}); + expect(study?.id).toBe(response.body.result.data.id); + }); + test('Create a study (no permission)', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = user.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + const response = await request; + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Only admin can create a study.'); + }); + test('Create a study (duplicates names)', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + await request; + const request2 = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + const response2 = await request2; + expect(response2.status).toBe(400); + expect(response2.body.error.message).toBe('Study name already used.'); + }); + test('Edit a study', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + const response1 = await request; + const request2 = admin.post('/trpc/study.editStudy') + .field('studyId', response1.body.result.data.id) + .attach('profile', filePath) + .field('name', 'Test Study edited') + .field('description', ''); + const response2 = await request2; + expect(response2.status).toBe(200); + expect(response2.body.result.data.name).toBe('Test Study edited'); + const files = await db.collections.files_collection.find({}).toArray(); + expect(files).toHaveLength(2); + expect(files[1]?.id).toBe(response2.body.result.data.profile); + const study = await db.collections.studies_collection.findOne({}); + expect(study?.id).toBe(response2.body.result.data.id); + }); + test('Edit a study (authorised study manager)', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + const response1 = await request; + await db.collections.roles_collection.updateMany({}, { $set: { studyId: response1.body.result.data.id } }); + const request2 = authorisedUser.post('/trpc/study.editStudy') + .field('studyId', response1.body.result.data.id) + .attach('profile', filePath) + .field('name', 'Test Study edited') + .field('description', ''); + const response2 = await request2; + expect(response2.status).toBe(200); + expect(response2.body.result.data.name).toBe('Test Study edited'); + const files = await db.collections.files_collection.find({}).toArray(); + expect(files).toHaveLength(2); + expect(files[1]?.id).toBe(response2.body.result.data.profile); + const study = await db.collections.studies_collection.findOne({}); + expect(study?.id).toBe(response2.body.result.data.id); + + // clear database + await db.collections.roles_collection.updateMany({}, { $set: { studyId: '' } }); + }); + test('Edit a study (no permission)', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + const response1 = await request; + const request2 = user.post('/trpc/study.editStudy') + .field('studyId', response1.body.result.data.id) + .attach('profile', filePath) + .field('name', 'Test Study edited') + .field('description', ''); + const response2 = await request2; + expect(response2.status).toBe(400); + expect(response2.body.error.message).toBe('Only admin or study manager can edit a study.'); + }); + test('Edit a study (study does not exist)', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.editStudy') + .attach('profile', filePath) + .field('name', 'Test Study edited') + .field('description', '') + .field('studyId', 'random'); + const response = await request; + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Study does not exist.'); + }); + test('Edit a study (used name)', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request1 = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + const response1 = await request1; + const request2 = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study 2') + .field('description', ''); + await request2; + const request3 = admin.post('/trpc/study.editStudy') + .field('studyId', response1.body.result.data.id) + .attach('profile', filePath) + .field('name', 'Test Study 2') + .field('description', ''); + const response3 = await request3; + expect(response3.status).toBe(400); + expect(response3.body.error.message).toBe('Study name already used.'); + }); + test('Delete a study', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + const response1 = await request; + const response2 = await admin.post('/trpc/study.deleteStudy') + .send({ + studyId: response1.body.result.data.id + }); + expect(response2.status).toBe(200); + expect(response2.body.result.data.id).toBe(response1.body.result.data.id); + }); + test('Delete a study (no permission)', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + const response1 = await request; + const response2 = await user.post('/trpc/study.deleteStudy') + .send({ + studyId: response1.body.result.data.id + }); + expect(response2.status).toBe(400); + expect(response2.body.error.message).toBe('Only admin can delete a study.'); + }); + test('Delete a study (study does not exist)', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + await request; + const response2 = await admin.post('/trpc/study.deleteStudy') + .send({ + studyId: 'random' + }); + expect(response2.status).toBe(400); + expect(response2.body.error.message).toBe('Study does not exist.'); + }); + test('Get studies', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + const response1 = await request; + await db.collections.roles_collection.updateMany({}, { $set: { studyId: response1.body.result.data.id } }); + const paramteres = { + }; + const response2 = await authorisedUser.get('/trpc/study.getStudies?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response2.status).toBe(200); + expect(response2.body.result.data).toHaveLength(1); + expect(response2.body.result.data[0].id).toBe(response1.body.result.data.id); + }); + test('Get studies (with valid study id)', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + const response1 = await request; + await db.collections.roles_collection.updateMany({}, { $set: { studyId: response1.body.result.data.id } }); + const paramteres = { + studyId: response1.body.result.data.id + }; + const response2 = await authorisedUser.get('/trpc/study.getStudies?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response2.status).toBe(200); + expect(response2.body.result.data).toHaveLength(1); + expect(response2.body.result.data[0].id).toBe(response1.body.result.data.id); + }); + test('Get studies (with invalid study id)', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + const response1 = await request; + await db.collections.roles_collection.updateMany({}, { $set: { studyId: response1.body.result.data.id } }); + const paramteres = { + studyId: 'random' + }; + const response2 = await authorisedUser.get('/trpc/study.getStudies?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response2.status).toBe(400); + expect(response2.body.error.message).toBe('No permission to access the study.'); + }); + test('Get studies (unpermitted user)', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + const response1 = await request; + await db.collections.roles_collection.updateMany({}, { $set: { studyId: response1.body.result.data.id } }); + const paramteres = { + }; + const response2 = await user.get('/trpc/study.getStudies?input=' + encodeQueryParams(paramteres)) + .query({}); + expect(response2.status).toBe(200); + expect(response2.body.result.data).toHaveLength(0); + }); + test('Create a new data version', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + const response1 = await request; + await db.collections.roles_collection.updateMany({}, { $set: { studyId: response1.body.result.data.id } }); + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: response1.body.result.data.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: response1.body.result.data.id, + data: [{ + fieldId: '1', + value: '10' + }] + }); + const response2 = await authorisedUser.post('/trpc/study.createDataVersion') + .send({ + studyId: response1.body.result.data.id, + dataVersion: '1.0', + tag: '1.0' + }); + expect(response2.status).toBe(200); + expect(response2.body.result.data.version).toBe('1.0'); + const data = await db.collections.data_collection.findOne({}); + const field = await db.collections.field_dictionary_collection.findOne({}); + const study = await db.collections.studies_collection.findOne({}); + expect(field?.dataVersion).toBe(response2.body.result.data.id); + expect(data?.dataVersion).toBe(response2.body.result.data.id); + expect(study?.currentDataVersion).toBe(0); + expect(study?.dataVersions).toHaveLength(1); + expect(study?.dataVersions[0].id).toBe(response2.body.result.data.id); + // check cold storage + // const colddata = await db.collections.colddata_collection.findOne({}); + // expect(colddata?.dataVersion).toBe(response2.body.result.data.id); + }); + test('Create a new data version (no permission)', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + const response1 = await request; + const fullPermissionRole: IRole = { + id: 'full_permission_role_id', + studyId: response1.body.result.data.id, + name: 'Full Permissison Role', + description: '', + // data permissions for studyId + dataPermissions: [{ + fields: ['^1.*$'], + dataProperties: { + }, + permission: parseInt('110', 2) + }], + studyRole: enumStudyRoles.STUDY_MANAGER, + users: [authorisedUserProfile.id], + life: { + createdTime: 1591134065000, + createdUser: 'admin', + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + await db.collections.roles_collection.insertOne(fullPermissionRole); + await user.post('/trpc/data.createStudyField') + .send({ + studyId: response1.body.result.data.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + await user.post('/trpc/data.uploadStudyData') + .send({ + studyId: response1.body.result.data.id, + data: [{ + fieldId: '1', + value: '10' + }] + }); + const response2 = await user.post('/trpc/study.createDataVersion') + .send({ + studyId: response1.body.result.data.id, + dataVersion: '1.0', + tag: '1.0' + }); + expect(response2.status).toBe(400); + expect(response2.body.error.message).toBe('Only admin or study manager can create a study version.'); + // clear database + await db.collections.roles_collection.deleteMany({ id: fullPermissionRole.id }); + }); + test('Create a new data version (version is not float string)', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + const response1 = await request; + const fullPermissionRole: IRole = { + id: 'full_permission_role_id', + studyId: response1.body.result.data.id, + name: 'Full Permissison Role', + description: '', + // data permissions for studyId + dataPermissions: [{ + fields: ['^1.*$'], + dataProperties: { + }, + permission: parseInt('110', 2) + }], + studyRole: enumStudyRoles.STUDY_MANAGER, + users: [authorisedUserProfile.id], + life: { + createdTime: 1591134065000, + createdUser: 'admin', + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + await db.collections.roles_collection.insertOne(fullPermissionRole); + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: response1.body.result.data.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: response1.body.result.data.id, + data: [{ + fieldId: '1', + value: '10' + }] + }); + const response2 = await authorisedUser.post('/trpc/study.createDataVersion') + .send({ + studyId: response1.body.result.data.id, + dataVersion: '1.0.5', + tag: '1.0' + }); + expect(response2.status).toBe(400); + expect(response2.body.error.message).toBe('Version must be a float number.'); + // clear database + await db.collections.roles_collection.deleteMany({ id: fullPermissionRole.id }); + }); + test('Create a new data version (duplicate versions)', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + const response1 = await request; + const fullPermissionRole: IRole = { + id: 'full_permission_role_id', + studyId: response1.body.result.data.id, + name: 'Full Permissison Role', + description: '', + // data permissions for studyId + dataPermissions: [{ + fields: ['^1.*$'], + dataProperties: { + }, + permission: parseInt('110', 2) + }], + studyRole: enumStudyRoles.STUDY_MANAGER, + users: [authorisedUserProfile.id], + life: { + createdTime: 1591134065000, + createdUser: 'admin', + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + await db.collections.roles_collection.insertOne(fullPermissionRole); + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: response1.body.result.data.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: response1.body.result.data.id, + data: [{ + fieldId: '1', + value: '10' + }] + }); + await authorisedUser.post('/trpc/study.createDataVersion') + .send({ + studyId: response1.body.result.data.id, + dataVersion: '1.0', + tag: '1.0' + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: response1.body.result.data.id, + data: [{ + fieldId: '1', + value: '11' + }] + }); + const response2 = await authorisedUser.post('/trpc/study.createDataVersion') + .send({ + studyId: response1.body.result.data.id, + dataVersion: '1.0', + tag: '1.0' + }); + expect(response2.status).toBe(400); + expect(response2.body.error.message).toBe('Version has been used.'); + // clear database + await db.collections.roles_collection.deleteMany({ id: fullPermissionRole.id }); + }); + test('Create a new data version (nothing update)', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + const response1 = await request; + const fullPermissionRole: IRole = { + id: 'full_permission_role_id', + studyId: response1.body.result.data.id, + name: 'Full Permissison Role', + description: '', + // data permissions for studyId + dataPermissions: [{ + fields: ['^1.*$'], + dataProperties: { + }, + permission: parseInt('110', 2) + }], + studyRole: enumStudyRoles.STUDY_MANAGER, + users: [authorisedUserProfile.id], + life: { + createdTime: 1591134065000, + createdUser: 'admin', + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + await db.collections.roles_collection.insertOne(fullPermissionRole); + const response = await authorisedUser.post('/trpc/study.createDataVersion') + .send({ + studyId: response1.body.result.data.id, + dataVersion: '1.0', + tag: '1.0' + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Nothing to update.'); + // clear database + await db.collections.roles_collection.deleteMany({ id: fullPermissionRole.id }); + }); + test('Set to a previous data version', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + const response1 = await request; + const fullPermissionRole: IRole = { + id: 'full_permission_role_id', + studyId: response1.body.result.data.id, + name: 'Full Permissison Role', + description: '', + // data permissions for studyId + dataPermissions: [{ + fields: ['^1.*$'], + dataProperties: { + }, + permission: parseInt('110', 2) + }], + studyRole: enumStudyRoles.STUDY_MANAGER, + users: [authorisedUserProfile.id], + life: { + createdTime: 1591134065000, + createdUser: 'admin', + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + await db.collections.roles_collection.insertOne(fullPermissionRole); + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: response1.body.result.data.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: response1.body.result.data.id, + data: [{ + fieldId: '1', + value: '10' + }] + }); + const response2 = await authorisedUser.post('/trpc/study.createDataVersion') + .send({ + studyId: response1.body.result.data.id, + dataVersion: '1.0', + tag: '1.0' + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: response1.body.result.data.id, + data: [{ + fieldId: '1', + value: '11' + }] + }); + await authorisedUser.post('/trpc/study.createDataVersion') + .send({ + studyId: response1.body.result.data.id, + dataVersion: '1.1', + tag: '1.1' + }); + const response3 = await authorisedUser.post('/trpc/study.setDataversionAsCurrent') + .send({ + studyId: response1.body.result.data.id, + dataVersionId: response2.body.result.data.id + }); + expect(response3.status).toBe(200); + expect(response3.body.result.data.successful).toBe(true); + const study = await db.collections.studies_collection.findOne({}); + expect(study?.currentDataVersion).toBe(0); + expect(study?.dataVersions).toHaveLength(2); + // clear database + await db.collections.roles_collection.deleteMany({ id: fullPermissionRole.id }); + }); + test('Set to a previous data version (no permission)', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + const response1 = await request; + const fullPermissionRole: IRole = { + id: 'full_permission_role_id', + studyId: response1.body.result.data.id, + name: 'Full Permissison Role', + description: '', + // data permissions for studyId + dataPermissions: [{ + fields: ['^1.*$'], + dataProperties: { + }, + permission: parseInt('110', 2) + }], + studyRole: enumStudyRoles.STUDY_MANAGER, + users: [authorisedUserProfile.id], + life: { + createdTime: 1591134065000, + createdUser: 'admin', + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + await db.collections.roles_collection.insertOne(fullPermissionRole); + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: response1.body.result.data.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: response1.body.result.data.id, + data: [{ + fieldId: '1', + value: '10' + }] + }); + const response2 = await authorisedUser.post('/trpc/study.createDataVersion') + .send({ + studyId: response1.body.result.data.id, + dataVersion: '1.0', + tag: '1.0' + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: response1.body.result.data.id, + data: [{ + fieldId: '1', + value: '11' + }] + }); + await db.collections?.roles_collection.updateOne({}, { + $set: { + studyRole: enumStudyRoles.STUDY_USER + } + }); + await authorisedUser.post('/trpc/study.createDataVersion') + .send({ + studyId: response1.body.result.data.id, + dataVersion: '1.1', + tag: '1.1' + }); + const response3 = await user.post('/trpc/study.setDataversionAsCurrent') + .send({ + studyId: response1.body.result.data.id, + dataVersionId: response2.body.result.data.id + }); + expect(response3.status).toBe(400); + expect(response3.body.error.message).toBe('Only admin or study manager can set a study version.'); + // clear database + await db.collections.roles_collection.deleteMany({ id: fullPermissionRole.id }); + }); + test('Set to a previous data version (version id not exist)', async () => { + const filePath = path.join(__dirname, '../filesForTests/dsi.jpeg'); + const request = admin.post('/trpc/study.createStudy') + .attach('profile', filePath) + .field('name', 'Test Study') + .field('description', ''); + const response1 = await request; + const fullPermissionRole: IRole = { + id: 'full_permission_role_id', + studyId: response1.body.result.data.id, + name: 'Full Permissison Role', + description: '', + // data permissions for studyId + dataPermissions: [{ + fields: ['^1.*$'], + dataProperties: { + }, + permission: parseInt('110', 2) + }], + studyRole: enumStudyRoles.STUDY_MANAGER, + users: [authorisedUserProfile.id], + life: { + createdTime: 1591134065000, + createdUser: 'admin', + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + await db.collections.roles_collection.insertOne(fullPermissionRole); + await authorisedUser.post('/trpc/data.createStudyField') + .send({ + studyId: response1.body.result.data.id, + fieldName: 'Test Field 1', + fieldId: '1', + description: '', + dataType: enumDataTypes.INTEGER + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: response1.body.result.data.id, + data: [{ + fieldId: '1', + value: '10' + }] + }); + await authorisedUser.post('/trpc/study.createDataVersion') + .send({ + studyId: response1.body.result.data.id, + dataVersion: '1.0', + tag: '1.0' + }); + await authorisedUser.post('/trpc/data.uploadStudyData') + .send({ + studyId: response1.body.result.data.id, + data: [{ + fieldId: '1', + value: '11' + }] + }); + await authorisedUser.post('/trpc/study.createDataVersion') + .send({ + studyId: response1.body.result.data.id, + dataVersion: '1.1', + tag: '1.1' + }); + const response3 = await authorisedUser.post('/trpc/study.setDataversionAsCurrent') + .send({ + studyId: response1.body.result.data.id, + dataVersionId: 'random' + }); + expect(response3.status).toBe(400); + expect(response3.body.error.message).toBe('Data version does not exist.'); + // clear database + await db.collections.roles_collection.deleteMany({ id: fullPermissionRole.id }); + }); + }); +} else { + test(`${__filename.split(/[\\/]/).pop()} skipped because it requires Minio on Docker`, () => { + expect(true).toBe(true); + }); +} diff --git a/packages/itmat-interface/test/trpcTests/types.ts b/packages/itmat-interface/test/trpcTests/types.ts new file mode 100644 index 000000000..0a2724b02 --- /dev/null +++ b/packages/itmat-interface/test/trpcTests/types.ts @@ -0,0 +1,19 @@ +export interface MinioGlobal { + hasMinio: boolean; + minioContainerPort?: number; +} + +export type MinioAwareGlobal = typeof global & MinioGlobal; + +export const getMinioAwareGlobal = (): MinioAwareGlobal => { + return global as MinioAwareGlobal; +}; + +export const getRequiredMinioPort = (): number => { + const minioGlobal = getMinioAwareGlobal(); + if (minioGlobal.minioContainerPort === undefined) { + throw new Error('minioContainerPort is not available on global. Ensure Minio test environment is initialised.'); + } + return minioGlobal.minioContainerPort; +}; + diff --git a/packages/itmat-interface/test/trpcTests/user.test.ts b/packages/itmat-interface/test/trpcTests/user.test.ts new file mode 100644 index 000000000..2acc5a513 --- /dev/null +++ b/packages/itmat-interface/test/trpcTests/user.test.ts @@ -0,0 +1,469 @@ +/** + * @with Minio + */ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +import { MongoMemoryServer } from 'mongodb-memory-server'; +import { db } from '../../src/database/database'; +import { Express } from 'express'; +import { objStore } from '../../src/objStore/objStore'; +import request from 'supertest'; +import { connectAdmin, connectUser } from './_loginHelper'; +import { Router } from '../../src/server/router'; +import { Db, MongoClient } from 'mongodb'; +import { setupDatabase } from '@itmat-broker/itmat-setup'; +import config from '../../config/config.sample.json'; +import { v4 as uuid } from 'uuid'; +import { IOrganisation, enumUserTypes } from '@itmat-broker/itmat-types'; +import { encodeQueryParams } from './helper'; +import { errorCodes } from '@itmat-broker/itmat-cores'; +import { getRequiredMinioPort } from './types'; + + +if (global.hasMinio) { + let app: Express; + let mongodb: MongoMemoryServer; + let admin: request.SuperTest; + let user: request.SuperTest; + let mongoConnection: MongoClient; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let mongoClient: Db; + let adminProfile; + let userProfile; + let organisation: IOrganisation; + + afterAll(async () => { + await db.closeConnection(); + await mongoConnection?.close(); + await mongodb.stop(); + + /* claer all mocks */ + jest.clearAllMocks(); + }); + + beforeAll(async () => { // eslint-disable-line no-undef + /* Creating a in-memory MongoDB instance for testing */ + const dbName = uuid(); + mongodb = await MongoMemoryServer.create({ instance: { dbName } }); + const connectionString = mongodb.getUri(); + await setupDatabase(connectionString, dbName); + /* Wiring up the backend server */ + config.objectStore.port = getRequiredMinioPort(); + config.database.mongo_url = connectionString; + config.database.database = dbName; + await db.connect(config.database, MongoClient); + await objStore.connect(config.objectStore); + const router = new Router(config); + await router.init(); + /* Connect mongo client (for test setup later / retrieve info later) */ + mongoConnection = await MongoClient.connect(connectionString); + mongoClient = mongoConnection.db(dbName); + + /* Connecting clients for testing later */ + app = router.getApp(); + admin = request.agent(app); + user = request.agent(app); + await connectAdmin(admin); + await connectUser(user); + + // add the root node for each user + const users = await db.collections.users_collection.find({}).toArray(); + adminProfile = users.filter(el => el.type === enumUserTypes.ADMIN)[0]; + userProfile = users.filter(el => el.type === enumUserTypes.STANDARD)[0]; + + // add an organisation + organisation = { + id: uuid(), + name: 'My Org', + shortname: '', + location: [], + profile: null, + metadata: { + }, + life: { + createdTime: 0, + createdUser: 'SYSTEMAGENT', + deletedUser: null, + deletedTime: null + } + }; + await db.collections.organisations_collection.insertOne(organisation); + }); + + afterEach(async () => { + await db.collections.studies_collection.deleteMany({}); + await db.collections.files_collection.deleteMany({}); + await db.collections.roles_collection.deleteMany({}); + await db.collections.field_dictionary_collection.deleteMany({}); + await db.collections.data_collection.deleteMany({}); + await db.collections.jobs_collection.deleteMany({}); + delete userProfile._id; + await db.collections.users_collection.findOneAndUpdate({ id: userProfile.id }, { + $set: userProfile + }); + await db.collections.users_collection.deleteMany({ id: { $nin: [adminProfile.id, userProfile.id] } }); + await db.collections.configs_collection.deleteMany({ key: { $nin: [adminProfile.id, userProfile.id] } }); + }); + + describe('tRPC User APIs', () => { + test('Create a user', async () => { + const response = await admin.post('/trpc/user.createUser') + .send({ + username: 'test', + firstname: 'firstname', + lastname: 'lastname', + email: 'test@test.com', + password: 'test_password', + description: '', + organisation: organisation.id + }); + expect(response.status).toBe(200); + expect(response.body.result.data.username).toBe('test'); + const user = await db.collections.users_collection.findOne({ username: 'test' }); + expect(user).toBeDefined(); + expect(user.id).toBe(response.body.result.data.id); + }); + test('Create a user (invalid email format)', async () => { + const response = await admin.post('/trpc/user.createUser') + .send({ + username: 'test', + firstname: 'firstname', + lastname: 'lastname', + email: 'test@test', + password: 'test_password', + description: '', + organisation: organisation.id + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Email is not the right format.'); + }); + test('Create a user (invalid password format)', async () => { + const response = await admin.post('/trpc/user.createUser') + .send({ + username: 'test', + firstname: 'firstname', + lastname: 'lastname', + email: 'test@test.com', + password: 'test', + description: '', + organisation: organisation.id + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Password has to be at least 8 character long.'); + }); + test('Create a user (username have spaces)', async () => { + const response = await admin.post('/trpc/user.createUser') + .send({ + username: 'te st', + firstname: 'firstname', + lastname: 'lastname', + email: 'test@test.com', + password: 'test_password', + description: '', + organisation: organisation.id + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Username or password cannot have spaces.'); + }); + test('Create a user (password have spaces)', async () => { + const response = await admin.post('/trpc/user.createUser') + .send({ + username: 'test', + firstname: 'firstname', + lastname: 'lastname', + email: 'test@test.com', + password: 'test_pa ssword', + description: '', + organisation: organisation.id + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Username or password cannot have spaces.'); + }); + test('Create a user (username already registered)', async () => { + const response = await admin.post('/trpc/user.createUser') + .send({ + username: userProfile.username, + firstname: 'firstname', + lastname: 'lastname', + email: 'test@test.com', + password: 'test_password', + description: '', + organisation: organisation.id + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Username or email already exists.'); + }); + test('Create a user (email already registered)', async () => { + const response = await admin.post('/trpc/user.createUser') + .send({ + username: 'test', + firstname: 'firstname', + lastname: 'lastname', + email: userProfile.email, + password: 'test_password', + description: '', + organisation: organisation.id + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Username or email already exists.'); + }); + test('Create a user (organisation does not exist)', async () => { + const response = await admin.post('/trpc/user.createUser') + .send({ + username: 'test', + firstname: 'firstname', + lastname: 'lastname', + email: 'test@test.com', + password: 'test_password', + description: '', + organisation: 'random' + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Organisation does not exist.'); + }); + test('Edit a user (admin)', async () => { + const response = await admin.post('/trpc/user.editUser') + .send({ + userId: userProfile.id, + username: 'edit_username', + firstname: 'edit_firstname', + lastname: 'edit_lastname', + email: 'edit_email@test.com', + description: 'edit_description', + type: enumUserTypes.MANAGER + }); + expect(response.status).toBe(200); + expect(response.body.result.data.username).toBe('edit_username'); + const editedUser = await db.collections.users_collection.findOne({ id: userProfile.id }); + expect(editedUser?.username).toBe('edit_username'); + }); + test('Edit a user (user)', async () => { + const response = await user.post('/trpc/user.editUser') + .send({ + userId: userProfile.id, + username: 'edit_username', + firstname: 'edit_firstname', + lastname: 'edit_lastname', + email: 'edit_email@test.com', + password: 'edit_password', + description: 'edit_description' + }); + expect(response.status).toBe(200); + expect(response.body.result.data.username).toBe('edit_username'); + const editedUser = await db.collections.users_collection.findOne({ id: userProfile.id }); + expect(editedUser?.username).toBe('edit_username'); + }); + test('Edit a user (user edit others accounts)', async () => { + const response = await user.post('/trpc/user.editUser') + .send({ + userId: adminProfile.id, + username: 'edit_username', + firstname: 'edit_firstname', + lastname: 'edit_lastname', + email: 'edit_email@test.com', + password: 'edit_password', + description: 'edit_description' + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('User can only edit his/her own account.'); + }); + test('Edit a user (user edit unpermitted fields)', async () => { + const response = await user.post('/trpc/user.editUser') + .send({ + userId: userProfile.id, + username: 'edit_username', + firstname: 'edit_firstname', + lastname: 'edit_lastname', + email: 'edit_email@test.com', + password: 'edit_password', + description: 'edit_description', + type: enumUserTypes.ADMIN + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Standard user can not change their type, expiration time. Please contact admins for help.'); + }); + test('Edit a user (password not long enough)', async () => { + const response = await user.post('/trpc/user.editUser') + .send({ + userId: userProfile.id, + username: 'edit_username', + firstname: 'edit_firstname', + lastname: 'edit_lastname', + email: 'edit_email@test.com', + password: 'edit_', + description: 'edit_description' + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Password has to be at least 8 character long.'); + }); + test('Edit a user (email not right format)', async () => { + const response = await user.post('/trpc/user.editUser') + .send({ + userId: userProfile.id, + username: 'edit_username', + firstname: 'edit_firstname', + lastname: 'edit_lastname', + email: 'edit_email@', + password: 'edit_password', + description: 'edit_description' + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Email is not the right format.'); + }); + test('Edit a user (username already used)', async () => { + const response = await user.post('/trpc/user.editUser') + .send({ + userId: userProfile.id, + username: adminProfile.username, + firstname: 'edit_firstname', + lastname: 'edit_lastname', + email: 'edit_email@test.com', + password: 'edit_password', + description: 'edit_description' + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Username already used.'); + }); + test('Edit a user (email already used)', async () => { + const response = await user.post('/trpc/user.editUser') + .send({ + userId: userProfile.id, + username: 'edit_username', + firstname: 'edit_firstname', + lastname: 'edit_lastname', + email: adminProfile.email, + password: 'edit_password', + description: 'edit_description' + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Email already used.'); + }); + test('Edit a user (organisation does not exist)', async () => { + const response = await admin.post('/trpc/user.editUser') + .send({ + userId: userProfile.id, + username: 'edit_username', + firstname: 'edit_firstname', + lastname: 'edit_lastname', + email: 'edit_email@test.com', + description: 'edit_description', + organisation: 'random' + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Organisation does not exist.'); + }); + test('Edit a user (old password)', async () => { + await user.post('/trpc/user.editUser') + .send({ + userId: userProfile.id, + password: 'edit_password' + }); + const response = await user.post('/trpc/user.editUser') + .send({ + userId: userProfile.id, + password: 'edit_password' + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('You need to select a new password.'); + }); + test('Edit a user (expired time is outdated)', async () => { + const response = await admin.post('/trpc/user.editUser') + .send({ + userId: userProfile.id, + username: 'edit_username', + firstname: 'edit_firstname', + lastname: 'edit_lastname', + email: 'edit_email@test.com', + description: 'edit_description', + expiredAt: Date.now() - 1000 + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('Cannot set to a previous time.'); + }); + test('Get a user by admin (through userId)', async () => { + const paramteres = { + userId: userProfile.id + }; + const response = await admin.get('/trpc/user.getUser?input=' + encodeQueryParams(paramteres)) + .query({ + }); + expect(response.status).toBe(200); + expect(response.body.result.data).toBeDefined(); + expect(response.body.result.data.username).toBe(userProfile.username); + }); + test('Get a user by user (through userId)', async () => { + const paramteres = { + userId: adminProfile.id + }; + const response = await user.get('/trpc/user.getUser?input=' + encodeQueryParams(paramteres)) + .query({ + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(errorCodes.NO_PERMISSION_ERROR); + }); + test('Get a user (through username)', async () => { + const paramteres = { + username: userProfile.username + }; + const response = await user.get('/trpc/user.getUser?input=' + encodeQueryParams(paramteres)) + .query({ + }); + expect(response.status).toBe(200); + expect(response.body.result.data).toBeDefined(); + expect(response.body.result.data.username).toBe(userProfile.username); + }); + test('Get a user (through email)', async () => { + const paramteres = { + email: userProfile.email + }; + const response = await user.get('/trpc/user.getUser?input=' + encodeQueryParams(paramteres)) + .query({ + }); + expect(response.status).toBe(200); + expect(response.body.result.data).toBeDefined(); + expect(response.body.result.data.username).toBe(userProfile.username); + }); + test('Get a user by user (user does not exist)', async () => { + const paramteres = { + email: 'random' + }; + const response = await user.get('/trpc/user.getUser?input=' + encodeQueryParams(paramteres)) + .query({ + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe(errorCodes.NO_PERMISSION_ERROR); + }); + test('Get a user by admin (user does not exist)', async () => { + const paramteres = { + email: 'random' + }; + const response = await admin.get('/trpc/user.getUser?input=' + encodeQueryParams(paramteres)) + .query({ + }); + expect(response.status).toBe(400); + expect(response.body.error.message).toBe('User does not exist.'); + }); + test('Get all users (admin)', async () => { + const paramteres = { + }; + const response = await admin.get('/trpc/user.getUsers?input=' + encodeQueryParams(paramteres)) + .query({ + }); + expect(response.status).toBe(200); + expect(response.body.result.data).toHaveLength(2); + }); + test('Get all users (user)', async () => { + const paramteres = { + }; + const response = await user.get('/trpc/user.getUsers?input=' + encodeQueryParams(paramteres)) + .query({ + }); + expect(response.status).toBe(200); + expect(response.body.result.data).toHaveLength(0); + }); + }); +} else { + test(`${__filename.split(/[\\/]/).pop()} skipped because it requires Minio on Docker`, () => { + expect(true).toBe(true); + }); +} diff --git a/packages/itmat-interface/test/trpcTests/webauthn.test.ts b/packages/itmat-interface/test/trpcTests/webauthn.test.ts new file mode 100644 index 000000000..bdc18f788 --- /dev/null +++ b/packages/itmat-interface/test/trpcTests/webauthn.test.ts @@ -0,0 +1,249 @@ +/** + * @with Minio + */ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +import { MongoMemoryServer } from 'mongodb-memory-server'; +import { db } from '../../src/database/database'; +import { Express } from 'express'; +import { objStore } from '../../src/objStore/objStore'; +import request from 'supertest'; +import { connectAdmin, connectUser } from './_loginHelper'; +import { Router } from '../../src/server/router'; +import { Db, MongoClient } from 'mongodb'; +import { setupDatabase } from '@itmat-broker/itmat-setup'; +import config from '../../config/config.sample.json'; +import { v4 as uuid } from 'uuid'; +import { IUser, enumUserTypes } from '@itmat-broker/itmat-types'; +import { encodeQueryParams } from './helper'; +import { PublicKeyCredentialCreationOptionsJSON, RegistrationResponseJSON, PublicKeyCredentialRequestOptionsJSON, AuthenticationResponseJSON } from '@simplewebauthn/types'; + +import { startRegistration, startAuthentication } from '@simplewebauthn/browser'; +import { + RegistrationResponseJSON, + PublicKeyCredentialCreationOptionsJSON +} from '@simplewebauthn/types'; + +import { WebauthnCore } from '@itmat-broker/itmat-cores'; +import { getRequiredMinioPort } from './types'; + + +if (global.hasMinio) { // eslint-disable-line no-undef + let app: Express; + let mongodb: MongoMemoryServer; + let admin: request.SuperTest; + let user: request.SuperTest; + let mongoConnection: MongoClient; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let mongoClient: Db; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let adminProfile: IUser; + let userProfile: IUser; + const setupDatabaseAndApp = async () => { + + const dbName = uuid(); + mongodb = await MongoMemoryServer.create({ instance: { dbName } }); + const connectionString = mongodb.getUri(); + await setupDatabase(connectionString, dbName); + + config.objectStore.port = getRequiredMinioPort(); + config.database.mongo_url = connectionString; + config.database.database = dbName; + + await db.connect(config.database, MongoClient); + + await objStore.connect(config.objectStore); + + const router = new Router(config); + await router.init(); + + mongoConnection = await MongoClient.connect(connectionString); + mongoClient = mongoConnection.db(dbName); + + app = router.getApp(); + admin = request.agent(app); + user = request.agent(app); + await connectAdmin(admin); + await connectUser(user); + + const users = await db.collections.users_collection.find({}).toArray(); + adminProfile = users.find(el => el.type === enumUserTypes.ADMIN); + userProfile = users.find(el => el.type === enumUserTypes.STANDARD); + + }; + + // Mocking getCurrentOriginAndRpID method only + let webauthnCoreMock: jest.SpyInstance; + + beforeAll(async () => { + await setupDatabaseAndApp(); + + // Mock getCurrentOriginAndRpID method + webauthnCoreMock = jest.spyOn(WebauthnCore.prototype, 'getCurrentOriginAndRpID') + .mockResolvedValue({ + origin: 'http://localhost:3000', + rpID: 'localhost' + }); + }); + + afterAll(async () => { + await db.closeConnection(); + await mongoConnection.close(); + await mongodb.stop(); + webauthnCoreMock.mockRestore(); // Restore original method after tests + jest.clearAllMocks(); + }); + + describe('tRPC WebAuthn APIs', () => { + + test('Should verify WebAuthn registration and retrieve WebAuthn ID successfully', async () => { + // Step 1: Get registration options + const registrationResponse = await user.post('/trpc/webauthn.webauthnRegister').send(); + + expect(registrationResponse.status).toBe(200); + + // Extract the webauthn_id from the registration response + const webauthnId = registrationResponse.body.result.data.webauthn_id; + + // Step 2: Generate the attestation response using startRegistration + const registrationOptions: PublicKeyCredentialCreationOptionsJSON = registrationResponse.body.result.data.options; + const attestationResponse: RegistrationResponseJSON = await startRegistration(registrationOptions); + + // Step 3: Verify the registration response + const verifyResponse = await user.post('/trpc/webauthn.webauthnRegisterVerify') + .send({ attestationResponse }) + .set('Content-Type', 'application/json'); + + // Step 4: Check the verification result + expect(verifyResponse.status).toBe(200); + expect(verifyResponse.body.result.data.successful).toBe(true); + + // Step 5: Use the webauthn_id to retrieve the registered WebAuthn data via the query endpoint + const paramteres = { + webauthn_ids: [webauthnId] + }; + const getWebauthnResponse = await user.get('/trpc/webauthn.getWebauthn?input=' + encodeQueryParams(paramteres)) + .query({}); + + // Step 6: Validate the response + expect(getWebauthnResponse.status).toBe(200); + expect(getWebauthnResponse.body.result.data).toBeDefined(); + expect(getWebauthnResponse.body.result.data).toHaveLength(1); + + // Step 7: Query the WebAuthn ID using the getWebauthnID endpoint + const getWebauthnIDResponse = await user.get('/trpc/webauthn.getWebauthnID') + .set('Content-Type', 'application/json'); + + // Validate the response + expect(getWebauthnIDResponse.status).toBe(200); + expect(getWebauthnIDResponse.body.result.data).toBeDefined(); + + // Verify that the returned WebAuthn ID matches the expected ID from the registration process + const webauthnData = getWebauthnIDResponse.body.result.data; + expect(webauthnData).toHaveProperty('id', webauthnId); // Ensure the ID matches the one returned during registration + + // Optional: Verify the device information + const webauthnRecord = await db.collections.webauthn_collection.findOne({ userId: userProfile.id }); + expect(webauthnRecord).toBeDefined(); + expect(webauthnRecord?.devices).toHaveLength(1); + expect(webauthnRecord?.devices[0].credentialID).toBe(attestationResponse.rawId); + }); + + + + test('Should verify WebAuthn authentication successfully', async () => { + // Step 1: Get authentication options + const authOptionsResponse = await user.post('/trpc/webauthn.webauthnAuthenticate') + .send({ userId: userProfile.id }) + .set('Content-Type', 'application/json'); + + expect(authOptionsResponse.status).toBe(200); + + // Step 2: Generate the assertion response using startAuthentication + const authenticationOptions: PublicKeyCredentialRequestOptionsJSON = authOptionsResponse.body.result.data; + const assertionResponse: AuthenticationResponseJSON = await startAuthentication(authenticationOptions); + + // Step 3: Verify the authentication response + const verifyResponse = await user.post('/trpc/webauthn.webauthnAuthenticateVerify') + .send({ + userId: userProfile.id, + assertionResponse: assertionResponse + }) + .set('Content-Type', 'application/json'); + + // Step 4: Check the verification result + expect(verifyResponse.status).toBe(200); + expect(verifyResponse.body.result.data.successful).toBe(true); + + // Step 5: Check that the webauthn record was updated correctly + const webauthnRecord = await db.collections.webauthn_collection.findOne({ userId: userProfile.id }); + expect(webauthnRecord).toBeDefined(); + expect(webauthnRecord?.devices).toHaveLength(1); + expect(webauthnRecord?.devices[0].counter).toBeGreaterThan(0); + }); + test('Should retrieve, update, and delete WebAuthn registered device successfully', async () => { + // Step 1: Retrieve the registered devices using the API + const getDevicesResponse = await user.get('/trpc/webauthn.getWebauthnRegisteredDevices').send(); + + // Validate the response and ensure there are registered devices + expect(getDevicesResponse.status).toBe(200); + expect(getDevicesResponse.body.result.data).toBeDefined(); + expect(getDevicesResponse.body.result.data).toHaveLength(1); // there's one device registered from the previous tests + + const registeredDevices = getDevicesResponse.body.result.data; + const mockDeviceId = registeredDevices[0].id; // Select the first device ID to update and delete + const newDeviceName = 'Updated Device Name'; + + // Step 2: Perform the update operation via the API to change the device's name + const updateNameResponse = await user.post('/trpc/webauthn.updateWebauthnDeviceName') + .send({ + deviceId: mockDeviceId, + name: newDeviceName + }) + .set('Content-Type', 'application/json'); + + // Validate the response after updating the device name + expect(updateNameResponse.status).toBe(200); + expect(updateNameResponse.body.result.data).toBeDefined(); + + // Step 3: Verify that the device name has been updated from the response + const updatedDevices = updateNameResponse.body.result.data; + const updatedDevice = updatedDevices.find(device => device.id === mockDeviceId); + expect(updatedDevice).toBeDefined(); + expect(updatedDevice?.name).toBe(newDeviceName); // Ensure the device name is updated + + // Step 4: Perform the delete operation via the API + const deleteDeviceResponse = await user.post('/trpc/webauthn.deleteWebauthnRegisteredDevices') + .send({ deviceId: mockDeviceId }) + .set('Content-Type', 'application/json'); + + // Validate the response and ensure the device list is now empty + expect(deleteDeviceResponse.status).toBe(200); + expect(deleteDeviceResponse.body.result.data).toBeDefined(); + expect(deleteDeviceResponse.body.result.data).toHaveLength(0); // The device list should now be empty + }); + + test('Should login with WebAuthn successfully', async () => { + // Perform the login operation via the API + const response = await user.post('/trpc/webauthn.webauthnLogin') + .send({ + userId: userProfile.id, // Adjusted to match the backend expectation (removed the 'input' wrapper) + requestExpiryDate: false + }) + .set('Content-Type', 'application/json'); + + // Validate the response + expect(response.status).toBe(200); + expect(response.body.result.data).toBeDefined(); + + // check specific properties in the response + expect(response.body.result.data).toHaveProperty('id', userProfile.id); + expect(response.body.result.data).toHaveProperty('username', userProfile.username); + expect(response.body.result.data).not.toHaveProperty('password'); // Ensure password is not included in the response + }); + }); +} else { + test(`${__filename.split(/[\\/]/).pop()} skipped because it requires Minio on Docker`, () => { + expect(true).toBe(true); + }); +} diff --git a/packages/itmat-interface/tsconfig.app.json b/packages/itmat-interface/tsconfig.app.json index 411502e7b..d480fe1da 100644 --- a/packages/itmat-interface/tsconfig.app.json +++ b/packages/itmat-interface/tsconfig.app.json @@ -2,7 +2,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", + "module": "ES2022", "types": [ "node", "express", diff --git a/packages/itmat-interface/tsconfig.json b/packages/itmat-interface/tsconfig.json index 7f9a7d526..e02193374 100644 --- a/packages/itmat-interface/tsconfig.json +++ b/packages/itmat-interface/tsconfig.json @@ -1,5 +1,15 @@ { "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "ES2022", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noImplicitAny": false, + "noFallthroughCasesInSwitch": true + }, "files": [], "include": [], "references": [ diff --git a/packages/itmat-interface/tsconfig.spec.json b/packages/itmat-interface/tsconfig.spec.json index 5fde83bbc..0e0652379 100644 --- a/packages/itmat-interface/tsconfig.spec.json +++ b/packages/itmat-interface/tsconfig.spec.json @@ -2,7 +2,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", + "module": "ES2022", "types": [ "jest", "node", diff --git a/packages/itmat-interface/webdav.d.ts b/packages/itmat-interface/webdav.d.ts new file mode 100644 index 000000000..63b94f575 --- /dev/null +++ b/packages/itmat-interface/webdav.d.ts @@ -0,0 +1,67 @@ +import { IUserWithoutToken } from '@itmat-broker/itmat-types'; + +// Augment the webdav-server module +declare module 'webdav-server' { + type IUser = IUserWithoutToken; + namespace v2 { + type IUser = IUserWithoutToken; + interface RequestContext { + user: IUserWithoutToken; + } + + interface DeleteInfo { + context: RequestContext; + } + + interface CreateInfo { + context: RequestContext; + } + + interface OpenReadStreamInfo { + context: RequestContext; + } + + interface OpenWriteStreamInfo { + context: RequestContext; + } + + interface ReadDirInfo { + context: { + user: IUserWithoutToken; + headers: { + depth?: number + } + }; + } + + interface CopyInfo { + context: RequestContext; + } + + interface MoveInfo { + context: RequestContext; + } + + abstract class FileSystemSerializer { + abstract uid(): string; + abstract serialize(fs: FileSystem, callback: ReturnCallback): void; + abstract unserialize(serializeData: unknown, callback: ReturnCallback): void; + } + + abstract class FileSystem { + constructor(FileSystemSerializer) + abstract _fastExistCheck(ctx: RequestContext, path: Path, callback: (exists: boolean) => void): Promise; + abstract _displayName(path: Path, ctx: DisplayNameInfo, callback: ReturnCallback): void; + abstract _lockManager(path: Path, ctx: LockManagerInfo, callback: ReturnCallback): void; + abstract _propertyManager(path: Path, ctx: PropertyManagerInfo, callback: ReturnCallback): void; + abstract _type(path: Path, ctx: TypeInfo, callback: ReturnCallback): void; + abstract _delete(path: Path, ctx: DeleteInfo, callback: SimpleCallback): Promise; + abstract _create(path: Path, ctx: CreateInfo, callback: SimpleCallback): Promise; + abstract _openReadStream(path: Path, ctx: OpenReadStreamInfo, callback: ReturnCallback): Promise; + abstract _openWriteStream(path: Path, ctx: OpenWriteStreamInfo, callback: ReturnCallback): Promise; + abstract _readDir(path: Path, ctx: ReadDirInfo, callback: ReturnCallback): Promise; + abstract _copy(pathFrom: Path, pathTo: Path, ctx: CopyInfo, callback: ReturnCallback): Promise; + abstract _move(pathFrom: Path, pathTo: Path, ctx: MoveInfo, callback: ReturnCallback): Promise; + } + } +} \ No newline at end of file diff --git a/packages/itmat-interface/webpack.config.js b/packages/itmat-interface/webpack.config.cjs similarity index 100% rename from packages/itmat-interface/webpack.config.js rename to packages/itmat-interface/webpack.config.cjs diff --git a/packages/itmat-job-executor/config/config.sample.json b/packages/itmat-job-executor/config/config.sample.json index ffae9593a..f60cf4731 100644 --- a/packages/itmat-job-executor/config/config.sample.json +++ b/packages/itmat-job-executor/config/config.sample.json @@ -1,4 +1,5 @@ -{ +{ + "appName": "ITMAT Job Executor", "database": { "mongo_url": "mongodb://localhost:27017", "database": "itmat", @@ -16,12 +17,24 @@ "files_collection": "FILES_COLLECTION", "sessions_collection": "SESSIONS_COLLECTION", "pubkeys_collection": "PUBKEY_COLLECTION", - "standardizations_collection": "STANDARDIZATION_COLLECTION" + "standardizations_collection": "STANDARDIZATION_COLLECTION", + "configs_collection": "CONFIG_COLLECTION", + "ontologies_collection": "ONTOLOGY_COLLECTION", + "docs_collection": "DOC_COLLECTION", + "cache_collection": "CACHE_COLLECTION", + "drives_collection": "DRIVE_COLLECTION", + "colddata_collection": "COLDDATA_COLLECTION", + "domains_collection": "DOMAIN_COLLECTION", + "webauthn_collection": "WEBAUTHN_COLLECTION", + "instance_collection": "INSTANCE_COLLECTION" } }, "server": { "port": 3003 }, + "bcrypt": { + "saltround": 2 + }, "objectStore": { "host": "localhost", "port": 9000, @@ -30,5 +43,41 @@ "bucketRegion": "region", "useSSL": false }, - "pollingInterval": 10000 + "sessionsSecret": "Change_Me", + "aesSecret": "change_this", + "nodemailer": { + "auth": { + "user": "change_this", + "pass": "change_this" + } + }, + "adminEmail": "admin@example.com", + "aeEndpoint": "http://localhost:9090", + "useWebdav": false, + "webdavPort": 1900, + "webdavServer": "http://localhost:4200/webdav", + "pollingInterval": 10000, + + "lxdEndpoint": "https://localhost:8443", + "lxdPortRange": [ + 30000, + 40000 + ], + "lxdSquidProxy": "traget_ip:3128", + "lxdStoragePool": "newpool", + "lxdProject": "default", + "systemKey":{ + "pubkey": "path/system_key.pub", + "privkey": "path/system_key.pem" + }, + "lxdCertFile": { + "cert": "/path/mylxd.crt", + "key": "/path/mylxd.key" + }, + "lxdRejectUnauthorized": false, + "jupyterPort": 8888, + "dmp": { + "host": "localhost", + "port": 3080 + } } \ No newline at end of file diff --git a/packages/itmat-job-executor/express-user.d.ts b/packages/itmat-job-executor/express-user.d.ts new file mode 100644 index 000000000..2fff509fe --- /dev/null +++ b/packages/itmat-job-executor/express-user.d.ts @@ -0,0 +1,27 @@ +import type { IUserWithoutToken } from '@itmat-broker/itmat-types'; + +declare global { + + namespace Express { + + // eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/no-empty-object-type + interface User extends IUserWithoutToken { } + + interface MulterFile { + fieldname: string; + originalname: string; + encoding: string; + mimetype: string; + buffer: Buffer; + size: number; + } + + interface Request { + user?: User; + files?: MulterFile[]; + headers: IncomingHttpHeaders; + login(user: User, done: (err: unknown) => void): void; + logout(done: (err: unknown) => void): void; + } + } +} \ No newline at end of file diff --git a/packages/itmat-job-executor/jest.config.ts b/packages/itmat-job-executor/jest.config.ts index f600e93e3..e404b4aad 100644 --- a/packages/itmat-job-executor/jest.config.ts +++ b/packages/itmat-job-executor/jest.config.ts @@ -1,7 +1,7 @@ /* eslint-disable */ export default { displayName: 'itmat-job-executor', - preset: '../../jest.preset.js', + preset: '../../jest.preset.cjs', testEnvironment: 'node', transform: { '^.+\\.[tj]s$': '@swc/jest' diff --git a/packages/itmat-job-executor/project.json b/packages/itmat-job-executor/project.json index 716e75c5c..bb67fa62c 100644 --- a/packages/itmat-job-executor/project.json +++ b/packages/itmat-job-executor/project.json @@ -3,6 +3,7 @@ "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "packages/itmat-job-executor/src", "projectType": "application", + "tags": [], "targets": { "build": { "executor": "@nx/webpack:webpack", @@ -16,7 +17,7 @@ "main": "packages/itmat-job-executor/src/index.ts", "tsConfig": "packages/itmat-job-executor/tsconfig.app.json", "assets": [], - "webpackConfig": "packages/itmat-job-executor/webpack.config.js" + "webpackConfig": "packages/itmat-job-executor/webpack.config.cjs" }, "configurations": { "production": { @@ -59,6 +60,5 @@ "jestConfig": "packages/itmat-job-executor/jest.config.ts" } } - }, - "tags": [] + } } \ No newline at end of file diff --git a/packages/itmat-job-executor/src/curation/CSVCurator.ts b/packages/itmat-job-executor/src/curation/CSVCurator.ts deleted file mode 100644 index 84864bd0e..000000000 --- a/packages/itmat-job-executor/src/curation/CSVCurator.ts +++ /dev/null @@ -1,326 +0,0 @@ -import * as csvparse from 'csv-parse'; -import { Collection } from 'mongodb'; -import { Writable, Readable } from 'stream'; -import { IFieldDescriptionObject, IDataEntry, IJobEntry } from '@itmat-broker/itmat-types'; - -/* update should be audit trailed */ -/* eid is not checked whether it is unique in the file: this is assumed to be enforced by database */ -export class CSVCurator { - /** - * things to check: - * - duplicate subject id - * - duplicate field - * - datatype mismatch - * - uneven column number - * - parse / encoding error - */ - private _header: (IFieldDescriptionObject | null)[]; - private _numOfSubj: number; - private _errored: boolean; - private _errors: string[]; - private _subjectIdIndex: number; - private _visitIdIndex: number; - - constructor( - private readonly dataCollection: Collection, - private readonly incomingWebStream: Readable, - private readonly parseOptions: csvparse.Options = { delimiter: ',', quote: '"', relax_column_count: true, skip_records_with_error: true }, - private readonly job: IJobEntry, - private readonly fieldsList: any[] - ) { - this._header = [null]; // the first element is subject id - this._numOfSubj = 0; - this._errored = false; - this._errors = []; - this._subjectIdIndex = 0; - this._visitIdIndex = 0; - } - - /* return list of errors. [] if no error */ - public processIncomingStreamAndUploadToMongo(): Promise { - return new Promise((resolve) => { - let lineNum = 0; - let isHeader = true; - const subjectString: string[] = []; - let bulkInsert = this.dataCollection.initializeUnorderedBulkOp(); - const csvparseStream = csvparse.parse(this.parseOptions); - const parseStream = this.incomingWebStream.pipe(csvparseStream); // piping the incoming stream to a parser stream - - csvparseStream.on('skip', (error: any) => { - lineNum++; - this._errored = true; - this._errors.push(error.toString()); - }); - - const uploadWriteStream: NodeJS.WritableStream = new Writable({ - objectMode: true, - write: async (line, _, next) => { - if (isHeader) { - lineNum++; - const { error, parsedHeader, subjectIdIndex, visitIdIndex } = processHeader(line, this.fieldsList); - if (error) { - this._errored = true; - this._errors.push(...error); - } - this._header = parsedHeader; - this._subjectIdIndex = subjectIdIndex; - this._visitIdIndex = visitIdIndex; - isHeader = false; - next(); - } else { - const currentLineNum = ++lineNum; - subjectString.push(line[this._subjectIdIndex]); - const { error, dataEntry } = processDataRow({ - subjectIdIndex: this._subjectIdIndex, - visitIdIndex: this._visitIdIndex, - lineNum: currentLineNum, - row: line, - parsedHeader: this._header, - job: this.job - }); - - if (error) { - this._errored = true; - this._errors.push(...error); - } - - if (this._errored) { - next(); - return; - } - - // // TO_DO { - // curator-defined constraints for values - // } - const matchObj = { - m_subjectId: dataEntry.m_subjectId, - m_visitId: dataEntry.m_visitId, - m_versionId: dataEntry.m_versionId, - m_studyId: dataEntry.m_studyId - }; - bulkInsert.find(matchObj).upsert().updateOne({ $set: dataEntry }); - - this._numOfSubj++; - if (this._numOfSubj > 999) { - this._numOfSubj = 0; - await bulkInsert.execute().catch((err) => { - if (err) { - //TODO Handle error recording - console.error(err); - } - }); - bulkInsert = this.dataCollection.initializeUnorderedBulkOp(); - } - next(); - } - } - }); - - uploadWriteStream.on('finish', async () => { - if (!this._errored) { - await bulkInsert.execute().catch((err) => { - if (err) { - //TODO Handle error recording - console.error(err); - } - }); - } - - resolve(this._errors); - }); - - parseStream.pipe(uploadWriteStream); - }); - } -} - - -export function processHeader(header: string[], fieldsList: any[]): { error?: string[], parsedHeader: any[], subjectIdIndex: number, visitIdIndex: number } { - /* pure function */ - /* headerline is ['eid', 1@0.0, 2@0.1:c] */ - /* returns a parsed object array and error (undefined if no error) */ - - // const fieldstrings: string[] = []; - const error: string[] = []; - const parsedHeader: any[] = Array(header.length); - let colNum = 0; - const fields: string[] = []; - const validatedFieldNames = fieldsList.map(el => el.fieldName); - if (!header.includes('SubjectID') || !header.includes('VisitID')) { - error.push('SubjectID or VisitID not found.'); - } - for (const each of header) { - if (colNum === 0) { - colNum++; - continue; - } - if (each === null || each === undefined || each === '') { - error.push(`Line 1, Column ${colNum}: Field Name should not be empty.`); - colNum++; - parsedHeader.push(null); - } else { - if (fields.includes(each)) { - // if duplicates happens, we only extract data from the first one - error.push(`Line 1 column ${colNum + 1}: Duplicate field.`); - parsedHeader.push({ fieldName: each, dataType: 'dul', fieldId: undefined }); - colNum++; - continue; - } - fields.push(each); - if (validatedFieldNames.includes(each)) { - parsedHeader.push(fieldsList.filter(el => el.fieldName === each)[0]); - } else { - error.push(`Line 1 column ${colNum + 1}: Unknown field.`); - parsedHeader.push({ fieldName: each, dataType: 'unk', fieldId: undefined }); - } - } - colNum++; - } - - const filteredParsedHeader = parsedHeader.filter(el => el !== undefined); - const subjectIdIndex = filteredParsedHeader.findIndex(el => el.fieldName === 'SubjectID') + 1; // ID is the first - const visitIdIndex = filteredParsedHeader.findIndex(el => el.fieldName === 'VisitID') + 1; - return ({ parsedHeader: filteredParsedHeader, error: error.length === 0 ? undefined : error, subjectIdIndex, visitIdIndex }); -} - -export function processDataRow({ subjectIdIndex, visitIdIndex, lineNum, row, parsedHeader, job }: { subjectIdIndex: number, visitIdIndex: number, lineNum: number, row: string[], parsedHeader: any[], job: IJobEntry }): { error?: string[], dataEntry: Partial } { - /* pure function */ - const error: string[] = []; - let colIndex = 0; - const dataEntry: any = { - m_studyId: job.studyId, - m_versionId: null, - deleted: null - }; - if (row.length !== (parsedHeader.filter(el => el !== undefined).length + 1)) { - error.push(`Line ${lineNum}: Uneven field Number; expected ${parsedHeader.length + 1} fields but got ${row.length}`); - return ({ error, dataEntry }); - } - for (const each of row) { - if (colIndex === 0) { - // first column is ID, no actual meaning - colIndex++; - continue; - } - - /* skip for missing data */ - if (each === '') { - colIndex++; - continue; - } - - if (parsedHeader[colIndex] === null) { - colIndex++; - continue; - } - - if (colIndex === subjectIdIndex) { - dataEntry.m_subjectId = each.replace('-', ''); - colIndex++; - continue; - } - - if (colIndex === visitIdIndex) { - dataEntry.m_visitId = each; - colIndex++; - continue; - } - const { fieldId, dataType, possibleValues } = parsedHeader[colIndex - 1]; - if (fieldId === undefined) { - colIndex++; - continue; - } - /* adding value to dataEntry */ - let value: any; - try { - if (each.toString() === '99999') { - value = '99999'; - } else { - switch (dataType) { - case 'cat': {// categorical - const code = parseInt(each, 10).toString(); - if (!possibleValues.map((el: any) => el.code).includes(code)) { - error.push(`Line ${lineNum} column ${colIndex + 1}: Cannot parse '${each}' as categorical, value is illegal.`); - colIndex++; - continue; - } - value = code; - break; - } - case 'dec': {// decimal - if (!/^\d+(.\d+)?$/.test(each)) { - error.push(`Line ${lineNum} column ${colIndex + 1}: Cannot parse '${each}' as decimal.`); - colIndex++; - continue; - } - value = parseFloat(each); - break; - } - case 'int': {// integer - if (!/^-?\d+$/.test(each)) { - error.push(`Line ${lineNum} column ${colIndex + 1}: Cannot parse '${each}' as integer.`); - colIndex++; - continue; - } - value = parseInt(each, 10); - break; - } - case 'bool': {// boolean - if (each.toLowerCase() === 'true' || each.toLowerCase() === 'false') { - value = each.toLowerCase() === 'true'; - } else { - error.push(`Line ${lineNum} column ${colIndex + 1}: value for boolean type must be 'true' or 'false'.`); - colIndex++; - continue; - } - break; - } - case 'str': { - value = each.toString(); - break; - } - case 'date': { - const matcher = /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?/; - if (!each.match(matcher)) { - error.push(`Line ${lineNum} column ${colIndex + 1}: value for date type must be in ISO format.`); - colIndex++; - continue; - } - value = each.toString(); - break; - } - case 'json': { - value = each; - break; - } - case 'file': { - value = each.toString(); - break; - } - default: { - error.push(`Line ${lineNum} column ${colIndex + 1}: Invalid data Type.`); - break; - } - } - } - } catch (e: any) { - error.push(e.toString()); - continue; - } - - if (dataEntry[fieldId] === undefined) { - dataEntry[fieldId] = null; - } - - dataEntry[fieldId] = value; - colIndex++; - } - - if (dataEntry.m_subjectId === undefined) { - error.push('No subject id provided.'); - } - if (dataEntry.m_visitId === undefined) { - error.push('No visit id provided.'); - } - return ({ error: error.length === 0 ? undefined : error, dataEntry }); -} diff --git a/packages/itmat-job-executor/src/curation/FieldCurator.ts b/packages/itmat-job-executor/src/curation/FieldCurator.ts deleted file mode 100644 index ff514360e..000000000 --- a/packages/itmat-job-executor/src/curation/FieldCurator.ts +++ /dev/null @@ -1,209 +0,0 @@ -import * as csvparse from 'csv-parse'; -import { Collection } from 'mongodb'; -import { Writable, Readable } from 'stream'; -import { v4 as uuid } from 'uuid'; -import { IJobEntryForFieldCuration, IFieldEntry, enumValueType } from '@itmat-broker/itmat-types'; - -/* update should be audit trailed */ -/* eid is not checked whether it is unique in the file: this is assumed to be enforced by database */ - -const CORRECT_NUMBER_OF_COLUMN = 33; - -export class FieldCurator { - private _errored: boolean; - private _errors: string[]; - private _numOfFields: number; - - constructor( - private readonly fieldCollection: Collection, - private readonly incomingWebStream: Readable, - private readonly parseOptions: csvparse.Options = { delimiter: ',', quote: '"', relax_column_count: true, skip_records_with_error: true }, - private readonly job: IJobEntryForFieldCuration, - private readonly codesObj: any - ) { - this._errored = false; - this._errors = []; - this._numOfFields = 0; - } - - /* return list of errors. [] if no error */ - public processIncomingStreamAndUploadToMongo(): Promise { - /** - * ID Database TableName TableID SequentialOrder QuestionNumber Fieldname Label Label_DE Label_NL Label_IT Label_ES Label_PL - * Label_F EligibleAnswer IneligibleAnswer Validation DataType ControlType SystemGenerated ValueList Length DisplayFormat Nullable Required - * Mandatory CollectIf NotMapped DefaultValue RegEx RegExErrorMsg ShowOnIndexView Comments - */ - return new Promise((resolve) => { - const fieldIdString: string[] = []; - let lineNum = 0; - let isHeader = true; - let bulkInsert = this.fieldCollection.initializeUnorderedBulkOp(); - const csvparseStream = csvparse.parse(this.parseOptions); - const parseStream = this.incomingWebStream.pipe(csvparseStream); // piping the incoming stream to a parser stream - - csvparseStream.on('skip', (error: any) => { - lineNum++; - this._errored = true; - this._errors.push(error.toString()); - }); - - const uploadWriteStream: NodeJS.WritableStream = new Writable({ - objectMode: true, - write: async (line, _, next) => { - if (isHeader) { - if (line.length !== CORRECT_NUMBER_OF_COLUMN) { - this._errored = true; - this._errors.push(`Line ${lineNum}: expected ${CORRECT_NUMBER_OF_COLUMN} fields but got ${line.length}`); - } - isHeader = false; - lineNum++; - next(); - } else { - const currentLineNum = ++lineNum; - fieldIdString.push(line[0]); - const { error, dataEntry } = processFieldRow({ - lineNum: currentLineNum, - row: line, - job: this.job, - codes: this.codesObj - }); - - if (error) { - this._errored = true; - this._errors.push(...error); - } - - if (this._errored) { - next(); - return; - } - - // // TO_DO { - // curator-defined constraints for values - // } - - bulkInsert.insert(dataEntry); - this._numOfFields++; - if (this._numOfFields > 999) { - this._numOfFields = 0; - await bulkInsert.execute().catch((err) => { - if (err) { - //TODO Handle error recording - console.error(err); - } - }); - bulkInsert = this.fieldCollection.initializeUnorderedBulkOp(); - } - next(); - } - } - }); - - uploadWriteStream.on('finish', async () => { - /* check for subject Id duplicate */ - const set = new Set(fieldIdString); - if (set.size !== fieldIdString.length) { - this._errors.push('Data Error: There is duplicate field id.'); - this._errored = true; - } - - if (!this._errored) { - await bulkInsert.execute().catch((err) => { - if (err) { - //TODO Handle error recording - console.error(err); - } - }); - } - - resolve(this._errors); - }); - - parseStream.pipe(uploadWriteStream); - }); - } -} - -export function processFieldRow({ lineNum, row, job, codes }: { lineNum: number, row: string[], job: IJobEntryForFieldCuration, codes: any }): { error?: string[], dataEntry: IFieldEntry } { - /* pure function */ - const error: string[] = []; - const dataEntry_nouse: any = {}; - const THESE_COL_CANT_BE_EMPTY: Record = { - 0: 'FieldID', - 6: 'Field Name', - 17: 'Data Type' - }; - if (row.length !== CORRECT_NUMBER_OF_COLUMN) { - error.push(`Line ${lineNum}: Uneven field Number; expected ${CORRECT_NUMBER_OF_COLUMN} fields but got ${row.length}.`); - return ({ error, dataEntry: dataEntry_nouse }); - } - - for (let i = 0; i < row.length; i++) { - if (THESE_COL_CANT_BE_EMPTY[i] !== undefined && row[i] === '') { - error.push(`Line ${lineNum} column ${i + 1}: ${THESE_COL_CANT_BE_EMPTY[i]} cannot be empty.`); - continue; - } - } - - /* these fields has to be numbers */ - // fieldId should not be '' - if (!/^\d+$/.test(row[0]) && row[0]) { - error.push(`Line ${lineNum} column 1: Cannot parse field ID as number.`); - } - if (!/^\d+$/.test(row[21]) && row[21] !== '') { - error.push(`Line ${lineNum} column 22: Cannot parse length of characters as number.`); - } - - /* check the value type */ - const dataTypeNames: Record = { - int: enumValueType.INTEGER, - decimal: enumValueType.DECIMAL, - nvarchar: enumValueType.STRING, - varchar: enumValueType.STRING, - datetime: enumValueType.DATETIME, - bit: enumValueType.BOOLEAN, - json: enumValueType.JSON, - file: enumValueType.FILE, - categorical: enumValueType.CATEGORICAL - }; - - // datatypes: if fieldid exist in codes, then convert datatype to categorical - let dataType; - let possibleValues: any[] = []; - if (codes[row[0].toString()] !== undefined) { - dataType = dataTypeNames.categorical; - possibleValues = codes[row[0].toString()]; - } else { - dataType = dataTypeNames[Object.keys(dataTypeNames).filter(el => row[17].toUpperCase().indexOf(el.toUpperCase()) >= 0)[0]]; - } - if (!(Object.keys(dataTypeNames).some(x => row[17].toUpperCase().indexOf(x.toUpperCase()) >= 0))) { - error.push(`Line ${lineNum} column 18: Invalid value type "${row[2]}": use "int" for integers, "decimal()" for decimals, "nvarchar/varchar" for characters/strings, "datetime" for date time, "bit" for bit.`); - } - - /* if valueType = C, then possibleValues cant be empty */ - // if (row[2] === 'c' && row[3] === '') { - // error.push(`Line ${lineNum} column 4: "Possible values" cannot be empty if value type is categorical.`); - // } - - if (error.length !== 0) { - return ({ error, dataEntry: dataEntry_nouse }); - } - - - const dataEntry: IFieldEntry = { - id: uuid(), - studyId: job.studyId, - fieldId: row[0], - fieldName: row[6], - tableName: row[2], - dataType: dataType, - possibleValues: possibleValues, - unit: '', - comments: row[32], - dateAdded: (new Date()).toISOString(), - dateDeleted: null, - dataVersion: null - }; - - return ({ error: undefined, dataEntry }); -} diff --git a/packages/itmat-job-executor/src/curation/JSONCurator.ts b/packages/itmat-job-executor/src/curation/JSONCurator.ts deleted file mode 100644 index 6d2fad057..000000000 --- a/packages/itmat-job-executor/src/curation/JSONCurator.ts +++ /dev/null @@ -1,312 +0,0 @@ -import { Collection } from 'mongodb'; -import { Writable, Readable } from 'stream'; -import JSONStream from 'JSONStream'; -import { IFieldDescriptionObject, IDataEntry, IJobEntry } from '@itmat-broker/itmat-types'; - -/* update should be audit trailed */ -/* eid is not checked whether it is unique in the file: this is assumed to be enforced by database */ -export class JSONCurator { - /** - * things to check: - * - duplicate subject id - * - duplicate field - * - datatype mismatch - * - uneven column number - * - parse / encoding error - */ - private _header: (IFieldDescriptionObject | null)[]; - private _numOfSubj: number; - private _errored: boolean; - private _errors: string[]; - private _subjectIdIndex: number; - private _visitIdIndex: number; - - constructor( - private readonly dataCollection: Collection, - private readonly incomingWebStream: Readable, - private readonly job: IJobEntry, - private readonly fieldsList: any[] - ) { - this._header = [null]; // the first element is subject id - this._numOfSubj = 0; - this._errored = false; - this._errors = []; - this._subjectIdIndex = 0; - this._visitIdIndex = 0; - } - - /* return list of errors. [] if no error */ - public processIncomingStreamAndUploadToMongo(): Promise { - return new Promise((resolve) => { - let isHeader = true; - let objectNum = 0; - const subjectString: string[] = []; - let bulkInsert = this.dataCollection.initializeUnorderedBulkOp(); - const jsonstream = JSONStream.parse([{}]); - const uploadWriteStream: NodeJS.WritableStream = new Writable({ - objectMode: true, - write: async (chunk, _, callback) => { - objectNum++; - if (isHeader) { - objectNum++; - const { error, parsedHeader, subjectIdIndex, visitIdIndex } = processJSONHeader(chunk, this.fieldsList); - if (error) { - this._errored = true; - this._errors.push(...error); - } - this._header = parsedHeader; - this._subjectIdIndex = subjectIdIndex; - this._visitIdIndex = visitIdIndex; - isHeader = false; - - } else { - subjectString.push(chunk[0]); - const { error, dataEntry } = processEachSubject({ - subjectIdIndex: this._subjectIdIndex, - visitIdIndex: this._visitIdIndex, - objectNum: objectNum, - subject: chunk, - parsedHeader: this._header, - job: this.job - }); - if (error) { - this._errored = true; - this._errors.push(...error); - } - - if (this._errored) { - callback(); - return; - } - const matchObj = { - m_subjectId: dataEntry.m_subjectId, - m_visitId: dataEntry.m_visitId, - m_versionId: dataEntry.m_versionId, - m_studyId: dataEntry.m_studyId - }; - bulkInsert.find(matchObj).upsert().updateOne({ $set: dataEntry }); - this._numOfSubj++; - - } - if (this._numOfSubj > 999) { - this._numOfSubj = 0; - await bulkInsert.execute().catch((err) => { - if (err) { - //TODO Handle error recording - console.error(err, err?.getWriteErrors?.()); - return; - } - }); - bulkInsert = this.dataCollection.initializeUnorderedBulkOp(); - } - callback(); - } - }); - - uploadWriteStream.on('finish', async () => { - if (!this._errored) { - await bulkInsert.execute().catch((err) => { - if (err) { - //TOTO Handle error recording - console.error(err); - return; - } - }); - } - resolve(this._errors); - }); - this.incomingWebStream.pipe(jsonstream); - jsonstream.pipe(uploadWriteStream); - }); - } -} - - -export function processJSONHeader(header: string[], fieldsList: any[]): { error?: string[], parsedHeader: any[], subjectIdIndex: number, visitIdIndex: number } { - /* pure function */ - /* headerObject is ['eid', 1@0.0, 2@0.1:c] */ - /* returns a parsed object array and error (undefined if no error) */ - - // const fieldstrings: string[] = []; - const error: string[] = []; - const parsedHeader: any[] = Array(header.length); - let colNum = 0; - const fields: string[] = []; - const validatedFieldNames = fieldsList.map(el => el.fieldName); - if (!header.includes('SubjectID') || !header.includes('VisitID')) { - error.push('SubjectID or VisitID not found.'); - } - for (const each of header) { - if (colNum === 0) { - colNum++; - continue; - } - if (each === null || each === undefined || each === '') { - error.push(`Object 1, Column ${colNum}: Field Name should not be empty.`); - colNum++; - parsedHeader.push(null); - } else { - if (fields.includes(each)) { - // if duplicates happens, we only extract data from the first one - error.push(`Object 1 column ${colNum + 1}: Duplicate field.`); - parsedHeader.push({ fieldName: each, dataType: 'dul', fieldId: undefined }); - colNum++; - continue; - } - fields.push(each); - if (validatedFieldNames.includes(each)) { - parsedHeader.push(fieldsList.filter(el => el.fieldName === each)[0]); - } else { - error.push(`Object 1 column ${colNum + 1}: Unknown field.`); - parsedHeader.push({ fieldName: each, dataType: 'unk', fieldId: undefined }); - } - } - colNum++; - } - - const filteredParsedHeader = parsedHeader.filter(el => el !== undefined); - const subjectIdIndex = filteredParsedHeader.findIndex(el => el.fieldName === 'SubjectID') + 1; // ID is the first - const visitIdIndex = filteredParsedHeader.findIndex(el => el.fieldName === 'VisitID') + 1; - return ({ parsedHeader: filteredParsedHeader, error: error.length === 0 ? undefined : error, subjectIdIndex, visitIdIndex }); -} - -export function processEachSubject({ subjectIdIndex, visitIdIndex, objectNum, subject, parsedHeader, job }: { subjectIdIndex: number, visitIdIndex: number, objectNum: number, subject: string[], parsedHeader: any[], job: IJobEntry }): { error?: string[], dataEntry: Partial } { - /* pure function */ - const error: string[] = []; - let colIndex = 0; - const dataEntry: any = { - m_studyId: job.studyId, - deleted: null - }; - if (subject.length !== (parsedHeader.filter(el => el !== undefined).length + 1)) { - error.push(`Object ${objectNum}: Uneven field Number; expected ${parsedHeader.length + 1} fields but got ${subject.length}`); - return ({ error, dataEntry }); - } - for (const each of subject) { - if (colIndex === 0) { - // first column is ID, no actual meaning - colIndex++; - continue; - } - - /* skip for missing data */ - if (each === '') { - colIndex++; - continue; - } - - if (parsedHeader[colIndex] === null) { - colIndex++; - continue; - } - - if (colIndex === subjectIdIndex) { - dataEntry.m_subjectId = each.replace('-', ''); - colIndex++; - continue; - } - - if (colIndex === visitIdIndex) { - dataEntry.m_visitId = each; - colIndex++; - continue; - } - const { fieldId, dataType, possibleValues } = parsedHeader[colIndex - 1]; - if (fieldId === undefined) { - colIndex++; - continue; - } - /* adding value to dataEntry */ - let value: any; - try { - if (each.toString() === '99999') { - value = '99999'; - } else { - switch (dataType) { - case 'cat': {// categorical - const code = parseInt(each, 10).toString(); - if (!possibleValues.map((el: any) => el.code).includes(code)) { - error.push(`Object ${objectNum} column ${colIndex + 1}: Cannot parse '${each}' as categorical, value is illegal.`); - colIndex++; - continue; - } - value = code; - break; - } - case 'dec': {// decimal - if (!/^\d+(.\d+)?$/.test(each)) { - error.push(`Object ${objectNum} column ${colIndex + 1}: Cannot parse '${each}' as decimal.`); - colIndex++; - continue; - } - value = parseFloat(each); - break; - } - case 'int': {// integer - if (!/^-?\d+$/.test(each)) { - error.push(`Object ${objectNum} column ${colIndex + 1}: Cannot parse '${each}' as integer.`); - colIndex++; - continue; - } - value = parseInt(each, 10); - break; - } - case 'bool': {// boolean - if (each.toLowerCase() === 'true' || each.toLowerCase() === 'false') { - value = each.toLowerCase() === 'true'; - } else { - error.push(`Object ${objectNum} column ${colIndex + 1}: value for boolean type must be 'true' or 'false'.`); - colIndex++; - continue; - } - break; - } - case 'str': { - value = each.toString(); - break; - } - case 'date': { - const matcher = /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?/; - if (!each.match(matcher)) { - error.push(`Object ${objectNum} column ${colIndex + 1}: value for date type must be in ISO format.`); - colIndex++; - continue; - } - value = each.toString(); - break; - } - case 'json': { - value = each; - break; - } - case 'file': { - value = each.toString(); - break; - } - default: { - error.push(`Object ${objectNum} column ${colIndex + 1}: Invalid data Type.`); - break; - } - } - } - } catch (e: any) { - error.push(e.toString()); - continue; - } - - if (dataEntry[fieldId] === undefined) { - dataEntry[fieldId] = null; - } - - dataEntry[fieldId] = value; - colIndex++; - } - - if (dataEntry.m_subjectId === undefined) { - error.push('No subject id provided.'); - } - if (dataEntry.m_visitId === undefined) { - error.push('No visit id provided.'); - } - return ({ error: error.length === 0 ? undefined : error, dataEntry }); -} diff --git a/packages/itmat-job-executor/src/database/database.ts b/packages/itmat-job-executor/src/database/database.ts index 2c8e14699..74eabf00e 100644 --- a/packages/itmat-job-executor/src/database/database.ts +++ b/packages/itmat-job-executor/src/database/database.ts @@ -1,27 +1,64 @@ -import type { IFile, IJobEntry, IProject, IQueryEntry, IDataEntry } from '@itmat-broker/itmat-types'; +// import { Database as DatabaseBase, IDatabaseBaseConfig } from '@itmat-broker/itmat-commons'; +// import { IDatabaseCollectionConfig } from '@itmat-broker/itmat-cores'; + + +import type { IField, IFile, IJob, ILog, IOrganisation, IProject, IPubkey, IQueryEntry, IRole, IStudy, IUser, IStandardization, IConfig, IData, IDrive, ICache, IDomain, IOntologyTree, IBase, IWebAuthn, IInstance} from '@itmat-broker/itmat-types'; import { Database as DatabaseBase, IDatabaseBaseConfig } from '@itmat-broker/itmat-commons'; import type { Collection } from 'mongodb'; export interface IDatabaseConfig extends IDatabaseBaseConfig { collections: { + users_collection: string, jobs_collection: string, - UKB_coding_collection: string, + studies_collection: string, + projects_collection: string, + queries_collection: string, field_dictionary_collection: string, + roles_collection: string, files_collection: string, + organisations_collection: string, + log_collection: string, + pubkeys_collection: string, data_collection: string, - queries_collection: string, - projects_collection: string + standardizations_collection: string, + configs_collection: string, + ontologies_collection: string, + drives_collection: string, + colddata_collection: string, + cache_collection: string, + domains_collection: string, + doc_collection: string, + webauthn_collection: string, + instance_collection: string }; } export interface IDatabaseCollectionConfig { - jobs_collection: Collection>, - UKB_coding_collection: Collection, - field_dictionary_collection: Collection, - files_collection: Collection, - data_collection: Collection, + users_collection: Collection, + jobs_collection: Collection, + studies_collection: Collection, + projects_collection: Collection, queries_collection: Collection, - projects_collection: Collection + field_dictionary_collection: Collection, + roles_collection: Collection, + files_collection: Collection, + organisations_collection: Collection, + log_collection: Collection, + pubkeys_collection: Collection, + data_collection: Collection, + standardizations_collection: Collection, + configs_collection: Collection, + ontologies_collection: Collection, + drives_collection: Collection, + colddata_collection: Collection, + cache_collection: Collection, + domains_collection: Collection, + // TODO: Implemet doc feature + docs_collection: Collection, + webauthn_collection: Collection, + instance_collection: Collection } +// export type DBType = DatabaseBase; + export const db = new DatabaseBase(); diff --git a/packages/itmat-job-executor/src/emailer/emailer.ts b/packages/itmat-job-executor/src/emailer/emailer.ts new file mode 100644 index 000000000..7c0782d8a --- /dev/null +++ b/packages/itmat-job-executor/src/emailer/emailer.ts @@ -0,0 +1,4 @@ +import appConfig from '../utils/configManager'; +import { Mailer } from '@itmat-broker/itmat-commons'; + +export const mailer = new Mailer(appConfig.nodemailer); diff --git a/packages/itmat-job-executor/src/index.ts b/packages/itmat-job-executor/src/index.ts index b40e47156..2d7a2369f 100644 --- a/packages/itmat-job-executor/src/index.ts +++ b/packages/itmat-job-executor/src/index.ts @@ -49,9 +49,7 @@ function serverSpinning() { interfaceSockets = []; interfaceServer.close(() => { console.info(`Shuting down executor server ${process.pid} ...`); - interfaceRouter?.on('close', () => { - serverStart(); - }) || serverStart(); + serverStart(); }); } else { serverStart(); @@ -60,6 +58,7 @@ function serverSpinning() { serverSpinning(); +// eslint-disable-next-line @typescript-eslint/no-explicit-any declare const module: any; if (module.hot) { module.hot.accept('./index', serverSpinning); diff --git a/packages/itmat-job-executor/src/jobDispatch/dispatcher.ts b/packages/itmat-job-executor/src/jobDispatch/dispatcher.ts index ba276419b..eb5ab2dbe 100644 --- a/packages/itmat-job-executor/src/jobDispatch/dispatcher.ts +++ b/packages/itmat-job-executor/src/jobDispatch/dispatcher.ts @@ -1,29 +1,53 @@ -import { IJobEntry } from '@itmat-broker/itmat-types'; +import { IJob, enumJobType, IJobActionReturn} from '@itmat-broker/itmat-types'; import { JobHandler } from '../jobHandlers/jobHandlerInterface'; +import { Logger } from '@itmat-broker/itmat-commons'; + export class JobDispatcher { private _handlerCollection: { - [jobType: string]: () => Promise + [jobType in enumJobType]?: () => Promise + }; + private _handlerInstances: { + [jobType in enumJobType]?: JobHandler }; constructor() { this.dispatch = this.dispatch.bind(this); this._handlerCollection = {}; + this._handlerInstances = {}; } - public registerJobType(jobType: string, getHandlerInstanceFunction: () => Promise): void { + public registerJobType(jobType: enumJobType, getHandlerInstanceFunction: () => Promise): void { this._handlerCollection[jobType] = getHandlerInstanceFunction; } - public removeHandler(jobType: string): void { + public removeHandler(jobType: enumJobType): void { delete this._handlerCollection[jobType]; + delete this._handlerInstances[jobType]; } - public async dispatch(job: IJobEntry): Promise { - if (!this._handlerCollection[job.jobType]) { + public async dispatch(job: IJob): Promise { + if (!this._handlerCollection[job.type]) { //TODO set job to UNPROCESSED - return; + Logger.error(`No JobHandler for job ${job.type}`); + throw Error(`No JobHandler for job ${job.type}`); + } + + let handler = this._handlerInstances[job.type]; + if (!handler) { + const handlerFactory = this._handlerCollection[job.type]; + if (handlerFactory) { + handler = await handlerFactory(); + this._handlerInstances[job.type] = handler; + } else { + throw new Error(`No JobHandler for job ${job.type}`); + } + } + + try { + return await handler.execute(job); + } catch (error) { + return { successful: false, error }; } - await (await this._handlerCollection[job.jobType]()).execute(job); } -} +} \ No newline at end of file diff --git a/packages/itmat-job-executor/src/jobExecutorRunner.ts b/packages/itmat-job-executor/src/jobExecutorRunner.ts index 2a2ae5638..d0ad179f2 100644 --- a/packages/itmat-job-executor/src/jobExecutorRunner.ts +++ b/packages/itmat-job-executor/src/jobExecutorRunner.ts @@ -1,16 +1,16 @@ // External node module imports import { v4 as uuid } from 'uuid'; -import { db } from './database/database'; import { objStore } from './objStore/objStore'; import { Router } from './server/router'; import { Runner } from './server/server'; import { JobPoller } from '@itmat-broker/itmat-commons'; import { JobDispatcher } from './jobDispatch/dispatcher'; import { MongoClient } from 'mongodb'; -import { UKB_CSV_UPLOAD_Handler } from './jobHandlers/UKB_CSV_UPLOAD_Handler'; -import { UKB_JSON_UPLOAD_Handler } from './jobHandlers/UKB_JSON_UPLOAD_Handler'; -import { UKB_FIELD_INFO_UPLOAD_Handler } from './jobHandlers/UKB_FIELD_INFO_UPLOAD_Handler'; -import { QueryHandler } from './query/queryHandler'; +import { APIHandler } from './jobHandlers/apiJobHandler'; +import { defaultSettings, enumJobType} from '@itmat-broker/itmat-types'; +import { LXDJobHandler} from './jobHandlers/lxdJobHandler'; +import { LXDMonitorHandler} from './jobHandlers/lxdMonitorHandler'; +import {db} from './database/database'; class ITMATJobExecutorRunner extends Runner { @@ -23,38 +23,39 @@ class ITMATJobExecutorRunner extends Runner { * @return {Promise} Resolve to a native Express.js router ready to use on success. * In case of error, an ErrorStack is rejected. */ - public start(): Promise { + public async start(): Promise { + // eslint-disable-next-line @typescript-eslint/no-this-alias const _this = this; return new Promise((resolve, reject) => { // Operate database migration if necessary db.connect(this.config.database, MongoClient) - .then(() => objStore.connect(this.config.objectStore)) + .then(async () => objStore.connect(this.config.objectStore)) .then(() => { - _this.router = new Router(); - const jobDispatcher = new JobDispatcher(); /* TO_DO: can we figure out the files at runtime and import at runtime */ - jobDispatcher.registerJobType('DATA_UPLOAD_CSV', UKB_CSV_UPLOAD_Handler.prototype.getInstance.bind(UKB_CSV_UPLOAD_Handler)); - jobDispatcher.registerJobType('DATA_UPLOAD_JSON', UKB_JSON_UPLOAD_Handler.prototype.getInstance.bind(UKB_JSON_UPLOAD_Handler)); - jobDispatcher.registerJobType('FIELD_INFO_UPLOAD', UKB_FIELD_INFO_UPLOAD_Handler.prototype.getInstance.bind(UKB_FIELD_INFO_UPLOAD_Handler)); - jobDispatcher.registerJobType('QUERY_EXECUTION', QueryHandler.prototype.getInstance.bind(QueryHandler)); - // jobDispatcher.registerJobType('UKB_IMAGE_UPLOAD', UKB_IMAGE_UPLOAD_Handler.prototype.getInstance); + // jobDispatcher.registerJobType('QUERY_EXECUTION', QueryHandler.prototype.getInstance.bind(QueryHandler)); + jobDispatcher.registerJobType(enumJobType.DMPAPI, APIHandler.getInstance.bind(APIHandler)); + // register the lxd jobhandler, bind the instance collection to LXDJobHandler + jobDispatcher.registerJobType(enumJobType.LXD, LXDJobHandler.getInstance.bind(LXDJobHandler)); + // Register the LXD monitor handler + jobDispatcher.registerJobType(enumJobType.LXD_MONITOR, LXDMonitorHandler.getInstance.bind(LXDMonitorHandler)); const poller = new JobPoller({ identity: uuid(), - jobCollection: db.collections!.jobs_collection, + jobCollection: db.collections.jobs_collection, pollingInterval: this.config.pollingInterval, - action: jobDispatcher.dispatch + action: jobDispatcher.dispatch.bind(jobDispatcher), + jobSchedulerConfig: defaultSettings.systemConfig.jobSchedulerConfig }); poller.setInterval(); // Return the Express application return resolve(_this.router); - }).catch((err: any) => reject(err)); + }).catch((err) => reject(err)); }); } diff --git a/packages/itmat-job-executor/src/jobHandlers/UKB_CSV_UPLOAD_Handler.ts b/packages/itmat-job-executor/src/jobHandlers/UKB_CSV_UPLOAD_Handler.ts deleted file mode 100644 index 9e1766519..000000000 --- a/packages/itmat-job-executor/src/jobHandlers/UKB_CSV_UPLOAD_Handler.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { IJobEntry } from '@itmat-broker/itmat-types'; -import { db } from '../database/database'; -import { objStore } from '../objStore/objStore'; -import { JobHandler } from './jobHandlerInterface'; -import { CSVCurator } from '../curation/CSVCurator'; -import { Readable } from 'stream'; - -export class UKB_CSV_UPLOAD_Handler extends JobHandler { - private _instance?: UKB_CSV_UPLOAD_Handler; - // private ukbCurator: UKBCurator; - - public async getInstance() { - if (!this._instance) { - this._instance = new UKB_CSV_UPLOAD_Handler(); - } - return this._instance; - } - - public async execute(job: IJobEntry) { - // check if study version exists: checked by resolvers - // check if fieldTreeId version exists: checked by resolvers - - - const errorsList: Array<{ - fileId: string; - fileName?: string; - error: string | string[]; - }> = []; - // get fieldid info from database - const fieldsList = await db.collections!.field_dictionary_collection.find({ studyId: job.studyId }).toArray(); - - for (const fileId of job.receivedFiles) { - try { - const file = await db.collections!.files_collection.findOne({ id: fileId, deleted: null })!; - if (!file) { - errorsList.push({ fileId: fileId, error: 'file does not exist' }); - continue; - } - const components = file.fileName.split('.')[0].split('_'); - components.shift(); - const tableName = components.join('_'); - const filteredFieldsList = fieldsList.filter(el => el.tableName === tableName); - const fileStream: Readable = await objStore.downloadFile(job.studyId, file.uri); - const csvcurator = new CSVCurator( - db.collections!.data_collection, - fileStream, - undefined, - job, - filteredFieldsList - ); - const errors = await csvcurator.processIncomingStreamAndUploadToMongo(); - if (errors.length !== 0) { - errorsList.push({ fileId: file.id, fileName: file.fileName, error: errors }); - await db.collections!.jobs_collection.updateOne({ id: job.id }, { $set: { status: 'error', error: errorsList as any } }); - } else { - await db.collections!.jobs_collection.updateOne({ id: job.id }, { $set: { status: 'finished' } }); - } - } catch (e) { - throw new Error(); - } - } - } -} diff --git a/packages/itmat-job-executor/src/jobHandlers/UKB_FIELD_INFO_UPLOAD_Handler.ts b/packages/itmat-job-executor/src/jobHandlers/UKB_FIELD_INFO_UPLOAD_Handler.ts deleted file mode 100644 index ff3d160a7..000000000 --- a/packages/itmat-job-executor/src/jobHandlers/UKB_FIELD_INFO_UPLOAD_Handler.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { db } from '../database/database'; -import { objStore } from '../objStore/objStore'; -import { JobHandler } from './jobHandlerInterface'; -import { IJobEntryForFieldCuration } from '@itmat-broker/itmat-types'; -import { FieldCurator } from '../curation/FieldCurator'; -import { Readable, Writable } from 'stream'; -import * as csvparse from 'csv-parse'; - -export class UKB_FIELD_INFO_UPLOAD_Handler extends JobHandler { - private _instance?: UKB_FIELD_INFO_UPLOAD_Handler; - - public async getInstance() { - if (!this._instance) { - this._instance = new UKB_FIELD_INFO_UPLOAD_Handler(); - } - return this._instance; - } - - public async execute(job: IJobEntryForFieldCuration) { - const file = await db.collections!.files_collection.findOne({ id: job.receivedFiles[0], deleted: null })!; - if (!file) { - throw new Error('File does not exists.'); - } - - const codesFile = await db.collections!.files_collection.findOne({ fileName: 'prolific_Codes.csv' }); - if (!codesFile) { - throw new Error('Codes File does not exists.'); - } - - const codesStream: Readable = await objStore.downloadFile(job.studyId, codesFile.uri); - const codesObj = processCodesFileStreamAndReturnList(codesStream); - - const fileStream: Readable = await objStore.downloadFile(job.studyId, file.uri); - const fieldcurator = new FieldCurator( - db.collections!.field_dictionary_collection, - fileStream, - undefined, - job, - codesObj - ); - const errors = await fieldcurator.processIncomingStreamAndUploadToMongo(); - - if (errors.length !== 0) { - await db.collections!.jobs_collection.updateOne({ id: job.id }, { $set: { status: 'error', error: errors as any } }); - return; - } else { - await db.collections!.jobs_collection.updateOne({ id: job.id }, { $set: { status: 'finished' } }); - // await this.updateFieldTreesInMongo(job, fieldTreeId); - } - - } - - // public async updateFieldTreesInMongo(job: IJobEntryForFieldCuration, fieldTreeId: string) { - // const queryObject = { 'id': job.studyId, 'deleted': null, 'dataVersions.id': job.data!.dataVersionId }; - // const updateObject = { $push: { 'dataVersions.$.fieldTrees': fieldTreeId } }; - // return await db.collections!.studies_collection.findOneAndUpdate(queryObject, updateObject); - // } -} - -function processCodesFileStreamAndReturnList(incomingStream: Readable): Promise { - const parseOptions: csvparse.Options = { delimiter: ',', quote: '"', relax_column_count: true, skip_records_with_error: true }; - const csvparseStream = csvparse.parse(parseOptions); - const parseStream = incomingStream.pipe(csvparseStream); - - // csvparseStream.on('skip', () => { - // }); - - let isHeader = true; - const codes: any = {}; - - const CORRECT_NUMBER_OF_COLUMN = 4; - const uploadWriteStream: NodeJS.WritableStream = new Writable({ - objectMode: true, - write: async (line, _, next) => { - if (isHeader) { - if (line.length !== CORRECT_NUMBER_OF_COLUMN) { - throw console.error('Line length not equal to 4.'); - - } - isHeader = false; - next(); - } else { - if (codes[line[1].toString()] === undefined) { - codes[line[1].toString()] = []; - } - codes[line[1].toString()].push({ - code: parseInt(line[2], 10).toString(), - description: line[3].toString() - }); - - next(); - } - } - }); - - uploadWriteStream.on('finish', async () => { - //TODO - /* check for subject Id duplicate */ - }); - - parseStream.pipe(uploadWriteStream); - return codes; -} diff --git a/packages/itmat-job-executor/src/jobHandlers/UKB_JSON_UPLOAD_Handler.ts b/packages/itmat-job-executor/src/jobHandlers/UKB_JSON_UPLOAD_Handler.ts deleted file mode 100644 index 597735f56..000000000 --- a/packages/itmat-job-executor/src/jobHandlers/UKB_JSON_UPLOAD_Handler.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { IJobEntry } from '@itmat-broker/itmat-types'; -import { db } from '../database/database'; -import { objStore } from '../objStore/objStore'; -import { JobHandler } from './jobHandlerInterface'; -import { JSONCurator } from '../curation/JSONCurator'; -import { Readable } from 'stream'; - -export class UKB_JSON_UPLOAD_Handler extends JobHandler { - private _instance?: UKB_JSON_UPLOAD_Handler; - // private ukbCurator: UKBCurator; - - public async getInstance() { - if (!this._instance) { - this._instance = new UKB_JSON_UPLOAD_Handler(); - } - return this._instance; - } - - public async execute(job: IJobEntry) { - const errorsList: Array<{ - fileId: string; - fileName?: string; - error: string | string[]; - }> = []; - // get fieldid info from database - const fieldsList = await db.collections!.field_dictionary_collection.find({ studyId: job.studyId }).toArray(); - - for (const fileId of job.receivedFiles) { - try { - const file = await db.collections!.files_collection.findOne({ id: fileId, deleted: null })!; - if (!file) { - errorsList.push({ fileId: fileId, error: 'file does not exist' }); - continue; - } - const components = file.fileName.split('.')[0].split('_'); - components.shift(); - const tableName = components.join('_'); - const filteredFieldsList = fieldsList.filter(el => el.tableName === tableName); - const fileStream: Readable = await objStore.downloadFile(job.studyId, file.uri); - const jsoncurator = new JSONCurator( - db.collections!.data_collection, - fileStream, - job, - filteredFieldsList - ); - const errors = await jsoncurator.processIncomingStreamAndUploadToMongo(); - if (errors.length !== 0) { - errorsList.push({ fileId: file.id, fileName: file.fileName, error: errors }); - await db.collections!.jobs_collection.updateOne({ id: job.id }, { $set: { status: 'error', error: errorsList as any } }); - } else { - await db.collections!.jobs_collection.updateOne({ id: job.id }, { $set: { status: 'finished' } }); - } - } catch (e) { - throw new Error(); - } - } - } -} diff --git a/packages/itmat-job-executor/src/jobHandlers/apiJobHandler.ts b/packages/itmat-job-executor/src/jobHandlers/apiJobHandler.ts new file mode 100644 index 000000000..8863a33cd --- /dev/null +++ b/packages/itmat-job-executor/src/jobHandlers/apiJobHandler.ts @@ -0,0 +1,70 @@ +import { UserCore, JobCore, InstanceCore, LxdManager } from '@itmat-broker/itmat-cores'; +import { mailer } from '../emailer/emailer'; +import configManager from '../utils/configManager'; +import { objStore } from '../objStore/objStore'; +import { JobHandler } from './jobHandlerInterface'; +import { IJob, IJobActionReturn } from '@itmat-broker/itmat-types'; +import {db} from '../database/database'; +import { Logger } from '@itmat-broker/itmat-commons'; + +export class APIHandler extends JobHandler { + private static _instance: APIHandler; + public jobCore: JobCore; + public instanceCore: InstanceCore; + public lxdManager: LxdManager; + + constructor() { + super(); + this.jobCore = new JobCore(db); + this.instanceCore = new InstanceCore(db, mailer, configManager, this.jobCore, new UserCore(db, mailer, configManager, objStore)); + this.lxdManager = new LxdManager(configManager); + + // Test connection without blocking startup + this.lxdManager.testConnection() + .then(isConnected => { + if (isConnected) { + Logger.log('LXD service is available'); + } else { + Logger.warn('LXD service is not available - will retry on first operation'); + } + }) + .catch(err => { + Logger.warn(`Error testing LXD connection: ${err}`); + }); + + } + + public static override async getInstance(): Promise { + if (!APIHandler._instance) { + APIHandler._instance = new APIHandler(); + } + return APIHandler._instance; + } + + public async execute(document: IJob):Promise{ + try { + if (!document.executor) { + return { successful: false, error: 'No path found.' }; + } + const [className, methodName] = document.executor.path.split('.'); + const instance = this[className as keyof this] as unknown; + + if (!instance || typeof (instance as Record)[methodName] !== 'function') { + throw new Error(`Method ${methodName} not found on class ${className}`); + } + + const method = (instance as Record unknown>)[methodName]; + let parameters: unknown[] = []; + if (Array.isArray(document.parameters)) { + parameters = document.parameters; + } else if (document.parameters && typeof document.parameters === 'object') { + parameters = [document.parameters]; + } + + const result = await method(...parameters); + return { successful: true, result }; + } catch (e) { + return { successful: false, error: e instanceof Error ? e.message : JSON.stringify(e) }; + } + } +} \ No newline at end of file diff --git a/packages/itmat-job-executor/src/jobHandlers/jobHandlerInterface.ts b/packages/itmat-job-executor/src/jobHandlers/jobHandlerInterface.ts index ddd9004ac..5517e5083 100644 --- a/packages/itmat-job-executor/src/jobHandlers/jobHandlerInterface.ts +++ b/packages/itmat-job-executor/src/jobHandlers/jobHandlerInterface.ts @@ -1,12 +1,19 @@ -import { IJobEntry } from '@itmat-broker/itmat-types'; +import { IJob, IJobActionReturn } from '@itmat-broker/itmat-types'; export abstract class JobHandler { /* subclass can decide either singleton (if there is expensive metadata collection) or just make this return new instance everytime. Gets called the first time when a job of the job type appears, so is lazily instantiated */ - public abstract getInstance(): Promise; + public static async getInstance(): Promise { + throw new Error('Subclass must implement static getInstance method.'); + } /* called by job dispatcher */ - public abstract execute(document: IJobEntry): Promise; + public abstract execute(document: IJob): Promise; } + +export type CodeRecords = Record>; \ No newline at end of file diff --git a/packages/itmat-job-executor/src/jobHandlers/lxdJobHandler.ts b/packages/itmat-job-executor/src/jobHandlers/lxdJobHandler.ts new file mode 100644 index 000000000..40a67a417 --- /dev/null +++ b/packages/itmat-job-executor/src/jobHandlers/lxdJobHandler.ts @@ -0,0 +1,313 @@ +import { + IJob, + IInstance, + enumOpeType, + enumInstanceStatus, + LxdConfiguration, + LXDInstanceState +} from '@itmat-broker/itmat-types'; +import { APIHandler } from './apiJobHandler'; +import { Logger } from '@itmat-broker/itmat-commons'; +import {CoreError, enumCoreErrors } from '@itmat-broker/itmat-types'; +import type * as mongodb from 'mongodb'; +import { pollOperation } from './lxdPollOperation'; +import { db } from '../database/database'; + +export class LXDJobHandler extends APIHandler { + private static instance: LXDJobHandler; + private readonly instanceCollection: mongodb.Collection; + + private constructor() { + super(); + this.instanceCollection = db.collections.instance_collection; + } + + public static override async getInstance(): Promise { + if (!LXDJobHandler.instance) { + LXDJobHandler.instance = new LXDJobHandler(); + } + return LXDJobHandler.instance; + } + + public override async execute(document: IJob) { + const { operation, instanceId } = document.metadata as { operation: string; instanceId: string }; + + if (!operation || !instanceId) { + throw new Error('Missing required metadata: operation or instanceId.'); + } + + switch (operation) { + case enumOpeType.CREATE: + return this.create(document); + case enumOpeType.UPDATE: + return this.update(document); + case enumOpeType.START: + return this.startStopInstance(instanceId, operation); + case enumOpeType.STOP: + return this.startStopInstance(instanceId, operation); + case enumOpeType.DELETE: + return this.deleteInstance(instanceId); + default: + throw new Error('Unsupported operation.'); + } + } + + private async create(document: IJob){ + const metadata = document.metadata as { payload: LxdConfiguration, instanceId: string }; + const payload = metadata.payload ?? {}; + const instanceId = metadata.instanceId ?? ''; + + const instanceData = await this.instanceCollection.findOne({ id: instanceId }); + if (!instanceData) { + throw new Error('Instance not found.'); + } + // if instance's status is not pending, then do not create the instance + if (instanceData.status !== enumInstanceStatus.PENDING) { + throw new Error('Instance status is not PENDING.'); + } + const project = instanceData.project || 'default'; + + try { + const data = await this.lxdManager.createInstance(payload, project); + + if (data?.operation) { + + try { + await pollOperation(this.lxdManager, data.operation, project); + // await this.updateInstanceMetadata(instanceId, data, enumInstanceStatus.STOPPED); + + // Add wait and check loop + let attempts = 0; + const maxAttempts = 30; // 5 minutes total + while (attempts < maxAttempts) { + const instanceInfo= await this.lxdManager.getInstanceInfo(instanceData.name, project); + const stateResponse = await this.lxdManager.getInstanceState(instanceData.name, project); + const instanceState = stateResponse.data as LXDInstanceState; + + // Check both operation status and instance state + if (!instanceInfo.data.metadata?.running_operation && + instanceState.status === 'Stopped') { + await this.updateInstanceMetadata(instanceId, data, enumInstanceStatus.STOPPED); + return { successful: true, result: data }; + } + + await new Promise(resolve => setTimeout(resolve, 10000)); + attempts++; + } + + throw new Error('Instance creation timeout - still busy after 5 minutes'); + + } catch (error) { + if (error instanceof CoreError && error.errorCode === enumCoreErrors.POLLING_ERROR) { + Logger.error(`LXD polling error for instance ${instanceId}: ${error.message}`); + + // Handle the case where instance already exists + const errorMessage = error.message || ''; + if (errorMessage.includes('Instance') && errorMessage.includes('already exists')) { + Logger.warn(`Instance ${instanceId} already exists, treating as successful creation`); + await this.updateInstanceMetadata(instanceId, {}, enumInstanceStatus.STOPPED); + return { + successful: true, + result: { message: 'Instance already exists' } + }; + } + + // Check for read-only filesystem error or other fatal storage errors + if (errorMessage.includes('read-only file system') || + errorMessage.includes('Failed creating instance') || + errorMessage.includes('Failed to create mount directory')) { + Logger.error(`Fatal storage error for instance ${instanceId}: ${errorMessage}`); + await this.updateInstanceMetadata(instanceId, { error: errorMessage }, enumInstanceStatus.FAILED); + return { + successful: false, + error: `Fatal storage error: ${errorMessage}` + }; + } + + // Don't update status, keep it as is for polling errors + return { + successful: false, + error: error.message + }; + } + // For non-polling errors, rethrow to be caught by outer catch + Logger.error(`Error polling operation for instance ${instanceId}: ${error}`); + + // if the instance is failed out of space or other errors, set the instance status to failed + + // Handle specific error cases for instance creation failures + const errorMessage = error instanceof Error ? error.message : String(error); + Logger.error(`Instance creation failed for ${instanceId}: ${errorMessage}`); + + // Check for specific error types + const errorDetails = { + message: errorMessage, + timestamp: new Date(), + type: 'creation_failure' + }; + + if (errorMessage.includes('out of space') || errorMessage.includes('no space left')) { + errorDetails.type = 'storage_space_error'; + } else if (errorMessage.includes('network') || errorMessage.includes('connection')) { + errorDetails.type = 'network_error'; + } else if (errorMessage.includes('timeout')) { + errorDetails.type = 'timeout_error'; + } + + // Update instance with error details and failed status + await this.updateInstanceMetadata( + instanceId, + { ...data, error: errorDetails }, + enumInstanceStatus.FAILED + ); + throw error; + } + } + + // await this.updateInstanceMetadata(instanceId, data, enumInstanceStatus.STOPPED); + return { successful: true, result: data }; + } catch (error) { + Logger.error(`Error creating instance: ${instanceId}, ${error}`); + await this.updateInstanceMetadata(instanceId, {}, enumInstanceStatus.FAILED); + return { successful: false, error: error instanceof Error ? error.message : String(error) }; + } + } + + private async update(document: IJob) { + // const { instanceId, updates } = document.metadata ?? {}; + const { instanceId, updates } = document.metadata as { instanceId: string; updates: LxdConfiguration }; + const instance = await this.instanceCollection.findOne({ id: instanceId }); + + if (!instance) { + Logger.error(`LXD update: Instance not found.: ${instanceId} ${JSON.stringify(updates)}`); + return { successful: false, error: 'LXD update: Instance not found' }; + } + const project = instance.project || 'default'; + try { + const data = await this.lxdManager.updateInstance(instance.name, updates, project); + return { successful: true, result: data }; + } catch (error) { + Logger.error(`Error updating instance configuration: ${instanceId} - ${JSON.stringify(error)}`); + return { successful: false, error: error instanceof Error ? error.message : String(error) }; + } + } + + private async startStopInstance(instanceId: string, action: enumOpeType.START | enumOpeType.STOP) { + const instanceData = await this.instanceCollection.findOne({ id: instanceId }); + if (!instanceData) { + return { successful: false, error: 'Instance not found.' }; + } + const project = instanceData.project || 'default'; + try { + const data = await this.lxdManager.startStopInstance(instanceData.name, action, project); + + if (data?.operation) { + try { + await pollOperation(this.lxdManager, data.operation, project); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + + // Handle "already running" case for START operation + if (action === enumOpeType.START && errorMessage.includes('already running')) { + Logger.warn(`Instance ${instanceData.name} is already running, treating as successful start`); + return { + successful: true, + result: { message: 'Instance already running' } + }; + } + + // Handle "already stopped" case for STOP operation + if (action === enumOpeType.STOP && errorMessage.includes('already stopped')) { + Logger.warn(`Instance ${instanceData.name} is already stopped, treating as successful stop`); + return { + successful: true, + result: { message: 'Instance already stopped' } + }; + } + + // Re-throw error for other cases + throw error; + } + } + // update the status of the instance by the state monitor + // const newStatus = action === enumOpeType.START ? enumInstanceStatus.RUNNING : enumInstanceStatus.STOPPED; + // await this.updateInstanceMetadata(instanceId, null, newStatus); + + return { successful: true, result: data }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + + // Handle "already running" error outside the poll operation + if (action === enumOpeType.START && errorMessage.includes('already running')) { + Logger.warn(`Instance ${instanceData.name} is already running, treating as successful start`); + return { + successful: true, + result: { message: 'Instance already running' } + }; + } + + // Handle "already stopped" error outside the poll operation + if (action === enumOpeType.STOP && errorMessage.includes('already stopped')) { + Logger.warn(`Instance ${instanceData.name} is already stopped, treating as successful stop`); + return { + successful: true, + result: { message: 'Instance already stopped' } + }; + } + + Logger.error(`Error in startStopInstance ${instanceData.name}: ${error}`); + return { successful: false, error: errorMessage }; + } + } + + private async deleteInstance(instanceId: string) { + const instanceData = await this.instanceCollection.findOne({ id: instanceId }); + if (!instanceData) { + return { successful: false, error: 'Instance not found.' }; + } + // if instance's status is not deleted, then do not delete the instance + if (instanceData.status !== enumInstanceStatus.DELETED) { + return { successful: false, error: 'Instance status is not DELETED.' }; + } + const project = instanceData.project || 'default'; + try { + const data = await this.lxdManager.deleteInstance(instanceData.name, project); + + if (data?.operation) { + await pollOperation(this.lxdManager, data.operation, project); + } + // once the instance is deleted, remove the instance from the database + await this.instanceCollection.deleteOne({ id: instanceId }); + return { successful: true, result: data }; + } catch (error) { + Logger.error(`[JOB] Failed to delete instance: ${error}`); + return { successful: false, error: error instanceof Error ? error.message : String(error) }; + } + } + + private async updateInstanceMetadata( + instanceId: string, + metadata: Record | null, + status: enumInstanceStatus + ) { + const updateObject: mongodb.UpdateFilter = { + $set: { + status: status + } + }; + if (metadata !== null) { + updateObject.$set = { ...updateObject.$set, metadata }; + } + + try { + const updateResult = await this.instanceCollection.findOneAndUpdate({ id: instanceId }, updateObject); + if (!updateResult) { + Logger.error(`Failed to update instance ${instanceId}:`); + throw new Error(`Failed to update instance metadata: ${instanceId}`); + } + } catch (error) { + Logger.error(`Failed to update instance ${instanceId} error: ${error}`); + throw new Error(`Failed to update instance metadata: ${instanceId} ${error instanceof Error ? error.message : String(error)}`); + } + } +} \ No newline at end of file diff --git a/packages/itmat-job-executor/src/jobHandlers/lxdMonitorHandler.ts b/packages/itmat-job-executor/src/jobHandlers/lxdMonitorHandler.ts new file mode 100644 index 000000000..24f604804 --- /dev/null +++ b/packages/itmat-job-executor/src/jobHandlers/lxdMonitorHandler.ts @@ -0,0 +1,352 @@ +import { + IJob, + IInstance, + enumInstanceStatus, + enumMonitorType, + LXDInstanceState, + IJobActionReturn +} from '@itmat-broker/itmat-types'; +import { APIHandler } from './apiJobHandler'; +import { LogThrottler } from '@itmat-broker/itmat-commons'; +import type * as mongodb from 'mongodb'; +import { db } from '../database/database'; + +export class LXDMonitorHandler extends APIHandler { + private static instance: LXDMonitorHandler; + private readonly instanceCollection: mongodb.Collection; + private instanceErrors: Map = new Map(); + + constructor() { + super(); + this.instanceCollection = db.collections.instance_collection; + } + + public static override async getInstance(): Promise { + if (!LXDMonitorHandler.instance) { + LXDMonitorHandler.instance = new LXDMonitorHandler(); + } + return LXDMonitorHandler.instance; + } + + public override async execute(document: IJob): Promise{ + const { operation, userId } = document.metadata as { operation: string; userId: string } ?? {}; + + if (!operation || !userId) { + return { successful: false, error: 'Missing required metadata: operation or userId.' }; + } + + try { + switch (operation) { + case enumMonitorType.STATE: + return await this.updateInstanceState(userId); + case enumMonitorType.SYNC_DELETION: + return await this.synchronizeDeletedInstances(userId); + default: + return { successful: false, error: 'Unsupported operation.' }; + } + } catch (error) { + return { successful: false, error: error instanceof Error ? error.message : String(error) }; + } + } + + private async updateInstanceState(userId: string) { + // Get all instances for this user, filter the status of DELETED instances + const instances = await this.instanceCollection.find({ + userId: userId, + status: { $ne: enumInstanceStatus.DELETED } + }).toArray(); + + for (const instance of instances) { + try { + const project = instance.project || 'default'; + const instanceLocation = await this.getInstanceLocationWithErrorHandling(instance); + + // If getInstanceLocation failed and returned null, skip this instance + if (instanceLocation === null) { + continue; + } + + const hostIp = await this.getInstanceHostIpWithErrorHandling(instanceLocation, instance); + + // If getInstanceHostIp failed, skip this instance + if (hostIp === null) { + continue; + } + + const response = await this.getInstanceStateWithErrorHandling(instance, project); + + // If getInstanceState failed, skip this instance + if (response === null) { + continue; + } + + if (response.data) { + const instanceState = response.data as LXDInstanceState; + instanceState.hostIp = hostIp; // add the host ip to the instance state + + await this.instanceCollection.updateOne( + { id: instance.id }, + { + $set: { + lxdState: instanceState, + status: this.determineInstanceStatus(instanceState) + } + } + ); + + // Reset any error counts for this instance + this.resetErrorCounts(instance.name); + } else { + // Use throttled logging for missing state data + LogThrottler.throttledLog( + `instance-state-null-${instance.name}`, + `Failed to retrieve state data for instance: ${instance.name}`, + 'warn' + ); + } + } catch (error) { + // Use throttled logging for unexpected errors + LogThrottler.throttledLog( + `unexpected-error-${instance.name}`, + `Unexpected error updating state for instance ${instance.name}: ${error}`, + 'error' + ); + } + } + + // Add a default return statement + return { successful: true}; + } + + + private async getInstanceLocationWithErrorHandling(instance: IInstance): Promise { + try { + return await this.lxdManager.getInstanceLocation(instance.name, instance.project || 'default'); + } catch (error) { + // Determine if this is an AggregateError + const isAggregateError = String(error).includes('AggregateError'); + const errorMsg = error instanceof Error ? error.message : String(error); + + // Define throttle options based on error type + const throttleOptions = { + initialLogInterval: isAggregateError ? 600000 : 300000, // 10 min for AggregateError, 5 min for others + subsequentLogInterval: isAggregateError ? 1800000 : 900000, // 30 min for AggregateError, 15 min for others + maxOccurrences: isAggregateError ? 3 : 5, + summarizeInterval: isAggregateError ? 1800000 : 900000 // Provide summary less frequently for AggregateError + }; + + if (errorMsg.includes('404') || errorMsg.includes('Instance not found')) { + // Use a throttled log for not found errors + LogThrottler.throttledLog( + `instance-not-found-${instance.name}`, + `Instance not found while determining location: ${instance.name}`, + 'warn', + throttleOptions + ); + } else { + // For other, more unusual errors, also use explicit throttling options + LogThrottler.throttledLog( + `location-error-${instance.name}`, + `Error determining instance location for ${instance.name}: ${errorMsg}`, + 'error', + throttleOptions + ); + } + return null; + } + } + + private async getInstanceHostIpWithErrorHandling(instanceLocation: string, instance: IInstance): Promise { + try { + return await this.lxdManager.getInstanceHostIp(instanceLocation); + } catch (error) { + LogThrottler.throttledLog( + `host-ip-error-${instance.name}`, + `Error retrieving host IP for instance ${instance.name}: ${error}`, + 'error' + ); + return null; + } + } + + private async getInstanceStateWithErrorHandling(instance: IInstance, project: string) { + try { + return await this.lxdManager.getInstanceState(instance.name, project); + } catch (error) { + // Handle 404 errors specially - these are common during instance lifecycle + const errorMsg = error instanceof Error ? error.message : String(error); + + if (errorMsg.includes('404') || errorMsg.includes('Instance not found')) { + // Use throttled logging + LogThrottler.throttledLog( + `state-not-found-${instance.name}`, + `Instance ${instance.name} not found while checking state`, + 'warn', + { + initialLogInterval: 300000, // 5 minutes + subsequentLogInterval: 1800000 // 30 minutes + } + ); + } else { + // For other errors, log with normal throttling + LogThrottler.throttledLog( + `state-error-${instance.name}`, + `Error updating state for instance ${instance.name}: ${errorMsg}`, + 'error' + ); + } + return null; + } + } + + // TODO: define the function to delete the instance that synchronously the status with both in the database and the lxd server + // call delete again from the lxd server if the instance is showed deleted in the database + // set the status to DELETED in the database if the instance is deleted in the lxd server (not exist in the lxd server) + + + // TODO: define the function to delete the instance that should be deleted after the lifespan + 10 day ( comapre the life.deletedTime) + // check the expried instance and delete them, and recaulate the lifespan of them + + + + private determineInstanceStatus(state: LXDInstanceState): enumInstanceStatus { + switch (state.status) { + case 'Running': + return enumInstanceStatus.RUNNING; + case 'Stopped': + return enumInstanceStatus.STOPPED; + case 'Error': + return enumInstanceStatus.FAILED; + default: + // Use throttled logging for unknown states + LogThrottler.throttledLog( + `unknown-state-${state.status}`, + `Unknown instance state: ${state.status}`, + 'error' + ); + return enumInstanceStatus.FAILED; + } + } + + private resetErrorCounts(instanceName: string): void { + // Reset error tracking for an instance that is now working correctly + LogThrottler.resetTracking(`instance-not-found-${instanceName}`); + LogThrottler.resetTracking(`location-error-${instanceName}`); + LogThrottler.resetTracking(`host-ip-error-${instanceName}`); + LogThrottler.resetTracking(`state-not-found-${instanceName}`); + LogThrottler.resetTracking(`state-error-${instanceName}`); + LogThrottler.resetTracking(`unexpected-error-${instanceName}`); + } + + /** + * Synchronizes instance deletion status between the database and LXD server + * - Deletes instances from LXD server if they're marked as DELETED in the database but still exist on the server + * - Updates database status to DELETED if instances don't exist on the LXD server + * @param userId The user ID whose instances to synchronize + * @returns Result of the synchronization operation + */ + private async synchronizeDeletedInstances(userId: string): Promise { + try { + // Get all instances for this user, including ones marked as DELETED + const instances = await this.instanceCollection.find({ + userId: userId + }).toArray(); + + let syncCount = 0; + + for (const instance of instances) { + const project = instance.project || 'default'; + + try { + // Check if the instance exists on the LXD server + let instanceExistsOnLXD = true; + + try { + // Try to get instance state - will throw an error if instance doesn't exist + await this.lxdManager.getInstanceState(instance.name, project); + } catch (error) { + // If error contains 404 or "not found", the instance doesn't exist on LXD + const errorMsg = error instanceof Error ? error.message : String(error); + if (errorMsg.includes('404') || errorMsg.includes('Instance not found')) { + instanceExistsOnLXD = false; + } else { + // For other errors, log and continue to next instance + LogThrottler.throttledLog( + `sync-deletion-error-${instance.name}`, + `Error checking instance existence for ${instance.name}: ${errorMsg}`, + 'error' + ); + continue; + } + } + + // Case 1: Instance is marked as DELETED in DB but still exists on LXD server + if (instance.status === enumInstanceStatus.DELETED && instanceExistsOnLXD) { + try { + await this.lxdManager.deleteInstance(instance.name, project); + // Reduce verbosity for routine cleanup operations with longer throttling intervals + LogThrottler.throttledLog( + `sync-deletion-success-${instance.name}`, + `Successfully deleted instance ${instance.name} from LXD server that was already marked as deleted in DB`, + 'info', + { + initialLogInterval: 300000, // 5 minutes + subsequentLogInterval: 1800000, // 30 minutes + maxOccurrences: 5, // Reduce logging frequency after 5 occurrences + summarizeInterval: 7200000 // Summarize every 2 hours + } + ); + syncCount++; + } catch (deleteError) { + LogThrottler.throttledLog( + `sync-deletion-failed-${instance.name}`, + `Failed to delete instance ${instance.name} from LXD server: ${deleteError}`, + 'error' + ); + } + } + // Case 2: Instance exists in DB but not on LXD server (and not already marked as deleted) + else if (!instanceExistsOnLXD && instance.status !== enumInstanceStatus.DELETED) { + await this.instanceCollection.updateOne( + { id: instance.id }, + { + $set: { + status: enumInstanceStatus.DELETED + } + } + ); + LogThrottler.throttledLog( + `sync-status-updated-${instance.name}`, + `Updated instance ${instance.name} status to DELETED in DB as it no longer exists on LXD server`, + 'info', + { + initialLogInterval: 300000, // 5 minutes + subsequentLogInterval: 1800000, // 30 minutes + maxOccurrences: 5, // Reduce logging frequency after 5 occurrences + summarizeInterval: 7200000 // Summarize every 2 hours + } + ); + syncCount++; + } + + } catch (instanceError) { + LogThrottler.throttledLog( + `sync-deletion-instance-error-${instance.name}`, + `Error synchronizing deletion for instance ${instance.name}: ${instanceError}`, + 'error' + ); + } + } + + return { + successful: true, + result: `Synchronized deletion status for ${syncCount} instances` + }; + } catch (error) { + return { + successful: false, + error: `Failed to synchronize deleted instances: ${error instanceof Error ? error.message : String(error)}` + }; + } + } +} \ No newline at end of file diff --git a/packages/itmat-job-executor/src/jobHandlers/lxdPollOperation.ts b/packages/itmat-job-executor/src/jobHandlers/lxdPollOperation.ts new file mode 100644 index 000000000..74112fa1c --- /dev/null +++ b/packages/itmat-job-executor/src/jobHandlers/lxdPollOperation.ts @@ -0,0 +1,119 @@ +import { Logger, LogThrottler } from '@itmat-broker/itmat-commons'; +import { LxdManager } from '@itmat-broker/itmat-cores'; // Adjust the import path as necessary +import {CoreError, enumCoreErrors } from '@itmat-broker/itmat-types'; + +/** + * Polls an LXD operation until it is completed + * @param lxdManager The LxdManager instance + * @param operationUrl The URL of the operation to poll + * @param maxTry The maximum number of tries before giving up + * @returns A promise that resolves when the operation is completed + */ +export const pollOperation = async ( + lxdManager: LxdManager, + operationUrl: string, + project: string, + maxTry = 50 +): Promise => { + let tryCount = 0; + return new Promise((resolve, reject) => { + const operationIdMatch = operationUrl.match(/\/1\.0\/operations\/([^/]+)/); + if (!operationIdMatch) { + reject(new Error('Invalid operation URL')); + return; + } + const operationId = operationIdMatch[1]; + const throttleKey = `lxd-poll-${operationId.substring(0, 8)}`; + + const interval = setInterval(() => { + tryCount++; + if (tryCount > maxTry) { + clearInterval(interval); + const errorMessage = `Operation polling timed out: ${operationUrl} -> ${operationId}`; + LogThrottler.throttledLog( + `lxd-poll-timeout-${operationId.substring(0, 8)}`, + errorMessage, + 'error', + { initialLogInterval: 60000, subsequentLogInterval: 300000 } + ); + reject(new Error(errorMessage)); + return; + } + void (async () => { + try { + const opData = await lxdManager.getOperationStatus(`/1.0/operations/${operationId}?project=${project}`); + const operationStatus = opData.metadata.status; + + if (operationStatus === 'Success') { + clearInterval(interval); + resolve(); // Operation succeeded + } else if (operationStatus === 'Failure') { + if (opData.metadata.err.includes('Instance is busy running')) { + return; + } else { + clearInterval(interval); + const errorMessage = `Operation failed for ${opData.metadata.err}`; + // Don't throttle specific failure messages as they're important + Logger.error(errorMessage); + reject(new CoreError(enumCoreErrors.POLLING_ERROR, errorMessage)); + } + } else if (operationStatus === 'Running') { + return; + } else { + clearInterval(interval); + const errorMessage = `Unknown operation status: ${operationStatus}`; + LogThrottler.throttledLog( + `lxd-unknown-status-${operationStatus}`, + errorMessage, + 'error' + ); + reject(new CoreError(enumCoreErrors.POLLING_ERROR, errorMessage)); + } + } catch (error) { + // Extract meaningful info from error + const errorMessage = LogThrottler.getErrorMessage(error); + + // Categorize and throttle different types of errors + if (errorMessage.includes('timeout') || errorMessage.includes('ECONNABORTED')) { + // Timeout errors - these happen frequently in batches + LogThrottler.throttledLog( + `lxd-timeout-${operationId.substring(0, 8)}`, + `Error fetching operation status: ${errorMessage}`, + 'error', + { + initialLogInterval: 30000, // Only log every 30 seconds initially + subsequentLogInterval: 300000, // Then only every 5 minutes + summarizeInterval: 900000 // Summary every 15 minutes + } + ); + } else if (errorMessage.includes('Cannot read properties of null')) { + // Null reference errors - also frequent during certain operations + LogThrottler.throttledLog( + `lxd-null-ref-${operationId.substring(0, 8)}`, + `Error polling operation: ${errorMessage}`, + 'error', + { initialLogInterval: 60000, subsequentLogInterval: 300000 } + ); + } else { + // Other errors - log more frequently as they might be novel + LogThrottler.throttledLog( + `lxd-error-${throttleKey}`, + `Error polling operation: ${errorMessage}`, + 'error', + { initialLogInterval: 10000, subsequentLogInterval: 60000 } + ); + } + + // Only reject and stop polling on sustained issues that persist for several attempts + if (tryCount % 10 === 0) { // Every 10th try (with 4s interval = ~40s) + clearInterval(interval); + reject(new CoreError( + enumCoreErrors.POLLING_ERROR, + `Fatal error polling operation: ${errorMessage}` + )); + } + } + })(); + }, 4000); + }); +}; \ No newline at end of file diff --git a/packages/itmat-job-executor/src/query/pipeLineGenerator.ts b/packages/itmat-job-executor/src/query/pipeLineGenerator.ts deleted file mode 100644 index fc9213416..000000000 --- a/packages/itmat-job-executor/src/query/pipeLineGenerator.ts +++ /dev/null @@ -1,246 +0,0 @@ -// /** -// * @fn QueryHelper -// * @desc Helper to manipulate and query the MongoDB database storing UK biobank data formatted in the -// * white paper format -// * @param config -// * @constructor -// */ - -class PipelineGenerator { - constructor(private readonly config = {}) { } - - /* - * @fn buildPipeline - * @desc Methods that builds thee pipeline for the mongo query - * @param query - * structure of the request - * { - "new_fields": [ - { - "name": String // Name of the new field - "value": String // equation defining the derived feature - "op": String // Derived only - } - ], - "cohort": [ - [{ - "field": String, // Field identifier or field in the form X.X for a count - "value": String Or Float, // Value requested can either categorical - "op": String // Logical operation - }, - { - "field": String, // Field identifier or field in the form X.X for a count - "value": String Or Float, // Value requested can either categorical - "op": String // Logical operation - } - ], - [ - { - "field": String, // Field identifier or field in the form X.X for a count - "value": String Or Float, // Value requested can either categorical - "op": String // Logical operation - } - ]], - "data_requested": [ "field1", "field2", "field3"] // Fields requested - } - } - */ - public buildPipeline(query: any, studyId: string, availableDataVersions: any) { - // check query, then decide whether to parse the query - if (query['data_requested'] === undefined || query['cohort'] === undefined || query['new_fields'] === undefined) { - return null; - } - if (Array.isArray(query['data_requested']) === false || Array.isArray(query['cohort']) === false || Array.isArray(query['new_fields']) === false) { - return null; - } - - const fields = { _id: 0, m_subjectId: 1, m_visitId: 1 }; - // We send back the requested fields - query.data_requested.forEach((field: any) => { - (fields as any)[field] = 1; - }); - const addFields = {}; - // We send back the newly created derived fields by default - if (query.new_fields.length > 0) { - query.new_fields.forEach((field: any) => { - if (field.op === 'derived') { - (fields as any)[field.name] = 1; - (addFields as any)[field.name] = this._createNewField(field.value); - } else { - return 'Error'; - } - }); - } - let match = {}; - if (query.cohort.length > 1) { - const subqueries: any = []; - query.cohort.forEach((subcohort: any) => { - // addFields. - subqueries.push(this._translateCohort(subcohort)); - }); - match = { $or: subqueries }; - } else { - match = this._translateCohort(query.cohort[0]); - } - let dataVersionsFilter: any; - if (availableDataVersions == null) { - dataVersionsFilter = null; - } else { - dataVersionsFilter = { $in: availableDataVersions }; - } - if (this._isEmptyObject(addFields)) { - return [ - { $match: { m_studyId: studyId } }, - { $match: match }, - { $match: { m_versionId: dataVersionsFilter } }, - { $project: fields } - ]; - } else { - return [ - { $match: { m_studyId: studyId } }, - { $addFields: addFields }, - { $match: match }, - { $match: { m_versionId: dataVersionsFilter } }, - { $project: fields } - ]; - } - } - - - /** - * @fn _createNewdField - * @desc Creates the new fields required to compare when using an expresion like BMI or an average - * expression = { - * "left": json for nested or string for field id or Float, - * "right": json for nested or string for field id or Float, - * "op": String // Logical operation - * @param expression - * @return json formated in the mongo format in the pipeline stage addfield - * @private - */ - private _createNewField(expression: any) { - let newField = {}; - switch (expression.op) { - case '*': - newField = { - $multiply: [this._createNewField(expression.left), this._createNewField(expression.right)] - }; - break; - case '/': - newField = { - $divide: [this._createNewField(expression.left), this._createNewField(expression.right)] - }; - break; - case '-': - newField = { - $subtract: [this._createNewField(expression.left), this._createNewField(expression.right)] - }; - break; - case '+': - newField = { - $add: [this._createNewField(expression.left), this._createNewField(expression.right)] - }; - break; - case '^': - // NB the right side my be an integer while the left must be a field ! - newField = { - $pow: ['$' + expression.left, parseInt(expression.right, 10)] - }; - break; - case 'val': - newField = parseFloat(expression.left); - break; - case 'field': - newField = '$' + expression.left; - break; - default: - break; - } - - return newField; - } - - /** - * @fn _isEmptyObject - * @desc tests if an object is empty - * @param obj - * @returns {boolean} - * @private - */ - private _isEmptyObject(obj: any) { - return !Object.keys(obj).length; - } - - /** - * @fn _translateCohort - * @desc Tranforms a query into a mongo query. - * @param cohort - * @private - */ - private _translateCohort(cohort: any) { - const match = {}; - - cohort.forEach(function (select: any) { - - switch (select.op) { - case '=': - // select.value must be an array - (match as any)[select.field] = { $in: [select.value] }; - break; - case '!=': - // select.value must be an array - (match as any)[select.field] = { $ne: [select.value] }; - break; - case '<': - // select.value must be a float - (match as any)[select.field] = { $lt: parseFloat(select.value) }; - break; - case '>': - // select.value must be a float - (match as any)[select.field] = { $gt: parseFloat(select.value) }; - break; - case 'derived': { - // equation must only have + - * / - const derivedOperation = select.value.split(' '); - if (derivedOperation[0] === '=') { - (match as any)[select.field] = { $eq: parseFloat(select.value) }; - } - if (derivedOperation[0] === '>') { - (match as any)[select.field] = { $gt: parseFloat(select.value) }; - } - if (derivedOperation[0] === '<') { - (match as any)[select.field] = { $lt: parseFloat(select.value) }; - } - break; - } - case 'exists': - // We check if the field exists. This is to be used for checking if a patient - // has an image - (match as any)[select.field] = { $exists: true }; - break; - case 'count': { - // counts can only be positive. NB: > and < are inclusive e.g. < is <= - const countOperation = select.value.split(' '); - const countfield = select.field + '.count'; - if (countOperation[0] === '=') { - (match as any)[countfield] = { $eq: parseInt(countOperation[1], 10) }; - } - if (countOperation[0] === '>') { - (match as any)[countfield] = { $gt: parseInt(countOperation[1], 10) }; - } - if (countOperation[0] === '<') { - (match as any)[countfield] = { $lt: parseInt(countOperation[1], 10) }; - } - break; - } - default: - break; - } - } - ); - return match; - } -} - - -export const pipelineGenerator = Object.freeze(new PipelineGenerator()); diff --git a/packages/itmat-job-executor/src/query/queryController.ts b/packages/itmat-job-executor/src/query/queryController.ts deleted file mode 100644 index 0fcf29628..000000000 --- a/packages/itmat-job-executor/src/query/queryController.ts +++ /dev/null @@ -1,88 +0,0 @@ -// TEST QUERY example -// -// const query_test = { -// id : '474b9676-8b2c-4983-9dbe-a6ba84370288', -// queryString : '{"31.0.0":"Male"}', -// study : null, -// requester : 'admin', -// status : 'PROCESSING', -// error : null, -// cancelled : false, -// lastClaimed : 1542288352356.0, -// queryResult : {}, -// data_requested: [ '31.0.0', '102.0.1', '102.0.2'], -// cohort: [ -// [{ field: '102.0', -// value: '> 1', -// op: 'count'}, -// { field: '31.0.0', -// value: 'Male', -// op: '='}], -// [{ field: '31.0.0', -// value: 'Female', -// op: '='}, -// { field: '102.0.1', -// value: '', -// op: 'exists'}] -// ], -// new_fields: [ -// { -// name: 'BMI', -// value: { -// left: { -// left: '12143.0.0', -// right: '', -// op: 'field' -// }, -// right: { -// left: '12144.0.0', -// right: '2', -// op: '^' -// }, -// op: '/' -// }, -// op: 'derived' -// } -// ] -// }; - -/// ** -// * @fn getStatus -// * @desc HTTP method GET handler on this service status -// * @param req Incoming message -// * @param res Server Response -// */ -// QueryController.prototype.processQuery = function(req, res) { -// let _this = this; -// let queryId = req.body.query_id; -// try { -// _this._queryCollection.findOne({id : queryId}).then(function(query){ -// -// let pipeline = _this._queryHelper.buildPipeline(query); -// -// if(pipeline === 'Error'){ -// res.status(500); -// res.json('Error while building the pipeline: ' + query); -// } -// -// _this._dataCollection.aggregate(pipeline).toArray().then(function (results) { -// res.status(200); -// res.json(results); -// },function(error){ -// res.status(500).json(error + ' ' + error.toString()); -// -// }); -// },function(error){ -// res.status(401); -// res.json('Error while processing the query ' + queryId + error.toString()); -// }); -// } -// catch (error) { -// res.status(510); -// res.json('Error occurred', error); -// } -// }; -// -// -// module.exports = QueryController; -// diff --git a/packages/itmat-job-executor/src/query/queryHandler.ts b/packages/itmat-job-executor/src/query/queryHandler.ts deleted file mode 100644 index 5eda74887..000000000 --- a/packages/itmat-job-executor/src/query/queryHandler.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { IJobEntry } from '@itmat-broker/itmat-types'; -import { Logger } from '@itmat-broker/itmat-commons'; -import { db } from '../database/database'; -import { pipelineGenerator } from './pipeLineGenerator'; -import { JobHandler } from '../jobHandlers/jobHandlerInterface'; - -export class QueryHandler extends JobHandler { - private _instance?: QueryHandler; - // private ukbCurator: UKBCurator; - - public async getInstance() { - if (!this._instance) { - this._instance = new QueryHandler(); - } - return this._instance; - } - - public async execute(job: IJobEntry<{ queryId: string[], projectId: string, studyId: string }>) { - // get available data versions - const thisProject = await db.collections!.projects_collection.findOne({ id: job.data?.projectId }); - const availableDataVersions = [thisProject!.dataVersion]; - const queryId = job.data?.queryId[0]; - const queryString = await db.collections!.queries_collection.findOne({ id: queryId })!; - const pipeline = pipelineGenerator.buildPipeline(queryString!.queryString, job.studyId, availableDataVersions); - if (!pipeline) { - /* update query status */ - await db.collections!.queries_collection.findOneAndUpdate({ queryId }, { - $set: { - error: 'Pipeline was not generated properly', - status: 'FINISHED WITH ERROR' - } - }); - return; - } - try { - const result = await db.collections!.data_collection.aggregate(pipeline).toArray(); - /* if the query is on a project, then we need to map the results m_eid */ - if (job.projectId) { - const project = await db.collections!.projects_collection.findOne({ id: job.data?.projectId })!; - if (project === null || project === undefined) { - await db.collections!.queries_collection.findOneAndUpdate({ queryId }, { - $set: { - error: 'Project does not exist or has been deleted.', - status: 'FINISHED WITH ERROR' - } - }); - return; - } - const mapping = project.patientMapping; - result.forEach((el) => { - if (el.m_eid === undefined) { return; } - el.m_eid = mapping[el.m_eid]; - }); - } - await db.collections!.queries_collection.findOneAndUpdate({ id: queryId }, { - $set: { - queryResult: JSON.stringify(result), - status: 'FINISHED' - } - }); - await db.collections!.jobs_collection.updateOne({ id: job.id }, { $set: { status: 'finished' } }); - return; - } catch (e: any) { - /* log */ - Logger.error(e.toString()); - - /* update query status */ - await db.collections!.queries_collection.findOneAndUpdate({ queryId }, { - $set: { - error: e.toString(), - status: 'FINISHED WITH ERROR' - } - }); - return; - } - } - - -} diff --git a/packages/itmat-job-executor/src/server/server.ts b/packages/itmat-job-executor/src/server/server.ts index a7f61751d..f92d461d4 100644 --- a/packages/itmat-job-executor/src/server/server.ts +++ b/packages/itmat-job-executor/src/server/server.ts @@ -1,16 +1,15 @@ -import { IServerBaseConfig, ServerBase } from '@itmat-broker/itmat-commons'; -import { IConfiguration } from '../utils/configManager'; +import { ServerBase, Logger, CustomError} from '@itmat-broker/itmat-commons'; - -export interface IServerConfig extends IServerBaseConfig { - pollingInterval: number; -} +import {IServerConfig, IConfiguration} from '../utils/configManager'; export class Runner extends ServerBase { - constructor(protected config: IConfiguration) { + constructor(protected override config: IConfiguration) { super(config); } protected async additionalChecksAndActions(): Promise { - return; + if (isNaN(parseInt(`${this.config.bcrypt.saltround}`, 10))) { + Logger.log(new CustomError('Salt round must be a number')); + process.exit(1); + } } -} +} \ No newline at end of file diff --git a/packages/itmat-job-executor/src/utils/configManager.ts b/packages/itmat-job-executor/src/utils/configManager.ts index 7170cb8c8..9ca2fe6f7 100644 --- a/packages/itmat-job-executor/src/utils/configManager.ts +++ b/packages/itmat-job-executor/src/utils/configManager.ts @@ -1,31 +1,60 @@ import merge from 'deepmerge'; import fs from 'fs-extra'; import path from 'path'; -import { IObjectStoreConfig, IDatabaseBaseConfig } from '@itmat-broker/itmat-commons'; +import { IObjectStoreConfig, IDatabaseBaseConfig, Logger } from '@itmat-broker/itmat-commons'; import configDefaults from '../../config/config.sample.json'; -import { IServerConfig } from '../server/server.js'; +import chalk from 'chalk'; +import SMTPTransport from 'nodemailer/lib/smtp-transport'; + +import {IServerBaseConfig} from '@itmat-broker/itmat-commons'; + +export interface IServerConfig extends IServerBaseConfig { + bcrypt: { + saltround: number, + }; + pollingInterval: number; +} + export interface IConfiguration extends IServerConfig { + appName: string; database: IDatabaseBaseConfig; objectStore: IObjectStoreConfig; + nodemailer: SMTPTransport.Options & { auth: { user: string, pass: string } }; + aesSecret: string; + sessionsSecret: string; + adminEmail: string; + aeEndpoint: string; + useWebdav: boolean; + webdavPort: number; + lxdEndpoint: string; + lxdPortRange: number[]; // the port range for lxd a list of numbers + lxdSquidProxy: string; // the squid proxy server for lxd + lxdStoragePool: string; + lxdProject: string; + webdavServer: string; + systemKey: Record; + lxdCertFile: Record; + lxdRejectUnauthorized: boolean; + jupyterPort: number; + dmp: Record; } - -class ConfigurationManager { +export class ConfigurationManager { public static expand(configurationFiles: string[]): IConfiguration { let config = configDefaults; - console.log('Applied default configuration.'); + Logger.log('Applied default configuration.'); configurationFiles.forEach((configurationFile) => { try { if (fs.existsSync(configurationFile)) { const content = fs.readFileSync(configurationFile, 'utf8'); config = merge(config, JSON.parse(content)); - console.log(`Applied configuration from ${path.resolve(configurationFile)}.`); + Logger.log(`Applied configuration from ${path.resolve(configurationFile)}.`); } } catch (e) { - console.error('Could not parse configuration file.'); + Logger.error(chalk.red(`Cannot parse configuration file.${JSON.stringify(e)}`)); } }); @@ -34,4 +63,4 @@ class ConfigurationManager { } -export default ConfigurationManager.expand((process.env.NODE_ENV === 'development' ? [path.join(__dirname.replace('dist', ''), 'config/config.json')] : []).concat(['config/config.json'])); +export default ConfigurationManager.expand((process.env['NODE_ENV'] === 'development' ? [path.join(__dirname.replace('dist', ''), 'config/config.json')] : []).concat(['config/config.json'])); diff --git a/packages/itmat-job-executor/src/utils/noop.ts b/packages/itmat-job-executor/src/utils/noop.ts index 65d573907..5e791dcc8 100644 --- a/packages/itmat-job-executor/src/utils/noop.ts +++ b/packages/itmat-job-executor/src/utils/noop.ts @@ -1,2 +1 @@ -/* eslint-disable import/no-anonymous-default-export */ export default (): void => { return; }; diff --git a/packages/itmat-job-executor/test/testFiles/CSVCurator.tsv b/packages/itmat-job-executor/test/testFiles/CSVCurator.tsv deleted file mode 100644 index 1efb9b85a..000000000 --- a/packages/itmat-job-executor/test/testFiles/CSVCurator.tsv +++ /dev/null @@ -1,2227 +0,0 @@ -ID,SubjectID,VisitID,Sex,Description,Weight -1,I7N3G6G,1,0,no description,60.2 -2,I7N3G6G,2,1,no description,60.2 -3,I7N3G6G,3,1,no description,60.2 -4,I7N3G6G,4,1,no description,60.2 -5,I7N3G6G,5,1,no description,60.2 -6,I7N3G6G,6,1,no description,60.2 -7,I7N3G6G,7,1,no description,60.2 -8,I7N3G6G,8,1,no description,60.2 -9,I7N3G6G,9,1,no description,60.2 -10,I7N3G6G,10,1,no description,60.2 -11,I7N3G6G,11,1,no description,60.2 -12,I7N3G6G,12,1,no description,60.2 -13,I7N3G6G,13,1,no description,60.2 -14,I7N3G6G,14,1,no description,60.2 -15,I7N3G6G,15,1,no description,60.2 -16,I7N3G6G,16,1,no description,60.2 -17,I7N3G6G,17,1,no description,60.2 -18,I7N3G6G,18,1,no description,60.2 -19,I7N3G6G,19,1,no description,60.2 -20,I7N3G6G,20,1,no description,60.2 -21,I7N3G6G,21,1,no description,60.2 -22,I7N3G6G,22,1,no description,60.2 -23,I7N3G6G,23,1,no description,60.2 -24,I7N3G6G,24,1,no description,60.2 -25,I7N3G6G,25,1,no description,60.2 -26,I7N3G6G,26,1,no description,60.2 -27,I7N3G6G,27,1,no description,60.2 -28,I7N3G6G,28,1,no description,60.2 -29,I7N3G6G,29,1,no description,60.2 -30,I7N3G6G,30,1,no description,60.2 -31,I7N3G6G,31,1,no description,60.2 -32,I7N3G6G,32,1,no description,60.2 -33,I7N3G6G,33,1,no description,60.2 -34,I7N3G6G,34,1,no description,60.2 -35,I7N3G6G,35,1,no description,60.2 -36,I7N3G6G,36,1,no description,60.2 -37,I7N3G6G,37,1,no description,60.2 -38,I7N3G6G,38,1,no description,60.2 -39,I7N3G6G,39,1,no description,60.2 -40,I7N3G6G,40,1,no description,60.2 -41,I7N3G6G,41,1,no description,60.2 -42,I7N3G6G,42,1,no description,60.2 -43,I7N3G6G,43,1,no description,60.2 -44,I7N3G6G,44,1,no description,60.2 -45,I7N3G6G,45,1,no description,60.2 -46,I7N3G6G,46,1,no description,60.2 -47,I7N3G6G,47,1,no description,60.2 -48,I7N3G6G,48,1,no description,60.2 -49,I7N3G6G,49,1,no description,60.2 -50,I7N3G6G,50,1,no description,60.2 -51,I7N3G6G,51,1,no description,60.2 -52,I7N3G6G,52,1,no description,60.2 -53,I7N3G6G,53,1,no description,60.2 -54,I7N3G6G,54,1,no description,60.2 -55,I7N3G6G,55,1,no description,60.2 -56,I7N3G6G,56,1,no description,60.2 -57,I7N3G6G,57,1,no description,60.2 -58,I7N3G6G,58,1,no description,60.2 -59,I7N3G6G,59,1,no description,60.2 -60,I7N3G6G,60,1,no description,60.2 -61,I7N3G6G,61,1,no description,60.2 -62,I7N3G6G,62,1,no description,60.2 -63,I7N3G6G,63,1,no description,60.2 -64,I7N3G6G,64,1,no description,60.2 -65,I7N3G6G,65,1,no description,60.2 -66,I7N3G6G,66,1,no description,60.2 -67,I7N3G6G,67,1,no description,60.2 -68,I7N3G6G,68,1,no description,60.2 -69,I7N3G6G,69,1,no description,60.2 -70,I7N3G6G,70,1,no description,60.2 -71,I7N3G6G,71,1,no description,60.2 -72,I7N3G6G,72,1,no description,60.2 -73,I7N3G6G,73,1,no description,60.2 -74,I7N3G6G,74,1,no description,60.2 -75,I7N3G6G,75,1,no description,60.2 -76,I7N3G6G,76,1,no description,60.2 -77,I7N3G6G,77,1,no description,60.2 -78,I7N3G6G,78,1,no description,60.2 -79,I7N3G6G,79,1,no description,60.2 -80,I7N3G6G,80,1,no description,60.2 -81,I7N3G6G,81,1,no description,60.2 -82,I7N3G6G,82,1,no description,60.2 -83,I7N3G6G,83,1,no description,60.2 -84,I7N3G6G,84,1,no description,60.2 -85,I7N3G6G,85,1,no description,60.2 -86,I7N3G6G,86,1,no description,60.2 -87,I7N3G6G,87,1,no description,60.2 -88,I7N3G6G,88,1,no description,60.2 -89,I7N3G6G,89,1,no description,60.2 -90,I7N3G6G,90,1,no description,60.2 -91,I7N3G6G,91,1,no description,60.2 -92,I7N3G6G,92,1,no description,60.2 -93,I7N3G6G,93,1,no description,60.2 -94,I7N3G6G,94,1,no description,60.2 -95,I7N3G6G,95,1,no description,60.2 -96,I7N3G6G,96,1,no description,60.2 -97,I7N3G6G,97,1,no description,60.2 -98,I7N3G6G,98,1,no description,60.2 -99,I7N3G6G,99,1,no description,60.2 -100,I7N3G6G,100,1,no description,60.2 -101,I7N3G6G,101,1,no description,60.2 -102,I7N3G6G,102,1,no description,60.2 -103,I7N3G6G,103,1,no description,60.2 -104,I7N3G6G,104,1,no description,60.2 -105,I7N3G6G,105,1,no description,60.2 -106,I7N3G6G,106,1,no description,60.2 -107,I7N3G6G,107,1,no description,60.2 -108,I7N3G6G,108,1,no description,60.2 -109,I7N3G6G,109,1,no description,60.2 -110,I7N3G6G,110,1,no description,60.2 -111,I7N3G6G,111,1,no description,60.2 -112,I7N3G6G,112,1,no description,60.2 -113,I7N3G6G,113,1,no description,60.2 -114,I7N3G6G,114,1,no description,60.2 -115,I7N3G6G,115,1,no description,60.2 -116,I7N3G6G,116,1,no description,60.2 -117,I7N3G6G,117,1,no description,60.2 -118,I7N3G6G,118,1,no description,60.2 -119,I7N3G6G,119,1,no description,60.2 -120,I7N3G6G,120,1,no description,60.2 -121,I7N3G6G,121,1,no description,60.2 -122,I7N3G6G,122,1,no description,60.2 -123,I7N3G6G,123,1,no description,60.2 -124,I7N3G6G,124,1,no description,60.2 -125,I7N3G6G,125,1,no description,60.2 -126,I7N3G6G,126,1,no description,60.2 -127,I7N3G6G,127,1,no description,60.2 -128,I7N3G6G,128,1,no description,60.2 -129,I7N3G6G,129,1,no description,60.2 -130,I7N3G6G,130,1,no description,60.2 -131,I7N3G6G,131,1,no description,60.2 -132,I7N3G6G,132,1,no description,60.2 -133,I7N3G6G,133,1,no description,60.2 -134,I7N3G6G,134,1,no description,60.2 -135,I7N3G6G,135,1,no description,60.2 -136,I7N3G6G,136,1,no description,60.2 -137,I7N3G6G,137,1,no description,60.2 -138,I7N3G6G,138,1,no description,60.2 -139,I7N3G6G,139,1,no description,60.2 -140,I7N3G6G,140,1,no description,60.2 -141,I7N3G6G,141,1,no description,60.2 -142,I7N3G6G,142,1,no description,60.2 -143,I7N3G6G,143,1,no description,60.2 -144,I7N3G6G,144,1,no description,60.2 -145,I7N3G6G,145,1,no description,60.2 -146,I7N3G6G,146,1,no description,60.2 -147,I7N3G6G,147,1,no description,60.2 -148,I7N3G6G,148,1,no description,60.2 -149,I7N3G6G,149,1,no description,60.2 -150,I7N3G6G,150,1,no description,60.2 -151,I7N3G6G,151,1,no description,60.2 -152,I7N3G6G,152,1,no description,60.2 -153,I7N3G6G,153,1,no description,60.2 -154,I7N3G6G,154,1,no description,60.2 -155,I7N3G6G,155,1,no description,60.2 -156,I7N3G6G,156,1,no description,60.2 -157,I7N3G6G,157,1,no description,60.2 -158,I7N3G6G,158,1,no description,60.2 -159,I7N3G6G,159,1,no description,60.2 -160,I7N3G6G,160,1,no description,60.2 -161,I7N3G6G,161,1,no description,60.2 -162,I7N3G6G,162,1,no description,60.2 -163,I7N3G6G,163,1,no description,60.2 -164,I7N3G6G,164,1,no description,60.2 -165,I7N3G6G,165,1,no description,60.2 -166,I7N3G6G,166,1,no description,60.2 -167,I7N3G6G,167,1,no description,60.2 -168,I7N3G6G,168,1,no description,60.2 -169,I7N3G6G,169,1,no description,60.2 -170,I7N3G6G,170,1,no description,60.2 -171,I7N3G6G,171,1,no description,60.2 -172,I7N3G6G,172,1,no description,60.2 -173,I7N3G6G,173,1,no description,60.2 -174,I7N3G6G,174,1,no description,60.2 -175,I7N3G6G,175,1,no description,60.2 -176,I7N3G6G,176,1,no description,60.2 -177,I7N3G6G,177,1,no description,60.2 -178,I7N3G6G,178,1,no description,60.2 -179,I7N3G6G,179,1,no description,60.2 -180,I7N3G6G,180,1,no description,60.2 -181,I7N3G6G,181,1,no description,60.2 -182,I7N3G6G,182,1,no description,60.2 -183,I7N3G6G,183,1,no description,60.2 -184,I7N3G6G,184,1,no description,60.2 -185,I7N3G6G,185,1,no description,60.2 -186,I7N3G6G,186,1,no description,60.2 -187,I7N3G6G,187,1,no description,60.2 -188,I7N3G6G,188,1,no description,60.2 -189,I7N3G6G,189,1,no description,60.2 -190,I7N3G6G,190,1,no description,60.2 -191,I7N3G6G,191,1,no description,60.2 -192,I7N3G6G,192,1,no description,60.2 -193,I7N3G6G,193,1,no description,60.2 -194,I7N3G6G,194,1,no description,60.2 -195,I7N3G6G,195,1,no description,60.2 -196,I7N3G6G,196,1,no description,60.2 -197,I7N3G6G,197,1,no description,60.2 -198,I7N3G6G,198,1,no description,60.2 -199,I7N3G6G,199,1,no description,60.2 -200,I7N3G6G,200,1,no description,60.2 -201,I7N3G6G,201,1,no description,60.2 -202,I7N3G6G,202,1,no description,60.2 -203,I7N3G6G,203,1,no description,60.2 -204,I7N3G6G,204,1,no description,60.2 -205,I7N3G6G,205,1,no description,60.2 -206,I7N3G6G,206,1,no description,60.2 -207,I7N3G6G,207,1,no description,60.2 -208,I7N3G6G,208,1,no description,60.2 -209,I7N3G6G,209,1,no description,60.2 -210,I7N3G6G,210,1,no description,60.2 -211,I7N3G6G,211,1,no description,60.2 -212,I7N3G6G,212,1,no description,60.2 -213,I7N3G6G,213,1,no description,60.2 -214,I7N3G6G,214,1,no description,60.2 -215,I7N3G6G,215,1,no description,60.2 -216,I7N3G6G,216,1,no description,60.2 -217,I7N3G6G,217,1,no description,60.2 -218,I7N3G6G,218,1,no description,60.2 -219,I7N3G6G,219,1,no description,60.2 -220,I7N3G6G,220,1,no description,60.2 -221,I7N3G6G,221,1,no description,60.2 -222,I7N3G6G,222,1,no description,60.2 -223,I7N3G6G,223,1,no description,60.2 -224,I7N3G6G,224,1,no description,60.2 -225,I7N3G6G,225,1,no description,60.2 -226,I7N3G6G,226,1,no description,60.2 -227,I7N3G6G,227,1,no description,60.2 -228,I7N3G6G,228,1,no description,60.2 -229,I7N3G6G,229,1,no description,60.2 -230,I7N3G6G,230,1,no description,60.2 -231,I7N3G6G,231,1,no description,60.2 -232,I7N3G6G,232,1,no description,60.2 -233,I7N3G6G,233,1,no description,60.2 -234,I7N3G6G,234,1,no description,60.2 -235,I7N3G6G,235,1,no description,60.2 -236,I7N3G6G,236,1,no description,60.2 -237,I7N3G6G,237,1,no description,60.2 -238,I7N3G6G,238,1,no description,60.2 -239,I7N3G6G,239,1,no description,60.2 -240,I7N3G6G,240,1,no description,60.2 -241,I7N3G6G,241,1,no description,60.2 -242,I7N3G6G,242,1,no description,60.2 -243,I7N3G6G,243,1,no description,60.2 -244,I7N3G6G,244,1,no description,60.2 -245,I7N3G6G,245,1,no description,60.2 -246,I7N3G6G,246,1,no description,60.2 -247,I7N3G6G,247,1,no description,60.2 -248,I7N3G6G,248,1,no description,60.2 -249,I7N3G6G,249,1,no description,60.2 -250,I7N3G6G,250,1,no description,60.2 -251,I7N3G6G,251,1,no description,60.2 -252,I7N3G6G,252,1,no description,60.2 -253,I7N3G6G,253,1,no description,60.2 -254,I7N3G6G,254,1,no description,60.2 -255,I7N3G6G,255,1,no description,60.2 -256,I7N3G6G,256,1,no description,60.2 -257,I7N3G6G,257,1,no description,60.2 -258,I7N3G6G,258,1,no description,60.2 -259,I7N3G6G,259,1,no description,60.2 -260,I7N3G6G,260,1,no description,60.2 -261,I7N3G6G,261,1,no description,60.2 -262,I7N3G6G,262,1,no description,60.2 -263,I7N3G6G,263,1,no description,60.2 -264,I7N3G6G,264,1,no description,60.2 -265,I7N3G6G,265,1,no description,60.2 -266,I7N3G6G,266,1,no description,60.2 -267,I7N3G6G,267,1,no description,60.2 -268,I7N3G6G,268,1,no description,60.2 -269,I7N3G6G,269,1,no description,60.2 -270,I7N3G6G,270,1,no description,60.2 -271,I7N3G6G,271,1,no description,60.2 -272,I7N3G6G,272,1,no description,60.2 -273,I7N3G6G,273,1,no description,60.2 -274,I7N3G6G,274,1,no description,60.2 -275,I7N3G6G,275,1,no description,60.2 -276,I7N3G6G,276,1,no description,60.2 -277,I7N3G6G,277,1,no description,60.2 -278,I7N3G6G,278,1,no description,60.2 -279,I7N3G6G,279,1,no description,60.2 -280,I7N3G6G,280,1,no description,60.2 -281,I7N3G6G,281,1,no description,60.2 -282,I7N3G6G,282,1,no description,60.2 -283,I7N3G6G,283,1,no description,60.2 -284,I7N3G6G,284,1,no description,60.2 -285,I7N3G6G,285,1,no description,60.2 -286,I7N3G6G,286,1,no description,60.2 -287,I7N3G6G,287,1,no description,60.2 -288,I7N3G6G,288,1,no description,60.2 -289,I7N3G6G,289,1,no description,60.2 -290,I7N3G6G,290,1,no description,60.2 -291,I7N3G6G,291,1,no description,60.2 -292,I7N3G6G,292,1,no description,60.2 -293,I7N3G6G,293,1,no description,60.2 -294,I7N3G6G,294,1,no description,60.2 -295,I7N3G6G,295,1,no description,60.2 -296,I7N3G6G,296,1,no description,60.2 -297,I7N3G6G,297,1,no description,60.2 -298,I7N3G6G,298,1,no description,60.2 -299,I7N3G6G,299,1,no description,60.2 -300,I7N3G6G,300,1,no description,60.2 -301,I7N3G6G,301,1,no description,60.2 -302,I7N3G6G,302,1,no description,60.2 -303,I7N3G6G,303,1,no description,60.2 -304,I7N3G6G,304,1,no description,60.2 -305,I7N3G6G,305,1,no description,60.2 -306,I7N3G6G,306,1,no description,60.2 -307,I7N3G6G,307,1,no description,60.2 -308,I7N3G6G,308,1,no description,60.2 -309,I7N3G6G,309,1,no description,60.2 -310,I7N3G6G,310,1,no description,60.2 -311,I7N3G6G,311,1,no description,60.2 -312,I7N3G6G,312,1,no description,60.2 -313,I7N3G6G,313,1,no description,60.2 -314,I7N3G6G,314,1,no description,60.2 -315,I7N3G6G,315,1,no description,60.2 -316,I7N3G6G,316,1,no description,60.2 -317,I7N3G6G,317,1,no description,60.2 -318,I7N3G6G,318,1,no description,60.2 -319,I7N3G6G,319,1,no description,60.2 -320,I7N3G6G,320,1,no description,60.2 -321,I7N3G6G,321,1,no description,60.2 -322,I7N3G6G,322,1,no description,60.2 -323,I7N3G6G,323,1,no description,60.2 -324,I7N3G6G,324,1,no description,60.2 -325,I7N3G6G,325,1,no description,60.2 -326,I7N3G6G,326,1,no description,60.2 -327,I7N3G6G,327,1,no description,60.2 -328,I7N3G6G,328,1,no description,60.2 -329,I7N3G6G,329,1,no description,60.2 -330,I7N3G6G,330,1,no description,60.2 -331,I7N3G6G,331,1,no description,60.2 -332,I7N3G6G,332,1,no description,60.2 -333,I7N3G6G,333,1,no description,60.2 -334,I7N3G6G,334,1,no description,60.2 -335,I7N3G6G,335,1,no description,60.2 -336,I7N3G6G,336,1,no description,60.2 -337,I7N3G6G,337,1,no description,60.2 -338,I7N3G6G,338,1,no description,60.2 -339,I7N3G6G,339,1,no description,60.2 -340,I7N3G6G,340,1,no description,60.2 -341,I7N3G6G,341,1,no description,60.2 -342,I7N3G6G,342,1,no description,60.2 -343,I7N3G6G,343,1,no description,60.2 -344,I7N3G6G,344,1,no description,60.2 -345,I7N3G6G,345,1,no description,60.2 -346,I7N3G6G,346,1,no description,60.2 -347,I7N3G6G,347,1,no description,60.2 -348,I7N3G6G,348,1,no description,60.2 -349,I7N3G6G,349,1,no description,60.2 -350,I7N3G6G,350,1,no description,60.2 -351,I7N3G6G,351,1,no description,60.2 -352,I7N3G6G,352,1,no description,60.2 -353,I7N3G6G,353,1,no description,60.2 -354,I7N3G6G,354,1,no description,60.2 -355,I7N3G6G,355,1,no description,60.2 -356,I7N3G6G,356,1,no description,60.2 -357,I7N3G6G,357,1,no description,60.2 -358,I7N3G6G,358,1,no description,60.2 -359,I7N3G6G,359,1,no description,60.2 -360,I7N3G6G,360,1,no description,60.2 -361,I7N3G6G,361,1,no description,60.2 -362,I7N3G6G,362,1,no description,60.2 -363,I7N3G6G,363,1,no description,60.2 -364,I7N3G6G,364,1,no description,60.2 -365,I7N3G6G,365,1,no description,60.2 -366,I7N3G6G,366,1,no description,60.2 -367,I7N3G6G,367,1,no description,60.2 -368,I7N3G6G,368,1,no description,60.2 -369,I7N3G6G,369,1,no description,60.2 -370,I7N3G6G,370,1,no description,60.2 -371,I7N3G6G,371,1,no description,60.2 -372,I7N3G6G,372,1,no description,60.2 -373,I7N3G6G,373,1,no description,60.2 -374,I7N3G6G,374,1,no description,60.2 -375,I7N3G6G,375,1,no description,60.2 -376,I7N3G6G,376,1,no description,60.2 -377,I7N3G6G,377,1,no description,60.2 -378,I7N3G6G,378,1,no description,60.2 -379,I7N3G6G,379,1,no description,60.2 -380,I7N3G6G,380,1,no description,60.2 -381,I7N3G6G,381,1,no description,60.2 -382,I7N3G6G,382,1,no description,60.2 -383,I7N3G6G,383,1,no description,60.2 -384,I7N3G6G,384,1,no description,60.2 -385,I7N3G6G,385,1,no description,60.2 -386,I7N3G6G,386,1,no description,60.2 -387,I7N3G6G,387,1,no description,60.2 -388,I7N3G6G,388,1,no description,60.2 -389,I7N3G6G,389,1,no description,60.2 -390,I7N3G6G,390,1,no description,60.2 -391,I7N3G6G,391,1,no description,60.2 -392,I7N3G6G,392,1,no description,60.2 -393,I7N3G6G,393,1,no description,60.2 -394,I7N3G6G,394,1,no description,60.2 -395,I7N3G6G,395,1,no description,60.2 -396,I7N3G6G,396,1,no description,60.2 -397,I7N3G6G,397,1,no description,60.2 -398,I7N3G6G,398,1,no description,60.2 -399,I7N3G6G,399,1,no description,60.2 -400,I7N3G6G,400,1,no description,60.2 -401,I7N3G6G,401,1,no description,60.2 -402,I7N3G6G,402,1,no description,60.2 -403,I7N3G6G,403,1,no description,60.2 -404,I7N3G6G,404,1,no description,60.2 -405,I7N3G6G,405,1,no description,60.2 -406,I7N3G6G,406,1,no description,60.2 -407,I7N3G6G,407,1,no description,60.2 -408,I7N3G6G,408,1,no description,60.2 -409,I7N3G6G,409,1,no description,60.2 -410,I7N3G6G,410,1,no description,60.2 -411,I7N3G6G,411,1,no description,60.2 -412,I7N3G6G,412,1,no description,60.2 -413,I7N3G6G,413,1,no description,60.2 -414,I7N3G6G,414,1,no description,60.2 -415,I7N3G6G,415,1,no description,60.2 -416,I7N3G6G,416,1,no description,60.2 -417,I7N3G6G,417,1,no description,60.2 -418,I7N3G6G,418,1,no description,60.2 -419,I7N3G6G,419,1,no description,60.2 -420,I7N3G6G,420,1,no description,60.2 -421,I7N3G6G,421,1,no description,60.2 -422,I7N3G6G,422,1,no description,60.2 -423,I7N3G6G,423,1,no description,60.2 -424,I7N3G6G,424,1,no description,60.2 -425,I7N3G6G,425,1,no description,60.2 -426,I7N3G6G,426,1,no description,60.2 -427,I7N3G6G,427,1,no description,60.2 -428,I7N3G6G,428,1,no description,60.2 -429,I7N3G6G,429,1,no description,60.2 -430,I7N3G6G,430,1,no description,60.2 -431,I7N3G6G,431,1,no description,60.2 -432,I7N3G6G,432,1,no description,60.2 -433,I7N3G6G,433,1,no description,60.2 -434,I7N3G6G,434,1,no description,60.2 -435,I7N3G6G,435,1,no description,60.2 -436,I7N3G6G,436,1,no description,60.2 -437,I7N3G6G,437,1,no description,60.2 -438,I7N3G6G,438,1,no description,60.2 -439,I7N3G6G,439,1,no description,60.2 -440,I7N3G6G,440,1,no description,60.2 -441,I7N3G6G,441,1,no description,60.2 -442,I7N3G6G,442,1,no description,60.2 -443,I7N3G6G,443,1,no description,60.2 -444,I7N3G6G,444,1,no description,60.2 -445,I7N3G6G,445,1,no description,60.2 -446,I7N3G6G,446,1,no description,60.2 -447,I7N3G6G,447,1,no description,60.2 -448,I7N3G6G,448,1,no description,60.2 -449,I7N3G6G,449,1,no description,60.2 -450,I7N3G6G,450,1,no description,60.2 -451,I7N3G6G,451,1,no description,60.2 -452,I7N3G6G,452,1,no description,60.2 -453,I7N3G6G,453,1,no description,60.2 -454,I7N3G6G,454,1,no description,60.2 -455,I7N3G6G,455,1,no description,60.2 -456,I7N3G6G,456,1,no description,60.2 -457,I7N3G6G,457,1,no description,60.2 -458,I7N3G6G,458,1,no description,60.2 -459,I7N3G6G,459,1,no description,60.2 -460,I7N3G6G,460,1,no description,60.2 -461,I7N3G6G,461,1,no description,60.2 -462,I7N3G6G,462,1,no description,60.2 -463,I7N3G6G,463,1,no description,60.2 -464,I7N3G6G,464,1,no description,60.2 -465,I7N3G6G,465,1,no description,60.2 -466,I7N3G6G,466,1,no description,60.2 -467,I7N3G6G,467,1,no description,60.2 -468,I7N3G6G,468,1,no description,60.2 -469,I7N3G6G,469,1,no description,60.2 -470,I7N3G6G,470,1,no description,60.2 -471,I7N3G6G,471,1,no description,60.2 -472,I7N3G6G,472,1,no description,60.2 -473,I7N3G6G,473,1,no description,60.2 -474,I7N3G6G,474,1,no description,60.2 -475,I7N3G6G,475,1,no description,60.2 -476,I7N3G6G,476,1,no description,60.2 -477,I7N3G6G,477,1,no description,60.2 -478,I7N3G6G,478,1,no description,60.2 -479,I7N3G6G,479,1,no description,60.2 -480,I7N3G6G,480,1,no description,60.2 -481,I7N3G6G,481,1,no description,60.2 -482,I7N3G6G,482,1,no description,60.2 -483,I7N3G6G,483,1,no description,60.2 -484,I7N3G6G,484,1,no description,60.2 -485,I7N3G6G,485,1,no description,60.2 -486,I7N3G6G,486,1,no description,60.2 -487,I7N3G6G,487,1,no description,60.2 -488,I7N3G6G,488,1,no description,60.2 -489,I7N3G6G,489,1,no description,60.2 -490,I7N3G6G,490,1,no description,60.2 -491,I7N3G6G,491,1,no description,60.2 -492,I7N3G6G,492,1,no description,60.2 -493,I7N3G6G,493,1,no description,60.2 -494,I7N3G6G,494,1,no description,60.2 -495,I7N3G6G,495,1,no description,60.2 -496,I7N3G6G,496,1,no description,60.2 -497,I7N3G6G,497,1,no description,60.2 -498,I7N3G6G,498,1,no description,60.2 -499,I7N3G6G,499,1,no description,60.2 -500,I7N3G6G,500,1,no description,60.2 -501,I7N3G6G,501,1,no description,60.2 -502,I7N3G6G,502,1,no description,60.2 -503,I7N3G6G,503,1,no description,60.2 -504,I7N3G6G,504,1,no description,60.2 -505,I7N3G6G,505,1,no description,60.2 -506,I7N3G6G,506,1,no description,60.2 -507,I7N3G6G,507,1,no description,60.2 -508,I7N3G6G,508,1,no description,60.2 -509,I7N3G6G,509,1,no description,60.2 -510,I7N3G6G,510,1,no description,60.2 -511,I7N3G6G,511,1,no description,60.2 -512,I7N3G6G,512,1,no description,60.2 -513,I7N3G6G,513,1,no description,60.2 -514,I7N3G6G,514,1,no description,60.2 -515,I7N3G6G,515,1,no description,60.2 -516,I7N3G6G,516,1,no description,60.2 -517,I7N3G6G,517,1,no description,60.2 -518,I7N3G6G,518,1,no description,60.2 -519,I7N3G6G,519,1,no description,60.2 -520,I7N3G6G,520,1,no description,60.2 -521,I7N3G6G,521,1,no description,60.2 -522,I7N3G6G,522,1,no description,60.2 -523,I7N3G6G,523,1,no description,60.2 -524,I7N3G6G,524,1,no description,60.2 -525,I7N3G6G,525,1,no description,60.2 -526,I7N3G6G,526,1,no description,60.2 -527,I7N3G6G,527,1,no description,60.2 -528,I7N3G6G,528,1,no description,60.2 -529,I7N3G6G,529,1,no description,60.2 -530,I7N3G6G,530,1,no description,60.2 -531,I7N3G6G,531,1,no description,60.2 -532,I7N3G6G,532,1,no description,60.2 -533,I7N3G6G,533,1,no description,60.2 -534,I7N3G6G,534,1,no description,60.2 -535,I7N3G6G,535,1,no description,60.2 -536,I7N3G6G,536,1,no description,60.2 -537,I7N3G6G,537,1,no description,60.2 -538,I7N3G6G,538,1,no description,60.2 -539,I7N3G6G,539,1,no description,60.2 -540,I7N3G6G,540,1,no description,60.2 -541,I7N3G6G,541,1,no description,60.2 -542,I7N3G6G,542,1,no description,60.2 -543,I7N3G6G,543,1,no description,60.2 -544,I7N3G6G,544,1,no description,60.2 -545,I7N3G6G,545,1,no description,60.2 -546,I7N3G6G,546,1,no description,60.2 -547,I7N3G6G,547,1,no description,60.2 -548,I7N3G6G,548,1,no description,60.2 -549,I7N3G6G,549,1,no description,60.2 -550,I7N3G6G,550,1,no description,60.2 -551,I7N3G6G,551,1,no description,60.2 -552,I7N3G6G,552,1,no description,60.2 -553,I7N3G6G,553,1,no description,60.2 -554,I7N3G6G,554,1,no description,60.2 -555,I7N3G6G,555,1,no description,60.2 -556,I7N3G6G,556,1,no description,60.2 -557,I7N3G6G,557,1,no description,60.2 -558,I7N3G6G,558,1,no description,60.2 -559,I7N3G6G,559,1,no description,60.2 -560,I7N3G6G,560,1,no description,60.2 -561,I7N3G6G,561,1,no description,60.2 -562,I7N3G6G,562,1,no description,60.2 -563,I7N3G6G,563,1,no description,60.2 -564,I7N3G6G,564,1,no description,60.2 -565,I7N3G6G,565,1,no description,60.2 -566,I7N3G6G,566,1,no description,60.2 -567,I7N3G6G,567,1,no description,60.2 -568,I7N3G6G,568,1,no description,60.2 -569,I7N3G6G,569,1,no description,60.2 -570,I7N3G6G,570,1,no description,60.2 -571,I7N3G6G,571,1,no description,60.2 -572,I7N3G6G,572,1,no description,60.2 -573,I7N3G6G,573,1,no description,60.2 -574,I7N3G6G,574,1,no description,60.2 -575,I7N3G6G,575,1,no description,60.2 -576,I7N3G6G,576,1,no description,60.2 -577,I7N3G6G,577,1,no description,60.2 -578,I7N3G6G,578,1,no description,60.2 -579,I7N3G6G,579,1,no description,60.2 -580,I7N3G6G,580,1,no description,60.2 -581,I7N3G6G,581,1,no description,60.2 -582,I7N3G6G,582,1,no description,60.2 -583,I7N3G6G,583,1,no description,60.2 -584,I7N3G6G,584,1,no description,60.2 -585,I7N3G6G,585,1,no description,60.2 -586,I7N3G6G,586,1,no description,60.2 -587,I7N3G6G,587,1,no description,60.2 -588,I7N3G6G,588,1,no description,60.2 -589,I7N3G6G,589,1,no description,60.2 -590,I7N3G6G,590,1,no description,60.2 -591,I7N3G6G,591,1,no description,60.2 -592,I7N3G6G,592,1,no description,60.2 -593,I7N3G6G,593,1,no description,60.2 -594,I7N3G6G,594,1,no description,60.2 -595,I7N3G6G,595,1,no description,60.2 -596,I7N3G6G,596,1,no description,60.2 -597,I7N3G6G,597,1,no description,60.2 -598,I7N3G6G,598,1,no description,60.2 -599,I7N3G6G,599,1,no description,60.2 -600,I7N3G6G,600,1,no description,60.2 -601,I7N3G6G,601,1,no description,60.2 -602,I7N3G6G,602,1,no description,60.2 -603,I7N3G6G,603,1,no description,60.2 -604,I7N3G6G,604,1,no description,60.2 -605,I7N3G6G,605,1,no description,60.2 -606,I7N3G6G,606,1,no description,60.2 -607,I7N3G6G,607,1,no description,60.2 -608,I7N3G6G,608,1,no description,60.2 -609,I7N3G6G,609,1,no description,60.2 -610,I7N3G6G,610,1,no description,60.2 -611,I7N3G6G,611,1,no description,60.2 -612,I7N3G6G,612,1,no description,60.2 -613,I7N3G6G,613,1,no description,60.2 -614,I7N3G6G,614,1,no description,60.2 -615,I7N3G6G,615,1,no description,60.2 -616,I7N3G6G,616,1,no description,60.2 -617,I7N3G6G,617,1,no description,60.2 -618,I7N3G6G,618,1,no description,60.2 -619,I7N3G6G,619,1,no description,60.2 -620,I7N3G6G,620,1,no description,60.2 -621,I7N3G6G,621,1,no description,60.2 -622,I7N3G6G,622,1,no description,60.2 -623,I7N3G6G,623,1,no description,60.2 -624,I7N3G6G,624,1,no description,60.2 -625,I7N3G6G,625,1,no description,60.2 -626,I7N3G6G,626,1,no description,60.2 -627,I7N3G6G,627,1,no description,60.2 -628,I7N3G6G,628,1,no description,60.2 -629,I7N3G6G,629,1,no description,60.2 -630,I7N3G6G,630,1,no description,60.2 -631,I7N3G6G,631,1,no description,60.2 -632,I7N3G6G,632,1,no description,60.2 -633,I7N3G6G,633,1,no description,60.2 -634,I7N3G6G,634,1,no description,60.2 -635,I7N3G6G,635,1,no description,60.2 -636,I7N3G6G,636,1,no description,60.2 -637,I7N3G6G,637,1,no description,60.2 -638,I7N3G6G,638,1,no description,60.2 -639,I7N3G6G,639,1,no description,60.2 -640,I7N3G6G,640,1,no description,60.2 -641,I7N3G6G,641,1,no description,60.2 -642,I7N3G6G,642,1,no description,60.2 -643,I7N3G6G,643,1,no description,60.2 -644,I7N3G6G,644,1,no description,60.2 -645,I7N3G6G,645,1,no description,60.2 -646,I7N3G6G,646,1,no description,60.2 -647,I7N3G6G,647,1,no description,60.2 -648,I7N3G6G,648,1,no description,60.2 -649,I7N3G6G,649,1,no description,60.2 -650,I7N3G6G,650,1,no description,60.2 -651,I7N3G6G,651,1,no description,60.2 -652,I7N3G6G,652,1,no description,60.2 -653,I7N3G6G,653,1,no description,60.2 -654,I7N3G6G,654,1,no description,60.2 -655,I7N3G6G,655,1,no description,60.2 -656,I7N3G6G,656,1,no description,60.2 -657,I7N3G6G,657,1,no description,60.2 -658,I7N3G6G,658,1,no description,60.2 -659,I7N3G6G,659,1,no description,60.2 -660,I7N3G6G,660,1,no description,60.2 -661,I7N3G6G,661,1,no description,60.2 -662,I7N3G6G,662,1,no description,60.2 -663,I7N3G6G,663,1,no description,60.2 -664,I7N3G6G,664,1,no description,60.2 -665,I7N3G6G,665,1,no description,60.2 -666,I7N3G6G,666,1,no description,60.2 -667,I7N3G6G,667,1,no description,60.2 -668,I7N3G6G,668,1,no description,60.2 -669,I7N3G6G,669,1,no description,60.2 -670,I7N3G6G,670,1,no description,60.2 -671,I7N3G6G,671,1,no description,60.2 -672,I7N3G6G,672,1,no description,60.2 -673,I7N3G6G,673,1,no description,60.2 -674,I7N3G6G,674,1,no description,60.2 -675,I7N3G6G,675,1,no description,60.2 -676,I7N3G6G,676,1,no description,60.2 -677,I7N3G6G,677,1,no description,60.2 -678,I7N3G6G,678,1,no description,60.2 -679,I7N3G6G,679,1,no description,60.2 -680,I7N3G6G,680,1,no description,60.2 -681,I7N3G6G,681,1,no description,60.2 -682,I7N3G6G,682,1,no description,60.2 -683,I7N3G6G,683,1,no description,60.2 -684,I7N3G6G,684,1,no description,60.2 -685,I7N3G6G,685,1,no description,60.2 -686,I7N3G6G,686,1,no description,60.2 -687,I7N3G6G,687,1,no description,60.2 -688,I7N3G6G,688,1,no description,60.2 -689,I7N3G6G,689,1,no description,60.2 -690,I7N3G6G,690,1,no description,60.2 -691,I7N3G6G,691,1,no description,60.2 -692,I7N3G6G,692,1,no description,60.2 -693,I7N3G6G,693,1,no description,60.2 -694,I7N3G6G,694,1,no description,60.2 -695,I7N3G6G,695,1,no description,60.2 -696,I7N3G6G,696,1,no description,60.2 -697,I7N3G6G,697,1,no description,60.2 -698,I7N3G6G,698,1,no description,60.2 -699,I7N3G6G,699,1,no description,60.2 -700,I7N3G6G,700,1,no description,60.2 -701,I7N3G6G,701,1,no description,60.2 -702,I7N3G6G,702,1,no description,60.2 -703,I7N3G6G,703,1,no description,60.2 -704,I7N3G6G,704,1,no description,60.2 -705,I7N3G6G,705,1,no description,60.2 -706,I7N3G6G,706,1,no description,60.2 -707,I7N3G6G,707,1,no description,60.2 -708,I7N3G6G,708,1,no description,60.2 -709,I7N3G6G,709,1,no description,60.2 -710,I7N3G6G,710,1,no description,60.2 -711,I7N3G6G,711,1,no description,60.2 -712,I7N3G6G,712,1,no description,60.2 -713,I7N3G6G,713,1,no description,60.2 -714,I7N3G6G,714,1,no description,60.2 -715,I7N3G6G,715,1,no description,60.2 -716,I7N3G6G,716,1,no description,60.2 -717,I7N3G6G,717,1,no description,60.2 -718,I7N3G6G,718,1,no description,60.2 -719,I7N3G6G,719,1,no description,60.2 -720,I7N3G6G,720,1,no description,60.2 -721,I7N3G6G,721,1,no description,60.2 -722,I7N3G6G,722,1,no description,60.2 -723,I7N3G6G,723,1,no description,60.2 -724,I7N3G6G,724,1,no description,60.2 -725,I7N3G6G,725,1,no description,60.2 -726,I7N3G6G,726,1,no description,60.2 -727,I7N3G6G,727,1,no description,60.2 -728,I7N3G6G,728,1,no description,60.2 -729,I7N3G6G,729,1,no description,60.2 -730,I7N3G6G,730,1,no description,60.2 -731,I7N3G6G,731,1,no description,60.2 -732,I7N3G6G,732,1,no description,60.2 -733,I7N3G6G,733,1,no description,60.2 -734,I7N3G6G,734,1,no description,60.2 -735,I7N3G6G,735,1,no description,60.2 -736,I7N3G6G,736,1,no description,60.2 -737,I7N3G6G,737,1,no description,60.2 -738,I7N3G6G,738,1,no description,60.2 -739,I7N3G6G,739,1,no description,60.2 -740,I7N3G6G,740,1,no description,60.2 -741,I7N3G6G,741,1,no description,60.2 -742,I7N3G6G,742,1,no description,60.2 -743,I7N3G6G,743,1,no description,60.2 -744,I7N3G6G,744,1,no description,60.2 -745,I7N3G6G,745,1,no description,60.2 -746,I7N3G6G,746,1,no description,60.2 -747,I7N3G6G,747,1,no description,60.2 -748,I7N3G6G,748,1,no description,60.2 -749,I7N3G6G,749,1,no description,60.2 -750,I7N3G6G,750,1,no description,60.2 -751,I7N3G6G,751,1,no description,60.2 -752,I7N3G6G,752,1,no description,60.2 -753,I7N3G6G,753,1,no description,60.2 -754,I7N3G6G,754,1,no description,60.2 -755,I7N3G6G,755,1,no description,60.2 -756,I7N3G6G,756,1,no description,60.2 -757,I7N3G6G,757,1,no description,60.2 -758,I7N3G6G,758,1,no description,60.2 -759,I7N3G6G,759,1,no description,60.2 -760,I7N3G6G,760,1,no description,60.2 -761,I7N3G6G,761,1,no description,60.2 -762,I7N3G6G,762,1,no description,60.2 -763,I7N3G6G,763,1,no description,60.2 -764,I7N3G6G,764,1,no description,60.2 -765,I7N3G6G,765,1,no description,60.2 -766,I7N3G6G,766,1,no description,60.2 -767,I7N3G6G,767,1,no description,60.2 -768,I7N3G6G,768,1,no description,60.2 -769,I7N3G6G,769,1,no description,60.2 -770,I7N3G6G,770,1,no description,60.2 -771,I7N3G6G,771,1,no description,60.2 -772,I7N3G6G,772,1,no description,60.2 -773,I7N3G6G,773,1,no description,60.2 -774,I7N3G6G,774,1,no description,60.2 -775,I7N3G6G,775,1,no description,60.2 -776,I7N3G6G,776,1,no description,60.2 -777,I7N3G6G,777,1,no description,60.2 -778,I7N3G6G,778,1,no description,60.2 -779,I7N3G6G,779,1,no description,60.2 -780,I7N3G6G,780,1,no description,60.2 -781,I7N3G6G,781,1,no description,60.2 -782,I7N3G6G,782,1,no description,60.2 -783,I7N3G6G,783,1,no description,60.2 -784,I7N3G6G,784,1,no description,60.2 -785,I7N3G6G,785,1,no description,60.2 -786,I7N3G6G,786,1,no description,60.2 -787,I7N3G6G,787,1,no description,60.2 -788,I7N3G6G,788,1,no description,60.2 -789,I7N3G6G,789,1,no description,60.2 -790,I7N3G6G,790,1,no description,60.2 -791,I7N3G6G,791,1,no description,60.2 -792,I7N3G6G,792,1,no description,60.2 -793,I7N3G6G,793,1,no description,60.2 -794,I7N3G6G,794,1,no description,60.2 -795,I7N3G6G,795,1,no description,60.2 -796,I7N3G6G,796,1,no description,60.2 -797,I7N3G6G,797,1,no description,60.2 -798,I7N3G6G,798,1,no description,60.2 -799,I7N3G6G,799,1,no description,60.2 -800,I7N3G6G,800,1,no description,60.2 -801,I7N3G6G,801,1,no description,60.2 -802,I7N3G6G,802,1,no description,60.2 -803,I7N3G6G,803,1,no description,60.2 -804,I7N3G6G,804,1,no description,60.2 -805,I7N3G6G,805,1,no description,60.2 -806,I7N3G6G,806,1,no description,60.2 -807,I7N3G6G,807,1,no description,60.2 -808,I7N3G6G,808,1,no description,60.2 -809,I7N3G6G,809,1,no description,60.2 -810,I7N3G6G,810,1,no description,60.2 -811,I7N3G6G,811,1,no description,60.2 -812,I7N3G6G,812,1,no description,60.2 -813,I7N3G6G,813,1,no description,60.2 -814,I7N3G6G,814,1,no description,60.2 -815,I7N3G6G,815,1,no description,60.2 -816,I7N3G6G,816,1,no description,60.2 -817,I7N3G6G,817,1,no description,60.2 -818,I7N3G6G,818,1,no description,60.2 -819,I7N3G6G,819,1,no description,60.2 -820,I7N3G6G,820,1,no description,60.2 -821,I7N3G6G,821,1,no description,60.2 -822,I7N3G6G,822,1,no description,60.2 -823,I7N3G6G,823,1,no description,60.2 -824,I7N3G6G,824,1,no description,60.2 -825,I7N3G6G,825,1,no description,60.2 -826,I7N3G6G,826,1,no description,60.2 -827,I7N3G6G,827,1,no description,60.2 -828,I7N3G6G,828,1,no description,60.2 -829,I7N3G6G,829,1,no description,60.2 -830,I7N3G6G,830,1,no description,60.2 -831,I7N3G6G,831,1,no description,60.2 -832,I7N3G6G,832,1,no description,60.2 -833,I7N3G6G,833,1,no description,60.2 -834,I7N3G6G,834,1,no description,60.2 -835,I7N3G6G,835,1,no description,60.2 -836,I7N3G6G,836,1,no description,60.2 -837,I7N3G6G,837,1,no description,60.2 -838,I7N3G6G,838,1,no description,60.2 -839,I7N3G6G,839,1,no description,60.2 -840,I7N3G6G,840,1,no description,60.2 -841,I7N3G6G,841,1,no description,60.2 -842,I7N3G6G,842,1,no description,60.2 -843,I7N3G6G,843,1,no description,60.2 -844,I7N3G6G,844,1,no description,60.2 -845,I7N3G6G,845,1,no description,60.2 -846,I7N3G6G,846,1,no description,60.2 -847,I7N3G6G,847,1,no description,60.2 -848,I7N3G6G,848,1,no description,60.2 -849,I7N3G6G,849,1,no description,60.2 -850,I7N3G6G,850,1,no description,60.2 -851,I7N3G6G,851,1,no description,60.2 -852,I7N3G6G,852,1,no description,60.2 -853,I7N3G6G,853,1,no description,60.2 -854,I7N3G6G,854,1,no description,60.2 -855,I7N3G6G,855,1,no description,60.2 -856,I7N3G6G,856,1,no description,60.2 -857,I7N3G6G,857,1,no description,60.2 -858,I7N3G6G,858,1,no description,60.2 -859,I7N3G6G,859,1,no description,60.2 -860,I7N3G6G,860,1,no description,60.2 -861,I7N3G6G,861,1,no description,60.2 -862,I7N3G6G,862,1,no description,60.2 -863,I7N3G6G,863,1,no description,60.2 -864,I7N3G6G,864,1,no description,60.2 -865,I7N3G6G,865,1,no description,60.2 -866,I7N3G6G,866,1,no description,60.2 -867,I7N3G6G,867,1,no description,60.2 -868,I7N3G6G,868,1,no description,60.2 -869,I7N3G6G,869,1,no description,60.2 -870,I7N3G6G,870,1,no description,60.2 -871,I7N3G6G,871,1,no description,60.2 -872,I7N3G6G,872,1,no description,60.2 -873,I7N3G6G,873,1,no description,60.2 -874,I7N3G6G,874,1,no description,60.2 -875,I7N3G6G,875,1,no description,60.2 -876,I7N3G6G,876,1,no description,60.2 -877,I7N3G6G,877,1,no description,60.2 -878,I7N3G6G,878,1,no description,60.2 -879,I7N3G6G,879,1,no description,60.2 -880,I7N3G6G,880,1,no description,60.2 -881,I7N3G6G,881,1,no description,60.2 -882,I7N3G6G,882,1,no description,60.2 -883,I7N3G6G,883,1,no description,60.2 -884,I7N3G6G,884,1,no description,60.2 -885,I7N3G6G,885,1,no description,60.2 -886,I7N3G6G,886,1,no description,60.2 -887,I7N3G6G,887,1,no description,60.2 -888,I7N3G6G,888,1,no description,60.2 -889,I7N3G6G,889,1,no description,60.2 -890,I7N3G6G,890,1,no description,60.2 -891,I7N3G6G,891,1,no description,60.2 -892,I7N3G6G,892,1,no description,60.2 -893,I7N3G6G,893,1,no description,60.2 -894,I7N3G6G,894,1,no description,60.2 -895,I7N3G6G,895,1,no description,60.2 -896,I7N3G6G,896,1,no description,60.2 -897,I7N3G6G,897,1,no description,60.2 -898,I7N3G6G,898,1,no description,60.2 -899,I7N3G6G,899,1,no description,60.2 -900,I7N3G6G,900,1,no description,60.2 -901,I7N3G6G,901,1,no description,60.2 -902,I7N3G6G,902,1,no description,60.2 -903,I7N3G6G,903,1,no description,60.2 -904,I7N3G6G,904,1,no description,60.2 -905,I7N3G6G,905,1,no description,60.2 -906,I7N3G6G,906,1,no description,60.2 -907,I7N3G6G,907,1,no description,60.2 -908,I7N3G6G,908,1,no description,60.2 -909,I7N3G6G,909,1,no description,60.2 -910,I7N3G6G,910,1,no description,60.2 -911,I7N3G6G,911,1,no description,60.2 -912,I7N3G6G,912,1,no description,60.2 -913,I7N3G6G,913,1,no description,60.2 -914,I7N3G6G,914,1,no description,60.2 -915,I7N3G6G,915,1,no description,60.2 -916,I7N3G6G,916,1,no description,60.2 -917,I7N3G6G,917,1,no description,60.2 -918,I7N3G6G,918,1,no description,60.2 -919,I7N3G6G,919,1,no description,60.2 -920,I7N3G6G,920,1,no description,60.2 -921,I7N3G6G,921,1,no description,60.2 -922,I7N3G6G,922,1,no description,60.2 -923,I7N3G6G,923,1,no description,60.2 -924,I7N3G6G,924,1,no description,60.2 -925,I7N3G6G,925,1,no description,60.2 -926,I7N3G6G,926,1,no description,60.2 -927,I7N3G6G,927,1,no description,60.2 -928,I7N3G6G,928,1,no description,60.2 -929,I7N3G6G,929,1,no description,60.2 -930,I7N3G6G,930,1,no description,60.2 -931,I7N3G6G,931,1,no description,60.2 -932,I7N3G6G,932,1,no description,60.2 -933,I7N3G6G,933,1,no description,60.2 -934,I7N3G6G,934,1,no description,60.2 -935,I7N3G6G,935,1,no description,60.2 -936,I7N3G6G,936,1,no description,60.2 -937,I7N3G6G,937,1,no description,60.2 -938,I7N3G6G,938,1,no description,60.2 -939,I7N3G6G,939,1,no description,60.2 -940,I7N3G6G,940,1,no description,60.2 -941,I7N3G6G,941,1,no description,60.2 -942,I7N3G6G,942,1,no description,60.2 -943,I7N3G6G,943,1,no description,60.2 -944,I7N3G6G,944,1,no description,60.2 -945,I7N3G6G,945,1,no description,60.2 -946,I7N3G6G,946,1,no description,60.2 -947,I7N3G6G,947,1,no description,60.2 -948,I7N3G6G,948,1,no description,60.2 -949,I7N3G6G,949,1,no description,60.2 -950,I7N3G6G,950,1,no description,60.2 -951,I7N3G6G,951,1,no description,60.2 -952,I7N3G6G,952,1,no description,60.2 -953,I7N3G6G,953,1,no description,60.2 -954,I7N3G6G,954,1,no description,60.2 -955,I7N3G6G,955,1,no description,60.2 -956,I7N3G6G,956,1,no description,60.2 -957,I7N3G6G,957,1,no description,60.2 -958,I7N3G6G,958,1,no description,60.2 -959,I7N3G6G,959,1,no description,60.2 -960,I7N3G6G,960,1,no description,60.2 -961,I7N3G6G,961,1,no description,60.2 -962,I7N3G6G,962,1,no description,60.2 -963,I7N3G6G,963,1,no description,60.2 -964,I7N3G6G,964,1,no description,60.2 -965,I7N3G6G,965,1,no description,60.2 -966,I7N3G6G,966,1,no description,60.2 -967,I7N3G6G,967,1,no description,60.2 -968,I7N3G6G,968,1,no description,60.2 -969,I7N3G6G,969,1,no description,60.2 -970,I7N3G6G,970,1,no description,60.2 -971,I7N3G6G,971,1,no description,60.2 -972,I7N3G6G,972,1,no description,60.2 -973,I7N3G6G,973,1,no description,60.2 -974,I7N3G6G,974,1,no description,60.2 -975,I7N3G6G,975,1,no description,60.2 -976,I7N3G6G,976,1,no description,60.2 -977,I7N3G6G,977,1,no description,60.2 -978,I7N3G6G,978,1,no description,60.2 -979,I7N3G6G,979,1,no description,60.2 -980,I7N3G6G,980,1,no description,60.2 -981,I7N3G6G,981,1,no description,60.2 -982,I7N3G6G,982,1,no description,60.2 -983,I7N3G6G,983,1,no description,60.2 -984,I7N3G6G,984,1,no description,60.2 -985,I7N3G6G,985,1,no description,60.2 -986,I7N3G6G,986,1,no description,60.2 -987,I7N3G6G,987,1,no description,60.2 -988,I7N3G6G,988,1,no description,60.2 -989,I7N3G6G,989,1,no description,60.2 -990,I7N3G6G,990,1,no description,60.2 -991,I7N3G6G,991,1,no description,60.2 -992,I7N3G6G,992,1,no description,60.2 -993,I7N3G6G,993,1,no description,60.2 -994,I7N3G6G,994,1,no description,60.2 -995,I7N3G6G,995,1,no description,60.2 -996,I7N3G6G,996,1,no description,60.2 -997,I7N3G6G,997,1,no description,60.2 -998,I7N3G6G,998,1,no description,60.2 -999,I7N3G6G,999,1,no description,60.2 -1000,I7N3G6G,1000,1,no description,60.2 -1001,I7N3G6G,1001,1,no description,60.2 -1002,I7N3G6G,1002,1,no description,60.2 -1003,I7N3G6G,1003,1,no description,60.2 -1004,I7N3G6G,1004,1,no description,60.2 -1005,I7N3G6G,1005,1,no description,60.2 -1006,I7N3G6G,1006,1,no description,60.2 -1007,I7N3G6G,1007,1,no description,60.2 -1008,I7N3G6G,1008,1,no description,60.2 -1009,I7N3G6G,1009,1,no description,60.2 -1010,I7N3G6G,1010,1,no description,60.2 -1011,I7N3G6G,1011,1,no description,60.2 -1012,I7N3G6G,1012,1,no description,60.2 -1013,I7N3G6G,1013,1,no description,60.2 -1014,I7N3G6G,1014,1,no description,60.2 -1015,I7N3G6G,1015,1,no description,60.2 -1016,I7N3G6G,1016,1,no description,60.2 -1017,I7N3G6G,1017,1,no description,60.2 -1018,I7N3G6G,1018,1,no description,60.2 -1019,I7N3G6G,1019,1,no description,60.2 -1020,I7N3G6G,1020,1,no description,60.2 -1021,I7N3G6G,1021,1,no description,60.2 -1022,I7N3G6G,1022,1,no description,60.2 -1023,I7N3G6G,1023,1,no description,60.2 -1024,I7N3G6G,1024,1,no description,60.2 -1025,I7N3G6G,1025,1,no description,60.2 -1026,I7N3G6G,1026,1,no description,60.2 -1027,I7N3G6G,1027,1,no description,60.2 -1028,I7N3G6G,1028,1,no description,60.2 -1029,I7N3G6G,1029,1,no description,60.2 -1030,I7N3G6G,1030,1,no description,60.2 -1031,I7N3G6G,1031,1,no description,60.2 -1032,I7N3G6G,1032,1,no description,60.2 -1033,I7N3G6G,1033,1,no description,60.2 -1034,I7N3G6G,1034,1,no description,60.2 -1035,I7N3G6G,1035,1,no description,60.2 -1036,I7N3G6G,1036,1,no description,60.2 -1037,I7N3G6G,1037,1,no description,60.2 -1038,I7N3G6G,1038,1,no description,60.2 -1039,I7N3G6G,1039,1,no description,60.2 -1040,I7N3G6G,1040,1,no description,60.2 -1041,I7N3G6G,1041,1,no description,60.2 -1042,I7N3G6G,1042,1,no description,60.2 -1043,I7N3G6G,1043,1,no description,60.2 -1044,I7N3G6G,1044,1,no description,60.2 -1045,I7N3G6G,1045,1,no description,60.2 -1046,I7N3G6G,1046,1,no description,60.2 -1047,I7N3G6G,1047,1,no description,60.2 -1048,I7N3G6G,1048,1,no description,60.2 -1049,I7N3G6G,1049,1,no description,60.2 -1050,I7N3G6G,1050,1,no description,60.2 -1051,I7N3G6G,1051,1,no description,60.2 -1052,I7N3G6G,1052,1,no description,60.2 -1053,I7N3G6G,1053,1,no description,60.2 -1054,I7N3G6G,1054,1,no description,60.2 -1055,I7N3G6G,1055,1,no description,60.2 -1056,I7N3G6G,1056,1,no description,60.2 -1057,I7N3G6G,1057,1,no description,60.2 -1058,I7N3G6G,1058,1,no description,60.2 -1059,I7N3G6G,1059,1,no description,60.2 -1060,I7N3G6G,1060,1,no description,60.2 -1061,I7N3G6G,1061,1,no description,60.2 -1062,I7N3G6G,1062,1,no description,60.2 -1063,I7N3G6G,1063,1,no description,60.2 -1064,I7N3G6G,1064,1,no description,60.2 -1065,I7N3G6G,1065,1,no description,60.2 -1066,I7N3G6G,1066,1,no description,60.2 -1067,I7N3G6G,1067,1,no description,60.2 -1068,I7N3G6G,1068,1,no description,60.2 -1069,I7N3G6G,1069,1,no description,60.2 -1070,I7N3G6G,1070,1,no description,60.2 -1071,I7N3G6G,1071,1,no description,60.2 -1072,I7N3G6G,1072,1,no description,60.2 -1073,I7N3G6G,1073,1,no description,60.2 -1074,I7N3G6G,1074,1,no description,60.2 -1075,I7N3G6G,1075,1,no description,60.2 -1076,I7N3G6G,1076,1,no description,60.2 -1077,I7N3G6G,1077,1,no description,60.2 -1078,I7N3G6G,1078,1,no description,60.2 -1079,I7N3G6G,1079,1,no description,60.2 -1080,I7N3G6G,1080,1,no description,60.2 -1081,I7N3G6G,1081,1,no description,60.2 -1082,I7N3G6G,1082,1,no description,60.2 -1083,I7N3G6G,1083,1,no description,60.2 -1084,I7N3G6G,1084,1,no description,60.2 -1085,I7N3G6G,1085,1,no description,60.2 -1086,I7N3G6G,1086,1,no description,60.2 -1087,I7N3G6G,1087,1,no description,60.2 -1088,I7N3G6G,1088,1,no description,60.2 -1089,I7N3G6G,1089,1,no description,60.2 -1090,I7N3G6G,1090,1,no description,60.2 -1091,I7N3G6G,1091,1,no description,60.2 -1092,I7N3G6G,1092,1,no description,60.2 -1093,I7N3G6G,1093,1,no description,60.2 -1094,I7N3G6G,1094,1,no description,60.2 -1095,I7N3G6G,1095,1,no description,60.2 -1096,I7N3G6G,1096,1,no description,60.2 -1097,I7N3G6G,1097,1,no description,60.2 -1098,I7N3G6G,1098,1,no description,60.2 -1099,I7N3G6G,1099,1,no description,60.2 -1100,I7N3G6G,1100,1,no description,60.2 -1101,I7N3G6G,1101,1,no description,60.2 -1102,I7N3G6G,1102,1,no description,60.2 -1103,I7N3G6G,1103,1,no description,60.2 -1104,I7N3G6G,1104,1,no description,60.2 -1105,I7N3G6G,1105,1,no description,60.2 -1106,I7N3G6G,1106,1,no description,60.2 -1107,I7N3G6G,1107,1,no description,60.2 -1108,I7N3G6G,1108,1,no description,60.2 -1109,I7N3G6G,1109,1,no description,60.2 -1110,I7N3G6G,1110,1,no description,60.2 -1111,I7N3G6G,1111,1,no description,60.2 -1112,I7N3G6G,1112,1,no description,60.2 -1113,I7N3G6G,1113,1,no description,60.2 -1114,I7N3G6G,1114,1,no description,60.2 -1115,I7N3G6G,1115,1,no description,60.2 -1116,I7N3G6G,1116,1,no description,60.2 -1117,I7N3G6G,1117,1,no description,60.2 -1118,I7N3G6G,1118,1,no description,60.2 -1119,I7N3G6G,1119,1,no description,60.2 -1120,I7N3G6G,1120,1,no description,60.2 -1121,I7N3G6G,1121,1,no description,60.2 -1122,I7N3G6G,1122,1,no description,60.2 -1123,I7N3G6G,1123,1,no description,60.2 -1124,I7N3G6G,1124,1,no description,60.2 -1125,I7N3G6G,1125,1,no description,60.2 -1126,I7N3G6G,1126,1,no description,60.2 -1127,I7N3G6G,1127,1,no description,60.2 -1128,I7N3G6G,1128,1,no description,60.2 -1129,I7N3G6G,1129,1,no description,60.2 -1130,I7N3G6G,1130,1,no description,60.2 -1131,I7N3G6G,1131,1,no description,60.2 -1132,I7N3G6G,1132,1,no description,60.2 -1133,I7N3G6G,1133,1,no description,60.2 -1134,I7N3G6G,1134,1,no description,60.2 -1135,I7N3G6G,1135,1,no description,60.2 -1136,I7N3G6G,1136,1,no description,60.2 -1137,I7N3G6G,1137,1,no description,60.2 -1138,I7N3G6G,1138,1,no description,60.2 -1139,I7N3G6G,1139,1,no description,60.2 -1140,I7N3G6G,1140,1,no description,60.2 -1141,I7N3G6G,1141,1,no description,60.2 -1142,I7N3G6G,1142,1,no description,60.2 -1143,I7N3G6G,1143,1,no description,60.2 -1144,I7N3G6G,1144,1,no description,60.2 -1145,I7N3G6G,1145,1,no description,60.2 -1146,I7N3G6G,1146,1,no description,60.2 -1147,I7N3G6G,1147,1,no description,60.2 -1148,I7N3G6G,1148,1,no description,60.2 -1149,I7N3G6G,1149,1,no description,60.2 -1150,I7N3G6G,1150,1,no description,60.2 -1151,I7N3G6G,1151,1,no description,60.2 -1152,I7N3G6G,1152,1,no description,60.2 -1153,I7N3G6G,1153,1,no description,60.2 -1154,I7N3G6G,1154,1,no description,60.2 -1155,I7N3G6G,1155,1,no description,60.2 -1156,I7N3G6G,1156,1,no description,60.2 -1157,I7N3G6G,1157,1,no description,60.2 -1158,I7N3G6G,1158,1,no description,60.2 -1159,I7N3G6G,1159,1,no description,60.2 -1160,I7N3G6G,1160,1,no description,60.2 -1161,I7N3G6G,1161,1,no description,60.2 -1162,I7N3G6G,1162,1,no description,60.2 -1163,I7N3G6G,1163,1,no description,60.2 -1164,I7N3G6G,1164,1,no description,60.2 -1165,I7N3G6G,1165,1,no description,60.2 -1166,I7N3G6G,1166,1,no description,60.2 -1167,I7N3G6G,1167,1,no description,60.2 -1168,I7N3G6G,1168,1,no description,60.2 -1169,I7N3G6G,1169,1,no description,60.2 -1170,I7N3G6G,1170,1,no description,60.2 -1171,I7N3G6G,1171,1,no description,60.2 -1172,I7N3G6G,1172,1,no description,60.2 -1173,I7N3G6G,1173,1,no description,60.2 -1174,I7N3G6G,1174,1,no description,60.2 -1175,I7N3G6G,1175,1,no description,60.2 -1176,I7N3G6G,1176,1,no description,60.2 -1177,I7N3G6G,1177,1,no description,60.2 -1178,I7N3G6G,1178,1,no description,60.2 -1179,I7N3G6G,1179,1,no description,60.2 -1180,I7N3G6G,1180,1,no description,60.2 -1181,I7N3G6G,1181,1,no description,60.2 -1182,I7N3G6G,1182,1,no description,60.2 -1183,I7N3G6G,1183,1,no description,60.2 -1184,I7N3G6G,1184,1,no description,60.2 -1185,I7N3G6G,1185,1,no description,60.2 -1186,I7N3G6G,1186,1,no description,60.2 -1187,I7N3G6G,1187,1,no description,60.2 -1188,I7N3G6G,1188,1,no description,60.2 -1189,I7N3G6G,1189,1,no description,60.2 -1190,I7N3G6G,1190,1,no description,60.2 -1191,I7N3G6G,1191,1,no description,60.2 -1192,I7N3G6G,1192,1,no description,60.2 -1193,I7N3G6G,1193,1,no description,60.2 -1194,I7N3G6G,1194,1,no description,60.2 -1195,I7N3G6G,1195,1,no description,60.2 -1196,I7N3G6G,1196,1,no description,60.2 -1197,I7N3G6G,1197,1,no description,60.2 -1198,I7N3G6G,1198,1,no description,60.2 -1199,I7N3G6G,1199,1,no description,60.2 -1200,I7N3G6G,1200,1,no description,60.2 -1201,I7N3G6G,1201,1,no description,60.2 -1202,I7N3G6G,1202,1,no description,60.2 -1203,I7N3G6G,1203,1,no description,60.2 -1204,I7N3G6G,1204,1,no description,60.2 -1205,I7N3G6G,1205,1,no description,60.2 -1206,I7N3G6G,1206,1,no description,60.2 -1207,I7N3G6G,1207,1,no description,60.2 -1208,I7N3G6G,1208,1,no description,60.2 -1209,I7N3G6G,1209,1,no description,60.2 -1210,I7N3G6G,1210,1,no description,60.2 -1211,I7N3G6G,1211,1,no description,60.2 -1212,I7N3G6G,1212,1,no description,60.2 -1213,I7N3G6G,1213,1,no description,60.2 -1214,I7N3G6G,1214,1,no description,60.2 -1215,I7N3G6G,1215,1,no description,60.2 -1216,I7N3G6G,1216,1,no description,60.2 -1217,I7N3G6G,1217,1,no description,60.2 -1218,I7N3G6G,1218,1,no description,60.2 -1219,I7N3G6G,1219,1,no description,60.2 -1220,I7N3G6G,1220,1,no description,60.2 -1221,I7N3G6G,1221,1,no description,60.2 -1222,I7N3G6G,1222,1,no description,60.2 -1223,I7N3G6G,1223,1,no description,60.2 -1224,I7N3G6G,1224,1,no description,60.2 -1225,I7N3G6G,1225,1,no description,60.2 -1226,I7N3G6G,1226,1,no description,60.2 -1227,I7N3G6G,1227,1,no description,60.2 -1228,I7N3G6G,1228,1,no description,60.2 -1229,I7N3G6G,1229,1,no description,60.2 -1230,I7N3G6G,1230,1,no description,60.2 -1231,I7N3G6G,1231,1,no description,60.2 -1232,I7N3G6G,1232,1,no description,60.2 -1233,I7N3G6G,1233,1,no description,60.2 -1234,I7N3G6G,1234,1,no description,60.2 -1235,I7N3G6G,1235,1,no description,60.2 -1236,I7N3G6G,1236,1,no description,60.2 -1237,I7N3G6G,1237,1,no description,60.2 -1238,I7N3G6G,1238,1,no description,60.2 -1239,I7N3G6G,1239,1,no description,60.2 -1240,I7N3G6G,1240,1,no description,60.2 -1241,I7N3G6G,1241,1,no description,60.2 -1242,I7N3G6G,1242,1,no description,60.2 -1243,I7N3G6G,1243,1,no description,60.2 -1244,I7N3G6G,1244,1,no description,60.2 -1245,I7N3G6G,1245,1,no description,60.2 -1246,I7N3G6G,1246,1,no description,60.2 -1247,I7N3G6G,1247,1,no description,60.2 -1248,I7N3G6G,1248,1,no description,60.2 -1249,I7N3G6G,1249,1,no description,60.2 -1250,I7N3G6G,1250,1,no description,60.2 -1251,I7N3G6G,1251,1,no description,60.2 -1252,I7N3G6G,1252,1,no description,60.2 -1253,I7N3G6G,1253,1,no description,60.2 -1254,I7N3G6G,1254,1,no description,60.2 -1255,I7N3G6G,1255,1,no description,60.2 -1256,I7N3G6G,1256,1,no description,60.2 -1257,I7N3G6G,1257,1,no description,60.2 -1258,I7N3G6G,1258,1,no description,60.2 -1259,I7N3G6G,1259,1,no description,60.2 -1260,I7N3G6G,1260,1,no description,60.2 -1261,I7N3G6G,1261,1,no description,60.2 -1262,I7N3G6G,1262,1,no description,60.2 -1263,I7N3G6G,1263,1,no description,60.2 -1264,I7N3G6G,1264,1,no description,60.2 -1265,I7N3G6G,1265,1,no description,60.2 -1266,I7N3G6G,1266,1,no description,60.2 -1267,I7N3G6G,1267,1,no description,60.2 -1268,I7N3G6G,1268,1,no description,60.2 -1269,I7N3G6G,1269,1,no description,60.2 -1270,I7N3G6G,1270,1,no description,60.2 -1271,I7N3G6G,1271,1,no description,60.2 -1272,I7N3G6G,1272,1,no description,60.2 -1273,I7N3G6G,1273,1,no description,60.2 -1274,I7N3G6G,1274,1,no description,60.2 -1275,I7N3G6G,1275,1,no description,60.2 -1276,I7N3G6G,1276,1,no description,60.2 -1277,I7N3G6G,1277,1,no description,60.2 -1278,I7N3G6G,1278,1,no description,60.2 -1279,I7N3G6G,1279,1,no description,60.2 -1280,I7N3G6G,1280,1,no description,60.2 -1281,I7N3G6G,1281,1,no description,60.2 -1282,I7N3G6G,1282,1,no description,60.2 -1283,I7N3G6G,1283,1,no description,60.2 -1284,I7N3G6G,1284,1,no description,60.2 -1285,I7N3G6G,1285,1,no description,60.2 -1286,I7N3G6G,1286,1,no description,60.2 -1287,I7N3G6G,1287,1,no description,60.2 -1288,I7N3G6G,1288,1,no description,60.2 -1289,I7N3G6G,1289,1,no description,60.2 -1290,I7N3G6G,1290,1,no description,60.2 -1291,I7N3G6G,1291,1,no description,60.2 -1292,I7N3G6G,1292,1,no description,60.2 -1293,I7N3G6G,1293,1,no description,60.2 -1294,I7N3G6G,1294,1,no description,60.2 -1295,I7N3G6G,1295,1,no description,60.2 -1296,I7N3G6G,1296,1,no description,60.2 -1297,I7N3G6G,1297,1,no description,60.2 -1298,I7N3G6G,1298,1,no description,60.2 -1299,I7N3G6G,1299,1,no description,60.2 -1300,I7N3G6G,1300,1,no description,60.2 -1301,I7N3G6G,1301,1,no description,60.2 -1302,I7N3G6G,1302,1,no description,60.2 -1303,I7N3G6G,1303,1,no description,60.2 -1304,I7N3G6G,1304,1,no description,60.2 -1305,I7N3G6G,1305,1,no description,60.2 -1306,I7N3G6G,1306,1,no description,60.2 -1307,I7N3G6G,1307,1,no description,60.2 -1308,I7N3G6G,1308,1,no description,60.2 -1309,I7N3G6G,1309,1,no description,60.2 -1310,I7N3G6G,1310,1,no description,60.2 -1311,I7N3G6G,1311,1,no description,60.2 -1312,I7N3G6G,1312,1,no description,60.2 -1313,I7N3G6G,1313,1,no description,60.2 -1314,I7N3G6G,1314,1,no description,60.2 -1315,I7N3G6G,1315,1,no description,60.2 -1316,I7N3G6G,1316,1,no description,60.2 -1317,I7N3G6G,1317,1,no description,60.2 -1318,I7N3G6G,1318,1,no description,60.2 -1319,I7N3G6G,1319,1,no description,60.2 -1320,I7N3G6G,1320,1,no description,60.2 -1321,I7N3G6G,1321,1,no description,60.2 -1322,I7N3G6G,1322,1,no description,60.2 -1323,I7N3G6G,1323,1,no description,60.2 -1324,I7N3G6G,1324,1,no description,60.2 -1325,I7N3G6G,1325,1,no description,60.2 -1326,I7N3G6G,1326,1,no description,60.2 -1327,I7N3G6G,1327,1,no description,60.2 -1328,I7N3G6G,1328,1,no description,60.2 -1329,I7N3G6G,1329,1,no description,60.2 -1330,I7N3G6G,1330,1,no description,60.2 -1331,I7N3G6G,1331,1,no description,60.2 -1332,I7N3G6G,1332,1,no description,60.2 -1333,I7N3G6G,1333,1,no description,60.2 -1334,I7N3G6G,1334,1,no description,60.2 -1335,I7N3G6G,1335,1,no description,60.2 -1336,I7N3G6G,1336,1,no description,60.2 -1337,I7N3G6G,1337,1,no description,60.2 -1338,I7N3G6G,1338,1,no description,60.2 -1339,I7N3G6G,1339,1,no description,60.2 -1340,I7N3G6G,1340,1,no description,60.2 -1341,I7N3G6G,1341,1,no description,60.2 -1342,I7N3G6G,1342,1,no description,60.2 -1343,I7N3G6G,1343,1,no description,60.2 -1344,I7N3G6G,1344,1,no description,60.2 -1345,I7N3G6G,1345,1,no description,60.2 -1346,I7N3G6G,1346,1,no description,60.2 -1347,I7N3G6G,1347,1,no description,60.2 -1348,I7N3G6G,1348,1,no description,60.2 -1349,I7N3G6G,1349,1,no description,60.2 -1350,I7N3G6G,1350,1,no description,60.2 -1351,I7N3G6G,1351,1,no description,60.2 -1352,I7N3G6G,1352,1,no description,60.2 -1353,I7N3G6G,1353,1,no description,60.2 -1354,I7N3G6G,1354,1,no description,60.2 -1355,I7N3G6G,1355,1,no description,60.2 -1356,I7N3G6G,1356,1,no description,60.2 -1357,I7N3G6G,1357,1,no description,60.2 -1358,I7N3G6G,1358,1,no description,60.2 -1359,I7N3G6G,1359,1,no description,60.2 -1360,I7N3G6G,1360,1,no description,60.2 -1361,I7N3G6G,1361,1,no description,60.2 -1362,I7N3G6G,1362,1,no description,60.2 -1363,I7N3G6G,1363,1,no description,60.2 -1364,I7N3G6G,1364,1,no description,60.2 -1365,I7N3G6G,1365,1,no description,60.2 -1366,I7N3G6G,1366,1,no description,60.2 -1367,I7N3G6G,1367,1,no description,60.2 -1368,I7N3G6G,1368,1,no description,60.2 -1369,I7N3G6G,1369,1,no description,60.2 -1370,I7N3G6G,1370,1,no description,60.2 -1371,I7N3G6G,1371,1,no description,60.2 -1372,I7N3G6G,1372,1,no description,60.2 -1373,I7N3G6G,1373,1,no description,60.2 -1374,I7N3G6G,1374,1,no description,60.2 -1375,I7N3G6G,1375,1,no description,60.2 -1376,I7N3G6G,1376,1,no description,60.2 -1377,I7N3G6G,1377,1,no description,60.2 -1378,I7N3G6G,1378,1,no description,60.2 -1379,I7N3G6G,1379,1,no description,60.2 -1380,I7N3G6G,1380,1,no description,60.2 -1381,I7N3G6G,1381,1,no description,60.2 -1382,I7N3G6G,1382,1,no description,60.2 -1383,I7N3G6G,1383,1,no description,60.2 -1384,I7N3G6G,1384,1,no description,60.2 -1385,I7N3G6G,1385,1,no description,60.2 -1386,I7N3G6G,1386,1,no description,60.2 -1387,I7N3G6G,1387,1,no description,60.2 -1388,I7N3G6G,1388,1,no description,60.2 -1389,I7N3G6G,1389,1,no description,60.2 -1390,I7N3G6G,1390,1,no description,60.2 -1391,I7N3G6G,1391,1,no description,60.2 -1392,I7N3G6G,1392,1,no description,60.2 -1393,I7N3G6G,1393,1,no description,60.2 -1394,I7N3G6G,1394,1,no description,60.2 -1395,I7N3G6G,1395,1,no description,60.2 -1396,I7N3G6G,1396,1,no description,60.2 -1397,I7N3G6G,1397,1,no description,60.2 -1398,I7N3G6G,1398,1,no description,60.2 -1399,I7N3G6G,1399,1,no description,60.2 -1400,I7N3G6G,1400,1,no description,60.2 -1401,I7N3G6G,1401,1,no description,60.2 -1402,I7N3G6G,1402,1,no description,60.2 -1403,I7N3G6G,1403,1,no description,60.2 -1404,I7N3G6G,1404,1,no description,60.2 -1405,I7N3G6G,1405,1,no description,60.2 -1406,I7N3G6G,1406,1,no description,60.2 -1407,I7N3G6G,1407,1,no description,60.2 -1408,I7N3G6G,1408,1,no description,60.2 -1409,I7N3G6G,1409,1,no description,60.2 -1410,I7N3G6G,1410,1,no description,60.2 -1411,I7N3G6G,1411,1,no description,60.2 -1412,I7N3G6G,1412,1,no description,60.2 -1413,I7N3G6G,1413,1,no description,60.2 -1414,I7N3G6G,1414,1,no description,60.2 -1415,I7N3G6G,1415,1,no description,60.2 -1416,I7N3G6G,1416,1,no description,60.2 -1417,I7N3G6G,1417,1,no description,60.2 -1418,I7N3G6G,1418,1,no description,60.2 -1419,I7N3G6G,1419,1,no description,60.2 -1420,I7N3G6G,1420,1,no description,60.2 -1421,I7N3G6G,1421,1,no description,60.2 -1422,I7N3G6G,1422,1,no description,60.2 -1423,I7N3G6G,1423,1,no description,60.2 -1424,I7N3G6G,1424,1,no description,60.2 -1425,I7N3G6G,1425,1,no description,60.2 -1426,I7N3G6G,1426,1,no description,60.2 -1427,I7N3G6G,1427,1,no description,60.2 -1428,I7N3G6G,1428,1,no description,60.2 -1429,I7N3G6G,1429,1,no description,60.2 -1430,I7N3G6G,1430,1,no description,60.2 -1431,I7N3G6G,1431,1,no description,60.2 -1432,I7N3G6G,1432,1,no description,60.2 -1433,I7N3G6G,1433,1,no description,60.2 -1434,I7N3G6G,1434,1,no description,60.2 -1435,I7N3G6G,1435,1,no description,60.2 -1436,I7N3G6G,1436,1,no description,60.2 -1437,I7N3G6G,1437,1,no description,60.2 -1438,I7N3G6G,1438,1,no description,60.2 -1439,I7N3G6G,1439,1,no description,60.2 -1440,I7N3G6G,1440,1,no description,60.2 -1441,I7N3G6G,1441,1,no description,60.2 -1442,I7N3G6G,1442,1,no description,60.2 -1443,I7N3G6G,1443,1,no description,60.2 -1444,I7N3G6G,1444,1,no description,60.2 -1445,I7N3G6G,1445,1,no description,60.2 -1446,I7N3G6G,1446,1,no description,60.2 -1447,I7N3G6G,1447,1,no description,60.2 -1448,I7N3G6G,1448,1,no description,60.2 -1449,I7N3G6G,1449,1,no description,60.2 -1450,I7N3G6G,1450,1,no description,60.2 -1451,I7N3G6G,1451,1,no description,60.2 -1452,I7N3G6G,1452,1,no description,60.2 -1453,I7N3G6G,1453,1,no description,60.2 -1454,I7N3G6G,1454,1,no description,60.2 -1455,I7N3G6G,1455,1,no description,60.2 -1456,I7N3G6G,1456,1,no description,60.2 -1457,I7N3G6G,1457,1,no description,60.2 -1458,I7N3G6G,1458,1,no description,60.2 -1459,I7N3G6G,1459,1,no description,60.2 -1460,I7N3G6G,1460,1,no description,60.2 -1461,I7N3G6G,1461,1,no description,60.2 -1462,I7N3G6G,1462,1,no description,60.2 -1463,I7N3G6G,1463,1,no description,60.2 -1464,I7N3G6G,1464,1,no description,60.2 -1465,I7N3G6G,1465,1,no description,60.2 -1466,I7N3G6G,1466,1,no description,60.2 -1467,I7N3G6G,1467,1,no description,60.2 -1468,I7N3G6G,1468,1,no description,60.2 -1469,I7N3G6G,1469,1,no description,60.2 -1470,I7N3G6G,1470,1,no description,60.2 -1471,I7N3G6G,1471,1,no description,60.2 -1472,I7N3G6G,1472,1,no description,60.2 -1473,I7N3G6G,1473,1,no description,60.2 -1474,I7N3G6G,1474,1,no description,60.2 -1475,I7N3G6G,1475,1,no description,60.2 -1476,I7N3G6G,1476,1,no description,60.2 -1477,I7N3G6G,1477,1,no description,60.2 -1478,I7N3G6G,1478,1,no description,60.2 -1479,I7N3G6G,1479,1,no description,60.2 -1480,I7N3G6G,1480,1,no description,60.2 -1481,I7N3G6G,1481,1,no description,60.2 -1482,I7N3G6G,1482,1,no description,60.2 -1483,I7N3G6G,1483,1,no description,60.2 -1484,I7N3G6G,1484,1,no description,60.2 -1485,I7N3G6G,1485,1,no description,60.2 -1486,I7N3G6G,1486,1,no description,60.2 -1487,I7N3G6G,1487,1,no description,60.2 -1488,I7N3G6G,1488,1,no description,60.2 -1489,I7N3G6G,1489,1,no description,60.2 -1490,I7N3G6G,1490,1,no description,60.2 -1491,I7N3G6G,1491,1,no description,60.2 -1492,I7N3G6G,1492,1,no description,60.2 -1493,I7N3G6G,1493,1,no description,60.2 -1494,I7N3G6G,1494,1,no description,60.2 -1495,I7N3G6G,1495,1,no description,60.2 -1496,I7N3G6G,1496,1,no description,60.2 -1497,I7N3G6G,1497,1,no description,60.2 -1498,I7N3G6G,1498,1,no description,60.2 -1499,I7N3G6G,1499,1,no description,60.2 -1500,I7N3G6G,1500,1,no description,60.2 -1501,I7N3G6G,1501,1,no description,60.2 -1502,I7N3G6G,1502,1,no description,60.2 -1503,I7N3G6G,1503,1,no description,60.2 -1504,I7N3G6G,1504,1,no description,60.2 -1505,I7N3G6G,1505,1,no description,60.2 -1506,I7N3G6G,1506,1,no description,60.2 -1507,I7N3G6G,1507,1,no description,60.2 -1508,I7N3G6G,1508,1,no description,60.2 -1509,I7N3G6G,1509,1,no description,60.2 -1510,I7N3G6G,1510,1,no description,60.2 -1511,I7N3G6G,1511,1,no description,60.2 -1512,I7N3G6G,1512,1,no description,60.2 -1513,I7N3G6G,1513,1,no description,60.2 -1514,I7N3G6G,1514,1,no description,60.2 -1515,I7N3G6G,1515,1,no description,60.2 -1516,I7N3G6G,1516,1,no description,60.2 -1517,I7N3G6G,1517,1,no description,60.2 -1518,I7N3G6G,1518,1,no description,60.2 -1519,I7N3G6G,1519,1,no description,60.2 -1520,I7N3G6G,1520,1,no description,60.2 -1521,I7N3G6G,1521,1,no description,60.2 -1522,I7N3G6G,1522,1,no description,60.2 -1523,I7N3G6G,1523,1,no description,60.2 -1524,I7N3G6G,1524,1,no description,60.2 -1525,I7N3G6G,1525,1,no description,60.2 -1526,I7N3G6G,1526,1,no description,60.2 -1527,I7N3G6G,1527,1,no description,60.2 -1528,I7N3G6G,1528,1,no description,60.2 -1529,I7N3G6G,1529,1,no description,60.2 -1530,I7N3G6G,1530,1,no description,60.2 -1531,I7N3G6G,1531,1,no description,60.2 -1532,I7N3G6G,1532,1,no description,60.2 -1533,I7N3G6G,1533,1,no description,60.2 -1534,I7N3G6G,1534,1,no description,60.2 -1535,I7N3G6G,1535,1,no description,60.2 -1536,I7N3G6G,1536,1,no description,60.2 -1537,I7N3G6G,1537,1,no description,60.2 -1538,I7N3G6G,1538,1,no description,60.2 -1539,I7N3G6G,1539,1,no description,60.2 -1540,I7N3G6G,1540,1,no description,60.2 -1541,I7N3G6G,1541,1,no description,60.2 -1542,I7N3G6G,1542,1,no description,60.2 -1543,I7N3G6G,1543,1,no description,60.2 -1544,I7N3G6G,1544,1,no description,60.2 -1545,I7N3G6G,1545,1,no description,60.2 -1546,I7N3G6G,1546,1,no description,60.2 -1547,I7N3G6G,1547,1,no description,60.2 -1548,I7N3G6G,1548,1,no description,60.2 -1549,I7N3G6G,1549,1,no description,60.2 -1550,I7N3G6G,1550,1,no description,60.2 -1551,I7N3G6G,1551,1,no description,60.2 -1552,I7N3G6G,1552,1,no description,60.2 -1553,I7N3G6G,1553,1,no description,60.2 -1554,I7N3G6G,1554,1,no description,60.2 -1555,I7N3G6G,1555,1,no description,60.2 -1556,I7N3G6G,1556,1,no description,60.2 -1557,I7N3G6G,1557,1,no description,60.2 -1558,I7N3G6G,1558,1,no description,60.2 -1559,I7N3G6G,1559,1,no description,60.2 -1560,I7N3G6G,1560,1,no description,60.2 -1561,I7N3G6G,1561,1,no description,60.2 -1562,I7N3G6G,1562,1,no description,60.2 -1563,I7N3G6G,1563,1,no description,60.2 -1564,I7N3G6G,1564,1,no description,60.2 -1565,I7N3G6G,1565,1,no description,60.2 -1566,I7N3G6G,1566,1,no description,60.2 -1567,I7N3G6G,1567,1,no description,60.2 -1568,I7N3G6G,1568,1,no description,60.2 -1569,I7N3G6G,1569,1,no description,60.2 -1570,I7N3G6G,1570,1,no description,60.2 -1571,I7N3G6G,1571,1,no description,60.2 -1572,I7N3G6G,1572,1,no description,60.2 -1573,I7N3G6G,1573,1,no description,60.2 -1574,I7N3G6G,1574,1,no description,60.2 -1575,I7N3G6G,1575,1,no description,60.2 -1576,I7N3G6G,1576,1,no description,60.2 -1577,I7N3G6G,1577,1,no description,60.2 -1578,I7N3G6G,1578,1,no description,60.2 -1579,I7N3G6G,1579,1,no description,60.2 -1580,I7N3G6G,1580,1,no description,60.2 -1581,I7N3G6G,1581,1,no description,60.2 -1582,I7N3G6G,1582,1,no description,60.2 -1583,I7N3G6G,1583,1,no description,60.2 -1584,I7N3G6G,1584,1,no description,60.2 -1585,I7N3G6G,1585,1,no description,60.2 -1586,I7N3G6G,1586,1,no description,60.2 -1587,I7N3G6G,1587,1,no description,60.2 -1588,I7N3G6G,1588,1,no description,60.2 -1589,I7N3G6G,1589,1,no description,60.2 -1590,I7N3G6G,1590,1,no description,60.2 -1591,I7N3G6G,1591,1,no description,60.2 -1592,I7N3G6G,1592,1,no description,60.2 -1593,I7N3G6G,1593,1,no description,60.2 -1594,I7N3G6G,1594,1,no description,60.2 -1595,I7N3G6G,1595,1,no description,60.2 -1596,I7N3G6G,1596,1,no description,60.2 -1597,I7N3G6G,1597,1,no description,60.2 -1598,I7N3G6G,1598,1,no description,60.2 -1599,I7N3G6G,1599,1,no description,60.2 -1600,I7N3G6G,1600,1,no description,60.2 -1601,I7N3G6G,1601,1,no description,60.2 -1602,I7N3G6G,1602,1,no description,60.2 -1603,I7N3G6G,1603,1,no description,60.2 -1604,I7N3G6G,1604,1,no description,60.2 -1605,I7N3G6G,1605,1,no description,60.2 -1606,I7N3G6G,1606,1,no description,60.2 -1607,I7N3G6G,1607,1,no description,60.2 -1608,I7N3G6G,1608,1,no description,60.2 -1609,I7N3G6G,1609,1,no description,60.2 -1610,I7N3G6G,1610,1,no description,60.2 -1611,I7N3G6G,1611,1,no description,60.2 -1612,I7N3G6G,1612,1,no description,60.2 -1613,I7N3G6G,1613,1,no description,60.2 -1614,I7N3G6G,1614,1,no description,60.2 -1615,I7N3G6G,1615,1,no description,60.2 -1616,I7N3G6G,1616,1,no description,60.2 -1617,I7N3G6G,1617,1,no description,60.2 -1618,I7N3G6G,1618,1,no description,60.2 -1619,I7N3G6G,1619,1,no description,60.2 -1620,I7N3G6G,1620,1,no description,60.2 -1621,I7N3G6G,1621,1,no description,60.2 -1622,I7N3G6G,1622,1,no description,60.2 -1623,I7N3G6G,1623,1,no description,60.2 -1624,I7N3G6G,1624,1,no description,60.2 -1625,I7N3G6G,1625,1,no description,60.2 -1626,I7N3G6G,1626,1,no description,60.2 -1627,I7N3G6G,1627,1,no description,60.2 -1628,I7N3G6G,1628,1,no description,60.2 -1629,I7N3G6G,1629,1,no description,60.2 -1630,I7N3G6G,1630,1,no description,60.2 -1631,I7N3G6G,1631,1,no description,60.2 -1632,I7N3G6G,1632,1,no description,60.2 -1633,I7N3G6G,1633,1,no description,60.2 -1634,I7N3G6G,1634,1,no description,60.2 -1635,I7N3G6G,1635,1,no description,60.2 -1636,I7N3G6G,1636,1,no description,60.2 -1637,I7N3G6G,1637,1,no description,60.2 -1638,I7N3G6G,1638,1,no description,60.2 -1639,I7N3G6G,1639,1,no description,60.2 -1640,I7N3G6G,1640,1,no description,60.2 -1641,I7N3G6G,1641,1,no description,60.2 -1642,I7N3G6G,1642,1,no description,60.2 -1643,I7N3G6G,1643,1,no description,60.2 -1644,I7N3G6G,1644,1,no description,60.2 -1645,I7N3G6G,1645,1,no description,60.2 -1646,I7N3G6G,1646,1,no description,60.2 -1647,I7N3G6G,1647,1,no description,60.2 -1648,I7N3G6G,1648,1,no description,60.2 -1649,I7N3G6G,1649,1,no description,60.2 -1650,I7N3G6G,1650,1,no description,60.2 -1651,I7N3G6G,1651,1,no description,60.2 -1652,I7N3G6G,1652,1,no description,60.2 -1653,I7N3G6G,1653,1,no description,60.2 -1654,I7N3G6G,1654,1,no description,60.2 -1655,I7N3G6G,1655,1,no description,60.2 -1656,I7N3G6G,1656,1,no description,60.2 -1657,I7N3G6G,1657,1,no description,60.2 -1658,I7N3G6G,1658,1,no description,60.2 -1659,I7N3G6G,1659,1,no description,60.2 -1660,I7N3G6G,1660,1,no description,60.2 -1661,I7N3G6G,1661,1,no description,60.2 -1662,I7N3G6G,1662,1,no description,60.2 -1663,I7N3G6G,1663,1,no description,60.2 -1664,I7N3G6G,1664,1,no description,60.2 -1665,I7N3G6G,1665,1,no description,60.2 -1666,I7N3G6G,1666,1,no description,60.2 -1667,I7N3G6G,1667,1,no description,60.2 -1668,I7N3G6G,1668,1,no description,60.2 -1669,I7N3G6G,1669,1,no description,60.2 -1670,I7N3G6G,1670,1,no description,60.2 -1671,I7N3G6G,1671,1,no description,60.2 -1672,I7N3G6G,1672,1,no description,60.2 -1673,I7N3G6G,1673,1,no description,60.2 -1674,I7N3G6G,1674,1,no description,60.2 -1675,I7N3G6G,1675,1,no description,60.2 -1676,I7N3G6G,1676,1,no description,60.2 -1677,I7N3G6G,1677,1,no description,60.2 -1678,I7N3G6G,1678,1,no description,60.2 -1679,I7N3G6G,1679,1,no description,60.2 -1680,I7N3G6G,1680,1,no description,60.2 -1681,I7N3G6G,1681,1,no description,60.2 -1682,I7N3G6G,1682,1,no description,60.2 -1683,I7N3G6G,1683,1,no description,60.2 -1684,I7N3G6G,1684,1,no description,60.2 -1685,I7N3G6G,1685,1,no description,60.2 -1686,I7N3G6G,1686,1,no description,60.2 -1687,I7N3G6G,1687,1,no description,60.2 -1688,I7N3G6G,1688,1,no description,60.2 -1689,I7N3G6G,1689,1,no description,60.2 -1690,I7N3G6G,1690,1,no description,60.2 -1691,I7N3G6G,1691,1,no description,60.2 -1692,I7N3G6G,1692,1,no description,60.2 -1693,I7N3G6G,1693,1,no description,60.2 -1694,I7N3G6G,1694,1,no description,60.2 -1695,I7N3G6G,1695,1,no description,60.2 -1696,I7N3G6G,1696,1,no description,60.2 -1697,I7N3G6G,1697,1,no description,60.2 -1698,I7N3G6G,1698,1,no description,60.2 -1699,I7N3G6G,1699,1,no description,60.2 -1700,I7N3G6G,1700,1,no description,60.2 -1701,I7N3G6G,1701,1,no description,60.2 -1702,I7N3G6G,1702,1,no description,60.2 -1703,I7N3G6G,1703,1,no description,60.2 -1704,I7N3G6G,1704,1,no description,60.2 -1705,I7N3G6G,1705,1,no description,60.2 -1706,I7N3G6G,1706,1,no description,60.2 -1707,I7N3G6G,1707,1,no description,60.2 -1708,I7N3G6G,1708,1,no description,60.2 -1709,I7N3G6G,1709,1,no description,60.2 -1710,I7N3G6G,1710,1,no description,60.2 -1711,I7N3G6G,1711,1,no description,60.2 -1712,I7N3G6G,1712,1,no description,60.2 -1713,I7N3G6G,1713,1,no description,60.2 -1714,I7N3G6G,1714,1,no description,60.2 -1715,I7N3G6G,1715,1,no description,60.2 -1716,I7N3G6G,1716,1,no description,60.2 -1717,I7N3G6G,1717,1,no description,60.2 -1718,I7N3G6G,1718,1,no description,60.2 -1719,I7N3G6G,1719,1,no description,60.2 -1720,I7N3G6G,1720,1,no description,60.2 -1721,I7N3G6G,1721,1,no description,60.2 -1722,I7N3G6G,1722,1,no description,60.2 -1723,I7N3G6G,1723,1,no description,60.2 -1724,I7N3G6G,1724,1,no description,60.2 -1725,I7N3G6G,1725,1,no description,60.2 -1726,I7N3G6G,1726,1,no description,60.2 -1727,I7N3G6G,1727,1,no description,60.2 -1728,I7N3G6G,1728,1,no description,60.2 -1729,I7N3G6G,1729,1,no description,60.2 -1730,I7N3G6G,1730,1,no description,60.2 -1731,I7N3G6G,1731,1,no description,60.2 -1732,I7N3G6G,1732,1,no description,60.2 -1733,I7N3G6G,1733,1,no description,60.2 -1734,I7N3G6G,1734,1,no description,60.2 -1735,I7N3G6G,1735,1,no description,60.2 -1736,I7N3G6G,1736,1,no description,60.2 -1737,I7N3G6G,1737,1,no description,60.2 -1738,I7N3G6G,1738,1,no description,60.2 -1739,I7N3G6G,1739,1,no description,60.2 -1740,I7N3G6G,1740,1,no description,60.2 -1741,I7N3G6G,1741,1,no description,60.2 -1742,I7N3G6G,1742,1,no description,60.2 -1743,I7N3G6G,1743,1,no description,60.2 -1744,I7N3G6G,1744,1,no description,60.2 -1745,I7N3G6G,1745,1,no description,60.2 -1746,I7N3G6G,1746,1,no description,60.2 -1747,I7N3G6G,1747,1,no description,60.2 -1748,I7N3G6G,1748,1,no description,60.2 -1749,I7N3G6G,1749,1,no description,60.2 -1750,I7N3G6G,1750,1,no description,60.2 -1751,I7N3G6G,1751,1,no description,60.2 -1752,I7N3G6G,1752,1,no description,60.2 -1753,I7N3G6G,1753,1,no description,60.2 -1754,I7N3G6G,1754,1,no description,60.2 -1755,I7N3G6G,1755,1,no description,60.2 -1756,I7N3G6G,1756,1,no description,60.2 -1757,I7N3G6G,1757,1,no description,60.2 -1758,I7N3G6G,1758,1,no description,60.2 -1759,I7N3G6G,1759,1,no description,60.2 -1760,I7N3G6G,1760,1,no description,60.2 -1761,I7N3G6G,1761,1,no description,60.2 -1762,I7N3G6G,1762,1,no description,60.2 -1763,I7N3G6G,1763,1,no description,60.2 -1764,I7N3G6G,1764,1,no description,60.2 -1765,I7N3G6G,1765,1,no description,60.2 -1766,I7N3G6G,1766,1,no description,60.2 -1767,I7N3G6G,1767,1,no description,60.2 -1768,I7N3G6G,1768,1,no description,60.2 -1769,I7N3G6G,1769,1,no description,60.2 -1770,I7N3G6G,1770,1,no description,60.2 -1771,I7N3G6G,1771,1,no description,60.2 -1772,I7N3G6G,1772,1,no description,60.2 -1773,I7N3G6G,1773,1,no description,60.2 -1774,I7N3G6G,1774,1,no description,60.2 -1775,I7N3G6G,1775,1,no description,60.2 -1776,I7N3G6G,1776,1,no description,60.2 -1777,I7N3G6G,1777,1,no description,60.2 -1778,I7N3G6G,1778,1,no description,60.2 -1779,I7N3G6G,1779,1,no description,60.2 -1780,I7N3G6G,1780,1,no description,60.2 -1781,I7N3G6G,1781,1,no description,60.2 -1782,I7N3G6G,1782,1,no description,60.2 -1783,I7N3G6G,1783,1,no description,60.2 -1784,I7N3G6G,1784,1,no description,60.2 -1785,I7N3G6G,1785,1,no description,60.2 -1786,I7N3G6G,1786,1,no description,60.2 -1787,I7N3G6G,1787,1,no description,60.2 -1788,I7N3G6G,1788,1,no description,60.2 -1789,I7N3G6G,1789,1,no description,60.2 -1790,I7N3G6G,1790,1,no description,60.2 -1791,I7N3G6G,1791,1,no description,60.2 -1792,I7N3G6G,1792,1,no description,60.2 -1793,I7N3G6G,1793,1,no description,60.2 -1794,I7N3G6G,1794,1,no description,60.2 -1795,I7N3G6G,1795,1,no description,60.2 -1796,I7N3G6G,1796,1,no description,60.2 -1797,I7N3G6G,1797,1,no description,60.2 -1798,I7N3G6G,1798,1,no description,60.2 -1799,I7N3G6G,1799,1,no description,60.2 -1800,I7N3G6G,1800,1,no description,60.2 -1801,I7N3G6G,1801,1,no description,60.2 -1802,I7N3G6G,1802,1,no description,60.2 -1803,I7N3G6G,1803,1,no description,60.2 -1804,I7N3G6G,1804,1,no description,60.2 -1805,I7N3G6G,1805,1,no description,60.2 -1806,I7N3G6G,1806,1,no description,60.2 -1807,I7N3G6G,1807,1,no description,60.2 -1808,I7N3G6G,1808,1,no description,60.2 -1809,I7N3G6G,1809,1,no description,60.2 -1810,I7N3G6G,1810,1,no description,60.2 -1811,I7N3G6G,1811,1,no description,60.2 -1812,I7N3G6G,1812,1,no description,60.2 -1813,I7N3G6G,1813,1,no description,60.2 -1814,I7N3G6G,1814,1,no description,60.2 -1815,I7N3G6G,1815,1,no description,60.2 -1816,I7N3G6G,1816,1,no description,60.2 -1817,I7N3G6G,1817,1,no description,60.2 -1818,I7N3G6G,1818,1,no description,60.2 -1819,I7N3G6G,1819,1,no description,60.2 -1820,I7N3G6G,1820,1,no description,60.2 -1821,I7N3G6G,1821,1,no description,60.2 -1822,I7N3G6G,1822,1,no description,60.2 -1823,I7N3G6G,1823,1,no description,60.2 -1824,I7N3G6G,1824,1,no description,60.2 -1825,I7N3G6G,1825,1,no description,60.2 -1826,I7N3G6G,1826,1,no description,60.2 -1827,I7N3G6G,1827,1,no description,60.2 -1828,I7N3G6G,1828,1,no description,60.2 -1829,I7N3G6G,1829,1,no description,60.2 -1830,I7N3G6G,1830,1,no description,60.2 -1831,I7N3G6G,1831,1,no description,60.2 -1832,I7N3G6G,1832,1,no description,60.2 -1833,I7N3G6G,1833,1,no description,60.2 -1834,I7N3G6G,1834,1,no description,60.2 -1835,I7N3G6G,1835,1,no description,60.2 -1836,I7N3G6G,1836,1,no description,60.2 -1837,I7N3G6G,1837,1,no description,60.2 -1838,I7N3G6G,1838,1,no description,60.2 -1839,I7N3G6G,1839,1,no description,60.2 -1840,I7N3G6G,1840,1,no description,60.2 -1841,I7N3G6G,1841,1,no description,60.2 -1842,I7N3G6G,1842,1,no description,60.2 -1843,I7N3G6G,1843,1,no description,60.2 -1844,I7N3G6G,1844,1,no description,60.2 -1845,I7N3G6G,1845,1,no description,60.2 -1846,I7N3G6G,1846,1,no description,60.2 -1847,I7N3G6G,1847,1,no description,60.2 -1848,I7N3G6G,1848,1,no description,60.2 -1849,I7N3G6G,1849,1,no description,60.2 -1850,I7N3G6G,1850,1,no description,60.2 -1851,I7N3G6G,1851,1,no description,60.2 -1852,I7N3G6G,1852,1,no description,60.2 -1853,I7N3G6G,1853,1,no description,60.2 -1854,I7N3G6G,1854,1,no description,60.2 -1855,I7N3G6G,1855,1,no description,60.2 -1856,I7N3G6G,1856,1,no description,60.2 -1857,I7N3G6G,1857,1,no description,60.2 -1858,I7N3G6G,1858,1,no description,60.2 -1859,I7N3G6G,1859,1,no description,60.2 -1860,I7N3G6G,1860,1,no description,60.2 -1861,I7N3G6G,1861,1,no description,60.2 -1862,I7N3G6G,1862,1,no description,60.2 -1863,I7N3G6G,1863,1,no description,60.2 -1864,I7N3G6G,1864,1,no description,60.2 -1865,I7N3G6G,1865,1,no description,60.2 -1866,I7N3G6G,1866,1,no description,60.2 -1867,I7N3G6G,1867,1,no description,60.2 -1868,I7N3G6G,1868,1,no description,60.2 -1869,I7N3G6G,1869,1,no description,60.2 -1870,I7N3G6G,1870,1,no description,60.2 -1871,I7N3G6G,1871,1,no description,60.2 -1872,I7N3G6G,1872,1,no description,60.2 -1873,I7N3G6G,1873,1,no description,60.2 -1874,I7N3G6G,1874,1,no description,60.2 -1875,I7N3G6G,1875,1,no description,60.2 -1876,I7N3G6G,1876,1,no description,60.2 -1877,I7N3G6G,1877,1,no description,60.2 -1878,I7N3G6G,1878,1,no description,60.2 -1879,I7N3G6G,1879,1,no description,60.2 -1880,I7N3G6G,1880,1,no description,60.2 -1881,I7N3G6G,1881,1,no description,60.2 -1882,I7N3G6G,1882,1,no description,60.2 -1883,I7N3G6G,1883,1,no description,60.2 -1884,I7N3G6G,1884,1,no description,60.2 -1885,I7N3G6G,1885,1,no description,60.2 -1886,I7N3G6G,1886,1,no description,60.2 -1887,I7N3G6G,1887,1,no description,60.2 -1888,I7N3G6G,1888,1,no description,60.2 -1889,I7N3G6G,1889,1,no description,60.2 -1890,I7N3G6G,1890,1,no description,60.2 -1891,I7N3G6G,1891,1,no description,60.2 -1892,I7N3G6G,1892,1,no description,60.2 -1893,I7N3G6G,1893,1,no description,60.2 -1894,I7N3G6G,1894,1,no description,60.2 -1895,I7N3G6G,1895,1,no description,60.2 -1896,I7N3G6G,1896,1,no description,60.2 -1897,I7N3G6G,1897,1,no description,60.2 -1898,I7N3G6G,1898,1,no description,60.2 -1899,I7N3G6G,1899,1,no description,60.2 -1900,I7N3G6G,1900,1,no description,60.2 -1901,I7N3G6G,1901,1,no description,60.2 -1902,I7N3G6G,1902,1,no description,60.2 -1903,I7N3G6G,1903,1,no description,60.2 -1904,I7N3G6G,1904,1,no description,60.2 -1905,I7N3G6G,1905,1,no description,60.2 -1906,I7N3G6G,1906,1,no description,60.2 -1907,I7N3G6G,1907,1,no description,60.2 -1908,I7N3G6G,1908,1,no description,60.2 -1909,I7N3G6G,1909,1,no description,60.2 -1910,I7N3G6G,1910,1,no description,60.2 -1911,I7N3G6G,1911,1,no description,60.2 -1912,I7N3G6G,1912,1,no description,60.2 -1913,I7N3G6G,1913,1,no description,60.2 -1914,I7N3G6G,1914,1,no description,60.2 -1915,I7N3G6G,1915,1,no description,60.2 -1916,I7N3G6G,1916,1,no description,60.2 -1917,I7N3G6G,1917,1,no description,60.2 -1918,I7N3G6G,1918,1,no description,60.2 -1919,I7N3G6G,1919,1,no description,60.2 -1920,I7N3G6G,1920,1,no description,60.2 -1921,I7N3G6G,1921,1,no description,60.2 -1922,I7N3G6G,1922,1,no description,60.2 -1923,I7N3G6G,1923,1,no description,60.2 -1924,I7N3G6G,1924,1,no description,60.2 -1925,I7N3G6G,1925,1,no description,60.2 -1926,I7N3G6G,1926,1,no description,60.2 -1927,I7N3G6G,1927,1,no description,60.2 -1928,I7N3G6G,1928,1,no description,60.2 -1929,I7N3G6G,1929,1,no description,60.2 -1930,I7N3G6G,1930,1,no description,60.2 -1931,I7N3G6G,1931,1,no description,60.2 -1932,I7N3G6G,1932,1,no description,60.2 -1933,I7N3G6G,1933,1,no description,60.2 -1934,I7N3G6G,1934,1,no description,60.2 -1935,I7N3G6G,1935,1,no description,60.2 -1936,I7N3G6G,1936,1,no description,60.2 -1937,I7N3G6G,1937,1,no description,60.2 -1938,I7N3G6G,1938,1,no description,60.2 -1939,I7N3G6G,1939,1,no description,60.2 -1940,I7N3G6G,1940,1,no description,60.2 -1941,I7N3G6G,1941,1,no description,60.2 -1942,I7N3G6G,1942,1,no description,60.2 -1943,I7N3G6G,1943,1,no description,60.2 -1944,I7N3G6G,1944,1,no description,60.2 -1945,I7N3G6G,1945,1,no description,60.2 -1946,I7N3G6G,1946,1,no description,60.2 -1947,I7N3G6G,1947,1,no description,60.2 -1948,I7N3G6G,1948,1,no description,60.2 -1949,I7N3G6G,1949,1,no description,60.2 -1950,I7N3G6G,1950,1,no description,60.2 -1951,I7N3G6G,1951,1,no description,60.2 -1952,I7N3G6G,1952,1,no description,60.2 -1953,I7N3G6G,1953,1,no description,60.2 -1954,I7N3G6G,1954,1,no description,60.2 -1955,I7N3G6G,1955,1,no description,60.2 -1956,I7N3G6G,1956,1,no description,60.2 -1957,I7N3G6G,1957,1,no description,60.2 -1958,I7N3G6G,1958,1,no description,60.2 -1959,I7N3G6G,1959,1,no description,60.2 -1960,I7N3G6G,1960,1,no description,60.2 -1961,I7N3G6G,1961,1,no description,60.2 -1962,I7N3G6G,1962,1,no description,60.2 -1963,I7N3G6G,1963,1,no description,60.2 -1964,I7N3G6G,1964,1,no description,60.2 -1965,I7N3G6G,1965,1,no description,60.2 -1966,I7N3G6G,1966,1,no description,60.2 -1967,I7N3G6G,1967,1,no description,60.2 -1968,I7N3G6G,1968,1,no description,60.2 -1969,I7N3G6G,1969,1,no description,60.2 -1970,I7N3G6G,1970,1,no description,60.2 -1971,I7N3G6G,1971,1,no description,60.2 -1972,I7N3G6G,1972,1,no description,60.2 -1973,I7N3G6G,1973,1,no description,60.2 -1974,I7N3G6G,1974,1,no description,60.2 -1975,I7N3G6G,1975,1,no description,60.2 -1976,I7N3G6G,1976,1,no description,60.2 -1977,I7N3G6G,1977,1,no description,60.2 -1978,I7N3G6G,1978,1,no description,60.2 -1979,I7N3G6G,1979,1,no description,60.2 -1980,I7N3G6G,1980,1,no description,60.2 -1981,I7N3G6G,1981,1,no description,60.2 -1982,I7N3G6G,1982,1,no description,60.2 -1983,I7N3G6G,1983,1,no description,60.2 -1984,I7N3G6G,1984,1,no description,60.2 -1985,I7N3G6G,1985,1,no description,60.2 -1986,I7N3G6G,1986,1,no description,60.2 -1987,I7N3G6G,1987,1,no description,60.2 -1988,I7N3G6G,1988,1,no description,60.2 -1989,I7N3G6G,1989,1,no description,60.2 -1990,I7N3G6G,1990,1,no description,60.2 -1991,I7N3G6G,1991,1,no description,60.2 -1992,I7N3G6G,1992,1,no description,60.2 -1993,I7N3G6G,1993,1,no description,60.2 -1994,I7N3G6G,1994,1,no description,60.2 -1995,I7N3G6G,1995,1,no description,60.2 -1996,I7N3G6G,1996,1,no description,60.2 -1997,I7N3G6G,1997,1,no description,60.2 -1998,I7N3G6G,1998,1,no description,60.2 -1999,I7N3G6G,1999,1,no description,60.2 -2000,I7N3G6G,2000,1,no description,60.2 -2001,I7N3G6G,2001,1,no description,60.2 -2002,I7N3G6G,2002,1,no description,60.2 -2003,I7N3G6G,2003,1,no description,60.2 -2004,I7N3G6G,2004,1,no description,60.2 -2005,I7N3G6G,2005,1,no description,60.2 -2006,I7N3G6G,2006,1,no description,60.2 -2007,I7N3G6G,2007,1,no description,60.2 -2008,I7N3G6G,2008,1,no description,60.2 -2009,I7N3G6G,2009,1,no description,60.2 -2010,I7N3G6G,2010,1,no description,60.2 -2011,I7N3G6G,2011,1,no description,60.2 -2012,I7N3G6G,2012,1,no description,60.2 -2013,I7N3G6G,2013,1,no description,60.2 -2014,I7N3G6G,2014,1,no description,60.2 -2015,I7N3G6G,2015,1,no description,60.2 -2016,I7N3G6G,2016,1,no description,60.2 -2017,I7N3G6G,2017,1,no description,60.2 -2018,I7N3G6G,2018,1,no description,60.2 -2019,I7N3G6G,2019,1,no description,60.2 -2020,I7N3G6G,2020,1,no description,60.2 -2021,I7N3G6G,2021,1,no description,60.2 -2022,I7N3G6G,2022,1,no description,60.2 -2023,I7N3G6G,2023,1,no description,60.2 -2024,I7N3G6G,2024,1,no description,60.2 -2025,I7N3G6G,2025,1,no description,60.2 -2026,I7N3G6G,2026,1,no description,60.2 -2027,I7N3G6G,2027,1,no description,60.2 -2028,I7N3G6G,2028,1,no description,60.2 -2029,I7N3G6G,2029,1,no description,60.2 -2030,I7N3G6G,2030,1,no description,60.2 -2031,I7N3G6G,2031,1,no description,60.2 -2032,I7N3G6G,2032,1,no description,60.2 -2033,I7N3G6G,2033,1,no description,60.2 -2034,I7N3G6G,2034,1,no description,60.2 -2035,I7N3G6G,2035,1,no description,60.2 -2036,I7N3G6G,2036,1,no description,60.2 -2037,I7N3G6G,2037,1,no description,60.2 -2038,I7N3G6G,2038,1,no description,60.2 -2039,I7N3G6G,2039,1,no description,60.2 -2040,I7N3G6G,2040,1,no description,60.2 -2041,I7N3G6G,2041,1,no description,60.2 -2042,I7N3G6G,2042,1,no description,60.2 -2043,I7N3G6G,2043,1,no description,60.2 -2044,I7N3G6G,2044,1,no description,60.2 -2045,I7N3G6G,2045,1,no description,60.2 -2046,I7N3G6G,2046,1,no description,60.2 -2047,I7N3G6G,2047,1,no description,60.2 -2048,I7N3G6G,2048,1,no description,60.2 -2049,I7N3G6G,2049,1,no description,60.2 -2050,I7N3G6G,2050,1,no description,60.2 -2051,I7N3G6G,2051,1,no description,60.2 -2052,I7N3G6G,2052,1,no description,60.2 -2053,I7N3G6G,2053,1,no description,60.2 -2054,I7N3G6G,2054,1,no description,60.2 -2055,I7N3G6G,2055,1,no description,60.2 -2056,I7N3G6G,2056,1,no description,60.2 -2057,I7N3G6G,2057,1,no description,60.2 -2058,I7N3G6G,2058,1,no description,60.2 -2059,I7N3G6G,2059,1,no description,60.2 -2060,I7N3G6G,2060,1,no description,60.2 -2061,I7N3G6G,2061,1,no description,60.2 -2062,I7N3G6G,2062,1,no description,60.2 -2063,I7N3G6G,2063,1,no description,60.2 -2064,I7N3G6G,2064,1,no description,60.2 -2065,I7N3G6G,2065,1,no description,60.2 -2066,I7N3G6G,2066,1,no description,60.2 -2067,I7N3G6G,2067,1,no description,60.2 -2068,I7N3G6G,2068,1,no description,60.2 -2069,I7N3G6G,2069,1,no description,60.2 -2070,I7N3G6G,2070,1,no description,60.2 -2071,I7N3G6G,2071,1,no description,60.2 -2072,I7N3G6G,2072,1,no description,60.2 -2073,I7N3G6G,2073,1,no description,60.2 -2074,I7N3G6G,2074,1,no description,60.2 -2075,I7N3G6G,2075,1,no description,60.2 -2076,I7N3G6G,2076,1,no description,60.2 -2077,I7N3G6G,2077,1,no description,60.2 -2078,I7N3G6G,2078,1,no description,60.2 -2079,I7N3G6G,2079,1,no description,60.2 -2080,I7N3G6G,2080,1,no description,60.2 -2081,I7N3G6G,2081,1,no description,60.2 -2082,I7N3G6G,2082,1,no description,60.2 -2083,I7N3G6G,2083,1,no description,60.2 -2084,I7N3G6G,2084,1,no description,60.2 -2085,I7N3G6G,2085,1,no description,60.2 -2086,I7N3G6G,2086,1,no description,60.2 -2087,I7N3G6G,2087,1,no description,60.2 -2088,I7N3G6G,2088,1,no description,60.2 -2089,I7N3G6G,2089,1,no description,60.2 -2090,I7N3G6G,2090,1,no description,60.2 -2091,I7N3G6G,2091,1,no description,60.2 -2092,I7N3G6G,2092,1,no description,60.2 -2093,I7N3G6G,2093,1,no description,60.2 -2094,I7N3G6G,2094,1,no description,60.2 -2095,I7N3G6G,2095,1,no description,60.2 -2096,I7N3G6G,2096,1,no description,60.2 -2097,I7N3G6G,2097,1,no description,60.2 -2098,I7N3G6G,2098,1,no description,60.2 -2099,I7N3G6G,2099,1,no description,60.2 -2100,I7N3G6G,2100,1,no description,60.2 -2101,I7N3G6G,2101,1,no description,60.2 -2102,I7N3G6G,2102,1,no description,60.2 -2103,I7N3G6G,2103,1,no description,60.2 -2104,I7N3G6G,2104,1,no description,60.2 -2105,I7N3G6G,2105,1,no description,60.2 -2106,I7N3G6G,2106,1,no description,60.2 -2107,I7N3G6G,2107,1,no description,60.2 -2108,I7N3G6G,2108,1,no description,60.2 -2109,I7N3G6G,2109,1,no description,60.2 -2110,I7N3G6G,2110,1,no description,60.2 -2111,I7N3G6G,2111,1,no description,60.2 -2112,I7N3G6G,2112,1,no description,60.2 -2113,I7N3G6G,2113,1,no description,60.2 -2114,I7N3G6G,2114,1,no description,60.2 -2115,I7N3G6G,2115,1,no description,60.2 -2116,I7N3G6G,2116,1,no description,60.2 -2117,I7N3G6G,2117,1,no description,60.2 -2118,I7N3G6G,2118,1,no description,60.2 -2119,I7N3G6G,2119,1,no description,60.2 -2120,I7N3G6G,2120,1,no description,60.2 -2121,I7N3G6G,2121,1,no description,60.2 -2122,I7N3G6G,2122,1,no description,60.2 -2123,I7N3G6G,2123,1,no description,60.2 -2124,I7N3G6G,2124,1,no description,60.2 -2125,I7N3G6G,2125,1,no description,60.2 -2126,I7N3G6G,2126,1,no description,60.2 -2127,I7N3G6G,2127,1,no description,60.2 -2128,I7N3G6G,2128,1,no description,60.2 -2129,I7N3G6G,2129,1,no description,60.2 -2130,I7N3G6G,2130,1,no description,60.2 -2131,I7N3G6G,2131,1,no description,60.2 -2132,I7N3G6G,2132,1,no description,60.2 -2133,I7N3G6G,2133,1,no description,60.2 -2134,I7N3G6G,2134,1,no description,60.2 -2135,I7N3G6G,2135,1,no description,60.2 -2136,I7N3G6G,2136,1,no description,60.2 -2137,I7N3G6G,2137,1,no description,60.2 -2138,I7N3G6G,2138,1,no description,60.2 -2139,I7N3G6G,2139,1,no description,60.2 -2140,I7N3G6G,2140,1,no description,60.2 -2141,I7N3G6G,2141,1,no description,60.2 -2142,I7N3G6G,2142,1,no description,60.2 -2143,I7N3G6G,2143,1,no description,60.2 -2144,I7N3G6G,2144,1,no description,60.2 -2145,I7N3G6G,2145,1,no description,60.2 -2146,I7N3G6G,2146,1,no description,60.2 -2147,I7N3G6G,2147,1,no description,60.2 -2148,I7N3G6G,2148,1,no description,60.2 -2149,I7N3G6G,2149,1,no description,60.2 -2150,I7N3G6G,2150,1,no description,60.2 -2151,I7N3G6G,2151,1,no description,60.2 -2152,I7N3G6G,2152,1,no description,60.2 -2153,I7N3G6G,2153,1,no description,60.2 -2154,I7N3G6G,2154,1,no description,60.2 -2155,I7N3G6G,2155,1,no description,60.2 -2156,I7N3G6G,2156,1,no description,60.2 -2157,I7N3G6G,2157,1,no description,60.2 -2158,I7N3G6G,2158,1,no description,60.2 -2159,I7N3G6G,2159,1,no description,60.2 -2160,I7N3G6G,2160,1,no description,60.2 -2161,I7N3G6G,2161,1,no description,60.2 -2162,I7N3G6G,2162,1,no description,60.2 -2163,I7N3G6G,2163,1,no description,60.2 -2164,I7N3G6G,2164,1,no description,60.2 -2165,I7N3G6G,2165,1,no description,60.2 -2166,I7N3G6G,2166,1,no description,60.2 -2167,I7N3G6G,2167,1,no description,60.2 -2168,I7N3G6G,2168,1,no description,60.2 -2169,I7N3G6G,2169,1,no description,60.2 -2170,I7N3G6G,2170,1,no description,60.2 -2171,I7N3G6G,2171,1,no description,60.2 -2172,I7N3G6G,2172,1,no description,60.2 -2173,I7N3G6G,2173,1,no description,60.2 -2174,I7N3G6G,2174,1,no description,60.2 -2175,I7N3G6G,2175,1,no description,60.2 -2176,I7N3G6G,2176,1,no description,60.2 -2177,I7N3G6G,2177,1,no description,60.2 -2178,I7N3G6G,2178,1,no description,60.2 -2179,I7N3G6G,2179,1,no description,60.2 -2180,I7N3G6G,2180,1,no description,60.2 -2181,I7N3G6G,2181,1,no description,60.2 -2182,I7N3G6G,2182,1,no description,60.2 -2183,I7N3G6G,2183,1,no description,60.2 -2184,I7N3G6G,2184,1,no description,60.2 -2185,I7N3G6G,2185,1,no description,60.2 -2186,I7N3G6G,2186,1,no description,60.2 -2187,I7N3G6G,2187,1,no description,60.2 -2188,I7N3G6G,2188,1,no description,60.2 -2189,I7N3G6G,2189,1,no description,60.2 -2190,I7N3G6G,2190,1,no description,60.2 -2191,I7N3G6G,2191,1,no description,60.2 -2192,I7N3G6G,2192,1,no description,60.2 -2193,I7N3G6G,2193,1,no description,60.2 -2194,I7N3G6G,2194,1,no description,60.2 -2195,I7N3G6G,2195,1,no description,60.2 -2196,I7N3G6G,2196,1,no description,60.2 -2197,I7N3G6G,2197,1,no description,60.2 -2198,I7N3G6G,2198,1,no description,60.2 -2199,I7N3G6G,2199,1,no description,60.2 -2200,I7N3G6G,2200,1,no description,60.2 -2201,I7N3G6G,2201,1,no description,60.2 -2202,I7N3G6G,2202,1,no description,60.2 -2203,I7N3G6G,2203,1,no description,60.2 -2204,I7N3G6G,2204,1,no description,60.2 -2205,I7N3G6G,2205,1,no description,60.2 -2206,I7N3G6G,2206,1,no description,60.2 -2207,I7N3G6G,2207,1,no description,60.2 -2208,I7N3G6G,2208,1,no description,60.2 -2209,I7N3G6G,2209,1,no description,60.2 -2210,I7N3G6G,2210,1,no description,60.2 -2211,I7N3G6G,2211,1,no description,60.2 -2212,I7N3G6G,2212,1,no description,60.2 -2213,I7N3G6G,2213,1,no description,60.2 -2214,I7N3G6G,2214,1,no description,60.2 -2215,I7N3G6G,2215,1,no description,60.2 -2216,I7N3G6G,2216,1,no description,60.2 -2217,I7N3G6G,2217,1,no description,60.2 -2218,I7N3G6G,2218,1,no description,60.2 -2219,I7N3G6G,2219,1,no description,60.2 -2220,I7N3G6G,2220,1,no description,60.2 -2221,I7N3G6G,2221,1,no description,60.2 -2222,I7N3G6G,2222,1,no description,60.2 -2223,I7N3G6G,2223,1,no description,60.2 -2224,I7N3G6G,2224,1,no description,60.2 -2225,I7N3G6G,2225,1,no description,60.2 -2226,I7N3G6G,2226,1,no description,60.2 diff --git a/packages/itmat-job-executor/test/testFiles/CSVCurator_error1.tsv b/packages/itmat-job-executor/test/testFiles/CSVCurator_error1.tsv deleted file mode 100644 index 9a49e65bb..000000000 --- a/packages/itmat-job-executor/test/testFiles/CSVCurator_error1.tsv +++ /dev/null @@ -1,2227 +0,0 @@ -ID,SubjectID,VisitID,Sex,Description,Weight,UNKNOWN -1,I7N3G6G,1,1,no description,60.2,random -2,I7N3G6G,2,1,no description,60.2,random -3,I7N3G6G,3,1,no description,60.2,random -4,I7N3G6G,4,1,no description,60.2,random -5,I7N3G6G,5,1,no description,60.2,random -6,I7N3G6G,6,1,no description,60.2,random -7,I7N3G6G,7,1,no description,60.2,random -8,I7N3G6G,8,1,no description,60.2,random -9,I7N3G6G,9,1,no description,60.2,random -10,I7N3G6G,10,1,no description,60.2,random -11,I7N3G6G,11,1,no description,60.2,random -12,I7N3G6G,12,1,no description,60.2,random -13,I7N3G6G,13,1,no description,60.2,random -14,I7N3G6G,14,1,no description,60.2,random -15,I7N3G6G,15,1,no description,60.2,random -16,I7N3G6G,16,1,no description,60.2,random -17,I7N3G6G,17,1,no description,60.2,random -18,I7N3G6G,18,1,no description,60.2,random -19,I7N3G6G,19,1,no description,60.2,random -20,I7N3G6G,20,1,no description,60.2,random -21,I7N3G6G,21,1,no description,60.2,random -22,I7N3G6G,22,1,no description,60.2,random -23,I7N3G6G,23,1,no description,60.2,random -24,I7N3G6G,24,1,no description,60.2,random -25,I7N3G6G,25,1,no description,60.2,random -26,I7N3G6G,26,1,no description,60.2,random -27,I7N3G6G,27,1,no description,60.2,random -28,I7N3G6G,28,1,no description,60.2,random -29,I7N3G6G,29,1,no description,60.2,random -30,I7N3G6G,30,1,no description,60.2,random -31,I7N3G6G,31,1,no description,60.2,random -32,I7N3G6G,32,1,no description,60.2,random -33,I7N3G6G,33,1,no description,60.2,random -34,I7N3G6G,34,1,no description,60.2,random -35,I7N3G6G,35,1,no description,60.2,random -36,I7N3G6G,36,1,no description,60.2,random -37,I7N3G6G,37,1,no description,60.2,random -38,I7N3G6G,38,1,no description,60.2,random -39,I7N3G6G,39,1,no description,60.2,random -40,I7N3G6G,40,1,no description,60.2,random -41,I7N3G6G,41,1,no description,60.2,random -42,I7N3G6G,42,1,no description,60.2,random -43,I7N3G6G,43,1,no description,60.2,random -44,I7N3G6G,44,1,no description,60.2,random -45,I7N3G6G,45,1,no description,60.2,random -46,I7N3G6G,46,1,no description,60.2,random -47,I7N3G6G,47,1,no description,60.2,random -48,I7N3G6G,48,1,no description,60.2,random -49,I7N3G6G,49,1,no description,60.2,random -50,I7N3G6G,50,1,no description,60.2,random -51,I7N3G6G,51,1,no description,60.2,random -52,I7N3G6G,52,1,no description,60.2,random -53,I7N3G6G,53,1,no description,60.2,random -54,I7N3G6G,54,1,no description,60.2,random -55,I7N3G6G,55,1,no description,60.2,random -56,I7N3G6G,56,1,no description,60.2,random -57,I7N3G6G,57,1,no description,60.2,random -58,I7N3G6G,58,1,no description,60.2,random -59,I7N3G6G,59,1,no description,60.2,random -60,I7N3G6G,60,1,no description,60.2,random -61,I7N3G6G,61,1,no description,60.2,random -62,I7N3G6G,62,1,no description,60.2,random -63,I7N3G6G,63,1,no description,60.2,random -64,I7N3G6G,64,1,no description,60.2,random -65,I7N3G6G,65,1,no description,60.2,random -66,I7N3G6G,66,1,no description,60.2,random -67,I7N3G6G,67,1,no description,60.2,random -68,I7N3G6G,68,1,no description,60.2,random -69,I7N3G6G,69,1,no description,60.2,random -70,I7N3G6G,70,1,no description,60.2,random -71,I7N3G6G,71,1,no description,60.2,random -72,I7N3G6G,72,1,no description,60.2,random -73,I7N3G6G,73,1,no description,60.2,random -74,I7N3G6G,74,1,no description,60.2,random -75,I7N3G6G,75,1,no description,60.2,random -76,I7N3G6G,76,1,no description,60.2,random -77,I7N3G6G,77,1,no description,60.2,random -78,I7N3G6G,78,1,no description,60.2,random -79,I7N3G6G,79,1,no description,60.2,random -80,I7N3G6G,80,1,no description,60.2,random -81,I7N3G6G,81,1,no description,60.2,random -82,I7N3G6G,82,1,no description,60.2,random -83,I7N3G6G,83,1,no description,60.2,random -84,I7N3G6G,84,1,no description,60.2,random -85,I7N3G6G,85,1,no description,60.2,random -86,I7N3G6G,86,1,no description,60.2,random -87,I7N3G6G,87,1,no description,60.2,random -88,I7N3G6G,88,1,no description,60.2,random -89,I7N3G6G,89,1,no description,60.2,random -90,I7N3G6G,90,1,no description,60.2,random -91,I7N3G6G,91,1,no description,60.2,random -92,I7N3G6G,92,1,no description,60.2,random -93,I7N3G6G,93,1,no description,60.2,random -94,I7N3G6G,94,1,no description,60.2,random -95,I7N3G6G,95,1,no description,60.2,random -96,I7N3G6G,96,1,no description,60.2,random -97,I7N3G6G,97,1,no description,60.2,random -98,I7N3G6G,98,1,no description,60.2,random -99,I7N3G6G,99,1,no description,60.2,random -100,I7N3G6G,100,1,no description,60.2,random -101,I7N3G6G,101,1,no description,60.2,random -102,I7N3G6G,102,1,no description,60.2,random -103,I7N3G6G,103,1,no description,60.2,random -104,I7N3G6G,104,1,no description,60.2,random -105,I7N3G6G,105,1,no description,60.2,random -106,I7N3G6G,106,1,no description,60.2,random -107,I7N3G6G,107,1,no description,60.2,random -108,I7N3G6G,108,1,no description,60.2,random -109,I7N3G6G,109,1,no description,60.2,random -110,I7N3G6G,110,1,no description,60.2,random -111,I7N3G6G,111,1,no description,60.2,random -112,I7N3G6G,112,1,no description,60.2,random -113,I7N3G6G,113,1,no description,60.2,random -114,I7N3G6G,114,1,no description,60.2,random -115,I7N3G6G,115,1,no description,60.2,random -116,I7N3G6G,116,1,no description,60.2,random -117,I7N3G6G,117,1,no description,60.2,random -118,I7N3G6G,118,1,no description,60.2,random -119,I7N3G6G,119,1,no description,60.2,random -120,I7N3G6G,120,1,no description,60.2,random -121,I7N3G6G,121,1,no description,60.2,random -122,I7N3G6G,122,1,no description,60.2,random -123,I7N3G6G,123,1,no description,60.2,random -124,I7N3G6G,124,1,no description,60.2,random -125,I7N3G6G,125,1,no description,60.2,random -126,I7N3G6G,126,1,no description,60.2,random -127,I7N3G6G,127,1,no description,60.2,random -128,I7N3G6G,128,1,no description,60.2,random -129,I7N3G6G,129,1,no description,60.2,random -130,I7N3G6G,130,1,no description,60.2,random -131,I7N3G6G,131,1,no description,60.2,random -132,I7N3G6G,132,1,no description,60.2,random -133,I7N3G6G,133,1,no description,60.2,random -134,I7N3G6G,134,1,no description,60.2,random -135,I7N3G6G,135,1,no description,60.2,random -136,I7N3G6G,136,1,no description,60.2,random -137,I7N3G6G,137,1,no description,60.2,random -138,I7N3G6G,138,1,no description,60.2,random -139,I7N3G6G,139,1,no description,60.2,random -140,I7N3G6G,140,1,no description,60.2,random -141,I7N3G6G,141,1,no description,60.2,random -142,I7N3G6G,142,1,no description,60.2,random -143,I7N3G6G,143,1,no description,60.2,random -144,I7N3G6G,144,1,no description,60.2,random -145,I7N3G6G,145,1,no description,60.2,random -146,I7N3G6G,146,1,no description,60.2,random -147,I7N3G6G,147,1,no description,60.2,random -148,I7N3G6G,148,1,no description,60.2,random -149,I7N3G6G,149,1,no description,60.2,random -150,I7N3G6G,150,1,no description,60.2,random -151,I7N3G6G,151,1,no description,60.2,random -152,I7N3G6G,152,1,no description,60.2,random -153,I7N3G6G,153,1,no description,60.2,random -154,I7N3G6G,154,1,no description,60.2,random -155,I7N3G6G,155,1,no description,60.2,random -156,I7N3G6G,156,1,no description,60.2,random -157,I7N3G6G,157,1,no description,60.2,random -158,I7N3G6G,158,1,no description,60.2,random -159,I7N3G6G,159,1,no description,60.2,random -160,I7N3G6G,160,1,no description,60.2,random -161,I7N3G6G,161,1,no description,60.2,random -162,I7N3G6G,162,1,no description,60.2,random -163,I7N3G6G,163,1,no description,60.2,random -164,I7N3G6G,164,1,no description,60.2,random -165,I7N3G6G,165,1,no description,60.2,random -166,I7N3G6G,166,1,no description,60.2,random -167,I7N3G6G,167,1,no description,60.2,random -168,I7N3G6G,168,1,no description,60.2,random -169,I7N3G6G,169,1,no description,60.2,random -170,I7N3G6G,170,1,no description,60.2,random -171,I7N3G6G,171,1,no description,60.2,random -172,I7N3G6G,172,1,no description,60.2,random -173,I7N3G6G,173,1,no description,60.2,random -174,I7N3G6G,174,1,no description,60.2,random -175,I7N3G6G,175,1,no description,60.2,random -176,I7N3G6G,176,1,no description,60.2,random -177,I7N3G6G,177,1,no description,60.2,random -178,I7N3G6G,178,1,no description,60.2,random -179,I7N3G6G,179,1,no description,60.2,random -180,I7N3G6G,180,1,no description,60.2,random -181,I7N3G6G,181,1,no description,60.2,random -182,I7N3G6G,182,1,no description,60.2,random -183,I7N3G6G,183,1,no description,60.2,random -184,I7N3G6G,184,1,no description,60.2,random -185,I7N3G6G,185,1,no description,60.2,random -186,I7N3G6G,186,1,no description,60.2,random -187,I7N3G6G,187,1,no description,60.2,random -188,I7N3G6G,188,1,no description,60.2,random -189,I7N3G6G,189,1,no description,60.2,random -190,I7N3G6G,190,1,no description,60.2,random -191,I7N3G6G,191,1,no description,60.2,random -192,I7N3G6G,192,1,no description,60.2,random -193,I7N3G6G,193,1,no description,60.2,random -194,I7N3G6G,194,1,no description,60.2,random -195,I7N3G6G,195,1,no description,60.2,random -196,I7N3G6G,196,1,no description,60.2,random -197,I7N3G6G,197,1,no description,60.2,random -198,I7N3G6G,198,1,no description,60.2,random -199,I7N3G6G,199,1,no description,60.2,random -200,I7N3G6G,200,1,no description,60.2,random -201,I7N3G6G,201,1,no description,60.2,random -202,I7N3G6G,202,1,no description,60.2,random -203,I7N3G6G,203,1,no description,60.2,random -204,I7N3G6G,204,1,no description,60.2,random -205,I7N3G6G,205,1,no description,60.2,random -206,I7N3G6G,206,1,no description,60.2,random -207,I7N3G6G,207,1,no description,60.2,random -208,I7N3G6G,208,1,no description,60.2,random -209,I7N3G6G,209,1,no description,60.2,random -210,I7N3G6G,210,1,no description,60.2,random -211,I7N3G6G,211,1,no description,60.2,random -212,I7N3G6G,212,1,no description,60.2,random -213,I7N3G6G,213,1,no description,60.2,random -214,I7N3G6G,214,1,no description,60.2,random -215,I7N3G6G,215,1,no description,60.2,random -216,I7N3G6G,216,1,no description,60.2,random -217,I7N3G6G,217,1,no description,60.2,random -218,I7N3G6G,218,1,no description,60.2,random -219,I7N3G6G,219,1,no description,60.2,random -220,I7N3G6G,220,1,no description,60.2,random -221,I7N3G6G,221,1,no description,60.2,random -222,I7N3G6G,222,1,no description,60.2,random -223,I7N3G6G,223,1,no description,60.2,random -224,I7N3G6G,224,1,no description,60.2,random -225,I7N3G6G,225,1,no description,60.2,random -226,I7N3G6G,226,1,no description,60.2,random -227,I7N3G6G,227,1,no description,60.2,random -228,I7N3G6G,228,1,no description,60.2,random -229,I7N3G6G,229,1,no description,60.2,random -230,I7N3G6G,230,1,no description,60.2,random -231,I7N3G6G,231,1,no description,60.2,random -232,I7N3G6G,232,1,no description,60.2,random -233,I7N3G6G,233,1,no description,60.2,random -234,I7N3G6G,234,1,no description,60.2,random -235,I7N3G6G,235,1,no description,60.2,random -236,I7N3G6G,236,1,no description,60.2,random -237,I7N3G6G,237,1,no description,60.2,random -238,I7N3G6G,238,1,no description,60.2,random -239,I7N3G6G,239,1,no description,60.2,random -240,I7N3G6G,240,1,no description,60.2,random -241,I7N3G6G,241,1,no description,60.2,random -242,I7N3G6G,242,1,no description,60.2,random -243,I7N3G6G,243,1,no description,60.2,random -244,I7N3G6G,244,1,no description,60.2,random -245,I7N3G6G,245,1,no description,60.2,random -246,I7N3G6G,246,1,no description,60.2,random -247,I7N3G6G,247,1,no description,60.2,random -248,I7N3G6G,248,1,no description,60.2,random -249,I7N3G6G,249,1,no description,60.2,random -250,I7N3G6G,250,1,no description,60.2,random -251,I7N3G6G,251,1,no description,60.2,random -252,I7N3G6G,252,1,no description,60.2,random -253,I7N3G6G,253,1,no description,60.2,random -254,I7N3G6G,254,1,no description,60.2,random -255,I7N3G6G,255,1,no description,60.2,random -256,I7N3G6G,256,1,no description,60.2,random -257,I7N3G6G,257,1,no description,60.2,random -258,I7N3G6G,258,1,no description,60.2,random -259,I7N3G6G,259,1,no description,60.2,random -260,I7N3G6G,260,1,no description,60.2,random -261,I7N3G6G,261,1,no description,60.2,random -262,I7N3G6G,262,1,no description,60.2,random -263,I7N3G6G,263,1,no description,60.2,random -264,I7N3G6G,264,1,no description,60.2,random -265,I7N3G6G,265,1,no description,60.2,random -266,I7N3G6G,266,1,no description,60.2,random -267,I7N3G6G,267,1,no description,60.2,random -268,I7N3G6G,268,1,no description,60.2,random -269,I7N3G6G,269,1,no description,60.2,random -270,I7N3G6G,270,1,no description,60.2,random -271,I7N3G6G,271,1,no description,60.2,random -272,I7N3G6G,272,1,no description,60.2,random -273,I7N3G6G,273,1,no description,60.2,random -274,I7N3G6G,274,1,no description,60.2,random -275,I7N3G6G,275,1,no description,60.2,random -276,I7N3G6G,276,1,no description,60.2,random -277,I7N3G6G,277,1,no description,60.2,random -278,I7N3G6G,278,1,no description,60.2,random -279,I7N3G6G,279,1,no description,60.2,random -280,I7N3G6G,280,1,no description,60.2,random -281,I7N3G6G,281,1,no description,60.2,random -282,I7N3G6G,282,1,no description,60.2,random -283,I7N3G6G,283,1,no description,60.2,random -284,I7N3G6G,284,1,no description,60.2,random -285,I7N3G6G,285,1,no description,60.2,random -286,I7N3G6G,286,1,no description,60.2,random -287,I7N3G6G,287,1,no description,60.2,random -288,I7N3G6G,288,1,no description,60.2,random -289,I7N3G6G,289,1,no description,60.2,random -290,I7N3G6G,290,1,no description,60.2,random -291,I7N3G6G,291,1,no description,60.2,random -292,I7N3G6G,292,1,no description,60.2,random -293,I7N3G6G,293,1,no description,60.2,random -294,I7N3G6G,294,1,no description,60.2,random -295,I7N3G6G,295,1,no description,60.2,random -296,I7N3G6G,296,1,no description,60.2,random -297,I7N3G6G,297,1,no description,60.2,random -298,I7N3G6G,298,1,no description,60.2,random -299,I7N3G6G,299,1,no description,60.2,random -300,I7N3G6G,300,1,no description,60.2,random -301,I7N3G6G,301,1,no description,60.2,random -302,I7N3G6G,302,1,no description,60.2,random -303,I7N3G6G,303,1,no description,60.2,random -304,I7N3G6G,304,1,no description,60.2,random -305,I7N3G6G,305,1,no description,60.2,random -306,I7N3G6G,306,1,no description,60.2,random -307,I7N3G6G,307,1,no description,60.2,random -308,I7N3G6G,308,1,no description,60.2,random -309,I7N3G6G,309,1,no description,60.2,random -310,I7N3G6G,310,1,no description,60.2,random -311,I7N3G6G,311,1,no description,60.2,random -312,I7N3G6G,312,1,no description,60.2,random -313,I7N3G6G,313,1,no description,60.2,random -314,I7N3G6G,314,1,no description,60.2,random -315,I7N3G6G,315,1,no description,60.2,random -316,I7N3G6G,316,1,no description,60.2,random -317,I7N3G6G,317,1,no description,60.2,random -318,I7N3G6G,318,1,no description,60.2,random -319,I7N3G6G,319,1,no description,60.2,random -320,I7N3G6G,320,1,no description,60.2,random -321,I7N3G6G,321,1,no description,60.2,random -322,I7N3G6G,322,1,no description,60.2,random -323,I7N3G6G,323,1,no description,60.2,random -324,I7N3G6G,324,1,no description,60.2,random -325,I7N3G6G,325,1,no description,60.2,random -326,I7N3G6G,326,1,no description,60.2,random -327,I7N3G6G,327,1,no description,60.2,random -328,I7N3G6G,328,1,no description,60.2,random -329,I7N3G6G,329,1,no description,60.2,random -330,I7N3G6G,330,1,no description,60.2,random -331,I7N3G6G,331,1,no description,60.2,random -332,I7N3G6G,332,1,no description,60.2,random -333,I7N3G6G,333,1,no description,60.2,random -334,I7N3G6G,334,1,no description,60.2,random -335,I7N3G6G,335,1,no description,60.2,random -336,I7N3G6G,336,1,no description,60.2,random -337,I7N3G6G,337,1,no description,60.2,random -338,I7N3G6G,338,1,no description,60.2,random -339,I7N3G6G,339,1,no description,60.2,random -340,I7N3G6G,340,1,no description,60.2,random -341,I7N3G6G,341,1,no description,60.2,random -342,I7N3G6G,342,1,no description,60.2,random -343,I7N3G6G,343,1,no description,60.2,random -344,I7N3G6G,344,1,no description,60.2,random -345,I7N3G6G,345,1,no description,60.2,random -346,I7N3G6G,346,1,no description,60.2,random -347,I7N3G6G,347,1,no description,60.2,random -348,I7N3G6G,348,1,no description,60.2,random -349,I7N3G6G,349,1,no description,60.2,random -350,I7N3G6G,350,1,no description,60.2,random -351,I7N3G6G,351,1,no description,60.2,random -352,I7N3G6G,352,1,no description,60.2,random -353,I7N3G6G,353,1,no description,60.2,random -354,I7N3G6G,354,1,no description,60.2,random -355,I7N3G6G,355,1,no description,60.2,random -356,I7N3G6G,356,1,no description,60.2,random -357,I7N3G6G,357,1,no description,60.2,random -358,I7N3G6G,358,1,no description,60.2,random -359,I7N3G6G,359,1,no description,60.2,random -360,I7N3G6G,360,1,no description,60.2,random -361,I7N3G6G,361,1,no description,60.2,random -362,I7N3G6G,362,1,no description,60.2,random -363,I7N3G6G,363,1,no description,60.2,random -364,I7N3G6G,364,1,no description,60.2,random -365,I7N3G6G,365,1,no description,60.2,random -366,I7N3G6G,366,1,no description,60.2,random -367,I7N3G6G,367,1,no description,60.2,random -368,I7N3G6G,368,1,no description,60.2,random -369,I7N3G6G,369,1,no description,60.2,random -370,I7N3G6G,370,1,no description,60.2,random -371,I7N3G6G,371,1,no description,60.2,random -372,I7N3G6G,372,1,no description,60.2,random -373,I7N3G6G,373,1,no description,60.2,random -374,I7N3G6G,374,1,no description,60.2,random -375,I7N3G6G,375,1,no description,60.2,random -376,I7N3G6G,376,1,no description,60.2,random -377,I7N3G6G,377,1,no description,60.2,random -378,I7N3G6G,378,1,no description,60.2,random -379,I7N3G6G,379,1,no description,60.2,random -380,I7N3G6G,380,1,no description,60.2,random -381,I7N3G6G,381,1,no description,60.2,random -382,I7N3G6G,382,1,no description,60.2,random -383,I7N3G6G,383,1,no description,60.2,random -384,I7N3G6G,384,1,no description,60.2,random -385,I7N3G6G,385,1,no description,60.2,random -386,I7N3G6G,386,1,no description,60.2,random -387,I7N3G6G,387,1,no description,60.2,random -388,I7N3G6G,388,1,no description,60.2,random -389,I7N3G6G,389,1,no description,60.2,random -390,I7N3G6G,390,1,no description,60.2,random -391,I7N3G6G,391,1,no description,60.2,random -392,I7N3G6G,392,1,no description,60.2,random -393,I7N3G6G,393,1,no description,60.2,random -394,I7N3G6G,394,1,no description,60.2,random -395,I7N3G6G,395,1,no description,60.2,random -396,I7N3G6G,396,1,no description,60.2,random -397,I7N3G6G,397,1,no description,60.2,random -398,I7N3G6G,398,1,no description,60.2,random -399,I7N3G6G,399,1,no description,60.2,random -400,I7N3G6G,400,1,no description,60.2,random -401,I7N3G6G,401,1,no description,60.2,random -402,I7N3G6G,402,1,no description,60.2,random -403,I7N3G6G,403,1,no description,60.2,random -404,I7N3G6G,404,1,no description,60.2,random -405,I7N3G6G,405,1,no description,60.2,random -406,I7N3G6G,406,1,no description,60.2,random -407,I7N3G6G,407,1,no description,60.2,random -408,I7N3G6G,408,1,no description,60.2,random -409,I7N3G6G,409,1,no description,60.2,random -410,I7N3G6G,410,1,no description,60.2,random -411,I7N3G6G,411,1,no description,60.2,random -412,I7N3G6G,412,1,no description,60.2,random -413,I7N3G6G,413,1,no description,60.2,random -414,I7N3G6G,414,1,no description,60.2,random -415,I7N3G6G,415,1,no description,60.2,random -416,I7N3G6G,416,1,no description,60.2,random -417,I7N3G6G,417,1,no description,60.2,random -418,I7N3G6G,418,1,no description,60.2,random -419,I7N3G6G,419,1,no description,60.2,random -420,I7N3G6G,420,1,no description,60.2,random -421,I7N3G6G,421,1,no description,60.2,random -422,I7N3G6G,422,1,no description,60.2,random -423,I7N3G6G,423,1,no description,60.2,random -424,I7N3G6G,424,1,no description,60.2,random -425,I7N3G6G,425,1,no description,60.2,random -426,I7N3G6G,426,1,no description,60.2,random -427,I7N3G6G,427,1,no description,60.2,random -428,I7N3G6G,428,1,no description,60.2,random -429,I7N3G6G,429,1,no description,60.2,random -430,I7N3G6G,430,1,no description,60.2,random -431,I7N3G6G,431,1,no description,60.2,random -432,I7N3G6G,432,1,no description,60.2,random -433,I7N3G6G,433,1,no description,60.2,random -434,I7N3G6G,434,1,no description,60.2,random -435,I7N3G6G,435,1,no description,60.2,random -436,I7N3G6G,436,1,no description,60.2,random -437,I7N3G6G,437,1,no description,60.2,random -438,I7N3G6G,438,1,no description,60.2,random -439,I7N3G6G,439,1,no description,60.2,random -440,I7N3G6G,440,1,no description,60.2,random -441,I7N3G6G,441,1,no description,60.2,random -442,I7N3G6G,442,1,no description,60.2,random -443,I7N3G6G,443,1,no description,60.2,random -444,I7N3G6G,444,1,no description,60.2,random -445,I7N3G6G,445,1,no description,60.2,random -446,I7N3G6G,446,1,no description,60.2,random -447,I7N3G6G,447,1,no description,60.2,random -448,I7N3G6G,448,1,no description,60.2,random -449,I7N3G6G,449,1,no description,60.2,random -450,I7N3G6G,450,1,no description,60.2,random -451,I7N3G6G,451,1,no description,60.2,random -452,I7N3G6G,452,1,no description,60.2,random -453,I7N3G6G,453,1,no description,60.2,random -454,I7N3G6G,454,1,no description,60.2,random -455,I7N3G6G,455,1,no description,60.2,random -456,I7N3G6G,456,1,no description,60.2,random -457,I7N3G6G,457,1,no description,60.2,random -458,I7N3G6G,458,1,no description,60.2,random -459,I7N3G6G,459,1,no description,60.2,random -460,I7N3G6G,460,1,no description,60.2,random -461,I7N3G6G,461,1,no description,60.2,random -462,I7N3G6G,462,1,no description,60.2,random -463,I7N3G6G,463,1,no description,60.2,random -464,I7N3G6G,464,1,no description,60.2,random -465,I7N3G6G,465,1,no description,60.2,random -466,I7N3G6G,466,1,no description,60.2,random -467,I7N3G6G,467,1,no description,60.2,random -468,I7N3G6G,468,1,no description,60.2,random -469,I7N3G6G,469,1,no description,60.2,random -470,I7N3G6G,470,1,no description,60.2,random -471,I7N3G6G,471,1,no description,60.2,random -472,I7N3G6G,472,1,no description,60.2,random -473,I7N3G6G,473,1,no description,60.2,random -474,I7N3G6G,474,1,no description,60.2,random -475,I7N3G6G,475,1,no description,60.2,random -476,I7N3G6G,476,1,no description,60.2,random -477,I7N3G6G,477,1,no description,60.2,random -478,I7N3G6G,478,1,no description,60.2,random -479,I7N3G6G,479,1,no description,60.2,random -480,I7N3G6G,480,1,no description,60.2,random -481,I7N3G6G,481,1,no description,60.2,random -482,I7N3G6G,482,1,no description,60.2,random -483,I7N3G6G,483,1,no description,60.2,random -484,I7N3G6G,484,1,no description,60.2,random -485,I7N3G6G,485,1,no description,60.2,random -486,I7N3G6G,486,1,no description,60.2,random -487,I7N3G6G,487,1,no description,60.2,random -488,I7N3G6G,488,1,no description,60.2,random -489,I7N3G6G,489,1,no description,60.2,random -490,I7N3G6G,490,1,no description,60.2,random -491,I7N3G6G,491,1,no description,60.2,random -492,I7N3G6G,492,1,no description,60.2,random -493,I7N3G6G,493,1,no description,60.2,random -494,I7N3G6G,494,1,no description,60.2,random -495,I7N3G6G,495,1,no description,60.2,random -496,I7N3G6G,496,1,no description,60.2,random -497,I7N3G6G,497,1,no description,60.2,random -498,I7N3G6G,498,1,no description,60.2,random -499,I7N3G6G,499,1,no description,60.2,random -500,I7N3G6G,500,1,no description,60.2,random -501,I7N3G6G,501,1,no description,60.2,random -502,I7N3G6G,502,1,no description,60.2,random -503,I7N3G6G,503,1,no description,60.2,random -504,I7N3G6G,504,1,no description,60.2,random -505,I7N3G6G,505,1,no description,60.2,random -506,I7N3G6G,506,1,no description,60.2,random -507,I7N3G6G,507,1,no description,60.2,random -508,I7N3G6G,508,1,no description,60.2,random -509,I7N3G6G,509,1,no description,60.2,random -510,I7N3G6G,510,1,no description,60.2,random -511,I7N3G6G,511,1,no description,60.2,random -512,I7N3G6G,512,1,no description,60.2,random -513,I7N3G6G,513,1,no description,60.2,random -514,I7N3G6G,514,1,no description,60.2,random -515,I7N3G6G,515,1,no description,60.2,random -516,I7N3G6G,516,1,no description,60.2,random -517,I7N3G6G,517,1,no description,60.2,random -518,I7N3G6G,518,1,no description,60.2,random -519,I7N3G6G,519,1,no description,60.2,random -520,I7N3G6G,520,1,no description,60.2,random -521,I7N3G6G,521,1,no description,60.2,random -522,I7N3G6G,522,1,no description,60.2,random -523,I7N3G6G,523,1,no description,60.2,random -524,I7N3G6G,524,1,no description,60.2,random -525,I7N3G6G,525,1,no description,60.2,random -526,I7N3G6G,526,1,no description,60.2,random -527,I7N3G6G,527,1,no description,60.2,random -528,I7N3G6G,528,1,no description,60.2,random -529,I7N3G6G,529,1,no description,60.2,random -530,I7N3G6G,530,1,no description,60.2,random -531,I7N3G6G,531,1,no description,60.2,random -532,I7N3G6G,532,1,no description,60.2,random -533,I7N3G6G,533,1,no description,60.2,random -534,I7N3G6G,534,1,no description,60.2,random -535,I7N3G6G,535,1,no description,60.2,random -536,I7N3G6G,536,1,no description,60.2,random -537,I7N3G6G,537,1,no description,60.2,random -538,I7N3G6G,538,1,no description,60.2,random -539,I7N3G6G,539,1,no description,60.2,random -540,I7N3G6G,540,1,no description,60.2,random -541,I7N3G6G,541,1,no description,60.2,random -542,I7N3G6G,542,1,no description,60.2,random -543,I7N3G6G,543,1,no description,60.2,random -544,I7N3G6G,544,1,no description,60.2,random -545,I7N3G6G,545,1,no description,60.2,random -546,I7N3G6G,546,1,no description,60.2,random -547,I7N3G6G,547,1,no description,60.2,random -548,I7N3G6G,548,1,no description,60.2,random -549,I7N3G6G,549,1,no description,60.2,random -550,I7N3G6G,550,1,no description,60.2,random -551,I7N3G6G,551,1,no description,60.2,random -552,I7N3G6G,552,1,no description,60.2,random -553,I7N3G6G,553,1,no description,60.2,random -554,I7N3G6G,554,1,no description,60.2,random -555,I7N3G6G,555,1,no description,60.2,random -556,I7N3G6G,556,1,no description,60.2,random -557,I7N3G6G,557,1,no description,60.2,random -558,I7N3G6G,558,1,no description,60.2,random -559,I7N3G6G,559,1,no description,60.2,random -560,I7N3G6G,560,1,no description,60.2,random -561,I7N3G6G,561,1,no description,60.2,random -562,I7N3G6G,562,1,no description,60.2,random -563,I7N3G6G,563,1,no description,60.2,random -564,I7N3G6G,564,1,no description,60.2,random -565,I7N3G6G,565,1,no description,60.2,random -566,I7N3G6G,566,1,no description,60.2,random -567,I7N3G6G,567,1,no description,60.2,random -568,I7N3G6G,568,1,no description,60.2,random -569,I7N3G6G,569,1,no description,60.2,random -570,I7N3G6G,570,1,no description,60.2,random -571,I7N3G6G,571,1,no description,60.2,random -572,I7N3G6G,572,1,no description,60.2,random -573,I7N3G6G,573,1,no description,60.2,random -574,I7N3G6G,574,1,no description,60.2,random -575,I7N3G6G,575,1,no description,60.2,random -576,I7N3G6G,576,1,no description,60.2,random -577,I7N3G6G,577,1,no description,60.2,random -578,I7N3G6G,578,1,no description,60.2,random -579,I7N3G6G,579,1,no description,60.2,random -580,I7N3G6G,580,1,no description,60.2,random -581,I7N3G6G,581,1,no description,60.2,random -582,I7N3G6G,582,1,no description,60.2,random -583,I7N3G6G,583,1,no description,60.2,random -584,I7N3G6G,584,1,no description,60.2,random -585,I7N3G6G,585,1,no description,60.2,random -586,I7N3G6G,586,1,no description,60.2,random -587,I7N3G6G,587,1,no description,60.2,random -588,I7N3G6G,588,1,no description,60.2,random -589,I7N3G6G,589,1,no description,60.2,random -590,I7N3G6G,590,1,no description,60.2,random -591,I7N3G6G,591,1,no description,60.2,random -592,I7N3G6G,592,1,no description,60.2,random -593,I7N3G6G,593,1,no description,60.2,random -594,I7N3G6G,594,1,no description,60.2,random -595,I7N3G6G,595,1,no description,60.2,random -596,I7N3G6G,596,1,no description,60.2,random -597,I7N3G6G,597,1,no description,60.2,random -598,I7N3G6G,598,1,no description,60.2,random -599,I7N3G6G,599,1,no description,60.2,random -600,I7N3G6G,600,1,no description,60.2,random -601,I7N3G6G,601,1,no description,60.2,random -602,I7N3G6G,602,1,no description,60.2,random -603,I7N3G6G,603,1,no description,60.2,random -604,I7N3G6G,604,1,no description,60.2,random -605,I7N3G6G,605,1,no description,60.2,random -606,I7N3G6G,606,1,no description,60.2,random -607,I7N3G6G,607,1,no description,60.2,random -608,I7N3G6G,608,1,no description,60.2,random -609,I7N3G6G,609,1,no description,60.2,random -610,I7N3G6G,610,1,no description,60.2,random -611,I7N3G6G,611,1,no description,60.2,random -612,I7N3G6G,612,1,no description,60.2,random -613,I7N3G6G,613,1,no description,60.2,random -614,I7N3G6G,614,1,no description,60.2,random -615,I7N3G6G,615,1,no description,60.2,random -616,I7N3G6G,616,1,no description,60.2,random -617,I7N3G6G,617,1,no description,60.2,random -618,I7N3G6G,618,1,no description,60.2,random -619,I7N3G6G,619,1,no description,60.2,random -620,I7N3G6G,620,1,no description,60.2,random -621,I7N3G6G,621,1,no description,60.2,random -622,I7N3G6G,622,1,no description,60.2,random -623,I7N3G6G,623,1,no description,60.2,random -624,I7N3G6G,624,1,no description,60.2,random -625,I7N3G6G,625,1,no description,60.2,random -626,I7N3G6G,626,1,no description,60.2,random -627,I7N3G6G,627,1,no description,60.2,random -628,I7N3G6G,628,1,no description,60.2,random -629,I7N3G6G,629,1,no description,60.2,random -630,I7N3G6G,630,1,no description,60.2,random -631,I7N3G6G,631,1,no description,60.2,random -632,I7N3G6G,632,1,no description,60.2,random -633,I7N3G6G,633,1,no description,60.2,random -634,I7N3G6G,634,1,no description,60.2,random -635,I7N3G6G,635,1,no description,60.2,random -636,I7N3G6G,636,1,no description,60.2,random -637,I7N3G6G,637,1,no description,60.2,random -638,I7N3G6G,638,1,no description,60.2,random -639,I7N3G6G,639,1,no description,60.2,random -640,I7N3G6G,640,1,no description,60.2,random -641,I7N3G6G,641,1,no description,60.2,random -642,I7N3G6G,642,1,no description,60.2,random -643,I7N3G6G,643,1,no description,60.2,random -644,I7N3G6G,644,1,no description,60.2,random -645,I7N3G6G,645,1,no description,60.2,random -646,I7N3G6G,646,1,no description,60.2,random -647,I7N3G6G,647,1,no description,60.2,random -648,I7N3G6G,648,1,no description,60.2,random -649,I7N3G6G,649,1,no description,60.2,random -650,I7N3G6G,650,1,no description,60.2,random -651,I7N3G6G,651,1,no description,60.2,random -652,I7N3G6G,652,1,no description,60.2,random -653,I7N3G6G,653,1,no description,60.2,random -654,I7N3G6G,654,1,no description,60.2,random -655,I7N3G6G,655,1,no description,60.2,random -656,I7N3G6G,656,1,no description,60.2,random -657,I7N3G6G,657,1,no description,60.2,random -658,I7N3G6G,658,1,no description,60.2,random -659,I7N3G6G,659,1,no description,60.2,random -660,I7N3G6G,660,1,no description,60.2,random -661,I7N3G6G,661,1,no description,60.2,random -662,I7N3G6G,662,1,no description,60.2,random -663,I7N3G6G,663,1,no description,60.2,random -664,I7N3G6G,664,1,no description,60.2,random -665,I7N3G6G,665,1,no description,60.2,random -666,I7N3G6G,666,1,no description,60.2,random -667,I7N3G6G,667,1,no description,60.2,random -668,I7N3G6G,668,1,no description,60.2,random -669,I7N3G6G,669,1,no description,60.2,random -670,I7N3G6G,670,1,no description,60.2,random -671,I7N3G6G,671,1,no description,60.2,random -672,I7N3G6G,672,1,no description,60.2,random -673,I7N3G6G,673,1,no description,60.2,random -674,I7N3G6G,674,1,no description,60.2,random -675,I7N3G6G,675,1,no description,60.2,random -676,I7N3G6G,676,1,no description,60.2,random -677,I7N3G6G,677,1,no description,60.2,random -678,I7N3G6G,678,1,no description,60.2,random -679,I7N3G6G,679,1,no description,60.2,random -680,I7N3G6G,680,1,no description,60.2,random -681,I7N3G6G,681,1,no description,60.2,random -682,I7N3G6G,682,1,no description,60.2,random -683,I7N3G6G,683,1,no description,60.2,random -684,I7N3G6G,684,1,no description,60.2,random -685,I7N3G6G,685,1,no description,60.2,random -686,I7N3G6G,686,1,no description,60.2,random -687,I7N3G6G,687,1,no description,60.2,random -688,I7N3G6G,688,1,no description,60.2,random -689,I7N3G6G,689,1,no description,60.2,random -690,I7N3G6G,690,1,no description,60.2,random -691,I7N3G6G,691,1,no description,60.2,random -692,I7N3G6G,692,1,no description,60.2,random -693,I7N3G6G,693,1,no description,60.2,random -694,I7N3G6G,694,1,no description,60.2,random -695,I7N3G6G,695,1,no description,60.2,random -696,I7N3G6G,696,1,no description,60.2,random -697,I7N3G6G,697,1,no description,60.2,random -698,I7N3G6G,698,1,no description,60.2,random -699,I7N3G6G,699,1,no description,60.2,random -700,I7N3G6G,700,1,no description,60.2,random -701,I7N3G6G,701,1,no description,60.2,random -702,I7N3G6G,702,1,no description,60.2,random -703,I7N3G6G,703,1,no description,60.2,random -704,I7N3G6G,704,1,no description,60.2,random -705,I7N3G6G,705,1,no description,60.2,random -706,I7N3G6G,706,1,no description,60.2,random -707,I7N3G6G,707,1,no description,60.2,random -708,I7N3G6G,708,1,no description,60.2,random -709,I7N3G6G,709,1,no description,60.2,random -710,I7N3G6G,710,1,no description,60.2,random -711,I7N3G6G,711,1,no description,60.2,random -712,I7N3G6G,712,1,no description,60.2,random -713,I7N3G6G,713,1,no description,60.2,random -714,I7N3G6G,714,1,no description,60.2,random -715,I7N3G6G,715,1,no description,60.2,random -716,I7N3G6G,716,1,no description,60.2,random -717,I7N3G6G,717,1,no description,60.2,random -718,I7N3G6G,718,1,no description,60.2,random -719,I7N3G6G,719,1,no description,60.2,random -720,I7N3G6G,720,1,no description,60.2,random -721,I7N3G6G,721,1,no description,60.2,random -722,I7N3G6G,722,1,no description,60.2,random -723,I7N3G6G,723,1,no description,60.2,random -724,I7N3G6G,724,1,no description,60.2,random -725,I7N3G6G,725,1,no description,60.2,random -726,I7N3G6G,726,1,no description,60.2,random -727,I7N3G6G,727,1,no description,60.2,random -728,I7N3G6G,728,1,no description,60.2,random -729,I7N3G6G,729,1,no description,60.2,random -730,I7N3G6G,730,1,no description,60.2,random -731,I7N3G6G,731,1,no description,60.2,random -732,I7N3G6G,732,1,no description,60.2,random -733,I7N3G6G,733,1,no description,60.2,random -734,I7N3G6G,734,1,no description,60.2,random -735,I7N3G6G,735,1,no description,60.2,random -736,I7N3G6G,736,1,no description,60.2,random -737,I7N3G6G,737,1,no description,60.2,random -738,I7N3G6G,738,1,no description,60.2,random -739,I7N3G6G,739,1,no description,60.2,random -740,I7N3G6G,740,1,no description,60.2,random -741,I7N3G6G,741,1,no description,60.2,random -742,I7N3G6G,742,1,no description,60.2,random -743,I7N3G6G,743,1,no description,60.2,random -744,I7N3G6G,744,1,no description,60.2,random -745,I7N3G6G,745,1,no description,60.2,random -746,I7N3G6G,746,1,no description,60.2,random -747,I7N3G6G,747,1,no description,60.2,random -748,I7N3G6G,748,1,no description,60.2,random -749,I7N3G6G,749,1,no description,60.2,random -750,I7N3G6G,750,1,no description,60.2,random -751,I7N3G6G,751,1,no description,60.2,random -752,I7N3G6G,752,1,no description,60.2,random -753,I7N3G6G,753,1,no description,60.2,random -754,I7N3G6G,754,1,no description,60.2,random -755,I7N3G6G,755,1,no description,60.2,random -756,I7N3G6G,756,1,no description,60.2,random -757,I7N3G6G,757,1,no description,60.2,random -758,I7N3G6G,758,1,no description,60.2,random -759,I7N3G6G,759,1,no description,60.2,random -760,I7N3G6G,760,1,no description,60.2,random -761,I7N3G6G,761,1,no description,60.2,random -762,I7N3G6G,762,1,no description,60.2,random -763,I7N3G6G,763,1,no description,60.2,random -764,I7N3G6G,764,1,no description,60.2,random -765,I7N3G6G,765,1,no description,60.2,random -766,I7N3G6G,766,1,no description,60.2,random -767,I7N3G6G,767,1,no description,60.2,random -768,I7N3G6G,768,1,no description,60.2,random -769,I7N3G6G,769,1,no description,60.2,random -770,I7N3G6G,770,1,no description,60.2,random -771,I7N3G6G,771,1,no description,60.2,random -772,I7N3G6G,772,1,no description,60.2,random -773,I7N3G6G,773,1,no description,60.2,random -774,I7N3G6G,774,1,no description,60.2,random -775,I7N3G6G,775,1,no description,60.2,random -776,I7N3G6G,776,1,no description,60.2,random -777,I7N3G6G,777,1,no description,60.2,random -778,I7N3G6G,778,1,no description,60.2,random -779,I7N3G6G,779,1,no description,60.2,random -780,I7N3G6G,780,1,no description,60.2,random -781,I7N3G6G,781,1,no description,60.2,random -782,I7N3G6G,782,1,no description,60.2,random -783,I7N3G6G,783,1,no description,60.2,random -784,I7N3G6G,784,1,no description,60.2,random -785,I7N3G6G,785,1,no description,60.2,random -786,I7N3G6G,786,1,no description,60.2,random -787,I7N3G6G,787,1,no description,60.2,random -788,I7N3G6G,788,1,no description,60.2,random -789,I7N3G6G,789,1,no description,60.2,random -790,I7N3G6G,790,1,no description,60.2,random -791,I7N3G6G,791,1,no description,60.2,random -792,I7N3G6G,792,1,no description,60.2,random -793,I7N3G6G,793,1,no description,60.2,random -794,I7N3G6G,794,1,no description,60.2,random -795,I7N3G6G,795,1,no description,60.2,random -796,I7N3G6G,796,1,no description,60.2,random -797,I7N3G6G,797,1,no description,60.2,random -798,I7N3G6G,798,1,no description,60.2,random -799,I7N3G6G,799,1,no description,60.2,random -800,I7N3G6G,800,1,no description,60.2,random -801,I7N3G6G,801,1,no description,60.2,random -802,I7N3G6G,802,1,no description,60.2,random -803,I7N3G6G,803,1,no description,60.2,random -804,I7N3G6G,804,1,no description,60.2,random -805,I7N3G6G,805,1,no description,60.2,random -806,I7N3G6G,806,1,no description,60.2,random -807,I7N3G6G,807,1,no description,60.2,random -808,I7N3G6G,808,1,no description,60.2,random -809,I7N3G6G,809,1,no description,60.2,random -810,I7N3G6G,810,1,no description,60.2,random -811,I7N3G6G,811,1,no description,60.2,random -812,I7N3G6G,812,1,no description,60.2,random -813,I7N3G6G,813,1,no description,60.2,random -814,I7N3G6G,814,1,no description,60.2,random -815,I7N3G6G,815,1,no description,60.2,random -816,I7N3G6G,816,1,no description,60.2,random -817,I7N3G6G,817,1,no description,60.2,random -818,I7N3G6G,818,1,no description,60.2,random -819,I7N3G6G,819,1,no description,60.2,random -820,I7N3G6G,820,1,no description,60.2,random -821,I7N3G6G,821,1,no description,60.2,random -822,I7N3G6G,822,1,no description,60.2,random -823,I7N3G6G,823,1,no description,60.2,random -824,I7N3G6G,824,1,no description,60.2,random -825,I7N3G6G,825,1,no description,60.2,random -826,I7N3G6G,826,1,no description,60.2,random -827,I7N3G6G,827,1,no description,60.2,random -828,I7N3G6G,828,1,no description,60.2,random -829,I7N3G6G,829,1,no description,60.2,random -830,I7N3G6G,830,1,no description,60.2,random -831,I7N3G6G,831,1,no description,60.2,random -832,I7N3G6G,832,1,no description,60.2,random -833,I7N3G6G,833,1,no description,60.2,random -834,I7N3G6G,834,1,no description,60.2,random -835,I7N3G6G,835,1,no description,60.2,random -836,I7N3G6G,836,1,no description,60.2,random -837,I7N3G6G,837,1,no description,60.2,random -838,I7N3G6G,838,1,no description,60.2,random -839,I7N3G6G,839,1,no description,60.2,random -840,I7N3G6G,840,1,no description,60.2,random -841,I7N3G6G,841,1,no description,60.2,random -842,I7N3G6G,842,1,no description,60.2,random -843,I7N3G6G,843,1,no description,60.2,random -844,I7N3G6G,844,1,no description,60.2,random -845,I7N3G6G,845,1,no description,60.2,random -846,I7N3G6G,846,1,no description,60.2,random -847,I7N3G6G,847,1,no description,60.2,random -848,I7N3G6G,848,1,no description,60.2,random -849,I7N3G6G,849,1,no description,60.2,random -850,I7N3G6G,850,1,no description,60.2,random -851,I7N3G6G,851,1,no description,60.2,random -852,I7N3G6G,852,1,no description,60.2,random -853,I7N3G6G,853,1,no description,60.2,random -854,I7N3G6G,854,1,no description,60.2,random -855,I7N3G6G,855,1,no description,60.2,random -856,I7N3G6G,856,1,no description,60.2,random -857,I7N3G6G,857,1,no description,60.2,random -858,I7N3G6G,858,1,no description,60.2,random -859,I7N3G6G,859,1,no description,60.2,random -860,I7N3G6G,860,1,no description,60.2,random -861,I7N3G6G,861,1,no description,60.2,random -862,I7N3G6G,862,1,no description,60.2,random -863,I7N3G6G,863,1,no description,60.2,random -864,I7N3G6G,864,1,no description,60.2,random -865,I7N3G6G,865,1,no description,60.2,random -866,I7N3G6G,866,1,no description,60.2,random -867,I7N3G6G,867,1,no description,60.2,random -868,I7N3G6G,868,1,no description,60.2,random -869,I7N3G6G,869,1,no description,60.2,random -870,I7N3G6G,870,1,no description,60.2,random -871,I7N3G6G,871,1,no description,60.2,random -872,I7N3G6G,872,1,no description,60.2,random -873,I7N3G6G,873,1,no description,60.2,random -874,I7N3G6G,874,1,no description,60.2,random -875,I7N3G6G,875,1,no description,60.2,random -876,I7N3G6G,876,1,no description,60.2,random -877,I7N3G6G,877,1,no description,60.2,random -878,I7N3G6G,878,1,no description,60.2,random -879,I7N3G6G,879,1,no description,60.2,random -880,I7N3G6G,880,1,no description,60.2,random -881,I7N3G6G,881,1,no description,60.2,random -882,I7N3G6G,882,1,no description,60.2,random -883,I7N3G6G,883,1,no description,60.2,random -884,I7N3G6G,884,1,no description,60.2,random -885,I7N3G6G,885,1,no description,60.2,random -886,I7N3G6G,886,1,no description,60.2,random -887,I7N3G6G,887,1,no description,60.2,random -888,I7N3G6G,888,1,no description,60.2,random -889,I7N3G6G,889,1,no description,60.2,random -890,I7N3G6G,890,1,no description,60.2,random -891,I7N3G6G,891,1,no description,60.2,random -892,I7N3G6G,892,1,no description,60.2,random -893,I7N3G6G,893,1,no description,60.2,random -894,I7N3G6G,894,1,no description,60.2,random -895,I7N3G6G,895,1,no description,60.2,random -896,I7N3G6G,896,1,no description,60.2,random -897,I7N3G6G,897,1,no description,60.2,random -898,I7N3G6G,898,1,no description,60.2,random -899,I7N3G6G,899,1,no description,60.2,random -900,I7N3G6G,900,1,no description,60.2,random -901,I7N3G6G,901,1,no description,60.2,random -902,I7N3G6G,902,1,no description,60.2,random -903,I7N3G6G,903,1,no description,60.2,random -904,I7N3G6G,904,1,no description,60.2,random -905,I7N3G6G,905,1,no description,60.2,random -906,I7N3G6G,906,1,no description,60.2,random -907,I7N3G6G,907,1,no description,60.2,random -908,I7N3G6G,908,1,no description,60.2,random -909,I7N3G6G,909,1,no description,60.2,random -910,I7N3G6G,910,1,no description,60.2,random -911,I7N3G6G,911,1,no description,60.2,random -912,I7N3G6G,912,1,no description,60.2,random -913,I7N3G6G,913,1,no description,60.2,random -914,I7N3G6G,914,1,no description,60.2,random -915,I7N3G6G,915,1,no description,60.2,random -916,I7N3G6G,916,1,no description,60.2,random -917,I7N3G6G,917,1,no description,60.2,random -918,I7N3G6G,918,1,no description,60.2,random -919,I7N3G6G,919,1,no description,60.2,random -920,I7N3G6G,920,1,no description,60.2,random -921,I7N3G6G,921,1,no description,60.2,random -922,I7N3G6G,922,1,no description,60.2,random -923,I7N3G6G,923,1,no description,60.2,random -924,I7N3G6G,924,1,no description,60.2,random -925,I7N3G6G,925,1,no description,60.2,random -926,I7N3G6G,926,1,no description,60.2,random -927,I7N3G6G,927,1,no description,60.2,random -928,I7N3G6G,928,1,no description,60.2,random -929,I7N3G6G,929,1,no description,60.2,random -930,I7N3G6G,930,1,no description,60.2,random -931,I7N3G6G,931,1,no description,60.2,random -932,I7N3G6G,932,1,no description,60.2,random -933,I7N3G6G,933,1,no description,60.2,random -934,I7N3G6G,934,1,no description,60.2,random -935,I7N3G6G,935,1,no description,60.2,random -936,I7N3G6G,936,1,no description,60.2,random -937,I7N3G6G,937,1,no description,60.2,random -938,I7N3G6G,938,1,no description,60.2,random -939,I7N3G6G,939,1,no description,60.2,random -940,I7N3G6G,940,1,no description,60.2,random -941,I7N3G6G,941,1,no description,60.2,random -942,I7N3G6G,942,1,no description,60.2,random -943,I7N3G6G,943,1,no description,60.2,random -944,I7N3G6G,944,1,no description,60.2,random -945,I7N3G6G,945,1,no description,60.2,random -946,I7N3G6G,946,1,no description,60.2,random -947,I7N3G6G,947,1,no description,60.2,random -948,I7N3G6G,948,1,no description,60.2,random -949,I7N3G6G,949,1,no description,60.2,random -950,I7N3G6G,950,1,no description,60.2,random -951,I7N3G6G,951,1,no description,60.2,random -952,I7N3G6G,952,1,no description,60.2,random -953,I7N3G6G,953,1,no description,60.2,random -954,I7N3G6G,954,1,no description,60.2,random -955,I7N3G6G,955,1,no description,60.2,random -956,I7N3G6G,956,1,no description,60.2,random -957,I7N3G6G,957,1,no description,60.2,random -958,I7N3G6G,958,1,no description,60.2,random -959,I7N3G6G,959,1,no description,60.2,random -960,I7N3G6G,960,1,no description,60.2,random -961,I7N3G6G,961,1,no description,60.2,random -962,I7N3G6G,962,1,no description,60.2,random -963,I7N3G6G,963,1,no description,60.2,random -964,I7N3G6G,964,1,no description,60.2,random -965,I7N3G6G,965,1,no description,60.2,random -966,I7N3G6G,966,1,no description,60.2,random -967,I7N3G6G,967,1,no description,60.2,random -968,I7N3G6G,968,1,no description,60.2,random -969,I7N3G6G,969,1,no description,60.2,random -970,I7N3G6G,970,1,no description,60.2,random -971,I7N3G6G,971,1,no description,60.2,random -972,I7N3G6G,972,1,no description,60.2,random -973,I7N3G6G,973,1,no description,60.2,random -974,I7N3G6G,974,1,no description,60.2,random -975,I7N3G6G,975,1,no description,60.2,random -976,I7N3G6G,976,1,no description,60.2,random -977,I7N3G6G,977,1,no description,60.2,random -978,I7N3G6G,978,1,no description,60.2,random -979,I7N3G6G,979,1,no description,60.2,random -980,I7N3G6G,980,1,no description,60.2,random -981,I7N3G6G,981,1,no description,60.2,random -982,I7N3G6G,982,1,no description,60.2,random -983,I7N3G6G,983,1,no description,60.2,random -984,I7N3G6G,984,1,no description,60.2,random -985,I7N3G6G,985,1,no description,60.2,random -986,I7N3G6G,986,1,no description,60.2,random -987,I7N3G6G,987,1,no description,60.2,random -988,I7N3G6G,988,1,no description,60.2,random -989,I7N3G6G,989,1,no description,60.2,random -990,I7N3G6G,990,1,no description,60.2,random -991,I7N3G6G,991,1,no description,60.2,random -992,I7N3G6G,992,1,no description,60.2,random -993,I7N3G6G,993,1,no description,60.2,random -994,I7N3G6G,994,1,no description,60.2,random -995,I7N3G6G,995,1,no description,60.2,random -996,I7N3G6G,996,1,no description,60.2,random -997,I7N3G6G,997,1,no description,60.2,random -998,I7N3G6G,998,1,no description,60.2,random -999,I7N3G6G,999,1,no description,60.2,random -1000,I7N3G6G,1000,1,no description,60.2,random -1001,I7N3G6G,1001,1,no description,60.2,random -1002,I7N3G6G,1002,1,no description,60.2,random -1003,I7N3G6G,1003,1,no description,60.2,random -1004,I7N3G6G,1004,1,no description,60.2,random -1005,I7N3G6G,1005,1,no description,60.2,random -1006,I7N3G6G,1006,1,no description,60.2,random -1007,I7N3G6G,1007,1,no description,60.2,random -1008,I7N3G6G,1008,1,no description,60.2,random -1009,I7N3G6G,1009,1,no description,60.2,random -1010,I7N3G6G,1010,1,no description,60.2,random -1011,I7N3G6G,1011,1,no description,60.2,random -1012,I7N3G6G,1012,1,no description,60.2,random -1013,I7N3G6G,1013,1,no description,60.2,random -1014,I7N3G6G,1014,1,no description,60.2,random -1015,I7N3G6G,1015,1,no description,60.2,random -1016,I7N3G6G,1016,1,no description,60.2,random -1017,I7N3G6G,1017,1,no description,60.2,random -1018,I7N3G6G,1018,1,no description,60.2,random -1019,I7N3G6G,1019,1,no description,60.2,random -1020,I7N3G6G,1020,1,no description,60.2,random -1021,I7N3G6G,1021,1,no description,60.2,random -1022,I7N3G6G,1022,1,no description,60.2,random -1023,I7N3G6G,1023,1,no description,60.2,random -1024,I7N3G6G,1024,1,no description,60.2,random -1025,I7N3G6G,1025,1,no description,60.2,random -1026,I7N3G6G,1026,1,no description,60.2,random -1027,I7N3G6G,1027,1,no description,60.2,random -1028,I7N3G6G,1028,1,no description,60.2,random -1029,I7N3G6G,1029,1,no description,60.2,random -1030,I7N3G6G,1030,1,no description,60.2,random -1031,I7N3G6G,1031,1,no description,60.2,random -1032,I7N3G6G,1032,1,no description,60.2,random -1033,I7N3G6G,1033,1,no description,60.2,random -1034,I7N3G6G,1034,1,no description,60.2,random -1035,I7N3G6G,1035,1,no description,60.2,random -1036,I7N3G6G,1036,1,no description,60.2,random -1037,I7N3G6G,1037,1,no description,60.2,random -1038,I7N3G6G,1038,1,no description,60.2,random -1039,I7N3G6G,1039,1,no description,60.2,random -1040,I7N3G6G,1040,1,no description,60.2,random -1041,I7N3G6G,1041,1,no description,60.2,random -1042,I7N3G6G,1042,1,no description,60.2,random -1043,I7N3G6G,1043,1,no description,60.2,random -1044,I7N3G6G,1044,1,no description,60.2,random -1045,I7N3G6G,1045,1,no description,60.2,random -1046,I7N3G6G,1046,1,no description,60.2,random -1047,I7N3G6G,1047,1,no description,60.2,random -1048,I7N3G6G,1048,1,no description,60.2,random -1049,I7N3G6G,1049,1,no description,60.2,random -1050,I7N3G6G,1050,1,no description,60.2,random -1051,I7N3G6G,1051,1,no description,60.2,random -1052,I7N3G6G,1052,1,no description,60.2,random -1053,I7N3G6G,1053,1,no description,60.2,random -1054,I7N3G6G,1054,1,no description,60.2,random -1055,I7N3G6G,1055,1,no description,60.2,random -1056,I7N3G6G,1056,1,no description,60.2,random -1057,I7N3G6G,1057,1,no description,60.2,random -1058,I7N3G6G,1058,1,no description,60.2,random -1059,I7N3G6G,1059,1,no description,60.2,random -1060,I7N3G6G,1060,1,no description,60.2,random -1061,I7N3G6G,1061,1,no description,60.2,random -1062,I7N3G6G,1062,1,no description,60.2,random -1063,I7N3G6G,1063,1,no description,60.2,random -1064,I7N3G6G,1064,1,no description,60.2,random -1065,I7N3G6G,1065,1,no description,60.2,random -1066,I7N3G6G,1066,1,no description,60.2,random -1067,I7N3G6G,1067,1,no description,60.2,random -1068,I7N3G6G,1068,1,no description,60.2,random -1069,I7N3G6G,1069,1,no description,60.2,random -1070,I7N3G6G,1070,1,no description,60.2,random -1071,I7N3G6G,1071,1,no description,60.2,random -1072,I7N3G6G,1072,1,no description,60.2,random -1073,I7N3G6G,1073,1,no description,60.2,random -1074,I7N3G6G,1074,1,no description,60.2,random -1075,I7N3G6G,1075,1,no description,60.2,random -1076,I7N3G6G,1076,1,no description,60.2,random -1077,I7N3G6G,1077,1,no description,60.2,random -1078,I7N3G6G,1078,1,no description,60.2,random -1079,I7N3G6G,1079,1,no description,60.2,random -1080,I7N3G6G,1080,1,no description,60.2,random -1081,I7N3G6G,1081,1,no description,60.2,random -1082,I7N3G6G,1082,1,no description,60.2,random -1083,I7N3G6G,1083,1,no description,60.2,random -1084,I7N3G6G,1084,1,no description,60.2,random -1085,I7N3G6G,1085,1,no description,60.2,random -1086,I7N3G6G,1086,1,no description,60.2,random -1087,I7N3G6G,1087,1,no description,60.2,random -1088,I7N3G6G,1088,1,no description,60.2,random -1089,I7N3G6G,1089,1,no description,60.2,random -1090,I7N3G6G,1090,1,no description,60.2,random -1091,I7N3G6G,1091,1,no description,60.2,random -1092,I7N3G6G,1092,1,no description,60.2,random -1093,I7N3G6G,1093,1,no description,60.2,random -1094,I7N3G6G,1094,1,no description,60.2,random -1095,I7N3G6G,1095,1,no description,60.2,random -1096,I7N3G6G,1096,1,no description,60.2,random -1097,I7N3G6G,1097,1,no description,60.2,random -1098,I7N3G6G,1098,1,no description,60.2,random -1099,I7N3G6G,1099,1,no description,60.2,random -1100,I7N3G6G,1100,1,no description,60.2,random -1101,I7N3G6G,1101,1,no description,60.2,random -1102,I7N3G6G,1102,1,no description,60.2,random -1103,I7N3G6G,1103,1,no description,60.2,random -1104,I7N3G6G,1104,1,no description,60.2,random -1105,I7N3G6G,1105,1,no description,60.2,random -1106,I7N3G6G,1106,1,no description,60.2,random -1107,I7N3G6G,1107,1,no description,60.2,random -1108,I7N3G6G,1108,1,no description,60.2,random -1109,I7N3G6G,1109,1,no description,60.2,random -1110,I7N3G6G,1110,1,no description,60.2,random -1111,I7N3G6G,1111,1,no description,60.2,random -1112,I7N3G6G,1112,1,no description,60.2,random -1113,I7N3G6G,1113,1,no description,60.2,random -1114,I7N3G6G,1114,1,no description,60.2,random -1115,I7N3G6G,1115,1,no description,60.2,random -1116,I7N3G6G,1116,1,no description,60.2,random -1117,I7N3G6G,1117,1,no description,60.2,random -1118,I7N3G6G,1118,1,no description,60.2,random -1119,I7N3G6G,1119,1,no description,60.2,random -1120,I7N3G6G,1120,1,no description,60.2,random -1121,I7N3G6G,1121,1,no description,60.2,random -1122,I7N3G6G,1122,1,no description,60.2,random -1123,I7N3G6G,1123,1,no description,60.2,random -1124,I7N3G6G,1124,1,no description,60.2,random -1125,I7N3G6G,1125,1,no description,60.2,random -1126,I7N3G6G,1126,1,no description,60.2,random -1127,I7N3G6G,1127,1,no description,60.2,random -1128,I7N3G6G,1128,1,no description,60.2,random -1129,I7N3G6G,1129,1,no description,60.2,random -1130,I7N3G6G,1130,1,no description,60.2,random -1131,I7N3G6G,1131,1,no description,60.2,random -1132,I7N3G6G,1132,1,no description,60.2,random -1133,I7N3G6G,1133,1,no description,60.2,random -1134,I7N3G6G,1134,1,no description,60.2,random -1135,I7N3G6G,1135,1,no description,60.2,random -1136,I7N3G6G,1136,1,no description,60.2,random -1137,I7N3G6G,1137,1,no description,60.2,random -1138,I7N3G6G,1138,1,no description,60.2,random -1139,I7N3G6G,1139,1,no description,60.2,random -1140,I7N3G6G,1140,1,no description,60.2,random -1141,I7N3G6G,1141,1,no description,60.2,random -1142,I7N3G6G,1142,1,no description,60.2,random -1143,I7N3G6G,1143,1,no description,60.2,random -1144,I7N3G6G,1144,1,no description,60.2,random -1145,I7N3G6G,1145,1,no description,60.2,random -1146,I7N3G6G,1146,1,no description,60.2,random -1147,I7N3G6G,1147,1,no description,60.2,random -1148,I7N3G6G,1148,1,no description,60.2,random -1149,I7N3G6G,1149,1,no description,60.2,random -1150,I7N3G6G,1150,1,no description,60.2,random -1151,I7N3G6G,1151,1,no description,60.2,random -1152,I7N3G6G,1152,1,no description,60.2,random -1153,I7N3G6G,1153,1,no description,60.2,random -1154,I7N3G6G,1154,1,no description,60.2,random -1155,I7N3G6G,1155,1,no description,60.2,random -1156,I7N3G6G,1156,1,no description,60.2,random -1157,I7N3G6G,1157,1,no description,60.2,random -1158,I7N3G6G,1158,1,no description,60.2,random -1159,I7N3G6G,1159,1,no description,60.2,random -1160,I7N3G6G,1160,1,no description,60.2,random -1161,I7N3G6G,1161,1,no description,60.2,random -1162,I7N3G6G,1162,1,no description,60.2,random -1163,I7N3G6G,1163,1,no description,60.2,random -1164,I7N3G6G,1164,1,no description,60.2,random -1165,I7N3G6G,1165,1,no description,60.2,random -1166,I7N3G6G,1166,1,no description,60.2,random -1167,I7N3G6G,1167,1,no description,60.2,random -1168,I7N3G6G,1168,1,no description,60.2,random -1169,I7N3G6G,1169,1,no description,60.2,random -1170,I7N3G6G,1170,1,no description,60.2,random -1171,I7N3G6G,1171,1,no description,60.2,random -1172,I7N3G6G,1172,1,no description,60.2,random -1173,I7N3G6G,1173,1,no description,60.2,random -1174,I7N3G6G,1174,1,no description,60.2,random -1175,I7N3G6G,1175,1,no description,60.2,random -1176,I7N3G6G,1176,1,no description,60.2,random -1177,I7N3G6G,1177,1,no description,60.2,random -1178,I7N3G6G,1178,1,no description,60.2,random -1179,I7N3G6G,1179,1,no description,60.2,random -1180,I7N3G6G,1180,1,no description,60.2,random -1181,I7N3G6G,1181,1,no description,60.2,random -1182,I7N3G6G,1182,1,no description,60.2,random -1183,I7N3G6G,1183,1,no description,60.2,random -1184,I7N3G6G,1184,1,no description,60.2,random -1185,I7N3G6G,1185,1,no description,60.2,random -1186,I7N3G6G,1186,1,no description,60.2,random -1187,I7N3G6G,1187,1,no description,60.2,random -1188,I7N3G6G,1188,1,no description,60.2,random -1189,I7N3G6G,1189,1,no description,60.2,random -1190,I7N3G6G,1190,1,no description,60.2,random -1191,I7N3G6G,1191,1,no description,60.2,random -1192,I7N3G6G,1192,1,no description,60.2,random -1193,I7N3G6G,1193,1,no description,60.2,random -1194,I7N3G6G,1194,1,no description,60.2,random -1195,I7N3G6G,1195,1,no description,60.2,random -1196,I7N3G6G,1196,1,no description,60.2,random -1197,I7N3G6G,1197,1,no description,60.2,random -1198,I7N3G6G,1198,1,no description,60.2,random -1199,I7N3G6G,1199,1,no description,60.2,random -1200,I7N3G6G,1200,1,no description,60.2,random -1201,I7N3G6G,1201,1,no description,60.2,random -1202,I7N3G6G,1202,1,no description,60.2,random -1203,I7N3G6G,1203,1,no description,60.2,random -1204,I7N3G6G,1204,1,no description,60.2,random -1205,I7N3G6G,1205,1,no description,60.2,random -1206,I7N3G6G,1206,1,no description,60.2,random -1207,I7N3G6G,1207,1,no description,60.2,random -1208,I7N3G6G,1208,1,no description,60.2,random -1209,I7N3G6G,1209,1,no description,60.2,random -1210,I7N3G6G,1210,1,no description,60.2,random -1211,I7N3G6G,1211,1,no description,60.2,random -1212,I7N3G6G,1212,1,no description,60.2,random -1213,I7N3G6G,1213,1,no description,60.2,random -1214,I7N3G6G,1214,1,no description,60.2,random -1215,I7N3G6G,1215,1,no description,60.2,random -1216,I7N3G6G,1216,1,no description,60.2,random -1217,I7N3G6G,1217,1,no description,60.2,random -1218,I7N3G6G,1218,1,no description,60.2,random -1219,I7N3G6G,1219,1,no description,60.2,random -1220,I7N3G6G,1220,1,no description,60.2,random -1221,I7N3G6G,1221,1,no description,60.2,random -1222,I7N3G6G,1222,1,no description,60.2,random -1223,I7N3G6G,1223,1,no description,60.2,random -1224,I7N3G6G,1224,1,no description,60.2,random -1225,I7N3G6G,1225,1,no description,60.2,random -1226,I7N3G6G,1226,1,no description,60.2,random -1227,I7N3G6G,1227,1,no description,60.2,random -1228,I7N3G6G,1228,1,no description,60.2,random -1229,I7N3G6G,1229,1,no description,60.2,random -1230,I7N3G6G,1230,1,no description,60.2,random -1231,I7N3G6G,1231,1,no description,60.2,random -1232,I7N3G6G,1232,1,no description,60.2,random -1233,I7N3G6G,1233,1,no description,60.2,random -1234,I7N3G6G,1234,1,no description,60.2,random -1235,I7N3G6G,1235,1,no description,60.2,random -1236,I7N3G6G,1236,1,no description,60.2,random -1237,I7N3G6G,1237,1,no description,60.2,random -1238,I7N3G6G,1238,1,no description,60.2,random -1239,I7N3G6G,1239,1,no description,60.2,random -1240,I7N3G6G,1240,1,no description,60.2,random -1241,I7N3G6G,1241,1,no description,60.2,random -1242,I7N3G6G,1242,1,no description,60.2,random -1243,I7N3G6G,1243,1,no description,60.2,random -1244,I7N3G6G,1244,1,no description,60.2,random -1245,I7N3G6G,1245,1,no description,60.2,random -1246,I7N3G6G,1246,1,no description,60.2,random -1247,I7N3G6G,1247,1,no description,60.2,random -1248,I7N3G6G,1248,1,no description,60.2,random -1249,I7N3G6G,1249,1,no description,60.2,random -1250,I7N3G6G,1250,1,no description,60.2,random -1251,I7N3G6G,1251,1,no description,60.2,random -1252,I7N3G6G,1252,1,no description,60.2,random -1253,I7N3G6G,1253,1,no description,60.2,random -1254,I7N3G6G,1254,1,no description,60.2,random -1255,I7N3G6G,1255,1,no description,60.2,random -1256,I7N3G6G,1256,1,no description,60.2,random -1257,I7N3G6G,1257,1,no description,60.2,random -1258,I7N3G6G,1258,1,no description,60.2,random -1259,I7N3G6G,1259,1,no description,60.2,random -1260,I7N3G6G,1260,1,no description,60.2,random -1261,I7N3G6G,1261,1,no description,60.2,random -1262,I7N3G6G,1262,1,no description,60.2,random -1263,I7N3G6G,1263,1,no description,60.2,random -1264,I7N3G6G,1264,1,no description,60.2,random -1265,I7N3G6G,1265,1,no description,60.2,random -1266,I7N3G6G,1266,1,no description,60.2,random -1267,I7N3G6G,1267,1,no description,60.2,random -1268,I7N3G6G,1268,1,no description,60.2,random -1269,I7N3G6G,1269,1,no description,60.2,random -1270,I7N3G6G,1270,1,no description,60.2,random -1271,I7N3G6G,1271,1,no description,60.2,random -1272,I7N3G6G,1272,1,no description,60.2,random -1273,I7N3G6G,1273,1,no description,60.2,random -1274,I7N3G6G,1274,1,no description,60.2,random -1275,I7N3G6G,1275,1,no description,60.2,random -1276,I7N3G6G,1276,1,no description,60.2,random -1277,I7N3G6G,1277,1,no description,60.2,random -1278,I7N3G6G,1278,1,no description,60.2,random -1279,I7N3G6G,1279,1,no description,60.2,random -1280,I7N3G6G,1280,1,no description,60.2,random -1281,I7N3G6G,1281,1,no description,60.2,random -1282,I7N3G6G,1282,1,no description,60.2,random -1283,I7N3G6G,1283,1,no description,60.2,random -1284,I7N3G6G,1284,1,no description,60.2,random -1285,I7N3G6G,1285,1,no description,60.2,random -1286,I7N3G6G,1286,1,no description,60.2,random -1287,I7N3G6G,1287,1,no description,60.2,random -1288,I7N3G6G,1288,1,no description,60.2,random -1289,I7N3G6G,1289,1,no description,60.2,random -1290,I7N3G6G,1290,1,no description,60.2,random -1291,I7N3G6G,1291,1,no description,60.2,random -1292,I7N3G6G,1292,1,no description,60.2,random -1293,I7N3G6G,1293,1,no description,60.2,random -1294,I7N3G6G,1294,1,no description,60.2,random -1295,I7N3G6G,1295,1,no description,60.2,random -1296,I7N3G6G,1296,1,no description,60.2,random -1297,I7N3G6G,1297,1,no description,60.2,random -1298,I7N3G6G,1298,1,no description,60.2,random -1299,I7N3G6G,1299,1,no description,60.2,random -1300,I7N3G6G,1300,1,no description,60.2,random -1301,I7N3G6G,1301,1,no description,60.2,random -1302,I7N3G6G,1302,1,no description,60.2,random -1303,I7N3G6G,1303,1,no description,60.2,random -1304,I7N3G6G,1304,1,no description,60.2,random -1305,I7N3G6G,1305,1,no description,60.2,random -1306,I7N3G6G,1306,1,no description,60.2,random -1307,I7N3G6G,1307,1,no description,60.2,random -1308,I7N3G6G,1308,1,no description,60.2,random -1309,I7N3G6G,1309,1,no description,60.2,random -1310,I7N3G6G,1310,1,no description,60.2,random -1311,I7N3G6G,1311,1,no description,60.2,random -1312,I7N3G6G,1312,1,no description,60.2,random -1313,I7N3G6G,1313,1,no description,60.2,random -1314,I7N3G6G,1314,1,no description,60.2,random -1315,I7N3G6G,1315,1,no description,60.2,random -1316,I7N3G6G,1316,1,no description,60.2,random -1317,I7N3G6G,1317,1,no description,60.2,random -1318,I7N3G6G,1318,1,no description,60.2,random -1319,I7N3G6G,1319,1,no description,60.2,random -1320,I7N3G6G,1320,1,no description,60.2,random -1321,I7N3G6G,1321,1,no description,60.2,random -1322,I7N3G6G,1322,1,no description,60.2,random -1323,I7N3G6G,1323,1,no description,60.2,random -1324,I7N3G6G,1324,1,no description,60.2,random -1325,I7N3G6G,1325,1,no description,60.2,random -1326,I7N3G6G,1326,1,no description,60.2,random -1327,I7N3G6G,1327,1,no description,60.2,random -1328,I7N3G6G,1328,1,no description,60.2,random -1329,I7N3G6G,1329,1,no description,60.2,random -1330,I7N3G6G,1330,1,no description,60.2,random -1331,I7N3G6G,1331,1,no description,60.2,random -1332,I7N3G6G,1332,1,no description,60.2,random -1333,I7N3G6G,1333,1,no description,60.2,random -1334,I7N3G6G,1334,1,no description,60.2,random -1335,I7N3G6G,1335,1,no description,60.2,random -1336,I7N3G6G,1336,1,no description,60.2,random -1337,I7N3G6G,1337,1,no description,60.2,random -1338,I7N3G6G,1338,1,no description,60.2,random -1339,I7N3G6G,1339,1,no description,60.2,random -1340,I7N3G6G,1340,1,no description,60.2,random -1341,I7N3G6G,1341,1,no description,60.2,random -1342,I7N3G6G,1342,1,no description,60.2,random -1343,I7N3G6G,1343,1,no description,60.2,random -1344,I7N3G6G,1344,1,no description,60.2,random -1345,I7N3G6G,1345,1,no description,60.2,random -1346,I7N3G6G,1346,1,no description,60.2,random -1347,I7N3G6G,1347,1,no description,60.2,random -1348,I7N3G6G,1348,1,no description,60.2,random -1349,I7N3G6G,1349,1,no description,60.2,random -1350,I7N3G6G,1350,1,no description,60.2,random -1351,I7N3G6G,1351,1,no description,60.2,random -1352,I7N3G6G,1352,1,no description,60.2,random -1353,I7N3G6G,1353,1,no description,60.2,random -1354,I7N3G6G,1354,1,no description,60.2,random -1355,I7N3G6G,1355,1,no description,60.2,random -1356,I7N3G6G,1356,1,no description,60.2,random -1357,I7N3G6G,1357,1,no description,60.2,random -1358,I7N3G6G,1358,1,no description,60.2,random -1359,I7N3G6G,1359,1,no description,60.2,random -1360,I7N3G6G,1360,1,no description,60.2,random -1361,I7N3G6G,1361,1,no description,60.2,random -1362,I7N3G6G,1362,1,no description,60.2,random -1363,I7N3G6G,1363,1,no description,60.2,random -1364,I7N3G6G,1364,1,no description,60.2,random -1365,I7N3G6G,1365,1,no description,60.2,random -1366,I7N3G6G,1366,1,no description,60.2,random -1367,I7N3G6G,1367,1,no description,60.2,random -1368,I7N3G6G,1368,1,no description,60.2,random -1369,I7N3G6G,1369,1,no description,60.2,random -1370,I7N3G6G,1370,1,no description,60.2,random -1371,I7N3G6G,1371,1,no description,60.2,random -1372,I7N3G6G,1372,1,no description,60.2,random -1373,I7N3G6G,1373,1,no description,60.2,random -1374,I7N3G6G,1374,1,no description,60.2,random -1375,I7N3G6G,1375,1,no description,60.2,random -1376,I7N3G6G,1376,1,no description,60.2,random -1377,I7N3G6G,1377,1,no description,60.2,random -1378,I7N3G6G,1378,1,no description,60.2,random -1379,I7N3G6G,1379,1,no description,60.2,random -1380,I7N3G6G,1380,1,no description,60.2,random -1381,I7N3G6G,1381,1,no description,60.2,random -1382,I7N3G6G,1382,1,no description,60.2,random -1383,I7N3G6G,1383,1,no description,60.2,random -1384,I7N3G6G,1384,1,no description,60.2,random -1385,I7N3G6G,1385,1,no description,60.2,random -1386,I7N3G6G,1386,1,no description,60.2,random -1387,I7N3G6G,1387,1,no description,60.2,random -1388,I7N3G6G,1388,1,no description,60.2,random -1389,I7N3G6G,1389,1,no description,60.2,random -1390,I7N3G6G,1390,1,no description,60.2,random -1391,I7N3G6G,1391,1,no description,60.2,random -1392,I7N3G6G,1392,1,no description,60.2,random -1393,I7N3G6G,1393,1,no description,60.2,random -1394,I7N3G6G,1394,1,no description,60.2,random -1395,I7N3G6G,1395,1,no description,60.2,random -1396,I7N3G6G,1396,1,no description,60.2,random -1397,I7N3G6G,1397,1,no description,60.2,random -1398,I7N3G6G,1398,1,no description,60.2,random -1399,I7N3G6G,1399,1,no description,60.2,random -1400,I7N3G6G,1400,1,no description,60.2,random -1401,I7N3G6G,1401,1,no description,60.2,random -1402,I7N3G6G,1402,1,no description,60.2,random -1403,I7N3G6G,1403,1,no description,60.2,random -1404,I7N3G6G,1404,1,no description,60.2,random -1405,I7N3G6G,1405,1,no description,60.2,random -1406,I7N3G6G,1406,1,no description,60.2,random -1407,I7N3G6G,1407,1,no description,60.2,random -1408,I7N3G6G,1408,1,no description,60.2,random -1409,I7N3G6G,1409,1,no description,60.2,random -1410,I7N3G6G,1410,1,no description,60.2,random -1411,I7N3G6G,1411,1,no description,60.2,random -1412,I7N3G6G,1412,1,no description,60.2,random -1413,I7N3G6G,1413,1,no description,60.2,random -1414,I7N3G6G,1414,1,no description,60.2,random -1415,I7N3G6G,1415,1,no description,60.2,random -1416,I7N3G6G,1416,1,no description,60.2,random -1417,I7N3G6G,1417,1,no description,60.2,random -1418,I7N3G6G,1418,1,no description,60.2,random -1419,I7N3G6G,1419,1,no description,60.2,random -1420,I7N3G6G,1420,1,no description,60.2,random -1421,I7N3G6G,1421,1,no description,60.2,random -1422,I7N3G6G,1422,1,no description,60.2,random -1423,I7N3G6G,1423,1,no description,60.2,random -1424,I7N3G6G,1424,1,no description,60.2,random -1425,I7N3G6G,1425,1,no description,60.2,random -1426,I7N3G6G,1426,1,no description,60.2,random -1427,I7N3G6G,1427,1,no description,60.2,random -1428,I7N3G6G,1428,1,no description,60.2,random -1429,I7N3G6G,1429,1,no description,60.2,random -1430,I7N3G6G,1430,1,no description,60.2,random -1431,I7N3G6G,1431,1,no description,60.2,random -1432,I7N3G6G,1432,1,no description,60.2,random -1433,I7N3G6G,1433,1,no description,60.2,random -1434,I7N3G6G,1434,1,no description,60.2,random -1435,I7N3G6G,1435,1,no description,60.2,random -1436,I7N3G6G,1436,1,no description,60.2,random -1437,I7N3G6G,1437,1,no description,60.2,random -1438,I7N3G6G,1438,1,no description,60.2,random -1439,I7N3G6G,1439,1,no description,60.2,random -1440,I7N3G6G,1440,1,no description,60.2,random -1441,I7N3G6G,1441,1,no description,60.2,random -1442,I7N3G6G,1442,1,no description,60.2,random -1443,I7N3G6G,1443,1,no description,60.2,random -1444,I7N3G6G,1444,1,no description,60.2,random -1445,I7N3G6G,1445,1,no description,60.2,random -1446,I7N3G6G,1446,1,no description,60.2,random -1447,I7N3G6G,1447,1,no description,60.2,random -1448,I7N3G6G,1448,1,no description,60.2,random -1449,I7N3G6G,1449,1,no description,60.2,random -1450,I7N3G6G,1450,1,no description,60.2,random -1451,I7N3G6G,1451,1,no description,60.2,random -1452,I7N3G6G,1452,1,no description,60.2,random -1453,I7N3G6G,1453,1,no description,60.2,random -1454,I7N3G6G,1454,1,no description,60.2,random -1455,I7N3G6G,1455,1,no description,60.2,random -1456,I7N3G6G,1456,1,no description,60.2,random -1457,I7N3G6G,1457,1,no description,60.2,random -1458,I7N3G6G,1458,1,no description,60.2,random -1459,I7N3G6G,1459,1,no description,60.2,random -1460,I7N3G6G,1460,1,no description,60.2,random -1461,I7N3G6G,1461,1,no description,60.2,random -1462,I7N3G6G,1462,1,no description,60.2,random -1463,I7N3G6G,1463,1,no description,60.2,random -1464,I7N3G6G,1464,1,no description,60.2,random -1465,I7N3G6G,1465,1,no description,60.2,random -1466,I7N3G6G,1466,1,no description,60.2,random -1467,I7N3G6G,1467,1,no description,60.2,random -1468,I7N3G6G,1468,1,no description,60.2,random -1469,I7N3G6G,1469,1,no description,60.2,random -1470,I7N3G6G,1470,1,no description,60.2,random -1471,I7N3G6G,1471,1,no description,60.2,random -1472,I7N3G6G,1472,1,no description,60.2,random -1473,I7N3G6G,1473,1,no description,60.2,random -1474,I7N3G6G,1474,1,no description,60.2,random -1475,I7N3G6G,1475,1,no description,60.2,random -1476,I7N3G6G,1476,1,no description,60.2,random -1477,I7N3G6G,1477,1,no description,60.2,random -1478,I7N3G6G,1478,1,no description,60.2,random -1479,I7N3G6G,1479,1,no description,60.2,random -1480,I7N3G6G,1480,1,no description,60.2,random -1481,I7N3G6G,1481,1,no description,60.2,random -1482,I7N3G6G,1482,1,no description,60.2,random -1483,I7N3G6G,1483,1,no description,60.2,random -1484,I7N3G6G,1484,1,no description,60.2,random -1485,I7N3G6G,1485,1,no description,60.2,random -1486,I7N3G6G,1486,1,no description,60.2,random -1487,I7N3G6G,1487,1,no description,60.2,random -1488,I7N3G6G,1488,1,no description,60.2,random -1489,I7N3G6G,1489,1,no description,60.2,random -1490,I7N3G6G,1490,1,no description,60.2,random -1491,I7N3G6G,1491,1,no description,60.2,random -1492,I7N3G6G,1492,1,no description,60.2,random -1493,I7N3G6G,1493,1,no description,60.2,random -1494,I7N3G6G,1494,1,no description,60.2,random -1495,I7N3G6G,1495,1,no description,60.2,random -1496,I7N3G6G,1496,1,no description,60.2,random -1497,I7N3G6G,1497,1,no description,60.2,random -1498,I7N3G6G,1498,1,no description,60.2,random -1499,I7N3G6G,1499,1,no description,60.2,random -1500,I7N3G6G,1500,1,no description,60.2,random -1501,I7N3G6G,1501,1,no description,60.2,random -1502,I7N3G6G,1502,1,no description,60.2,random -1503,I7N3G6G,1503,1,no description,60.2,random -1504,I7N3G6G,1504,1,no description,60.2,random -1505,I7N3G6G,1505,1,no description,60.2,random -1506,I7N3G6G,1506,1,no description,60.2,random -1507,I7N3G6G,1507,1,no description,60.2,random -1508,I7N3G6G,1508,1,no description,60.2,random -1509,I7N3G6G,1509,1,no description,60.2,random -1510,I7N3G6G,1510,1,no description,60.2,random -1511,I7N3G6G,1511,1,no description,60.2,random -1512,I7N3G6G,1512,1,no description,60.2,random -1513,I7N3G6G,1513,1,no description,60.2,random -1514,I7N3G6G,1514,1,no description,60.2,random -1515,I7N3G6G,1515,1,no description,60.2,random -1516,I7N3G6G,1516,1,no description,60.2,random -1517,I7N3G6G,1517,1,no description,60.2,random -1518,I7N3G6G,1518,1,no description,60.2,random -1519,I7N3G6G,1519,1,no description,60.2,random -1520,I7N3G6G,1520,1,no description,60.2,random -1521,I7N3G6G,1521,1,no description,60.2,random -1522,I7N3G6G,1522,1,no description,60.2,random -1523,I7N3G6G,1523,1,no description,60.2,random -1524,I7N3G6G,1524,1,no description,60.2,random -1525,I7N3G6G,1525,1,no description,60.2,random -1526,I7N3G6G,1526,1,no description,60.2,random -1527,I7N3G6G,1527,1,no description,60.2,random -1528,I7N3G6G,1528,1,no description,60.2,random -1529,I7N3G6G,1529,1,no description,60.2,random -1530,I7N3G6G,1530,1,no description,60.2,random -1531,I7N3G6G,1531,1,no description,60.2,random -1532,I7N3G6G,1532,1,no description,60.2,random -1533,I7N3G6G,1533,1,no description,60.2,random -1534,I7N3G6G,1534,1,no description,60.2,random -1535,I7N3G6G,1535,1,no description,60.2,random -1536,I7N3G6G,1536,1,no description,60.2,random -1537,I7N3G6G,1537,1,no description,60.2,random -1538,I7N3G6G,1538,1,no description,60.2,random -1539,I7N3G6G,1539,1,no description,60.2,random -1540,I7N3G6G,1540,1,no description,60.2,random -1541,I7N3G6G,1541,1,no description,60.2,random -1542,I7N3G6G,1542,1,no description,60.2,random -1543,I7N3G6G,1543,1,no description,60.2,random -1544,I7N3G6G,1544,1,no description,60.2,random -1545,I7N3G6G,1545,1,no description,60.2,random -1546,I7N3G6G,1546,1,no description,60.2,random -1547,I7N3G6G,1547,1,no description,60.2,random -1548,I7N3G6G,1548,1,no description,60.2,random -1549,I7N3G6G,1549,1,no description,60.2,random -1550,I7N3G6G,1550,1,no description,60.2,random -1551,I7N3G6G,1551,1,no description,60.2,random -1552,I7N3G6G,1552,1,no description,60.2,random -1553,I7N3G6G,1553,1,no description,60.2,random -1554,I7N3G6G,1554,1,no description,60.2,random -1555,I7N3G6G,1555,1,no description,60.2,random -1556,I7N3G6G,1556,1,no description,60.2,random -1557,I7N3G6G,1557,1,no description,60.2,random -1558,I7N3G6G,1558,1,no description,60.2,random -1559,I7N3G6G,1559,1,no description,60.2,random -1560,I7N3G6G,1560,1,no description,60.2,random -1561,I7N3G6G,1561,1,no description,60.2,random -1562,I7N3G6G,1562,1,no description,60.2,random -1563,I7N3G6G,1563,1,no description,60.2,random -1564,I7N3G6G,1564,1,no description,60.2,random -1565,I7N3G6G,1565,1,no description,60.2,random -1566,I7N3G6G,1566,1,no description,60.2,random -1567,I7N3G6G,1567,1,no description,60.2,random -1568,I7N3G6G,1568,1,no description,60.2,random -1569,I7N3G6G,1569,1,no description,60.2,random -1570,I7N3G6G,1570,1,no description,60.2,random -1571,I7N3G6G,1571,1,no description,60.2,random -1572,I7N3G6G,1572,1,no description,60.2,random -1573,I7N3G6G,1573,1,no description,60.2,random -1574,I7N3G6G,1574,1,no description,60.2,random -1575,I7N3G6G,1575,1,no description,60.2,random -1576,I7N3G6G,1576,1,no description,60.2,random -1577,I7N3G6G,1577,1,no description,60.2,random -1578,I7N3G6G,1578,1,no description,60.2,random -1579,I7N3G6G,1579,1,no description,60.2,random -1580,I7N3G6G,1580,1,no description,60.2,random -1581,I7N3G6G,1581,1,no description,60.2,random -1582,I7N3G6G,1582,1,no description,60.2,random -1583,I7N3G6G,1583,1,no description,60.2,random -1584,I7N3G6G,1584,1,no description,60.2,random -1585,I7N3G6G,1585,1,no description,60.2,random -1586,I7N3G6G,1586,1,no description,60.2,random -1587,I7N3G6G,1587,1,no description,60.2,random -1588,I7N3G6G,1588,1,no description,60.2,random -1589,I7N3G6G,1589,1,no description,60.2,random -1590,I7N3G6G,1590,1,no description,60.2,random -1591,I7N3G6G,1591,1,no description,60.2,random -1592,I7N3G6G,1592,1,no description,60.2,random -1593,I7N3G6G,1593,1,no description,60.2,random -1594,I7N3G6G,1594,1,no description,60.2,random -1595,I7N3G6G,1595,1,no description,60.2,random -1596,I7N3G6G,1596,1,no description,60.2,random -1597,I7N3G6G,1597,1,no description,60.2,random -1598,I7N3G6G,1598,1,no description,60.2,random -1599,I7N3G6G,1599,1,no description,60.2,random -1600,I7N3G6G,1600,1,no description,60.2,random -1601,I7N3G6G,1601,1,no description,60.2,random -1602,I7N3G6G,1602,1,no description,60.2,random -1603,I7N3G6G,1603,1,no description,60.2,random -1604,I7N3G6G,1604,1,no description,60.2,random -1605,I7N3G6G,1605,1,no description,60.2,random -1606,I7N3G6G,1606,1,no description,60.2,random -1607,I7N3G6G,1607,1,no description,60.2,random -1608,I7N3G6G,1608,1,no description,60.2,random -1609,I7N3G6G,1609,1,no description,60.2,random -1610,I7N3G6G,1610,1,no description,60.2,random -1611,I7N3G6G,1611,1,no description,60.2,random -1612,I7N3G6G,1612,1,no description,60.2,random -1613,I7N3G6G,1613,1,no description,60.2,random -1614,I7N3G6G,1614,1,no description,60.2,random -1615,I7N3G6G,1615,1,no description,60.2,random -1616,I7N3G6G,1616,1,no description,60.2,random -1617,I7N3G6G,1617,1,no description,60.2,random -1618,I7N3G6G,1618,1,no description,60.2,random -1619,I7N3G6G,1619,1,no description,60.2,random -1620,I7N3G6G,1620,1,no description,60.2,random -1621,I7N3G6G,1621,1,no description,60.2,random -1622,I7N3G6G,1622,1,no description,60.2,random -1623,I7N3G6G,1623,1,no description,60.2,random -1624,I7N3G6G,1624,1,no description,60.2,random -1625,I7N3G6G,1625,1,no description,60.2,random -1626,I7N3G6G,1626,1,no description,60.2,random -1627,I7N3G6G,1627,1,no description,60.2,random -1628,I7N3G6G,1628,1,no description,60.2,random -1629,I7N3G6G,1629,1,no description,60.2,random -1630,I7N3G6G,1630,1,no description,60.2,random -1631,I7N3G6G,1631,1,no description,60.2,random -1632,I7N3G6G,1632,1,no description,60.2,random -1633,I7N3G6G,1633,1,no description,60.2,random -1634,I7N3G6G,1634,1,no description,60.2,random -1635,I7N3G6G,1635,1,no description,60.2,random -1636,I7N3G6G,1636,1,no description,60.2,random -1637,I7N3G6G,1637,1,no description,60.2,random -1638,I7N3G6G,1638,1,no description,60.2,random -1639,I7N3G6G,1639,1,no description,60.2,random -1640,I7N3G6G,1640,1,no description,60.2,random -1641,I7N3G6G,1641,1,no description,60.2,random -1642,I7N3G6G,1642,1,no description,60.2,random -1643,I7N3G6G,1643,1,no description,60.2,random -1644,I7N3G6G,1644,1,no description,60.2,random -1645,I7N3G6G,1645,1,no description,60.2,random -1646,I7N3G6G,1646,1,no description,60.2,random -1647,I7N3G6G,1647,1,no description,60.2,random -1648,I7N3G6G,1648,1,no description,60.2,random -1649,I7N3G6G,1649,1,no description,60.2,random -1650,I7N3G6G,1650,1,no description,60.2,random -1651,I7N3G6G,1651,1,no description,60.2,random -1652,I7N3G6G,1652,1,no description,60.2,random -1653,I7N3G6G,1653,1,no description,60.2,random -1654,I7N3G6G,1654,1,no description,60.2,random -1655,I7N3G6G,1655,1,no description,60.2,random -1656,I7N3G6G,1656,1,no description,60.2,random -1657,I7N3G6G,1657,1,no description,60.2,random -1658,I7N3G6G,1658,1,no description,60.2,random -1659,I7N3G6G,1659,1,no description,60.2,random -1660,I7N3G6G,1660,1,no description,60.2,random -1661,I7N3G6G,1661,1,no description,60.2,random -1662,I7N3G6G,1662,1,no description,60.2,random -1663,I7N3G6G,1663,1,no description,60.2,random -1664,I7N3G6G,1664,1,no description,60.2,random -1665,I7N3G6G,1665,1,no description,60.2,random -1666,I7N3G6G,1666,1,no description,60.2,random -1667,I7N3G6G,1667,1,no description,60.2,random -1668,I7N3G6G,1668,1,no description,60.2,random -1669,I7N3G6G,1669,1,no description,60.2,random -1670,I7N3G6G,1670,1,no description,60.2,random -1671,I7N3G6G,1671,1,no description,60.2,random -1672,I7N3G6G,1672,1,no description,60.2,random -1673,I7N3G6G,1673,1,no description,60.2,random -1674,I7N3G6G,1674,1,no description,60.2,random -1675,I7N3G6G,1675,1,no description,60.2,random -1676,I7N3G6G,1676,1,no description,60.2,random -1677,I7N3G6G,1677,1,no description,60.2,random -1678,I7N3G6G,1678,1,no description,60.2,random -1679,I7N3G6G,1679,1,no description,60.2,random -1680,I7N3G6G,1680,1,no description,60.2,random -1681,I7N3G6G,1681,1,no description,60.2,random -1682,I7N3G6G,1682,1,no description,60.2,random -1683,I7N3G6G,1683,1,no description,60.2,random -1684,I7N3G6G,1684,1,no description,60.2,random -1685,I7N3G6G,1685,1,no description,60.2,random -1686,I7N3G6G,1686,1,no description,60.2,random -1687,I7N3G6G,1687,1,no description,60.2,random -1688,I7N3G6G,1688,1,no description,60.2,random -1689,I7N3G6G,1689,1,no description,60.2,random -1690,I7N3G6G,1690,1,no description,60.2,random -1691,I7N3G6G,1691,1,no description,60.2,random -1692,I7N3G6G,1692,1,no description,60.2,random -1693,I7N3G6G,1693,1,no description,60.2,random -1694,I7N3G6G,1694,1,no description,60.2,random -1695,I7N3G6G,1695,1,no description,60.2,random -1696,I7N3G6G,1696,1,no description,60.2,random -1697,I7N3G6G,1697,1,no description,60.2,random -1698,I7N3G6G,1698,1,no description,60.2,random -1699,I7N3G6G,1699,1,no description,60.2,random -1700,I7N3G6G,1700,1,no description,60.2,random -1701,I7N3G6G,1701,1,no description,60.2,random -1702,I7N3G6G,1702,1,no description,60.2,random -1703,I7N3G6G,1703,1,no description,60.2,random -1704,I7N3G6G,1704,1,no description,60.2,random -1705,I7N3G6G,1705,1,no description,60.2,random -1706,I7N3G6G,1706,1,no description,60.2,random -1707,I7N3G6G,1707,1,no description,60.2,random -1708,I7N3G6G,1708,1,no description,60.2,random -1709,I7N3G6G,1709,1,no description,60.2,random -1710,I7N3G6G,1710,1,no description,60.2,random -1711,I7N3G6G,1711,1,no description,60.2,random -1712,I7N3G6G,1712,1,no description,60.2,random -1713,I7N3G6G,1713,1,no description,60.2,random -1714,I7N3G6G,1714,1,no description,60.2,random -1715,I7N3G6G,1715,1,no description,60.2,random -1716,I7N3G6G,1716,1,no description,60.2,random -1717,I7N3G6G,1717,1,no description,60.2,random -1718,I7N3G6G,1718,1,no description,60.2,random -1719,I7N3G6G,1719,1,no description,60.2,random -1720,I7N3G6G,1720,1,no description,60.2,random -1721,I7N3G6G,1721,1,no description,60.2,random -1722,I7N3G6G,1722,1,no description,60.2,random -1723,I7N3G6G,1723,1,no description,60.2,random -1724,I7N3G6G,1724,1,no description,60.2,random -1725,I7N3G6G,1725,1,no description,60.2,random -1726,I7N3G6G,1726,1,no description,60.2,random -1727,I7N3G6G,1727,1,no description,60.2,random -1728,I7N3G6G,1728,1,no description,60.2,random -1729,I7N3G6G,1729,1,no description,60.2,random -1730,I7N3G6G,1730,1,no description,60.2,random -1731,I7N3G6G,1731,1,no description,60.2,random -1732,I7N3G6G,1732,1,no description,60.2,random -1733,I7N3G6G,1733,1,no description,60.2,random -1734,I7N3G6G,1734,1,no description,60.2,random -1735,I7N3G6G,1735,1,no description,60.2,random -1736,I7N3G6G,1736,1,no description,60.2,random -1737,I7N3G6G,1737,1,no description,60.2,random -1738,I7N3G6G,1738,1,no description,60.2,random -1739,I7N3G6G,1739,1,no description,60.2,random -1740,I7N3G6G,1740,1,no description,60.2,random -1741,I7N3G6G,1741,1,no description,60.2,random -1742,I7N3G6G,1742,1,no description,60.2,random -1743,I7N3G6G,1743,1,no description,60.2,random -1744,I7N3G6G,1744,1,no description,60.2,random -1745,I7N3G6G,1745,1,no description,60.2,random -1746,I7N3G6G,1746,1,no description,60.2,random -1747,I7N3G6G,1747,1,no description,60.2,random -1748,I7N3G6G,1748,1,no description,60.2,random -1749,I7N3G6G,1749,1,no description,60.2,random -1750,I7N3G6G,1750,1,no description,60.2,random -1751,I7N3G6G,1751,1,no description,60.2,random -1752,I7N3G6G,1752,1,no description,60.2,random -1753,I7N3G6G,1753,1,no description,60.2,random -1754,I7N3G6G,1754,1,no description,60.2,random -1755,I7N3G6G,1755,1,no description,60.2,random -1756,I7N3G6G,1756,1,no description,60.2,random -1757,I7N3G6G,1757,1,no description,60.2,random -1758,I7N3G6G,1758,1,no description,60.2,random -1759,I7N3G6G,1759,1,no description,60.2,random -1760,I7N3G6G,1760,1,no description,60.2,random -1761,I7N3G6G,1761,1,no description,60.2,random -1762,I7N3G6G,1762,1,no description,60.2,random -1763,I7N3G6G,1763,1,no description,60.2,random -1764,I7N3G6G,1764,1,no description,60.2,random -1765,I7N3G6G,1765,1,no description,60.2,random -1766,I7N3G6G,1766,1,no description,60.2,random -1767,I7N3G6G,1767,1,no description,60.2,random -1768,I7N3G6G,1768,1,no description,60.2,random -1769,I7N3G6G,1769,1,no description,60.2,random -1770,I7N3G6G,1770,1,no description,60.2,random -1771,I7N3G6G,1771,1,no description,60.2,random -1772,I7N3G6G,1772,1,no description,60.2,random -1773,I7N3G6G,1773,1,no description,60.2,random -1774,I7N3G6G,1774,1,no description,60.2,random -1775,I7N3G6G,1775,1,no description,60.2,random -1776,I7N3G6G,1776,1,no description,60.2,random -1777,I7N3G6G,1777,1,no description,60.2,random -1778,I7N3G6G,1778,1,no description,60.2,random -1779,I7N3G6G,1779,1,no description,60.2,random -1780,I7N3G6G,1780,1,no description,60.2,random -1781,I7N3G6G,1781,1,no description,60.2,random -1782,I7N3G6G,1782,1,no description,60.2,random -1783,I7N3G6G,1783,1,no description,60.2,random -1784,I7N3G6G,1784,1,no description,60.2,random -1785,I7N3G6G,1785,1,no description,60.2,random -1786,I7N3G6G,1786,1,no description,60.2,random -1787,I7N3G6G,1787,1,no description,60.2,random -1788,I7N3G6G,1788,1,no description,60.2,random -1789,I7N3G6G,1789,1,no description,60.2,random -1790,I7N3G6G,1790,1,no description,60.2,random -1791,I7N3G6G,1791,1,no description,60.2,random -1792,I7N3G6G,1792,1,no description,60.2,random -1793,I7N3G6G,1793,1,no description,60.2,random -1794,I7N3G6G,1794,1,no description,60.2,random -1795,I7N3G6G,1795,1,no description,60.2,random -1796,I7N3G6G,1796,1,no description,60.2,random -1797,I7N3G6G,1797,1,no description,60.2,random -1798,I7N3G6G,1798,1,no description,60.2,random -1799,I7N3G6G,1799,1,no description,60.2,random -1800,I7N3G6G,1800,1,no description,60.2,random -1801,I7N3G6G,1801,1,no description,60.2,random -1802,I7N3G6G,1802,1,no description,60.2,random -1803,I7N3G6G,1803,1,no description,60.2,random -1804,I7N3G6G,1804,1,no description,60.2,random -1805,I7N3G6G,1805,1,no description,60.2,random -1806,I7N3G6G,1806,1,no description,60.2,random -1807,I7N3G6G,1807,1,no description,60.2,random -1808,I7N3G6G,1808,1,no description,60.2,random -1809,I7N3G6G,1809,1,no description,60.2,random -1810,I7N3G6G,1810,1,no description,60.2,random -1811,I7N3G6G,1811,1,no description,60.2,random -1812,I7N3G6G,1812,1,no description,60.2,random -1813,I7N3G6G,1813,1,no description,60.2,random -1814,I7N3G6G,1814,1,no description,60.2,random -1815,I7N3G6G,1815,1,no description,60.2,random -1816,I7N3G6G,1816,1,no description,60.2,random -1817,I7N3G6G,1817,1,no description,60.2,random -1818,I7N3G6G,1818,1,no description,60.2,random -1819,I7N3G6G,1819,1,no description,60.2,random -1820,I7N3G6G,1820,1,no description,60.2,random -1821,I7N3G6G,1821,1,no description,60.2,random -1822,I7N3G6G,1822,1,no description,60.2,random -1823,I7N3G6G,1823,1,no description,60.2,random -1824,I7N3G6G,1824,1,no description,60.2,random -1825,I7N3G6G,1825,1,no description,60.2,random -1826,I7N3G6G,1826,1,no description,60.2,random -1827,I7N3G6G,1827,1,no description,60.2,random -1828,I7N3G6G,1828,1,no description,60.2,random -1829,I7N3G6G,1829,1,no description,60.2,random -1830,I7N3G6G,1830,1,no description,60.2,random -1831,I7N3G6G,1831,1,no description,60.2,random -1832,I7N3G6G,1832,1,no description,60.2,random -1833,I7N3G6G,1833,1,no description,60.2,random -1834,I7N3G6G,1834,1,no description,60.2,random -1835,I7N3G6G,1835,1,no description,60.2,random -1836,I7N3G6G,1836,1,no description,60.2,random -1837,I7N3G6G,1837,1,no description,60.2,random -1838,I7N3G6G,1838,1,no description,60.2,random -1839,I7N3G6G,1839,1,no description,60.2,random -1840,I7N3G6G,1840,1,no description,60.2,random -1841,I7N3G6G,1841,1,no description,60.2,random -1842,I7N3G6G,1842,1,no description,60.2,random -1843,I7N3G6G,1843,1,no description,60.2,random -1844,I7N3G6G,1844,1,no description,60.2,random -1845,I7N3G6G,1845,1,no description,60.2,random -1846,I7N3G6G,1846,1,no description,60.2,random -1847,I7N3G6G,1847,1,no description,60.2,random -1848,I7N3G6G,1848,1,no description,60.2,random -1849,I7N3G6G,1849,1,no description,60.2,random -1850,I7N3G6G,1850,1,no description,60.2,random -1851,I7N3G6G,1851,1,no description,60.2,random -1852,I7N3G6G,1852,1,no description,60.2,random -1853,I7N3G6G,1853,1,no description,60.2,random -1854,I7N3G6G,1854,1,no description,60.2,random -1855,I7N3G6G,1855,1,no description,60.2,random -1856,I7N3G6G,1856,1,no description,60.2,random -1857,I7N3G6G,1857,1,no description,60.2,random -1858,I7N3G6G,1858,1,no description,60.2,random -1859,I7N3G6G,1859,1,no description,60.2,random -1860,I7N3G6G,1860,1,no description,60.2,random -1861,I7N3G6G,1861,1,no description,60.2,random -1862,I7N3G6G,1862,1,no description,60.2,random -1863,I7N3G6G,1863,1,no description,60.2,random -1864,I7N3G6G,1864,1,no description,60.2,random -1865,I7N3G6G,1865,1,no description,60.2,random -1866,I7N3G6G,1866,1,no description,60.2,random -1867,I7N3G6G,1867,1,no description,60.2,random -1868,I7N3G6G,1868,1,no description,60.2,random -1869,I7N3G6G,1869,1,no description,60.2,random -1870,I7N3G6G,1870,1,no description,60.2,random -1871,I7N3G6G,1871,1,no description,60.2,random -1872,I7N3G6G,1872,1,no description,60.2,random -1873,I7N3G6G,1873,1,no description,60.2,random -1874,I7N3G6G,1874,1,no description,60.2,random -1875,I7N3G6G,1875,1,no description,60.2,random -1876,I7N3G6G,1876,1,no description,60.2,random -1877,I7N3G6G,1877,1,no description,60.2,random -1878,I7N3G6G,1878,1,no description,60.2,random -1879,I7N3G6G,1879,1,no description,60.2,random -1880,I7N3G6G,1880,1,no description,60.2,random -1881,I7N3G6G,1881,1,no description,60.2,random -1882,I7N3G6G,1882,1,no description,60.2,random -1883,I7N3G6G,1883,1,no description,60.2,random -1884,I7N3G6G,1884,1,no description,60.2,random -1885,I7N3G6G,1885,1,no description,60.2,random -1886,I7N3G6G,1886,1,no description,60.2,random -1887,I7N3G6G,1887,1,no description,60.2,random -1888,I7N3G6G,1888,1,no description,60.2,random -1889,I7N3G6G,1889,1,no description,60.2,random -1890,I7N3G6G,1890,1,no description,60.2,random -1891,I7N3G6G,1891,1,no description,60.2,random -1892,I7N3G6G,1892,1,no description,60.2,random -1893,I7N3G6G,1893,1,no description,60.2,random -1894,I7N3G6G,1894,1,no description,60.2,random -1895,I7N3G6G,1895,1,no description,60.2,random -1896,I7N3G6G,1896,1,no description,60.2,random -1897,I7N3G6G,1897,1,no description,60.2,random -1898,I7N3G6G,1898,1,no description,60.2,random -1899,I7N3G6G,1899,1,no description,60.2,random -1900,I7N3G6G,1900,1,no description,60.2,random -1901,I7N3G6G,1901,1,no description,60.2,random -1902,I7N3G6G,1902,1,no description,60.2,random -1903,I7N3G6G,1903,1,no description,60.2,random -1904,I7N3G6G,1904,1,no description,60.2,random -1905,I7N3G6G,1905,1,no description,60.2,random -1906,I7N3G6G,1906,1,no description,60.2,random -1907,I7N3G6G,1907,1,no description,60.2,random -1908,I7N3G6G,1908,1,no description,60.2,random -1909,I7N3G6G,1909,1,no description,60.2,random -1910,I7N3G6G,1910,1,no description,60.2,random -1911,I7N3G6G,1911,1,no description,60.2,random -1912,I7N3G6G,1912,1,no description,60.2,random -1913,I7N3G6G,1913,1,no description,60.2,random -1914,I7N3G6G,1914,1,no description,60.2,random -1915,I7N3G6G,1915,1,no description,60.2,random -1916,I7N3G6G,1916,1,no description,60.2,random -1917,I7N3G6G,1917,1,no description,60.2,random -1918,I7N3G6G,1918,1,no description,60.2,random -1919,I7N3G6G,1919,1,no description,60.2,random -1920,I7N3G6G,1920,1,no description,60.2,random -1921,I7N3G6G,1921,1,no description,60.2,random -1922,I7N3G6G,1922,1,no description,60.2,random -1923,I7N3G6G,1923,1,no description,60.2,random -1924,I7N3G6G,1924,1,no description,60.2,random -1925,I7N3G6G,1925,1,no description,60.2,random -1926,I7N3G6G,1926,1,no description,60.2,random -1927,I7N3G6G,1927,1,no description,60.2,random -1928,I7N3G6G,1928,1,no description,60.2,random -1929,I7N3G6G,1929,1,no description,60.2,random -1930,I7N3G6G,1930,1,no description,60.2,random -1931,I7N3G6G,1931,1,no description,60.2,random -1932,I7N3G6G,1932,1,no description,60.2,random -1933,I7N3G6G,1933,1,no description,60.2,random -1934,I7N3G6G,1934,1,no description,60.2,random -1935,I7N3G6G,1935,1,no description,60.2,random -1936,I7N3G6G,1936,1,no description,60.2,random -1937,I7N3G6G,1937,1,no description,60.2,random -1938,I7N3G6G,1938,1,no description,60.2,random -1939,I7N3G6G,1939,1,no description,60.2,random -1940,I7N3G6G,1940,1,no description,60.2,random -1941,I7N3G6G,1941,1,no description,60.2,random -1942,I7N3G6G,1942,1,no description,60.2,random -1943,I7N3G6G,1943,1,no description,60.2,random -1944,I7N3G6G,1944,1,no description,60.2,random -1945,I7N3G6G,1945,1,no description,60.2,random -1946,I7N3G6G,1946,1,no description,60.2,random -1947,I7N3G6G,1947,1,no description,60.2,random -1948,I7N3G6G,1948,1,no description,60.2,random -1949,I7N3G6G,1949,1,no description,60.2,random -1950,I7N3G6G,1950,1,no description,60.2,random -1951,I7N3G6G,1951,1,no description,60.2,random -1952,I7N3G6G,1952,1,no description,60.2,random -1953,I7N3G6G,1953,1,no description,60.2,random -1954,I7N3G6G,1954,1,no description,60.2,random -1955,I7N3G6G,1955,1,no description,60.2,random -1956,I7N3G6G,1956,1,no description,60.2,random -1957,I7N3G6G,1957,1,no description,60.2,random -1958,I7N3G6G,1958,1,no description,60.2,random -1959,I7N3G6G,1959,1,no description,60.2,random -1960,I7N3G6G,1960,1,no description,60.2,random -1961,I7N3G6G,1961,1,no description,60.2,random -1962,I7N3G6G,1962,1,no description,60.2,random -1963,I7N3G6G,1963,1,no description,60.2,random -1964,I7N3G6G,1964,1,no description,60.2,random -1965,I7N3G6G,1965,1,no description,60.2,random -1966,I7N3G6G,1966,1,no description,60.2,random -1967,I7N3G6G,1967,1,no description,60.2,random -1968,I7N3G6G,1968,1,no description,60.2,random -1969,I7N3G6G,1969,1,no description,60.2,random -1970,I7N3G6G,1970,1,no description,60.2,random -1971,I7N3G6G,1971,1,no description,60.2,random -1972,I7N3G6G,1972,1,no description,60.2,random -1973,I7N3G6G,1973,1,no description,60.2,random -1974,I7N3G6G,1974,1,no description,60.2,random -1975,I7N3G6G,1975,1,no description,60.2,random -1976,I7N3G6G,1976,1,no description,60.2,random -1977,I7N3G6G,1977,1,no description,60.2,random -1978,I7N3G6G,1978,1,no description,60.2,random -1979,I7N3G6G,1979,1,no description,60.2,random -1980,I7N3G6G,1980,1,no description,60.2,random -1981,I7N3G6G,1981,1,no description,60.2,random -1982,I7N3G6G,1982,1,no description,60.2,random -1983,I7N3G6G,1983,1,no description,60.2,random -1984,I7N3G6G,1984,1,no description,60.2,random -1985,I7N3G6G,1985,1,no description,60.2,random -1986,I7N3G6G,1986,1,no description,60.2,random -1987,I7N3G6G,1987,1,no description,60.2,random -1988,I7N3G6G,1988,1,no description,60.2,random -1989,I7N3G6G,1989,1,no description,60.2,random -1990,I7N3G6G,1990,1,no description,60.2,random -1991,I7N3G6G,1991,1,no description,60.2,random -1992,I7N3G6G,1992,1,no description,60.2,random -1993,I7N3G6G,1993,1,no description,60.2,random -1994,I7N3G6G,1994,1,no description,60.2,random -1995,I7N3G6G,1995,1,no description,60.2,random -1996,I7N3G6G,1996,1,no description,60.2,random -1997,I7N3G6G,1997,1,no description,60.2,random -1998,I7N3G6G,1998,1,no description,60.2,random -1999,I7N3G6G,1999,1,no description,60.2,random -2000,I7N3G6G,2000,1,no description,60.2,random -2001,I7N3G6G,2001,1,no description,60.2,random -2002,I7N3G6G,2002,1,no description,60.2,random -2003,I7N3G6G,2003,1,no description,60.2,random -2004,I7N3G6G,2004,1,no description,60.2,random -2005,I7N3G6G,2005,1,no description,60.2,random -2006,I7N3G6G,2006,1,no description,60.2,random -2007,I7N3G6G,2007,1,no description,60.2,random -2008,I7N3G6G,2008,1,no description,60.2,random -2009,I7N3G6G,2009,1,no description,60.2,random -2010,I7N3G6G,2010,1,no description,60.2,random -2011,I7N3G6G,2011,1,no description,60.2,random -2012,I7N3G6G,2012,1,no description,60.2,random -2013,I7N3G6G,2013,1,no description,60.2,random -2014,I7N3G6G,2014,1,no description,60.2,random -2015,I7N3G6G,2015,1,no description,60.2,random -2016,I7N3G6G,2016,1,no description,60.2,random -2017,I7N3G6G,2017,1,no description,60.2,random -2018,I7N3G6G,2018,1,no description,60.2,random -2019,I7N3G6G,2019,1,no description,60.2,random -2020,I7N3G6G,2020,1,no description,60.2,random -2021,I7N3G6G,2021,1,no description,60.2,random -2022,I7N3G6G,2022,1,no description,60.2,random -2023,I7N3G6G,2023,1,no description,60.2,random -2024,I7N3G6G,2024,1,no description,60.2,random -2025,I7N3G6G,2025,1,no description,60.2,random -2026,I7N3G6G,2026,1,no description,60.2,random -2027,I7N3G6G,2027,1,no description,60.2,random -2028,I7N3G6G,2028,1,no description,60.2,random -2029,I7N3G6G,2029,1,no description,60.2,random -2030,I7N3G6G,2030,1,no description,60.2,random -2031,I7N3G6G,2031,1,no description,60.2,random -2032,I7N3G6G,2032,1,no description,60.2,random -2033,I7N3G6G,2033,1,no description,60.2,random -2034,I7N3G6G,2034,1,no description,60.2,random -2035,I7N3G6G,2035,1,no description,60.2,random -2036,I7N3G6G,2036,1,no description,60.2,random -2037,I7N3G6G,2037,1,no description,60.2,random -2038,I7N3G6G,2038,1,no description,60.2,random -2039,I7N3G6G,2039,1,no description,60.2,random -2040,I7N3G6G,2040,1,no description,60.2,random -2041,I7N3G6G,2041,1,no description,60.2,random -2042,I7N3G6G,2042,1,no description,60.2,random -2043,I7N3G6G,2043,1,no description,60.2,random -2044,I7N3G6G,2044,1,no description,60.2,random -2045,I7N3G6G,2045,1,no description,60.2,random -2046,I7N3G6G,2046,1,no description,60.2,random -2047,I7N3G6G,2047,1,no description,60.2,random -2048,I7N3G6G,2048,1,no description,60.2,random -2049,I7N3G6G,2049,1,no description,60.2,random -2050,I7N3G6G,2050,1,no description,60.2,random -2051,I7N3G6G,2051,1,no description,60.2,random -2052,I7N3G6G,2052,1,no description,60.2,random -2053,I7N3G6G,2053,1,no description,60.2,random -2054,I7N3G6G,2054,1,no description,60.2,random -2055,I7N3G6G,2055,1,no description,60.2,random -2056,I7N3G6G,2056,1,no description,60.2,random -2057,I7N3G6G,2057,1,no description,60.2,random -2058,I7N3G6G,2058,1,no description,60.2,random -2059,I7N3G6G,2059,1,no description,60.2,random -2060,I7N3G6G,2060,1,no description,60.2,random -2061,I7N3G6G,2061,1,no description,60.2,random -2062,I7N3G6G,2062,1,no description,60.2,random -2063,I7N3G6G,2063,1,no description,60.2,random -2064,I7N3G6G,2064,1,no description,60.2,random -2065,I7N3G6G,2065,1,no description,60.2,random -2066,I7N3G6G,2066,1,no description,60.2,random -2067,I7N3G6G,2067,1,no description,60.2,random -2068,I7N3G6G,2068,1,no description,60.2,random -2069,I7N3G6G,2069,1,no description,60.2,random -2070,I7N3G6G,2070,1,no description,60.2,random -2071,I7N3G6G,2071,1,no description,60.2,random -2072,I7N3G6G,2072,1,no description,60.2,random -2073,I7N3G6G,2073,1,no description,60.2,random -2074,I7N3G6G,2074,1,no description,60.2,random -2075,I7N3G6G,2075,1,no description,60.2,random -2076,I7N3G6G,2076,1,no description,60.2,random -2077,I7N3G6G,2077,1,no description,60.2,random -2078,I7N3G6G,2078,1,no description,60.2,random -2079,I7N3G6G,2079,1,no description,60.2,random -2080,I7N3G6G,2080,1,no description,60.2,random -2081,I7N3G6G,2081,1,no description,60.2,random -2082,I7N3G6G,2082,1,no description,60.2,random -2083,I7N3G6G,2083,1,no description,60.2,random -2084,I7N3G6G,2084,1,no description,60.2,random -2085,I7N3G6G,2085,1,no description,60.2,random -2086,I7N3G6G,2086,1,no description,60.2,random -2087,I7N3G6G,2087,1,no description,60.2,random -2088,I7N3G6G,2088,1,no description,60.2,random -2089,I7N3G6G,2089,1,no description,60.2,random -2090,I7N3G6G,2090,1,no description,60.2,random -2091,I7N3G6G,2091,1,no description,60.2,random -2092,I7N3G6G,2092,1,no description,60.2,random -2093,I7N3G6G,2093,1,no description,60.2,random -2094,I7N3G6G,2094,1,no description,60.2,random -2095,I7N3G6G,2095,1,no description,60.2,random -2096,I7N3G6G,2096,1,no description,60.2,random -2097,I7N3G6G,2097,1,no description,60.2,random -2098,I7N3G6G,2098,1,no description,60.2,random -2099,I7N3G6G,2099,1,no description,60.2,random -2100,I7N3G6G,2100,1,no description,60.2,random -2101,I7N3G6G,2101,1,no description,60.2,random -2102,I7N3G6G,2102,1,no description,60.2,random -2103,I7N3G6G,2103,1,no description,60.2,random -2104,I7N3G6G,2104,1,no description,60.2,random -2105,I7N3G6G,2105,1,no description,60.2,random -2106,I7N3G6G,2106,1,no description,60.2,random -2107,I7N3G6G,2107,1,no description,60.2,random -2108,I7N3G6G,2108,1,no description,60.2,random -2109,I7N3G6G,2109,1,no description,60.2,random -2110,I7N3G6G,2110,1,no description,60.2,random -2111,I7N3G6G,2111,1,no description,60.2,random -2112,I7N3G6G,2112,1,no description,60.2,random -2113,I7N3G6G,2113,1,no description,60.2,random -2114,I7N3G6G,2114,1,no description,60.2,random -2115,I7N3G6G,2115,1,no description,60.2,random -2116,I7N3G6G,2116,1,no description,60.2,random -2117,I7N3G6G,2117,1,no description,60.2,random -2118,I7N3G6G,2118,1,no description,60.2,random -2119,I7N3G6G,2119,1,no description,60.2,random -2120,I7N3G6G,2120,1,no description,60.2,random -2121,I7N3G6G,2121,1,no description,60.2,random -2122,I7N3G6G,2122,1,no description,60.2,random -2123,I7N3G6G,2123,1,no description,60.2,random -2124,I7N3G6G,2124,1,no description,60.2,random -2125,I7N3G6G,2125,1,no description,60.2,random -2126,I7N3G6G,2126,1,no description,60.2,random -2127,I7N3G6G,2127,1,no description,60.2,random -2128,I7N3G6G,2128,1,no description,60.2,random -2129,I7N3G6G,2129,1,no description,60.2,random -2130,I7N3G6G,2130,1,no description,60.2,random -2131,I7N3G6G,2131,1,no description,60.2,random -2132,I7N3G6G,2132,1,no description,60.2,random -2133,I7N3G6G,2133,1,no description,60.2,random -2134,I7N3G6G,2134,1,no description,60.2,random -2135,I7N3G6G,2135,1,no description,60.2,random -2136,I7N3G6G,2136,1,no description,60.2,random -2137,I7N3G6G,2137,1,no description,60.2,random -2138,I7N3G6G,2138,1,no description,60.2,random -2139,I7N3G6G,2139,1,no description,60.2,random -2140,I7N3G6G,2140,1,no description,60.2,random -2141,I7N3G6G,2141,1,no description,60.2,random -2142,I7N3G6G,2142,1,no description,60.2,random -2143,I7N3G6G,2143,1,no description,60.2,random -2144,I7N3G6G,2144,1,no description,60.2,random -2145,I7N3G6G,2145,1,no description,60.2,random -2146,I7N3G6G,2146,1,no description,60.2,random -2147,I7N3G6G,2147,1,no description,60.2,random -2148,I7N3G6G,2148,1,no description,60.2,random -2149,I7N3G6G,2149,1,no description,60.2,random -2150,I7N3G6G,2150,1,no description,60.2,random -2151,I7N3G6G,2151,1,no description,60.2,random -2152,I7N3G6G,2152,1,no description,60.2,random -2153,I7N3G6G,2153,1,no description,60.2,random -2154,I7N3G6G,2154,1,no description,60.2,random -2155,I7N3G6G,2155,1,no description,60.2,random -2156,I7N3G6G,2156,1,no description,60.2,random -2157,I7N3G6G,2157,1,no description,60.2,random -2158,I7N3G6G,2158,1,no description,60.2,random -2159,I7N3G6G,2159,1,no description,60.2,random -2160,I7N3G6G,2160,1,no description,60.2,random -2161,I7N3G6G,2161,1,no description,60.2,random -2162,I7N3G6G,2162,1,no description,60.2,random -2163,I7N3G6G,2163,1,no description,60.2,random -2164,I7N3G6G,2164,1,no description,60.2,random -2165,I7N3G6G,2165,1,no description,60.2,random -2166,I7N3G6G,2166,1,no description,60.2,random -2167,I7N3G6G,2167,1,no description,60.2,random -2168,I7N3G6G,2168,1,no description,60.2,random -2169,I7N3G6G,2169,1,no description,60.2,random -2170,I7N3G6G,2170,1,no description,60.2,random -2171,I7N3G6G,2171,1,no description,60.2,random -2172,I7N3G6G,2172,1,no description,60.2,random -2173,I7N3G6G,2173,1,no description,60.2,random -2174,I7N3G6G,2174,1,no description,60.2,random -2175,I7N3G6G,2175,1,no description,60.2,random -2176,I7N3G6G,2176,1,no description,60.2,random -2177,I7N3G6G,2177,1,no description,60.2,random -2178,I7N3G6G,2178,1,no description,60.2,random -2179,I7N3G6G,2179,1,no description,60.2,random -2180,I7N3G6G,2180,1,no description,60.2,random -2181,I7N3G6G,2181,1,no description,60.2,random -2182,I7N3G6G,2182,1,no description,60.2,random -2183,I7N3G6G,2183,1,no description,60.2,random -2184,I7N3G6G,2184,1,no description,60.2,random -2185,I7N3G6G,2185,1,no description,60.2,random -2186,I7N3G6G,2186,1,no description,60.2,random -2187,I7N3G6G,2187,1,no description,60.2,random -2188,I7N3G6G,2188,1,no description,60.2,random -2189,I7N3G6G,2189,1,no description,60.2,random -2190,I7N3G6G,2190,1,no description,60.2,random -2191,I7N3G6G,2191,1,no description,60.2,random -2192,I7N3G6G,2192,1,no description,60.2,random -2193,I7N3G6G,2193,1,no description,60.2,random -2194,I7N3G6G,2194,1,no description,60.2,random -2195,I7N3G6G,2195,1,no description,60.2,random -2196,I7N3G6G,2196,1,no description,60.2,random -2197,I7N3G6G,2197,1,no description,60.2,random -2198,I7N3G6G,2198,1,no description,60.2,random -2199,I7N3G6G,2199,1,no description,60.2,random -2200,I7N3G6G,2200,1,no description,60.2,random -2201,I7N3G6G,2201,1,no description,60.2,random -2202,I7N3G6G,2202,1,no description,60.2,random -2203,I7N3G6G,2203,1,no description,60.2,random -2204,I7N3G6G,2204,1,no description,60.2,random -2205,I7N3G6G,2205,1,no description,60.2,random -2206,I7N3G6G,2206,1,no description,60.2,random -2207,I7N3G6G,2207,1,no description,60.2,random -2208,I7N3G6G,2208,1,no description,60.2,random -2209,I7N3G6G,2209,1,no description,60.2,random -2210,I7N3G6G,2210,1,no description,60.2,random -2211,I7N3G6G,2211,1,no description,60.2,random -2212,I7N3G6G,2212,1,no description,60.2,random -2213,I7N3G6G,2213,1,no description,60.2,random -2214,I7N3G6G,2214,1,no description,60.2,random -2215,I7N3G6G,2215,1,no description,60.2,random -2216,I7N3G6G,2216,1,no description,60.2,random -2217,I7N3G6G,2217,1,no description,60.2,random -2218,I7N3G6G,2218,1,no description,60.2,random -2219,I7N3G6G,2219,1,no description,60.2,random -2220,I7N3G6G,2220,1,no description,60.2,random -2221,I7N3G6G,2221,1,no description,60.2,random -2222,I7N3G6G,2222,1,no description,60.2,random -2223,I7N3G6G,2223,1,no description,60.2,random -2224,I7N3G6G,2224,1,no description,60.2,random -2225,I7N3G6G,2225,1,no description,60.2,random -2226,I7N3G6G,2226,1,no description,60.2,random diff --git a/packages/itmat-job-executor/test/testFiles/CSVCurator_error2.tsv b/packages/itmat-job-executor/test/testFiles/CSVCurator_error2.tsv deleted file mode 100644 index 27535ce6c..000000000 --- a/packages/itmat-job-executor/test/testFiles/CSVCurator_error2.tsv +++ /dev/null @@ -1,2227 +0,0 @@ -ID,SubjectID,VisitID,Sex,Description,Description -1,I7N3G6G,1,0,no description,60.2 -2,I7N3G6G,2,1,no description,60.2 -3,I7N3G6G,3,1,no description,60.2 -4,I7N3G6G,4,1,no description,60.2 -5,I7N3G6G,5,1,no description,60.2 -6,I7N3G6G,6,1,no description,60.2 -7,I7N3G6G,7,1,no description,60.2 -8,I7N3G6G,8,1,no description,60.2 -9,I7N3G6G,9,1,no description,60.2 -10,I7N3G6G,10,1,no description,60.2 -11,I7N3G6G,11,1,no description,60.2 -12,I7N3G6G,12,1,no description,60.2 -13,I7N3G6G,13,1,no description,60.2 -14,I7N3G6G,14,1,no description,60.2 -15,I7N3G6G,15,1,no description,60.2 -16,I7N3G6G,16,1,no description,60.2 -17,I7N3G6G,17,1,no description,60.2 -18,I7N3G6G,18,1,no description,60.2 -19,I7N3G6G,19,1,no description,60.2 -20,I7N3G6G,20,1,no description,60.2 -21,I7N3G6G,21,1,no description,60.2 -22,I7N3G6G,22,1,no description,60.2 -23,I7N3G6G,23,1,no description,60.2 -24,I7N3G6G,24,1,no description,60.2 -25,I7N3G6G,25,1,no description,60.2 -26,I7N3G6G,26,1,no description,60.2 -27,I7N3G6G,27,1,no description,60.2 -28,I7N3G6G,28,1,no description,60.2 -29,I7N3G6G,29,1,no description,60.2 -30,I7N3G6G,30,1,no description,60.2 -31,I7N3G6G,31,1,no description,60.2 -32,I7N3G6G,32,1,no description,60.2 -33,I7N3G6G,33,1,no description,60.2 -34,I7N3G6G,34,1,no description,60.2 -35,I7N3G6G,35,1,no description,60.2 -36,I7N3G6G,36,1,no description,60.2 -37,I7N3G6G,37,1,no description,60.2 -38,I7N3G6G,38,1,no description,60.2 -39,I7N3G6G,39,1,no description,60.2 -40,I7N3G6G,40,1,no description,60.2 -41,I7N3G6G,41,1,no description,60.2 -42,I7N3G6G,42,1,no description,60.2 -43,I7N3G6G,43,1,no description,60.2 -44,I7N3G6G,44,1,no description,60.2 -45,I7N3G6G,45,1,no description,60.2 -46,I7N3G6G,46,1,no description,60.2 -47,I7N3G6G,47,1,no description,60.2 -48,I7N3G6G,48,1,no description,60.2 -49,I7N3G6G,49,1,no description,60.2 -50,I7N3G6G,50,1,no description,60.2 -51,I7N3G6G,51,1,no description,60.2 -52,I7N3G6G,52,1,no description,60.2 -53,I7N3G6G,53,1,no description,60.2 -54,I7N3G6G,54,1,no description,60.2 -55,I7N3G6G,55,1,no description,60.2 -56,I7N3G6G,56,1,no description,60.2 -57,I7N3G6G,57,1,no description,60.2 -58,I7N3G6G,58,1,no description,60.2 -59,I7N3G6G,59,1,no description,60.2 -60,I7N3G6G,60,1,no description,60.2 -61,I7N3G6G,61,1,no description,60.2 -62,I7N3G6G,62,1,no description,60.2 -63,I7N3G6G,63,1,no description,60.2 -64,I7N3G6G,64,1,no description,60.2 -65,I7N3G6G,65,1,no description,60.2 -66,I7N3G6G,66,1,no description,60.2 -67,I7N3G6G,67,1,no description,60.2 -68,I7N3G6G,68,1,no description,60.2 -69,I7N3G6G,69,1,no description,60.2 -70,I7N3G6G,70,1,no description,60.2 -71,I7N3G6G,71,1,no description,60.2 -72,I7N3G6G,72,1,no description,60.2 -73,I7N3G6G,73,1,no description,60.2 -74,I7N3G6G,74,1,no description,60.2 -75,I7N3G6G,75,1,no description,60.2 -76,I7N3G6G,76,1,no description,60.2 -77,I7N3G6G,77,1,no description,60.2 -78,I7N3G6G,78,1,no description,60.2 -79,I7N3G6G,79,1,no description,60.2 -80,I7N3G6G,80,1,no description,60.2 -81,I7N3G6G,81,1,no description,60.2 -82,I7N3G6G,82,1,no description,60.2 -83,I7N3G6G,83,1,no description,60.2 -84,I7N3G6G,84,1,no description,60.2 -85,I7N3G6G,85,1,no description,60.2 -86,I7N3G6G,86,1,no description,60.2 -87,I7N3G6G,87,1,no description,60.2 -88,I7N3G6G,88,1,no description,60.2 -89,I7N3G6G,89,1,no description,60.2 -90,I7N3G6G,90,1,no description,60.2 -91,I7N3G6G,91,1,no description,60.2 -92,I7N3G6G,92,1,no description,60.2 -93,I7N3G6G,93,1,no description,60.2 -94,I7N3G6G,94,1,no description,60.2 -95,I7N3G6G,95,1,no description,60.2 -96,I7N3G6G,96,1,no description,60.2 -97,I7N3G6G,97,1,no description,60.2 -98,I7N3G6G,98,1,no description,60.2 -99,I7N3G6G,99,1,no description,60.2 -100,I7N3G6G,100,1,no description,60.2 -101,I7N3G6G,101,1,no description,60.2 -102,I7N3G6G,102,1,no description,60.2 -103,I7N3G6G,103,1,no description,60.2 -104,I7N3G6G,104,1,no description,60.2 -105,I7N3G6G,105,1,no description,60.2 -106,I7N3G6G,106,1,no description,60.2 -107,I7N3G6G,107,1,no description,60.2 -108,I7N3G6G,108,1,no description,60.2 -109,I7N3G6G,109,1,no description,60.2 -110,I7N3G6G,110,1,no description,60.2 -111,I7N3G6G,111,1,no description,60.2 -112,I7N3G6G,112,1,no description,60.2 -113,I7N3G6G,113,1,no description,60.2 -114,I7N3G6G,114,1,no description,60.2 -115,I7N3G6G,115,1,no description,60.2 -116,I7N3G6G,116,1,no description,60.2 -117,I7N3G6G,117,1,no description,60.2 -118,I7N3G6G,118,1,no description,60.2 -119,I7N3G6G,119,1,no description,60.2 -120,I7N3G6G,120,1,no description,60.2 -121,I7N3G6G,121,1,no description,60.2 -122,I7N3G6G,122,1,no description,60.2 -123,I7N3G6G,123,1,no description,60.2 -124,I7N3G6G,124,1,no description,60.2 -125,I7N3G6G,125,1,no description,60.2 -126,I7N3G6G,126,1,no description,60.2 -127,I7N3G6G,127,1,no description,60.2 -128,I7N3G6G,128,1,no description,60.2 -129,I7N3G6G,129,1,no description,60.2 -130,I7N3G6G,130,1,no description,60.2 -131,I7N3G6G,131,1,no description,60.2 -132,I7N3G6G,132,1,no description,60.2 -133,I7N3G6G,133,1,no description,60.2 -134,I7N3G6G,134,1,no description,60.2 -135,I7N3G6G,135,1,no description,60.2 -136,I7N3G6G,136,1,no description,60.2 -137,I7N3G6G,137,1,no description,60.2 -138,I7N3G6G,138,1,no description,60.2 -139,I7N3G6G,139,1,no description,60.2 -140,I7N3G6G,140,1,no description,60.2 -141,I7N3G6G,141,1,no description,60.2 -142,I7N3G6G,142,1,no description,60.2 -143,I7N3G6G,143,1,no description,60.2 -144,I7N3G6G,144,1,no description,60.2 -145,I7N3G6G,145,1,no description,60.2 -146,I7N3G6G,146,1,no description,60.2 -147,I7N3G6G,147,1,no description,60.2 -148,I7N3G6G,148,1,no description,60.2 -149,I7N3G6G,149,1,no description,60.2 -150,I7N3G6G,150,1,no description,60.2 -151,I7N3G6G,151,1,no description,60.2 -152,I7N3G6G,152,1,no description,60.2 -153,I7N3G6G,153,1,no description,60.2 -154,I7N3G6G,154,1,no description,60.2 -155,I7N3G6G,155,1,no description,60.2 -156,I7N3G6G,156,1,no description,60.2 -157,I7N3G6G,157,1,no description,60.2 -158,I7N3G6G,158,1,no description,60.2 -159,I7N3G6G,159,1,no description,60.2 -160,I7N3G6G,160,1,no description,60.2 -161,I7N3G6G,161,1,no description,60.2 -162,I7N3G6G,162,1,no description,60.2 -163,I7N3G6G,163,1,no description,60.2 -164,I7N3G6G,164,1,no description,60.2 -165,I7N3G6G,165,1,no description,60.2 -166,I7N3G6G,166,1,no description,60.2 -167,I7N3G6G,167,1,no description,60.2 -168,I7N3G6G,168,1,no description,60.2 -169,I7N3G6G,169,1,no description,60.2 -170,I7N3G6G,170,1,no description,60.2 -171,I7N3G6G,171,1,no description,60.2 -172,I7N3G6G,172,1,no description,60.2 -173,I7N3G6G,173,1,no description,60.2 -174,I7N3G6G,174,1,no description,60.2 -175,I7N3G6G,175,1,no description,60.2 -176,I7N3G6G,176,1,no description,60.2 -177,I7N3G6G,177,1,no description,60.2 -178,I7N3G6G,178,1,no description,60.2 -179,I7N3G6G,179,1,no description,60.2 -180,I7N3G6G,180,1,no description,60.2 -181,I7N3G6G,181,1,no description,60.2 -182,I7N3G6G,182,1,no description,60.2 -183,I7N3G6G,183,1,no description,60.2 -184,I7N3G6G,184,1,no description,60.2 -185,I7N3G6G,185,1,no description,60.2 -186,I7N3G6G,186,1,no description,60.2 -187,I7N3G6G,187,1,no description,60.2 -188,I7N3G6G,188,1,no description,60.2 -189,I7N3G6G,189,1,no description,60.2 -190,I7N3G6G,190,1,no description,60.2 -191,I7N3G6G,191,1,no description,60.2 -192,I7N3G6G,192,1,no description,60.2 -193,I7N3G6G,193,1,no description,60.2 -194,I7N3G6G,194,1,no description,60.2 -195,I7N3G6G,195,1,no description,60.2 -196,I7N3G6G,196,1,no description,60.2 -197,I7N3G6G,197,1,no description,60.2 -198,I7N3G6G,198,1,no description,60.2 -199,I7N3G6G,199,1,no description,60.2 -200,I7N3G6G,200,1,no description,60.2 -201,I7N3G6G,201,1,no description,60.2 -202,I7N3G6G,202,1,no description,60.2 -203,I7N3G6G,203,1,no description,60.2 -204,I7N3G6G,204,1,no description,60.2 -205,I7N3G6G,205,1,no description,60.2 -206,I7N3G6G,206,1,no description,60.2 -207,I7N3G6G,207,1,no description,60.2 -208,I7N3G6G,208,1,no description,60.2 -209,I7N3G6G,209,1,no description,60.2 -210,I7N3G6G,210,1,no description,60.2 -211,I7N3G6G,211,1,no description,60.2 -212,I7N3G6G,212,1,no description,60.2 -213,I7N3G6G,213,1,no description,60.2 -214,I7N3G6G,214,1,no description,60.2 -215,I7N3G6G,215,1,no description,60.2 -216,I7N3G6G,216,1,no description,60.2 -217,I7N3G6G,217,1,no description,60.2 -218,I7N3G6G,218,1,no description,60.2 -219,I7N3G6G,219,1,no description,60.2 -220,I7N3G6G,220,1,no description,60.2 -221,I7N3G6G,221,1,no description,60.2 -222,I7N3G6G,222,1,no description,60.2 -223,I7N3G6G,223,1,no description,60.2 -224,I7N3G6G,224,1,no description,60.2 -225,I7N3G6G,225,1,no description,60.2 -226,I7N3G6G,226,1,no description,60.2 -227,I7N3G6G,227,1,no description,60.2 -228,I7N3G6G,228,1,no description,60.2 -229,I7N3G6G,229,1,no description,60.2 -230,I7N3G6G,230,1,no description,60.2 -231,I7N3G6G,231,1,no description,60.2 -232,I7N3G6G,232,1,no description,60.2 -233,I7N3G6G,233,1,no description,60.2 -234,I7N3G6G,234,1,no description,60.2 -235,I7N3G6G,235,1,no description,60.2 -236,I7N3G6G,236,1,no description,60.2 -237,I7N3G6G,237,1,no description,60.2 -238,I7N3G6G,238,1,no description,60.2 -239,I7N3G6G,239,1,no description,60.2 -240,I7N3G6G,240,1,no description,60.2 -241,I7N3G6G,241,1,no description,60.2 -242,I7N3G6G,242,1,no description,60.2 -243,I7N3G6G,243,1,no description,60.2 -244,I7N3G6G,244,1,no description,60.2 -245,I7N3G6G,245,1,no description,60.2 -246,I7N3G6G,246,1,no description,60.2 -247,I7N3G6G,247,1,no description,60.2 -248,I7N3G6G,248,1,no description,60.2 -249,I7N3G6G,249,1,no description,60.2 -250,I7N3G6G,250,1,no description,60.2 -251,I7N3G6G,251,1,no description,60.2 -252,I7N3G6G,252,1,no description,60.2 -253,I7N3G6G,253,1,no description,60.2 -254,I7N3G6G,254,1,no description,60.2 -255,I7N3G6G,255,1,no description,60.2 -256,I7N3G6G,256,1,no description,60.2 -257,I7N3G6G,257,1,no description,60.2 -258,I7N3G6G,258,1,no description,60.2 -259,I7N3G6G,259,1,no description,60.2 -260,I7N3G6G,260,1,no description,60.2 -261,I7N3G6G,261,1,no description,60.2 -262,I7N3G6G,262,1,no description,60.2 -263,I7N3G6G,263,1,no description,60.2 -264,I7N3G6G,264,1,no description,60.2 -265,I7N3G6G,265,1,no description,60.2 -266,I7N3G6G,266,1,no description,60.2 -267,I7N3G6G,267,1,no description,60.2 -268,I7N3G6G,268,1,no description,60.2 -269,I7N3G6G,269,1,no description,60.2 -270,I7N3G6G,270,1,no description,60.2 -271,I7N3G6G,271,1,no description,60.2 -272,I7N3G6G,272,1,no description,60.2 -273,I7N3G6G,273,1,no description,60.2 -274,I7N3G6G,274,1,no description,60.2 -275,I7N3G6G,275,1,no description,60.2 -276,I7N3G6G,276,1,no description,60.2 -277,I7N3G6G,277,1,no description,60.2 -278,I7N3G6G,278,1,no description,60.2 -279,I7N3G6G,279,1,no description,60.2 -280,I7N3G6G,280,1,no description,60.2 -281,I7N3G6G,281,1,no description,60.2 -282,I7N3G6G,282,1,no description,60.2 -283,I7N3G6G,283,1,no description,60.2 -284,I7N3G6G,284,1,no description,60.2 -285,I7N3G6G,285,1,no description,60.2 -286,I7N3G6G,286,1,no description,60.2 -287,I7N3G6G,287,1,no description,60.2 -288,I7N3G6G,288,1,no description,60.2 -289,I7N3G6G,289,1,no description,60.2 -290,I7N3G6G,290,1,no description,60.2 -291,I7N3G6G,291,1,no description,60.2 -292,I7N3G6G,292,1,no description,60.2 -293,I7N3G6G,293,1,no description,60.2 -294,I7N3G6G,294,1,no description,60.2 -295,I7N3G6G,295,1,no description,60.2 -296,I7N3G6G,296,1,no description,60.2 -297,I7N3G6G,297,1,no description,60.2 -298,I7N3G6G,298,1,no description,60.2 -299,I7N3G6G,299,1,no description,60.2 -300,I7N3G6G,300,1,no description,60.2 -301,I7N3G6G,301,1,no description,60.2 -302,I7N3G6G,302,1,no description,60.2 -303,I7N3G6G,303,1,no description,60.2 -304,I7N3G6G,304,1,no description,60.2 -305,I7N3G6G,305,1,no description,60.2 -306,I7N3G6G,306,1,no description,60.2 -307,I7N3G6G,307,1,no description,60.2 -308,I7N3G6G,308,1,no description,60.2 -309,I7N3G6G,309,1,no description,60.2 -310,I7N3G6G,310,1,no description,60.2 -311,I7N3G6G,311,1,no description,60.2 -312,I7N3G6G,312,1,no description,60.2 -313,I7N3G6G,313,1,no description,60.2 -314,I7N3G6G,314,1,no description,60.2 -315,I7N3G6G,315,1,no description,60.2 -316,I7N3G6G,316,1,no description,60.2 -317,I7N3G6G,317,1,no description,60.2 -318,I7N3G6G,318,1,no description,60.2 -319,I7N3G6G,319,1,no description,60.2 -320,I7N3G6G,320,1,no description,60.2 -321,I7N3G6G,321,1,no description,60.2 -322,I7N3G6G,322,1,no description,60.2 -323,I7N3G6G,323,1,no description,60.2 -324,I7N3G6G,324,1,no description,60.2 -325,I7N3G6G,325,1,no description,60.2 -326,I7N3G6G,326,1,no description,60.2 -327,I7N3G6G,327,1,no description,60.2 -328,I7N3G6G,328,1,no description,60.2 -329,I7N3G6G,329,1,no description,60.2 -330,I7N3G6G,330,1,no description,60.2 -331,I7N3G6G,331,1,no description,60.2 -332,I7N3G6G,332,1,no description,60.2 -333,I7N3G6G,333,1,no description,60.2 -334,I7N3G6G,334,1,no description,60.2 -335,I7N3G6G,335,1,no description,60.2 -336,I7N3G6G,336,1,no description,60.2 -337,I7N3G6G,337,1,no description,60.2 -338,I7N3G6G,338,1,no description,60.2 -339,I7N3G6G,339,1,no description,60.2 -340,I7N3G6G,340,1,no description,60.2 -341,I7N3G6G,341,1,no description,60.2 -342,I7N3G6G,342,1,no description,60.2 -343,I7N3G6G,343,1,no description,60.2 -344,I7N3G6G,344,1,no description,60.2 -345,I7N3G6G,345,1,no description,60.2 -346,I7N3G6G,346,1,no description,60.2 -347,I7N3G6G,347,1,no description,60.2 -348,I7N3G6G,348,1,no description,60.2 -349,I7N3G6G,349,1,no description,60.2 -350,I7N3G6G,350,1,no description,60.2 -351,I7N3G6G,351,1,no description,60.2 -352,I7N3G6G,352,1,no description,60.2 -353,I7N3G6G,353,1,no description,60.2 -354,I7N3G6G,354,1,no description,60.2 -355,I7N3G6G,355,1,no description,60.2 -356,I7N3G6G,356,1,no description,60.2 -357,I7N3G6G,357,1,no description,60.2 -358,I7N3G6G,358,1,no description,60.2 -359,I7N3G6G,359,1,no description,60.2 -360,I7N3G6G,360,1,no description,60.2 -361,I7N3G6G,361,1,no description,60.2 -362,I7N3G6G,362,1,no description,60.2 -363,I7N3G6G,363,1,no description,60.2 -364,I7N3G6G,364,1,no description,60.2 -365,I7N3G6G,365,1,no description,60.2 -366,I7N3G6G,366,1,no description,60.2 -367,I7N3G6G,367,1,no description,60.2 -368,I7N3G6G,368,1,no description,60.2 -369,I7N3G6G,369,1,no description,60.2 -370,I7N3G6G,370,1,no description,60.2 -371,I7N3G6G,371,1,no description,60.2 -372,I7N3G6G,372,1,no description,60.2 -373,I7N3G6G,373,1,no description,60.2 -374,I7N3G6G,374,1,no description,60.2 -375,I7N3G6G,375,1,no description,60.2 -376,I7N3G6G,376,1,no description,60.2 -377,I7N3G6G,377,1,no description,60.2 -378,I7N3G6G,378,1,no description,60.2 -379,I7N3G6G,379,1,no description,60.2 -380,I7N3G6G,380,1,no description,60.2 -381,I7N3G6G,381,1,no description,60.2 -382,I7N3G6G,382,1,no description,60.2 -383,I7N3G6G,383,1,no description,60.2 -384,I7N3G6G,384,1,no description,60.2 -385,I7N3G6G,385,1,no description,60.2 -386,I7N3G6G,386,1,no description,60.2 -387,I7N3G6G,387,1,no description,60.2 -388,I7N3G6G,388,1,no description,60.2 -389,I7N3G6G,389,1,no description,60.2 -390,I7N3G6G,390,1,no description,60.2 -391,I7N3G6G,391,1,no description,60.2 -392,I7N3G6G,392,1,no description,60.2 -393,I7N3G6G,393,1,no description,60.2 -394,I7N3G6G,394,1,no description,60.2 -395,I7N3G6G,395,1,no description,60.2 -396,I7N3G6G,396,1,no description,60.2 -397,I7N3G6G,397,1,no description,60.2 -398,I7N3G6G,398,1,no description,60.2 -399,I7N3G6G,399,1,no description,60.2 -400,I7N3G6G,400,1,no description,60.2 -401,I7N3G6G,401,1,no description,60.2 -402,I7N3G6G,402,1,no description,60.2 -403,I7N3G6G,403,1,no description,60.2 -404,I7N3G6G,404,1,no description,60.2 -405,I7N3G6G,405,1,no description,60.2 -406,I7N3G6G,406,1,no description,60.2 -407,I7N3G6G,407,1,no description,60.2 -408,I7N3G6G,408,1,no description,60.2 -409,I7N3G6G,409,1,no description,60.2 -410,I7N3G6G,410,1,no description,60.2 -411,I7N3G6G,411,1,no description,60.2 -412,I7N3G6G,412,1,no description,60.2 -413,I7N3G6G,413,1,no description,60.2 -414,I7N3G6G,414,1,no description,60.2 -415,I7N3G6G,415,1,no description,60.2 -416,I7N3G6G,416,1,no description,60.2 -417,I7N3G6G,417,1,no description,60.2 -418,I7N3G6G,418,1,no description,60.2 -419,I7N3G6G,419,1,no description,60.2 -420,I7N3G6G,420,1,no description,60.2 -421,I7N3G6G,421,1,no description,60.2 -422,I7N3G6G,422,1,no description,60.2 -423,I7N3G6G,423,1,no description,60.2 -424,I7N3G6G,424,1,no description,60.2 -425,I7N3G6G,425,1,no description,60.2 -426,I7N3G6G,426,1,no description,60.2 -427,I7N3G6G,427,1,no description,60.2 -428,I7N3G6G,428,1,no description,60.2 -429,I7N3G6G,429,1,no description,60.2 -430,I7N3G6G,430,1,no description,60.2 -431,I7N3G6G,431,1,no description,60.2 -432,I7N3G6G,432,1,no description,60.2 -433,I7N3G6G,433,1,no description,60.2 -434,I7N3G6G,434,1,no description,60.2 -435,I7N3G6G,435,1,no description,60.2 -436,I7N3G6G,436,1,no description,60.2 -437,I7N3G6G,437,1,no description,60.2 -438,I7N3G6G,438,1,no description,60.2 -439,I7N3G6G,439,1,no description,60.2 -440,I7N3G6G,440,1,no description,60.2 -441,I7N3G6G,441,1,no description,60.2 -442,I7N3G6G,442,1,no description,60.2 -443,I7N3G6G,443,1,no description,60.2 -444,I7N3G6G,444,1,no description,60.2 -445,I7N3G6G,445,1,no description,60.2 -446,I7N3G6G,446,1,no description,60.2 -447,I7N3G6G,447,1,no description,60.2 -448,I7N3G6G,448,1,no description,60.2 -449,I7N3G6G,449,1,no description,60.2 -450,I7N3G6G,450,1,no description,60.2 -451,I7N3G6G,451,1,no description,60.2 -452,I7N3G6G,452,1,no description,60.2 -453,I7N3G6G,453,1,no description,60.2 -454,I7N3G6G,454,1,no description,60.2 -455,I7N3G6G,455,1,no description,60.2 -456,I7N3G6G,456,1,no description,60.2 -457,I7N3G6G,457,1,no description,60.2 -458,I7N3G6G,458,1,no description,60.2 -459,I7N3G6G,459,1,no description,60.2 -460,I7N3G6G,460,1,no description,60.2 -461,I7N3G6G,461,1,no description,60.2 -462,I7N3G6G,462,1,no description,60.2 -463,I7N3G6G,463,1,no description,60.2 -464,I7N3G6G,464,1,no description,60.2 -465,I7N3G6G,465,1,no description,60.2 -466,I7N3G6G,466,1,no description,60.2 -467,I7N3G6G,467,1,no description,60.2 -468,I7N3G6G,468,1,no description,60.2 -469,I7N3G6G,469,1,no description,60.2 -470,I7N3G6G,470,1,no description,60.2 -471,I7N3G6G,471,1,no description,60.2 -472,I7N3G6G,472,1,no description,60.2 -473,I7N3G6G,473,1,no description,60.2 -474,I7N3G6G,474,1,no description,60.2 -475,I7N3G6G,475,1,no description,60.2 -476,I7N3G6G,476,1,no description,60.2 -477,I7N3G6G,477,1,no description,60.2 -478,I7N3G6G,478,1,no description,60.2 -479,I7N3G6G,479,1,no description,60.2 -480,I7N3G6G,480,1,no description,60.2 -481,I7N3G6G,481,1,no description,60.2 -482,I7N3G6G,482,1,no description,60.2 -483,I7N3G6G,483,1,no description,60.2 -484,I7N3G6G,484,1,no description,60.2 -485,I7N3G6G,485,1,no description,60.2 -486,I7N3G6G,486,1,no description,60.2 -487,I7N3G6G,487,1,no description,60.2 -488,I7N3G6G,488,1,no description,60.2 -489,I7N3G6G,489,1,no description,60.2 -490,I7N3G6G,490,1,no description,60.2 -491,I7N3G6G,491,1,no description,60.2 -492,I7N3G6G,492,1,no description,60.2 -493,I7N3G6G,493,1,no description,60.2 -494,I7N3G6G,494,1,no description,60.2 -495,I7N3G6G,495,1,no description,60.2 -496,I7N3G6G,496,1,no description,60.2 -497,I7N3G6G,497,1,no description,60.2 -498,I7N3G6G,498,1,no description,60.2 -499,I7N3G6G,499,1,no description,60.2 -500,I7N3G6G,500,1,no description,60.2 -501,I7N3G6G,501,1,no description,60.2 -502,I7N3G6G,502,1,no description,60.2 -503,I7N3G6G,503,1,no description,60.2 -504,I7N3G6G,504,1,no description,60.2 -505,I7N3G6G,505,1,no description,60.2 -506,I7N3G6G,506,1,no description,60.2 -507,I7N3G6G,507,1,no description,60.2 -508,I7N3G6G,508,1,no description,60.2 -509,I7N3G6G,509,1,no description,60.2 -510,I7N3G6G,510,1,no description,60.2 -511,I7N3G6G,511,1,no description,60.2 -512,I7N3G6G,512,1,no description,60.2 -513,I7N3G6G,513,1,no description,60.2 -514,I7N3G6G,514,1,no description,60.2 -515,I7N3G6G,515,1,no description,60.2 -516,I7N3G6G,516,1,no description,60.2 -517,I7N3G6G,517,1,no description,60.2 -518,I7N3G6G,518,1,no description,60.2 -519,I7N3G6G,519,1,no description,60.2 -520,I7N3G6G,520,1,no description,60.2 -521,I7N3G6G,521,1,no description,60.2 -522,I7N3G6G,522,1,no description,60.2 -523,I7N3G6G,523,1,no description,60.2 -524,I7N3G6G,524,1,no description,60.2 -525,I7N3G6G,525,1,no description,60.2 -526,I7N3G6G,526,1,no description,60.2 -527,I7N3G6G,527,1,no description,60.2 -528,I7N3G6G,528,1,no description,60.2 -529,I7N3G6G,529,1,no description,60.2 -530,I7N3G6G,530,1,no description,60.2 -531,I7N3G6G,531,1,no description,60.2 -532,I7N3G6G,532,1,no description,60.2 -533,I7N3G6G,533,1,no description,60.2 -534,I7N3G6G,534,1,no description,60.2 -535,I7N3G6G,535,1,no description,60.2 -536,I7N3G6G,536,1,no description,60.2 -537,I7N3G6G,537,1,no description,60.2 -538,I7N3G6G,538,1,no description,60.2 -539,I7N3G6G,539,1,no description,60.2 -540,I7N3G6G,540,1,no description,60.2 -541,I7N3G6G,541,1,no description,60.2 -542,I7N3G6G,542,1,no description,60.2 -543,I7N3G6G,543,1,no description,60.2 -544,I7N3G6G,544,1,no description,60.2 -545,I7N3G6G,545,1,no description,60.2 -546,I7N3G6G,546,1,no description,60.2 -547,I7N3G6G,547,1,no description,60.2 -548,I7N3G6G,548,1,no description,60.2 -549,I7N3G6G,549,1,no description,60.2 -550,I7N3G6G,550,1,no description,60.2 -551,I7N3G6G,551,1,no description,60.2 -552,I7N3G6G,552,1,no description,60.2 -553,I7N3G6G,553,1,no description,60.2 -554,I7N3G6G,554,1,no description,60.2 -555,I7N3G6G,555,1,no description,60.2 -556,I7N3G6G,556,1,no description,60.2 -557,I7N3G6G,557,1,no description,60.2 -558,I7N3G6G,558,1,no description,60.2 -559,I7N3G6G,559,1,no description,60.2 -560,I7N3G6G,560,1,no description,60.2 -561,I7N3G6G,561,1,no description,60.2 -562,I7N3G6G,562,1,no description,60.2 -563,I7N3G6G,563,1,no description,60.2 -564,I7N3G6G,564,1,no description,60.2 -565,I7N3G6G,565,1,no description,60.2 -566,I7N3G6G,566,1,no description,60.2 -567,I7N3G6G,567,1,no description,60.2 -568,I7N3G6G,568,1,no description,60.2 -569,I7N3G6G,569,1,no description,60.2 -570,I7N3G6G,570,1,no description,60.2 -571,I7N3G6G,571,1,no description,60.2 -572,I7N3G6G,572,1,no description,60.2 -573,I7N3G6G,573,1,no description,60.2 -574,I7N3G6G,574,1,no description,60.2 -575,I7N3G6G,575,1,no description,60.2 -576,I7N3G6G,576,1,no description,60.2 -577,I7N3G6G,577,1,no description,60.2 -578,I7N3G6G,578,1,no description,60.2 -579,I7N3G6G,579,1,no description,60.2 -580,I7N3G6G,580,1,no description,60.2 -581,I7N3G6G,581,1,no description,60.2 -582,I7N3G6G,582,1,no description,60.2 -583,I7N3G6G,583,1,no description,60.2 -584,I7N3G6G,584,1,no description,60.2 -585,I7N3G6G,585,1,no description,60.2 -586,I7N3G6G,586,1,no description,60.2 -587,I7N3G6G,587,1,no description,60.2 -588,I7N3G6G,588,1,no description,60.2 -589,I7N3G6G,589,1,no description,60.2 -590,I7N3G6G,590,1,no description,60.2 -591,I7N3G6G,591,1,no description,60.2 -592,I7N3G6G,592,1,no description,60.2 -593,I7N3G6G,593,1,no description,60.2 -594,I7N3G6G,594,1,no description,60.2 -595,I7N3G6G,595,1,no description,60.2 -596,I7N3G6G,596,1,no description,60.2 -597,I7N3G6G,597,1,no description,60.2 -598,I7N3G6G,598,1,no description,60.2 -599,I7N3G6G,599,1,no description,60.2 -600,I7N3G6G,600,1,no description,60.2 -601,I7N3G6G,601,1,no description,60.2 -602,I7N3G6G,602,1,no description,60.2 -603,I7N3G6G,603,1,no description,60.2 -604,I7N3G6G,604,1,no description,60.2 -605,I7N3G6G,605,1,no description,60.2 -606,I7N3G6G,606,1,no description,60.2 -607,I7N3G6G,607,1,no description,60.2 -608,I7N3G6G,608,1,no description,60.2 -609,I7N3G6G,609,1,no description,60.2 -610,I7N3G6G,610,1,no description,60.2 -611,I7N3G6G,611,1,no description,60.2 -612,I7N3G6G,612,1,no description,60.2 -613,I7N3G6G,613,1,no description,60.2 -614,I7N3G6G,614,1,no description,60.2 -615,I7N3G6G,615,1,no description,60.2 -616,I7N3G6G,616,1,no description,60.2 -617,I7N3G6G,617,1,no description,60.2 -618,I7N3G6G,618,1,no description,60.2 -619,I7N3G6G,619,1,no description,60.2 -620,I7N3G6G,620,1,no description,60.2 -621,I7N3G6G,621,1,no description,60.2 -622,I7N3G6G,622,1,no description,60.2 -623,I7N3G6G,623,1,no description,60.2 -624,I7N3G6G,624,1,no description,60.2 -625,I7N3G6G,625,1,no description,60.2 -626,I7N3G6G,626,1,no description,60.2 -627,I7N3G6G,627,1,no description,60.2 -628,I7N3G6G,628,1,no description,60.2 -629,I7N3G6G,629,1,no description,60.2 -630,I7N3G6G,630,1,no description,60.2 -631,I7N3G6G,631,1,no description,60.2 -632,I7N3G6G,632,1,no description,60.2 -633,I7N3G6G,633,1,no description,60.2 -634,I7N3G6G,634,1,no description,60.2 -635,I7N3G6G,635,1,no description,60.2 -636,I7N3G6G,636,1,no description,60.2 -637,I7N3G6G,637,1,no description,60.2 -638,I7N3G6G,638,1,no description,60.2 -639,I7N3G6G,639,1,no description,60.2 -640,I7N3G6G,640,1,no description,60.2 -641,I7N3G6G,641,1,no description,60.2 -642,I7N3G6G,642,1,no description,60.2 -643,I7N3G6G,643,1,no description,60.2 -644,I7N3G6G,644,1,no description,60.2 -645,I7N3G6G,645,1,no description,60.2 -646,I7N3G6G,646,1,no description,60.2 -647,I7N3G6G,647,1,no description,60.2 -648,I7N3G6G,648,1,no description,60.2 -649,I7N3G6G,649,1,no description,60.2 -650,I7N3G6G,650,1,no description,60.2 -651,I7N3G6G,651,1,no description,60.2 -652,I7N3G6G,652,1,no description,60.2 -653,I7N3G6G,653,1,no description,60.2 -654,I7N3G6G,654,1,no description,60.2 -655,I7N3G6G,655,1,no description,60.2 -656,I7N3G6G,656,1,no description,60.2 -657,I7N3G6G,657,1,no description,60.2 -658,I7N3G6G,658,1,no description,60.2 -659,I7N3G6G,659,1,no description,60.2 -660,I7N3G6G,660,1,no description,60.2 -661,I7N3G6G,661,1,no description,60.2 -662,I7N3G6G,662,1,no description,60.2 -663,I7N3G6G,663,1,no description,60.2 -664,I7N3G6G,664,1,no description,60.2 -665,I7N3G6G,665,1,no description,60.2 -666,I7N3G6G,666,1,no description,60.2 -667,I7N3G6G,667,1,no description,60.2 -668,I7N3G6G,668,1,no description,60.2 -669,I7N3G6G,669,1,no description,60.2 -670,I7N3G6G,670,1,no description,60.2 -671,I7N3G6G,671,1,no description,60.2 -672,I7N3G6G,672,1,no description,60.2 -673,I7N3G6G,673,1,no description,60.2 -674,I7N3G6G,674,1,no description,60.2 -675,I7N3G6G,675,1,no description,60.2 -676,I7N3G6G,676,1,no description,60.2 -677,I7N3G6G,677,1,no description,60.2 -678,I7N3G6G,678,1,no description,60.2 -679,I7N3G6G,679,1,no description,60.2 -680,I7N3G6G,680,1,no description,60.2 -681,I7N3G6G,681,1,no description,60.2 -682,I7N3G6G,682,1,no description,60.2 -683,I7N3G6G,683,1,no description,60.2 -684,I7N3G6G,684,1,no description,60.2 -685,I7N3G6G,685,1,no description,60.2 -686,I7N3G6G,686,1,no description,60.2 -687,I7N3G6G,687,1,no description,60.2 -688,I7N3G6G,688,1,no description,60.2 -689,I7N3G6G,689,1,no description,60.2 -690,I7N3G6G,690,1,no description,60.2 -691,I7N3G6G,691,1,no description,60.2 -692,I7N3G6G,692,1,no description,60.2 -693,I7N3G6G,693,1,no description,60.2 -694,I7N3G6G,694,1,no description,60.2 -695,I7N3G6G,695,1,no description,60.2 -696,I7N3G6G,696,1,no description,60.2 -697,I7N3G6G,697,1,no description,60.2 -698,I7N3G6G,698,1,no description,60.2 -699,I7N3G6G,699,1,no description,60.2 -700,I7N3G6G,700,1,no description,60.2 -701,I7N3G6G,701,1,no description,60.2 -702,I7N3G6G,702,1,no description,60.2 -703,I7N3G6G,703,1,no description,60.2 -704,I7N3G6G,704,1,no description,60.2 -705,I7N3G6G,705,1,no description,60.2 -706,I7N3G6G,706,1,no description,60.2 -707,I7N3G6G,707,1,no description,60.2 -708,I7N3G6G,708,1,no description,60.2 -709,I7N3G6G,709,1,no description,60.2 -710,I7N3G6G,710,1,no description,60.2 -711,I7N3G6G,711,1,no description,60.2 -712,I7N3G6G,712,1,no description,60.2 -713,I7N3G6G,713,1,no description,60.2 -714,I7N3G6G,714,1,no description,60.2 -715,I7N3G6G,715,1,no description,60.2 -716,I7N3G6G,716,1,no description,60.2 -717,I7N3G6G,717,1,no description,60.2 -718,I7N3G6G,718,1,no description,60.2 -719,I7N3G6G,719,1,no description,60.2 -720,I7N3G6G,720,1,no description,60.2 -721,I7N3G6G,721,1,no description,60.2 -722,I7N3G6G,722,1,no description,60.2 -723,I7N3G6G,723,1,no description,60.2 -724,I7N3G6G,724,1,no description,60.2 -725,I7N3G6G,725,1,no description,60.2 -726,I7N3G6G,726,1,no description,60.2 -727,I7N3G6G,727,1,no description,60.2 -728,I7N3G6G,728,1,no description,60.2 -729,I7N3G6G,729,1,no description,60.2 -730,I7N3G6G,730,1,no description,60.2 -731,I7N3G6G,731,1,no description,60.2 -732,I7N3G6G,732,1,no description,60.2 -733,I7N3G6G,733,1,no description,60.2 -734,I7N3G6G,734,1,no description,60.2 -735,I7N3G6G,735,1,no description,60.2 -736,I7N3G6G,736,1,no description,60.2 -737,I7N3G6G,737,1,no description,60.2 -738,I7N3G6G,738,1,no description,60.2 -739,I7N3G6G,739,1,no description,60.2 -740,I7N3G6G,740,1,no description,60.2 -741,I7N3G6G,741,1,no description,60.2 -742,I7N3G6G,742,1,no description,60.2 -743,I7N3G6G,743,1,no description,60.2 -744,I7N3G6G,744,1,no description,60.2 -745,I7N3G6G,745,1,no description,60.2 -746,I7N3G6G,746,1,no description,60.2 -747,I7N3G6G,747,1,no description,60.2 -748,I7N3G6G,748,1,no description,60.2 -749,I7N3G6G,749,1,no description,60.2 -750,I7N3G6G,750,1,no description,60.2 -751,I7N3G6G,751,1,no description,60.2 -752,I7N3G6G,752,1,no description,60.2 -753,I7N3G6G,753,1,no description,60.2 -754,I7N3G6G,754,1,no description,60.2 -755,I7N3G6G,755,1,no description,60.2 -756,I7N3G6G,756,1,no description,60.2 -757,I7N3G6G,757,1,no description,60.2 -758,I7N3G6G,758,1,no description,60.2 -759,I7N3G6G,759,1,no description,60.2 -760,I7N3G6G,760,1,no description,60.2 -761,I7N3G6G,761,1,no description,60.2 -762,I7N3G6G,762,1,no description,60.2 -763,I7N3G6G,763,1,no description,60.2 -764,I7N3G6G,764,1,no description,60.2 -765,I7N3G6G,765,1,no description,60.2 -766,I7N3G6G,766,1,no description,60.2 -767,I7N3G6G,767,1,no description,60.2 -768,I7N3G6G,768,1,no description,60.2 -769,I7N3G6G,769,1,no description,60.2 -770,I7N3G6G,770,1,no description,60.2 -771,I7N3G6G,771,1,no description,60.2 -772,I7N3G6G,772,1,no description,60.2 -773,I7N3G6G,773,1,no description,60.2 -774,I7N3G6G,774,1,no description,60.2 -775,I7N3G6G,775,1,no description,60.2 -776,I7N3G6G,776,1,no description,60.2 -777,I7N3G6G,777,1,no description,60.2 -778,I7N3G6G,778,1,no description,60.2 -779,I7N3G6G,779,1,no description,60.2 -780,I7N3G6G,780,1,no description,60.2 -781,I7N3G6G,781,1,no description,60.2 -782,I7N3G6G,782,1,no description,60.2 -783,I7N3G6G,783,1,no description,60.2 -784,I7N3G6G,784,1,no description,60.2 -785,I7N3G6G,785,1,no description,60.2 -786,I7N3G6G,786,1,no description,60.2 -787,I7N3G6G,787,1,no description,60.2 -788,I7N3G6G,788,1,no description,60.2 -789,I7N3G6G,789,1,no description,60.2 -790,I7N3G6G,790,1,no description,60.2 -791,I7N3G6G,791,1,no description,60.2 -792,I7N3G6G,792,1,no description,60.2 -793,I7N3G6G,793,1,no description,60.2 -794,I7N3G6G,794,1,no description,60.2 -795,I7N3G6G,795,1,no description,60.2 -796,I7N3G6G,796,1,no description,60.2 -797,I7N3G6G,797,1,no description,60.2 -798,I7N3G6G,798,1,no description,60.2 -799,I7N3G6G,799,1,no description,60.2 -800,I7N3G6G,800,1,no description,60.2 -801,I7N3G6G,801,1,no description,60.2 -802,I7N3G6G,802,1,no description,60.2 -803,I7N3G6G,803,1,no description,60.2 -804,I7N3G6G,804,1,no description,60.2 -805,I7N3G6G,805,1,no description,60.2 -806,I7N3G6G,806,1,no description,60.2 -807,I7N3G6G,807,1,no description,60.2 -808,I7N3G6G,808,1,no description,60.2 -809,I7N3G6G,809,1,no description,60.2 -810,I7N3G6G,810,1,no description,60.2 -811,I7N3G6G,811,1,no description,60.2 -812,I7N3G6G,812,1,no description,60.2 -813,I7N3G6G,813,1,no description,60.2 -814,I7N3G6G,814,1,no description,60.2 -815,I7N3G6G,815,1,no description,60.2 -816,I7N3G6G,816,1,no description,60.2 -817,I7N3G6G,817,1,no description,60.2 -818,I7N3G6G,818,1,no description,60.2 -819,I7N3G6G,819,1,no description,60.2 -820,I7N3G6G,820,1,no description,60.2 -821,I7N3G6G,821,1,no description,60.2 -822,I7N3G6G,822,1,no description,60.2 -823,I7N3G6G,823,1,no description,60.2 -824,I7N3G6G,824,1,no description,60.2 -825,I7N3G6G,825,1,no description,60.2 -826,I7N3G6G,826,1,no description,60.2 -827,I7N3G6G,827,1,no description,60.2 -828,I7N3G6G,828,1,no description,60.2 -829,I7N3G6G,829,1,no description,60.2 -830,I7N3G6G,830,1,no description,60.2 -831,I7N3G6G,831,1,no description,60.2 -832,I7N3G6G,832,1,no description,60.2 -833,I7N3G6G,833,1,no description,60.2 -834,I7N3G6G,834,1,no description,60.2 -835,I7N3G6G,835,1,no description,60.2 -836,I7N3G6G,836,1,no description,60.2 -837,I7N3G6G,837,1,no description,60.2 -838,I7N3G6G,838,1,no description,60.2 -839,I7N3G6G,839,1,no description,60.2 -840,I7N3G6G,840,1,no description,60.2 -841,I7N3G6G,841,1,no description,60.2 -842,I7N3G6G,842,1,no description,60.2 -843,I7N3G6G,843,1,no description,60.2 -844,I7N3G6G,844,1,no description,60.2 -845,I7N3G6G,845,1,no description,60.2 -846,I7N3G6G,846,1,no description,60.2 -847,I7N3G6G,847,1,no description,60.2 -848,I7N3G6G,848,1,no description,60.2 -849,I7N3G6G,849,1,no description,60.2 -850,I7N3G6G,850,1,no description,60.2 -851,I7N3G6G,851,1,no description,60.2 -852,I7N3G6G,852,1,no description,60.2 -853,I7N3G6G,853,1,no description,60.2 -854,I7N3G6G,854,1,no description,60.2 -855,I7N3G6G,855,1,no description,60.2 -856,I7N3G6G,856,1,no description,60.2 -857,I7N3G6G,857,1,no description,60.2 -858,I7N3G6G,858,1,no description,60.2 -859,I7N3G6G,859,1,no description,60.2 -860,I7N3G6G,860,1,no description,60.2 -861,I7N3G6G,861,1,no description,60.2 -862,I7N3G6G,862,1,no description,60.2 -863,I7N3G6G,863,1,no description,60.2 -864,I7N3G6G,864,1,no description,60.2 -865,I7N3G6G,865,1,no description,60.2 -866,I7N3G6G,866,1,no description,60.2 -867,I7N3G6G,867,1,no description,60.2 -868,I7N3G6G,868,1,no description,60.2 -869,I7N3G6G,869,1,no description,60.2 -870,I7N3G6G,870,1,no description,60.2 -871,I7N3G6G,871,1,no description,60.2 -872,I7N3G6G,872,1,no description,60.2 -873,I7N3G6G,873,1,no description,60.2 -874,I7N3G6G,874,1,no description,60.2 -875,I7N3G6G,875,1,no description,60.2 -876,I7N3G6G,876,1,no description,60.2 -877,I7N3G6G,877,1,no description,60.2 -878,I7N3G6G,878,1,no description,60.2 -879,I7N3G6G,879,1,no description,60.2 -880,I7N3G6G,880,1,no description,60.2 -881,I7N3G6G,881,1,no description,60.2 -882,I7N3G6G,882,1,no description,60.2 -883,I7N3G6G,883,1,no description,60.2 -884,I7N3G6G,884,1,no description,60.2 -885,I7N3G6G,885,1,no description,60.2 -886,I7N3G6G,886,1,no description,60.2 -887,I7N3G6G,887,1,no description,60.2 -888,I7N3G6G,888,1,no description,60.2 -889,I7N3G6G,889,1,no description,60.2 -890,I7N3G6G,890,1,no description,60.2 -891,I7N3G6G,891,1,no description,60.2 -892,I7N3G6G,892,1,no description,60.2 -893,I7N3G6G,893,1,no description,60.2 -894,I7N3G6G,894,1,no description,60.2 -895,I7N3G6G,895,1,no description,60.2 -896,I7N3G6G,896,1,no description,60.2 -897,I7N3G6G,897,1,no description,60.2 -898,I7N3G6G,898,1,no description,60.2 -899,I7N3G6G,899,1,no description,60.2 -900,I7N3G6G,900,1,no description,60.2 -901,I7N3G6G,901,1,no description,60.2 -902,I7N3G6G,902,1,no description,60.2 -903,I7N3G6G,903,1,no description,60.2 -904,I7N3G6G,904,1,no description,60.2 -905,I7N3G6G,905,1,no description,60.2 -906,I7N3G6G,906,1,no description,60.2 -907,I7N3G6G,907,1,no description,60.2 -908,I7N3G6G,908,1,no description,60.2 -909,I7N3G6G,909,1,no description,60.2 -910,I7N3G6G,910,1,no description,60.2 -911,I7N3G6G,911,1,no description,60.2 -912,I7N3G6G,912,1,no description,60.2 -913,I7N3G6G,913,1,no description,60.2 -914,I7N3G6G,914,1,no description,60.2 -915,I7N3G6G,915,1,no description,60.2 -916,I7N3G6G,916,1,no description,60.2 -917,I7N3G6G,917,1,no description,60.2 -918,I7N3G6G,918,1,no description,60.2 -919,I7N3G6G,919,1,no description,60.2 -920,I7N3G6G,920,1,no description,60.2 -921,I7N3G6G,921,1,no description,60.2 -922,I7N3G6G,922,1,no description,60.2 -923,I7N3G6G,923,1,no description,60.2 -924,I7N3G6G,924,1,no description,60.2 -925,I7N3G6G,925,1,no description,60.2 -926,I7N3G6G,926,1,no description,60.2 -927,I7N3G6G,927,1,no description,60.2 -928,I7N3G6G,928,1,no description,60.2 -929,I7N3G6G,929,1,no description,60.2 -930,I7N3G6G,930,1,no description,60.2 -931,I7N3G6G,931,1,no description,60.2 -932,I7N3G6G,932,1,no description,60.2 -933,I7N3G6G,933,1,no description,60.2 -934,I7N3G6G,934,1,no description,60.2 -935,I7N3G6G,935,1,no description,60.2 -936,I7N3G6G,936,1,no description,60.2 -937,I7N3G6G,937,1,no description,60.2 -938,I7N3G6G,938,1,no description,60.2 -939,I7N3G6G,939,1,no description,60.2 -940,I7N3G6G,940,1,no description,60.2 -941,I7N3G6G,941,1,no description,60.2 -942,I7N3G6G,942,1,no description,60.2 -943,I7N3G6G,943,1,no description,60.2 -944,I7N3G6G,944,1,no description,60.2 -945,I7N3G6G,945,1,no description,60.2 -946,I7N3G6G,946,1,no description,60.2 -947,I7N3G6G,947,1,no description,60.2 -948,I7N3G6G,948,1,no description,60.2 -949,I7N3G6G,949,1,no description,60.2 -950,I7N3G6G,950,1,no description,60.2 -951,I7N3G6G,951,1,no description,60.2 -952,I7N3G6G,952,1,no description,60.2 -953,I7N3G6G,953,1,no description,60.2 -954,I7N3G6G,954,1,no description,60.2 -955,I7N3G6G,955,1,no description,60.2 -956,I7N3G6G,956,1,no description,60.2 -957,I7N3G6G,957,1,no description,60.2 -958,I7N3G6G,958,1,no description,60.2 -959,I7N3G6G,959,1,no description,60.2 -960,I7N3G6G,960,1,no description,60.2 -961,I7N3G6G,961,1,no description,60.2 -962,I7N3G6G,962,1,no description,60.2 -963,I7N3G6G,963,1,no description,60.2 -964,I7N3G6G,964,1,no description,60.2 -965,I7N3G6G,965,1,no description,60.2 -966,I7N3G6G,966,1,no description,60.2 -967,I7N3G6G,967,1,no description,60.2 -968,I7N3G6G,968,1,no description,60.2 -969,I7N3G6G,969,1,no description,60.2 -970,I7N3G6G,970,1,no description,60.2 -971,I7N3G6G,971,1,no description,60.2 -972,I7N3G6G,972,1,no description,60.2 -973,I7N3G6G,973,1,no description,60.2 -974,I7N3G6G,974,1,no description,60.2 -975,I7N3G6G,975,1,no description,60.2 -976,I7N3G6G,976,1,no description,60.2 -977,I7N3G6G,977,1,no description,60.2 -978,I7N3G6G,978,1,no description,60.2 -979,I7N3G6G,979,1,no description,60.2 -980,I7N3G6G,980,1,no description,60.2 -981,I7N3G6G,981,1,no description,60.2 -982,I7N3G6G,982,1,no description,60.2 -983,I7N3G6G,983,1,no description,60.2 -984,I7N3G6G,984,1,no description,60.2 -985,I7N3G6G,985,1,no description,60.2 -986,I7N3G6G,986,1,no description,60.2 -987,I7N3G6G,987,1,no description,60.2 -988,I7N3G6G,988,1,no description,60.2 -989,I7N3G6G,989,1,no description,60.2 -990,I7N3G6G,990,1,no description,60.2 -991,I7N3G6G,991,1,no description,60.2 -992,I7N3G6G,992,1,no description,60.2 -993,I7N3G6G,993,1,no description,60.2 -994,I7N3G6G,994,1,no description,60.2 -995,I7N3G6G,995,1,no description,60.2 -996,I7N3G6G,996,1,no description,60.2 -997,I7N3G6G,997,1,no description,60.2 -998,I7N3G6G,998,1,no description,60.2 -999,I7N3G6G,999,1,no description,60.2 -1000,I7N3G6G,1000,1,no description,60.2 -1001,I7N3G6G,1001,1,no description,60.2 -1002,I7N3G6G,1002,1,no description,60.2 -1003,I7N3G6G,1003,1,no description,60.2 -1004,I7N3G6G,1004,1,no description,60.2 -1005,I7N3G6G,1005,1,no description,60.2 -1006,I7N3G6G,1006,1,no description,60.2 -1007,I7N3G6G,1007,1,no description,60.2 -1008,I7N3G6G,1008,1,no description,60.2 -1009,I7N3G6G,1009,1,no description,60.2 -1010,I7N3G6G,1010,1,no description,60.2 -1011,I7N3G6G,1011,1,no description,60.2 -1012,I7N3G6G,1012,1,no description,60.2 -1013,I7N3G6G,1013,1,no description,60.2 -1014,I7N3G6G,1014,1,no description,60.2 -1015,I7N3G6G,1015,1,no description,60.2 -1016,I7N3G6G,1016,1,no description,60.2 -1017,I7N3G6G,1017,1,no description,60.2 -1018,I7N3G6G,1018,1,no description,60.2 -1019,I7N3G6G,1019,1,no description,60.2 -1020,I7N3G6G,1020,1,no description,60.2 -1021,I7N3G6G,1021,1,no description,60.2 -1022,I7N3G6G,1022,1,no description,60.2 -1023,I7N3G6G,1023,1,no description,60.2 -1024,I7N3G6G,1024,1,no description,60.2 -1025,I7N3G6G,1025,1,no description,60.2 -1026,I7N3G6G,1026,1,no description,60.2 -1027,I7N3G6G,1027,1,no description,60.2 -1028,I7N3G6G,1028,1,no description,60.2 -1029,I7N3G6G,1029,1,no description,60.2 -1030,I7N3G6G,1030,1,no description,60.2 -1031,I7N3G6G,1031,1,no description,60.2 -1032,I7N3G6G,1032,1,no description,60.2 -1033,I7N3G6G,1033,1,no description,60.2 -1034,I7N3G6G,1034,1,no description,60.2 -1035,I7N3G6G,1035,1,no description,60.2 -1036,I7N3G6G,1036,1,no description,60.2 -1037,I7N3G6G,1037,1,no description,60.2 -1038,I7N3G6G,1038,1,no description,60.2 -1039,I7N3G6G,1039,1,no description,60.2 -1040,I7N3G6G,1040,1,no description,60.2 -1041,I7N3G6G,1041,1,no description,60.2 -1042,I7N3G6G,1042,1,no description,60.2 -1043,I7N3G6G,1043,1,no description,60.2 -1044,I7N3G6G,1044,1,no description,60.2 -1045,I7N3G6G,1045,1,no description,60.2 -1046,I7N3G6G,1046,1,no description,60.2 -1047,I7N3G6G,1047,1,no description,60.2 -1048,I7N3G6G,1048,1,no description,60.2 -1049,I7N3G6G,1049,1,no description,60.2 -1050,I7N3G6G,1050,1,no description,60.2 -1051,I7N3G6G,1051,1,no description,60.2 -1052,I7N3G6G,1052,1,no description,60.2 -1053,I7N3G6G,1053,1,no description,60.2 -1054,I7N3G6G,1054,1,no description,60.2 -1055,I7N3G6G,1055,1,no description,60.2 -1056,I7N3G6G,1056,1,no description,60.2 -1057,I7N3G6G,1057,1,no description,60.2 -1058,I7N3G6G,1058,1,no description,60.2 -1059,I7N3G6G,1059,1,no description,60.2 -1060,I7N3G6G,1060,1,no description,60.2 -1061,I7N3G6G,1061,1,no description,60.2 -1062,I7N3G6G,1062,1,no description,60.2 -1063,I7N3G6G,1063,1,no description,60.2 -1064,I7N3G6G,1064,1,no description,60.2 -1065,I7N3G6G,1065,1,no description,60.2 -1066,I7N3G6G,1066,1,no description,60.2 -1067,I7N3G6G,1067,1,no description,60.2 -1068,I7N3G6G,1068,1,no description,60.2 -1069,I7N3G6G,1069,1,no description,60.2 -1070,I7N3G6G,1070,1,no description,60.2 -1071,I7N3G6G,1071,1,no description,60.2 -1072,I7N3G6G,1072,1,no description,60.2 -1073,I7N3G6G,1073,1,no description,60.2 -1074,I7N3G6G,1074,1,no description,60.2 -1075,I7N3G6G,1075,1,no description,60.2 -1076,I7N3G6G,1076,1,no description,60.2 -1077,I7N3G6G,1077,1,no description,60.2 -1078,I7N3G6G,1078,1,no description,60.2 -1079,I7N3G6G,1079,1,no description,60.2 -1080,I7N3G6G,1080,1,no description,60.2 -1081,I7N3G6G,1081,1,no description,60.2 -1082,I7N3G6G,1082,1,no description,60.2 -1083,I7N3G6G,1083,1,no description,60.2 -1084,I7N3G6G,1084,1,no description,60.2 -1085,I7N3G6G,1085,1,no description,60.2 -1086,I7N3G6G,1086,1,no description,60.2 -1087,I7N3G6G,1087,1,no description,60.2 -1088,I7N3G6G,1088,1,no description,60.2 -1089,I7N3G6G,1089,1,no description,60.2 -1090,I7N3G6G,1090,1,no description,60.2 -1091,I7N3G6G,1091,1,no description,60.2 -1092,I7N3G6G,1092,1,no description,60.2 -1093,I7N3G6G,1093,1,no description,60.2 -1094,I7N3G6G,1094,1,no description,60.2 -1095,I7N3G6G,1095,1,no description,60.2 -1096,I7N3G6G,1096,1,no description,60.2 -1097,I7N3G6G,1097,1,no description,60.2 -1098,I7N3G6G,1098,1,no description,60.2 -1099,I7N3G6G,1099,1,no description,60.2 -1100,I7N3G6G,1100,1,no description,60.2 -1101,I7N3G6G,1101,1,no description,60.2 -1102,I7N3G6G,1102,1,no description,60.2 -1103,I7N3G6G,1103,1,no description,60.2 -1104,I7N3G6G,1104,1,no description,60.2 -1105,I7N3G6G,1105,1,no description,60.2 -1106,I7N3G6G,1106,1,no description,60.2 -1107,I7N3G6G,1107,1,no description,60.2 -1108,I7N3G6G,1108,1,no description,60.2 -1109,I7N3G6G,1109,1,no description,60.2 -1110,I7N3G6G,1110,1,no description,60.2 -1111,I7N3G6G,1111,1,no description,60.2 -1112,I7N3G6G,1112,1,no description,60.2 -1113,I7N3G6G,1113,1,no description,60.2 -1114,I7N3G6G,1114,1,no description,60.2 -1115,I7N3G6G,1115,1,no description,60.2 -1116,I7N3G6G,1116,1,no description,60.2 -1117,I7N3G6G,1117,1,no description,60.2 -1118,I7N3G6G,1118,1,no description,60.2 -1119,I7N3G6G,1119,1,no description,60.2 -1120,I7N3G6G,1120,1,no description,60.2 -1121,I7N3G6G,1121,1,no description,60.2 -1122,I7N3G6G,1122,1,no description,60.2 -1123,I7N3G6G,1123,1,no description,60.2 -1124,I7N3G6G,1124,1,no description,60.2 -1125,I7N3G6G,1125,1,no description,60.2 -1126,I7N3G6G,1126,1,no description,60.2 -1127,I7N3G6G,1127,1,no description,60.2 -1128,I7N3G6G,1128,1,no description,60.2 -1129,I7N3G6G,1129,1,no description,60.2 -1130,I7N3G6G,1130,1,no description,60.2 -1131,I7N3G6G,1131,1,no description,60.2 -1132,I7N3G6G,1132,1,no description,60.2 -1133,I7N3G6G,1133,1,no description,60.2 -1134,I7N3G6G,1134,1,no description,60.2 -1135,I7N3G6G,1135,1,no description,60.2 -1136,I7N3G6G,1136,1,no description,60.2 -1137,I7N3G6G,1137,1,no description,60.2 -1138,I7N3G6G,1138,1,no description,60.2 -1139,I7N3G6G,1139,1,no description,60.2 -1140,I7N3G6G,1140,1,no description,60.2 -1141,I7N3G6G,1141,1,no description,60.2 -1142,I7N3G6G,1142,1,no description,60.2 -1143,I7N3G6G,1143,1,no description,60.2 -1144,I7N3G6G,1144,1,no description,60.2 -1145,I7N3G6G,1145,1,no description,60.2 -1146,I7N3G6G,1146,1,no description,60.2 -1147,I7N3G6G,1147,1,no description,60.2 -1148,I7N3G6G,1148,1,no description,60.2 -1149,I7N3G6G,1149,1,no description,60.2 -1150,I7N3G6G,1150,1,no description,60.2 -1151,I7N3G6G,1151,1,no description,60.2 -1152,I7N3G6G,1152,1,no description,60.2 -1153,I7N3G6G,1153,1,no description,60.2 -1154,I7N3G6G,1154,1,no description,60.2 -1155,I7N3G6G,1155,1,no description,60.2 -1156,I7N3G6G,1156,1,no description,60.2 -1157,I7N3G6G,1157,1,no description,60.2 -1158,I7N3G6G,1158,1,no description,60.2 -1159,I7N3G6G,1159,1,no description,60.2 -1160,I7N3G6G,1160,1,no description,60.2 -1161,I7N3G6G,1161,1,no description,60.2 -1162,I7N3G6G,1162,1,no description,60.2 -1163,I7N3G6G,1163,1,no description,60.2 -1164,I7N3G6G,1164,1,no description,60.2 -1165,I7N3G6G,1165,1,no description,60.2 -1166,I7N3G6G,1166,1,no description,60.2 -1167,I7N3G6G,1167,1,no description,60.2 -1168,I7N3G6G,1168,1,no description,60.2 -1169,I7N3G6G,1169,1,no description,60.2 -1170,I7N3G6G,1170,1,no description,60.2 -1171,I7N3G6G,1171,1,no description,60.2 -1172,I7N3G6G,1172,1,no description,60.2 -1173,I7N3G6G,1173,1,no description,60.2 -1174,I7N3G6G,1174,1,no description,60.2 -1175,I7N3G6G,1175,1,no description,60.2 -1176,I7N3G6G,1176,1,no description,60.2 -1177,I7N3G6G,1177,1,no description,60.2 -1178,I7N3G6G,1178,1,no description,60.2 -1179,I7N3G6G,1179,1,no description,60.2 -1180,I7N3G6G,1180,1,no description,60.2 -1181,I7N3G6G,1181,1,no description,60.2 -1182,I7N3G6G,1182,1,no description,60.2 -1183,I7N3G6G,1183,1,no description,60.2 -1184,I7N3G6G,1184,1,no description,60.2 -1185,I7N3G6G,1185,1,no description,60.2 -1186,I7N3G6G,1186,1,no description,60.2 -1187,I7N3G6G,1187,1,no description,60.2 -1188,I7N3G6G,1188,1,no description,60.2 -1189,I7N3G6G,1189,1,no description,60.2 -1190,I7N3G6G,1190,1,no description,60.2 -1191,I7N3G6G,1191,1,no description,60.2 -1192,I7N3G6G,1192,1,no description,60.2 -1193,I7N3G6G,1193,1,no description,60.2 -1194,I7N3G6G,1194,1,no description,60.2 -1195,I7N3G6G,1195,1,no description,60.2 -1196,I7N3G6G,1196,1,no description,60.2 -1197,I7N3G6G,1197,1,no description,60.2 -1198,I7N3G6G,1198,1,no description,60.2 -1199,I7N3G6G,1199,1,no description,60.2 -1200,I7N3G6G,1200,1,no description,60.2 -1201,I7N3G6G,1201,1,no description,60.2 -1202,I7N3G6G,1202,1,no description,60.2 -1203,I7N3G6G,1203,1,no description,60.2 -1204,I7N3G6G,1204,1,no description,60.2 -1205,I7N3G6G,1205,1,no description,60.2 -1206,I7N3G6G,1206,1,no description,60.2 -1207,I7N3G6G,1207,1,no description,60.2 -1208,I7N3G6G,1208,1,no description,60.2 -1209,I7N3G6G,1209,1,no description,60.2 -1210,I7N3G6G,1210,1,no description,60.2 -1211,I7N3G6G,1211,1,no description,60.2 -1212,I7N3G6G,1212,1,no description,60.2 -1213,I7N3G6G,1213,1,no description,60.2 -1214,I7N3G6G,1214,1,no description,60.2 -1215,I7N3G6G,1215,1,no description,60.2 -1216,I7N3G6G,1216,1,no description,60.2 -1217,I7N3G6G,1217,1,no description,60.2 -1218,I7N3G6G,1218,1,no description,60.2 -1219,I7N3G6G,1219,1,no description,60.2 -1220,I7N3G6G,1220,1,no description,60.2 -1221,I7N3G6G,1221,1,no description,60.2 -1222,I7N3G6G,1222,1,no description,60.2 -1223,I7N3G6G,1223,1,no description,60.2 -1224,I7N3G6G,1224,1,no description,60.2 -1225,I7N3G6G,1225,1,no description,60.2 -1226,I7N3G6G,1226,1,no description,60.2 -1227,I7N3G6G,1227,1,no description,60.2 -1228,I7N3G6G,1228,1,no description,60.2 -1229,I7N3G6G,1229,1,no description,60.2 -1230,I7N3G6G,1230,1,no description,60.2 -1231,I7N3G6G,1231,1,no description,60.2 -1232,I7N3G6G,1232,1,no description,60.2 -1233,I7N3G6G,1233,1,no description,60.2 -1234,I7N3G6G,1234,1,no description,60.2 -1235,I7N3G6G,1235,1,no description,60.2 -1236,I7N3G6G,1236,1,no description,60.2 -1237,I7N3G6G,1237,1,no description,60.2 -1238,I7N3G6G,1238,1,no description,60.2 -1239,I7N3G6G,1239,1,no description,60.2 -1240,I7N3G6G,1240,1,no description,60.2 -1241,I7N3G6G,1241,1,no description,60.2 -1242,I7N3G6G,1242,1,no description,60.2 -1243,I7N3G6G,1243,1,no description,60.2 -1244,I7N3G6G,1244,1,no description,60.2 -1245,I7N3G6G,1245,1,no description,60.2 -1246,I7N3G6G,1246,1,no description,60.2 -1247,I7N3G6G,1247,1,no description,60.2 -1248,I7N3G6G,1248,1,no description,60.2 -1249,I7N3G6G,1249,1,no description,60.2 -1250,I7N3G6G,1250,1,no description,60.2 -1251,I7N3G6G,1251,1,no description,60.2 -1252,I7N3G6G,1252,1,no description,60.2 -1253,I7N3G6G,1253,1,no description,60.2 -1254,I7N3G6G,1254,1,no description,60.2 -1255,I7N3G6G,1255,1,no description,60.2 -1256,I7N3G6G,1256,1,no description,60.2 -1257,I7N3G6G,1257,1,no description,60.2 -1258,I7N3G6G,1258,1,no description,60.2 -1259,I7N3G6G,1259,1,no description,60.2 -1260,I7N3G6G,1260,1,no description,60.2 -1261,I7N3G6G,1261,1,no description,60.2 -1262,I7N3G6G,1262,1,no description,60.2 -1263,I7N3G6G,1263,1,no description,60.2 -1264,I7N3G6G,1264,1,no description,60.2 -1265,I7N3G6G,1265,1,no description,60.2 -1266,I7N3G6G,1266,1,no description,60.2 -1267,I7N3G6G,1267,1,no description,60.2 -1268,I7N3G6G,1268,1,no description,60.2 -1269,I7N3G6G,1269,1,no description,60.2 -1270,I7N3G6G,1270,1,no description,60.2 -1271,I7N3G6G,1271,1,no description,60.2 -1272,I7N3G6G,1272,1,no description,60.2 -1273,I7N3G6G,1273,1,no description,60.2 -1274,I7N3G6G,1274,1,no description,60.2 -1275,I7N3G6G,1275,1,no description,60.2 -1276,I7N3G6G,1276,1,no description,60.2 -1277,I7N3G6G,1277,1,no description,60.2 -1278,I7N3G6G,1278,1,no description,60.2 -1279,I7N3G6G,1279,1,no description,60.2 -1280,I7N3G6G,1280,1,no description,60.2 -1281,I7N3G6G,1281,1,no description,60.2 -1282,I7N3G6G,1282,1,no description,60.2 -1283,I7N3G6G,1283,1,no description,60.2 -1284,I7N3G6G,1284,1,no description,60.2 -1285,I7N3G6G,1285,1,no description,60.2 -1286,I7N3G6G,1286,1,no description,60.2 -1287,I7N3G6G,1287,1,no description,60.2 -1288,I7N3G6G,1288,1,no description,60.2 -1289,I7N3G6G,1289,1,no description,60.2 -1290,I7N3G6G,1290,1,no description,60.2 -1291,I7N3G6G,1291,1,no description,60.2 -1292,I7N3G6G,1292,1,no description,60.2 -1293,I7N3G6G,1293,1,no description,60.2 -1294,I7N3G6G,1294,1,no description,60.2 -1295,I7N3G6G,1295,1,no description,60.2 -1296,I7N3G6G,1296,1,no description,60.2 -1297,I7N3G6G,1297,1,no description,60.2 -1298,I7N3G6G,1298,1,no description,60.2 -1299,I7N3G6G,1299,1,no description,60.2 -1300,I7N3G6G,1300,1,no description,60.2 -1301,I7N3G6G,1301,1,no description,60.2 -1302,I7N3G6G,1302,1,no description,60.2 -1303,I7N3G6G,1303,1,no description,60.2 -1304,I7N3G6G,1304,1,no description,60.2 -1305,I7N3G6G,1305,1,no description,60.2 -1306,I7N3G6G,1306,1,no description,60.2 -1307,I7N3G6G,1307,1,no description,60.2 -1308,I7N3G6G,1308,1,no description,60.2 -1309,I7N3G6G,1309,1,no description,60.2 -1310,I7N3G6G,1310,1,no description,60.2 -1311,I7N3G6G,1311,1,no description,60.2 -1312,I7N3G6G,1312,1,no description,60.2 -1313,I7N3G6G,1313,1,no description,60.2 -1314,I7N3G6G,1314,1,no description,60.2 -1315,I7N3G6G,1315,1,no description,60.2 -1316,I7N3G6G,1316,1,no description,60.2 -1317,I7N3G6G,1317,1,no description,60.2 -1318,I7N3G6G,1318,1,no description,60.2 -1319,I7N3G6G,1319,1,no description,60.2 -1320,I7N3G6G,1320,1,no description,60.2 -1321,I7N3G6G,1321,1,no description,60.2 -1322,I7N3G6G,1322,1,no description,60.2 -1323,I7N3G6G,1323,1,no description,60.2 -1324,I7N3G6G,1324,1,no description,60.2 -1325,I7N3G6G,1325,1,no description,60.2 -1326,I7N3G6G,1326,1,no description,60.2 -1327,I7N3G6G,1327,1,no description,60.2 -1328,I7N3G6G,1328,1,no description,60.2 -1329,I7N3G6G,1329,1,no description,60.2 -1330,I7N3G6G,1330,1,no description,60.2 -1331,I7N3G6G,1331,1,no description,60.2 -1332,I7N3G6G,1332,1,no description,60.2 -1333,I7N3G6G,1333,1,no description,60.2 -1334,I7N3G6G,1334,1,no description,60.2 -1335,I7N3G6G,1335,1,no description,60.2 -1336,I7N3G6G,1336,1,no description,60.2 -1337,I7N3G6G,1337,1,no description,60.2 -1338,I7N3G6G,1338,1,no description,60.2 -1339,I7N3G6G,1339,1,no description,60.2 -1340,I7N3G6G,1340,1,no description,60.2 -1341,I7N3G6G,1341,1,no description,60.2 -1342,I7N3G6G,1342,1,no description,60.2 -1343,I7N3G6G,1343,1,no description,60.2 -1344,I7N3G6G,1344,1,no description,60.2 -1345,I7N3G6G,1345,1,no description,60.2 -1346,I7N3G6G,1346,1,no description,60.2 -1347,I7N3G6G,1347,1,no description,60.2 -1348,I7N3G6G,1348,1,no description,60.2 -1349,I7N3G6G,1349,1,no description,60.2 -1350,I7N3G6G,1350,1,no description,60.2 -1351,I7N3G6G,1351,1,no description,60.2 -1352,I7N3G6G,1352,1,no description,60.2 -1353,I7N3G6G,1353,1,no description,60.2 -1354,I7N3G6G,1354,1,no description,60.2 -1355,I7N3G6G,1355,1,no description,60.2 -1356,I7N3G6G,1356,1,no description,60.2 -1357,I7N3G6G,1357,1,no description,60.2 -1358,I7N3G6G,1358,1,no description,60.2 -1359,I7N3G6G,1359,1,no description,60.2 -1360,I7N3G6G,1360,1,no description,60.2 -1361,I7N3G6G,1361,1,no description,60.2 -1362,I7N3G6G,1362,1,no description,60.2 -1363,I7N3G6G,1363,1,no description,60.2 -1364,I7N3G6G,1364,1,no description,60.2 -1365,I7N3G6G,1365,1,no description,60.2 -1366,I7N3G6G,1366,1,no description,60.2 -1367,I7N3G6G,1367,1,no description,60.2 -1368,I7N3G6G,1368,1,no description,60.2 -1369,I7N3G6G,1369,1,no description,60.2 -1370,I7N3G6G,1370,1,no description,60.2 -1371,I7N3G6G,1371,1,no description,60.2 -1372,I7N3G6G,1372,1,no description,60.2 -1373,I7N3G6G,1373,1,no description,60.2 -1374,I7N3G6G,1374,1,no description,60.2 -1375,I7N3G6G,1375,1,no description,60.2 -1376,I7N3G6G,1376,1,no description,60.2 -1377,I7N3G6G,1377,1,no description,60.2 -1378,I7N3G6G,1378,1,no description,60.2 -1379,I7N3G6G,1379,1,no description,60.2 -1380,I7N3G6G,1380,1,no description,60.2 -1381,I7N3G6G,1381,1,no description,60.2 -1382,I7N3G6G,1382,1,no description,60.2 -1383,I7N3G6G,1383,1,no description,60.2 -1384,I7N3G6G,1384,1,no description,60.2 -1385,I7N3G6G,1385,1,no description,60.2 -1386,I7N3G6G,1386,1,no description,60.2 -1387,I7N3G6G,1387,1,no description,60.2 -1388,I7N3G6G,1388,1,no description,60.2 -1389,I7N3G6G,1389,1,no description,60.2 -1390,I7N3G6G,1390,1,no description,60.2 -1391,I7N3G6G,1391,1,no description,60.2 -1392,I7N3G6G,1392,1,no description,60.2 -1393,I7N3G6G,1393,1,no description,60.2 -1394,I7N3G6G,1394,1,no description,60.2 -1395,I7N3G6G,1395,1,no description,60.2 -1396,I7N3G6G,1396,1,no description,60.2 -1397,I7N3G6G,1397,1,no description,60.2 -1398,I7N3G6G,1398,1,no description,60.2 -1399,I7N3G6G,1399,1,no description,60.2 -1400,I7N3G6G,1400,1,no description,60.2 -1401,I7N3G6G,1401,1,no description,60.2 -1402,I7N3G6G,1402,1,no description,60.2 -1403,I7N3G6G,1403,1,no description,60.2 -1404,I7N3G6G,1404,1,no description,60.2 -1405,I7N3G6G,1405,1,no description,60.2 -1406,I7N3G6G,1406,1,no description,60.2 -1407,I7N3G6G,1407,1,no description,60.2 -1408,I7N3G6G,1408,1,no description,60.2 -1409,I7N3G6G,1409,1,no description,60.2 -1410,I7N3G6G,1410,1,no description,60.2 -1411,I7N3G6G,1411,1,no description,60.2 -1412,I7N3G6G,1412,1,no description,60.2 -1413,I7N3G6G,1413,1,no description,60.2 -1414,I7N3G6G,1414,1,no description,60.2 -1415,I7N3G6G,1415,1,no description,60.2 -1416,I7N3G6G,1416,1,no description,60.2 -1417,I7N3G6G,1417,1,no description,60.2 -1418,I7N3G6G,1418,1,no description,60.2 -1419,I7N3G6G,1419,1,no description,60.2 -1420,I7N3G6G,1420,1,no description,60.2 -1421,I7N3G6G,1421,1,no description,60.2 -1422,I7N3G6G,1422,1,no description,60.2 -1423,I7N3G6G,1423,1,no description,60.2 -1424,I7N3G6G,1424,1,no description,60.2 -1425,I7N3G6G,1425,1,no description,60.2 -1426,I7N3G6G,1426,1,no description,60.2 -1427,I7N3G6G,1427,1,no description,60.2 -1428,I7N3G6G,1428,1,no description,60.2 -1429,I7N3G6G,1429,1,no description,60.2 -1430,I7N3G6G,1430,1,no description,60.2 -1431,I7N3G6G,1431,1,no description,60.2 -1432,I7N3G6G,1432,1,no description,60.2 -1433,I7N3G6G,1433,1,no description,60.2 -1434,I7N3G6G,1434,1,no description,60.2 -1435,I7N3G6G,1435,1,no description,60.2 -1436,I7N3G6G,1436,1,no description,60.2 -1437,I7N3G6G,1437,1,no description,60.2 -1438,I7N3G6G,1438,1,no description,60.2 -1439,I7N3G6G,1439,1,no description,60.2 -1440,I7N3G6G,1440,1,no description,60.2 -1441,I7N3G6G,1441,1,no description,60.2 -1442,I7N3G6G,1442,1,no description,60.2 -1443,I7N3G6G,1443,1,no description,60.2 -1444,I7N3G6G,1444,1,no description,60.2 -1445,I7N3G6G,1445,1,no description,60.2 -1446,I7N3G6G,1446,1,no description,60.2 -1447,I7N3G6G,1447,1,no description,60.2 -1448,I7N3G6G,1448,1,no description,60.2 -1449,I7N3G6G,1449,1,no description,60.2 -1450,I7N3G6G,1450,1,no description,60.2 -1451,I7N3G6G,1451,1,no description,60.2 -1452,I7N3G6G,1452,1,no description,60.2 -1453,I7N3G6G,1453,1,no description,60.2 -1454,I7N3G6G,1454,1,no description,60.2 -1455,I7N3G6G,1455,1,no description,60.2 -1456,I7N3G6G,1456,1,no description,60.2 -1457,I7N3G6G,1457,1,no description,60.2 -1458,I7N3G6G,1458,1,no description,60.2 -1459,I7N3G6G,1459,1,no description,60.2 -1460,I7N3G6G,1460,1,no description,60.2 -1461,I7N3G6G,1461,1,no description,60.2 -1462,I7N3G6G,1462,1,no description,60.2 -1463,I7N3G6G,1463,1,no description,60.2 -1464,I7N3G6G,1464,1,no description,60.2 -1465,I7N3G6G,1465,1,no description,60.2 -1466,I7N3G6G,1466,1,no description,60.2 -1467,I7N3G6G,1467,1,no description,60.2 -1468,I7N3G6G,1468,1,no description,60.2 -1469,I7N3G6G,1469,1,no description,60.2 -1470,I7N3G6G,1470,1,no description,60.2 -1471,I7N3G6G,1471,1,no description,60.2 -1472,I7N3G6G,1472,1,no description,60.2 -1473,I7N3G6G,1473,1,no description,60.2 -1474,I7N3G6G,1474,1,no description,60.2 -1475,I7N3G6G,1475,1,no description,60.2 -1476,I7N3G6G,1476,1,no description,60.2 -1477,I7N3G6G,1477,1,no description,60.2 -1478,I7N3G6G,1478,1,no description,60.2 -1479,I7N3G6G,1479,1,no description,60.2 -1480,I7N3G6G,1480,1,no description,60.2 -1481,I7N3G6G,1481,1,no description,60.2 -1482,I7N3G6G,1482,1,no description,60.2 -1483,I7N3G6G,1483,1,no description,60.2 -1484,I7N3G6G,1484,1,no description,60.2 -1485,I7N3G6G,1485,1,no description,60.2 -1486,I7N3G6G,1486,1,no description,60.2 -1487,I7N3G6G,1487,1,no description,60.2 -1488,I7N3G6G,1488,1,no description,60.2 -1489,I7N3G6G,1489,1,no description,60.2 -1490,I7N3G6G,1490,1,no description,60.2 -1491,I7N3G6G,1491,1,no description,60.2 -1492,I7N3G6G,1492,1,no description,60.2 -1493,I7N3G6G,1493,1,no description,60.2 -1494,I7N3G6G,1494,1,no description,60.2 -1495,I7N3G6G,1495,1,no description,60.2 -1496,I7N3G6G,1496,1,no description,60.2 -1497,I7N3G6G,1497,1,no description,60.2 -1498,I7N3G6G,1498,1,no description,60.2 -1499,I7N3G6G,1499,1,no description,60.2 -1500,I7N3G6G,1500,1,no description,60.2 -1501,I7N3G6G,1501,1,no description,60.2 -1502,I7N3G6G,1502,1,no description,60.2 -1503,I7N3G6G,1503,1,no description,60.2 -1504,I7N3G6G,1504,1,no description,60.2 -1505,I7N3G6G,1505,1,no description,60.2 -1506,I7N3G6G,1506,1,no description,60.2 -1507,I7N3G6G,1507,1,no description,60.2 -1508,I7N3G6G,1508,1,no description,60.2 -1509,I7N3G6G,1509,1,no description,60.2 -1510,I7N3G6G,1510,1,no description,60.2 -1511,I7N3G6G,1511,1,no description,60.2 -1512,I7N3G6G,1512,1,no description,60.2 -1513,I7N3G6G,1513,1,no description,60.2 -1514,I7N3G6G,1514,1,no description,60.2 -1515,I7N3G6G,1515,1,no description,60.2 -1516,I7N3G6G,1516,1,no description,60.2 -1517,I7N3G6G,1517,1,no description,60.2 -1518,I7N3G6G,1518,1,no description,60.2 -1519,I7N3G6G,1519,1,no description,60.2 -1520,I7N3G6G,1520,1,no description,60.2 -1521,I7N3G6G,1521,1,no description,60.2 -1522,I7N3G6G,1522,1,no description,60.2 -1523,I7N3G6G,1523,1,no description,60.2 -1524,I7N3G6G,1524,1,no description,60.2 -1525,I7N3G6G,1525,1,no description,60.2 -1526,I7N3G6G,1526,1,no description,60.2 -1527,I7N3G6G,1527,1,no description,60.2 -1528,I7N3G6G,1528,1,no description,60.2 -1529,I7N3G6G,1529,1,no description,60.2 -1530,I7N3G6G,1530,1,no description,60.2 -1531,I7N3G6G,1531,1,no description,60.2 -1532,I7N3G6G,1532,1,no description,60.2 -1533,I7N3G6G,1533,1,no description,60.2 -1534,I7N3G6G,1534,1,no description,60.2 -1535,I7N3G6G,1535,1,no description,60.2 -1536,I7N3G6G,1536,1,no description,60.2 -1537,I7N3G6G,1537,1,no description,60.2 -1538,I7N3G6G,1538,1,no description,60.2 -1539,I7N3G6G,1539,1,no description,60.2 -1540,I7N3G6G,1540,1,no description,60.2 -1541,I7N3G6G,1541,1,no description,60.2 -1542,I7N3G6G,1542,1,no description,60.2 -1543,I7N3G6G,1543,1,no description,60.2 -1544,I7N3G6G,1544,1,no description,60.2 -1545,I7N3G6G,1545,1,no description,60.2 -1546,I7N3G6G,1546,1,no description,60.2 -1547,I7N3G6G,1547,1,no description,60.2 -1548,I7N3G6G,1548,1,no description,60.2 -1549,I7N3G6G,1549,1,no description,60.2 -1550,I7N3G6G,1550,1,no description,60.2 -1551,I7N3G6G,1551,1,no description,60.2 -1552,I7N3G6G,1552,1,no description,60.2 -1553,I7N3G6G,1553,1,no description,60.2 -1554,I7N3G6G,1554,1,no description,60.2 -1555,I7N3G6G,1555,1,no description,60.2 -1556,I7N3G6G,1556,1,no description,60.2 -1557,I7N3G6G,1557,1,no description,60.2 -1558,I7N3G6G,1558,1,no description,60.2 -1559,I7N3G6G,1559,1,no description,60.2 -1560,I7N3G6G,1560,1,no description,60.2 -1561,I7N3G6G,1561,1,no description,60.2 -1562,I7N3G6G,1562,1,no description,60.2 -1563,I7N3G6G,1563,1,no description,60.2 -1564,I7N3G6G,1564,1,no description,60.2 -1565,I7N3G6G,1565,1,no description,60.2 -1566,I7N3G6G,1566,1,no description,60.2 -1567,I7N3G6G,1567,1,no description,60.2 -1568,I7N3G6G,1568,1,no description,60.2 -1569,I7N3G6G,1569,1,no description,60.2 -1570,I7N3G6G,1570,1,no description,60.2 -1571,I7N3G6G,1571,1,no description,60.2 -1572,I7N3G6G,1572,1,no description,60.2 -1573,I7N3G6G,1573,1,no description,60.2 -1574,I7N3G6G,1574,1,no description,60.2 -1575,I7N3G6G,1575,1,no description,60.2 -1576,I7N3G6G,1576,1,no description,60.2 -1577,I7N3G6G,1577,1,no description,60.2 -1578,I7N3G6G,1578,1,no description,60.2 -1579,I7N3G6G,1579,1,no description,60.2 -1580,I7N3G6G,1580,1,no description,60.2 -1581,I7N3G6G,1581,1,no description,60.2 -1582,I7N3G6G,1582,1,no description,60.2 -1583,I7N3G6G,1583,1,no description,60.2 -1584,I7N3G6G,1584,1,no description,60.2 -1585,I7N3G6G,1585,1,no description,60.2 -1586,I7N3G6G,1586,1,no description,60.2 -1587,I7N3G6G,1587,1,no description,60.2 -1588,I7N3G6G,1588,1,no description,60.2 -1589,I7N3G6G,1589,1,no description,60.2 -1590,I7N3G6G,1590,1,no description,60.2 -1591,I7N3G6G,1591,1,no description,60.2 -1592,I7N3G6G,1592,1,no description,60.2 -1593,I7N3G6G,1593,1,no description,60.2 -1594,I7N3G6G,1594,1,no description,60.2 -1595,I7N3G6G,1595,1,no description,60.2 -1596,I7N3G6G,1596,1,no description,60.2 -1597,I7N3G6G,1597,1,no description,60.2 -1598,I7N3G6G,1598,1,no description,60.2 -1599,I7N3G6G,1599,1,no description,60.2 -1600,I7N3G6G,1600,1,no description,60.2 -1601,I7N3G6G,1601,1,no description,60.2 -1602,I7N3G6G,1602,1,no description,60.2 -1603,I7N3G6G,1603,1,no description,60.2 -1604,I7N3G6G,1604,1,no description,60.2 -1605,I7N3G6G,1605,1,no description,60.2 -1606,I7N3G6G,1606,1,no description,60.2 -1607,I7N3G6G,1607,1,no description,60.2 -1608,I7N3G6G,1608,1,no description,60.2 -1609,I7N3G6G,1609,1,no description,60.2 -1610,I7N3G6G,1610,1,no description,60.2 -1611,I7N3G6G,1611,1,no description,60.2 -1612,I7N3G6G,1612,1,no description,60.2 -1613,I7N3G6G,1613,1,no description,60.2 -1614,I7N3G6G,1614,1,no description,60.2 -1615,I7N3G6G,1615,1,no description,60.2 -1616,I7N3G6G,1616,1,no description,60.2 -1617,I7N3G6G,1617,1,no description,60.2 -1618,I7N3G6G,1618,1,no description,60.2 -1619,I7N3G6G,1619,1,no description,60.2 -1620,I7N3G6G,1620,1,no description,60.2 -1621,I7N3G6G,1621,1,no description,60.2 -1622,I7N3G6G,1622,1,no description,60.2 -1623,I7N3G6G,1623,1,no description,60.2 -1624,I7N3G6G,1624,1,no description,60.2 -1625,I7N3G6G,1625,1,no description,60.2 -1626,I7N3G6G,1626,1,no description,60.2 -1627,I7N3G6G,1627,1,no description,60.2 -1628,I7N3G6G,1628,1,no description,60.2 -1629,I7N3G6G,1629,1,no description,60.2 -1630,I7N3G6G,1630,1,no description,60.2 -1631,I7N3G6G,1631,1,no description,60.2 -1632,I7N3G6G,1632,1,no description,60.2 -1633,I7N3G6G,1633,1,no description,60.2 -1634,I7N3G6G,1634,1,no description,60.2 -1635,I7N3G6G,1635,1,no description,60.2 -1636,I7N3G6G,1636,1,no description,60.2 -1637,I7N3G6G,1637,1,no description,60.2 -1638,I7N3G6G,1638,1,no description,60.2 -1639,I7N3G6G,1639,1,no description,60.2 -1640,I7N3G6G,1640,1,no description,60.2 -1641,I7N3G6G,1641,1,no description,60.2 -1642,I7N3G6G,1642,1,no description,60.2 -1643,I7N3G6G,1643,1,no description,60.2 -1644,I7N3G6G,1644,1,no description,60.2 -1645,I7N3G6G,1645,1,no description,60.2 -1646,I7N3G6G,1646,1,no description,60.2 -1647,I7N3G6G,1647,1,no description,60.2 -1648,I7N3G6G,1648,1,no description,60.2 -1649,I7N3G6G,1649,1,no description,60.2 -1650,I7N3G6G,1650,1,no description,60.2 -1651,I7N3G6G,1651,1,no description,60.2 -1652,I7N3G6G,1652,1,no description,60.2 -1653,I7N3G6G,1653,1,no description,60.2 -1654,I7N3G6G,1654,1,no description,60.2 -1655,I7N3G6G,1655,1,no description,60.2 -1656,I7N3G6G,1656,1,no description,60.2 -1657,I7N3G6G,1657,1,no description,60.2 -1658,I7N3G6G,1658,1,no description,60.2 -1659,I7N3G6G,1659,1,no description,60.2 -1660,I7N3G6G,1660,1,no description,60.2 -1661,I7N3G6G,1661,1,no description,60.2 -1662,I7N3G6G,1662,1,no description,60.2 -1663,I7N3G6G,1663,1,no description,60.2 -1664,I7N3G6G,1664,1,no description,60.2 -1665,I7N3G6G,1665,1,no description,60.2 -1666,I7N3G6G,1666,1,no description,60.2 -1667,I7N3G6G,1667,1,no description,60.2 -1668,I7N3G6G,1668,1,no description,60.2 -1669,I7N3G6G,1669,1,no description,60.2 -1670,I7N3G6G,1670,1,no description,60.2 -1671,I7N3G6G,1671,1,no description,60.2 -1672,I7N3G6G,1672,1,no description,60.2 -1673,I7N3G6G,1673,1,no description,60.2 -1674,I7N3G6G,1674,1,no description,60.2 -1675,I7N3G6G,1675,1,no description,60.2 -1676,I7N3G6G,1676,1,no description,60.2 -1677,I7N3G6G,1677,1,no description,60.2 -1678,I7N3G6G,1678,1,no description,60.2 -1679,I7N3G6G,1679,1,no description,60.2 -1680,I7N3G6G,1680,1,no description,60.2 -1681,I7N3G6G,1681,1,no description,60.2 -1682,I7N3G6G,1682,1,no description,60.2 -1683,I7N3G6G,1683,1,no description,60.2 -1684,I7N3G6G,1684,1,no description,60.2 -1685,I7N3G6G,1685,1,no description,60.2 -1686,I7N3G6G,1686,1,no description,60.2 -1687,I7N3G6G,1687,1,no description,60.2 -1688,I7N3G6G,1688,1,no description,60.2 -1689,I7N3G6G,1689,1,no description,60.2 -1690,I7N3G6G,1690,1,no description,60.2 -1691,I7N3G6G,1691,1,no description,60.2 -1692,I7N3G6G,1692,1,no description,60.2 -1693,I7N3G6G,1693,1,no description,60.2 -1694,I7N3G6G,1694,1,no description,60.2 -1695,I7N3G6G,1695,1,no description,60.2 -1696,I7N3G6G,1696,1,no description,60.2 -1697,I7N3G6G,1697,1,no description,60.2 -1698,I7N3G6G,1698,1,no description,60.2 -1699,I7N3G6G,1699,1,no description,60.2 -1700,I7N3G6G,1700,1,no description,60.2 -1701,I7N3G6G,1701,1,no description,60.2 -1702,I7N3G6G,1702,1,no description,60.2 -1703,I7N3G6G,1703,1,no description,60.2 -1704,I7N3G6G,1704,1,no description,60.2 -1705,I7N3G6G,1705,1,no description,60.2 -1706,I7N3G6G,1706,1,no description,60.2 -1707,I7N3G6G,1707,1,no description,60.2 -1708,I7N3G6G,1708,1,no description,60.2 -1709,I7N3G6G,1709,1,no description,60.2 -1710,I7N3G6G,1710,1,no description,60.2 -1711,I7N3G6G,1711,1,no description,60.2 -1712,I7N3G6G,1712,1,no description,60.2 -1713,I7N3G6G,1713,1,no description,60.2 -1714,I7N3G6G,1714,1,no description,60.2 -1715,I7N3G6G,1715,1,no description,60.2 -1716,I7N3G6G,1716,1,no description,60.2 -1717,I7N3G6G,1717,1,no description,60.2 -1718,I7N3G6G,1718,1,no description,60.2 -1719,I7N3G6G,1719,1,no description,60.2 -1720,I7N3G6G,1720,1,no description,60.2 -1721,I7N3G6G,1721,1,no description,60.2 -1722,I7N3G6G,1722,1,no description,60.2 -1723,I7N3G6G,1723,1,no description,60.2 -1724,I7N3G6G,1724,1,no description,60.2 -1725,I7N3G6G,1725,1,no description,60.2 -1726,I7N3G6G,1726,1,no description,60.2 -1727,I7N3G6G,1727,1,no description,60.2 -1728,I7N3G6G,1728,1,no description,60.2 -1729,I7N3G6G,1729,1,no description,60.2 -1730,I7N3G6G,1730,1,no description,60.2 -1731,I7N3G6G,1731,1,no description,60.2 -1732,I7N3G6G,1732,1,no description,60.2 -1733,I7N3G6G,1733,1,no description,60.2 -1734,I7N3G6G,1734,1,no description,60.2 -1735,I7N3G6G,1735,1,no description,60.2 -1736,I7N3G6G,1736,1,no description,60.2 -1737,I7N3G6G,1737,1,no description,60.2 -1738,I7N3G6G,1738,1,no description,60.2 -1739,I7N3G6G,1739,1,no description,60.2 -1740,I7N3G6G,1740,1,no description,60.2 -1741,I7N3G6G,1741,1,no description,60.2 -1742,I7N3G6G,1742,1,no description,60.2 -1743,I7N3G6G,1743,1,no description,60.2 -1744,I7N3G6G,1744,1,no description,60.2 -1745,I7N3G6G,1745,1,no description,60.2 -1746,I7N3G6G,1746,1,no description,60.2 -1747,I7N3G6G,1747,1,no description,60.2 -1748,I7N3G6G,1748,1,no description,60.2 -1749,I7N3G6G,1749,1,no description,60.2 -1750,I7N3G6G,1750,1,no description,60.2 -1751,I7N3G6G,1751,1,no description,60.2 -1752,I7N3G6G,1752,1,no description,60.2 -1753,I7N3G6G,1753,1,no description,60.2 -1754,I7N3G6G,1754,1,no description,60.2 -1755,I7N3G6G,1755,1,no description,60.2 -1756,I7N3G6G,1756,1,no description,60.2 -1757,I7N3G6G,1757,1,no description,60.2 -1758,I7N3G6G,1758,1,no description,60.2 -1759,I7N3G6G,1759,1,no description,60.2 -1760,I7N3G6G,1760,1,no description,60.2 -1761,I7N3G6G,1761,1,no description,60.2 -1762,I7N3G6G,1762,1,no description,60.2 -1763,I7N3G6G,1763,1,no description,60.2 -1764,I7N3G6G,1764,1,no description,60.2 -1765,I7N3G6G,1765,1,no description,60.2 -1766,I7N3G6G,1766,1,no description,60.2 -1767,I7N3G6G,1767,1,no description,60.2 -1768,I7N3G6G,1768,1,no description,60.2 -1769,I7N3G6G,1769,1,no description,60.2 -1770,I7N3G6G,1770,1,no description,60.2 -1771,I7N3G6G,1771,1,no description,60.2 -1772,I7N3G6G,1772,1,no description,60.2 -1773,I7N3G6G,1773,1,no description,60.2 -1774,I7N3G6G,1774,1,no description,60.2 -1775,I7N3G6G,1775,1,no description,60.2 -1776,I7N3G6G,1776,1,no description,60.2 -1777,I7N3G6G,1777,1,no description,60.2 -1778,I7N3G6G,1778,1,no description,60.2 -1779,I7N3G6G,1779,1,no description,60.2 -1780,I7N3G6G,1780,1,no description,60.2 -1781,I7N3G6G,1781,1,no description,60.2 -1782,I7N3G6G,1782,1,no description,60.2 -1783,I7N3G6G,1783,1,no description,60.2 -1784,I7N3G6G,1784,1,no description,60.2 -1785,I7N3G6G,1785,1,no description,60.2 -1786,I7N3G6G,1786,1,no description,60.2 -1787,I7N3G6G,1787,1,no description,60.2 -1788,I7N3G6G,1788,1,no description,60.2 -1789,I7N3G6G,1789,1,no description,60.2 -1790,I7N3G6G,1790,1,no description,60.2 -1791,I7N3G6G,1791,1,no description,60.2 -1792,I7N3G6G,1792,1,no description,60.2 -1793,I7N3G6G,1793,1,no description,60.2 -1794,I7N3G6G,1794,1,no description,60.2 -1795,I7N3G6G,1795,1,no description,60.2 -1796,I7N3G6G,1796,1,no description,60.2 -1797,I7N3G6G,1797,1,no description,60.2 -1798,I7N3G6G,1798,1,no description,60.2 -1799,I7N3G6G,1799,1,no description,60.2 -1800,I7N3G6G,1800,1,no description,60.2 -1801,I7N3G6G,1801,1,no description,60.2 -1802,I7N3G6G,1802,1,no description,60.2 -1803,I7N3G6G,1803,1,no description,60.2 -1804,I7N3G6G,1804,1,no description,60.2 -1805,I7N3G6G,1805,1,no description,60.2 -1806,I7N3G6G,1806,1,no description,60.2 -1807,I7N3G6G,1807,1,no description,60.2 -1808,I7N3G6G,1808,1,no description,60.2 -1809,I7N3G6G,1809,1,no description,60.2 -1810,I7N3G6G,1810,1,no description,60.2 -1811,I7N3G6G,1811,1,no description,60.2 -1812,I7N3G6G,1812,1,no description,60.2 -1813,I7N3G6G,1813,1,no description,60.2 -1814,I7N3G6G,1814,1,no description,60.2 -1815,I7N3G6G,1815,1,no description,60.2 -1816,I7N3G6G,1816,1,no description,60.2 -1817,I7N3G6G,1817,1,no description,60.2 -1818,I7N3G6G,1818,1,no description,60.2 -1819,I7N3G6G,1819,1,no description,60.2 -1820,I7N3G6G,1820,1,no description,60.2 -1821,I7N3G6G,1821,1,no description,60.2 -1822,I7N3G6G,1822,1,no description,60.2 -1823,I7N3G6G,1823,1,no description,60.2 -1824,I7N3G6G,1824,1,no description,60.2 -1825,I7N3G6G,1825,1,no description,60.2 -1826,I7N3G6G,1826,1,no description,60.2 -1827,I7N3G6G,1827,1,no description,60.2 -1828,I7N3G6G,1828,1,no description,60.2 -1829,I7N3G6G,1829,1,no description,60.2 -1830,I7N3G6G,1830,1,no description,60.2 -1831,I7N3G6G,1831,1,no description,60.2 -1832,I7N3G6G,1832,1,no description,60.2 -1833,I7N3G6G,1833,1,no description,60.2 -1834,I7N3G6G,1834,1,no description,60.2 -1835,I7N3G6G,1835,1,no description,60.2 -1836,I7N3G6G,1836,1,no description,60.2 -1837,I7N3G6G,1837,1,no description,60.2 -1838,I7N3G6G,1838,1,no description,60.2 -1839,I7N3G6G,1839,1,no description,60.2 -1840,I7N3G6G,1840,1,no description,60.2 -1841,I7N3G6G,1841,1,no description,60.2 -1842,I7N3G6G,1842,1,no description,60.2 -1843,I7N3G6G,1843,1,no description,60.2 -1844,I7N3G6G,1844,1,no description,60.2 -1845,I7N3G6G,1845,1,no description,60.2 -1846,I7N3G6G,1846,1,no description,60.2 -1847,I7N3G6G,1847,1,no description,60.2 -1848,I7N3G6G,1848,1,no description,60.2 -1849,I7N3G6G,1849,1,no description,60.2 -1850,I7N3G6G,1850,1,no description,60.2 -1851,I7N3G6G,1851,1,no description,60.2 -1852,I7N3G6G,1852,1,no description,60.2 -1853,I7N3G6G,1853,1,no description,60.2 -1854,I7N3G6G,1854,1,no description,60.2 -1855,I7N3G6G,1855,1,no description,60.2 -1856,I7N3G6G,1856,1,no description,60.2 -1857,I7N3G6G,1857,1,no description,60.2 -1858,I7N3G6G,1858,1,no description,60.2 -1859,I7N3G6G,1859,1,no description,60.2 -1860,I7N3G6G,1860,1,no description,60.2 -1861,I7N3G6G,1861,1,no description,60.2 -1862,I7N3G6G,1862,1,no description,60.2 -1863,I7N3G6G,1863,1,no description,60.2 -1864,I7N3G6G,1864,1,no description,60.2 -1865,I7N3G6G,1865,1,no description,60.2 -1866,I7N3G6G,1866,1,no description,60.2 -1867,I7N3G6G,1867,1,no description,60.2 -1868,I7N3G6G,1868,1,no description,60.2 -1869,I7N3G6G,1869,1,no description,60.2 -1870,I7N3G6G,1870,1,no description,60.2 -1871,I7N3G6G,1871,1,no description,60.2 -1872,I7N3G6G,1872,1,no description,60.2 -1873,I7N3G6G,1873,1,no description,60.2 -1874,I7N3G6G,1874,1,no description,60.2 -1875,I7N3G6G,1875,1,no description,60.2 -1876,I7N3G6G,1876,1,no description,60.2 -1877,I7N3G6G,1877,1,no description,60.2 -1878,I7N3G6G,1878,1,no description,60.2 -1879,I7N3G6G,1879,1,no description,60.2 -1880,I7N3G6G,1880,1,no description,60.2 -1881,I7N3G6G,1881,1,no description,60.2 -1882,I7N3G6G,1882,1,no description,60.2 -1883,I7N3G6G,1883,1,no description,60.2 -1884,I7N3G6G,1884,1,no description,60.2 -1885,I7N3G6G,1885,1,no description,60.2 -1886,I7N3G6G,1886,1,no description,60.2 -1887,I7N3G6G,1887,1,no description,60.2 -1888,I7N3G6G,1888,1,no description,60.2 -1889,I7N3G6G,1889,1,no description,60.2 -1890,I7N3G6G,1890,1,no description,60.2 -1891,I7N3G6G,1891,1,no description,60.2 -1892,I7N3G6G,1892,1,no description,60.2 -1893,I7N3G6G,1893,1,no description,60.2 -1894,I7N3G6G,1894,1,no description,60.2 -1895,I7N3G6G,1895,1,no description,60.2 -1896,I7N3G6G,1896,1,no description,60.2 -1897,I7N3G6G,1897,1,no description,60.2 -1898,I7N3G6G,1898,1,no description,60.2 -1899,I7N3G6G,1899,1,no description,60.2 -1900,I7N3G6G,1900,1,no description,60.2 -1901,I7N3G6G,1901,1,no description,60.2 -1902,I7N3G6G,1902,1,no description,60.2 -1903,I7N3G6G,1903,1,no description,60.2 -1904,I7N3G6G,1904,1,no description,60.2 -1905,I7N3G6G,1905,1,no description,60.2 -1906,I7N3G6G,1906,1,no description,60.2 -1907,I7N3G6G,1907,1,no description,60.2 -1908,I7N3G6G,1908,1,no description,60.2 -1909,I7N3G6G,1909,1,no description,60.2 -1910,I7N3G6G,1910,1,no description,60.2 -1911,I7N3G6G,1911,1,no description,60.2 -1912,I7N3G6G,1912,1,no description,60.2 -1913,I7N3G6G,1913,1,no description,60.2 -1914,I7N3G6G,1914,1,no description,60.2 -1915,I7N3G6G,1915,1,no description,60.2 -1916,I7N3G6G,1916,1,no description,60.2 -1917,I7N3G6G,1917,1,no description,60.2 -1918,I7N3G6G,1918,1,no description,60.2 -1919,I7N3G6G,1919,1,no description,60.2 -1920,I7N3G6G,1920,1,no description,60.2 -1921,I7N3G6G,1921,1,no description,60.2 -1922,I7N3G6G,1922,1,no description,60.2 -1923,I7N3G6G,1923,1,no description,60.2 -1924,I7N3G6G,1924,1,no description,60.2 -1925,I7N3G6G,1925,1,no description,60.2 -1926,I7N3G6G,1926,1,no description,60.2 -1927,I7N3G6G,1927,1,no description,60.2 -1928,I7N3G6G,1928,1,no description,60.2 -1929,I7N3G6G,1929,1,no description,60.2 -1930,I7N3G6G,1930,1,no description,60.2 -1931,I7N3G6G,1931,1,no description,60.2 -1932,I7N3G6G,1932,1,no description,60.2 -1933,I7N3G6G,1933,1,no description,60.2 -1934,I7N3G6G,1934,1,no description,60.2 -1935,I7N3G6G,1935,1,no description,60.2 -1936,I7N3G6G,1936,1,no description,60.2 -1937,I7N3G6G,1937,1,no description,60.2 -1938,I7N3G6G,1938,1,no description,60.2 -1939,I7N3G6G,1939,1,no description,60.2 -1940,I7N3G6G,1940,1,no description,60.2 -1941,I7N3G6G,1941,1,no description,60.2 -1942,I7N3G6G,1942,1,no description,60.2 -1943,I7N3G6G,1943,1,no description,60.2 -1944,I7N3G6G,1944,1,no description,60.2 -1945,I7N3G6G,1945,1,no description,60.2 -1946,I7N3G6G,1946,1,no description,60.2 -1947,I7N3G6G,1947,1,no description,60.2 -1948,I7N3G6G,1948,1,no description,60.2 -1949,I7N3G6G,1949,1,no description,60.2 -1950,I7N3G6G,1950,1,no description,60.2 -1951,I7N3G6G,1951,1,no description,60.2 -1952,I7N3G6G,1952,1,no description,60.2 -1953,I7N3G6G,1953,1,no description,60.2 -1954,I7N3G6G,1954,1,no description,60.2 -1955,I7N3G6G,1955,1,no description,60.2 -1956,I7N3G6G,1956,1,no description,60.2 -1957,I7N3G6G,1957,1,no description,60.2 -1958,I7N3G6G,1958,1,no description,60.2 -1959,I7N3G6G,1959,1,no description,60.2 -1960,I7N3G6G,1960,1,no description,60.2 -1961,I7N3G6G,1961,1,no description,60.2 -1962,I7N3G6G,1962,1,no description,60.2 -1963,I7N3G6G,1963,1,no description,60.2 -1964,I7N3G6G,1964,1,no description,60.2 -1965,I7N3G6G,1965,1,no description,60.2 -1966,I7N3G6G,1966,1,no description,60.2 -1967,I7N3G6G,1967,1,no description,60.2 -1968,I7N3G6G,1968,1,no description,60.2 -1969,I7N3G6G,1969,1,no description,60.2 -1970,I7N3G6G,1970,1,no description,60.2 -1971,I7N3G6G,1971,1,no description,60.2 -1972,I7N3G6G,1972,1,no description,60.2 -1973,I7N3G6G,1973,1,no description,60.2 -1974,I7N3G6G,1974,1,no description,60.2 -1975,I7N3G6G,1975,1,no description,60.2 -1976,I7N3G6G,1976,1,no description,60.2 -1977,I7N3G6G,1977,1,no description,60.2 -1978,I7N3G6G,1978,1,no description,60.2 -1979,I7N3G6G,1979,1,no description,60.2 -1980,I7N3G6G,1980,1,no description,60.2 -1981,I7N3G6G,1981,1,no description,60.2 -1982,I7N3G6G,1982,1,no description,60.2 -1983,I7N3G6G,1983,1,no description,60.2 -1984,I7N3G6G,1984,1,no description,60.2 -1985,I7N3G6G,1985,1,no description,60.2 -1986,I7N3G6G,1986,1,no description,60.2 -1987,I7N3G6G,1987,1,no description,60.2 -1988,I7N3G6G,1988,1,no description,60.2 -1989,I7N3G6G,1989,1,no description,60.2 -1990,I7N3G6G,1990,1,no description,60.2 -1991,I7N3G6G,1991,1,no description,60.2 -1992,I7N3G6G,1992,1,no description,60.2 -1993,I7N3G6G,1993,1,no description,60.2 -1994,I7N3G6G,1994,1,no description,60.2 -1995,I7N3G6G,1995,1,no description,60.2 -1996,I7N3G6G,1996,1,no description,60.2 -1997,I7N3G6G,1997,1,no description,60.2 -1998,I7N3G6G,1998,1,no description,60.2 -1999,I7N3G6G,1999,1,no description,60.2 -2000,I7N3G6G,2000,1,no description,60.2 -2001,I7N3G6G,2001,1,no description,60.2 -2002,I7N3G6G,2002,1,no description,60.2 -2003,I7N3G6G,2003,1,no description,60.2 -2004,I7N3G6G,2004,1,no description,60.2 -2005,I7N3G6G,2005,1,no description,60.2 -2006,I7N3G6G,2006,1,no description,60.2 -2007,I7N3G6G,2007,1,no description,60.2 -2008,I7N3G6G,2008,1,no description,60.2 -2009,I7N3G6G,2009,1,no description,60.2 -2010,I7N3G6G,2010,1,no description,60.2 -2011,I7N3G6G,2011,1,no description,60.2 -2012,I7N3G6G,2012,1,no description,60.2 -2013,I7N3G6G,2013,1,no description,60.2 -2014,I7N3G6G,2014,1,no description,60.2 -2015,I7N3G6G,2015,1,no description,60.2 -2016,I7N3G6G,2016,1,no description,60.2 -2017,I7N3G6G,2017,1,no description,60.2 -2018,I7N3G6G,2018,1,no description,60.2 -2019,I7N3G6G,2019,1,no description,60.2 -2020,I7N3G6G,2020,1,no description,60.2 -2021,I7N3G6G,2021,1,no description,60.2 -2022,I7N3G6G,2022,1,no description,60.2 -2023,I7N3G6G,2023,1,no description,60.2 -2024,I7N3G6G,2024,1,no description,60.2 -2025,I7N3G6G,2025,1,no description,60.2 -2026,I7N3G6G,2026,1,no description,60.2 -2027,I7N3G6G,2027,1,no description,60.2 -2028,I7N3G6G,2028,1,no description,60.2 -2029,I7N3G6G,2029,1,no description,60.2 -2030,I7N3G6G,2030,1,no description,60.2 -2031,I7N3G6G,2031,1,no description,60.2 -2032,I7N3G6G,2032,1,no description,60.2 -2033,I7N3G6G,2033,1,no description,60.2 -2034,I7N3G6G,2034,1,no description,60.2 -2035,I7N3G6G,2035,1,no description,60.2 -2036,I7N3G6G,2036,1,no description,60.2 -2037,I7N3G6G,2037,1,no description,60.2 -2038,I7N3G6G,2038,1,no description,60.2 -2039,I7N3G6G,2039,1,no description,60.2 -2040,I7N3G6G,2040,1,no description,60.2 -2041,I7N3G6G,2041,1,no description,60.2 -2042,I7N3G6G,2042,1,no description,60.2 -2043,I7N3G6G,2043,1,no description,60.2 -2044,I7N3G6G,2044,1,no description,60.2 -2045,I7N3G6G,2045,1,no description,60.2 -2046,I7N3G6G,2046,1,no description,60.2 -2047,I7N3G6G,2047,1,no description,60.2 -2048,I7N3G6G,2048,1,no description,60.2 -2049,I7N3G6G,2049,1,no description,60.2 -2050,I7N3G6G,2050,1,no description,60.2 -2051,I7N3G6G,2051,1,no description,60.2 -2052,I7N3G6G,2052,1,no description,60.2 -2053,I7N3G6G,2053,1,no description,60.2 -2054,I7N3G6G,2054,1,no description,60.2 -2055,I7N3G6G,2055,1,no description,60.2 -2056,I7N3G6G,2056,1,no description,60.2 -2057,I7N3G6G,2057,1,no description,60.2 -2058,I7N3G6G,2058,1,no description,60.2 -2059,I7N3G6G,2059,1,no description,60.2 -2060,I7N3G6G,2060,1,no description,60.2 -2061,I7N3G6G,2061,1,no description,60.2 -2062,I7N3G6G,2062,1,no description,60.2 -2063,I7N3G6G,2063,1,no description,60.2 -2064,I7N3G6G,2064,1,no description,60.2 -2065,I7N3G6G,2065,1,no description,60.2 -2066,I7N3G6G,2066,1,no description,60.2 -2067,I7N3G6G,2067,1,no description,60.2 -2068,I7N3G6G,2068,1,no description,60.2 -2069,I7N3G6G,2069,1,no description,60.2 -2070,I7N3G6G,2070,1,no description,60.2 -2071,I7N3G6G,2071,1,no description,60.2 -2072,I7N3G6G,2072,1,no description,60.2 -2073,I7N3G6G,2073,1,no description,60.2 -2074,I7N3G6G,2074,1,no description,60.2 -2075,I7N3G6G,2075,1,no description,60.2 -2076,I7N3G6G,2076,1,no description,60.2 -2077,I7N3G6G,2077,1,no description,60.2 -2078,I7N3G6G,2078,1,no description,60.2 -2079,I7N3G6G,2079,1,no description,60.2 -2080,I7N3G6G,2080,1,no description,60.2 -2081,I7N3G6G,2081,1,no description,60.2 -2082,I7N3G6G,2082,1,no description,60.2 -2083,I7N3G6G,2083,1,no description,60.2 -2084,I7N3G6G,2084,1,no description,60.2 -2085,I7N3G6G,2085,1,no description,60.2 -2086,I7N3G6G,2086,1,no description,60.2 -2087,I7N3G6G,2087,1,no description,60.2 -2088,I7N3G6G,2088,1,no description,60.2 -2089,I7N3G6G,2089,1,no description,60.2 -2090,I7N3G6G,2090,1,no description,60.2 -2091,I7N3G6G,2091,1,no description,60.2 -2092,I7N3G6G,2092,1,no description,60.2 -2093,I7N3G6G,2093,1,no description,60.2 -2094,I7N3G6G,2094,1,no description,60.2 -2095,I7N3G6G,2095,1,no description,60.2 -2096,I7N3G6G,2096,1,no description,60.2 -2097,I7N3G6G,2097,1,no description,60.2 -2098,I7N3G6G,2098,1,no description,60.2 -2099,I7N3G6G,2099,1,no description,60.2 -2100,I7N3G6G,2100,1,no description,60.2 -2101,I7N3G6G,2101,1,no description,60.2 -2102,I7N3G6G,2102,1,no description,60.2 -2103,I7N3G6G,2103,1,no description,60.2 -2104,I7N3G6G,2104,1,no description,60.2 -2105,I7N3G6G,2105,1,no description,60.2 -2106,I7N3G6G,2106,1,no description,60.2 -2107,I7N3G6G,2107,1,no description,60.2 -2108,I7N3G6G,2108,1,no description,60.2 -2109,I7N3G6G,2109,1,no description,60.2 -2110,I7N3G6G,2110,1,no description,60.2 -2111,I7N3G6G,2111,1,no description,60.2 -2112,I7N3G6G,2112,1,no description,60.2 -2113,I7N3G6G,2113,1,no description,60.2 -2114,I7N3G6G,2114,1,no description,60.2 -2115,I7N3G6G,2115,1,no description,60.2 -2116,I7N3G6G,2116,1,no description,60.2 -2117,I7N3G6G,2117,1,no description,60.2 -2118,I7N3G6G,2118,1,no description,60.2 -2119,I7N3G6G,2119,1,no description,60.2 -2120,I7N3G6G,2120,1,no description,60.2 -2121,I7N3G6G,2121,1,no description,60.2 -2122,I7N3G6G,2122,1,no description,60.2 -2123,I7N3G6G,2123,1,no description,60.2 -2124,I7N3G6G,2124,1,no description,60.2 -2125,I7N3G6G,2125,1,no description,60.2 -2126,I7N3G6G,2126,1,no description,60.2 -2127,I7N3G6G,2127,1,no description,60.2 -2128,I7N3G6G,2128,1,no description,60.2 -2129,I7N3G6G,2129,1,no description,60.2 -2130,I7N3G6G,2130,1,no description,60.2 -2131,I7N3G6G,2131,1,no description,60.2 -2132,I7N3G6G,2132,1,no description,60.2 -2133,I7N3G6G,2133,1,no description,60.2 -2134,I7N3G6G,2134,1,no description,60.2 -2135,I7N3G6G,2135,1,no description,60.2 -2136,I7N3G6G,2136,1,no description,60.2 -2137,I7N3G6G,2137,1,no description,60.2 -2138,I7N3G6G,2138,1,no description,60.2 -2139,I7N3G6G,2139,1,no description,60.2 -2140,I7N3G6G,2140,1,no description,60.2 -2141,I7N3G6G,2141,1,no description,60.2 -2142,I7N3G6G,2142,1,no description,60.2 -2143,I7N3G6G,2143,1,no description,60.2 -2144,I7N3G6G,2144,1,no description,60.2 -2145,I7N3G6G,2145,1,no description,60.2 -2146,I7N3G6G,2146,1,no description,60.2 -2147,I7N3G6G,2147,1,no description,60.2 -2148,I7N3G6G,2148,1,no description,60.2 -2149,I7N3G6G,2149,1,no description,60.2 -2150,I7N3G6G,2150,1,no description,60.2 -2151,I7N3G6G,2151,1,no description,60.2 -2152,I7N3G6G,2152,1,no description,60.2 -2153,I7N3G6G,2153,1,no description,60.2 -2154,I7N3G6G,2154,1,no description,60.2 -2155,I7N3G6G,2155,1,no description,60.2 -2156,I7N3G6G,2156,1,no description,60.2 -2157,I7N3G6G,2157,1,no description,60.2 -2158,I7N3G6G,2158,1,no description,60.2 -2159,I7N3G6G,2159,1,no description,60.2 -2160,I7N3G6G,2160,1,no description,60.2 -2161,I7N3G6G,2161,1,no description,60.2 -2162,I7N3G6G,2162,1,no description,60.2 -2163,I7N3G6G,2163,1,no description,60.2 -2164,I7N3G6G,2164,1,no description,60.2 -2165,I7N3G6G,2165,1,no description,60.2 -2166,I7N3G6G,2166,1,no description,60.2 -2167,I7N3G6G,2167,1,no description,60.2 -2168,I7N3G6G,2168,1,no description,60.2 -2169,I7N3G6G,2169,1,no description,60.2 -2170,I7N3G6G,2170,1,no description,60.2 -2171,I7N3G6G,2171,1,no description,60.2 -2172,I7N3G6G,2172,1,no description,60.2 -2173,I7N3G6G,2173,1,no description,60.2 -2174,I7N3G6G,2174,1,no description,60.2 -2175,I7N3G6G,2175,1,no description,60.2 -2176,I7N3G6G,2176,1,no description,60.2 -2177,I7N3G6G,2177,1,no description,60.2 -2178,I7N3G6G,2178,1,no description,60.2 -2179,I7N3G6G,2179,1,no description,60.2 -2180,I7N3G6G,2180,1,no description,60.2 -2181,I7N3G6G,2181,1,no description,60.2 -2182,I7N3G6G,2182,1,no description,60.2 -2183,I7N3G6G,2183,1,no description,60.2 -2184,I7N3G6G,2184,1,no description,60.2 -2185,I7N3G6G,2185,1,no description,60.2 -2186,I7N3G6G,2186,1,no description,60.2 -2187,I7N3G6G,2187,1,no description,60.2 -2188,I7N3G6G,2188,1,no description,60.2 -2189,I7N3G6G,2189,1,no description,60.2 -2190,I7N3G6G,2190,1,no description,60.2 -2191,I7N3G6G,2191,1,no description,60.2 -2192,I7N3G6G,2192,1,no description,60.2 -2193,I7N3G6G,2193,1,no description,60.2 -2194,I7N3G6G,2194,1,no description,60.2 -2195,I7N3G6G,2195,1,no description,60.2 -2196,I7N3G6G,2196,1,no description,60.2 -2197,I7N3G6G,2197,1,no description,60.2 -2198,I7N3G6G,2198,1,no description,60.2 -2199,I7N3G6G,2199,1,no description,60.2 -2200,I7N3G6G,2200,1,no description,60.2 -2201,I7N3G6G,2201,1,no description,60.2 -2202,I7N3G6G,2202,1,no description,60.2 -2203,I7N3G6G,2203,1,no description,60.2 -2204,I7N3G6G,2204,1,no description,60.2 -2205,I7N3G6G,2205,1,no description,60.2 -2206,I7N3G6G,2206,1,no description,60.2 -2207,I7N3G6G,2207,1,no description,60.2 -2208,I7N3G6G,2208,1,no description,60.2 -2209,I7N3G6G,2209,1,no description,60.2 -2210,I7N3G6G,2210,1,no description,60.2 -2211,I7N3G6G,2211,1,no description,60.2 -2212,I7N3G6G,2212,1,no description,60.2 -2213,I7N3G6G,2213,1,no description,60.2 -2214,I7N3G6G,2214,1,no description,60.2 -2215,I7N3G6G,2215,1,no description,60.2 -2216,I7N3G6G,2216,1,no description,60.2 -2217,I7N3G6G,2217,1,no description,60.2 -2218,I7N3G6G,2218,1,no description,60.2 -2219,I7N3G6G,2219,1,no description,60.2 -2220,I7N3G6G,2220,1,no description,60.2 -2221,I7N3G6G,2221,1,no description,60.2 -2222,I7N3G6G,2222,1,no description,60.2 -2223,I7N3G6G,2223,1,no description,60.2 -2224,I7N3G6G,2224,1,no description,60.2 -2225,I7N3G6G,2225,1,no description,60.2 -2226,I7N3G6G,2226,1,no description,60.2 diff --git a/packages/itmat-job-executor/test/testFiles/CSVCurator_error3.tsv b/packages/itmat-job-executor/test/testFiles/CSVCurator_error3.tsv deleted file mode 100644 index e8d39f125..000000000 --- a/packages/itmat-job-executor/test/testFiles/CSVCurator_error3.tsv +++ /dev/null @@ -1,2227 +0,0 @@ -ID,SubjectID,VisitID,Sex,Description,Weight -1,I7N3G6G,1,0,no description,60.2 -2,I7N3G6G,2,1,no description,60.2 -3,I7N3G6G,3,1,no description,60.2 -4,I7N3G6G,4,1,no description,60.2 -5,I7N3G6G,5,1,no description,60.2 -6,I7N3G6G,6,1,no description,60.2 -7,I7N3G6G,7,1,no description,60.2 -8,I7N3G6G,8,1,no description,60.2 -9,I7N3G6G,9,1,no description,60.2 -10,I7N3G6G,10,1,no description,60.2 -11,I7N3G6G,11,1,no description,60.2 -12,I7N3G6G,12,1,no description,60.2 -13,I7N3G6G,13,1,no description,60.2 -14,I7N3G6G,14,1,no description,60.2 -15,I7N3G6G,15,1,no description,60.2 -16,I7N3G6G,16,1,no description,60.2 -17,I7N3G6G,17,1,no description -18,I7N3G6G,18,1,no description,60.2 -19,I7N3G6G,19,1,no description,60.2 -20,I7N3G6G,20,1,no description,60.2 -21,I7N3G6G,21,1,no description,60.2 -22,I7N3G6G,22,1,no description,60.2 -23,I7N3G6G,23,1,no description,60.2 -24,I7N3G6G,24,1,no description,60.2 -25,I7N3G6G,25,1,no description,60.2 -26,I7N3G6G,26,1,no description,60.2 -27,I7N3G6G,27,1,60.2 -28,I7N3G6G,28,1,no description,60.2 -29,I7N3G6G,29,1,no description,60.2 -30,I7N3G6G,30,1,no description,60.2 -31,I7N3G6G,31,1,no description,60.2 -32,I7N3G6G,32,1,no description,60.2 -33,I7N3G6G,33,1,no description,60.2 -34,I7N3G6G,34,1,no description,60.2 -35,I7N3G6G,35,1,no description,60.2 -36,I7N3G6G,36,1,no description,60.2 -37,I7N3G6G,37,1,no description,60.2 -38,I7N3G6G,38,1,no description,60.2 -39,I7N3G6G,39,1,no description,60.2 -40,I7N3G6G,40,1,no description,60.2 -41,I7N3G6G,41,1,no description,60.2 -42,I7N3G6G,42,1,no description,60.2 -43,I7N3G6G,43,1,no description,60.2 -44,I7N3G6G,44,1,no description,60.2 -45,I7N3G6G,45,1,no description,60.2 -46,I7N3G6G,46,1,no description,60.2 -47,I7N3G6G,47,1,no description,60.2 -48,I7N3G6G,48,1,no description,60.2 -49,I7N3G6G,49,1,no description,60.2 -50,I7N3G6G,50,1,no description,60.2 -51,I7N3G6G,51,1,no description,60.2 -52,I7N3G6G,52,1,no description,60.2 -53,I7N3G6G,53,1,no description,60.2 -54,I7N3G6G,54,1,no description,60.2 -55,I7N3G6G,55,1,no description,60.2 -56,I7N3G6G,56,1,no description,60.2 -57,I7N3G6G,57,1,no description,60.2 -58,I7N3G6G,58,1,no description,60.2 -59,I7N3G6G,59,1,no description,60.2 -60,I7N3G6G,60,1,no description,60.2 -61,I7N3G6G,61,1,no description,60.2 -62,I7N3G6G,62,1,no description,60.2 -63,I7N3G6G,63,1,no description,60.2 -64,I7N3G6G,64,1,no description,60.2 -65,I7N3G6G,65,1,no description,60.2 -66,I7N3G6G,66,1,no description,60.2 -67,I7N3G6G,67,1,no description,60.2 -68,I7N3G6G,68,1,no description,60.2 -69,I7N3G6G,69,1,no description,60.2 -70,I7N3G6G,70,1,no description,60.2 -71,I7N3G6G,71,1,no description,60.2 -72,I7N3G6G,72,1,no description,60.2 -73,I7N3G6G,73,1,no description,60.2 -74,I7N3G6G,74,1,no description,60.2 -75,I7N3G6G,75,1,no description,60.2 -76,I7N3G6G,76,1,no description,60.2 -77,I7N3G6G,77,1,no description,60.2 -78,I7N3G6G,78,1,no description,60.2 -79,I7N3G6G,79,1,no description,60.2 -80,I7N3G6G,80,1,no description,60.2 -81,I7N3G6G,81,1,no description,60.2 -82,I7N3G6G,82,1,no description,60.2 -83,I7N3G6G,83,1,no description,60.2 -84,I7N3G6G,84,1,no description,60.2 -85,I7N3G6G,85,1,no description,60.2 -86,I7N3G6G,86,1,no description,60.2 -87,I7N3G6G,87,1,no description,60.2 -88,I7N3G6G,88,1,no description,60.2 -89,I7N3G6G,89,1,no description,60.2 -90,I7N3G6G,90,1,no description,60.2 -91,I7N3G6G,91,1,no description,60.2 -92,I7N3G6G,92,1,no description,60.2 -93,I7N3G6G,93,1,no description,60.2 -94,I7N3G6G,94,1,no description,60.2 -95,I7N3G6G,95,1,no description,60.2 -96,I7N3G6G,96,1,no description,60.2 -97,I7N3G6G,97,1,no description,60.2 -98,I7N3G6G,98,1,no description,60.2 -99,I7N3G6G,99,1,no description,60.2 -100,I7N3G6G,100,1,no description,60.2 -101,I7N3G6G,101,1,no description,60.2 -102,I7N3G6G,102,1,no description,60.2 -103,I7N3G6G,103,1,no description,60.2 -104,I7N3G6G,104,1,no description,60.2 -105,I7N3G6G,105,1,no description,60.2 -106,I7N3G6G,106,1,no description,60.2 -107,I7N3G6G,107,1,no description,60.2 -108,I7N3G6G,108,1,no description,60.2 -109,I7N3G6G,109,1,no description,60.2 -110,I7N3G6G,110,1,no description,60.2 -111,I7N3G6G,111,1,no description,60.2 -112,I7N3G6G,112,1,no description,60.2 -113,I7N3G6G,113,1,no description,60.2 -114,I7N3G6G,114,1,no description,60.2 -115,I7N3G6G,115,1,no description,60.2 -116,I7N3G6G,116,1,no description,60.2 -117,I7N3G6G,117,1,no description,60.2 -118,I7N3G6G,118,1,no description,60.2 -119,I7N3G6G,119,1,no description,60.2 -120,I7N3G6G,120,1,no description,60.2 -121,I7N3G6G,121,1,no description,60.2 -122,I7N3G6G,122,1,no description,60.2 -123,I7N3G6G,123,1,no description,60.2 -124,I7N3G6G,124,1,no description,60.2 -125,I7N3G6G,125,1,no description,60.2 -126,I7N3G6G,126,1,no description,60.2 -127,I7N3G6G,127,1,no description,60.2 -128,I7N3G6G,128,1,no description,60.2 -129,I7N3G6G,129,1,no description,60.2 -130,I7N3G6G,130,1,no description,60.2 -131,I7N3G6G,131,1,no description,60.2 -132,I7N3G6G,132,1,no description,60.2 -133,I7N3G6G,133,1,no description,60.2 -134,I7N3G6G,134,1,no description,60.2 -135,I7N3G6G,135,1,no description,60.2 -136,I7N3G6G,136,1,no description,60.2 -137,I7N3G6G,137,1,no description,60.2 -138,I7N3G6G,138,1,no description,60.2 -139,I7N3G6G,139,1,no description,60.2 -140,I7N3G6G,140,1,no description,60.2 -141,I7N3G6G,141,1,no description,60.2 -142,I7N3G6G,142,1,no description,60.2 -143,I7N3G6G,143,1,no description,60.2 -144,I7N3G6G,144,1,no description,60.2 -145,I7N3G6G,145,1,no description,60.2 -146,I7N3G6G,146,1,no description,60.2 -147,I7N3G6G,147,1,no description,60.2 -148,I7N3G6G,148,1,no description,60.2 -149,I7N3G6G,149,1,no description,60.2 -150,I7N3G6G,150,1,no description,60.2 -151,I7N3G6G,151,1,no description,60.2 -152,I7N3G6G,152,1,no description,60.2 -153,I7N3G6G,153,1,no description,60.2 -154,I7N3G6G,154,1,no description,60.2 -155,I7N3G6G,155,1,no description,60.2 -156,I7N3G6G,156,1,no description,60.2 -157,I7N3G6G,157,1,no description,60.2 -158,I7N3G6G,158,1,no description,60.2 -159,I7N3G6G,159,1,no description,60.2 -160,I7N3G6G,160,1,no description,60.2 -161,I7N3G6G,161,1,no description,60.2 -162,I7N3G6G,162,1,no description,60.2 -163,I7N3G6G,163,1,no description,60.2 -164,I7N3G6G,164,1,no description,60.2 -165,I7N3G6G,165,1,no description,60.2 -166,I7N3G6G,166,1,no description,60.2 -167,I7N3G6G,167,1,no description,60.2 -168,I7N3G6G,168,1,no description,60.2 -169,I7N3G6G,169,1,no description,60.2 -170,I7N3G6G,170,1,no description,60.2 -171,I7N3G6G,171,1,no description,60.2 -172,I7N3G6G,172,1,no description,60.2 -173,I7N3G6G,173,1,no description,60.2 -174,I7N3G6G,174,1,no description,60.2 -175,I7N3G6G,175,1,no description,60.2 -176,I7N3G6G,176,1,no description,60.2 -177,I7N3G6G,177,1,no description,60.2 -178,I7N3G6G,178,1,no description,60.2 -179,I7N3G6G,179,1,no description,60.2 -180,I7N3G6G,180,1,no description,60.2 -181,I7N3G6G,181,1,no description,60.2 -182,I7N3G6G,182,1,no description,60.2 -183,I7N3G6G,183,1,no description,60.2 -184,I7N3G6G,184,1,no description,60.2 -185,I7N3G6G,185,1,no description,60.2 -186,I7N3G6G,186,1,no description,60.2 -187,I7N3G6G,187,1,no description,60.2 -188,I7N3G6G,188,1,no description,60.2 -189,I7N3G6G,189,1,no description,60.2 -190,I7N3G6G,190,1,no description,60.2 -191,I7N3G6G,191,1,no description,60.2 -192,I7N3G6G,192,1,no description,60.2 -193,I7N3G6G,193,1,no description,60.2 -194,I7N3G6G,194,1,no description,60.2 -195,I7N3G6G,195,1,no description,60.2 -196,I7N3G6G,196,1,no description,60.2 -197,I7N3G6G,197,1,no description,60.2 -198,I7N3G6G,198,1,no description,60.2 -199,I7N3G6G,199,1,no description,60.2 -200,I7N3G6G,200,1,no description,60.2 -201,I7N3G6G,201,1,no description,60.2 -202,I7N3G6G,202,1,no description,60.2 -203,I7N3G6G,203,1,no description,60.2 -204,I7N3G6G,204,1,no description,60.2 -205,I7N3G6G,205,1,no description,60.2 -206,I7N3G6G,206,1,no description,60.2 -207,I7N3G6G,207,1,no description,60.2 -208,I7N3G6G,208,1,no description,60.2 -209,I7N3G6G,209,1,no description,60.2 -210,I7N3G6G,210,1,no description,60.2 -211,I7N3G6G,211,1,no description,60.2 -212,I7N3G6G,212,1,no description,60.2 -213,I7N3G6G,213,1,no description,60.2 -214,I7N3G6G,214,1,no description,60.2 -215,I7N3G6G,215,1,no description,60.2 -216,I7N3G6G,216,1,no description,60.2 -217,I7N3G6G,217,1,no description,60.2 -218,I7N3G6G,218,1,no description,60.2 -219,I7N3G6G,219,1,no description,60.2 -220,I7N3G6G,220,1,no description,60.2 -221,I7N3G6G,221,1,no description,60.2 -222,I7N3G6G,222,1,no description,60.2 -223,I7N3G6G,223,1,no description,60.2 -224,I7N3G6G,224,1,no description,60.2 -225,I7N3G6G,225,1,no description,60.2 -226,I7N3G6G,226,1,no description,60.2 -227,I7N3G6G,227,1,no description,60.2 -228,I7N3G6G,228,1,no description,60.2 -229,I7N3G6G,229,1,no description,60.2 -230,I7N3G6G,230,1,no description,60.2 -231,I7N3G6G,231,1,no description,60.2 -232,I7N3G6G,232,1,no description,60.2 -233,I7N3G6G,233,1,no description,60.2 -234,I7N3G6G,234,1,no description,60.2 -235,I7N3G6G,235,1,no description,60.2 -236,I7N3G6G,236,1,no description,60.2 -237,I7N3G6G,237,1,no description,60.2 -238,I7N3G6G,238,1,no description,60.2 -239,I7N3G6G,239,1,no description,60.2 -240,I7N3G6G,240,1,no description,60.2 -241,I7N3G6G,241,1,no description,60.2 -242,I7N3G6G,242,1,no description,60.2 -243,I7N3G6G,243,1,no description,60.2 -244,I7N3G6G,244,1,no description,60.2 -245,I7N3G6G,245,1,no description,60.2 -246,I7N3G6G,246,1,no description,60.2 -247,I7N3G6G,247,1,no description,60.2 -248,I7N3G6G,248,1,no description,60.2 -249,I7N3G6G,249,1,no description,60.2 -250,I7N3G6G,250,1,no description,60.2 -251,I7N3G6G,251,1,no description,60.2 -252,I7N3G6G,252,1,no description,60.2 -253,I7N3G6G,253,1,no description,60.2 -254,I7N3G6G,254,1,no description,60.2 -255,I7N3G6G,255,1,no description,60.2 -256,I7N3G6G,256,1,no description,60.2 -257,I7N3G6G,257,1,no description,60.2 -258,I7N3G6G,258,1,no description,60.2 -259,I7N3G6G,259,1,no description,60.2 -260,I7N3G6G,260,1,no description,60.2 -261,I7N3G6G,261,1,no description,60.2 -262,I7N3G6G,262,1,no description,60.2 -263,I7N3G6G,263,1,no description,60.2 -264,I7N3G6G,264,1,no description,60.2 -265,I7N3G6G,265,1,no description,60.2 -266,I7N3G6G,266,1,no description,60.2 -267,I7N3G6G,267,1,no description,60.2 -268,I7N3G6G,268,1,no description,60.2 -269,I7N3G6G,269,1,no description,60.2 -270,I7N3G6G,270,1,no description,60.2 -271,I7N3G6G,271,1,no description,60.2 -272,I7N3G6G,272,1,no description,60.2 -273,I7N3G6G,273,1,no description,60.2 -274,I7N3G6G,274,1,no description,60.2 -275,I7N3G6G,275,1,no description,60.2 -276,I7N3G6G,276,1,no description,60.2 -277,I7N3G6G,277,1,no description,60.2 -278,I7N3G6G,278,1,no description,60.2 -279,I7N3G6G,279,1,no description,60.2 -280,I7N3G6G,280,1,no description,60.2 -281,I7N3G6G,281,1,no description,60.2 -282,I7N3G6G,282,1,no description,60.2 -283,I7N3G6G,283,1,no description,60.2 -284,I7N3G6G,284,1,no description,60.2 -285,I7N3G6G,285,1,no description,60.2 -286,I7N3G6G,286,1,no description,60.2 -287,I7N3G6G,287,1,no description,60.2 -288,I7N3G6G,288,1,no description,60.2 -289,I7N3G6G,289,1,no description,60.2 -290,I7N3G6G,290,1,no description,60.2 -291,I7N3G6G,291,1,no description,60.2 -292,I7N3G6G,292,1,no description,60.2 -293,I7N3G6G,293,1,no description,60.2 -294,I7N3G6G,294,1,no description,60.2 -295,I7N3G6G,295,1,no description,60.2 -296,I7N3G6G,296,1,no description,60.2 -297,I7N3G6G,297,1,no description,60.2 -298,I7N3G6G,298,1,no description,60.2 -299,I7N3G6G,299,1,no description,60.2 -300,I7N3G6G,300,1,no description,60.2 -301,I7N3G6G,301,1,no description,60.2 -302,I7N3G6G,302,1,no description,60.2 -303,I7N3G6G,303,1,no description,60.2 -304,I7N3G6G,304,1,no description,60.2 -305,I7N3G6G,305,1,no description,60.2 -306,I7N3G6G,306,1,no description,60.2 -307,I7N3G6G,307,1,no description,60.2 -308,I7N3G6G,308,1,no description,60.2 -309,I7N3G6G,309,1,no description,60.2 -310,I7N3G6G,310,1,no description,60.2 -311,I7N3G6G,311,1,no description,60.2 -312,I7N3G6G,312,1,no description,60.2 -313,I7N3G6G,313,1,no description,60.2 -314,I7N3G6G,314,1,no description,60.2 -315,I7N3G6G,315,1,no description,60.2 -316,I7N3G6G,316,1,no description,60.2 -317,I7N3G6G,317,1,no description,60.2 -318,I7N3G6G,318,1,no description,60.2 -319,I7N3G6G,319,1,no description,60.2 -320,I7N3G6G,320,1,no description,60.2 -321,I7N3G6G,321,1,no description,60.2 -322,I7N3G6G,322,1,no description,60.2 -323,I7N3G6G,323,1,no description,60.2 -324,I7N3G6G,324,1,no description,60.2 -325,I7N3G6G,325,1,no description,60.2 -326,I7N3G6G,326,1,no description,60.2 -327,I7N3G6G,327,1,no description,60.2 -328,I7N3G6G,328,1,no description,60.2 -329,I7N3G6G,329,1,no description,60.2 -330,I7N3G6G,330,1,no description,60.2 -331,I7N3G6G,331,1,no description,60.2 -332,I7N3G6G,332,1,no description,60.2 -333,I7N3G6G,333,1,no description,60.2 -334,I7N3G6G,334,1,no description,60.2 -335,I7N3G6G,335,1,no description,60.2 -336,I7N3G6G,336,1,no description,60.2 -337,I7N3G6G,337,1,no description,60.2 -338,I7N3G6G,338,1,no description,60.2 -339,I7N3G6G,339,1,no description,60.2 -340,I7N3G6G,340,1,no description,60.2 -341,I7N3G6G,341,1,no description,60.2 -342,I7N3G6G,342,1,no description,60.2 -343,I7N3G6G,343,1,no description,60.2 -344,I7N3G6G,344,1,no description,60.2 -345,I7N3G6G,345,1,no description,60.2 -346,I7N3G6G,346,1,no description,60.2 -347,I7N3G6G,347,1,no description,60.2 -348,I7N3G6G,348,1,no description,60.2 -349,I7N3G6G,349,1,no description,60.2 -350,I7N3G6G,350,1,no description,60.2 -351,I7N3G6G,351,1,no description,60.2 -352,I7N3G6G,352,1,no description,60.2 -353,I7N3G6G,353,1,no description,60.2 -354,I7N3G6G,354,1,no description,60.2 -355,I7N3G6G,355,1,no description,60.2 -356,I7N3G6G,356,1,no description,60.2 -357,I7N3G6G,357,1,no description,60.2 -358,I7N3G6G,358,1,no description,60.2 -359,I7N3G6G,359,1,no description,60.2 -360,I7N3G6G,360,1,no description,60.2 -361,I7N3G6G,361,1,no description,60.2 -362,I7N3G6G,362,1,no description,60.2 -363,I7N3G6G,363,1,no description,60.2 -364,I7N3G6G,364,1,no description,60.2 -365,I7N3G6G,365,1,no description,60.2 -366,I7N3G6G,366,1,no description,60.2 -367,I7N3G6G,367,1,no description,60.2 -368,I7N3G6G,368,1,no description,60.2 -369,I7N3G6G,369,1,no description,60.2 -370,I7N3G6G,370,1,no description,60.2 -371,I7N3G6G,371,1,no description,60.2 -372,I7N3G6G,372,1,no description,60.2 -373,I7N3G6G,373,1,no description,60.2 -374,I7N3G6G,374,1,no description,60.2 -375,I7N3G6G,375,1,no description,60.2 -376,I7N3G6G,376,1,no description,60.2 -377,I7N3G6G,377,1,no description,60.2 -378,I7N3G6G,378,1,no description,60.2 -379,I7N3G6G,379,1,no description,60.2 -380,I7N3G6G,380,1,no description,60.2 -381,I7N3G6G,381,1,no description,60.2 -382,I7N3G6G,382,1,no description,60.2 -383,I7N3G6G,383,1,no description,60.2 -384,I7N3G6G,384,1,no description,60.2 -385,I7N3G6G,385,1,no description,60.2 -386,I7N3G6G,386,1,no description,60.2 -387,I7N3G6G,387,1,no description,60.2 -388,I7N3G6G,388,1,no description,60.2 -389,I7N3G6G,389,1,no description,60.2 -390,I7N3G6G,390,1,no description,60.2 -391,I7N3G6G,391,1,no description,60.2 -392,I7N3G6G,392,1,no description,60.2 -393,I7N3G6G,393,1,no description,60.2 -394,I7N3G6G,394,1,no description,60.2 -395,I7N3G6G,395,1,no description,60.2 -396,I7N3G6G,396,1,no description,60.2 -397,I7N3G6G,397,1,no description,60.2 -398,I7N3G6G,398,1,no description,60.2 -399,I7N3G6G,399,1,no description,60.2 -400,I7N3G6G,400,1,no description,60.2 -401,I7N3G6G,401,1,no description,60.2 -402,I7N3G6G,402,1,no description,60.2 -403,I7N3G6G,403,1,no description,60.2 -404,I7N3G6G,404,1,no description,60.2 -405,I7N3G6G,405,1,no description,60.2 -406,I7N3G6G,406,1,no description,60.2 -407,I7N3G6G,407,1,no description,60.2 -408,I7N3G6G,408,1,no description,60.2 -409,I7N3G6G,409,1,no description,60.2 -410,I7N3G6G,410,1,no description,60.2 -411,I7N3G6G,411,1,no description,60.2 -412,I7N3G6G,412,1,no description,60.2 -413,I7N3G6G,413,1,no description,60.2 -414,I7N3G6G,414,1,no description,60.2 -415,I7N3G6G,415,1,no description,60.2 -416,I7N3G6G,416,1,no description,60.2 -417,I7N3G6G,417,1,no description,60.2 -418,I7N3G6G,418,1,no description,60.2 -419,I7N3G6G,419,1,no description,60.2 -420,I7N3G6G,420,1,no description,60.2 -421,I7N3G6G,421,1,no description,60.2 -422,I7N3G6G,422,1,no description,60.2 -423,I7N3G6G,423,1,no description,60.2 -424,I7N3G6G,424,1,no description,60.2 -425,I7N3G6G,425,1,no description,60.2 -426,I7N3G6G,426,1,no description,60.2 -427,I7N3G6G,427,1,no description,60.2 -428,I7N3G6G,428,1,no description,60.2 -429,I7N3G6G,429,1,no description,60.2 -430,I7N3G6G,430,1,no description,60.2 -431,I7N3G6G,431,1,no description,60.2 -432,I7N3G6G,432,1,no description,60.2 -433,I7N3G6G,433,1,no description,60.2 -434,I7N3G6G,434,1,no description,60.2 -435,I7N3G6G,435,1,no description,60.2 -436,I7N3G6G,436,1,no description,60.2 -437,I7N3G6G,437,1,no description,60.2 -438,I7N3G6G,438,1,no description,60.2 -439,I7N3G6G,439,1,no description,60.2 -440,I7N3G6G,440,1,no description,60.2 -441,I7N3G6G,441,1,no description,60.2 -442,I7N3G6G,442,1,no description,60.2 -443,I7N3G6G,443,1,no description,60.2 -444,I7N3G6G,444,1,no description,60.2 -445,I7N3G6G,445,1,no description,60.2 -446,I7N3G6G,446,1,no description,60.2 -447,I7N3G6G,447,1,no description,60.2 -448,I7N3G6G,448,1,no description,60.2 -449,I7N3G6G,449,1,no description,60.2 -450,I7N3G6G,450,1,no description,60.2 -451,I7N3G6G,451,1,no description,60.2 -452,I7N3G6G,452,1,no description,60.2 -453,I7N3G6G,453,1,no description,60.2 -454,I7N3G6G,454,1,no description,60.2 -455,I7N3G6G,455,1,no description,60.2 -456,I7N3G6G,456,1,no description,60.2 -457,I7N3G6G,457,1,no description,60.2 -458,I7N3G6G,458,1,no description,60.2 -459,I7N3G6G,459,1,no description,60.2 -460,I7N3G6G,460,1,no description,60.2 -461,I7N3G6G,461,1,no description,60.2 -462,I7N3G6G,462,1,no description,60.2 -463,I7N3G6G,463,1,no description,60.2 -464,I7N3G6G,464,1,no description,60.2 -465,I7N3G6G,465,1,no description,60.2 -466,I7N3G6G,466,1,no description,60.2 -467,I7N3G6G,467,1,no description,60.2 -468,I7N3G6G,468,1,no description,60.2 -469,I7N3G6G,469,1,no description,60.2 -470,I7N3G6G,470,1,no description,60.2 -471,I7N3G6G,471,1,no description,60.2 -472,I7N3G6G,472,1,no description,60.2 -473,I7N3G6G,473,1,no description,60.2 -474,I7N3G6G,474,1,no description,60.2 -475,I7N3G6G,475,1,no description,60.2 -476,I7N3G6G,476,1,no description,60.2 -477,I7N3G6G,477,1,no description,60.2 -478,I7N3G6G,478,1,no description,60.2 -479,I7N3G6G,479,1,no description,60.2 -480,I7N3G6G,480,1,no description,60.2 -481,I7N3G6G,481,1,no description,60.2 -482,I7N3G6G,482,1,no description,60.2 -483,I7N3G6G,483,1,no description,60.2 -484,I7N3G6G,484,1,no description,60.2 -485,I7N3G6G,485,1,no description,60.2 -486,I7N3G6G,486,1,no description,60.2 -487,I7N3G6G,487,1,no description,60.2 -488,I7N3G6G,488,1,no description,60.2 -489,I7N3G6G,489,1,no description,60.2 -490,I7N3G6G,490,1,no description,60.2 -491,I7N3G6G,491,1,no description,60.2 -492,I7N3G6G,492,1,no description,60.2 -493,I7N3G6G,493,1,no description,60.2 -494,I7N3G6G,494,1,no description,60.2 -495,I7N3G6G,495,1,no description,60.2 -496,I7N3G6G,496,1,no description,60.2 -497,I7N3G6G,497,1,no description,60.2 -498,I7N3G6G,498,1,no description,60.2 -499,I7N3G6G,499,1,no description,60.2 -500,I7N3G6G,500,1,no description,60.2 -501,I7N3G6G,501,1,no description,60.2 -502,I7N3G6G,502,1,no description,60.2 -503,I7N3G6G,503,1,no description,60.2 -504,I7N3G6G,504,1,no description,60.2 -505,I7N3G6G,505,1,no description,60.2 -506,I7N3G6G,506,1,no description,60.2 -507,I7N3G6G,507,1,no description,60.2 -508,I7N3G6G,508,1,no description,60.2 -509,I7N3G6G,509,1,no description,60.2 -510,I7N3G6G,510,1,no description,60.2 -511,I7N3G6G,511,1,no description,60.2 -512,I7N3G6G,512,1,no description,60.2 -513,I7N3G6G,513,1,no description,60.2 -514,I7N3G6G,514,1,no description,60.2 -515,I7N3G6G,515,1,no description,60.2 -516,I7N3G6G,516,1,no description,60.2 -517,I7N3G6G,517,1,no description,60.2 -518,I7N3G6G,518,1,no description,60.2 -519,I7N3G6G,519,1,no description,60.2 -520,I7N3G6G,520,1,no description,60.2 -521,I7N3G6G,521,1,no description,60.2 -522,I7N3G6G,522,1,no description,60.2 -523,I7N3G6G,523,1,no description,60.2 -524,I7N3G6G,524,1,no description,60.2 -525,I7N3G6G,525,1,no description,60.2 -526,I7N3G6G,526,1,no description,60.2 -527,I7N3G6G,527,1,no description,60.2 -528,I7N3G6G,528,1,no description,60.2 -529,I7N3G6G,529,1,no description,60.2 -530,I7N3G6G,530,1,no description,60.2 -531,I7N3G6G,531,1,no description,60.2 -532,I7N3G6G,532,1,no description,60.2 -533,I7N3G6G,533,1,no description,60.2 -534,I7N3G6G,534,1,no description,60.2 -535,I7N3G6G,535,1,no description,60.2 -536,I7N3G6G,536,1,no description,60.2 -537,I7N3G6G,537,1,no description,60.2 -538,I7N3G6G,538,1,no description,60.2 -539,I7N3G6G,539,1,no description,60.2 -540,I7N3G6G,540,1,no description,60.2 -541,I7N3G6G,541,1,no description,60.2 -542,I7N3G6G,542,1,no description,60.2 -543,I7N3G6G,543,1,no description,60.2 -544,I7N3G6G,544,1,no description,60.2 -545,I7N3G6G,545,1,no description,60.2 -546,I7N3G6G,546,1,no description,60.2 -547,I7N3G6G,547,1,no description,60.2 -548,I7N3G6G,548,1,no description,60.2 -549,I7N3G6G,549,1,no description,60.2 -550,I7N3G6G,550,1,no description,60.2 -551,I7N3G6G,551,1,no description,60.2 -552,I7N3G6G,552,1,no description,60.2 -553,I7N3G6G,553,1,no description,60.2 -554,I7N3G6G,554,1,no description,60.2 -555,I7N3G6G,555,1,no description,60.2 -556,I7N3G6G,556,1,no description,60.2 -557,I7N3G6G,557,1,no description,60.2 -558,I7N3G6G,558,1,no description,60.2 -559,I7N3G6G,559,1,no description,60.2 -560,I7N3G6G,560,1,no description,60.2 -561,I7N3G6G,561,1,no description,60.2 -562,I7N3G6G,562,1,no description,60.2 -563,I7N3G6G,563,1,no description,60.2 -564,I7N3G6G,564,1,no description,60.2 -565,I7N3G6G,565,1,no description,60.2 -566,I7N3G6G,566,1,no description,60.2 -567,I7N3G6G,567,1,no description,60.2 -568,I7N3G6G,568,1,no description,60.2 -569,I7N3G6G,569,1,no description,60.2 -570,I7N3G6G,570,1,no description,60.2 -571,I7N3G6G,571,1,no description,60.2 -572,I7N3G6G,572,1,no description,60.2 -573,I7N3G6G,573,1,no description,60.2 -574,I7N3G6G,574,1,no description,60.2 -575,I7N3G6G,575,1,no description,60.2 -576,I7N3G6G,576,1,no description,60.2 -577,I7N3G6G,577,1,no description,60.2 -578,I7N3G6G,578,1,no description,60.2 -579,I7N3G6G,579,1,no description,60.2 -580,I7N3G6G,580,1,no description,60.2 -581,I7N3G6G,581,1,no description,60.2 -582,I7N3G6G,582,1,no description,60.2 -583,I7N3G6G,583,1,no description,60.2 -584,I7N3G6G,584,1,no description,60.2 -585,I7N3G6G,585,1,no description,60.2 -586,I7N3G6G,586,1,no description,60.2 -587,I7N3G6G,587,1,no description,60.2 -588,I7N3G6G,588,1,no description,60.2 -589,I7N3G6G,589,1,no description,60.2 -590,I7N3G6G,590,1,no description,60.2 -591,I7N3G6G,591,1,no description,60.2 -592,I7N3G6G,592,1,no description,60.2 -593,I7N3G6G,593,1,no description,60.2 -594,I7N3G6G,594,1,no description,60.2 -595,I7N3G6G,595,1,no description,60.2 -596,I7N3G6G,596,1,no description,60.2 -597,I7N3G6G,597,1,no description,60.2 -598,I7N3G6G,598,1,no description,60.2 -599,I7N3G6G,599,1,no description,60.2 -600,I7N3G6G,600,1,no description,60.2 -601,I7N3G6G,601,1,no description,60.2 -602,I7N3G6G,602,1,no description,60.2 -603,I7N3G6G,603,1,no description,60.2 -604,I7N3G6G,604,1,no description,60.2 -605,I7N3G6G,605,1,no description,60.2 -606,I7N3G6G,606,1,no description,60.2 -607,I7N3G6G,607,1,no description,60.2 -608,I7N3G6G,608,1,no description,60.2 -609,I7N3G6G,609,1,no description,60.2 -610,I7N3G6G,610,1,no description,60.2 -611,I7N3G6G,611,1,no description,60.2 -612,I7N3G6G,612,1,no description,60.2 -613,I7N3G6G,613,1,no description,60.2 -614,I7N3G6G,614,1,no description,60.2 -615,I7N3G6G,615,1,no description,60.2 -616,I7N3G6G,616,1,no description,60.2 -617,I7N3G6G,617,1,no description,60.2 -618,I7N3G6G,618,1,no description,60.2 -619,I7N3G6G,619,1,no description,60.2 -620,I7N3G6G,620,1,no description,60.2 -621,I7N3G6G,621,1,no description,60.2 -622,I7N3G6G,622,1,no description,60.2 -623,I7N3G6G,623,1,no description,60.2 -624,I7N3G6G,624,1,no description,60.2 -625,I7N3G6G,625,1,no description,60.2 -626,I7N3G6G,626,1,no description,60.2 -627,I7N3G6G,627,1,no description,60.2 -628,I7N3G6G,628,1,no description,60.2 -629,I7N3G6G,629,1,no description,60.2 -630,I7N3G6G,630,1,no description,60.2 -631,I7N3G6G,631,1,no description,60.2 -632,I7N3G6G,632,1,no description,60.2 -633,I7N3G6G,633,1,no description,60.2 -634,I7N3G6G,634,1,no description,60.2 -635,I7N3G6G,635,1,no description,60.2 -636,I7N3G6G,636,1,no description,60.2 -637,I7N3G6G,637,1,no description,60.2 -638,I7N3G6G,638,1,no description,60.2 -639,I7N3G6G,639,1,no description,60.2 -640,I7N3G6G,640,1,no description,60.2 -641,I7N3G6G,641,1,no description,60.2 -642,I7N3G6G,642,1,no description,60.2 -643,I7N3G6G,643,1,no description,60.2 -644,I7N3G6G,644,1,no description,60.2 -645,I7N3G6G,645,1,no description,60.2 -646,I7N3G6G,646,1,no description,60.2 -647,I7N3G6G,647,1,no description,60.2 -648,I7N3G6G,648,1,no description,60.2 -649,I7N3G6G,649,1,no description,60.2 -650,I7N3G6G,650,1,no description,60.2 -651,I7N3G6G,651,1,no description,60.2 -652,I7N3G6G,652,1,no description,60.2 -653,I7N3G6G,653,1,no description,60.2 -654,I7N3G6G,654,1,no description,60.2 -655,I7N3G6G,655,1,no description,60.2 -656,I7N3G6G,656,1,no description,60.2 -657,I7N3G6G,657,1,no description,60.2 -658,I7N3G6G,658,1,no description,60.2 -659,I7N3G6G,659,1,no description,60.2 -660,I7N3G6G,660,1,no description,60.2 -661,I7N3G6G,661,1,no description,60.2 -662,I7N3G6G,662,1,no description,60.2 -663,I7N3G6G,663,1,no description,60.2 -664,I7N3G6G,664,1,no description,60.2 -665,I7N3G6G,665,1,no description,60.2 -666,I7N3G6G,666,1,no description,60.2 -667,I7N3G6G,667,1,no description,60.2 -668,I7N3G6G,668,1,no description,60.2 -669,I7N3G6G,669,1,no description,60.2 -670,I7N3G6G,670,1,no description,60.2 -671,I7N3G6G,671,1,no description,60.2 -672,I7N3G6G,672,1,no description,60.2 -673,I7N3G6G,673,1,no description,60.2 -674,I7N3G6G,674,1,no description,60.2 -675,I7N3G6G,675,1,no description,60.2 -676,I7N3G6G,676,1,no description,60.2 -677,I7N3G6G,677,1,no description,60.2 -678,I7N3G6G,678,1,no description,60.2 -679,I7N3G6G,679,1,no description,60.2 -680,I7N3G6G,680,1,no description,60.2 -681,I7N3G6G,681,1,no description,60.2 -682,I7N3G6G,682,1,no description,60.2 -683,I7N3G6G,683,1,no description,60.2 -684,I7N3G6G,684,1,no description,60.2 -685,I7N3G6G,685,1,no description,60.2 -686,I7N3G6G,686,1,no description,60.2 -687,I7N3G6G,687,1,no description,60.2 -688,I7N3G6G,688,1,no description,60.2 -689,I7N3G6G,689,1,no description,60.2 -690,I7N3G6G,690,1,no description,60.2 -691,I7N3G6G,691,1,no description,60.2 -692,I7N3G6G,692,1,no description,60.2 -693,I7N3G6G,693,1,no description,60.2 -694,I7N3G6G,694,1,no description,60.2 -695,I7N3G6G,695,1,no description,60.2 -696,I7N3G6G,696,1,no description,60.2 -697,I7N3G6G,697,1,no description,60.2 -698,I7N3G6G,698,1,no description,60.2 -699,I7N3G6G,699,1,no description,60.2 -700,I7N3G6G,700,1,no description,60.2 -701,I7N3G6G,701,1,no description,60.2 -702,I7N3G6G,702,1,no description,60.2 -703,I7N3G6G,703,1,no description,60.2 -704,I7N3G6G,704,1,no description,60.2 -705,I7N3G6G,705,1,no description,60.2 -706,I7N3G6G,706,1,no description,60.2 -707,I7N3G6G,707,1,no description,60.2 -708,I7N3G6G,708,1,no description,60.2 -709,I7N3G6G,709,1,no description,60.2 -710,I7N3G6G,710,1,no description,60.2 -711,I7N3G6G,711,1,no description,60.2 -712,I7N3G6G,712,1,no description,60.2 -713,I7N3G6G,713,1,no description,60.2 -714,I7N3G6G,714,1,no description,60.2 -715,I7N3G6G,715,1,no description,60.2 -716,I7N3G6G,716,1,no description,60.2 -717,I7N3G6G,717,1,no description,60.2 -718,I7N3G6G,718,1,no description,60.2 -719,I7N3G6G,719,1,no description,60.2 -720,I7N3G6G,720,1,no description,60.2 -721,I7N3G6G,721,1,no description,60.2 -722,I7N3G6G,722,1,no description,60.2 -723,I7N3G6G,723,1,no description,60.2 -724,I7N3G6G,724,1,no description,60.2 -725,I7N3G6G,725,1,no description,60.2 -726,I7N3G6G,726,1,no description,60.2 -727,I7N3G6G,727,1,no description,60.2 -728,I7N3G6G,728,1,no description,60.2 -729,I7N3G6G,729,1,no description,60.2 -730,I7N3G6G,730,1,no description,60.2 -731,I7N3G6G,731,1,no description,60.2 -732,I7N3G6G,732,1,no description,60.2 -733,I7N3G6G,733,1,no description,60.2 -734,I7N3G6G,734,1,no description,60.2 -735,I7N3G6G,735,1,no description,60.2 -736,I7N3G6G,736,1,no description,60.2 -737,I7N3G6G,737,1,no description,60.2 -738,I7N3G6G,738,1,no description,60.2 -739,I7N3G6G,739,1,no description,60.2 -740,I7N3G6G,740,1,no description,60.2 -741,I7N3G6G,741,1,no description,60.2 -742,I7N3G6G,742,1,no description,60.2 -743,I7N3G6G,743,1,no description,60.2 -744,I7N3G6G,744,1,no description,60.2 -745,I7N3G6G,745,1,no description,60.2 -746,I7N3G6G,746,1,no description,60.2 -747,I7N3G6G,747,1,no description,60.2 -748,I7N3G6G,748,1,no description,60.2 -749,I7N3G6G,749,1,no description,60.2 -750,I7N3G6G,750,1,no description,60.2 -751,I7N3G6G,751,1,no description,60.2 -752,I7N3G6G,752,1,no description,60.2 -753,I7N3G6G,753,1,no description,60.2 -754,I7N3G6G,754,1,no description,60.2 -755,I7N3G6G,755,1,no description,60.2 -756,I7N3G6G,756,1,no description,60.2 -757,I7N3G6G,757,1,no description,60.2 -758,I7N3G6G,758,1,no description,60.2 -759,I7N3G6G,759,1,no description,60.2 -760,I7N3G6G,760,1,no description,60.2 -761,I7N3G6G,761,1,no description,60.2 -762,I7N3G6G,762,1,no description,60.2 -763,I7N3G6G,763,1,no description,60.2 -764,I7N3G6G,764,1,no description,60.2 -765,I7N3G6G,765,1,no description,60.2 -766,I7N3G6G,766,1,no description,60.2 -767,I7N3G6G,767,1,no description,60.2 -768,I7N3G6G,768,1,no description,60.2 -769,I7N3G6G,769,1,no description,60.2 -770,I7N3G6G,770,1,no description,60.2 -771,I7N3G6G,771,1,no description,60.2 -772,I7N3G6G,772,1,no description,60.2 -773,I7N3G6G,773,1,no description,60.2 -774,I7N3G6G,774,1,no description,60.2 -775,I7N3G6G,775,1,no description,60.2 -776,I7N3G6G,776,1,no description,60.2 -777,I7N3G6G,777,1,no description,60.2 -778,I7N3G6G,778,1,no description,60.2 -779,I7N3G6G,779,1,no description,60.2 -780,I7N3G6G,780,1,no description,60.2 -781,I7N3G6G,781,1,no description,60.2 -782,I7N3G6G,782,1,no description,60.2 -783,I7N3G6G,783,1,no description,60.2 -784,I7N3G6G,784,1,no description,60.2 -785,I7N3G6G,785,1,no description,60.2 -786,I7N3G6G,786,1,no description,60.2 -787,I7N3G6G,787,1,no description,60.2 -788,I7N3G6G,788,1,no description,60.2 -789,I7N3G6G,789,1,no description,60.2 -790,I7N3G6G,790,1,no description,60.2 -791,I7N3G6G,791,1,no description,60.2 -792,I7N3G6G,792,1,no description,60.2 -793,I7N3G6G,793,1,no description,60.2 -794,I7N3G6G,794,1,no description,60.2 -795,I7N3G6G,795,1,no description,60.2 -796,I7N3G6G,796,1,no description,60.2 -797,I7N3G6G,797,1,no description,60.2 -798,I7N3G6G,798,1,no description,60.2 -799,I7N3G6G,799,1,no description,60.2 -800,I7N3G6G,800,1,no description,60.2 -801,I7N3G6G,801,1,no description,60.2 -802,I7N3G6G,802,1,no description,60.2 -803,I7N3G6G,803,1,no description,60.2 -804,I7N3G6G,804,1,no description,60.2 -805,I7N3G6G,805,1,no description,60.2 -806,I7N3G6G,806,1,no description,60.2 -807,I7N3G6G,807,1,no description,60.2 -808,I7N3G6G,808,1,no description,60.2 -809,I7N3G6G,809,1,no description,60.2 -810,I7N3G6G,810,1,no description,60.2 -811,I7N3G6G,811,1,no description,60.2 -812,I7N3G6G,812,1,no description,60.2 -813,I7N3G6G,813,1,no description,60.2 -814,I7N3G6G,814,1,no description,60.2 -815,I7N3G6G,815,1,no description,60.2 -816,I7N3G6G,816,1,no description,60.2 -817,I7N3G6G,817,1,no description,60.2 -818,I7N3G6G,818,1,no description,60.2 -819,I7N3G6G,819,1,no description,60.2 -820,I7N3G6G,820,1,no description,60.2 -821,I7N3G6G,821,1,no description,60.2 -822,I7N3G6G,822,1,no description,60.2 -823,I7N3G6G,823,1,no description,60.2 -824,I7N3G6G,824,1,no description,60.2 -825,I7N3G6G,825,1,no description,60.2 -826,I7N3G6G,826,1,no description,60.2 -827,I7N3G6G,827,1,no description,60.2 -828,I7N3G6G,828,1,no description,60.2 -829,I7N3G6G,829,1,no description,60.2 -830,I7N3G6G,830,1,no description,60.2 -831,I7N3G6G,831,1,no description,60.2 -832,I7N3G6G,832,1,no description,60.2 -833,I7N3G6G,833,1,no description,60.2 -834,I7N3G6G,834,1,no description,60.2 -835,I7N3G6G,835,1,no description,60.2 -836,I7N3G6G,836,1,no description,60.2 -837,I7N3G6G,837,1,no description,60.2 -838,I7N3G6G,838,1,no description,60.2 -839,I7N3G6G,839,1,no description,60.2 -840,I7N3G6G,840,1,no description,60.2 -841,I7N3G6G,841,1,no description,60.2 -842,I7N3G6G,842,1,no description,60.2 -843,I7N3G6G,843,1,no description,60.2 -844,I7N3G6G,844,1,no description,60.2 -845,I7N3G6G,845,1,no description,60.2 -846,I7N3G6G,846,1,no description,60.2 -847,I7N3G6G,847,1,no description,60.2 -848,I7N3G6G,848,1,no description,60.2 -849,I7N3G6G,849,1,no description,60.2 -850,I7N3G6G,850,1,no description,60.2 -851,I7N3G6G,851,1,no description,60.2 -852,I7N3G6G,852,1,no description,60.2 -853,I7N3G6G,853,1,no description,60.2 -854,I7N3G6G,854,1,no description,60.2 -855,I7N3G6G,855,1,no description,60.2 -856,I7N3G6G,856,1,no description,60.2 -857,I7N3G6G,857,1,no description,60.2 -858,I7N3G6G,858,1,no description,60.2 -859,I7N3G6G,859,1,no description,60.2 -860,I7N3G6G,860,1,no description,60.2 -861,I7N3G6G,861,1,no description,60.2 -862,I7N3G6G,862,1,no description,60.2 -863,I7N3G6G,863,1,no description,60.2 -864,I7N3G6G,864,1,no description,60.2 -865,I7N3G6G,865,1,no description,60.2 -866,I7N3G6G,866,1,no description,60.2 -867,I7N3G6G,867,1,no description,60.2 -868,I7N3G6G,868,1,no description,60.2 -869,I7N3G6G,869,1,no description,60.2 -870,I7N3G6G,870,1,no description,60.2 -871,I7N3G6G,871,1,no description,60.2 -872,I7N3G6G,872,1,no description,60.2 -873,I7N3G6G,873,1,no description,60.2 -874,I7N3G6G,874,1,no description,60.2 -875,I7N3G6G,875,1,no description,60.2 -876,I7N3G6G,876,1,no description,60.2 -877,I7N3G6G,877,1,no description,60.2 -878,I7N3G6G,878,1,no description,60.2 -879,I7N3G6G,879,1,no description,60.2 -880,I7N3G6G,880,1,no description,60.2 -881,I7N3G6G,881,1,no description,60.2 -882,I7N3G6G,882,1,no description,60.2 -883,I7N3G6G,883,1,no description,60.2 -884,I7N3G6G,884,1,no description,60.2 -885,I7N3G6G,885,1,no description,60.2 -886,I7N3G6G,886,1,no description,60.2 -887,I7N3G6G,887,1,no description,60.2 -888,I7N3G6G,888,1,no description,60.2 -889,I7N3G6G,889,1,no description,60.2 -890,I7N3G6G,890,1,no description,60.2 -891,I7N3G6G,891,1,no description,60.2 -892,I7N3G6G,892,1,no description,60.2 -893,I7N3G6G,893,1,no description,60.2 -894,I7N3G6G,894,1,no description,60.2 -895,I7N3G6G,895,1,no description,60.2 -896,I7N3G6G,896,1,no description,60.2 -897,I7N3G6G,897,1,no description,60.2 -898,I7N3G6G,898,1,no description,60.2 -899,I7N3G6G,899,1,no description,60.2 -900,I7N3G6G,900,1,no description,60.2 -901,I7N3G6G,901,1,no description,60.2 -902,I7N3G6G,902,1,no description,60.2 -903,I7N3G6G,903,1,no description,60.2 -904,I7N3G6G,904,1,no description,60.2 -905,I7N3G6G,905,1,no description,60.2 -906,I7N3G6G,906,1,no description,60.2 -907,I7N3G6G,907,1,no description,60.2 -908,I7N3G6G,908,1,no description,60.2 -909,I7N3G6G,909,1,no description,60.2 -910,I7N3G6G,910,1,no description,60.2 -911,I7N3G6G,911,1,no description,60.2 -912,I7N3G6G,912,1,no description,60.2 -913,I7N3G6G,913,1,no description,60.2 -914,I7N3G6G,914,1,no description,60.2 -915,I7N3G6G,915,1,no description,60.2 -916,I7N3G6G,916,1,no description,60.2 -917,I7N3G6G,917,1,no description,60.2 -918,I7N3G6G,918,1,no description,60.2 -919,I7N3G6G,919,1,no description,60.2 -920,I7N3G6G,920,1,no description,60.2 -921,I7N3G6G,921,1,no description,60.2 -922,I7N3G6G,922,1,no description,60.2 -923,I7N3G6G,923,1,no description,60.2 -924,I7N3G6G,924,1,no description,60.2 -925,I7N3G6G,925,1,no description,60.2 -926,I7N3G6G,926,1,no description,60.2 -927,I7N3G6G,927,1,no description,60.2 -928,I7N3G6G,928,1,no description,60.2 -929,I7N3G6G,929,1,no description,60.2 -930,I7N3G6G,930,1,no description,60.2 -931,I7N3G6G,931,1,no description,60.2 -932,I7N3G6G,932,1,no description,60.2 -933,I7N3G6G,933,1,no description,60.2 -934,I7N3G6G,934,1,no description,60.2 -935,I7N3G6G,935,1,no description,60.2 -936,I7N3G6G,936,1,no description,60.2 -937,I7N3G6G,937,1,no description,60.2 -938,I7N3G6G,938,1,no description,60.2 -939,I7N3G6G,939,1,no description,60.2 -940,I7N3G6G,940,1,no description,60.2 -941,I7N3G6G,941,1,no description,60.2 -942,I7N3G6G,942,1,no description,60.2 -943,I7N3G6G,943,1,no description,60.2 -944,I7N3G6G,944,1,no description,60.2 -945,I7N3G6G,945,1,no description,60.2 -946,I7N3G6G,946,1,no description,60.2 -947,I7N3G6G,947,1,no description,60.2 -948,I7N3G6G,948,1,no description,60.2 -949,I7N3G6G,949,1,no description,60.2 -950,I7N3G6G,950,1,no description,60.2 -951,I7N3G6G,951,1,no description,60.2 -952,I7N3G6G,952,1,no description,60.2 -953,I7N3G6G,953,1,no description,60.2 -954,I7N3G6G,954,1,no description,60.2 -955,I7N3G6G,955,1,no description,60.2 -956,I7N3G6G,956,1,no description,60.2 -957,I7N3G6G,957,1,no description,60.2 -958,I7N3G6G,958,1,no description,60.2 -959,I7N3G6G,959,1,no description,60.2 -960,I7N3G6G,960,1,no description,60.2 -961,I7N3G6G,961,1,no description,60.2 -962,I7N3G6G,962,1,no description,60.2 -963,I7N3G6G,963,1,no description,60.2 -964,I7N3G6G,964,1,no description,60.2 -965,I7N3G6G,965,1,no description,60.2 -966,I7N3G6G,966,1,no description,60.2 -967,I7N3G6G,967,1,no description,60.2 -968,I7N3G6G,968,1,no description,60.2 -969,I7N3G6G,969,1,no description,60.2 -970,I7N3G6G,970,1,no description,60.2 -971,I7N3G6G,971,1,no description,60.2 -972,I7N3G6G,972,1,no description,60.2 -973,I7N3G6G,973,1,no description,60.2 -974,I7N3G6G,974,1,no description,60.2 -975,I7N3G6G,975,1,no description,60.2 -976,I7N3G6G,976,1,no description,60.2 -977,I7N3G6G,977,1,no description,60.2 -978,I7N3G6G,978,1,no description,60.2 -979,I7N3G6G,979,1,no description,60.2 -980,I7N3G6G,980,1,no description,60.2 -981,I7N3G6G,981,1,no description,60.2 -982,I7N3G6G,982,1,no description,60.2 -983,I7N3G6G,983,1,no description,60.2 -984,I7N3G6G,984,1,no description,60.2 -985,I7N3G6G,985,1,no description,60.2 -986,I7N3G6G,986,1,no description,60.2 -987,I7N3G6G,987,1,no description,60.2 -988,I7N3G6G,988,1,no description,60.2 -989,I7N3G6G,989,1,no description,60.2 -990,I7N3G6G,990,1,no description,60.2 -991,I7N3G6G,991,1,no description,60.2 -992,I7N3G6G,992,1,no description,60.2 -993,I7N3G6G,993,1,no description,60.2 -994,I7N3G6G,994,1,no description,60.2 -995,I7N3G6G,995,1,no description,60.2 -996,I7N3G6G,996,1,no description,60.2 -997,I7N3G6G,997,1,no description,60.2 -998,I7N3G6G,998,1,no description,60.2 -999,I7N3G6G,999,1,no description,60.2 -1000,I7N3G6G,1000,1,no description,60.2 -1001,I7N3G6G,1001,1,no description,60.2 -1002,I7N3G6G,1002,1,no description,60.2 -1003,I7N3G6G,1003,1,no description,60.2 -1004,I7N3G6G,1004,1,no description,60.2 -1005,I7N3G6G,1005,1,no description,60.2 -1006,I7N3G6G,1006,1,no description,60.2 -1007,I7N3G6G,1007,1,no description,60.2 -1008,I7N3G6G,1008,1,no description,60.2 -1009,I7N3G6G,1009,1,no description,60.2 -1010,I7N3G6G,1010,1,no description,60.2 -1011,I7N3G6G,1011,1,no description,60.2 -1012,I7N3G6G,1012,1,no description,60.2 -1013,I7N3G6G,1013,1,no description,60.2 -1014,I7N3G6G,1014,1,no description,60.2 -1015,I7N3G6G,1015,1,no description,60.2 -1016,I7N3G6G,1016,1,no description,60.2 -1017,I7N3G6G,1017,1,no description,60.2 -1018,I7N3G6G,1018,1,no description,60.2 -1019,I7N3G6G,1019,1,no description,60.2 -1020,I7N3G6G,1020,1,no description,60.2 -1021,I7N3G6G,1021,1,no description,60.2 -1022,I7N3G6G,1022,1,no description,60.2 -1023,I7N3G6G,1023,1,no description,60.2 -1024,I7N3G6G,1024,1,no description,60.2 -1025,I7N3G6G,1025,1,no description,60.2 -1026,I7N3G6G,1026,1,no description,60.2 -1027,I7N3G6G,1027,1,no description,60.2 -1028,I7N3G6G,1028,1,no description,60.2 -1029,I7N3G6G,1029,1,no description,60.2 -1030,I7N3G6G,1030,1,no description,60.2 -1031,I7N3G6G,1031,1,no description,60.2 -1032,I7N3G6G,1032,1,no description,60.2 -1033,I7N3G6G,1033,1,no description,60.2 -1034,I7N3G6G,1034,1,no description,60.2 -1035,I7N3G6G,1035,1,no description,60.2 -1036,I7N3G6G,1036,1,no description,60.2 -1037,I7N3G6G,1037,1,no description,60.2 -1038,I7N3G6G,1038,1,no description,60.2 -1039,I7N3G6G,1039,1,no description,60.2 -1040,I7N3G6G,1040,1,no description,60.2 -1041,I7N3G6G,1041,1,no description,60.2 -1042,I7N3G6G,1042,1,no description,60.2 -1043,I7N3G6G,1043,1,no description,60.2 -1044,I7N3G6G,1044,1,no description,60.2 -1045,I7N3G6G,1045,1,no description,60.2 -1046,I7N3G6G,1046,1,no description,60.2 -1047,I7N3G6G,1047,1,no description,60.2 -1048,I7N3G6G,1048,1,no description,60.2 -1049,I7N3G6G,1049,1,no description,60.2 -1050,I7N3G6G,1050,1,no description,60.2 -1051,I7N3G6G,1051,1,no description,60.2 -1052,I7N3G6G,1052,1,no description,60.2 -1053,I7N3G6G,1053,1,no description,60.2 -1054,I7N3G6G,1054,1,no description,60.2 -1055,I7N3G6G,1055,1,no description,60.2 -1056,I7N3G6G,1056,1,no description,60.2 -1057,I7N3G6G,1057,1,no description,60.2 -1058,I7N3G6G,1058,1,no description,60.2 -1059,I7N3G6G,1059,1,no description,60.2 -1060,I7N3G6G,1060,1,no description,60.2 -1061,I7N3G6G,1061,1,no description,60.2 -1062,I7N3G6G,1062,1,no description,60.2 -1063,I7N3G6G,1063,1,no description,60.2 -1064,I7N3G6G,1064,1,no description,60.2 -1065,I7N3G6G,1065,1,no description,60.2 -1066,I7N3G6G,1066,1,no description,60.2 -1067,I7N3G6G,1067,1,no description,60.2 -1068,I7N3G6G,1068,1,no description,60.2 -1069,I7N3G6G,1069,1,no description,60.2 -1070,I7N3G6G,1070,1,no description,60.2 -1071,I7N3G6G,1071,1,no description,60.2 -1072,I7N3G6G,1072,1,no description,60.2 -1073,I7N3G6G,1073,1,no description,60.2 -1074,I7N3G6G,1074,1,no description,60.2 -1075,I7N3G6G,1075,1,no description,60.2 -1076,I7N3G6G,1076,1,no description,60.2 -1077,I7N3G6G,1077,1,no description,60.2 -1078,I7N3G6G,1078,1,no description,60.2 -1079,I7N3G6G,1079,1,no description,60.2 -1080,I7N3G6G,1080,1,no description,60.2 -1081,I7N3G6G,1081,1,no description,60.2 -1082,I7N3G6G,1082,1,no description,60.2 -1083,I7N3G6G,1083,1,no description,60.2 -1084,I7N3G6G,1084,1,no description,60.2 -1085,I7N3G6G,1085,1,no description,60.2 -1086,I7N3G6G,1086,1,no description,60.2 -1087,I7N3G6G,1087,1,no description,60.2 -1088,I7N3G6G,1088,1,no description,60.2 -1089,I7N3G6G,1089,1,no description,60.2 -1090,I7N3G6G,1090,1,no description,60.2 -1091,I7N3G6G,1091,1,no description,60.2 -1092,I7N3G6G,1092,1,no description,60.2 -1093,I7N3G6G,1093,1,no description,60.2 -1094,I7N3G6G,1094,1,no description,60.2 -1095,I7N3G6G,1095,1,no description,60.2 -1096,I7N3G6G,1096,1,no description,60.2 -1097,I7N3G6G,1097,1,no description,60.2 -1098,I7N3G6G,1098,1,no description,60.2 -1099,I7N3G6G,1099,1,no description,60.2 -1100,I7N3G6G,1100,1,no description,60.2 -1101,I7N3G6G,1101,1,no description,60.2 -1102,I7N3G6G,1102,1,no description,60.2 -1103,I7N3G6G,1103,1,no description,60.2 -1104,I7N3G6G,1104,1,no description,60.2 -1105,I7N3G6G,1105,1,no description,60.2 -1106,I7N3G6G,1106,1,no description,60.2 -1107,I7N3G6G,1107,1,no description,60.2 -1108,I7N3G6G,1108,1,no description,60.2 -1109,I7N3G6G,1109,1,no description,60.2 -1110,I7N3G6G,1110,1,no description,60.2 -1111,I7N3G6G,1111,1,no description,60.2 -1112,I7N3G6G,1112,1,no description,60.2 -1113,I7N3G6G,1113,1,no description,60.2 -1114,I7N3G6G,1114,1,no description,60.2 -1115,I7N3G6G,1115,1,no description,60.2 -1116,I7N3G6G,1116,1,no description,60.2 -1117,I7N3G6G,1117,1,no description,60.2 -1118,I7N3G6G,1118,1,no description,60.2 -1119,I7N3G6G,1119,1,no description,60.2 -1120,I7N3G6G,1120,1,no description,60.2 -1121,I7N3G6G,1121,1,no description,60.2 -1122,I7N3G6G,1122,1,no description,60.2 -1123,I7N3G6G,1123,1,no description,60.2 -1124,I7N3G6G,1124,1,no description,60.2 -1125,I7N3G6G,1125,1,no description,60.2 -1126,I7N3G6G,1126,1,no description,60.2 -1127,I7N3G6G,1127,1,no description,60.2 -1128,I7N3G6G,1128,1,no description,60.2 -1129,I7N3G6G,1129,1,no description,60.2 -1130,I7N3G6G,1130,1,no description,60.2 -1131,I7N3G6G,1131,1,no description,60.2 -1132,I7N3G6G,1132,1,no description,60.2 -1133,I7N3G6G,1133,1,no description,60.2 -1134,I7N3G6G,1134,1,no description,60.2 -1135,I7N3G6G,1135,1,no description,60.2 -1136,I7N3G6G,1136,1,no description,60.2 -1137,I7N3G6G,1137,1,no description,60.2 -1138,I7N3G6G,1138,1,no description,60.2 -1139,I7N3G6G,1139,1,no description,60.2 -1140,I7N3G6G,1140,1,no description,60.2 -1141,I7N3G6G,1141,1,no description,60.2 -1142,I7N3G6G,1142,1,no description,60.2 -1143,I7N3G6G,1143,1,no description,60.2 -1144,I7N3G6G,1144,1,no description,60.2 -1145,I7N3G6G,1145,1,no description,60.2 -1146,I7N3G6G,1146,1,no description,60.2 -1147,I7N3G6G,1147,1,no description,60.2 -1148,I7N3G6G,1148,1,no description,60.2 -1149,I7N3G6G,1149,1,no description,60.2 -1150,I7N3G6G,1150,1,no description,60.2 -1151,I7N3G6G,1151,1,no description,60.2 -1152,I7N3G6G,1152,1,no description,60.2 -1153,I7N3G6G,1153,1,no description,60.2 -1154,I7N3G6G,1154,1,no description,60.2 -1155,I7N3G6G,1155,1,no description,60.2 -1156,I7N3G6G,1156,1,no description,60.2 -1157,I7N3G6G,1157,1,no description,60.2 -1158,I7N3G6G,1158,1,no description,60.2 -1159,I7N3G6G,1159,1,no description,60.2 -1160,I7N3G6G,1160,1,no description,60.2 -1161,I7N3G6G,1161,1,no description,60.2 -1162,I7N3G6G,1162,1,no description,60.2 -1163,I7N3G6G,1163,1,no description,60.2 -1164,I7N3G6G,1164,1,no description,60.2 -1165,I7N3G6G,1165,1,no description,60.2 -1166,I7N3G6G,1166,1,no description,60.2 -1167,I7N3G6G,1167,1,no description,60.2 -1168,I7N3G6G,1168,1,no description,60.2 -1169,I7N3G6G,1169,1,no description,60.2 -1170,I7N3G6G,1170,1,no description,60.2 -1171,I7N3G6G,1171,1,no description,60.2 -1172,I7N3G6G,1172,1,no description,60.2 -1173,I7N3G6G,1173,1,no description,60.2 -1174,I7N3G6G,1174,1,no description,60.2 -1175,I7N3G6G,1175,1,no description,60.2 -1176,I7N3G6G,1176,1,no description,60.2 -1177,I7N3G6G,1177,1,no description,60.2 -1178,I7N3G6G,1178,1,no description,60.2 -1179,I7N3G6G,1179,1,no description,60.2 -1180,I7N3G6G,1180,1,no description,60.2 -1181,I7N3G6G,1181,1,no description,60.2 -1182,I7N3G6G,1182,1,no description,60.2 -1183,I7N3G6G,1183,1,no description,60.2 -1184,I7N3G6G,1184,1,no description,60.2 -1185,I7N3G6G,1185,1,no description,60.2 -1186,I7N3G6G,1186,1,no description,60.2 -1187,I7N3G6G,1187,1,no description,60.2 -1188,I7N3G6G,1188,1,no description,60.2 -1189,I7N3G6G,1189,1,no description,60.2 -1190,I7N3G6G,1190,1,no description,60.2 -1191,I7N3G6G,1191,1,no description,60.2 -1192,I7N3G6G,1192,1,no description,60.2 -1193,I7N3G6G,1193,1,no description,60.2 -1194,I7N3G6G,1194,1,no description,60.2 -1195,I7N3G6G,1195,1,no description,60.2 -1196,I7N3G6G,1196,1,no description,60.2 -1197,I7N3G6G,1197,1,no description,60.2 -1198,I7N3G6G,1198,1,no description,60.2 -1199,I7N3G6G,1199,1,no description,60.2 -1200,I7N3G6G,1200,1,no description,60.2 -1201,I7N3G6G,1201,1,no description,60.2 -1202,I7N3G6G,1202,1,no description,60.2 -1203,I7N3G6G,1203,1,no description,60.2 -1204,I7N3G6G,1204,1,no description,60.2 -1205,I7N3G6G,1205,1,no description,60.2 -1206,I7N3G6G,1206,1,no description,60.2 -1207,I7N3G6G,1207,1,no description,60.2 -1208,I7N3G6G,1208,1,no description,60.2 -1209,I7N3G6G,1209,1,no description,60.2 -1210,I7N3G6G,1210,1,no description,60.2 -1211,I7N3G6G,1211,1,no description,60.2 -1212,I7N3G6G,1212,1,no description,60.2 -1213,I7N3G6G,1213,1,no description,60.2 -1214,I7N3G6G,1214,1,no description,60.2 -1215,I7N3G6G,1215,1,no description,60.2 -1216,I7N3G6G,1216,1,no description,60.2 -1217,I7N3G6G,1217,1,no description,60.2 -1218,I7N3G6G,1218,1,no description,60.2 -1219,I7N3G6G,1219,1,no description,60.2 -1220,I7N3G6G,1220,1,no description,60.2 -1221,I7N3G6G,1221,1,no description,60.2 -1222,I7N3G6G,1222,1,no description,60.2 -1223,I7N3G6G,1223,1,no description,60.2 -1224,I7N3G6G,1224,1,no description,60.2 -1225,I7N3G6G,1225,1,no description,60.2 -1226,I7N3G6G,1226,1,no description,60.2 -1227,I7N3G6G,1227,1,no description,60.2 -1228,I7N3G6G,1228,1,no description,60.2 -1229,I7N3G6G,1229,1,no description,60.2 -1230,I7N3G6G,1230,1,no description,60.2 -1231,I7N3G6G,1231,1,no description,60.2 -1232,I7N3G6G,1232,1,no description,60.2 -1233,I7N3G6G,1233,1,no description,60.2 -1234,I7N3G6G,1234,1,no description,60.2 -1235,I7N3G6G,1235,1,no description,60.2 -1236,I7N3G6G,1236,1,no description,60.2 -1237,I7N3G6G,1237,1,no description,60.2 -1238,I7N3G6G,1238,1,no description,60.2 -1239,I7N3G6G,1239,1,no description,60.2 -1240,I7N3G6G,1240,1,no description,60.2 -1241,I7N3G6G,1241,1,no description,60.2 -1242,I7N3G6G,1242,1,no description,60.2 -1243,I7N3G6G,1243,1,no description,60.2 -1244,I7N3G6G,1244,1,no description,60.2 -1245,I7N3G6G,1245,1,no description,60.2 -1246,I7N3G6G,1246,1,no description,60.2 -1247,I7N3G6G,1247,1,no description,60.2 -1248,I7N3G6G,1248,1,no description,60.2 -1249,I7N3G6G,1249,1,no description,60.2 -1250,I7N3G6G,1250,1,no description,60.2 -1251,I7N3G6G,1251,1,no description,60.2 -1252,I7N3G6G,1252,1,no description,60.2 -1253,I7N3G6G,1253,1,no description,60.2 -1254,I7N3G6G,1254,1,no description,60.2 -1255,I7N3G6G,1255,1,no description,60.2 -1256,I7N3G6G,1256,1,no description,60.2 -1257,I7N3G6G,1257,1,no description,60.2 -1258,I7N3G6G,1258,1,no description,60.2 -1259,I7N3G6G,1259,1,no description,60.2 -1260,I7N3G6G,1260,1,no description,60.2 -1261,I7N3G6G,1261,1,no description,60.2 -1262,I7N3G6G,1262,1,no description,60.2 -1263,I7N3G6G,1263,1,no description,60.2 -1264,I7N3G6G,1264,1,no description,60.2 -1265,I7N3G6G,1265,1,no description,60.2 -1266,I7N3G6G,1266,1,no description,60.2 -1267,I7N3G6G,1267,1,no description,60.2 -1268,I7N3G6G,1268,1,no description,60.2 -1269,I7N3G6G,1269,1,no description,60.2 -1270,I7N3G6G,1270,1,no description,60.2 -1271,I7N3G6G,1271,1,no description,60.2 -1272,I7N3G6G,1272,1,no description,60.2 -1273,I7N3G6G,1273,1,no description,60.2 -1274,I7N3G6G,1274,1,no description,60.2 -1275,I7N3G6G,1275,1,no description,60.2 -1276,I7N3G6G,1276,1,no description,60.2 -1277,I7N3G6G,1277,1,no description,60.2 -1278,I7N3G6G,1278,1,no description,60.2 -1279,I7N3G6G,1279,1,no description,60.2 -1280,I7N3G6G,1280,1,no description,60.2 -1281,I7N3G6G,1281,1,no description,60.2 -1282,I7N3G6G,1282,1,no description,60.2 -1283,I7N3G6G,1283,1,no description,60.2 -1284,I7N3G6G,1284,1,no description,60.2 -1285,I7N3G6G,1285,1,no description,60.2 -1286,I7N3G6G,1286,1,no description,60.2 -1287,I7N3G6G,1287,1,no description,60.2 -1288,I7N3G6G,1288,1,no description,60.2 -1289,I7N3G6G,1289,1,no description,60.2 -1290,I7N3G6G,1290,1,no description,60.2 -1291,I7N3G6G,1291,1,no description,60.2 -1292,I7N3G6G,1292,1,no description,60.2 -1293,I7N3G6G,1293,1,no description,60.2 -1294,I7N3G6G,1294,1,no description,60.2 -1295,I7N3G6G,1295,1,no description,60.2 -1296,I7N3G6G,1296,1,no description,60.2 -1297,I7N3G6G,1297,1,no description,60.2 -1298,I7N3G6G,1298,1,no description,60.2 -1299,I7N3G6G,1299,1,no description,60.2 -1300,I7N3G6G,1300,1,no description,60.2 -1301,I7N3G6G,1301,1,no description,60.2 -1302,I7N3G6G,1302,1,no description,60.2 -1303,I7N3G6G,1303,1,no description,60.2 -1304,I7N3G6G,1304,1,no description,60.2 -1305,I7N3G6G,1305,1,no description,60.2 -1306,I7N3G6G,1306,1,no description,60.2 -1307,I7N3G6G,1307,1,no description,60.2 -1308,I7N3G6G,1308,1,no description,60.2 -1309,I7N3G6G,1309,1,no description,60.2 -1310,I7N3G6G,1310,1,no description,60.2 -1311,I7N3G6G,1311,1,no description,60.2 -1312,I7N3G6G,1312,1,no description,60.2 -1313,I7N3G6G,1313,1,no description,60.2 -1314,I7N3G6G,1314,1,no description,60.2 -1315,I7N3G6G,1315,1,no description,60.2 -1316,I7N3G6G,1316,1,no description,60.2 -1317,I7N3G6G,1317,1,no description,60.2 -1318,I7N3G6G,1318,1,no description,60.2 -1319,I7N3G6G,1319,1,no description,60.2 -1320,I7N3G6G,1320,1,no description,60.2 -1321,I7N3G6G,1321,1,no description,60.2 -1322,I7N3G6G,1322,1,no description,60.2 -1323,I7N3G6G,1323,1,no description,60.2 -1324,I7N3G6G,1324,1,no description,60.2 -1325,I7N3G6G,1325,1,no description,60.2 -1326,I7N3G6G,1326,1,no description,60.2 -1327,I7N3G6G,1327,1,no description,60.2 -1328,I7N3G6G,1328,1,no description,60.2 -1329,I7N3G6G,1329,1,no description,60.2 -1330,I7N3G6G,1330,1,no description,60.2 -1331,I7N3G6G,1331,1,no description,60.2 -1332,I7N3G6G,1332,1,no description,60.2 -1333,I7N3G6G,1333,1,no description,60.2 -1334,I7N3G6G,1334,1,no description,60.2 -1335,I7N3G6G,1335,1,no description,60.2 -1336,I7N3G6G,1336,1,no description,60.2 -1337,I7N3G6G,1337,1,no description,60.2 -1338,I7N3G6G,1338,1,no description,60.2 -1339,I7N3G6G,1339,1,no description,60.2 -1340,I7N3G6G,1340,1,no description,60.2 -1341,I7N3G6G,1341,1,no description,60.2 -1342,I7N3G6G,1342,1,no description,60.2 -1343,I7N3G6G,1343,1,no description,60.2 -1344,I7N3G6G,1344,1,no description,60.2 -1345,I7N3G6G,1345,1,no description,60.2 -1346,I7N3G6G,1346,1,no description,60.2 -1347,I7N3G6G,1347,1,no description,60.2 -1348,I7N3G6G,1348,1,no description,60.2 -1349,I7N3G6G,1349,1,no description,60.2 -1350,I7N3G6G,1350,1,no description,60.2 -1351,I7N3G6G,1351,1,no description,60.2 -1352,I7N3G6G,1352,1,no description,60.2 -1353,I7N3G6G,1353,1,no description,60.2 -1354,I7N3G6G,1354,1,no description,60.2 -1355,I7N3G6G,1355,1,no description,60.2 -1356,I7N3G6G,1356,1,no description,60.2 -1357,I7N3G6G,1357,1,no description,60.2 -1358,I7N3G6G,1358,1,no description,60.2 -1359,I7N3G6G,1359,1,no description,60.2 -1360,I7N3G6G,1360,1,no description,60.2 -1361,I7N3G6G,1361,1,no description,60.2 -1362,I7N3G6G,1362,1,no description,60.2 -1363,I7N3G6G,1363,1,no description,60.2 -1364,I7N3G6G,1364,1,no description,60.2 -1365,I7N3G6G,1365,1,no description,60.2 -1366,I7N3G6G,1366,1,no description,60.2 -1367,I7N3G6G,1367,1,no description,60.2 -1368,I7N3G6G,1368,1,no description,60.2 -1369,I7N3G6G,1369,1,no description,60.2 -1370,I7N3G6G,1370,1,no description,60.2 -1371,I7N3G6G,1371,1,no description,60.2 -1372,I7N3G6G,1372,1,no description,60.2 -1373,I7N3G6G,1373,1,no description,60.2 -1374,I7N3G6G,1374,1,no description,60.2 -1375,I7N3G6G,1375,1,no description,60.2 -1376,I7N3G6G,1376,1,no description,60.2 -1377,I7N3G6G,1377,1,no description,60.2 -1378,I7N3G6G,1378,1,no description,60.2 -1379,I7N3G6G,1379,1,no description,60.2 -1380,I7N3G6G,1380,1,no description,60.2 -1381,I7N3G6G,1381,1,no description,60.2 -1382,I7N3G6G,1382,1,no description,60.2 -1383,I7N3G6G,1383,1,no description,60.2 -1384,I7N3G6G,1384,1,no description,60.2 -1385,I7N3G6G,1385,1,no description,60.2 -1386,I7N3G6G,1386,1,no description,60.2 -1387,I7N3G6G,1387,1,no description,60.2 -1388,I7N3G6G,1388,1,no description,60.2 -1389,I7N3G6G,1389,1,no description,60.2 -1390,I7N3G6G,1390,1,no description,60.2 -1391,I7N3G6G,1391,1,no description,60.2 -1392,I7N3G6G,1392,1,no description,60.2 -1393,I7N3G6G,1393,1,no description,60.2 -1394,I7N3G6G,1394,1,no description,60.2 -1395,I7N3G6G,1395,1,no description,60.2 -1396,I7N3G6G,1396,1,no description,60.2 -1397,I7N3G6G,1397,1,no description,60.2 -1398,I7N3G6G,1398,1,no description,60.2 -1399,I7N3G6G,1399,1,no description,60.2 -1400,I7N3G6G,1400,1,no description,60.2 -1401,I7N3G6G,1401,1,no description,60.2 -1402,I7N3G6G,1402,1,no description,60.2 -1403,I7N3G6G,1403,1,no description,60.2 -1404,I7N3G6G,1404,1,no description,60.2 -1405,I7N3G6G,1405,1,no description,60.2 -1406,I7N3G6G,1406,1,no description,60.2 -1407,I7N3G6G,1407,1,no description,60.2 -1408,I7N3G6G,1408,1,no description,60.2 -1409,I7N3G6G,1409,1,no description,60.2 -1410,I7N3G6G,1410,1,no description,60.2 -1411,I7N3G6G,1411,1,no description,60.2 -1412,I7N3G6G,1412,1,no description,60.2 -1413,I7N3G6G,1413,1,no description,60.2 -1414,I7N3G6G,1414,1,no description,60.2 -1415,I7N3G6G,1415,1,no description,60.2 -1416,I7N3G6G,1416,1,no description,60.2 -1417,I7N3G6G,1417,1,no description,60.2 -1418,I7N3G6G,1418,1,no description,60.2 -1419,I7N3G6G,1419,1,no description,60.2 -1420,I7N3G6G,1420,1,no description,60.2 -1421,I7N3G6G,1421,1,no description,60.2 -1422,I7N3G6G,1422,1,no description,60.2 -1423,I7N3G6G,1423,1,no description,60.2 -1424,I7N3G6G,1424,1,no description,60.2 -1425,I7N3G6G,1425,1,no description,60.2 -1426,I7N3G6G,1426,1,no description,60.2 -1427,I7N3G6G,1427,1,no description,60.2 -1428,I7N3G6G,1428,1,no description,60.2 -1429,I7N3G6G,1429,1,no description,60.2 -1430,I7N3G6G,1430,1,no description,60.2 -1431,I7N3G6G,1431,1,no description,60.2 -1432,I7N3G6G,1432,1,no description,60.2 -1433,I7N3G6G,1433,1,no description,60.2 -1434,I7N3G6G,1434,1,no description,60.2 -1435,I7N3G6G,1435,1,no description,60.2 -1436,I7N3G6G,1436,1,no description,60.2 -1437,I7N3G6G,1437,1,no description,60.2 -1438,I7N3G6G,1438,1,no description,60.2 -1439,I7N3G6G,1439,1,no description,60.2 -1440,I7N3G6G,1440,1,no description,60.2 -1441,I7N3G6G,1441,1,no description,60.2 -1442,I7N3G6G,1442,1,no description,60.2 -1443,I7N3G6G,1443,1,no description,60.2 -1444,I7N3G6G,1444,1,no description,60.2 -1445,I7N3G6G,1445,1,no description,60.2 -1446,I7N3G6G,1446,1,no description,60.2 -1447,I7N3G6G,1447,1,no description,60.2 -1448,I7N3G6G,1448,1,no description,60.2 -1449,I7N3G6G,1449,1,no description,60.2 -1450,I7N3G6G,1450,1,no description,60.2 -1451,I7N3G6G,1451,1,no description,60.2 -1452,I7N3G6G,1452,1,no description,60.2 -1453,I7N3G6G,1453,1,no description,60.2 -1454,I7N3G6G,1454,1,no description,60.2 -1455,I7N3G6G,1455,1,no description,60.2 -1456,I7N3G6G,1456,1,no description,60.2 -1457,I7N3G6G,1457,1,no description,60.2 -1458,I7N3G6G,1458,1,no description,60.2 -1459,I7N3G6G,1459,1,no description,60.2 -1460,I7N3G6G,1460,1,no description,60.2 -1461,I7N3G6G,1461,1,no description,60.2 -1462,I7N3G6G,1462,1,no description,60.2 -1463,I7N3G6G,1463,1,no description,60.2 -1464,I7N3G6G,1464,1,no description,60.2 -1465,I7N3G6G,1465,1,no description,60.2 -1466,I7N3G6G,1466,1,no description,60.2 -1467,I7N3G6G,1467,1,no description,60.2 -1468,I7N3G6G,1468,1,no description,60.2 -1469,I7N3G6G,1469,1,no description,60.2 -1470,I7N3G6G,1470,1,no description,60.2 -1471,I7N3G6G,1471,1,no description,60.2 -1472,I7N3G6G,1472,1,no description,60.2 -1473,I7N3G6G,1473,1,no description,60.2 -1474,I7N3G6G,1474,1,no description,60.2 -1475,I7N3G6G,1475,1,no description,60.2 -1476,I7N3G6G,1476,1,no description,60.2 -1477,I7N3G6G,1477,1,no description,60.2 -1478,I7N3G6G,1478,1,no description,60.2 -1479,I7N3G6G,1479,1,no description,60.2 -1480,I7N3G6G,1480,1,no description,60.2 -1481,I7N3G6G,1481,1,no description,60.2 -1482,I7N3G6G,1482,1,no description,60.2 -1483,I7N3G6G,1483,1,no description,60.2 -1484,I7N3G6G,1484,1,no description,60.2 -1485,I7N3G6G,1485,1,no description,60.2 -1486,I7N3G6G,1486,1,no description,60.2 -1487,I7N3G6G,1487,1,no description,60.2 -1488,I7N3G6G,1488,1,no description,60.2 -1489,I7N3G6G,1489,1,no description,60.2 -1490,I7N3G6G,1490,1,no description,60.2 -1491,I7N3G6G,1491,1,no description,60.2 -1492,I7N3G6G,1492,1,no description,60.2 -1493,I7N3G6G,1493,1,no description,60.2 -1494,I7N3G6G,1494,1,no description,60.2 -1495,I7N3G6G,1495,1,no description,60.2 -1496,I7N3G6G,1496,1,no description,60.2 -1497,I7N3G6G,1497,1,no description,60.2 -1498,I7N3G6G,1498,1,no description,60.2 -1499,I7N3G6G,1499,1,no description,60.2 -1500,I7N3G6G,1500,1,no description,60.2 -1501,I7N3G6G,1501,1,no description,60.2 -1502,I7N3G6G,1502,1,no description,60.2 -1503,I7N3G6G,1503,1,no description,60.2 -1504,I7N3G6G,1504,1,no description,60.2 -1505,I7N3G6G,1505,1,no description,60.2 -1506,I7N3G6G,1506,1,no description,60.2 -1507,I7N3G6G,1507,1,no description,60.2 -1508,I7N3G6G,1508,1,no description,60.2 -1509,I7N3G6G,1509,1,no description,60.2 -1510,I7N3G6G,1510,1,no description,60.2 -1511,I7N3G6G,1511,1,no description,60.2 -1512,I7N3G6G,1512,1,no description,60.2 -1513,I7N3G6G,1513,1,no description,60.2 -1514,I7N3G6G,1514,1,no description,60.2 -1515,I7N3G6G,1515,1,no description,60.2 -1516,I7N3G6G,1516,1,no description,60.2 -1517,I7N3G6G,1517,1,no description,60.2 -1518,I7N3G6G,1518,1,no description,60.2 -1519,I7N3G6G,1519,1,no description,60.2 -1520,I7N3G6G,1520,1,no description,60.2 -1521,I7N3G6G,1521,1,no description,60.2 -1522,I7N3G6G,1522,1,no description,60.2 -1523,I7N3G6G,1523,1,no description,60.2 -1524,I7N3G6G,1524,1,no description,60.2 -1525,I7N3G6G,1525,1,no description,60.2 -1526,I7N3G6G,1526,1,no description,60.2 -1527,I7N3G6G,1527,1,no description,60.2 -1528,I7N3G6G,1528,1,no description,60.2 -1529,I7N3G6G,1529,1,no description,60.2 -1530,I7N3G6G,1530,1,no description,60.2 -1531,I7N3G6G,1531,1,no description,60.2 -1532,I7N3G6G,1532,1,no description,60.2 -1533,I7N3G6G,1533,1,no description,60.2 -1534,I7N3G6G,1534,1,no description,60.2 -1535,I7N3G6G,1535,1,no description,60.2 -1536,I7N3G6G,1536,1,no description,60.2 -1537,I7N3G6G,1537,1,no description,60.2 -1538,I7N3G6G,1538,1,no description,60.2 -1539,I7N3G6G,1539,1,no description,60.2 -1540,I7N3G6G,1540,1,no description,60.2 -1541,I7N3G6G,1541,1,no description,60.2 -1542,I7N3G6G,1542,1,no description,60.2 -1543,I7N3G6G,1543,1,no description,60.2 -1544,I7N3G6G,1544,1,no description,60.2 -1545,I7N3G6G,1545,1,no description,60.2 -1546,I7N3G6G,1546,1,no description,60.2 -1547,I7N3G6G,1547,1,no description,60.2 -1548,I7N3G6G,1548,1,no description,60.2 -1549,I7N3G6G,1549,1,no description,60.2 -1550,I7N3G6G,1550,1,no description,60.2 -1551,I7N3G6G,1551,1,no description,60.2 -1552,I7N3G6G,1552,1,no description,60.2 -1553,I7N3G6G,1553,1,no description,60.2 -1554,I7N3G6G,1554,1,no description,60.2 -1555,I7N3G6G,1555,1,no description,60.2 -1556,I7N3G6G,1556,1,no description,60.2 -1557,I7N3G6G,1557,1,no description,60.2 -1558,I7N3G6G,1558,1,no description,60.2 -1559,I7N3G6G,1559,1,no description,60.2 -1560,I7N3G6G,1560,1,no description,60.2 -1561,I7N3G6G,1561,1,no description,60.2 -1562,I7N3G6G,1562,1,no description,60.2 -1563,I7N3G6G,1563,1,no description,60.2 -1564,I7N3G6G,1564,1,no description,60.2 -1565,I7N3G6G,1565,1,no description,60.2 -1566,I7N3G6G,1566,1,no description,60.2 -1567,I7N3G6G,1567,1,no description,60.2 -1568,I7N3G6G,1568,1,no description,60.2 -1569,I7N3G6G,1569,1,no description,60.2 -1570,I7N3G6G,1570,1,no description,60.2 -1571,I7N3G6G,1571,1,no description,60.2 -1572,I7N3G6G,1572,1,no description,60.2 -1573,I7N3G6G,1573,1,no description,60.2 -1574,I7N3G6G,1574,1,no description,60.2 -1575,I7N3G6G,1575,1,no description,60.2 -1576,I7N3G6G,1576,1,no description,60.2 -1577,I7N3G6G,1577,1,no description,60.2 -1578,I7N3G6G,1578,1,no description,60.2 -1579,I7N3G6G,1579,1,no description,60.2 -1580,I7N3G6G,1580,1,no description,60.2 -1581,I7N3G6G,1581,1,no description,60.2 -1582,I7N3G6G,1582,1,no description,60.2 -1583,I7N3G6G,1583,1,no description,60.2 -1584,I7N3G6G,1584,1,no description,60.2 -1585,I7N3G6G,1585,1,no description,60.2 -1586,I7N3G6G,1586,1,no description,60.2 -1587,I7N3G6G,1587,1,no description,60.2 -1588,I7N3G6G,1588,1,no description,60.2 -1589,I7N3G6G,1589,1,no description,60.2 -1590,I7N3G6G,1590,1,no description,60.2 -1591,I7N3G6G,1591,1,no description,60.2 -1592,I7N3G6G,1592,1,no description,60.2 -1593,I7N3G6G,1593,1,no description,60.2 -1594,I7N3G6G,1594,1,no description,60.2 -1595,I7N3G6G,1595,1,no description,60.2 -1596,I7N3G6G,1596,1,no description,60.2 -1597,I7N3G6G,1597,1,no description,60.2 -1598,I7N3G6G,1598,1,no description,60.2 -1599,I7N3G6G,1599,1,no description,60.2 -1600,I7N3G6G,1600,1,no description,60.2 -1601,I7N3G6G,1601,1,no description,60.2 -1602,I7N3G6G,1602,1,no description,60.2 -1603,I7N3G6G,1603,1,no description,60.2 -1604,I7N3G6G,1604,1,no description,60.2 -1605,I7N3G6G,1605,1,no description,60.2 -1606,I7N3G6G,1606,1,no description,60.2 -1607,I7N3G6G,1607,1,no description,60.2 -1608,I7N3G6G,1608,1,no description,60.2 -1609,I7N3G6G,1609,1,no description,60.2 -1610,I7N3G6G,1610,1,no description,60.2 -1611,I7N3G6G,1611,1,no description,60.2 -1612,I7N3G6G,1612,1,no description,60.2 -1613,I7N3G6G,1613,1,no description,60.2 -1614,I7N3G6G,1614,1,no description,60.2 -1615,I7N3G6G,1615,1,no description,60.2 -1616,I7N3G6G,1616,1,no description,60.2 -1617,I7N3G6G,1617,1,no description,60.2 -1618,I7N3G6G,1618,1,no description,60.2 -1619,I7N3G6G,1619,1,no description,60.2 -1620,I7N3G6G,1620,1,no description,60.2 -1621,I7N3G6G,1621,1,no description,60.2 -1622,I7N3G6G,1622,1,no description,60.2 -1623,I7N3G6G,1623,1,no description,60.2 -1624,I7N3G6G,1624,1,no description,60.2 -1625,I7N3G6G,1625,1,no description,60.2 -1626,I7N3G6G,1626,1,no description,60.2 -1627,I7N3G6G,1627,1,no description,60.2 -1628,I7N3G6G,1628,1,no description,60.2 -1629,I7N3G6G,1629,1,no description,60.2 -1630,I7N3G6G,1630,1,no description,60.2 -1631,I7N3G6G,1631,1,no description,60.2 -1632,I7N3G6G,1632,1,no description,60.2 -1633,I7N3G6G,1633,1,no description,60.2 -1634,I7N3G6G,1634,1,no description,60.2 -1635,I7N3G6G,1635,1,no description,60.2 -1636,I7N3G6G,1636,1,no description,60.2 -1637,I7N3G6G,1637,1,no description,60.2 -1638,I7N3G6G,1638,1,no description,60.2 -1639,I7N3G6G,1639,1,no description,60.2 -1640,I7N3G6G,1640,1,no description,60.2 -1641,I7N3G6G,1641,1,no description,60.2 -1642,I7N3G6G,1642,1,no description,60.2 -1643,I7N3G6G,1643,1,no description,60.2 -1644,I7N3G6G,1644,1,no description,60.2 -1645,I7N3G6G,1645,1,no description,60.2 -1646,I7N3G6G,1646,1,no description,60.2 -1647,I7N3G6G,1647,1,no description,60.2 -1648,I7N3G6G,1648,1,no description,60.2 -1649,I7N3G6G,1649,1,no description,60.2 -1650,I7N3G6G,1650,1,no description,60.2 -1651,I7N3G6G,1651,1,no description,60.2 -1652,I7N3G6G,1652,1,no description,60.2 -1653,I7N3G6G,1653,1,no description,60.2 -1654,I7N3G6G,1654,1,no description,60.2 -1655,I7N3G6G,1655,1,no description,60.2 -1656,I7N3G6G,1656,1,no description,60.2 -1657,I7N3G6G,1657,1,no description,60.2 -1658,I7N3G6G,1658,1,no description,60.2 -1659,I7N3G6G,1659,1,no description,60.2 -1660,I7N3G6G,1660,1,no description,60.2 -1661,I7N3G6G,1661,1,no description,60.2 -1662,I7N3G6G,1662,1,no description,60.2 -1663,I7N3G6G,1663,1,no description,60.2 -1664,I7N3G6G,1664,1,no description,60.2 -1665,I7N3G6G,1665,1,no description,60.2 -1666,I7N3G6G,1666,1,no description,60.2 -1667,I7N3G6G,1667,1,no description,60.2 -1668,I7N3G6G,1668,1,no description,60.2 -1669,I7N3G6G,1669,1,no description,60.2 -1670,I7N3G6G,1670,1,no description,60.2 -1671,I7N3G6G,1671,1,no description,60.2 -1672,I7N3G6G,1672,1,no description,60.2 -1673,I7N3G6G,1673,1,no description,60.2 -1674,I7N3G6G,1674,1,no description,60.2 -1675,I7N3G6G,1675,1,no description,60.2 -1676,I7N3G6G,1676,1,no description,60.2 -1677,I7N3G6G,1677,1,no description,60.2 -1678,I7N3G6G,1678,1,no description,60.2 -1679,I7N3G6G,1679,1,no description,60.2 -1680,I7N3G6G,1680,1,no description,60.2 -1681,I7N3G6G,1681,1,no description,60.2 -1682,I7N3G6G,1682,1,no description,60.2 -1683,I7N3G6G,1683,1,no description,60.2 -1684,I7N3G6G,1684,1,no description,60.2 -1685,I7N3G6G,1685,1,no description,60.2 -1686,I7N3G6G,1686,1,no description,60.2 -1687,I7N3G6G,1687,1,no description,60.2 -1688,I7N3G6G,1688,1,no description,60.2 -1689,I7N3G6G,1689,1,no description,60.2 -1690,I7N3G6G,1690,1,no description,60.2 -1691,I7N3G6G,1691,1,no description,60.2 -1692,I7N3G6G,1692,1,no description,60.2 -1693,I7N3G6G,1693,1,no description,60.2 -1694,I7N3G6G,1694,1,no description,60.2 -1695,I7N3G6G,1695,1,no description,60.2 -1696,I7N3G6G,1696,1,no description,60.2 -1697,I7N3G6G,1697,1,no description,60.2 -1698,I7N3G6G,1698,1,no description,60.2 -1699,I7N3G6G,1699,1,no description,60.2 -1700,I7N3G6G,1700,1,no description,60.2 -1701,I7N3G6G,1701,1,no description,60.2 -1702,I7N3G6G,1702,1,no description,60.2 -1703,I7N3G6G,1703,1,no description,60.2 -1704,I7N3G6G,1704,1,no description,60.2 -1705,I7N3G6G,1705,1,no description,60.2 -1706,I7N3G6G,1706,1,no description,60.2 -1707,I7N3G6G,1707,1,no description,60.2 -1708,I7N3G6G,1708,1,no description,60.2 -1709,I7N3G6G,1709,1,no description,60.2 -1710,I7N3G6G,1710,1,no description,60.2 -1711,I7N3G6G,1711,1,no description,60.2 -1712,I7N3G6G,1712,1,no description,60.2 -1713,I7N3G6G,1713,1,no description,60.2 -1714,I7N3G6G,1714,1,no description,60.2 -1715,I7N3G6G,1715,1,no description,60.2 -1716,I7N3G6G,1716,1,no description,60.2 -1717,I7N3G6G,1717,1,no description,60.2 -1718,I7N3G6G,1718,1,no description,60.2 -1719,I7N3G6G,1719,1,no description,60.2 -1720,I7N3G6G,1720,1,no description,60.2 -1721,I7N3G6G,1721,1,no description,60.2 -1722,I7N3G6G,1722,1,no description,60.2 -1723,I7N3G6G,1723,1,no description,60.2 -1724,I7N3G6G,1724,1,no description,60.2 -1725,I7N3G6G,1725,1,no description,60.2 -1726,I7N3G6G,1726,1,no description,60.2 -1727,I7N3G6G,1727,1,no description,60.2 -1728,I7N3G6G,1728,1,no description,60.2 -1729,I7N3G6G,1729,1,no description,60.2 -1730,I7N3G6G,1730,1,no description,60.2 -1731,I7N3G6G,1731,1,no description,60.2 -1732,I7N3G6G,1732,1,no description,60.2 -1733,I7N3G6G,1733,1,no description,60.2 -1734,I7N3G6G,1734,1,no description,60.2 -1735,I7N3G6G,1735,1,no description,60.2 -1736,I7N3G6G,1736,1,no description,60.2 -1737,I7N3G6G,1737,1,no description,60.2 -1738,I7N3G6G,1738,1,no description,60.2 -1739,I7N3G6G,1739,1,no description,60.2 -1740,I7N3G6G,1740,1,no description,60.2 -1741,I7N3G6G,1741,1,no description,60.2 -1742,I7N3G6G,1742,1,no description,60.2 -1743,I7N3G6G,1743,1,no description,60.2 -1744,I7N3G6G,1744,1,no description,60.2 -1745,I7N3G6G,1745,1,no description,60.2 -1746,I7N3G6G,1746,1,no description,60.2 -1747,I7N3G6G,1747,1,no description,60.2 -1748,I7N3G6G,1748,1,no description,60.2 -1749,I7N3G6G,1749,1,no description,60.2 -1750,I7N3G6G,1750,1,no description,60.2 -1751,I7N3G6G,1751,1,no description,60.2 -1752,I7N3G6G,1752,1,no description,60.2 -1753,I7N3G6G,1753,1,no description,60.2 -1754,I7N3G6G,1754,1,no description,60.2 -1755,I7N3G6G,1755,1,no description,60.2 -1756,I7N3G6G,1756,1,no description,60.2 -1757,I7N3G6G,1757,1,no description,60.2 -1758,I7N3G6G,1758,1,no description,60.2 -1759,I7N3G6G,1759,1,no description,60.2 -1760,I7N3G6G,1760,1,no description,60.2 -1761,I7N3G6G,1761,1,no description,60.2 -1762,I7N3G6G,1762,1,no description,60.2 -1763,I7N3G6G,1763,1,no description,60.2 -1764,I7N3G6G,1764,1,no description,60.2 -1765,I7N3G6G,1765,1,no description,60.2 -1766,I7N3G6G,1766,1,no description,60.2 -1767,I7N3G6G,1767,1,no description,60.2 -1768,I7N3G6G,1768,1,no description,60.2 -1769,I7N3G6G,1769,1,no description,60.2 -1770,I7N3G6G,1770,1,no description,60.2 -1771,I7N3G6G,1771,1,no description,60.2 -1772,I7N3G6G,1772,1,no description,60.2 -1773,I7N3G6G,1773,1,no description,60.2 -1774,I7N3G6G,1774,1,no description,60.2 -1775,I7N3G6G,1775,1,no description,60.2 -1776,I7N3G6G,1776,1,no description,60.2 -1777,I7N3G6G,1777,1,no description,60.2 -1778,I7N3G6G,1778,1,no description,60.2 -1779,I7N3G6G,1779,1,no description,60.2 -1780,I7N3G6G,1780,1,no description,60.2 -1781,I7N3G6G,1781,1,no description,60.2 -1782,I7N3G6G,1782,1,no description,60.2 -1783,I7N3G6G,1783,1,no description,60.2 -1784,I7N3G6G,1784,1,no description,60.2 -1785,I7N3G6G,1785,1,no description,60.2 -1786,I7N3G6G,1786,1,no description,60.2 -1787,I7N3G6G,1787,1,no description,60.2 -1788,I7N3G6G,1788,1,no description,60.2 -1789,I7N3G6G,1789,1,no description,60.2 -1790,I7N3G6G,1790,1,no description,60.2 -1791,I7N3G6G,1791,1,no description,60.2 -1792,I7N3G6G,1792,1,no description,60.2 -1793,I7N3G6G,1793,1,no description,60.2 -1794,I7N3G6G,1794,1,no description,60.2 -1795,I7N3G6G,1795,1,no description,60.2 -1796,I7N3G6G,1796,1,no description,60.2 -1797,I7N3G6G,1797,1,no description,60.2 -1798,I7N3G6G,1798,1,no description,60.2 -1799,I7N3G6G,1799,1,no description,60.2 -1800,I7N3G6G,1800,1,no description,60.2 -1801,I7N3G6G,1801,1,no description,60.2 -1802,I7N3G6G,1802,1,no description,60.2 -1803,I7N3G6G,1803,1,no description,60.2 -1804,I7N3G6G,1804,1,no description,60.2 -1805,I7N3G6G,1805,1,no description,60.2 -1806,I7N3G6G,1806,1,no description,60.2 -1807,I7N3G6G,1807,1,no description,60.2 -1808,I7N3G6G,1808,1,no description,60.2 -1809,I7N3G6G,1809,1,no description,60.2 -1810,I7N3G6G,1810,1,no description,60.2 -1811,I7N3G6G,1811,1,no description,60.2 -1812,I7N3G6G,1812,1,no description,60.2 -1813,I7N3G6G,1813,1,no description,60.2 -1814,I7N3G6G,1814,1,no description,60.2 -1815,I7N3G6G,1815,1,no description,60.2 -1816,I7N3G6G,1816,1,no description,60.2 -1817,I7N3G6G,1817,1,no description,60.2 -1818,I7N3G6G,1818,1,no description,60.2 -1819,I7N3G6G,1819,1,no description,60.2 -1820,I7N3G6G,1820,1,no description,60.2 -1821,I7N3G6G,1821,1,no description,60.2 -1822,I7N3G6G,1822,1,no description,60.2 -1823,I7N3G6G,1823,1,no description,60.2 -1824,I7N3G6G,1824,1,no description,60.2 -1825,I7N3G6G,1825,1,no description,60.2 -1826,I7N3G6G,1826,1,no description,60.2 -1827,I7N3G6G,1827,1,no description,60.2 -1828,I7N3G6G,1828,1,no description,60.2 -1829,I7N3G6G,1829,1,no description,60.2 -1830,I7N3G6G,1830,1,no description,60.2 -1831,I7N3G6G,1831,1,no description,60.2 -1832,I7N3G6G,1832,1,no description,60.2 -1833,I7N3G6G,1833,1,no description,60.2 -1834,I7N3G6G,1834,1,no description,60.2 -1835,I7N3G6G,1835,1,no description,60.2 -1836,I7N3G6G,1836,1,no description,60.2 -1837,I7N3G6G,1837,1,no description,60.2 -1838,I7N3G6G,1838,1,no description,60.2 -1839,I7N3G6G,1839,1,no description,60.2 -1840,I7N3G6G,1840,1,no description,60.2 -1841,I7N3G6G,1841,1,no description,60.2 -1842,I7N3G6G,1842,1,no description,60.2 -1843,I7N3G6G,1843,1,no description,60.2 -1844,I7N3G6G,1844,1,no description,60.2 -1845,I7N3G6G,1845,1,no description,60.2 -1846,I7N3G6G,1846,1,no description,60.2 -1847,I7N3G6G,1847,1,no description,60.2 -1848,I7N3G6G,1848,1,no description,60.2 -1849,I7N3G6G,1849,1,no description,60.2 -1850,I7N3G6G,1850,1,no description,60.2 -1851,I7N3G6G,1851,1,no description,60.2 -1852,I7N3G6G,1852,1,no description,60.2 -1853,I7N3G6G,1853,1,no description,60.2 -1854,I7N3G6G,1854,1,no description,60.2 -1855,I7N3G6G,1855,1,no description,60.2 -1856,I7N3G6G,1856,1,no description,60.2 -1857,I7N3G6G,1857,1,no description,60.2 -1858,I7N3G6G,1858,1,no description,60.2 -1859,I7N3G6G,1859,1,no description,60.2 -1860,I7N3G6G,1860,1,no description,60.2 -1861,I7N3G6G,1861,1,no description,60.2 -1862,I7N3G6G,1862,1,no description,60.2 -1863,I7N3G6G,1863,1,no description,60.2 -1864,I7N3G6G,1864,1,no description,60.2 -1865,I7N3G6G,1865,1,no description,60.2 -1866,I7N3G6G,1866,1,no description,60.2 -1867,I7N3G6G,1867,1,no description,60.2 -1868,I7N3G6G,1868,1,no description,60.2 -1869,I7N3G6G,1869,1,no description,60.2 -1870,I7N3G6G,1870,1,no description,60.2 -1871,I7N3G6G,1871,1,no description,60.2 -1872,I7N3G6G,1872,1,no description,60.2 -1873,I7N3G6G,1873,1,no description,60.2 -1874,I7N3G6G,1874,1,no description,60.2 -1875,I7N3G6G,1875,1,no description,60.2 -1876,I7N3G6G,1876,1,no description,60.2 -1877,I7N3G6G,1877,1,no description,60.2 -1878,I7N3G6G,1878,1,no description,60.2 -1879,I7N3G6G,1879,1,no description,60.2 -1880,I7N3G6G,1880,1,no description,60.2 -1881,I7N3G6G,1881,1,no description,60.2 -1882,I7N3G6G,1882,1,no description,60.2 -1883,I7N3G6G,1883,1,no description,60.2 -1884,I7N3G6G,1884,1,no description,60.2 -1885,I7N3G6G,1885,1,no description,60.2 -1886,I7N3G6G,1886,1,no description,60.2 -1887,I7N3G6G,1887,1,no description,60.2 -1888,I7N3G6G,1888,1,no description,60.2 -1889,I7N3G6G,1889,1,no description,60.2 -1890,I7N3G6G,1890,1,no description,60.2 -1891,I7N3G6G,1891,1,no description,60.2 -1892,I7N3G6G,1892,1,no description,60.2 -1893,I7N3G6G,1893,1,no description,60.2 -1894,I7N3G6G,1894,1,no description,60.2 -1895,I7N3G6G,1895,1,no description,60.2 -1896,I7N3G6G,1896,1,no description,60.2 -1897,I7N3G6G,1897,1,no description,60.2 -1898,I7N3G6G,1898,1,no description,60.2 -1899,I7N3G6G,1899,1,no description,60.2 -1900,I7N3G6G,1900,1,no description,60.2 -1901,I7N3G6G,1901,1,no description,60.2 -1902,I7N3G6G,1902,1,no description,60.2 -1903,I7N3G6G,1903,1,no description,60.2 -1904,I7N3G6G,1904,1,no description,60.2 -1905,I7N3G6G,1905,1,no description,60.2 -1906,I7N3G6G,1906,1,no description,60.2 -1907,I7N3G6G,1907,1,no description,60.2 -1908,I7N3G6G,1908,1,no description,60.2 -1909,I7N3G6G,1909,1,no description,60.2 -1910,I7N3G6G,1910,1,no description,60.2 -1911,I7N3G6G,1911,1,no description,60.2 -1912,I7N3G6G,1912,1,no description,60.2 -1913,I7N3G6G,1913,1,no description,60.2 -1914,I7N3G6G,1914,1,no description,60.2 -1915,I7N3G6G,1915,1,no description,60.2 -1916,I7N3G6G,1916,1,no description,60.2 -1917,I7N3G6G,1917,1,no description,60.2 -1918,I7N3G6G,1918,1,no description,60.2 -1919,I7N3G6G,1919,1,no description,60.2 -1920,I7N3G6G,1920,1,no description,60.2 -1921,I7N3G6G,1921,1,no description,60.2 -1922,I7N3G6G,1922,1,no description,60.2 -1923,I7N3G6G,1923,1,no description,60.2 -1924,I7N3G6G,1924,1,no description,60.2 -1925,I7N3G6G,1925,1,no description,60.2 -1926,I7N3G6G,1926,1,no description,60.2 -1927,I7N3G6G,1927,1,no description,60.2 -1928,I7N3G6G,1928,1,no description,60.2 -1929,I7N3G6G,1929,1,no description,60.2 -1930,I7N3G6G,1930,1,no description,60.2 -1931,I7N3G6G,1931,1,no description,60.2 -1932,I7N3G6G,1932,1,no description,60.2 -1933,I7N3G6G,1933,1,no description,60.2 -1934,I7N3G6G,1934,1,no description,60.2 -1935,I7N3G6G,1935,1,no description,60.2 -1936,I7N3G6G,1936,1,no description,60.2 -1937,I7N3G6G,1937,1,no description,60.2 -1938,I7N3G6G,1938,1,no description,60.2 -1939,I7N3G6G,1939,1,no description,60.2 -1940,I7N3G6G,1940,1,no description,60.2 -1941,I7N3G6G,1941,1,no description,60.2 -1942,I7N3G6G,1942,1,no description,60.2 -1943,I7N3G6G,1943,1,no description,60.2 -1944,I7N3G6G,1944,1,no description,60.2 -1945,I7N3G6G,1945,1,no description,60.2 -1946,I7N3G6G,1946,1,no description,60.2 -1947,I7N3G6G,1947,1,no description,60.2 -1948,I7N3G6G,1948,1,no description,60.2 -1949,I7N3G6G,1949,1,no description,60.2 -1950,I7N3G6G,1950,1,no description,60.2 -1951,I7N3G6G,1951,1,no description,60.2 -1952,I7N3G6G,1952,1,no description,60.2 -1953,I7N3G6G,1953,1,no description,60.2 -1954,I7N3G6G,1954,1,no description,60.2 -1955,I7N3G6G,1955,1,no description,60.2 -1956,I7N3G6G,1956,1,no description,60.2 -1957,I7N3G6G,1957,1,no description,60.2 -1958,I7N3G6G,1958,1,no description,60.2 -1959,I7N3G6G,1959,1,no description,60.2 -1960,I7N3G6G,1960,1,no description,60.2 -1961,I7N3G6G,1961,1,no description,60.2 -1962,I7N3G6G,1962,1,no description,60.2 -1963,I7N3G6G,1963,1,no description,60.2 -1964,I7N3G6G,1964,1,no description,60.2 -1965,I7N3G6G,1965,1,no description,60.2 -1966,I7N3G6G,1966,1,no description,60.2 -1967,I7N3G6G,1967,1,no description,60.2 -1968,I7N3G6G,1968,1,no description,60.2 -1969,I7N3G6G,1969,1,no description,60.2 -1970,I7N3G6G,1970,1,no description,60.2 -1971,I7N3G6G,1971,1,no description,60.2 -1972,I7N3G6G,1972,1,no description,60.2 -1973,I7N3G6G,1973,1,no description,60.2 -1974,I7N3G6G,1974,1,no description,60.2 -1975,I7N3G6G,1975,1,no description,60.2 -1976,I7N3G6G,1976,1,no description,60.2 -1977,I7N3G6G,1977,1,no description,60.2 -1978,I7N3G6G,1978,1,no description,60.2 -1979,I7N3G6G,1979,1,no description,60.2 -1980,I7N3G6G,1980,1,no description,60.2 -1981,I7N3G6G,1981,1,no description,60.2 -1982,I7N3G6G,1982,1,no description,60.2 -1983,I7N3G6G,1983,1,no description,60.2 -1984,I7N3G6G,1984,1,no description,60.2 -1985,I7N3G6G,1985,1,no description,60.2 -1986,I7N3G6G,1986,1,no description,60.2 -1987,I7N3G6G,1987,1,no description,60.2 -1988,I7N3G6G,1988,1,no description,60.2 -1989,I7N3G6G,1989,1,no description,60.2 -1990,I7N3G6G,1990,1,no description,60.2 -1991,I7N3G6G,1991,1,no description,60.2 -1992,I7N3G6G,1992,1,no description,60.2 -1993,I7N3G6G,1993,1,no description,60.2 -1994,I7N3G6G,1994,1,no description,60.2 -1995,I7N3G6G,1995,1,no description,60.2 -1996,I7N3G6G,1996,1,no description,60.2 -1997,I7N3G6G,1997,1,no description,60.2 -1998,I7N3G6G,1998,1,no description,60.2 -1999,I7N3G6G,1999,1,no description,60.2 -2000,I7N3G6G,2000,1,no description,60.2 -2001,I7N3G6G,2001,1,no description,60.2 -2002,I7N3G6G,2002,1,no description,60.2 -2003,I7N3G6G,2003,1,no description,60.2 -2004,I7N3G6G,2004,1,no description,60.2 -2005,I7N3G6G,2005,1,no description,60.2 -2006,I7N3G6G,2006,1,no description,60.2 -2007,I7N3G6G,2007,1,no description,60.2 -2008,I7N3G6G,2008,1,no description,60.2 -2009,I7N3G6G,2009,1,no description,60.2 -2010,I7N3G6G,2010,1,no description,60.2 -2011,I7N3G6G,2011,1,no description,60.2 -2012,I7N3G6G,2012,1,no description,60.2 -2013,I7N3G6G,2013,1,no description,60.2 -2014,I7N3G6G,2014,1,no description,60.2 -2015,I7N3G6G,2015,1,no description,60.2 -2016,I7N3G6G,2016,1,no description,60.2 -2017,I7N3G6G,2017,1,no description,60.2 -2018,I7N3G6G,2018,1,no description,60.2 -2019,I7N3G6G,2019,1,no description,60.2 -2020,I7N3G6G,2020,1,no description,60.2 -2021,I7N3G6G,2021,1,no description,60.2 -2022,I7N3G6G,2022,1,no description,60.2 -2023,I7N3G6G,2023,1,no description,60.2 -2024,I7N3G6G,2024,1,no description,60.2 -2025,I7N3G6G,2025,1,no description,60.2 -2026,I7N3G6G,2026,1,no description,60.2 -2027,I7N3G6G,2027,1,no description,60.2 -2028,I7N3G6G,2028,1,no description,60.2 -2029,I7N3G6G,2029,1,no description,60.2 -2030,I7N3G6G,2030,1,no description,60.2 -2031,I7N3G6G,2031,1,no description,60.2 -2032,I7N3G6G,2032,1,no description,60.2 -2033,I7N3G6G,2033,1,no description,60.2 -2034,I7N3G6G,2034,1,no description,60.2 -2035,I7N3G6G,2035,1,no description,60.2 -2036,I7N3G6G,2036,1,no description,60.2 -2037,I7N3G6G,2037,1,no description,60.2 -2038,I7N3G6G,2038,1,no description,60.2 -2039,I7N3G6G,2039,1,no description,60.2 -2040,I7N3G6G,2040,1,no description,60.2 -2041,I7N3G6G,2041,1,no description,60.2 -2042,I7N3G6G,2042,1,no description,60.2 -2043,I7N3G6G,2043,1,no description,60.2 -2044,I7N3G6G,2044,1,no description,60.2 -2045,I7N3G6G,2045,1,no description,60.2 -2046,I7N3G6G,2046,1,no description,60.2 -2047,I7N3G6G,2047,1,no description,60.2 -2048,I7N3G6G,2048,1,no description,60.2 -2049,I7N3G6G,2049,1,no description,60.2 -2050,I7N3G6G,2050,1,no description,60.2 -2051,I7N3G6G,2051,1,no description,60.2 -2052,I7N3G6G,2052,1,no description,60.2 -2053,I7N3G6G,2053,1,no description,60.2 -2054,I7N3G6G,2054,1,no description,60.2 -2055,I7N3G6G,2055,1,no description,60.2 -2056,I7N3G6G,2056,1,no description,60.2 -2057,I7N3G6G,2057,1,no description,60.2 -2058,I7N3G6G,2058,1,no description,60.2 -2059,I7N3G6G,2059,1,no description,60.2 -2060,I7N3G6G,2060,1,no description,60.2 -2061,I7N3G6G,2061,1,no description,60.2 -2062,I7N3G6G,2062,1,no description,60.2 -2063,I7N3G6G,2063,1,no description,60.2 -2064,I7N3G6G,2064,1,no description,60.2 -2065,I7N3G6G,2065,1,no description,60.2 -2066,I7N3G6G,2066,1,no description,60.2 -2067,I7N3G6G,2067,1,no description,60.2 -2068,I7N3G6G,2068,1,no description,60.2 -2069,I7N3G6G,2069,1,no description,60.2 -2070,I7N3G6G,2070,1,no description,60.2 -2071,I7N3G6G,2071,1,no description,60.2 -2072,I7N3G6G,2072,1,no description,60.2 -2073,I7N3G6G,2073,1,no description,60.2 -2074,I7N3G6G,2074,1,no description,60.2 -2075,I7N3G6G,2075,1,no description,60.2 -2076,I7N3G6G,2076,1,no description,60.2 -2077,I7N3G6G,2077,1,no description,60.2 -2078,I7N3G6G,2078,1,no description,60.2 -2079,I7N3G6G,2079,1,no description,60.2 -2080,I7N3G6G,2080,1,no description,60.2 -2081,I7N3G6G,2081,1,no description,60.2 -2082,I7N3G6G,2082,1,no description,60.2 -2083,I7N3G6G,2083,1,no description,60.2 -2084,I7N3G6G,2084,1,no description,60.2 -2085,I7N3G6G,2085,1,no description,60.2 -2086,I7N3G6G,2086,1,no description,60.2 -2087,I7N3G6G,2087,1,no description,60.2 -2088,I7N3G6G,2088,1,no description,60.2 -2089,I7N3G6G,2089,1,no description,60.2 -2090,I7N3G6G,2090,1,no description,60.2 -2091,I7N3G6G,2091,1,no description,60.2 -2092,I7N3G6G,2092,1,no description,60.2 -2093,I7N3G6G,2093,1,no description,60.2 -2094,I7N3G6G,2094,1,no description,60.2 -2095,I7N3G6G,2095,1,no description,60.2 -2096,I7N3G6G,2096,1,no description,60.2 -2097,I7N3G6G,2097,1,no description,60.2 -2098,I7N3G6G,2098,1,no description,60.2 -2099,I7N3G6G,2099,1,no description,60.2 -2100,I7N3G6G,2100,1,no description,60.2 -2101,I7N3G6G,2101,1,no description,60.2 -2102,I7N3G6G,2102,1,no description,60.2 -2103,I7N3G6G,2103,1,no description,60.2 -2104,I7N3G6G,2104,1,no description,60.2 -2105,I7N3G6G,2105,1,no description,60.2 -2106,I7N3G6G,2106,1,no description,60.2 -2107,I7N3G6G,2107,1,no description,60.2 -2108,I7N3G6G,2108,1,no description,60.2 -2109,I7N3G6G,2109,1,no description,60.2 -2110,I7N3G6G,2110,1,no description,60.2 -2111,I7N3G6G,2111,1,no description,60.2 -2112,I7N3G6G,2112,1,no description,60.2 -2113,I7N3G6G,2113,1,no description,60.2 -2114,I7N3G6G,2114,1,no description,60.2 -2115,I7N3G6G,2115,1,no description,60.2 -2116,I7N3G6G,2116,1,no description,60.2 -2117,I7N3G6G,2117,1,no description,60.2 -2118,I7N3G6G,2118,1,no description,60.2 -2119,I7N3G6G,2119,1,no description,60.2 -2120,I7N3G6G,2120,1,no description,60.2 -2121,I7N3G6G,2121,1,no description,60.2 -2122,I7N3G6G,2122,1,no description,60.2 -2123,I7N3G6G,2123,1,no description,60.2 -2124,I7N3G6G,2124,1,no description,60.2 -2125,I7N3G6G,2125,1,no description,60.2 -2126,I7N3G6G,2126,1,no description,60.2 -2127,I7N3G6G,2127,1,no description,60.2 -2128,I7N3G6G,2128,1,no description,60.2 -2129,I7N3G6G,2129,1,no description,60.2 -2130,I7N3G6G,2130,1,no description,60.2 -2131,I7N3G6G,2131,1,no description,60.2 -2132,I7N3G6G,2132,1,no description,60.2 -2133,I7N3G6G,2133,1,no description,60.2 -2134,I7N3G6G,2134,1,no description,60.2 -2135,I7N3G6G,2135,1,no description,60.2 -2136,I7N3G6G,2136,1,no description,60.2 -2137,I7N3G6G,2137,1,no description,60.2 -2138,I7N3G6G,2138,1,no description,60.2 -2139,I7N3G6G,2139,1,no description,60.2 -2140,I7N3G6G,2140,1,no description,60.2 -2141,I7N3G6G,2141,1,no description,60.2 -2142,I7N3G6G,2142,1,no description,60.2 -2143,I7N3G6G,2143,1,no description,60.2 -2144,I7N3G6G,2144,1,no description,60.2 -2145,I7N3G6G,2145,1,no description,60.2 -2146,I7N3G6G,2146,1,no description,60.2 -2147,I7N3G6G,2147,1,no description,60.2 -2148,I7N3G6G,2148,1,no description,60.2 -2149,I7N3G6G,2149,1,no description,60.2 -2150,I7N3G6G,2150,1,no description,60.2 -2151,I7N3G6G,2151,1,no description,60.2 -2152,I7N3G6G,2152,1,no description,60.2 -2153,I7N3G6G,2153,1,no description,60.2 -2154,I7N3G6G,2154,1,no description,60.2 -2155,I7N3G6G,2155,1,no description,60.2 -2156,I7N3G6G,2156,1,no description,60.2 -2157,I7N3G6G,2157,1,no description,60.2 -2158,I7N3G6G,2158,1,no description,60.2 -2159,I7N3G6G,2159,1,no description,60.2 -2160,I7N3G6G,2160,1,no description,60.2 -2161,I7N3G6G,2161,1,no description,60.2 -2162,I7N3G6G,2162,1,no description,60.2 -2163,I7N3G6G,2163,1,no description,60.2 -2164,I7N3G6G,2164,1,no description,60.2 -2165,I7N3G6G,2165,1,no description,60.2 -2166,I7N3G6G,2166,1,no description,60.2 -2167,I7N3G6G,2167,1,no description,60.2 -2168,I7N3G6G,2168,1,no description,60.2 -2169,I7N3G6G,2169,1,no description,60.2 -2170,I7N3G6G,2170,1,no description,60.2 -2171,I7N3G6G,2171,1,no description,60.2 -2172,I7N3G6G,2172,1,no description,60.2 -2173,I7N3G6G,2173,1,no description,60.2 -2174,I7N3G6G,2174,1,no description,60.2 -2175,I7N3G6G,2175,1,no description,60.2 -2176,I7N3G6G,2176,1,no description,60.2 -2177,I7N3G6G,2177,1,no description,60.2 -2178,I7N3G6G,2178,1,no description,60.2 -2179,I7N3G6G,2179,1,no description,60.2 -2180,I7N3G6G,2180,1,no description,60.2 -2181,I7N3G6G,2181,1,no description,60.2 -2182,I7N3G6G,2182,1,no description,60.2 -2183,I7N3G6G,2183,1,no description,60.2 -2184,I7N3G6G,2184,1,no description,60.2 -2185,I7N3G6G,2185,1,no description,60.2 -2186,I7N3G6G,2186,1,no description,60.2 -2187,I7N3G6G,2187,1,no description,60.2 -2188,I7N3G6G,2188,1,no description,60.2 -2189,I7N3G6G,2189,1,no description,60.2 -2190,I7N3G6G,2190,1,no description,60.2 -2191,I7N3G6G,2191,1,no description,60.2 -2192,I7N3G6G,2192,1,no description,60.2 -2193,I7N3G6G,2193,1,no description,60.2 -2194,I7N3G6G,2194,1,no description,60.2 -2195,I7N3G6G,2195,1,no description,60.2 -2196,I7N3G6G,2196,1,no description,60.2 -2197,I7N3G6G,2197,1,no description,60.2 -2198,I7N3G6G,2198,1,no description,60.2 -2199,I7N3G6G,2199,1,no description,60.2 -2200,I7N3G6G,2200,1,no description,60.2 -2201,I7N3G6G,2201,1,no description,60.2 -2202,I7N3G6G,2202,1,no description,60.2 -2203,I7N3G6G,2203,1,no description,60.2 -2204,I7N3G6G,2204,1,no description,60.2 -2205,I7N3G6G,2205,1,no description,60.2 -2206,I7N3G6G,2206,1,no description,60.2 -2207,I7N3G6G,2207,1,no description,60.2 -2208,I7N3G6G,2208,1,no description,60.2 -2209,I7N3G6G,2209,1,no description,60.2 -2210,I7N3G6G,2210,1,no description,60.2 -2211,I7N3G6G,2211,1,no description,60.2 -2212,I7N3G6G,2212,1,no description,60.2 -2213,I7N3G6G,2213,1,no description,60.2 -2214,I7N3G6G,2214,1,no description,60.2 -2215,I7N3G6G,2215,1,no description,60.2 -2216,I7N3G6G,2216,1,no description,60.2 -2217,I7N3G6G,2217,1,no description,60.2 -2218,I7N3G6G,2218,1,no description,60.2 -2219,I7N3G6G,2219,1,no description,60.2 -2220,I7N3G6G,2220,1,no description,60.2 -2221,I7N3G6G,2221,1,no description,60.2 -2222,I7N3G6G,2222,1,no description,60.2 -2223,I7N3G6G,2223,1,no description,60.2 -2224,I7N3G6G,2224,1,no description,60.2 -2225,I7N3G6G,2225,1,no description,60.2 -2226,I7N3G6G,2226,1,no description,60.2 diff --git a/packages/itmat-job-executor/test/testFiles/CSVCurator_error4.tsv b/packages/itmat-job-executor/test/testFiles/CSVCurator_error4.tsv deleted file mode 100644 index 0a2a61fbc..000000000 --- a/packages/itmat-job-executor/test/testFiles/CSVCurator_error4.tsv +++ /dev/null @@ -1,2227 +0,0 @@ -ID,SubjectID,VisitID,Sex,Description,Weight -1,I7N3G6G,1,0,no description,60.2 -2,I7N3G6G,2,1,no description,60.2 -3,I7N3G6G,3,1,no description,60.2 -4,I7N3G6G,4,1,no description,60.2 -5,I7N3G6G,5,1,no description,60.2 -6,I7N3G6G,6,1,no description,60.2 -7,I7N3G6G,7,1,no description,60.2 -8,I7N3G6G,8,1,no description,60.2 -9,I7N3G6G,9,1,no description,60.2 -10,I7N3G6G,10,1,no description,60.2 -11,I7N3G6G,11,1,no description,60.2 -12,I7N3G6G,12,1,no description,60.2 -13,I7N3G6G,13,1,no description,60.2 -14,I7N3G6G,14,1,no description,60.2 -15,I7N3G6G,15,1,no description,60.2 -16,I7N3G6G,16,1,no description,60.2 -17,I7N3G6G,17,1,no description,60.2 -18,I7N3G6G,18,1,no description,60.2 -19,I7N3G6G,19,1,no description,60.2 -20,I7N3G6G,20,1,no description,60.2 -21,I7N3G6G,21,1,no description,60.2 -22,I7N3G6G,22,1,no description,60.2 -23,I7N3G6G,23,1,no description,60.2 -24,I7N3G6G,24,1,no description,60.2 -25,I7N3G6G,25,1,no description,60.2 -26,I7N3G6G,26,1,no description,60.2 -27,I7N3G6G,27,1,no description,60.2 -28,I7N3G6G,28,1,no description,60.2 -29,I7N3G6G,29,1,no description,60.2 -30,I7N3G6G,30,1,no description,60.2 -31,I7N3G6G,31,1,no description,60.2 -32,I7N3G6G,32,1,no description,60.2 -33,I7N3G6G,33,1,no description,60.2 -34,I7N3G6G,34,1,no description,60.2 -35,I7N3G6G,35,1,no description,60.2 -36,I7N3G6G,36,1,no description,60.2 -37,I7N3G6G,37,1,no description,60.2 -38,I7N3G6G,38,1,no description,60.2 -39,I7N3G6G,39,1,no description,60.2 -40,I7N3G6G,40,1,no description,60.2 -41,I7N3G6G,41,1,no description,60.2 -42,I7N3G6G,42,1,no description,60.2 -43,I7N3G6G,43,1,no description,60.2 -44,I7N3G6G,44,1,no description,60.2 -45,I7N3G6G,45,1,no description,60.2 -46,I7N3G6G,46,1,no description,60.2 -47,I7N3G6G,47,1,no description,60.2 -48,I7N3G6G,48,1,no description,60.2 -49,I7N3G6G,49,1,no description,60.2 -50,I7N3G6G,50,1,no description,60.2 -51,I7N3G6G,51,1,no description,60.2 -52,I7N3G6G,52,1,no description,60.2 -53,I7N3G6G,53,1,no description,60.2 -54,I7N3G6G,54,1,no description,60.2 -55,I7N3G6G,55,1,no description,60.2 -56,I7N3G6G,56,1,no description,60.2 -57,I7N3G6G,57,1,no description,60.2 -58,I7N3G6G,58,1,no description,60.2 -59,I7N3G6G,59,1,no description,60.2 -60,I7N3G6G,60,1,no description,60.2 -61,I7N3G6G,61,1,no description,60.2 -62,I7N3G6G,62,1,no description,60.2 -63,I7N3G6G,63,1,no description,60.2 -64,I7N3G6G,64,1,no description,60.2 -65,I7N3G6G,65,1,no description,60.2 -66,I7N3G6G,66,1,no description,60.2 -67,I7N3G6G,67,1,no description,60.2 -68,I7N3G6G,68,1,no description,60.2 -69,I7N3G6G,69,1,no description,60.2 -70,I7N3G6G,70,1,no description,60.2 -71,I7N3G6G,71,1,no description,60.2 -72,I7N3G6G,72,1,no description,60.2 -73,I7N3G6G,73,1,no description,60.2 -74,I7N3G6G,74,1,no description,60.2 -75,I7N3G6G,75,1,no description,60.2 -76,I7N3G6G,76,1,no description,60.2 -77,I7N3G6G,77,1,no description,60.2 -78,I7N3G6G,78,1,no description,60.2 -79,I7N3G6G,79,1,no description,60.2 -80,I7N3G6G,80,1,no description,60.2 -81,I7N3G6G,81,1,no description,60.2 -82,I7N3G6G,82,1,no description,60.2 -83,I7N3G6G,83,1,no description,60.2 -84,I7N3G6G,84,1,no description,60.2 -85,I7N3G6G,85,1,no description,60.2 -86,I7N3G6G,86,1,no description,60.2 -87,I7N3G6G,87,1,no description,60.2 -88,I7N3G6G,88,1,no description,60.2 -89,I7N3G6G,89,1,no description,60.2 -90,I7N3G6G,90,1,no description,60.2 -91,I7N3G6G,91,1,no description,60.2 -92,I7N3G6G,92,1,no description,60.2 -93,I7N3G6G,93,1,no description,60.2 -94,I7N3G6G,94,1,no description,60.2 -95,I7N3G6G,95,1,no description,60.2 -96,I7N3G6G,96,1,no description,60.2 -97,I7N3G6G,97,1,no description,60.2 -98,I7N3G6G,98,1,no description,60.2 -99,I7N3G6G,99,1,no description,60.2 -100,I7N3G6G,100,1,no description,60.2 -101,I7N3G6G,101,1,no description,60.2 -102,I7N3G6G,102,1,no description,60.2 -103,I7N3G6G,103,1,no description,60.2 -104,I7N3G6G,104,1,no description,60.2 -105,I7N3G6G,105,1,no description,60.2 -106,I7N3G6G,106,1,no description,60.2 -107,I7N3G6G,107,1,no description,60.2 -108,I7N3G6G,108,1,no description,60.2 -109,I7N3G6G,109,1,no description,60.2 -110,I7N3G6G,110,1,no description,60.2 -111,I7N3G6G,111,1,no description,60.2 -112,I7N3G6G,112,1,no description,60.2 -113,I7N3G6G,113,1,no description,60.2 -114,I7N3G6G,114,1,no description,60.2 -115,I7N3G6G,115,1,no description,60.2 -116,I7N3G6G,116,1,no description,60.2 -117,I7N3G6G,117,1,no description,60.2 -118,I7N3G6G,118,1,no description,60.2 -119,I7N3G6G,119,1,no description,60.2 -120,I7N3G6G,120,1,no description,60.2 -121,I7N3G6G,121,1,no description,60.2 -122,I7N3G6G,122,1,no description,60.2 -123,I7N3G6G,123,1,no description,60.2 -124,I7N3G6G,124,1,no description,60.2 -125,I7N3G6G,125,1,no description,60.2 -126,I7N3G6G,126,1,no description,60.2 -127,I7N3G6G,127,1,no description,60.2 -128,I7N3G6G,128,1,no description,60.2 -129,I7N3G6G,129,1,no description,60.2 -130,I7N3G6G,130,1,no description,60.2 -131,I7N3G6G,131,1,no description,60.2 -132,I7N3G6G,132,1,no description,60.2 -133,I7N3G6G,133,1,no description,60.2 -134,I7N3G6G,134,1,no description,60.2 -135,I7N3G6G,135,1,no description,60.2 -136,I7N3G6G,136,1,no description,60.2 -137,I7N3G6G,137,1,no description,60.2 -138,I7N3G6G,138,1,no description,60.2 -139,I7N3G6G,139,1,no description,60.2 -140,I7N3G6G,140,1,no description,60.2 -141,I7N3G6G,141,1,no description,60.2 -142,I7N3G6G,142,1,no description,60.2 -143,I7N3G6G,143,1,no description,60.2 -144,I7N3G6G,144,1,no description,60.2 -145,I7N3G6G,145,1,no description,60.2 -146,I7N3G6G,146,1,no description,60.2 -147,I7N3G6G,147,1,no description,60.2 -148,I7N3G6G,148,1,no description,60.2 -149,I7N3G6G,149,1,no description,60.2 -150,I7N3G6G,150,1,no description,60.2 -151,I7N3G6G,151,1,no description,60.2 -152,I7N3G6G,152,1,no description,60.2 -153,I7N3G6G,153,1,no description,60.2 -154,I7N3G6G,154,1,no description,60.2 -155,I7N3G6G,155,1,no description,60.2 -156,I7N3G6G,156,1,no description,60.2 -157,I7N3G6G,157,1,no description,60.2 -158,I7N3G6G,158,1,no description,60.2 -159,I7N3G6G,159,1,no description,60.2 -160,I7N3G6G,160,1,no description,60.2 -161,I7N3G6G,161,1,no description,60.2 -162,I7N3G6G,162,1,no description,60.2 -163,I7N3G6G,163,1,no description,60.2 -164,I7N3G6G,164,1,no description,60.2 -165,I7N3G6G,165,1,no description,60.2 -166,I7N3G6G,166,1,no description,60.2 -167,I7N3G6G,167,1,no description,60.2 -168,I7N3G6G,168,1,no description,60.2 -169,I7N3G6G,169,1,no description,60.2 -170,I7N3G6G,170,1,no description,60.2 -171,I7N3G6G,171,1,no description,60.2 -172,I7N3G6G,172,1,no description,60.2 -173,I7N3G6G,173,1,no description,60.2 -174,I7N3G6G,174,1,no description,60.2 -175,I7N3G6G,175,1,no description,60.2 -176,I7N3G6G,176,1,no description,60.2 -177,I7N3G6G,177,1,no description,60.2 -178,I7N3G6G,178,1,no description,60.2 -179,I7N3G6G,179,1,no description,60.2 -180,I7N3G6G,180,1,no description,60.2 -181,I7N3G6G,181,1,no description,60.2 -182,I7N3G6G,182,1,no description,60.2 -183,I7N3G6G,183,1,no description,60.2 -184,I7N3G6G,184,1,no description,60.2 -185,I7N3G6G,185,1,no description,60.2 -186,I7N3G6G,186,1,no description,60.2 -187,I7N3G6G,187,1,no description,60.2 -188,I7N3G6G,188,1,no description,60.2 -189,I7N3G6G,189,1,no description,60.2 -190,I7N3G6G,190,1,no description,60.2 -191,I7N3G6G,191,1,no description,60.2 -192,I7N3G6G,192,1,no description,60.2 -193,I7N3G6G,193,1,no description,60.2 -194,I7N3G6G,194,1,no description,60.2 -195,I7N3G6G,195,1,no description,60.2 -196,I7N3G6G,196,1,no description,60.2 -197,I7N3G6G,197,1,no description,60.2 -198,I7N3G6G,198,1,no description,60.2 -199,I7N3G6G,199,1,no description,60.2 -200,I7N3G6G,200,1,no description,60.2 -201,I7N3G6G,201,1,no description,60.2 -202,I7N3G6G,202,1,no description,60.2 -203,I7N3G6G,203,1,no description,60.2 -204,I7N3G6G,204,1,no description,60.2 -205,I7N3G6G,205,1,no description,60.2 -206,I7N3G6G,206,1,no description,60.2 -207,I7N3G6G,207,1,no description,60.2 -208,I7N3G6G,208,1,no description,60.2 -209,I7N3G6G,209,1,no description,60.2 -210,I7N3G6G,210,1,no description,60.2 -211,I7N3G6G,211,1,no description,60.2 -212,I7N3G6G,212,1,no description,60.2 -213,I7N3G6G,213,1,no description,60.2 -214,I7N3G6G,214,1,no description,60.2 -215,I7N3G6G,215,1,no description,60.2 -216,I7N3G6G,216,1,no description,60.2 -217,I7N3G6G,217,1,no description,60.2 -218,I7N3G6G,218,1,no description,60.2 -219,I7N3G6G,219,1,no description,60.2 -220,I7N3G6G,220,1,no description,60.2 -221,I7N3G6G,221,1,no description,60.2 -222,I7N3G6G,222,1,no description,60.2 -223,I7N3G6G,223,1,no description,60.2 -224,I7N3G6G,224,1,no description,60.2 -225,I7N3G6G,225,1,no description,60.2 -226,I7N3G6G,226,1,no description,60.2 -227,I7N3G6G,227,1,no description,60.2 -228,I7N3G6G,228,1,no description,60.2 -229,I7N3G6G,229,1,no description,60.2 -230,I7N3G6G,230,1,no description,60.2 -231,I7N3G6G,231,1,no description,60.2 -232,I7N3G6G,232,1,no description,60.2 -233,I7N3G6G,233,1,no description,60.2 -234,I7N3G6G,234,1,no description,60.2 -235,I7N3G6G,235,1,no description,60.2 -236,I7N3G6G,236,1,no description,60.2 -237,I7N3G6G,237,1,no description,60.2 -238,I7N3G6G,238,1,no description,60.2 -239,I7N3G6G,239,1,no description,60.2 -240,I7N3G6G,240,1,no description,60.2 -241,I7N3G6G,241,1,no description,60.2 -242,I7N3G6G,242,1,no description,60.2 -243,I7N3G6G,243,1,no description,60.2 -244,I7N3G6G,244,1,no description,60.2 -245,I7N3G6G,245,1,no description,60.2 -246,I7N3G6G,246,1,no description,60.2 -247,I7N3G6G,247,1,no description,60.2 -248,I7N3G6G,248,1,no description,60.2 -249,I7N3G6G,249,1,no description,60.2 -250,I7N3G6G,250,1,no description,60.2 -251,I7N3G6G,251,1,no description,60.2 -252,I7N3G6G,252,1,no description,60.2 -253,I7N3G6G,253,1,no description,60.2 -254,I7N3G6G,254,1,no description,60.2 -255,I7N3G6G,255,1,no description,60.2 -256,I7N3G6G,256,1,no description,60.2 -257,I7N3G6G,257,1,no description,60.2 -258,I7N3G6G,258,1,no description,60.2 -259,I7N3G6G,259,1,no description,60.2 -260,I7N3G6G,260,1,no description,60.2 -261,I7N3G6G,261,1,no description,60.2 -262,I7N3G6G,262,1,no description,60.2 -263,I7N3G6G,263,1,no description,60.2 -264,I7N3G6G,264,1,no description,60.2 -265,I7N3G6G,265,1,no description,60.2 -266,I7N3G6G,266,1,no description,60.2 -267,I7N3G6G,267,1,no description,60.2 -268,I7N3G6G,268,1,no description,60.2 -269,I7N3G6G,269,1,no description,60.2 -270,I7N3G6G,270,1,no description,60.2 -271,I7N3G6G,271,1,no description,60.2 -272,I7N3G6G,272,1,no description,60.2 -273,I7N3G6G,273,1,no description,60.2 -274,I7N3G6G,274,1,no description,60.2 -275,I7N3G6G,275,1,no description,60.2 -276,I7N3G6G,276,1,no description,60.2 -277,I7N3G6G,277,1,no description,60.2 -278,I7N3G6G,278,1,no description,60.2 -279,I7N3G6G,279,1,no description,60.2 -280,I7N3G6G,280,1,no description,60.2 -281,I7N3G6G,281,1,no description,60.2 -282,I7N3G6G,282,1,no description,60.2 -283,I7N3G6G,283,1,no description,60.2 -284,I7N3G6G,284,1,no description,60.2 -285,I7N3G6G,285,1,no description,60.2 -286,I7N3G6G,286,1,no description,60.2 -287,I7N3G6G,287,1,no description,60.2 -288,I7N3G6G,288,1,no description,60.2 -289,I7N3G6G,289,1,no description,60.2 -290,I7N3G6G,290,1,no description,60.2 -291,I7N3G6G,291,1,no description,60.2 -292,I7N3G6G,292,1,no description,60.2 -293,I7N3G6G,293,1,no description,60.2 -294,I7N3G6G,294,1,no description,60.2 -295,I7N3G6G,295,1,no description,60.2 -296,I7N3G6G,296,1,no description,60.2 -297,I7N3G6G,297,1,no description,60.2 -298,I7N3G6G,298,1,no description,60.2 -299,I7N3G6G,299,1,no description,60.2 -300,I7N3G6G,300,1,no description,60.2 -301,I7N3G6G,301,1,no description,60.2 -302,I7N3G6G,302,1,no description,60.2 -303,I7N3G6G,303,1,no description,60.2 -304,I7N3G6G,304,1,no description,60.2 -305,I7N3G6G,305,1,no description,60.2 -306,I7N3G6G,306,1,no description,60.2 -307,I7N3G6G,307,1,no description,60.2 -308,I7N3G6G,308,1,no description,60.2 -309,I7N3G6G,309,1,no description,60.2 -310,I7N3G6G,310,1,no description,60.2 -311,I7N3G6G,311,1,no description,60.2 -312,I7N3G6G,312,1,no description,60.2 -313,I7N3G6G,313,1,no description,60.2 -314,I7N3G6G,314,1,no description,60.2 -315,I7N3G6G,315,1,no description,60.2 -316,I7N3G6G,316,1,no description,60.2 -317,I7N3G6G,317,1,no description,60.2 -318,I7N3G6G,318,1,no description,60.2 -319,I7N3G6G,319,1,no description,60.2 -320,I7N3G6G,320,1,no description,60.2 -321,I7N3G6G,321,1,no description,60.2 -322,I7N3G6G,322,1,no description,60.2 -323,I7N3G6G,323,1,no description,60.2 -324,I7N3G6G,324,1,no description,60.2 -325,I7N3G6G,325,1,no description,60.2 -326,I7N3G6G,326,1,no description,60.2 -327,I7N3G6G,327,1,no description,60.2 -328,I7N3G6G,328,1,no description,60.2 -329,I7N3G6G,329,1,no description,60.2 -330,I7N3G6G,330,1,no description,60.2 -331,I7N3G6G,331,1,no description,60.2 -332,I7N3G6G,332,1,no description,60.2 -333,I7N3G6G,333,1,no description,60.2 -334,I7N3G6G,334,1,no description,60.2 -335,I7N3G6G,335,1,no description,60.2 -336,I7N3G6G,336,1,no description,60.2 -337,I7N3G6G,337,1,no description,60.2 -338,I7N3G6G,338,1,no description,60.2 -339,I7N3G6G,339,1,no description,60.2 -340,I7N3G6G,340,1,no description,60.2 -341,I7N3G6G,341,1,no description,60.2 -342,I7N3G6G,342,1,no description,60.2 -343,I7N3G6G,343,1,no description,60.2 -344,I7N3G6G,344,1,no description,60.2 -345,I7N3G6G,345,1,no description,60.2 -346,I7N3G6G,346,1,no description,60.2 -347,I7N3G6G,347,1,no description,60.2 -348,I7N3G6G,348,1,no description,60.2 -349,I7N3G6G,349,1,no description,60.2 -350,I7N3G6G,350,1,no description,60.2 -351,I7N3G6G,351,1,no description,60.2 -352,I7N3G6G,352,1,no description,60.2 -353,I7N3G6G,353,1,no description,60.2 -354,I7N3G6G,354,1,no description,60.2 -355,I7N3G6G,355,1,no description,60.2 -356,I7N3G6G,356,1,no description,60.2 -357,I7N3G6G,357,1,no description,60.2 -358,I7N3G6G,358,1,no description,60.2 -359,I7N3G6G,359,1,no description,60.2 -360,I7N3G6G,360,1,no description,60.2 -361,I7N3G6G,361,1,no description,60.2 -362,I7N3G6G,362,1,no description,60.2 -363,I7N3G6G,363,1,no description,60.2 -364,I7N3G6G,364,1,no description,60.2 -365,I7N3G6G,365,1,no description,60.2 -366,I7N3G6G,366,1,no description,60.2 -367,I7N3G6G,367,1,no description,60.2 -368,I7N3G6G,368,1,no description,60.2 -369,I7N3G6G,369,1,no description,60.2 -370,I7N3G6G,370,1,no description,60.2 -371,I7N3G6G,371,1,no description,60.2 -372,I7N3G6G,372,1,no description,60.2 -373,I7N3G6G,373,1,no description,60.2 -374,I7N3G6G,374,1,no description,60.2 -375,I7N3G6G,375,1,no description,60.2 -376,I7N3G6G,376,1,no description,60.2 -377,I7N3G6G,377,1,no description,60.2 -378,I7N3G6G,378,1,no description,60.2 -379,I7N3G6G,379,1,no description,60.2 -380,I7N3G6G,380,1,no description,60.2 -381,I7N3G6G,381,1,no description,60.2 -382,I7N3G6G,382,1,no description,60.2 -383,I7N3G6G,383,1,no description,60.2 -384,I7N3G6G,384,1,no description,60.2 -385,I7N3G6G,385,1,no description,60.2 -386,I7N3G6G,386,1,no description,60.2 -387,I7N3G6G,387,1,no description,60.2 -388,I7N3G6G,388,1,no description,60.2 -389,I7N3G6G,389,1,no description,60.2 -390,I7N3G6G,390,1,no description,60.2 -391,I7N3G6G,391,1,no description,60.2 -392,I7N3G6G,392,1,no description,60.2 -393,I7N3G6G,393,1,no description,60.2 -394,I7N3G6G,394,1,no description,60.2 -395,I7N3G6G,395,1,no description,60.2 -396,I7N3G6G,396,1,no description,60.2 -397,I7N3G6G,397,1,no description,60.2 -398,I7N3G6G,398,1,no description,60.2 -399,I7N3G6G,399,1,no description,60.2 -400,I7N3G6G,400,1,no description,60.2 -401,I7N3G6G,401,1,no description,60.2 -402,I7N3G6G,402,1,no description,60.2 -403,I7N3G6G,403,1,no description,60.2 -404,I7N3G6G,404,1,no description,60.2 -405,I7N3G6G,405,1,no description,60.2 -406,I7N3G6G,406,1,no description,60.2 -407,I7N3G6G,407,1,no description,60.2 -408,I7N3G6G,408,1,no description,60.2 -409,I7N3G6G,409,1,no description,60.2 -410,I7N3G6G,410,1,no description,60.2 -411,I7N3G6G,411,1,no description,60.2 -412,I7N3G6G,412,1,no description,60.2 -413,I7N3G6G,413,1,no description,60.2 -414,I7N3G6G,414,1,no description,60.2 -415,I7N3G6G,415,1,no description,60.2 -416,I7N3G6G,416,1,no description,60.2 -417,I7N3G6G,417,1,no description,60.2 -418,I7N3G6G,418,1,no description,60.2 -419,I7N3G6G,419,1,no description,60.2 -420,I7N3G6G,420,1,no description,60.2 -421,I7N3G6G,421,1,no description,60.2 -422,I7N3G6G,422,1,no description,60.2 -423,I7N3G6G,423,1,no description,60.2 -424,I7N3G6G,424,1,no description,60.2 -425,I7N3G6G,425,1,no description,60.2 -426,I7N3G6G,426,1,no description,60.2 -427,I7N3G6G,427,1,no description,60.2 -428,I7N3G6G,428,1,no description,60.2 -429,I7N3G6G,429,1,no description,60.2 -430,I7N3G6G,430,1,no description,60.2 -431,I7N3G6G,431,1,no description,60.2 -432,I7N3G6G,432,1,no description,60.2 -433,I7N3G6G,433,1,no description,60.2 -434,I7N3G6G,434,1,no description,60.2 -435,I7N3G6G,435,1,no description,60.2 -436,I7N3G6G,436,1,no description,60.2 -437,I7N3G6G,437,1,no description,60.2 -438,I7N3G6G,438,1,no description,60.2 -439,I7N3G6G,439,1,no description,60.2 -440,I7N3G6G,440,1,no description,60.2 -441,I7N3G6G,441,1,no description,60.2 -442,I7N3G6G,442,1,no description,60.2 -443,I7N3G6G,443,1,no description,60.2 -444,I7N3G6G,444,1,no description,60.2 -445,I7N3G6G,445,1,no description,60.2 -446,I7N3G6G,446,1,no description,60.2 -447,I7N3G6G,447,1,no description,60.2 -448,I7N3G6G,448,1,no description,60.2 -449,I7N3G6G,449,1,no description,60.2 -450,I7N3G6G,450,1,no description,60.2 -451,I7N3G6G,451,1,no description,60.2 -452,I7N3G6G,452,1,no description,60.2 -453,I7N3G6G,453,1,no description,60.2 -454,I7N3G6G,454,1,no description,60.2 -455,I7N3G6G,455,1,no description,60.2 -456,I7N3G6G,456,1,no description,60.2 -457,I7N3G6G,457,1,no description,60.2 -458,I7N3G6G,458,1,no description,60.2 -459,I7N3G6G,459,1,no description,60.2 -460,I7N3G6G,460,1,no description,60.2 -461,I7N3G6G,461,1,no description,60.2 -462,I7N3G6G,462,1,no description,60.2 -463,I7N3G6G,463,1,no description,60.2 -464,I7N3G6G,464,1,no description,60.2 -465,I7N3G6G,465,1,no description,60.2 -466,I7N3G6G,466,1,no description,60.2 -467,I7N3G6G,467,1,no description,60.2 -468,I7N3G6G,468,1,no description,60.2 -469,I7N3G6G,469,1,no description,60.2 -470,I7N3G6G,470,1,no description,60.2 -471,I7N3G6G,471,1,no description,60.2 -472,I7N3G6G,472,1,no description,60.2 -473,I7N3G6G,473,1,no description,60.2 -474,I7N3G6G,474,1,no description,60.2 -475,I7N3G6G,475,1,no description,60.2 -476,I7N3G6G,476,1,no description,60.2 -477,I7N3G6G,477,1,no description,60.2 -478,I7N3G6G,478,1,no description,60.2 -479,I7N3G6G,479,1,no description,60.2 -480,I7N3G6G,480,1,no description,60.2 -481,I7N3G6G,481,1,no description,60.2 -482,I7N3G6G,482,1,no description,60.2 -483,I7N3G6G,483,1,no description,60.2 -484,I7N3G6G,484,1,no description,60.2 -485,I7N3G6G,485,1,no description,60.2 -486,I7N3G6G,486,1,no description,60.2 -487,I7N3G6G,487,1,no description,60.2 -488,I7N3G6G,488,1,no description,60.2 -489,I7N3G6G,489,1,no description,60.2 -490,I7N3G6G,490,1,no description,60.2 -491,I7N3G6G,491,1,no description,60.2 -492,I7N3G6G,492,1,no description,60.2 -493,I7N3G6G,493,1,no description,60.2 -494,I7N3G6G,494,1,no description,60.2 -495,I7N3G6G,495,1,no description,60.2 -496,I7N3G6G,496,1,no description,60.2 -497,I7N3G6G,497,1,no description,60.2 -498,I7N3G6G,498,1,no description,60.2 -499,I7N3G6G,499,1,no description,60.2 -500,I7N3G6G,500,1,no description,60.2 -501,I7N3G6G,501,1,no description,60.2 -502,I7N3G6G,502,1,no description,60.2 -503,I7N3G6G,503,1,no description,60.2 -504,I7N3G6G,504,1,no description,60.2 -505,I7N3G6G,505,1,no description,60.2 -506,I7N3G6G,506,1,no description,60.2 -507,I7N3G6G,507,1,no description,60.2 -508,I7N3G6G,508,1,no description,60.2 -509,I7N3G6G,509,1,no description,60.2 -510,I7N3G6G,510,1,no description,60.2 -511,I7N3G6G,511,1,no description,60.2 -512,I7N3G6G,512,1,no description,60.2 -513,I7N3G6G,513,1,no description,60.2 -514,I7N3G6G,514,1,no description,60.2 -515,I7N3G6G,515,1,no description,60.2 -516,I7N3G6G,516,1,no description,60.2 -517,I7N3G6G,517,1,no description,60.2 -518,I7N3G6G,518,1,no description,60.2 -519,I7N3G6G,519,1,no description,60.2 -520,I7N3G6G,520,1,no description,60.2 -521,I7N3G6G,521,1,no description,60.2 -522,I7N3G6G,522,1,no description,60.2 -523,I7N3G6G,523,1,no description,60.2 -524,I7N3G6G,524,1,no description,60.2 -525,I7N3G6G,525,1,no description,60.2 -526,I7N3G6G,526,1,no description,60.2 -527,I7N3G6G,527,1,no description,60.2 -528,I7N3G6G,528,1,no description,60.2 -529,I7N3G6G,529,1,no description,60.2 -530,I7N3G6G,530,1,no description,60.2 -531,I7N3G6G,531,1,no description,60.2 -532,I7N3G6G,532,1,no description,60.2 -533,I7N3G6G,533,1,no description,60.2 -534,I7N3G6G,534,1,no description,60.2 -535,I7N3G6G,535,1,no description,60.2 -536,I7N3G6G,536,1,no description,60.2 -537,I7N3G6G,537,1,no description,60.2 -538,I7N3G6G,538,1,no description,60.2 -539,I7N3G6G,539,1,no description,60.2 -540,I7N3G6G,540,1,no description,60.2 -541,I7N3G6G,541,1,no description,60.2 -542,I7N3G6G,542,1,no description,60.2 -543,I7N3G6G,543,1,no description,60.2 -544,I7N3G6G,544,1,no description,60.2 -545,I7N3G6G,545,1,no description,60.2 -546,I7N3G6G,546,1,no description,60.2 -547,I7N3G6G,547,1,no description,60.2 -548,I7N3G6G,548,1,no description,60.2 -549,I7N3G6G,549,1,no description,60.2 -550,I7N3G6G,550,1,no description,60.2 -551,I7N3G6G,551,1,no description,60.2 -552,I7N3G6G,552,1,no description,60.2 -553,I7N3G6G,553,1,no description,60.2 -554,I7N3G6G,554,1,no description,60.2 -555,I7N3G6G,555,1,no description,60.2 -556,I7N3G6G,556,1,no description,60.2 -557,I7N3G6G,557,1,no description,60.2 -558,I7N3G6G,558,1,no description,60.2 -559,I7N3G6G,559,1,no description,60.2 -560,I7N3G6G,560,1,no description,60.2 -561,I7N3G6G,561,1,no description,60.2 -562,I7N3G6G,562,1,no description,60.2 -563,I7N3G6G,563,1,no description,60.2 -564,I7N3G6G,564,1,no description,60.2 -565,I7N3G6G,565,1,no description,60.2 -566,I7N3G6G,566,1,no description,60.2 -567,I7N3G6G,567,1,no description,60.2 -568,I7N3G6G,568,1,no description,60.2 -569,I7N3G6G,569,1,no description,60.2 -570,I7N3G6G,570,1,no description,60.2 -571,I7N3G6G,571,1,no description,60.2 -572,I7N3G6G,572,1,no description,60.2 -573,I7N3G6G,573,1,no description,60.2 -574,I7N3G6G,574,1,no description,60.2 -575,I7N3G6G,575,1,no description,60.2 -576,I7N3G6G,576,1,no description,60.2 -577,I7N3G6G,577,1,no description,60.2 -578,I7N3G6G,578,1,no description,60.2 -579,I7N3G6G,579,1,no description,60.2 -580,I7N3G6G,580,1,no description,60.2 -581,I7N3G6G,581,1,no description,60.2 -582,I7N3G6G,582,1,no description,60.2 -583,I7N3G6G,583,1,no description,60.2 -584,I7N3G6G,584,1,no description,60.2 -585,I7N3G6G,585,1,no description,60.2 -586,I7N3G6G,586,1,no description,60.2 -587,I7N3G6G,587,1,no description,60.2 -588,I7N3G6G,588,1,no description,60.2 -589,I7N3G6G,589,1,no description,60.2 -590,I7N3G6G,590,1,no description,60.2 -591,I7N3G6G,591,1,no description,60.2 -592,I7N3G6G,592,1,no description,60.2 -593,I7N3G6G,593,1,no description,60.2 -594,I7N3G6G,594,1,no description,60.2 -595,I7N3G6G,595,1,no description,60.2 -596,I7N3G6G,596,1,no description,60.2 -597,I7N3G6G,597,1,no description,60.2 -598,I7N3G6G,598,1,no description,60.2 -599,I7N3G6G,599,1,no description,60.2 -600,I7N3G6G,600,1,no description,60.2 -601,I7N3G6G,601,1,no description,60.2 -602,I7N3G6G,602,1,no description,60.2 -603,I7N3G6G,603,1,no description,60.2 -604,I7N3G6G,604,1,no description,60.2 -605,I7N3G6G,605,1,no description,60.2 -606,I7N3G6G,606,1,no description,60.2 -607,I7N3G6G,607,1,no description,60.2 -608,I7N3G6G,608,1,no description,60.2 -609,I7N3G6G,609,1,no description,60.2 -610,I7N3G6G,610,1,no description,60.2 -611,I7N3G6G,611,1,no description,60.2 -612,I7N3G6G,612,1,no description,60.2 -613,I7N3G6G,613,1,no description,60.2 -614,I7N3G6G,614,1,no description,60.2 -615,I7N3G6G,615,1,no description,60.2 -616,I7N3G6G,616,1,no description,60.2 -617,I7N3G6G,617,1,no description,60.2 -618,I7N3G6G,618,1,no description,60.2 -619,I7N3G6G,619,1,no description,60.2 -620,I7N3G6G,620,1,no description,60.2 -621,I7N3G6G,621,1,no description,60.2 -622,I7N3G6G,622,1,no description,60.2 -623,I7N3G6G,623,1,no description,60.2 -624,I7N3G6G,624,1,no description,60.2 -625,I7N3G6G,625,1,no description,60.2 -626,I7N3G6G,626,1,no description,60.2 -627,I7N3G6G,627,1,no description,60.2 -628,I7N3G6G,628,1,no description,60.2 -629,I7N3G6G,629,1,no description,60.2 -630,I7N3G6G,630,1,no description,60.2 -631,I7N3G6G,631,1,no description,60.2 -632,I7N3G6G,632,1,no description,60.2 -633,I7N3G6G,633,1,no description,60.2 -634,I7N3G6G,634,1,no description,60.2 -635,I7N3G6G,635,1,no description,60.2 -636,I7N3G6G,636,1,no description,60.2 -637,I7N3G6G,637,1,no description,60.2 -638,I7N3G6G,638,1,no description,60.2 -639,I7N3G6G,639,1,no description,60.2 -640,I7N3G6G,640,1,no description,60.2 -641,I7N3G6G,641,1,no description,60.2 -642,I7N3G6G,642,1,no description,60.2 -643,I7N3G6G,643,1,no description,60.2 -644,I7N3G6G,644,1,no description,60.2 -645,I7N3G6G,645,1,no description,60.2 -646,I7N3G6G,646,1,no description,60.2 -647,I7N3G6G,647,1,no description,60.2 -648,I7N3G6G,648,1,no description,60.2 -649,I7N3G6G,649,1,no description,60.2 -650,I7N3G6G,650,1,no description,60.2 -651,I7N3G6G,651,1,no description,60.2 -652,I7N3G6G,652,1,no description,60.2 -653,I7N3G6G,653,1,no description,60.2 -654,I7N3G6G,654,1,no description,60.2 -655,I7N3G6G,655,1,no description,60.2 -656,I7N3G6G,656,1,no description,60.2 -657,I7N3G6G,657,1,no description,60.2 -658,I7N3G6G,658,1,no description,60.2 -659,I7N3G6G,659,1,no description,60.2 -660,I7N3G6G,660,1,no description,60.2 -661,I7N3G6G,661,1,no description,60.2 -662,I7N3G6G,662,1,no description,60.2 -663,I7N3G6G,663,1,no description,60.2 -664,I7N3G6G,664,1,no description,60.2 -665,I7N3G6G,665,1,no description,60.2 -666,I7N3G6G,666,1,no description,60.2 -667,I7N3G6G,667,1,no description,60.2 -668,I7N3G6G,668,1,no description,60.2 -669,I7N3G6G,669,1,no description,60.2 -670,I7N3G6G,670,1,no description,60.2 -671,I7N3G6G,671,1,no description,60.2 -672,I7N3G6G,672,1,no description,60.2 -673,I7N3G6G,673,1,no description,60.2 -674,I7N3G6G,674,1,no description,60.2 -675,I7N3G6G,675,1,no description,60.2 -676,I7N3G6G,676,1,no description,60.2 -677,I7N3G6G,677,1,no description,60.2 -678,I7N3G6G,678,1,no description,60.2 -679,I7N3G6G,679,1,no description,60.2 -680,I7N3G6G,680,1,no description,60.2 -681,I7N3G6G,681,1,no description,60.2 -682,I7N3G6G,682,1,no description,60.2 -683,I7N3G6G,683,1,no description,60.2 -684,I7N3G6G,684,1,no description,60.2 -685,I7N3G6G,685,1,no description,60.2 -686,I7N3G6G,686,1,no description,60.2 -687,I7N3G6G,687,1,no description,60.2 -688,I7N3G6G,688,1,no description,60.2 -689,I7N3G6G,689,1,no description,60.2 -690,I7N3G6G,690,1,no description,60.2 -691,I7N3G6G,691,1,no description,60.2 -692,I7N3G6G,692,1,no description,60.2 -693,I7N3G6G,693,1,no description,60.2 -694,I7N3G6G,694,1,no description,60.2 -695,I7N3G6G,695,1,no description,60.2 -696,I7N3G6G,696,1,no description,60.2 -697,I7N3G6G,697,1,no description,60.2 -698,I7N3G6G,698,1,no description,60.2 -699,I7N3G6G,699,1,no description,60.2 -700,I7N3G6G,700,1,no description,60.2 -701,I7N3G6G,701,1,no description,60.2 -702,I7N3G6G,702,1,no description,60.2 -703,I7N3G6G,703,1,no description,60.2 -704,I7N3G6G,704,1,no description,60.2 -705,I7N3G6G,705,1,no description,60.2 -706,I7N3G6G,706,1,no description,60.2 -707,I7N3G6G,707,1,no description,60.2 -708,I7N3G6G,708,1,no description,60.2 -709,I7N3G6G,709,1,no description,60.2 -710,I7N3G6G,710,1,no description,60.2 -711,I7N3G6G,711,1,no description,60.2 -712,I7N3G6G,712,1,no description,60.2 -713,I7N3G6G,713,1,no description,60.2 -714,I7N3G6G,714,1,no description,60.2 -715,I7N3G6G,715,1,no description,60.2 -716,I7N3G6G,716,1,no description,60.2 -717,I7N3G6G,717,1,no description,60.2 -718,I7N3G6G,718,1,no description,60.2 -719,I7N3G6G,719,1,no description,60.2 -720,I7N3G6G,720,1,no description,60.2 -721,I7N3G6G,721,1,no description,60.2 -722,I7N3G6G,722,1,no description,60.2 -723,I7N3G6G,723,1,no description,60.2 -724,I7N3G6G,724,1,no description,60.2 -725,I7N3G6G,725,1,no description,60.2 -726,I7N3G6G,726,1,no description,60.2 -727,I7N3G6G,727,1,no description,60.2 -728,I7N3G6G,728,1,no description,60.2 -729,I7N3G6G,729,1,no description,60.2 -730,I7N3G6G,730,1,no description,60.2 -731,I7N3G6G,731,1,no description,60.2 -732,I7N3G6G,732,1,no description,60.2 -733,I7N3G6G,733,1,no description,60.2 -734,I7N3G6G,734,1,no description,60.2 -735,I7N3G6G,735,1,no description,60.2 -736,I7N3G6G,736,1,no description,60.2 -737,I7N3G6G,737,1,no description,60.2 -738,I7N3G6G,738,1,no description,60.2 -739,I7N3G6G,739,1,no description,60.2 -740,I7N3G6G,740,1,no description,60.2 -741,I7N3G6G,741,1,no description,60.2 -742,I7N3G6G,742,1,no description,60.2 -743,I7N3G6G,743,1,no description,60.2 -744,I7N3G6G,744,1,no description,60.2 -745,I7N3G6G,745,1,no description,60.2 -746,I7N3G6G,746,1,no description,60.2 -747,I7N3G6G,747,1,no description,60.2 -748,I7N3G6G,748,1,no description,60.2 -749,I7N3G6G,749,1,no description,60.2 -750,I7N3G6G,750,1,no description,60.2 -751,I7N3G6G,751,1,no description,60.2 -752,I7N3G6G,752,1,no description,60.2 -753,I7N3G6G,753,1,no description,60.2 -754,I7N3G6G,754,1,no description,60.2 -755,I7N3G6G,755,1,no description,60.2 -756,I7N3G6G,756,1,no description,60.2 -757,I7N3G6G,757,1,no description,60.2 -758,I7N3G6G,758,1,no description,60.2 -759,I7N3G6G,759,1,no description,60.2 -760,I7N3G6G,760,1,no description,60.2 -761,I7N3G6G,761,1,no description,60.2 -762,I7N3G6G,762,1,no description,60.2 -763,I7N3G6G,763,1,no description,60.2 -764,I7N3G6G,764,1,no description,60.2 -765,I7N3G6G,765,1,no description,60.2 -766,I7N3G6G,766,1,no description,60.2 -767,I7N3G6G,767,1,no description,60.2 -768,I7N3G6G,768,1,no description,60.2 -769,I7N3G6G,769,1,no description,60.2 -770,I7N3G6G,770,1,no description,60.2 -771,I7N3G6G,771,1,no description,60.2 -772,I7N3G6G,772,1,no description,60.2 -773,I7N3G6G,773,1,no description,60.2 -774,I7N3G6G,774,1,no description,60.2 -775,I7N3G6G,775,1,no description,60.2 -776,I7N3G6G,776,1,no description,60.2 -777,I7N3G6G,777,1,no description,60.2 -778,I7N3G6G,778,1,no description,60.2 -779,I7N3G6G,779,1,no description,60.2 -780,I7N3G6G,780,1,no description,60.2 -781,I7N3G6G,781,1,no description,60.2 -782,I7N3G6G,782,1,no description,60.2 -783,I7N3G6G,783,1,no description,60.2 -784,I7N3G6G,784,1,no description,60.2 -785,I7N3G6G,785,1,no description,60.2 -786,I7N3G6G,786,1,no description,60.2 -787,I7N3G6G,787,1,no description,60.2 -788,I7N3G6G,788,1,no description,60.2 -789,I7N3G6G,789,1,no description,60.2 -790,I7N3G6G,790,1,no description,60.2 -791,I7N3G6G,791,1,no description,60.2 -792,I7N3G6G,792,1,no description,60.2 -793,I7N3G6G,793,1,no description,60.2 -794,I7N3G6G,794,1,no description,60.2 -795,I7N3G6G,795,1,no description,60.2 -796,I7N3G6G,796,1,no description,60.2 -797,I7N3G6G,797,1,no description,60.2 -798,I7N3G6G,798,1,no description,60.2 -799,I7N3G6G,799,1,no description,60.2 -800,I7N3G6G,800,1,no description,60.2 -801,I7N3G6G,801,1,no description,60.2 -802,I7N3G6G,802,1,no description,60.2 -803,I7N3G6G,803,1,no description,60.2 -804,I7N3G6G,804,1,no description,60.2 -805,I7N3G6G,805,1,no description,60.2 -806,I7N3G6G,806,1,no description,60.2 -807,I7N3G6G,807,1,no description,60.2 -808,I7N3G6G,808,1,no description,60.2 -809,I7N3G6G,809,1,no description,60.2 -810,I7N3G6G,810,1,no description,60.2 -811,I7N3G6G,811,1,no description,60.2 -812,I7N3G6G,812,1,no description,60.2 -813,I7N3G6G,813,1,no description,60.2 -814,I7N3G6G,814,1,no description,60.2 -815,I7N3G6G,815,1,no description,60.2 -816,I7N3G6G,816,1,no description,60.2 -817,I7N3G6G,817,1,no description,60.2 -818,I7N3G6G,818,1,no description,60.2 -819,I7N3G6G,819,1,no description,60.2 -820,I7N3G6G,820,1,no description,60.2 -821,I7N3G6G,821,1,no description,60.2 -822,I7N3G6G,822,1,no description,60.2 -823,I7N3G6G,823,1,no description,60.2 -824,I7N3G6G,824,1,no description,60.2 -825,I7N3G6G,825,1,no description,60.2 -826,I7N3G6G,826,1,no description,60.2 -827,I7N3G6G,827,1,no description,60.2 -828,I7N3G6G,828,1,no description,60.2 -829,I7N3G6G,829,1,no description,60.2 -830,I7N3G6G,830,1,no description,60.2 -831,I7N3G6G,831,1,no description,60.2 -832,I7N3G6G,832,1,no description,60.2 -833,I7N3G6G,833,1,no description,60.2 -834,I7N3G6G,834,1,no description,60.2 -835,I7N3G6G,835,1,no description,60.2 -836,I7N3G6G,836,1,no description,60.2 -837,I7N3G6G,837,1,no description,60.2 -838,I7N3G6G,838,1,no description,60.2 -839,I7N3G6G,839,1,no description,60.2 -840,I7N3G6G,840,1,no description,60.2 -841,I7N3G6G,841,1,no description,60.2 -842,I7N3G6G,842,1,no description,60.2 -843,I7N3G6G,843,1,no description,60.2 -844,I7N3G6G,844,1,no description,60.2 -845,I7N3G6G,845,1,no description,60.2 -846,I7N3G6G,846,1,no description,60.2 -847,I7N3G6G,847,1,no description,60.2 -848,I7N3G6G,848,1,no description,60.2 -849,I7N3G6G,849,1,no description,60.2 -850,I7N3G6G,850,1,no description,60.2 -851,I7N3G6G,851,1,no description,60.2 -852,I7N3G6G,852,1,no description,60.2 -853,I7N3G6G,853,1,no description,60.2 -854,I7N3G6G,854,1,no description,60.2 -855,I7N3G6G,855,1,no description,60.2 -856,I7N3G6G,856,1,no description,60.2 -857,I7N3G6G,857,1,no description,60.2 -858,I7N3G6G,858,1,no description,60.2 -859,I7N3G6G,859,1,no description,60.2 -860,I7N3G6G,860,1,no description,60.2 -861,I7N3G6G,861,1,no description,60.2 -862,I7N3G6G,862,1,no description,60.2 -863,I7N3G6G,863,1,no description,60.2 -864,I7N3G6G,864,1,no description,60.2 -865,I7N3G6G,865,1,no description,60.2 -866,I7N3G6G,866,1,no description,60.2 -867,I7N3G6G,867,1,no description,60.2 -868,I7N3G6G,868,1,no description,60.2 -869,I7N3G6G,869,1,no description,60.2 -870,I7N3G6G,870,1,no description,60.2 -871,I7N3G6G,871,1,no description,60.2 -872,I7N3G6G,872,1,no description,60.2 -873,I7N3G6G,873,1,no description,60.2 -874,I7N3G6G,874,1,no description,60.2 -875,I7N3G6G,875,1,no description,60.2 -876,I7N3G6G,876,1,no description,60.2 -877,I7N3G6G,877,1,no description,60.2 -878,I7N3G6G,878,1,no description,60.2 -879,I7N3G6G,879,1,no description,60.2 -880,I7N3G6G,880,1,no description,60.2 -881,I7N3G6G,881,1,no description,60.2 -882,I7N3G6G,882,1,no description,60.2 -883,I7N3G6G,883,1,no description,60.2 -884,I7N3G6G,884,1,no description,60.2 -885,I7N3G6G,885,1,no description,60.2 -886,I7N3G6G,886,1,no description,60.2 -887,I7N3G6G,887,1,no description,60.2 -888,I7N3G6G,888,1,no description,60.2 -889,I7N3G6G,889,1,no description,60.2 -890,I7N3G6G,890,1,no description,60.2 -891,I7N3G6G,891,1,no description,60.2 -892,I7N3G6G,892,1,no description,60.2 -893,I7N3G6G,893,1,no description,60.2 -894,I7N3G6G,894,1,no description,60.2 -895,I7N3G6G,895,1,no description,60.2 -896,I7N3G6G,896,1,no description,60.2 -897,I7N3G6G,897,1,no description,60.2 -898,I7N3G6G,898,1,no description,60.2 -899,I7N3G6G,899,1,no description,60.2 -900,I7N3G6G,900,1,no description,60.2 -901,I7N3G6G,901,1,no description,60.2 -902,I7N3G6G,902,1,no description,60.2 -903,I7N3G6G,903,1,no description,60.2 -904,I7N3G6G,904,1,no description,60.2 -905,I7N3G6G,905,1,no description,60.2 -906,I7N3G6G,906,1,no description,60.2 -907,I7N3G6G,907,1,no description,60.2 -908,I7N3G6G,908,1,no description,60.2 -909,I7N3G6G,909,1,no description,60.2 -910,I7N3G6G,910,1,no description,60.2 -911,I7N3G6G,911,1,no description,60.2 -912,I7N3G6G,912,1,no description,60.2 -913,I7N3G6G,913,1,no description,60.2 -914,I7N3G6G,914,1,no description,60.2 -915,I7N3G6G,915,1,no description,60.2 -916,I7N3G6G,916,1,no description,60.2 -917,I7N3G6G,917,1,no description,60.2 -918,I7N3G6G,918,1,no description,60.2 -919,I7N3G6G,919,1,no description,60.2 -920,I7N3G6G,920,1,no description,60.2 -921,I7N3G6G,921,1,no description,60.2 -922,I7N3G6G,922,1,no description,60.2 -923,I7N3G6G,923,1,no description,60.2 -924,I7N3G6G,924,1,no description,60.2 -925,I7N3G6G,925,1,no description,60.2 -926,I7N3G6G,926,1,no description,60.2 -927,I7N3G6G,927,1,no description,60.2 -928,I7N3G6G,928,1,no description,60.2 -929,I7N3G6G,929,1,no description,60.2 -930,I7N3G6G,930,1,no description,60.2 -931,I7N3G6G,931,1,no description,60.2 -932,I7N3G6G,932,1,no description,60.2 -933,I7N3G6G,933,1,no description,60.2 -934,I7N3G6G,934,1,no description,60.2 -935,I7N3G6G,935,1,no description,60.2 -936,I7N3G6G,936,1,no description,60.2 -937,I7N3G6G,937,1,no description,60.2 -938,I7N3G6G,938,1,no description,60.2 -939,I7N3G6G,939,1,no description,60.2 -940,I7N3G6G,940,1,no description,60.2 -941,I7N3G6G,941,1,no description,60.2 -942,I7N3G6G,942,1,no description,60.2 -943,I7N3G6G,943,1,no description,60.2 -944,I7N3G6G,944,1,no description,60.2 -945,I7N3G6G,945,1,no description,60.2 -946,I7N3G6G,946,1,no description,60.2 -947,I7N3G6G,947,1,no description,60.2 -948,I7N3G6G,948,1,no description,60.2 -949,I7N3G6G,949,1,no description,60.2 -950,I7N3G6G,950,1,no description,60.2 -951,I7N3G6G,951,1,no description,60.2 -952,I7N3G6G,952,1,no description,60.2 -953,I7N3G6G,953,1,no description,60.2 -954,I7N3G6G,954,1,no description,60.2 -955,I7N3G6G,955,1,no description,60.2 -956,I7N3G6G,956,1,no description,60.2 -957,I7N3G6G,957,1,no description,60.2 -958,I7N3G6G,958,1,no description,60.2 -959,I7N3G6G,959,1,no description,60.2 -960,I7N3G6G,960,1,no description,60.2 -961,I7N3G6G,961,1,no description,60.2 -962,I7N3G6G,962,1,no description,60.2 -963,I7N3G6G,963,1,no description,60.2 -964,I7N3G6G,964,1,no description,60.2 -965,I7N3G6G,965,1,no description,60.2 -966,I7N3G6G,966,1,no description,60.2 -967,I7N3G6G,967,1,no description,60.2 -968,I7N3G6G,968,1,no description,60.2 -969,I7N3G6G,969,1,no description,60.2 -970,I7N3G6G,970,1,no description,60.2 -971,I7N3G6G,971,1,no description,60.2 -972,I7N3G6G,972,1,no description,60.2 -973,I7N3G6G,973,1,no description,60.2 -974,I7N3G6G,974,1,no description,60.2 -975,I7N3G6G,975,1,no description,60.2 -976,I7N3G6G,976,1,no description,60.2 -977,I7N3G6G,977,1,no description,60.2 -978,I7N3G6G,978,1,no description,60.2 -979,I7N3G6G,979,1,no description,60.2 -980,I7N3G6G,980,1,no description,60.2 -981,I7N3G6G,981,1,no description,60.2 -982,I7N3G6G,982,1,no description,60.2 -983,I7N3G6G,983,1,no description,60.2 -984,I7N3G6G,984,1,no description,60.2 -985,I7N3G6G,985,1,no description,60.2 -986,I7N3G6G,986,1,no description,60.2 -987,I7N3G6G,987,1,no description,60.2 -988,I7N3G6G,988,1,no description,60.2 -989,I7N3G6G,989,1,no description,60.2 -990,I7N3G6G,990,1,no description,60.2 -991,I7N3G6G,991,1,no description,60.2 -992,I7N3G6G,992,1,no description,60.2 -993,I7N3G6G,993,1,no description,60.2 -994,I7N3G6G,994,1,no description,60.2 -995,I7N3G6G,995,1,no description,60.2 -996,I7N3G6G,996,1,no description,60.2 -997,I7N3G6G,997,1,no description,60.2 -998,I7N3G6G,998,1,no description,60.2 -999,I7N3G6G,999,1,no description,60.2 -1000,I7N3G6G,1000,1,no description,60.2 -1001,I7N3G6G,1001,1,no description,60.2 -1002,I7N3G6G,1002,1,no description,60.2 -1003,I7N3G6G,1003,1,no description,60.2 -1004,I7N3G6G,1004,1,no description,60.2 -1005,I7N3G6G,1005,1,no description,60.2 -1006,I7N3G6G,1006,1,no description,60.2 -1007,I7N3G6G,1007,1,no description,60.2 -1008,I7N3G6G,1008,1,no description,60.2 -1009,I7N3G6G,1009,1,no description,60.2 -1010,I7N3G6G,1010,1,no description,60.2 -1011,I7N3G6G,1011,1,no description,60.2 -1012,I7N3G6G,1012,1,no description,60.2 -1013,I7N3G6G,1013,1,no description,60.2 -1014,I7N3G6G,1014,1,no description,60.2 -1015,I7N3G6G,1015,1,no description,60.2 -1016,I7N3G6G,1016,1,no description,60.2 -1017,I7N3G6G,1017,1,no description,60.2 -1018,I7N3G6G,1018,1,no description,60.2 -1019,I7N3G6G,1019,1,no description,60.2 -1020,I7N3G6G,1020,1,no description,60.2 -1021,I7N3G6G,1021,1,no description,60.2 -1022,I7N3G6G,1022,1,no description,60.2 -1023,I7N3G6G,1023,1,no description,60.2 -1024,I7N3G6G,1024,1,no description,60.2 -1025,I7N3G6G,1025,1,no description,60.2 -1026,I7N3G6G,1026,1,no description,60.2 -1027,I7N3G6G,1027,1,no description,60.2 -1028,I7N3G6G,1028,1,no description,60.2 -1029,I7N3G6G,1029,1,no description,60.2 -1030,I7N3G6G,1030,1,no description,60.2 -1031,I7N3G6G,1031,1,no description,60.2 -1032,I7N3G6G,1032,1,no description,60.2 -1033,I7N3G6G,1033,1,no description,60.2 -1034,I7N3G6G,1034,1,no description,60.2 -1035,I7N3G6G,1035,1,no description,60.2 -1036,I7N3G6G,1036,1,no description,60.2 -1037,I7N3G6G,1037,1,no description,60.2 -1038,I7N3G6G,1038,1,no description,60.2 -1039,I7N3G6G,1039,1,no description,60.2 -1040,I7N3G6G,1040,1,no description,60.2 -1041,I7N3G6G,1041,1,no description,60.2 -1042,I7N3G6G,1042,1,no description,60.2 -1043,I7N3G6G,1043,1,no description,60.2 -1044,I7N3G6G,1044,1,no description,60.2 -1045,I7N3G6G,1045,1,no description,60.2 -1046,I7N3G6G,1046,1,no description,60.2 -1047,I7N3G6G,1047,1,no description,60.2 -1048,I7N3G6G,1048,1,no description,60.2 -1049,I7N3G6G,1049,1,no description,60.2 -1050,I7N3G6G,1050,1,no description,60.2 -1051,I7N3G6G,1051,1,no description,60.2 -1052,I7N3G6G,1052,1,no description,60.2 -1053,I7N3G6G,1053,1,no description,60.2 -1054,I7N3G6G,1054,1,no description,60.2 -1055,I7N3G6G,1055,1,no description,60.2 -1056,I7N3G6G,1056,1,no description,60.2 -1057,I7N3G6G,1057,1,no description,60.2 -1058,I7N3G6G,1058,1,no description,60.2 -1059,I7N3G6G,1059,1,no description,60.2 -1060,I7N3G6G,1060,1,no description,60.2 -1061,I7N3G6G,1061,1,no description,60.2 -1062,I7N3G6G,1062,1,no description,60.2 -1063,I7N3G6G,1063,1,no description,60.2 -1064,I7N3G6G,1064,1,no description,60.2 -1065,I7N3G6G,1065,1,no description,60.2 -1066,I7N3G6G,1066,1,no description,60.2 -1067,I7N3G6G,1067,1,no description,60.2 -1068,I7N3G6G,1068,1,no description,60.2 -1069,I7N3G6G,1069,1,no description,60.2 -1070,I7N3G6G,1070,1,no description,60.2 -1071,I7N3G6G,1071,1,no description,60.2 -1072,I7N3G6G,1072,1,no description,60.2 -1073,I7N3G6G,1073,1,no description,60.2 -1074,I7N3G6G,1074,1,no description,60.2 -1075,I7N3G6G,1075,1,no description,60.2 -1076,I7N3G6G,1076,1,no description,60.2 -1077,I7N3G6G,1077,1,no description,60.2 -1078,I7N3G6G,1078,1,no description,60.2 -1079,I7N3G6G,1079,1,no description,60.2 -1080,I7N3G6G,1080,1,no description,60.2 -1081,I7N3G6G,1081,1,no description,60.2 -1082,I7N3G6G,1082,1,no description,60.2 -1083,I7N3G6G,1083,1,no description,60.2 -1084,I7N3G6G,1084,1,no description,60.2 -1085,I7N3G6G,1085,1,no description,60.2 -1086,I7N3G6G,1086,1,no description,60.2 -1087,I7N3G6G,1087,1,no description,60.2 -1088,I7N3G6G,1088,1,no description,60.2 -1089,I7N3G6G,1089,1,no description,60.2 -1090,I7N3G6G,1090,1,no description,60.2 -1091,I7N3G6G,1091,1,no description,60.2 -1092,I7N3G6G,1092,1,no description,60.2 -1093,I7N3G6G,1093,1,no description,60.2 -1094,I7N3G6G,1094,1,no description,60.2 -1095,I7N3G6G,1095,1,no description,60.2 -1096,I7N3G6G,1096,1,no description,60.2 -1097,I7N3G6G,1097,1,no description,60.2 -1098,I7N3G6G,1098,1,no description,60.2 -1099,I7N3G6G,1099,1,no description,60.2 -1100,I7N3G6G,1100,1,no description,60.2 -1101,I7N3G6G,1101,1,no description,60.2 -1102,I7N3G6G,1102,1,no description,60.2 -1103,I7N3G6G,1103,1,no description,60.2 -1104,I7N3G6G,1104,1,no description,60.2 -1105,I7N3G6G,1105,1,no description,60.2 -1106,I7N3G6G,1106,1,no description,60.2 -1107,I7N3G6G,1107,1,no description,60.2 -1108,I7N3G6G,1108,1,no description,60.2 -1109,I7N3G6G,1109,1,no description,60.2 -1110,I7N3G6G,1110,1,no description,60.2 -1111,I7N3G6G,1111,1,no description,60.2 -1112,I7N3G6G,1112,1,no description,60.2 -1113,I7N3G6G,1113,1,no description,60.2 -1114,I7N3G6G,1114,1,no description,60.2 -1115,I7N3G6G,1115,1,no description,60.2 -1116,I7N3G6G,1116,1,no description,60.2 -1117,I7N3G6G,1117,1,no description,60.2 -1118,I7N3G6G,1118,1,no description,60.2 -1119,I7N3G6G,1119,1,no description,60.2 -1120,I7N3G6G,1120,1,no description,60.2 -1121,I7N3G6G,1121,1,no description,60.2 -1122,I7N3G6G,1122,1,no description,60.2 -1123,I7N3G6G,1123,1,no description,60.2 -1124,I7N3G6G,1124,1,no description,60.2 -1125,I7N3G6G,1125,1,no description,60.2 -1126,I7N3G6G,1126,1,no description,60.2 -1127,I7N3G6G,1127,1,no description,60.2 -1128,I7N3G6G,1128,1,no description,60.2 -1129,I7N3G6G,1129,1,no description,60.2 -1130,I7N3G6G,1130,1,no description,60.2 -1131,I7N3G6G,1131,1,no description,60.2 -1132,I7N3G6G,1132,1,no description,60.2 -1133,I7N3G6G,1133,1,no description,60.2 -1134,I7N3G6G,1134,1,no description,60.2 -1135,I7N3G6G,1135,1,no description,60.2 -1136,I7N3G6G,1136,1,no description,60.2 -1137,I7N3G6G,1137,1,no description,60.2 -1138,I7N3G6G,1138,1,no description,60.2 -1139,I7N3G6G,1139,1,no description,60.2 -1140,I7N3G6G,1140,1,no description,60.2 -1141,I7N3G6G,1141,1,no description,60.2 -1142,I7N3G6G,1142,1,no description,60.2 -1143,I7N3G6G,1143,1,no description,60.2 -1144,I7N3G6G,1144,1,no description,60.2 -1145,I7N3G6G,1145,1,no description,60.2 -1146,I7N3G6G,1146,1,no description,60.2 -1147,I7N3G6G,1147,1,no description,60.2 -1148,I7N3G6G,1148,1,no description,60.2 -1149,I7N3G6G,1149,1,no description,60.2 -1150,I7N3G6G,1150,1,no description,60.2 -1151,I7N3G6G,1151,1,no description,60.2 -1152,I7N3G6G,1152,1,no description,60.2 -1153,I7N3G6G,1153,1,no description,60.2 -1154,I7N3G6G,1154,1,no description,60.2 -1155,I7N3G6G,1155,1,no description,60.2 -1156,I7N3G6G,1156,1,no description,60.2 -1157,I7N3G6G,1157,1,no description,60.2 -1158,I7N3G6G,1158,1,no description,60.2 -1159,I7N3G6G,1159,1,no description,60.2 -1160,I7N3G6G,1160,1,no description,60.2 -1161,I7N3G6G,1161,1,no description,60.2 -1162,I7N3G6G,1162,1,no description,60.2 -1163,I7N3G6G,1163,1,no description,60.2 -1164,I7N3G6G,1164,1,no description,60.2 -1165,I7N3G6G,1165,1,no description,60.2 -1166,I7N3G6G,1166,1,no description,60.2 -1167,I7N3G6G,1167,1,no description,60.2 -1168,I7N3G6G,1168,1,no description,60.2 -1169,I7N3G6G,1169,1,no description,60.2 -1170,I7N3G6G,1170,1,no description,60.2 -1171,I7N3G6G,1171,1,no description,60.2 -1172,I7N3G6G,1172,1,no description,60.2 -1173,I7N3G6G,1173,1,no description,60.2 -1174,I7N3G6G,1174,1,no description,60.2 -1175,I7N3G6G,1175,1,no description,60.2 -1176,I7N3G6G,1176,1,no description,60.2 -1177,I7N3G6G,1177,1,no description,60.2 -1178,I7N3G6G,1178,1,no description,60.2 -1179,I7N3G6G,1179,1,no description,60.2 -1180,I7N3G6G,1180,1,no description,60.2 -1181,I7N3G6G,1181,1,no description,60.2 -1182,I7N3G6G,1182,1,no description,60.2 -1183,I7N3G6G,1183,1,no description,60.2 -1184,I7N3G6G,1184,1,no description,60.2 -1185,I7N3G6G,1185,1,no description,60.2 -1186,I7N3G6G,1186,1,no description,60.2 -1187,I7N3G6G,1187,1,no description,60.2 -1188,I7N3G6G,1188,1,no description,60.2 -1189,I7N3G6G,1189,1,no description,60.2 -1190,I7N3G6G,1190,1,no description,60.2 -1191,I7N3G6G,1191,1,no description,60.2 -1192,I7N3G6G,1192,1,no description,60.2 -1193,I7N3G6G,1193,1,no description,60.2 -1194,I7N3G6G,1194,1,no description,60.2 -1195,I7N3G6G,1195,1,no description,60.2 -1196,I7N3G6G,1196,1,no description,60.2 -1197,I7N3G6G,1197,1,no description,60.2 -1198,I7N3G6G,1198,1,no description,60.2 -1199,I7N3G6G,1199,1,no description,60.2 -1200,I7N3G6G,1200,1,no description,60.2 -1201,I7N3G6G,1201,1,no description,60.2 -1202,I7N3G6G,1202,1,no description,60.2 -1203,I7N3G6G,1203,1,no description,60.2 -1204,I7N3G6G,1204,1,no description,60.2 -1205,I7N3G6G,1205,1,no description,60.2 -1206,I7N3G6G,1206,1,no description,60.2 -1207,I7N3G6G,1207,1,no description,60.2 -1208,I7N3G6G,1208,1,no description,60.2 -1209,I7N3G6G,1209,1,no description,60.2 -1210,I7N3G6G,1210,1,no description,60.2 -1211,I7N3G6G,1211,1,no description,60.2 -1212,I7N3G6G,1212,1,no description,60.2 -1213,I7N3G6G,1213,1,no description,60.2 -1214,I7N3G6G,1214,1,no description,60.2 -1215,I7N3G6G,1215,1,no description,60.2 -1216,I7N3G6G,1216,1,no description,60.2 -1217,I7N3G6G,1217,1,no description,60.2 -1218,I7N3G6G,1218,1,no description,60.2 -1219,I7N3G6G,1219,1,no description,60.2 -1220,I7N3G6G,1220,1,no description,60.2 -1221,I7N3G6G,1221,1,no description,60.2 -1222,I7N3G6G,1222,1,no description,60.2 -1223,I7N3G6G,1223,1,no description,60.2 -1224,I7N3G6G,1224,1,no description,60.2 -1225,I7N3G6G,1225,1,no description,60.2 -1226,I7N3G6G,1226,1,no description,60.2 -1227,I7N3G6G,1227,1,no description,60.2 -1228,I7N3G6G,1228,1,no description,60.2 -1229,I7N3G6G,1229,1,no description,60.2 -1230,I7N3G6G,1230,1,no description,60.2 -1231,I7N3G6G,1231,1,no description,60.2 -1232,I7N3G6G,1232,1,no description,60.2 -1233,I7N3G6G,1233,1,no description,60.2 -1234,I7N3G6G,1234,1,no description,60.2 -1235,I7N3G6G,1235,1,no description,60.2 -1236,I7N3G6G,1236,1,no description,60.2 -1237,I7N3G6G,1237,1,no description,60.2 -1238,I7N3G6G,1238,1,no description,60.2 -1239,I7N3G6G,1239,1,no description,60.2 -1240,I7N3G6G,1240,1,no description,60.2 -1241,I7N3G6G,1241,1,no description,60.2 -1242,I7N3G6G,1242,1,no description,60.2 -1243,I7N3G6G,1243,1,no description,60.2 -1244,I7N3G6G,1244,1,no description,60.2 -1245,I7N3G6G,1245,1,no description,60.2 -1246,I7N3G6G,1246,1,no description,60.2 -1247,I7N3G6G,1247,1,no description,60.2 -1248,I7N3G6G,1248,1,no description,60.2 -1249,I7N3G6G,1249,1,no description,60.2 -1250,I7N3G6G,1250,1,no description,60.2 -1251,I7N3G6G,1251,1,no description,60.2 -1252,I7N3G6G,1252,1,no description,60.2 -1253,I7N3G6G,1253,1,no description,60.2 -1254,I7N3G6G,1254,1,no description,60.2 -1255,I7N3G6G,1255,1,no description,60.2 -1256,I7N3G6G,1256,1,no description,60.2 -1257,I7N3G6G,1257,1,no description,60.2 -1258,I7N3G6G,1258,1,no description,60.2 -1259,I7N3G6G,1259,1,no description,60.2 -1260,I7N3G6G,1260,1,no description,60.2 -1261,I7N3G6G,1261,1,no description,60.2 -1262,I7N3G6G,1262,1,no description,60.2 -1263,I7N3G6G,1263,1,no description,60.2 -1264,I7N3G6G,1264,1,no description,60.2 -1265,I7N3G6G,1265,1,no description,60.2 -1266,I7N3G6G,1266,1,no description,60.2 -1267,I7N3G6G,1267,1,no description,60.2 -1268,I7N3G6G,1268,1,no description,60.2 -1269,I7N3G6G,1269,1,no description,60.2 -1270,I7N3G6G,1270,1,no description,60.2 -1271,I7N3G6G,1271,1,no description,60.2 -1272,I7N3G6G,1272,1,no description,60.2 -1273,I7N3G6G,1273,1,no description,60.2 -1274,I7N3G6G,1274,1,no description,60.2 -1275,I7N3G6G,1275,1,no description,60.2 -1276,I7N3G6G,1276,1,no description,60.2 -1277,I7N3G6G,1277,1,no description,60.2 -1278,I7N3G6G,1278,1,no description,60.2 -1279,I7N3G6G,1279,1,no description,60.2 -1280,I7N3G6G,1280,1,no description,60.2 -1281,I7N3G6G,1281,1,no description,60.2 -1282,I7N3G6G,1282,1,no description,60.2 -1283,I7N3G6G,1283,1,no description,60.2 -1284,I7N3G6G,1284,1,no description,60.2 -1285,I7N3G6G,1285,1,no description,60.2 -1286,I7N3G6G,1286,1,no description,60.2 -1287,I7N3G6G,1287,1,no description,60.2 -1288,I7N3G6G,1288,1,no description,60.2 -1289,I7N3G6G,1289,1,no description,60.2 -1290,I7N3G6G,1290,1,no description,60.2 -1291,I7N3G6G,1291,1,no description,60.2 -1292,I7N3G6G,1292,1,no description,60.2 -1293,I7N3G6G,1293,1,no description,60.2 -1294,I7N3G6G,1294,1,no description,60.2 -1295,I7N3G6G,1295,1,no description,60.2 -1296,I7N3G6G,1296,1,no description,60.2 -1297,I7N3G6G,1297,1,no description,60.2 -1298,I7N3G6G,1298,1,no description,60.2 -1299,I7N3G6G,1299,1,no description,60.2 -1300,I7N3G6G,1300,1,no description,60.2 -1301,I7N3G6G,1301,1,no description,60.2 -1302,I7N3G6G,1302,1,no description,60.2 -1303,I7N3G6G,1303,1,no description,60.2 -1304,I7N3G6G,1304,1,no description,60.2 -1305,I7N3G6G,1305,1,no description,60.2 -1306,I7N3G6G,1306,1,no description,60.2 -1307,I7N3G6G,1307,1,no description,60.2 -1308,I7N3G6G,1308,1,no description,60.2 -1309,I7N3G6G,1309,1,no description,60.2 -1310,I7N3G6G,1310,1,no description,60.2 -1311,I7N3G6G,1311,1,no description,60.2 -1312,I7N3G6G,1312,1,no description,60.2 -1313,I7N3G6G,1313,1,no description,60.2 -1314,I7N3G6G,1314,1,no description,60.2 -1315,I7N3G6G,1315,1,no description,60.2 -1316,I7N3G6G,1316,1,no description,60.2 -1317,I7N3G6G,1317,1,no description,60.2 -1318,I7N3G6G,1318,1,no description,60.2 -1319,I7N3G6G,1319,1,no description,60.2 -1320,I7N3G6G,1320,1,no description,60.2 -1321,I7N3G6G,1321,1,no description,60.2 -1322,I7N3G6G,1322,1,no description,60.2 -1323,I7N3G6G,1323,1,no description,60.2 -1324,I7N3G6G,1324,1,no description,60.2 -1325,I7N3G6G,1325,1,no description,60.2 -1326,I7N3G6G,1326,1,no description,60.2 -1327,I7N3G6G,1327,1,no description,60.2 -1328,I7N3G6G,1328,1,no description,60.2 -1329,I7N3G6G,1329,1,no description,60.2 -1330,I7N3G6G,1330,1,no description,60.2 -1331,I7N3G6G,1331,1,no description,60.2 -1332,I7N3G6G,1332,1,no description,60.2 -1333,I7N3G6G,1333,1,no description,60.2 -1334,I7N3G6G,1334,1,no description,60.2 -1335,I7N3G6G,1335,1,no description,60.2 -1336,I7N3G6G,1336,1,no description,60.2 -1337,I7N3G6G,1337,1,no description,60.2 -1338,I7N3G6G,1338,1,no description,60.2 -1339,I7N3G6G,1339,1,no description,60.2 -1340,I7N3G6G,1340,1,no description,60.2 -1341,I7N3G6G,1341,1,no description,60.2 -1342,I7N3G6G,1342,1,no description,60.2 -1343,I7N3G6G,1343,1,no description,60.2 -1344,I7N3G6G,1344,1,no description,60.2 -1345,I7N3G6G,1345,1,no description,60.2 -1346,I7N3G6G,1346,1,no description,60.2 -1347,I7N3G6G,1347,1,no description,60.2 -1348,I7N3G6G,1348,1,no description,60.2 -1349,I7N3G6G,1349,1,no description,60.2 -1350,I7N3G6G,1350,1,no description,60.2 -1351,I7N3G6G,1351,1,no description,60.2 -1352,I7N3G6G,1352,1,no description,60.2 -1353,I7N3G6G,1353,1,no description,60.2 -1354,I7N3G6G,1354,1,no description,60.2 -1355,I7N3G6G,1355,1,no description,60.2 -1356,I7N3G6G,1356,1,no description,60.2 -1357,I7N3G6G,1357,1,no description,60.2 -1358,I7N3G6G,1358,1,no description,60.2 -1359,I7N3G6G,1359,1,no description,60.2 -1360,I7N3G6G,1360,1,no description,60.2 -1361,I7N3G6G,1361,1,no description,60.2 -1362,I7N3G6G,1362,1,no description,60.2 -1363,I7N3G6G,1363,1,no description,60.2 -1364,I7N3G6G,1364,1,no description,60.2 -1365,I7N3G6G,1365,1,no description,60.2 -1366,I7N3G6G,1366,1,no description,60.2 -1367,I7N3G6G,1367,1,no description,60.2 -1368,I7N3G6G,1368,1,no description,60.2 -1369,I7N3G6G,1369,1,no description,60.2 -1370,I7N3G6G,1370,1,no description,60.2 -1371,I7N3G6G,1371,1,no description,60.2 -1372,I7N3G6G,1372,1,no description,60.2 -1373,I7N3G6G,1373,1,no description,60.2 -1374,I7N3G6G,1374,1,no description,60.2 -1375,I7N3G6G,1375,1,no description,60.2 -1376,I7N3G6G,1376,1,no description,60.2 -1377,I7N3G6G,1377,1,no description,60.2 -1378,I7N3G6G,1378,1,no description,60.2 -1379,I7N3G6G,1379,1,no description,60.2 -1380,I7N3G6G,1380,1,no description,60.2 -1381,I7N3G6G,1381,1,no description,60.2 -1382,I7N3G6G,1382,1,no description,60.2 -1383,I7N3G6G,1383,1,no description,60.2 -1384,I7N3G6G,1384,1,no description,60.2 -1385,I7N3G6G,1385,1,no description,60.2 -1386,I7N3G6G,1386,1,no description,60.2 -1387,I7N3G6G,1387,1,no description,60.2 -1388,I7N3G6G,1388,1,no description,60.2 -1389,I7N3G6G,1389,1,no description,60.2 -1390,I7N3G6G,1390,1,no description,60.2 -1391,I7N3G6G,1391,1,no description,60.2 -1392,I7N3G6G,1392,1,no description,60.2 -1393,I7N3G6G,1393,1,no description,60.2 -1394,I7N3G6G,1394,1,no description,60.2 -1395,I7N3G6G,1395,1,no description,60.2 -1396,I7N3G6G,1396,1,no description,60.2 -1397,I7N3G6G,1397,1,no description,60.2 -1398,I7N3G6G,1398,1,no description,60.2 -1399,I7N3G6G,1399,1,no description,60.2 -1400,I7N3G6G,1400,1,no description,60.2 -1401,I7N3G6G,1401,1,no description,60.2 -1402,I7N3G6G,1402,1,no description,60.2 -1403,I7N3G6G,1403,1,no description,60.2 -1404,I7N3G6G,1404,1,no description,60.2 -1405,I7N3G6G,1405,1,no description,60.2 -1406,I7N3G6G,1406,1,no description,60.2 -1407,I7N3G6G,1407,1,no description,60.2 -1408,I7N3G6G,1408,1,no description,60.2 -1409,I7N3G6G,1409,1,no description,60.2 -1410,I7N3G6G,1410,1,no description,60.2 -1411,I7N3G6G,1411,1,no description,60.2 -1412,I7N3G6G,1412,1,no description,60.2 -1413,I7N3G6G,1413,1,no description,60.2 -1414,I7N3G6G,1414,1,no description,60.2 -1415,I7N3G6G,1415,1,no description,60.2 -1416,I7N3G6G,1416,1,no description,60.2 -1417,I7N3G6G,1417,1,no description,60.2 -1418,I7N3G6G,1418,1,no description,60.2 -1419,I7N3G6G,1419,1,no description,60.2 -1420,I7N3G6G,1420,1,no description,60.2 -1421,I7N3G6G,1421,1,no description,60.2 -1422,I7N3G6G,1422,1,no description,60.2 -1423,I7N3G6G,1423,1,no description,60.2 -1424,I7N3G6G,1424,1,no description,60.2 -1425,I7N3G6G,1425,1,no description,60.2 -1426,I7N3G6G,1426,1,no description,60.2 -1427,I7N3G6G,1427,1,no description,60.2 -1428,I7N3G6G,1428,1,no description,60.2 -1429,I7N3G6G,1429,1,no description,60.2 -1430,I7N3G6G,1430,1,no description,60.2 -1431,I7N3G6G,1431,1,no description,60.2 -1432,I7N3G6G,1432,1,no description,60.2 -1433,I7N3G6G,1433,1,no description,60.2 -1434,I7N3G6G,1434,1,no description,60.2 -1435,I7N3G6G,1435,1,no description,60.2 -1436,I7N3G6G,1436,1,no description,60.2 -1437,I7N3G6G,1437,1,no description,60.2 -1438,I7N3G6G,1438,1,no description,60.2 -1439,I7N3G6G,1439,1,no description,60.2 -1440,I7N3G6G,1440,1,no description,60.2 -1441,I7N3G6G,1441,1,no description,60.2 -1442,I7N3G6G,1442,1,no description,60.2 -1443,I7N3G6G,1443,1,no description,60.2 -1444,I7N3G6G,1444,1,no description,60.2 -1445,I7N3G6G,1445,1,no description,60.2 -1446,I7N3G6G,1446,1,no description,60.2 -1447,I7N3G6G,1447,1,no description,60.2 -1448,I7N3G6G,1448,1,no description,60.2 -1449,I7N3G6G,1449,1,no description,60.2 -1450,I7N3G6G,1450,1,no description,60.2 -1451,I7N3G6G,1451,1,no description,60.2 -1452,I7N3G6G,1452,1,no description,60.2 -1453,I7N3G6G,1453,1,no description,60.2 -1454,I7N3G6G,1454,1,no description,60.2 -1455,I7N3G6G,1455,1,no description,60.2 -1456,I7N3G6G,1456,1,no description,60.2 -1457,I7N3G6G,1457,1,no description,60.2 -1458,I7N3G6G,1458,1,no description,60.2 -1459,I7N3G6G,1459,1,no description,60.2 -1460,I7N3G6G,1460,1,no description,60.2 -1461,I7N3G6G,1461,1,no description,60.2 -1462,I7N3G6G,1462,1,no description,60.2 -1463,I7N3G6G,1463,1,no description,60.2 -1464,I7N3G6G,1464,1,no description,60.2 -1465,I7N3G6G,1465,1,no description,60.2 -1466,I7N3G6G,1466,1,no description,60.2 -1467,I7N3G6G,1467,1,no description,60.2 -1468,I7N3G6G,1468,1,no description,60.2 -1469,I7N3G6G,1469,1,no description,60.2 -1470,I7N3G6G,1470,1,no description,60.2 -1471,I7N3G6G,1471,1,no description,60.2 -1472,I7N3G6G,1472,1,no description,60.2 -1473,I7N3G6G,1473,1,no description,60.2 -1474,I7N3G6G,1474,1,no description,60.2 -1475,I7N3G6G,1475,1,no description,60.2 -1476,I7N3G6G,1476,1,no description,60.2 -1477,I7N3G6G,1477,1,no description,60.2 -1478,I7N3G6G,1478,1,no description,60.2 -1479,I7N3G6G,1479,1,no description,60.2 -1480,I7N3G6G,1480,1,no description,60.2 -1481,I7N3G6G,1481,1,no description,60.2 -1482,I7N3G6G,1482,1,no description,60.2 -1483,I7N3G6G,1483,1,no description,60.2 -1484,I7N3G6G,1484,1,no description,60.2 -1485,I7N3G6G,1485,1,no description,60.2 -1486,I7N3G6G,1486,1,no description,60.2 -1487,I7N3G6G,1487,1,no description,60.2 -1488,I7N3G6G,1488,1,no description,60.2 -1489,I7N3G6G,1489,1,no description,60.2 -1490,I7N3G6G,1490,1,no description,60.2 -1491,I7N3G6G,1491,1,no description,60.2 -1492,I7N3G6G,1492,1,no description,60.2 -1493,I7N3G6G,1493,1,no description,60.2 -1494,I7N3G6G,1494,1,no description,60.2 -1495,I7N3G6G,1495,1,no description,60.2 -1496,I7N3G6G,1496,1,no description,60.2 -1497,I7N3G6G,1497,1,no description,60.2 -1498,I7N3G6G,1498,1,no description,60.2 -1499,I7N3G6G,1499,1,no description,60.2 -1500,I7N3G6G,1500,1,no description,60.2 -1501,I7N3G6G,1501,1,no description,60.2 -1502,I7N3G6G,1502,1,no description,60.2 -1503,I7N3G6G,1503,1,no description,60.2 -1504,I7N3G6G,1504,1,no description,60.2 -1505,I7N3G6G,1505,1,no description,60.2 -1506,I7N3G6G,1506,1,no description,60.2 -1507,I7N3G6G,1507,1,no description,60.2 -1508,I7N3G6G,1508,1,no description,60.2 -1509,I7N3G6G,1509,1,no description,60.2 -1510,I7N3G6G,1510,1,no description,60.2 -1511,I7N3G6G,1511,1,no description,60.2 -1512,I7N3G6G,1512,1,no description,60.2 -1513,I7N3G6G,1513,1,no description,60.2 -1514,I7N3G6G,1514,1,no description,60.2 -1515,I7N3G6G,1515,1,no description,60.2 -1516,I7N3G6G,1516,1,no description,60.2 -1517,I7N3G6G,1517,1,no description,60.2 -1518,I7N3G6G,1518,1,no description,60.2 -1519,I7N3G6G,1519,1,no description,60.2 -1520,I7N3G6G,1520,1,no description,60.2 -1521,I7N3G6G,1521,1,no description,60.2 -1522,I7N3G6G,1522,1,no description,60.2 -1523,I7N3G6G,1523,1,no description,60.2 -1524,I7N3G6G,1524,1,no description,60.2 -1525,I7N3G6G,1525,1,no description,60.2 -1526,I7N3G6G,1526,1,no description,60.2 -1527,I7N3G6G,1527,1,no description,60.2 -1528,I7N3G6G,1528,1,no description,60.2 -1529,I7N3G6G,1529,1 -1530,I7N3G6G,1530,1,no description,60.2 -1531,I7N3G6G,1531,1,no description,60.2 -1532,I7N3G6G,1532,1,no description,60.2 -1533,I7N3G6G,1533,1,no description,60.2 -1534,I7N3G6G,1534,1,no description,60.2 -1535,I7N3G6G,1535,1,no description,60.2 -1536,I7N3G6G,1536,1,no description,60.2 -1537,I7N3G6G,1537,1,no description,60.2 -1538,I7N3G6G,1538,1,no description,60.2 -1539,I7N3G6G,1539,1,no description,60.2 -1540,I7N3G6G,1540,1,no description,60.2 -1541,I7N3G6G,1541,1,no description,60.2 -1542,I7N3G6G,1542,1,no description,60.2 -1543,I7N3G6G,1543,1,no description,60.2 -1544,I7N3G6G,1544,1,no description,60.2 -1545,I7N3G6G,1545,1,no description,60.2 -1546,I7N3G6G,1546,1,no description,60.2 -1547,I7N3G6G,1547,1,no description,60.2 -1548,I7N3G6G,1548,1,no description,60.2 -1549,I7N3G6G,1549,1,no description,60.2 -1550,I7N3G6G,1550,1,no description,60.2 -1551,I7N3G6G,1551,1,no description,60.2 -1552,I7N3G6G,1552,1,no description,60.2 -1553,I7N3G6G,1553,1,no description,60.2 -1554,I7N3G6G,1554,1,no description,60.2 -1555,I7N3G6G,1555,1,no description,60.2 -1556,I7N3G6G,1556,1,no description,60.2 -1557,I7N3G6G,1557,1,no description,60.2 -1558,I7N3G6G,1558,1,no description,60.2 -1559,I7N3G6G,1559,1,no description,60.2 -1560,I7N3G6G,1560,1,no description,60.2 -1561,I7N3G6G,1561,1,no description,60.2 -1562,I7N3G6G,1562,1,no description,60.2 -1563,I7N3G6G,1563,1,no description,60.2 -1564,I7N3G6G,1564,1,no description,60.2 -1565,I7N3G6G,1565,1,no description,60.2 -1566,I7N3G6G,1566,1,no description,60.2 -1567,I7N3G6G,1567,1,no description,60.2 -1568,I7N3G6G,1568,1,no description,60.2 -1569,I7N3G6G,1569,1,no description,60.2 -1570,I7N3G6G,1570,1,no description,60.2 -1571,I7N3G6G,1571,1,no description,60.2 -1572,I7N3G6G,1572,1,no description,60.2 -1573,I7N3G6G,1573,1,no description,60.2 -1574,I7N3G6G,1574,1,no description,60.2 -1575,I7N3G6G,1575,1,no description,60.2 -1576,I7N3G6G,1576,1,no description,60.2 -1577,I7N3G6G,1577,1,no description,60.2 -1578,I7N3G6G,1578,1,no description,60.2 -1579,I7N3G6G,1579,1,no description,60.2 -1580,I7N3G6G,1580,1,no description,60.2 -1581,I7N3G6G,1581,1,no description,60.2 -1582,I7N3G6G,1582,1,no description,60.2 -1583,I7N3G6G,1583,1,no description,60.2 -1584,I7N3G6G,1584,1,no description,60.2 -1585,I7N3G6G,1585,1,no description,60.2 -1586,I7N3G6G,1586,1,no description,60.2 -1587,I7N3G6G,1587,1,no description,60.2 -1588,I7N3G6G,1588,1,no description,60.2 -1589,I7N3G6G,1589,1,no description,60.2 -1590,I7N3G6G,1590,1,no description,60.2 -1591,I7N3G6G,1591,1,no description,60.2 -1592,I7N3G6G,1592,1,no description,60.2 -1593,I7N3G6G,1593,1,no description,60.2 -1594,I7N3G6G,1594,1,no description,60.2 -1595,I7N3G6G,1595,1,no description,60.2 -1596,I7N3G6G,1596,1,no description,60.2 -1597,I7N3G6G,1597,1,no description,60.2 -1598,I7N3G6G,1598,1,no description,60.2 -1599,I7N3G6G,1599,1,no description,60.2 -1600,I7N3G6G,1600,1,no description,60.2 -1601,I7N3G6G,1601,1,no description,60.2 -1602,I7N3G6G,1602,1,no description,60.2 -1603,I7N3G6G,1603,1,no description,60.2 -1604,I7N3G6G,1604,1,no description,60.2 -1605,I7N3G6G,1605,1,no description,60.2 -1606,I7N3G6G,1606,1,no description,60.2 -1607,I7N3G6G,1607,1,no description,60.2 -1608,I7N3G6G,1608,1,no description,60.2 -1609,I7N3G6G,1609,1,no description,60.2 -1610,I7N3G6G,1610,1,no description,60.2 -1611,I7N3G6G,1611,1,no description,60.2 -1612,I7N3G6G,1612,1,no description,60.2 -1613,I7N3G6G,1613,1,no description,60.2 -1614,I7N3G6G,1614,1,no description,60.2 -1615,I7N3G6G,1615,1,no description,60.2 -1616,I7N3G6G,1616,1,no description,60.2 -1617,I7N3G6G,1617,1,no description,60.2 -1618,I7N3G6G,1618,1,no description,60.2 -1619,I7N3G6G,1619,1,no description,60.2 -1620,I7N3G6G,1620,1,no description,60.2 -1621,I7N3G6G,1621,1,no description,60.2 -1622,I7N3G6G,1622,1,no description,60.2 -1623,I7N3G6G,1623,1,no description,60.2 -1624,I7N3G6G,1624,1,no description,60.2 -1625,I7N3G6G,1625,1,no description,60.2 -1626,I7N3G6G,1626,1,no description,60.2 -1627,I7N3G6G,1627,1,no description,60.2 -1628,I7N3G6G,1628,1,no description,60.2 -1629,I7N3G6G,1629,1,no description,60.2 -1630,I7N3G6G,1630,1,no description,60.2 -1631,I7N3G6G,1631,1,no description,60.2 -1632,I7N3G6G,1632,1,no description,60.2 -1633,I7N3G6G,1633,1,no description,60.2 -1634,I7N3G6G,1634,1,no description,60.2 -1635,I7N3G6G,1635,1,no description,60.2 -1636,I7N3G6G,1636,1,no description,60.2 -1637,I7N3G6G,1637,1,no description,60.2 -1638,I7N3G6G,1638,1,no description,60.2 -1639,I7N3G6G,1639,1,no description,60.2 -1640,I7N3G6G,1640,1,no description,60.2 -1641,I7N3G6G,1641,1,no description,60.2 -1642,I7N3G6G,1642,1,no description,60.2 -1643,I7N3G6G,1643,1,no description,60.2 -1644,I7N3G6G,1644,1,no description,60.2 -1645,I7N3G6G,1645,1,no description,60.2 -1646,I7N3G6G,1646,1,no description,60.2 -1647,I7N3G6G,1647,1,no description,60.2 -1648,I7N3G6G,1648,1,no description,60.2 -1649,I7N3G6G,1649,1,no description,60.2 -1650,I7N3G6G,1650,1,no description,60.2 -1651,I7N3G6G,1651,1,no description,60.2 -1652,I7N3G6G,1652,1,no description,60.2 -1653,I7N3G6G,1653,1,no description,60.2 -1654,I7N3G6G,1654,1,no description,60.2 -1655,I7N3G6G,1655,1,no description,60.2 -1656,I7N3G6G,1656,1,no description,60.2 -1657,I7N3G6G,1657,1,no description,60.2 -1658,I7N3G6G,1658,1,no description,60.2 -1659,I7N3G6G,1659,1,no description,60.2 -1660,I7N3G6G,1660,1,no description,60.2 -1661,I7N3G6G,1661,1,no description,60.2 -1662,I7N3G6G,1662,1,no description,60.2 -1663,I7N3G6G,1663,1,no description,60.2 -1664,I7N3G6G,1664,1,no description,60.2 -1665,I7N3G6G,1665,1,no description,60.2 -1666,I7N3G6G,1666,1,no description,60.2 -1667,I7N3G6G,1667,1,no description,60.2 -1668,I7N3G6G,1668,1,no description,60.2 -1669,I7N3G6G,1669,1,no description,60.2 -1670,I7N3G6G,1670,1,no description,60.2 -1671,I7N3G6G,1671,1,no description,60.2 -1672,I7N3G6G,1672,1,no description,60.2 -1673,I7N3G6G,1673,1,no description,60.2 -1674,I7N3G6G,1674,1,no description,60.2 -1675,I7N3G6G,1675,1,no description,60.2 -1676,I7N3G6G,1676,1,no description,60.2 -1677,I7N3G6G,1677,1,no description,60.2 -1678,I7N3G6G,1678,1,no description,60.2 -1679,I7N3G6G,1679,1,no description,60.2 -1680,I7N3G6G,1680,1,no description,60.2 -1681,I7N3G6G,1681,1,no description,60.2 -1682,I7N3G6G,1682,1,no description,60.2 -1683,I7N3G6G,1683,1,no description,60.2 -1684,I7N3G6G,1684,1,no description,60.2 -1685,I7N3G6G,1685,1,no description,60.2 -1686,I7N3G6G,1686,1,no description,60.2 -1687,I7N3G6G,1687,1,no description,60.2 -1688,I7N3G6G,1688,1,no description,60.2 -1689,I7N3G6G,1689,1,no description,60.2 -1690,I7N3G6G,1690,1,no description,60.2 -1691,I7N3G6G,1691,1,no description,60.2 -1692,I7N3G6G,1692,1,no description,60.2 -1693,I7N3G6G,1693,1,no description,60.2 -1694,I7N3G6G,1694,1,no description,60.2 -1695,I7N3G6G,1695,1,no description,60.2 -1696,I7N3G6G,1696,1,no description,60.2 -1697,I7N3G6G,1697,1,no description,60.2 -1698,I7N3G6G,1698,1,no description,60.2 -1699,I7N3G6G,1699,1,no description,60.2 -1700,I7N3G6G,1700,1,no description,60.2 -1701,I7N3G6G,1701,1,no description,60.2 -1702,I7N3G6G,1702,1,no description,60.2 -1703,I7N3G6G,1703,1,no description,60.2 -1704,I7N3G6G,1704,1,no description,60.2 -1705,I7N3G6G,1705,1,no description,60.2 -1706,I7N3G6G,1706,1,no description,60.2 -1707,I7N3G6G,1707,1,no description,60.2 -1708,I7N3G6G,1708,1,no description,60.2 -1709,I7N3G6G,1709,1,no description,60.2 -1710,I7N3G6G,1710,1,no description,60.2 -1711,I7N3G6G,1711,1,no description,60.2 -1712,I7N3G6G,1712,1,no description,60.2 -1713,I7N3G6G,1713,1,no description,60.2 -1714,I7N3G6G,1714,1,no description,60.2 -1715,I7N3G6G,1715,1,no description,60.2 -1716,I7N3G6G,1716,1,no description,60.2 -1717,I7N3G6G,1717,1,no description,60.2 -1718,I7N3G6G,1718,1,no description,60.2 -1719,I7N3G6G,1719,1,no description,60.2 -1720,I7N3G6G,1720,1,no description,60.2 -1721,I7N3G6G,1721,1,no description,60.2 -1722,I7N3G6G,1722,1,no description,60.2 -1723,I7N3G6G,1723,1,no description,60.2 -1724,I7N3G6G,1724,1,no description,60.2 -1725,I7N3G6G,1725,1,no description,60.2 -1726,I7N3G6G,1726,1,no description,60.2 -1727,I7N3G6G,1727,1,no description,60.2 -1728,I7N3G6G,1728,1,no description,60.2 -1729,I7N3G6G,1729,1,no description,60.2 -1730,I7N3G6G,1730,1,no description,60.2 -1731,I7N3G6G,1731,1,no description,60.2 -1732,I7N3G6G,1732,1,no description,60.2 -1733,I7N3G6G,1733,1,no description,60.2 -1734,I7N3G6G,1734,1,no description,60.2 -1735,I7N3G6G,1735,1,no description,60.2 -1736,I7N3G6G,1736,1,no description,60.2 -1737,I7N3G6G,1737,1,no description,60.2 -1738,I7N3G6G,1738,1,no description,60.2 -1739,I7N3G6G,1739,1,no description,60.2 -1740,I7N3G6G,1740,1,no description,60.2 -1741,I7N3G6G,1741,1,no description,60.2 -1742,I7N3G6G,1742,1,no description,60.2 -1743,I7N3G6G,1743,1,no description,60.2 -1744,I7N3G6G,1744,1,no description,60.2 -1745,I7N3G6G,1745,1,no description,60.2 -1746,I7N3G6G,1746,1,no description,60.2 -1747,I7N3G6G,1747,1,no description,60.2 -1748,I7N3G6G,1748,1,no description,60.2 -1749,I7N3G6G,1749,1,no description,60.2 -1750,I7N3G6G,1750,1,no description,60.2 -1751,I7N3G6G,1751,1,no description,60.2 -1752,I7N3G6G,1752,1,no description,60.2 -1753,I7N3G6G,1753,1,no description,60.2 -1754,I7N3G6G,1754,1,no description,60.2 -1755,I7N3G6G,1755,1,no description,60.2 -1756,I7N3G6G,1756,1,no description,60.2 -1757,I7N3G6G,1757,1,no description,60.2 -1758,I7N3G6G,1758,1,no description,60.2 -1759,I7N3G6G,1759,1,no description,60.2 -1760,I7N3G6G,1760,1,no description,60.2 -1761,I7N3G6G,1761,1,no description,60.2 -1762,I7N3G6G,1762,1,no description,60.2 -1763,I7N3G6G,1763,1,no description,60.2 -1764,I7N3G6G,1764,1,no description,60.2 -1765,I7N3G6G,1765,1,no description,60.2 -1766,I7N3G6G,1766,1,no description,60.2 -1767,I7N3G6G,1767,1,no description,60.2 -1768,I7N3G6G,1768,1,no description,60.2 -1769,I7N3G6G,1769,1,no description,60.2 -1770,I7N3G6G,1770,1,no description,60.2 -1771,I7N3G6G,1771,1,no description,60.2 -1772,I7N3G6G,1772,1,no description,60.2 -1773,I7N3G6G,1773,1,no description,60.2 -1774,I7N3G6G,1774,1,no description,60.2 -1775,I7N3G6G,1775,1,no description,60.2 -1776,I7N3G6G,1776,1,no description,60.2 -1777,I7N3G6G,1777,1,no description,60.2 -1778,I7N3G6G,1778,1,no description,60.2 -1779,I7N3G6G,1779,1,no description,60.2 -1780,I7N3G6G,1780,1,no description,60.2 -1781,I7N3G6G,1781,1,no description,60.2 -1782,I7N3G6G,1782,1,no description,60.2 -1783,I7N3G6G,1783,1,no description,60.2 -1784,I7N3G6G,1784,1,no description,60.2 -1785,I7N3G6G,1785,1,no description,60.2 -1786,I7N3G6G,1786,1,no description,60.2 -1787,I7N3G6G,1787,1,no description,60.2 -1788,I7N3G6G,1788,1,no description,60.2 -1789,I7N3G6G,1789,1,no description,60.2 -1790,I7N3G6G,1790,1,no description,60.2 -1791,I7N3G6G,1791,1,no description,60.2 -1792,I7N3G6G,1792,1,no description,60.2 -1793,I7N3G6G,1793,1,no description,60.2 -1794,I7N3G6G,1794,1,no description,60.2 -1795,I7N3G6G,1795,1,no description,60.2 -1796,I7N3G6G,1796,1,no description,60.2 -1797,I7N3G6G,1797,1,no description,60.2 -1798,I7N3G6G,1798,1,no description,60.2 -1799,I7N3G6G,1799,1,no description,60.2 -1800,I7N3G6G,1800,1,no description,60.2 -1801,I7N3G6G,1801,1,no description,60.2 -1802,I7N3G6G,1802,1,no description,60.2 -1803,I7N3G6G,1803,1,no description,60.2 -1804,I7N3G6G,1804,1,no description,60.2 -1805,I7N3G6G,1805,1,no description,60.2 -1806,I7N3G6G,1806,1,no description,60.2 -1807,I7N3G6G,1807,1,no description,60.2 -1808,I7N3G6G,1808,1,no description,60.2 -1809,I7N3G6G,1809,1,no description,60.2 -1810,I7N3G6G,1810,1,no description,60.2 -1811,I7N3G6G,1811,1,no description,60.2 -1812,I7N3G6G,1812,1,no description,60.2 -1813,I7N3G6G,1813,1,no description,60.2 -1814,I7N3G6G,1814,1,no description,60.2 -1815,I7N3G6G,1815,1,no description,60.2 -1816,I7N3G6G,1816,1,no description,60.2 -1817,I7N3G6G,1817,1,no description,60.2 -1818,I7N3G6G,1818,1,no description,60.2 -1819,I7N3G6G,1819,1,no description,60.2 -1820,I7N3G6G,1820,1,no description,60.2 -1821,I7N3G6G,1821,1,no description,60.2 -1822,I7N3G6G,1822,1,no description,60.2 -1823,I7N3G6G,1823,1,no description,60.2 -1824,I7N3G6G,1824,1,no description,60.2 -1825,I7N3G6G,1825,1,no description,60.2 -1826,I7N3G6G,1826,1,no description,60.2 -1827,I7N3G6G,1827,1,no description,60.2 -1828,I7N3G6G,1828,1,no description,60.2 -1829,I7N3G6G,1829,1,no description,60.2 -1830,I7N3G6G,1830,1,no description,60.2 -1831,I7N3G6G,1831,1,no description,60.2 -1832,I7N3G6G,1832,1,no description,60.2 -1833,I7N3G6G,1833,1,no description,60.2 -1834,I7N3G6G,1834,1,no description,60.2 -1835,I7N3G6G,1835,no description,60.2 -1836,I7N3G6G,1836,1,no description,60.2 -1837,I7N3G6G,1837,1,no description,60.2 -1838,I7N3G6G,1838,1,no description,60.2 -1839,I7N3G6G,1839,1,no description,60.2 -1840,I7N3G6G,1840,1,no description,60.2 -1841,I7N3G6G,1841,1,no description,60.2 -1842,I7N3G6G,1842,1,no description,60.2 -1843,I7N3G6G,1843,1,no description,60.2 -1844,I7N3G6G,1844,1,no description,60.2 -1845,I7N3G6G,1845,1,no description,60.2 -1846,I7N3G6G,1846,1,no description,60.2 -1847,I7N3G6G,1847,1,no description,60.2 -1848,I7N3G6G,1848,1,no description,60.2 -1849,I7N3G6G,1849,1,no description,60.2 -1850,I7N3G6G,1850,1,no description,60.2 -1851,I7N3G6G,1851,1,no description,60.2 -1852,I7N3G6G,1852,1,no description,60.2 -1853,I7N3G6G,1853,1,no description,60.2 -1854,I7N3G6G,1854,1,no description,60.2 -1855,I7N3G6G,1855,1,no description,60.2 -1856,I7N3G6G,1856,1,no description,60.2 -1857,I7N3G6G,1857,1,no description,60.2 -1858,I7N3G6G,1858,1,no description,60.2 -1859,I7N3G6G,1859,1,no description,60.2 -1860,I7N3G6G,1860,1,no description,60.2 -1861,I7N3G6G,1861,1,no description,60.2 -1862,I7N3G6G,1862,1,no description,60.2 -1863,I7N3G6G,1863,1,no description,60.2 -1864,I7N3G6G,1864,1,no description,60.2 -1865,I7N3G6G,1865,1,no description,60.2 -1866,I7N3G6G,1866,1,no description,60.2 -1867,I7N3G6G,1867,1,no description,60.2 -1868,I7N3G6G,1868,1,no description,60.2 -1869,I7N3G6G,1869,1,no description,60.2 -1870,I7N3G6G,1870,1,no description,60.2 -1871,I7N3G6G,1871,1,no description,60.2 -1872,I7N3G6G,1872,1,no description,60.2 -1873,I7N3G6G,1873,1,no description,60.2 -1874,I7N3G6G,1874,1,no description,60.2 -1875,I7N3G6G,1875,1,no description,60.2 -1876,I7N3G6G,1876,1,no description,60.2 -1877,I7N3G6G,1877,1,no description,60.2 -1878,I7N3G6G,1878,1,no description,60.2 -1879,I7N3G6G,1879,1,no description,60.2 -1880,I7N3G6G,1880,1,no description,60.2 -1881,I7N3G6G,1881,1,no description,60.2 -1882,I7N3G6G,1882,1,no description,60.2 -1883,I7N3G6G,1883,1,no description,60.2 -1884,I7N3G6G,1884,1,no description,60.2 -1885,I7N3G6G,1885,1,no description,60.2 -1886,I7N3G6G,1886,1,no description,60.2 -1887,I7N3G6G,1887,1,no description,60.2 -1888,I7N3G6G,1888,1,no description,60.2 -1889,I7N3G6G,1889,1,no description,60.2 -1890,I7N3G6G,1890,1,no description,60.2 -1891,I7N3G6G,1891,1,no description,60.2 -1892,I7N3G6G,1892,1,no description,60.2 -1893,I7N3G6G,1893,1,no description,60.2 -1894,I7N3G6G,1894,1,no description,60.2 -1895,I7N3G6G,1895,1,no description,60.2 -1896,I7N3G6G,1896,1,no description,60.2 -1897,I7N3G6G,1897,1,no description,60.2 -1898,I7N3G6G,1898,1,no description,60.2 -1899,I7N3G6G,1899,1,no description,60.2 -1900,I7N3G6G,1900,1,no description,60.2 -1901,I7N3G6G,1901,1,no description,60.2 -1902,I7N3G6G,1902,1,no description,60.2 -1903,I7N3G6G,1903,1,no description,60.2 -1904,I7N3G6G,1904,1,no description,60.2 -1905,I7N3G6G,1905,1,no description,60.2 -1906,I7N3G6G,1906,1,no description,60.2 -1907,I7N3G6G,1907,1,no description,60.2 -1908,I7N3G6G,1908,1,no description,60.2 -1909,I7N3G6G,1909,1,no description,60.2 -1910,I7N3G6G,1910,1,no description,60.2 -1911,I7N3G6G,1911,1,no description,60.2 -1912,I7N3G6G,1912,1,no description,60.2 -1913,I7N3G6G,1913,1,no description,60.2 -1914,I7N3G6G,1914,1,no description,60.2 -1915,I7N3G6G,1915,1,no description,60.2 -1916,I7N3G6G,1916,1,no description,60.2 -1917,I7N3G6G,1917,1,no description,60.2 -1918,I7N3G6G,1918,1,no description,60.2 -1919,I7N3G6G,1919,1,no description,60.2 -1920,I7N3G6G,1920,1,no description,60.2 -1921,I7N3G6G,1921,1,no description,60.2 -1922,I7N3G6G,1922,1,no description,60.2 -1923,I7N3G6G,1923,1,no description,60.2 -1924,I7N3G6G,1924,1,no description,60.2 -1925,I7N3G6G,1925,1,no description,60.2 -1926,I7N3G6G,1926,1,no description,60.2 -1927,I7N3G6G,1927,1,no description,60.2 -1928,I7N3G6G,1928,1,no description,60.2 -1929,I7N3G6G,1929,1,no description,60.2 -1930,I7N3G6G,1930,1,no description,60.2 -1931,I7N3G6G,1931,1,no description,60.2 -1932,I7N3G6G,1932,1,no description,60.2 -1933,I7N3G6G,1933,1,no description,60.2 -1934,I7N3G6G,1934,1,no description,60.2 -1935,I7N3G6G,1935,1,no description,60.2 -1936,I7N3G6G,1936,1,no description,60.2 -1937,I7N3G6G,1937,1,no description,60.2 -1938,I7N3G6G,1938,1,no description,60.2 -1939,I7N3G6G,1939,1,no description,60.2 -1940,I7N3G6G,1940,1,no description,60.2 -1941,I7N3G6G,1941,1,no description,60.2 -1942,I7N3G6G,1942,1,no description,60.2 -1943,I7N3G6G,1943,1,no description,60.2 -1944,I7N3G6G,1944,1,no description,60.2 -1945,I7N3G6G,1945,1,no description,60.2 -1946,I7N3G6G,1946,1,no description,60.2 -1947,I7N3G6G,1947,1,no description,60.2 -1948,I7N3G6G,1948,1,no description,60.2 -1949,I7N3G6G,1949,1,no description,60.2 -1950,I7N3G6G,1950,1,no description,60.2 -1951,I7N3G6G,1951,1,no description,60.2 -1952,I7N3G6G,1952,1,no description,60.2 -1953,I7N3G6G,1953,1,no description,60.2 -1954,I7N3G6G,1954,1,no description,60.2 -1955,I7N3G6G,1955,1,no description,60.2 -1956,I7N3G6G,1956,1,no description,60.2 -1957,I7N3G6G,1957,1,no description,60.2 -1958,I7N3G6G,1958,1,no description,60.2 -1959,I7N3G6G,1959,1,no description,60.2 -1960,I7N3G6G,1960,1,no description,60.2 -1961,I7N3G6G,1961,1,no description,60.2 -1962,I7N3G6G,1962,1,no description,60.2 -1963,I7N3G6G,1963,1,no description,60.2 -1964,I7N3G6G,1964,1,no description,60.2 -1965,I7N3G6G,1965,1,no description,60.2 -1966,I7N3G6G,1966,1,no description,60.2 -1967,I7N3G6G,1967,1,no description,60.2 -1968,I7N3G6G,1968,1,no description,60.2 -1969,I7N3G6G,1969,1,no description,60.2 -1970,I7N3G6G,1970,1,no description,60.2 -1971,I7N3G6G,1971,1,no description,60.2 -1972,I7N3G6G,1972,1,no description,60.2 -1973,I7N3G6G,1973,1,no description,60.2 -1974,I7N3G6G,1974,1,no description,60.2 -1975,I7N3G6G,1975,1,no description,60.2 -1976,I7N3G6G,1976,1,no description,60.2 -1977,I7N3G6G,1977,1,no description,60.2 -1978,I7N3G6G,1978,1,no description,60.2 -1979,I7N3G6G,1979,1,no description,60.2 -1980,I7N3G6G,1980,1,no description,60.2 -1981,I7N3G6G,1981,1,no description,60.2 -1982,I7N3G6G,1982,1,no description,60.2 -1983,I7N3G6G,1983,1,no description,60.2 -1984,I7N3G6G,1984,1,no description,60.2 -1985,I7N3G6G,1985,1,no description,60.2 -1986,I7N3G6G,1986,1,no description,60.2 -1987,I7N3G6G,1987,1,no description,60.2 -1988,I7N3G6G,1988,1,no description,60.2 -1989,I7N3G6G,1989,1,no description,60.2 -1990,I7N3G6G,1990,1,no description,60.2 -1991,I7N3G6G,1991,1,no description,60.2 -1992,I7N3G6G,1992,1,no description,60.2 -1993,I7N3G6G,1993,1,no description,60.2 -1994,I7N3G6G,1994,1,no description,60.2 -1995,I7N3G6G,1995,1,no description,60.2 -1996,I7N3G6G,1996,1,no description,60.2 -1997,I7N3G6G,1997,1,no description,60.2 -1998,I7N3G6G,1998,1,no description,60.2 -1999,I7N3G6G,1999,1,no description,60.2 -2000,I7N3G6G,2000,1,no description,60.2 -2001,I7N3G6G,2001,1,no description,60.2 -2002,I7N3G6G,2002,1,no description,60.2 -2003,I7N3G6G,2003,1,no description,60.2 -2004,I7N3G6G,2004,1,no description,60.2 -2005,I7N3G6G,2005,1,no description,60.2 -2006,I7N3G6G,2006,1,no description,60.2 -2007,I7N3G6G,2007,1,no description,60.2 -2008,I7N3G6G,2008,1,no description,60.2 -2009,I7N3G6G,2009,1,no description,60.2 -2010,I7N3G6G,2010,1,no description,60.2 -2011,I7N3G6G,2011,1,no description,60.2 -2012,I7N3G6G,2012,1,no description,60.2 -2013,I7N3G6G,2013,1,no description,60.2 -2014,I7N3G6G,2014,1,no description,60.2 -2015,I7N3G6G,2015,1,no description,60.2 -2016,I7N3G6G,2016,1,no description,60.2 -2017,I7N3G6G,2017,1,no description,60.2 -2018,I7N3G6G,2018,1,no description,60.2 -2019,I7N3G6G,2019,1,no description,60.2 -2020,I7N3G6G,2020,1,no description,60.2 -2021,I7N3G6G,2021,1,no description,60.2 -2022,I7N3G6G,2022,1,no description,60.2 -2023,I7N3G6G,2023,1,no description,60.2 -2024,I7N3G6G,2024,1,no description,60.2 -2025,I7N3G6G,2025,1,no description,60.2 -2026,I7N3G6G,2026,1,no description,60.2 -2027,I7N3G6G,2027,1,no description,60.2 -2028,I7N3G6G,2028,1,no description,60.2 -2029,I7N3G6G,2029,1,no description,60.2 -2030,I7N3G6G,2030,1,no description,60.2 -2031,I7N3G6G,2031,1,no description,60.2 -2032,I7N3G6G,2032,1,no description,60.2 -2033,I7N3G6G,2033,1,no description,60.2 -2034,I7N3G6G,2034,1,no description,60.2 -2035,I7N3G6G,2035,1,no description,60.2 -2036,I7N3G6G,2036,1,no description,60.2 -2037,I7N3G6G,2037,1,no description,60.2 -2038,I7N3G6G,2038,1,no description,60.2 -2039,I7N3G6G,2039,1,no description,60.2 -2040,I7N3G6G,2040,1,no description,60.2 -2041,I7N3G6G,2041,1,no description,60.2 -2042,I7N3G6G,2042,1,no description,60.2 -2043,I7N3G6G,2043,1,no description,60.2 -2044,I7N3G6G,2044,1,no description,60.2 -2045,I7N3G6G,2045,1,no description,60.2 -2046,I7N3G6G,2046,1,no description,60.2 -2047,I7N3G6G,2047,1,no description,60.2 -2048,I7N3G6G,2048,1,no description,60.2 -2049,I7N3G6G,2049,1,no description,60.2 -2050,I7N3G6G,2050,1,no description,60.2 -2051,I7N3G6G,2051,1,no description,60.2 -2052,I7N3G6G,2052,1,no description,60.2 -2053,I7N3G6G,2053,1,no description,60.2 -2054,I7N3G6G,2054,1,no description,60.2 -2055,I7N3G6G,2055,1,no description,60.2 -2056,I7N3G6G,2056,1,no description,60.2 -2057,I7N3G6G,2057,1,no description,60.2 -2058,I7N3G6G,2058,1,no description,60.2 -2059,I7N3G6G,2059,1,no description,60.2 -2060,I7N3G6G,2060,1,no description,60.2 -2061,I7N3G6G,2061,1,no description,60.2 -2062,I7N3G6G,2062,1,no description,60.2 -2063,I7N3G6G,2063,1,no description,60.2 -2064,I7N3G6G,2064,1,no description,60.2 -2065,I7N3G6G,2065,1,no description,60.2 -2066,I7N3G6G,2066,1,no description,60.2 -2067,I7N3G6G,2067,1,no description,60.2 -2068,I7N3G6G,2068,1,no description,60.2 -2069,I7N3G6G,2069,1,no description,60.2 -2070,I7N3G6G,2070,1,no description,60.2 -2071,I7N3G6G,2071,1,no description,60.2 -2072,I7N3G6G,2072,1,no description,60.2 -2073,I7N3G6G,2073,1,no description,60.2 -2074,I7N3G6G,2074,1,no description,60.2 -2075,I7N3G6G,2075,1,no description,60.2 -2076,I7N3G6G,2076,1,no description,60.2 -2077,I7N3G6G,2077,1,no description,60.2 -2078,I7N3G6G,2078,1,no description,60.2 -2079,I7N3G6G,2079,1,no description,60.2 -2080,I7N3G6G,2080,1,no description,60.2 -2081,I7N3G6G,2081,1,no description,60.2 -2082,I7N3G6G,2082,1,no description,60.2 -2083,I7N3G6G,2083,1,no description,60.2 -2084,I7N3G6G,2084,1,no description,60.2 -2085,I7N3G6G,2085,1,no description,60.2 -2086,I7N3G6G,2086,1,no description,60.2 -2087,I7N3G6G,2087,1,no description,60.2 -2088,I7N3G6G,2088,1,no description,60.2 -2089,I7N3G6G,2089,1,no description,60.2 -2090,I7N3G6G,2090,1,no description,60.2 -2091,I7N3G6G,2091,1,no description,60.2 -2092,I7N3G6G,2092,1,no description,60.2 -2093,I7N3G6G,2093,1,no description,60.2 -2094,I7N3G6G,2094,1,no description,60.2 -2095,I7N3G6G,2095,1,no description,60.2 -2096,I7N3G6G,2096,1,no description,60.2 -2097,I7N3G6G,2097,1,no description,60.2 -2098,I7N3G6G,2098,1,no description,60.2 -2099,I7N3G6G,2099,1,no description,60.2 -2100,I7N3G6G,2100,1,no description,60.2 -2101,I7N3G6G,2101,1,no description,60.2 -2102,I7N3G6G,2102,1,no description,60.2 -2103,I7N3G6G,2103,1,no description,60.2 -2104,I7N3G6G,2104,1,no description,60.2 -2105,I7N3G6G,2105,1,no description,60.2 -2106,I7N3G6G,2106,1,no description,60.2 -2107,I7N3G6G,2107,1,no description,60.2 -2108,I7N3G6G,2108,1,no description,60.2 -2109,I7N3G6G,2109,1,no description,60.2 -2110,I7N3G6G,2110,1,no description,60.2 -2111,I7N3G6G,2111,1,no description,60.2 -2112,I7N3G6G,2112,1,no description,60.2 -2113,I7N3G6G,2113,1,no description,60.2 -2114,I7N3G6G,2114,1,no description,60.2 -2115,I7N3G6G,2115,1,no description,60.2 -2116,I7N3G6G,2116,1,no description,60.2 -2117,I7N3G6G,2117,1,no description,60.2 -2118,I7N3G6G,2118,1,no description,60.2 -2119,I7N3G6G,2119,1,no description,60.2 -2120,I7N3G6G,2120,1,no description,60.2 -2121,I7N3G6G,2121,1,no description,60.2 -2122,I7N3G6G,2122,1,no description,60.2 -2123,I7N3G6G,2123,1,no description,60.2 -2124,I7N3G6G,2124,1,no description,60.2 -2125,I7N3G6G,2125,1,no description,60.2 -2126,I7N3G6G,2126,1,no description,60.2 -2127,I7N3G6G,2127,1,no description,60.2 -2128,I7N3G6G,2128,1,no description,60.2 -2129,I7N3G6G,2129,1,no description,60.2 -2130,I7N3G6G,2130,1,no description,60.2 -2131,I7N3G6G,2131,1,no description,60.2 -2132,I7N3G6G,2132,1,no description,60.2 -2133,I7N3G6G,2133,1,no description,60.2 -2134,I7N3G6G,2134,1,no description,60.2 -2135,I7N3G6G,2135,1,no description,60.2 -2136,I7N3G6G,2136,1,no description,60.2 -2137,I7N3G6G,2137,1,no description,60.2 -2138,I7N3G6G,2138,1,no description,60.2 -2139,I7N3G6G,2139,1,no description,60.2 -2140,I7N3G6G,2140,1,no description,60.2 -2141,I7N3G6G,2141,1,no description,60.2 -2142,I7N3G6G,2142,1,no description,60.2 -2143,I7N3G6G,2143,1,no description,60.2 -2144,I7N3G6G,2144,1,no description,60.2 -2145,I7N3G6G,2145,1,no description,60.2 -2146,I7N3G6G,2146,1,no description,60.2 -2147,I7N3G6G,2147,1,no description,60.2 -2148,I7N3G6G,2148,1,no description,60.2 -2149,I7N3G6G,2149,1,no description,60.2 -2150,I7N3G6G,2150,1,no description,60.2 -2151,I7N3G6G,2151,1,no description,60.2 -2152,I7N3G6G,2152,1,no description,60.2 -2153,I7N3G6G,2153,1,no description,60.2 -2154,I7N3G6G,2154,1,no description,60.2 -2155,I7N3G6G,2155,1,no description,60.2 -2156,I7N3G6G,2156,1,no description,60.2 -2157,I7N3G6G,2157,1,no description,60.2 -2158,I7N3G6G,2158,1,no description,60.2 -2159,I7N3G6G,2159,1,no description,60.2 -2160,I7N3G6G,2160,1,no description,60.2 -2161,I7N3G6G,2161,1,no description,60.2 -2162,I7N3G6G,2162,1,no description,60.2 -2163,I7N3G6G,2163,1,no description,60.2 -2164,I7N3G6G,2164,1,no description,60.2 -2165,I7N3G6G,2165,1,no description,60.2 -2166,I7N3G6G,2166,1,no description,60.2 -2167,I7N3G6G,2167,1,no description,60.2 -2168,I7N3G6G,2168,1,no description,60.2 -2169,I7N3G6G,2169,1,no description,60.2 -2170,I7N3G6G,2170,1,no description,60.2 -2171,I7N3G6G,2171,1,no description,60.2 -2172,I7N3G6G,2172,1,no description,60.2 -2173,I7N3G6G,2173,1,no description,60.2 -2174,I7N3G6G,2174,1,no description,60.2 -2175,I7N3G6G,2175,1,no description,60.2 -2176,I7N3G6G,2176,1,no description,60.2 -2177,I7N3G6G,2177,1,no description,60.2 -2178,I7N3G6G,2178,1,no description,60.2 -2179,I7N3G6G,2179,1,no description,60.2 -2180,I7N3G6G,2180,1,no description,60.2 -2181,I7N3G6G,2181,1,no description,60.2 -2182,I7N3G6G,2182,1,no description,60.2 -2183,I7N3G6G,2183,1,no description,60.2 -2184,I7N3G6G,2184,1,no description,60.2 -2185,I7N3G6G,2185,1,no description,60.2 -2186,I7N3G6G,2186,1,no description,60.2 -2187,I7N3G6G,2187,1,no description,60.2 -2188,I7N3G6G,2188,1,no description,60.2 -2189,I7N3G6G,2189,1,no description,60.2 -2190,I7N3G6G,2190,1,no description,60.2 -2191,I7N3G6G,2191,1,no description,60.2 -2192,I7N3G6G,2192,1,no description,60.2 -2193,I7N3G6G,2193,1,no description,60.2 -2194,I7N3G6G,2194,1,no description,60.2 -2195,I7N3G6G,2195,1,no description,60.2 -2196,I7N3G6G,2196,1,no description,60.2 -2197,I7N3G6G,2197,1,no description,60.2 -2198,I7N3G6G,2198,1,no description,60.2 -2199,I7N3G6G,2199,1,no description,60.2 -2200,I7N3G6G,2200,1,no description,60.2 -2201,I7N3G6G,2201,1,no description,60.2 -2202,I7N3G6G,2202,1,no description,60.2 -2203,I7N3G6G,2203,1,no description,60.2 -2204,I7N3G6G,2204,1,no description,60.2 -2205,I7N3G6G,2205,1,no description,60.2 -2206,I7N3G6G,2206,1,no description,60.2 -2207,I7N3G6G,2207,1,no description,60.2 -2208,I7N3G6G,2208,1,no description,60.2 -2209,I7N3G6G,2209,1,no description,60.2 -2210,I7N3G6G,2210,1,no description,60.2 -2211,I7N3G6G,2211,1,no description,60.2 -2212,I7N3G6G,2212,1,no description,60.2 -2213,I7N3G6G,2213,1,no description,60.2 -2214,I7N3G6G,2214,1,no description,60.2 -2215,I7N3G6G,2215,1,no description,60.2 -2216,I7N3G6G,2216,1,no description,60.2 -2217,I7N3G6G,2217,1,no description,60.2 -2218,I7N3G6G,2218,1,no description,60.2 -2219,I7N3G6G,2219,1,no description,60.2 -2220,I7N3G6G,2220,1,no description,60.2 -2221,I7N3G6G,2221,1,no description,60.2 -2222,I7N3G6G,2222,1,no description,60.2 -2223,I7N3G6G,2223,1,no description,60.2 -2224,I7N3G6G,2224,1,no description,60.2 -2225,I7N3G6G,2225,1,no description,60.2 -2226,I7N3G6G,2226,1,no description,60.2 diff --git a/packages/itmat-job-executor/test/testFiles/CSVCurator_error5.tsv b/packages/itmat-job-executor/test/testFiles/CSVCurator_error5.tsv deleted file mode 100644 index fd5fcf489..000000000 --- a/packages/itmat-job-executor/test/testFiles/CSVCurator_error5.tsv +++ /dev/null @@ -1,2227 +0,0 @@ -ID,SubjectID,VisitID,Sex,Description,Weight,UNKNOWN -1,I7N3G6G,1,1,no description,60.2,1 -2,I7N3G6G,2,1,no description,60.2,1 -3,I7N3G6G,3,1,no description,60.2,1 -4,I7N3G6G,4,1,no description,60.2,1 -5,I7N3G6G,5,1,no description,60.2,1 -6,I7N3G6G,6,1,no description,60.2,1 -7,I7N3G6G,7,a,no description,60.2,1 -8,I7N3G6G,8,1,no description,60.2,1 -9,I7N3G6G,9,1,no description,60.2,1 -10,I7N3G6G,10,1,no description,60.2,1 -11,I7N3G6G,11,1,no description,60.2,1 -12,I7N3G6G,12,1,no description,60.2,1 -13,I7N3G6G,13,1,no description,60.2,1 -14,I7N3G6G,14,1,no description,60.2,1 -15,I7N3G6G,15,1,no description,60.2,1 -16,I7N3G6G,16,1,no description,60.2,1 -17,I7N3G6G,17,1,no description,60.2,1 -18,I7N3G6G,18,1,no description,60.2,1 -19,I7N3G6G,19,1,no description,60.2,1 -20,I7N3G6G,20,1,no description,60.2,1 -21,I7N3G6G,21,1,no description,60.2,1 -22,I7N3G6G,22,1,no description,60.2,1 -23,I7N3G6G,23,1,no description,60.2,1 -24,I7N3G6G,24,1,no description,60.2,1 -25,I7N3G6G,25,1,no description,60.2,1 -26,I7N3G6G,26,1,no description,60.2,1 -27,I7N3G6G,27,1,no description,60.2,1 -28,I7N3G6G,28,1,no description,60.2,1 -29,I7N3G6G,29,1,no description,60.2,1 -30,I7N3G6G,30,1,no description,60.2 -31,I7N3G6G,31,1,no description,60.2 -32,I7N3G6G,32,1,no description,60.2,1 -33,I7N3G6G,33,1,no description,60.2,1 -34,I7N3G6G,34,1,no description,60.2,1 -35,I7N3G6G,35,1,no description,60.2,1 -36,I7N3G6G,36,1,no description,60.2,1 -37,I7N3G6G,37,1,no description,60.2,1 -38,I7N3G6G,38,1,no description,60.2,1 -39,I7N3G6G,39,1,no description,60.2,1 -40,I7N3G6G,40,1,no description,60.2,1 -41,I7N3G6G,41,1,no description,60.2,1 -42,I7N3G6G,42,1,no description,60.2,1 -43,I7N3G6G,43,1,no description,60.2,1 -44,I7N3G6G,44,1,no description,60.2,1 -45,I7N3G6G,45,1,no description,60.2,1 -46,I7N3G6G,46,1,no description,60.2,1 -47,I7N3G6G,47,1,no description,60.2,1 -48,I7N3G6G,48,1,no description,60.2,1 -49,I7N3G6G,49,1,no description,60.2,1 -50,I7N3G6G,50,1,no description,60.2,1 -51,I7N3G6G,51,1,no description,60.2,1 -52,I7N3G6G,52,1,no description,60.2,1 -53,I7N3G6G,53,1,no description,60.2,1 -54,I7N3G6G,54,1,no description,60.2,1 -55,I7N3G6G,55,1,no description,60.2,1 -56,I7N3G6G,56,1,no description,60.2,1 -57,I7N3G6G,57,1,no description,60.2,1 -58,I7N3G6G,58,1,no description,60.2,1 -59,I7N3G6G,59,1,no description,60.2,1 -60,I7N3G6G,60,1,no description,60.2,1 -61,I7N3G6G,61,1,no description,60.2,1 -62,I7N3G6G,62,1,no description,60.2,1 -63,I7N3G6G,63,1,no description,60.2,1 -64,I7N3G6G,64,1,no description,60.2,1 -65,I7N3G6G,65,1,no description,60.2,1 -66,I7N3G6G,66,1,no description,60.2,1 -67,I7N3G6G,67,1,no description,60.2,1 -68,I7N3G6G,68,1,no description,60.2,1 -69,I7N3G6G,69,1,no description,60.2,1 -70,I7N3G6G,70,1,no description,60.2,1 -71,I7N3G6G,71,1,no description,60.2,1 -72,I7N3G6G,72,1,no description,60.2,1 -73,I7N3G6G,73,1,no description,60.2,1 -74,I7N3G6G,74,1,no description,60.2,1 -75,I7N3G6G,75,1,no description,60.2,1 -76,I7N3G6G,76,1,no description,60.2,1 -77,I7N3G6G,77,1,no description,60.2,1 -78,I7N3G6G,78,1,no description,60.2,1 -79,I7N3G6G,79,1,no description,60.2,1 -80,I7N3G6G,80,1,no description,60.2,1 -81,I7N3G6G,81,1,no description,60.2,1 -82,I7N3G6G,82,1,no description,60.2,1 -83,I7N3G6G,83,1,no description,60.2,1 -84,I7N3G6G,84,1,no description,60.2,1 -85,I7N3G6G,85,1,no description,60.2,1 -86,I7N3G6G,86,1,no description,60.2,1 -87,I7N3G6G,87,1,no description,60.2,1 -88,I7N3G6G,88,1,no description,60.2,1 -89,I7N3G6G,89,1,no description,60.2,1 -90,I7N3G6G,90,1,no description,60.2,1 -91,I7N3G6G,91,1,no description,60.2,1 -92,I7N3G6G,92,1,no description,60.2,1 -93,I7N3G6G,93,1,no description,60.2,1 -94,I7N3G6G,94,1,no description,60.2,1 -95,I7N3G6G,95,1,no description,60.2,1 -96,I7N3G6G,96,1,no description,60.2,1 -97,I7N3G6G,97,1,no description,60.2,1 -98,I7N3G6G,98,1,no description,60.2,1 -99,I7N3G6G,99,1,no description,60.2,1 -100,I7N3G6G,100,1,no description,60.2,1 -101,I7N3G6G,101,1,no description,60.2,1 -102,I7N3G6G,102,1,no description,60.2,1 -103,I7N3G6G,103,1,no description,60.2,1 -104,I7N3G6G,104,1,no description,60.2,1 -105,I7N3G6G,105,1,no description,60.2,1 -106,I7N3G6G,106,1,no description,60.2,1 -107,I7N3G6G,107,1,no description,60.2,1 -108,I7N3G6G,108,1,no description,60.2,1 -109,I7N3G6G,109,1,no description,60.2,1 -110,I7N3G6G,110,1,no description,60.2,1 -111,I7N3G6G,111,1,no description,60.2,1 -112,I7N3G6G,112,1,no description,60.2,1 -113,I7N3G6G,113,1,no description,60.2,1 -114,I7N3G6G,114,1,no description,60.2,1 -115,I7N3G6G,115,1,no description,60.2,1 -116,I7N3G6G,116,1,no description,60.2,1 -117,I7N3G6G,117,1,no description,60.2,1 -118,I7N3G6G,118,1,no description,60.2,1 -119,I7N3G6G,119,1,no description,60.2,1 -120,I7N3G6G,120,1,no description,60.2,1 -121,I7N3G6G,121,1,no description,60.2,1 -122,I7N3G6G,122,1,no description,60.2,1 -123,I7N3G6G,123,1,no description,60.2,1 -124,I7N3G6G,124,1,no description,60.2,1 -125,I7N3G6G,125,1,no description,60.2,1 -126,I7N3G6G,126,1,no description,60.2,1 -127,I7N3G6G,127,1,no description,60.2,1 -128,I7N3G6G,128,1,no description,60.2,1 -129,I7N3G6G,129,1,no description,60.2,1 -130,I7N3G6G,130,1,no description,60.2,1 -131,I7N3G6G,131,1,no description,60.2,1 -132,I7N3G6G,132,1,no description,60.2,1 -133,I7N3G6G,133,1,no description,60.2,1 -134,I7N3G6G,134,1,no description,60.2,1 -135,I7N3G6G,135,1,no description,60.2,1 -136,I7N3G6G,136,1,no description,60.2,1 -137,I7N3G6G,137,1,no description,60.2,1 -138,I7N3G6G,138,1,no description,60.2,1 -139,I7N3G6G,139,1,no description,60.2,1 -140,I7N3G6G,140,1,no description,60.2,1 -141,I7N3G6G,141,1,no description,60.2,1 -142,I7N3G6G,142,1,no description,60.2,1 -143,I7N3G6G,143,1,no description,60.2,1 -144,I7N3G6G,144,1,no description,60.2,1 -145,I7N3G6G,145,1,no description,60.2,1 -146,I7N3G6G,146,1,no description,60.2,1 -147,I7N3G6G,147,1,no description,60.2,1 -148,I7N3G6G,148,1,no description,60.2,1 -149,I7N3G6G,149,1,no description,60.2,1 -150,I7N3G6G,150,1,no description,60.2,1 -151,I7N3G6G,151,1,no description,60.2,1 -152,I7N3G6G,152,1,no description,60.2,1 -153,I7N3G6G,153,1,no description,60.2,1 -154,I7N3G6G,154,1,no description,60.2,1 -155,I7N3G6G,155,1,no description,60.2,1 -156,I7N3G6G,156,1,no description,60.2,1 -157,I7N3G6G,157,1,no description,60.2,1 -158,I7N3G6G,158,1,no description,60.2,1 -159,I7N3G6G,159,1,no description,60.2,1 -160,I7N3G6G,160,1,no description,60.2,1 -161,I7N3G6G,161,1,no description,60.2,1 -162,I7N3G6G,162,1,no description,60.2,1 -163,I7N3G6G,163,1,no description,60.2,1 -164,I7N3G6G,164,1,no description,60.2,1 -165,I7N3G6G,165,1,no description,60.2,1 -166,I7N3G6G,166,1,no description,60.2,1 -167,I7N3G6G,167,1,no description,60.2,1 -168,I7N3G6G,168,1,no description,60.2,1 -169,I7N3G6G,169,1,no description,60.2,1 -170,I7N3G6G,170,1,no description,60.2,1 -171,I7N3G6G,171,1,no description,60.2,1 -172,I7N3G6G,172,1,no description,60.2,1 -173,I7N3G6G,173,1,no description,60.2,1 -174,I7N3G6G,174,1,no description,60.2,1 -175,I7N3G6G,175,1,no description,60.2,1 -176,I7N3G6G,176,1,no description,60.2,1 -177,I7N3G6G,177,1,no description,60.2,1 -178,I7N3G6G,178,1,no description,60.2,1 -179,I7N3G6G,179,1,no description,60.2,1 -180,I7N3G6G,180,1,no description,60.2,1 -181,I7N3G6G,181,1,no description,60.2,1 -182,I7N3G6G,182,1,no description,60.2,1 -183,I7N3G6G,183,1,no description,60.2,1 -184,I7N3G6G,184,1,no description,60.2,1 -185,I7N3G6G,185,1,no description,60.2,1 -186,I7N3G6G,186,1,no description,60.2,1 -187,I7N3G6G,187,1,no description,60.2,1 -188,I7N3G6G,188,1,no description,60.2,1 -189,I7N3G6G,189,1,no description,60.2,1 -190,I7N3G6G,190,1,no description,60.2,1 -191,I7N3G6G,191,1,no description,60.2,1 -192,I7N3G6G,192,1,no description,60.2,1 -193,I7N3G6G,193,1,no description,60.2,1 -194,I7N3G6G,194,1,no description,60.2,1 -195,I7N3G6G,195,1,no description,60.2,1 -196,I7N3G6G,196,1,no description,60.2,1 -197,I7N3G6G,197,1,no description,60.2,1 -198,I7N3G6G,198,1,no description,60.2,1 -199,I7N3G6G,199,1,no description,60.2,1 -200,I7N3G6G,200,1,no description,60.2,1 -201,I7N3G6G,201,1,no description,60.2,1 -202,I7N3G6G,202,1,no description,60.2,1 -203,I7N3G6G,203,1,no description,60.2,1 -204,I7N3G6G,204,1,no description,60.2,1 -205,I7N3G6G,205,1,no description,60.2,1 -206,I7N3G6G,206,1,no description,60.2,1 -207,I7N3G6G,207,1,no description,60.2,1 -208,I7N3G6G,208,1,no description,60.2,1 -209,I7N3G6G,209,1,no description,60.2,1 -210,I7N3G6G,210,1,no description,60.2,1 -211,I7N3G6G,211,1,no description,60.2,1 -212,I7N3G6G,212,1,no description,60.2,1 -213,I7N3G6G,213,1,no description,60.2,1 -214,I7N3G6G,214,1,no description,60.2,1 -215,I7N3G6G,215,1,no description,60.2,1 -216,I7N3G6G,216,1,no description,60.2,1 -217,I7N3G6G,217,1,no description,60.2,1 -218,I7N3G6G,218,1,no description,60.2,1 -219,I7N3G6G,219,1,no description,60.2,1 -220,I7N3G6G,220,1,no description,60.2,1 -221,I7N3G6G,221,1,no description,60.2,1 -222,I7N3G6G,222,1,no description,60.2,1 -223,I7N3G6G,223,1,no description,60.2,1 -224,I7N3G6G,224,1,no description,60.2,1 -225,I7N3G6G,225,1,no description,60.2,1 -226,I7N3G6G,226,1,no description,60.2,1 -227,I7N3G6G,227,1,no description,60.2,1 -228,I7N3G6G,228,1,no description,60.2,1 -229,I7N3G6G,229,1,no description,60.2,1 -230,I7N3G6G,230,1,no description,60.2,1 -231,I7N3G6G,231,1,no description,60.2,1 -232,I7N3G6G,232,1,no description,60.2,1 -233,I7N3G6G,233,1,no description,60.2,1 -234,I7N3G6G,234,1,no description,60.2,1 -235,I7N3G6G,235,1,no description,60.2,1 -236,I7N3G6G,236,1,no description,60.2,1 -237,I7N3G6G,237,1,no description,60.2,1 -238,I7N3G6G,238,1,no description,60.2,1 -239,I7N3G6G,239,1,no description,60.2,1 -240,I7N3G6G,240,1,no description,60.2,1 -241,I7N3G6G,241,1,no description,60.2,1 -242,I7N3G6G,242,1,no description,60.2,1 -243,I7N3G6G,243,1,no description,60.2,1 -244,I7N3G6G,244,1,no description,60.2,1 -245,I7N3G6G,245,1,no description,60.2,1 -246,I7N3G6G,246,1,no description,60.2,1 -247,I7N3G6G,247,1,no description,60.2,1 -248,I7N3G6G,248,1,no description,60.2,1 -249,I7N3G6G,249,1,no description,60.2,1 -250,I7N3G6G,250,1,no description,60.2,1 -251,I7N3G6G,251,1,no description,60.2,1 -252,I7N3G6G,252,1,no description,60.2,1 -253,I7N3G6G,253,1,no description,60.2,1 -254,I7N3G6G,254,1,no description,60.2,1 -255,I7N3G6G,255,1,no description,60.2,1 -256,I7N3G6G,256,1,no description,60.2,1 -257,I7N3G6G,257,1,no description,60.2,1 -258,I7N3G6G,258,1,no description,60.2,1 -259,I7N3G6G,259,1,no description,60.2,1 -260,I7N3G6G,260,1,no description,60.2,1 -261,I7N3G6G,261,1,no description,60.2,1 -262,I7N3G6G,262,1,no description,60.2,1 -263,I7N3G6G,263,1,no description,60.2,1 -264,I7N3G6G,264,1,no description,60.2,1 -265,I7N3G6G,265,1,no description,60.2,1 -266,I7N3G6G,266,1,no description,60.2,1 -267,I7N3G6G,267,1,no description,60.2,1 -268,I7N3G6G,268,1,no description,60.2,1 -269,I7N3G6G,269,1,no description,60.2,1 -270,I7N3G6G,270,1,no description,60.2,1 -271,I7N3G6G,271,1,no description,60.2,1 -272,I7N3G6G,272,1,no description,60.2,1 -273,I7N3G6G,273,1,no description,60.2,1 -274,I7N3G6G,274,1,no description,60.2,1 -275,I7N3G6G,275,1,no description,60.2,1 -276,I7N3G6G,276,1,no description,60.2,1 -277,I7N3G6G,277,1,no description,60.2,1 -278,I7N3G6G,278,1,no description,60.2,1 -279,I7N3G6G,279,1,no description,60.2,1 -280,I7N3G6G,280,1,no description,60.2,1 -281,I7N3G6G,281,1,no description,60.2,1 -282,I7N3G6G,282,1,no description,60.2,1 -283,I7N3G6G,283,1,no description,60.2,1 -284,I7N3G6G,284,1,no description,60.2,1 -285,I7N3G6G,285,1,no description,60.2,1 -286,I7N3G6G,286,1,no description,60.2,1 -287,I7N3G6G,287,1,no description,60.2,1 -288,I7N3G6G,288,1,no description,60.2,1 -289,I7N3G6G,289,1,no description,60.2,1 -290,I7N3G6G,290,1,no description,60.2,1 -291,I7N3G6G,291,1,no description,60.2,1 -292,I7N3G6G,292,1,no description,60.2,1 -293,I7N3G6G,293,1,no description,60.2,1 -294,I7N3G6G,294,1,no description,60.2,1 -295,I7N3G6G,295,1,no description,60.2,1 -296,I7N3G6G,296,1,no description,60.2,1 -297,I7N3G6G,297,1,no description,60.2,1 -298,I7N3G6G,298,1,no description,60.2,1 -299,I7N3G6G,299,1,no description,60.2,1 -300,I7N3G6G,300,1,no description,60.2,1 -301,I7N3G6G,301,1,no description,60.2,1 -302,I7N3G6G,302,1,no description,60.2,1 -303,I7N3G6G,303,1,no description,60.2,1 -304,I7N3G6G,304,1,no description,60.2,1 -305,I7N3G6G,305,1,no description,60.2,1 -306,I7N3G6G,306,1,no description,60.2,1 -307,I7N3G6G,307,1,no description,60.2,1 -308,I7N3G6G,308,1,no description,60.2,1 -309,I7N3G6G,309,1,no description,60.2,1 -310,I7N3G6G,310,1,no description,60.2,1 -311,I7N3G6G,311,1,no description,60.2,1 -312,I7N3G6G,312,1,no description,60.2,1 -313,I7N3G6G,313,1,no description,60.2,1 -314,I7N3G6G,314,1,no description,60.2,1 -315,I7N3G6G,315,1,no description,60.2,1 -316,I7N3G6G,316,1,no description,60.2,1 -317,I7N3G6G,317,1,no description,60.2,1 -318,I7N3G6G,318,1,no description,60.2,1 -319,I7N3G6G,319,1,no description,60.2,1 -320,I7N3G6G,320,1,no description,60.2,1 -321,I7N3G6G,321,1,no description,60.2,1 -322,I7N3G6G,322,1,no description,60.2,1 -323,I7N3G6G,323,1,no description,60.2,1 -324,I7N3G6G,324,1,no description,60.2,1 -325,I7N3G6G,325,1,no description,60.2,1 -326,I7N3G6G,326,1,no description,60.2,1 -327,I7N3G6G,327,1,no description,60.2,1 -328,I7N3G6G,328,1,no description,60.2,1 -329,I7N3G6G,329,1,no description,60.2,1 -330,I7N3G6G,330,1,no description,60.2,1 -331,I7N3G6G,331,1,no description,60.2,1 -332,I7N3G6G,332,1,no description,60.2,1 -333,I7N3G6G,333,1,no description,60.2,1 -334,I7N3G6G,334,1,no description,60.2,1 -335,I7N3G6G,335,1,no description,60.2,1 -336,I7N3G6G,336,1,no description,60.2,1 -337,I7N3G6G,337,1,no description,60.2,1 -338,I7N3G6G,338,1,no description,60.2,1 -339,I7N3G6G,339,1,no description,60.2,1 -340,I7N3G6G,340,1,no description,60.2,1 -341,I7N3G6G,341,1,no description,60.2,1 -342,I7N3G6G,342,1,no description,60.2,1 -343,I7N3G6G,343,1,no description,60.2,1 -344,I7N3G6G,344,1,no description,60.2,1 -345,I7N3G6G,345,1,no description,60.2,1 -346,I7N3G6G,346,1,no description,60.2,1 -347,I7N3G6G,347,1,no description,60.2,1 -348,I7N3G6G,348,1,no description,60.2,1 -349,I7N3G6G,349,1,no description,60.2,1 -350,I7N3G6G,350,1,no description,60.2,1 -351,I7N3G6G,351,1,no description,60.2,1 -352,I7N3G6G,352,1,no description,60.2,1 -353,I7N3G6G,353,1,no description,60.2,1 -354,I7N3G6G,354,1,no description,60.2,1 -355,I7N3G6G,355,1,no description,60.2,1 -356,I7N3G6G,356,1,no description,60.2,1 -357,I7N3G6G,357,1,no description,60.2,1 -358,I7N3G6G,358,1,no description,60.2,1 -359,I7N3G6G,359,1,no description,60.2,1 -360,I7N3G6G,360,1,no description,60.2,1 -361,I7N3G6G,361,1,no description,60.2,1 -362,I7N3G6G,362,1,no description,60.2,1 -363,I7N3G6G,363,1,no description,60.2,1 -364,I7N3G6G,364,1,no description,60.2,1 -365,I7N3G6G,365,1,no description,60.2,1 -366,I7N3G6G,366,1,no description,60.2,1 -367,I7N3G6G,367,1,no description,60.2,1 -368,I7N3G6G,368,1,no description,60.2,1 -369,I7N3G6G,369,1,no description,60.2,1 -370,I7N3G6G,370,1,no description,60.2,1 -371,I7N3G6G,371,1,no description,60.2,1 -372,I7N3G6G,372,1,no description,60.2,1 -373,I7N3G6G,373,1,no description,60.2,1 -374,I7N3G6G,374,1,no description,60.2,1 -375,I7N3G6G,375,1,no description,60.2,1 -376,I7N3G6G,376,1,no description,60.2,1 -377,I7N3G6G,377,1,no description,60.2,1 -378,I7N3G6G,378,1,no description,60.2,1 -379,I7N3G6G,379,1,no description,60.2,1 -380,I7N3G6G,380,1,no description,60.2,1 -381,I7N3G6G,381,1,no description,60.2,1 -382,I7N3G6G,382,1,no description,60.2,1 -383,I7N3G6G,383,1,no description,60.2,1 -384,I7N3G6G,384,1,no description,60.2,1 -385,I7N3G6G,385,1,no description,60.2,1 -386,I7N3G6G,386,1,no description,60.2,1 -387,I7N3G6G,387,1,no description,60.2,1 -388,I7N3G6G,388,1,no description,60.2,1 -389,I7N3G6G,389,1,no description,60.2,1 -390,I7N3G6G,390,1,no description,60.2,1 -391,I7N3G6G,391,1,no description,60.2,1 -392,I7N3G6G,392,1,no description,60.2,1 -393,I7N3G6G,393,1,no description,60.2,1 -394,I7N3G6G,394,1,no description,60.2,1 -395,I7N3G6G,395,1,no description,60.2,1 -396,I7N3G6G,396,1,no description,60.2,1 -397,I7N3G6G,397,1,no description,60.2,1 -398,I7N3G6G,398,1,no description,60.2,1 -399,I7N3G6G,399,1,no description,60.2,1 -400,I7N3G6G,400,1,no description,60.2,1 -401,I7N3G6G,401,1,no description,60.2,1 -402,I7N3G6G,402,1,no description,60.2,1 -403,I7N3G6G,403,1,no description,60.2,1 -404,I7N3G6G,404,1,no description,60.2,1 -405,I7N3G6G,405,1,no description,60.2,1 -406,I7N3G6G,406,1,no description,60.2,1 -407,I7N3G6G,407,1,no description,60.2,1 -408,I7N3G6G,408,1,no description,60.2,1 -409,I7N3G6G,409,1,no description,60.2,1 -410,I7N3G6G,410,1,no description,60.2,1 -411,I7N3G6G,411,1,no description,60.2,1 -412,I7N3G6G,412,1,no description,60.2,1 -413,I7N3G6G,413,1,no description,60.2,1 -414,I7N3G6G,414,1,no description,60.2,1 -415,I7N3G6G,415,1,no description,60.2,1 -416,I7N3G6G,416,1,no description,60.2,1 -417,I7N3G6G,417,1,no description,60.2,1 -418,I7N3G6G,418,1,no description,60.2,1 -419,I7N3G6G,419,1,no description,60.2,1 -420,I7N3G6G,420,1,no description,60.2,1 -421,I7N3G6G,421,1,no description,60.2,1 -422,I7N3G6G,422,1,no description,60.2,1 -423,I7N3G6G,423,1,no description,60.2,1 -424,I7N3G6G,424,1,no description,60.2,1 -425,I7N3G6G,425,1,no description,60.2,1 -426,I7N3G6G,426,1,no description,60.2,1 -427,I7N3G6G,427,1,no description,60.2,1 -428,I7N3G6G,428,1,no description,60.2,1 -429,I7N3G6G,429,1,no description,60.2,1 -430,I7N3G6G,430,1,no description,60.2,1 -431,I7N3G6G,431,1,no description,60.2,1 -432,I7N3G6G,432,1,no description,60.2,1 -433,I7N3G6G,433,1,no description,60.2,1 -434,I7N3G6G,434,1,no description,60.2,1 -435,I7N3G6G,435,1,no description,60.2,1 -436,I7N3G6G,436,1,no description,60.2,1 -437,I7N3G6G,437,1,no description,60.2,1 -438,I7N3G6G,438,1,no description,60.2,1 -439,I7N3G6G,439,1,no description,60.2,1 -440,I7N3G6G,440,1,no description,60.2,1 -441,I7N3G6G,441,1,no description,60.2,1 -442,I7N3G6G,442,1,no description,60.2,1 -443,I7N3G6G,443,1,no description,60.2,1 -444,I7N3G6G,444,1,no description,60.2,1 -445,I7N3G6G,445,1,no description,60.2,1 -446,I7N3G6G,446,1,no description,60.2,1 -447,I7N3G6G,447,1,no description,60.2,1 -448,I7N3G6G,448,1,no description,60.2,1 -449,I7N3G6G,449,1,no description,60.2,1 -450,I7N3G6G,450,1,no description,60.2,1 -451,I7N3G6G,451,1,no description,60.2,1 -452,I7N3G6G,452,1,no description,60.2,1 -453,I7N3G6G,453,1,no description,60.2,1 -454,I7N3G6G,454,1,no description,60.2,1 -455,I7N3G6G,455,1,no description,60.2,1 -456,I7N3G6G,456,1,no description,60.2,1 -457,I7N3G6G,457,1,no description,60.2,1 -458,I7N3G6G,458,1,no description,60.2,1 -459,I7N3G6G,459,1,no description,60.2,1 -460,I7N3G6G,460,1,no description,60.2,1 -461,I7N3G6G,461,1,no description,60.2,1 -462,I7N3G6G,462,1,no description,60.2,1 -463,I7N3G6G,463,1,no description,60.2,1 -464,I7N3G6G,464,1,no description,60.2,1 -465,I7N3G6G,465,1,no description,60.2,1 -466,I7N3G6G,466,1,no description,60.2,1 -467,I7N3G6G,467,1,no description,60.2,1 -468,I7N3G6G,468,1,no description,60.2,1 -469,I7N3G6G,469,1,no description,60.2,1 -470,I7N3G6G,470,1,no description,60.2,1 -471,I7N3G6G,471,1,no description,60.2,1 -472,I7N3G6G,472,1,no description,60.2,1 -473,I7N3G6G,473,1,no description,60.2,1 -474,I7N3G6G,474,1,no description,60.2,1 -475,I7N3G6G,475,1,no description,60.2,1 -476,I7N3G6G,476,1,no description,60.2,1 -477,I7N3G6G,477,1,no description,60.2,1 -478,I7N3G6G,478,1,no description,60.2,1 -479,I7N3G6G,479,1,no description,60.2,1 -480,I7N3G6G,480,1,no description,60.2,1 -481,I7N3G6G,481,1,no description,60.2,1 -482,I7N3G6G,482,1,no description,60.2,1 -483,I7N3G6G,483,1,no description,60.2,1 -484,I7N3G6G,484,1,no description,60.2,1 -485,I7N3G6G,485,1,no description,60.2,1 -486,I7N3G6G,486,1,no description,60.2,1 -487,I7N3G6G,487,1,no description,60.2,1 -488,I7N3G6G,488,1,no description,60.2,1 -489,I7N3G6G,489,1,no description,60.2,1 -490,I7N3G6G,490,1,no description,60.2,1 -491,I7N3G6G,491,1,no description,60.2,1 -492,I7N3G6G,492,1,no description,60.2,1 -493,I7N3G6G,493,1,no description,60.2,1 -494,I7N3G6G,494,1,no description,60.2,1 -495,I7N3G6G,495,1,no description,60.2,1 -496,I7N3G6G,496,1,no description,60.2,1 -497,I7N3G6G,497,1,no description,60.2,1 -498,I7N3G6G,498,1,no description,60.2,1 -499,I7N3G6G,499,1,no description,60.2,1 -500,I7N3G6G,500,1,no description,60.2,1 -501,I7N3G6G,501,1,no description,60.2,1 -502,I7N3G6G,502,1,no description,60.2,1 -503,I7N3G6G,503,1,no description,60.2,1 -504,I7N3G6G,504,1,no description,60.2,1 -505,I7N3G6G,505,1,no description,60.2,1 -506,I7N3G6G,506,1,no description,60.2,1 -507,I7N3G6G,507,1,no description,60.2,1 -508,I7N3G6G,508,1,no description,60.2,1 -509,I7N3G6G,509,1,no description,60.2,1 -510,I7N3G6G,510,1,no description,60.2,1 -511,I7N3G6G,511,1,no description,60.2,1 -512,I7N3G6G,512,1,no description,60.2,1 -513,I7N3G6G,513,1,no description,60.2,1 -514,I7N3G6G,514,1,no description,60.2,1 -515,I7N3G6G,515,1,no description,60.2,1 -516,I7N3G6G,516,1,no description,60.2,1 -517,I7N3G6G,517,1,no description,60.2,1 -518,I7N3G6G,518,1,no description,60.2,1 -519,I7N3G6G,519,1,no description,60.2,1 -520,I7N3G6G,520,1,no description,60.2,1 -521,I7N3G6G,521,1,no description,60.2,1 -522,I7N3G6G,522,1,no description,60.2,1 -523,I7N3G6G,523,1,no description,60.2,1 -524,I7N3G6G,524,1,no description,60.2,1 -525,I7N3G6G,525,1,no description,60.2,1 -526,I7N3G6G,526,1,no description,60.2,1 -527,I7N3G6G,527,1,no description,60.2,1 -528,I7N3G6G,528,1,no description,60.2,1 -529,I7N3G6G,529,1,no description,60.2,1 -530,I7N3G6G,530,1,no description,60.2,1 -531,I7N3G6G,531,1,no description,60.2,1 -532,I7N3G6G,532,1,no description,60.2,1 -533,I7N3G6G,533,1,no description,60.2,1 -534,I7N3G6G,534,1,no description,60.2,1 -535,I7N3G6G,535,1,no description,60.2,1 -536,I7N3G6G,536,1,no description,60.2,1 -537,I7N3G6G,537,1,no description,60.2,1 -538,I7N3G6G,538,1,no description,60.2,1 -539,I7N3G6G,539,1,no description,60.2,1 -540,I7N3G6G,540,1,no description,60.2,1 -541,I7N3G6G,541,1,no description,60.2,1 -542,I7N3G6G,542,1,no description,60.2,1 -543,I7N3G6G,543,1,no description,60.2,1 -544,I7N3G6G,544,1,no description,60.2,1 -545,I7N3G6G,545,1,no description,60.2,1 -546,I7N3G6G,546,1,no description,60.2,1 -547,I7N3G6G,547,1,no description,60.2,1 -548,I7N3G6G,548,1,no description,60.2,1 -549,I7N3G6G,549,1,no description,60.2,1 -550,I7N3G6G,550,1,no description,60.2,1 -551,I7N3G6G,551,1,no description,60.2,1 -552,I7N3G6G,552,1,no description,60.2,1 -553,I7N3G6G,553,1,no description,60.2,1 -554,I7N3G6G,554,1,no description,60.2,1 -555,I7N3G6G,555,1,no description,60.2,1 -556,I7N3G6G,556,1,no description,60.2,1 -557,I7N3G6G,557,1,no description,60.2,1 -558,I7N3G6G,558,1,no description,60.2,1 -559,I7N3G6G,559,1,no description,60.2,1 -560,I7N3G6G,560,1,no description,60.2,1 -561,I7N3G6G,561,1,no description,60.2,1 -562,I7N3G6G,562,1,no description,60.2,1 -563,I7N3G6G,563,1,no description,60.2,1 -564,I7N3G6G,564,1,no description,60.2,1 -565,I7N3G6G,565,1,no description,60.2,1 -566,I7N3G6G,566,1,no description,60.2,1 -567,I7N3G6G,567,1,no description,60.2,1 -568,I7N3G6G,568,1,no description,60.2,1 -569,I7N3G6G,569,1,no description,60.2,1 -570,I7N3G6G,570,1,no description,60.2,1 -571,I7N3G6G,571,1,no description,60.2,1 -572,I7N3G6G,572,1,no description,60.2,1 -573,I7N3G6G,573,1,no description,60.2,1 -574,I7N3G6G,574,1,no description,60.2,1 -575,I7N3G6G,575,1,no description,60.2,1 -576,I7N3G6G,576,1,no description,60.2,1 -577,I7N3G6G,577,1,no description,60.2,1 -578,I7N3G6G,578,1,no description,60.2,1 -579,I7N3G6G,579,1,no description,60.2,1 -580,I7N3G6G,580,1,no description,60.2,1 -581,I7N3G6G,581,1,no description,60.2,1 -582,I7N3G6G,582,1,no description,60.2,1 -583,I7N3G6G,583,1,no description,60.2,1 -584,I7N3G6G,584,1,no description,60.2,1 -585,I7N3G6G,585,1,no description,60.2,1 -586,I7N3G6G,586,1,no description,60.2,1 -587,I7N3G6G,587,1,no description,60.2,1 -588,I7N3G6G,588,1,no description,60.2,1 -589,I7N3G6G,589,1,no description,60.2,1 -590,I7N3G6G,590,1,no description,60.2,1 -591,I7N3G6G,591,1,no description,60.2,1 -592,I7N3G6G,592,1,no description,60.2,1 -593,I7N3G6G,593,1,no description,60.2,1 -594,I7N3G6G,594,1,no description,60.2,1 -595,I7N3G6G,595,1,no description,60.2,1 -596,I7N3G6G,596,1,no description,60.2,1 -597,I7N3G6G,597,1,no description,60.2,1 -598,I7N3G6G,598,1,no description,60.2,1 -599,I7N3G6G,599,1,no description,60.2,1 -600,I7N3G6G,600,1,no description,60.2,1 -601,I7N3G6G,601,1,no description,60.2,1 -602,I7N3G6G,602,1,no description,60.2,1 -603,I7N3G6G,603,1,no description,60.2,1 -604,I7N3G6G,604,1,no description,60.2,1 -605,I7N3G6G,605,1,no description,60.2,1 -606,I7N3G6G,606,1,no description,60.2,1 -607,I7N3G6G,607,1,no description,60.2,1 -608,I7N3G6G,608,1,no description,60.2,1 -609,I7N3G6G,609,1,no description,60.2,1 -610,I7N3G6G,610,1,no description,60.2,1 -611,I7N3G6G,611,1,no description,60.2,1 -612,I7N3G6G,612,1,no description,60.2,1 -613,I7N3G6G,613,1,no description,60.2,1 -614,I7N3G6G,614,1,no description,60.2,1 -615,I7N3G6G,615,1,no description,60.2,1 -616,I7N3G6G,616,1,no description,60.2,1 -617,I7N3G6G,617,1,no description,60.2,1 -618,I7N3G6G,618,1,no description,60.2,1 -619,I7N3G6G,619,1,no description,60.2,1 -620,I7N3G6G,620,1,no description,60.2,1 -621,I7N3G6G,621,1,no description,60.2,1 -622,I7N3G6G,622,1,no description,60.2,1 -623,I7N3G6G,623,1,no description,60.2,1 -624,I7N3G6G,624,1,no description,60.2,1 -625,I7N3G6G,625,1,no description,60.2,1 -626,I7N3G6G,626,1,no description,60.2,1 -627,I7N3G6G,627,1,no description,60.2,1 -628,I7N3G6G,628,1,no description,60.2,1 -629,I7N3G6G,629,1,no description,60.2,1 -630,I7N3G6G,630,1,no description,60.2,1 -631,I7N3G6G,631,1,no description,60.2,1 -632,I7N3G6G,632,1,no description,60.2,1 -633,I7N3G6G,633,1,no description,60.2,1 -634,I7N3G6G,634,1,no description,60.2,1 -635,I7N3G6G,635,1,no description,60.2,1 -636,I7N3G6G,636,1,no description,60.2,1 -637,I7N3G6G,637,1,no description,60.2,1 -638,I7N3G6G,638,1,no description,60.2,1 -639,I7N3G6G,639,1,no description,60.2,1 -640,I7N3G6G,640,1,no description,60.2,1 -641,I7N3G6G,641,1,no description,60.2,1 -642,I7N3G6G,642,1,no description,60.2,1 -643,I7N3G6G,643,1,no description,60.2,1 -644,I7N3G6G,644,1,no description,60.2,1 -645,I7N3G6G,645,1,no description,60.2,1 -646,I7N3G6G,646,1,no description,60.2,1 -647,I7N3G6G,647,1,no description,60.2,1 -648,I7N3G6G,648,1,no description,60.2,1 -649,I7N3G6G,649,1,no description,60.2,1 -650,I7N3G6G,650,1,no description,60.2,1 -651,I7N3G6G,651,1,no description,60.2,1 -652,I7N3G6G,652,1,no description,60.2,1 -653,I7N3G6G,653,1,no description,60.2,1 -654,I7N3G6G,654,1,no description,60.2,1 -655,I7N3G6G,655,1,no description,60.2,1 -656,I7N3G6G,656,1,no description,60.2,1 -657,I7N3G6G,657,1,no description,60.2,1 -658,I7N3G6G,658,1,no description,60.2,1 -659,I7N3G6G,659,1,no description,60.2,1 -660,I7N3G6G,660,1,no description,60.2,1 -661,I7N3G6G,661,1,no description,60.2,1 -662,I7N3G6G,662,1,no description,60.2,1 -663,I7N3G6G,663,1,no description,60.2,1 -664,I7N3G6G,664,1,no description,60.2,1 -665,I7N3G6G,665,1,no description,60.2,1 -666,I7N3G6G,666,1,no description,60.2,1 -667,I7N3G6G,667,1,no description,60.2,1 -668,I7N3G6G,668,1,no description,60.2,1 -669,I7N3G6G,669,1,no description,60.2,1 -670,I7N3G6G,670,1,no description,60.2,1 -671,I7N3G6G,671,1,no description,60.2,1 -672,I7N3G6G,672,1,no description,60.2,1 -673,I7N3G6G,673,1,no description,60.2,1 -674,I7N3G6G,674,1,no description,60.2,1 -675,I7N3G6G,675,1,no description,60.2,1 -676,I7N3G6G,676,1,no description,60.2,1 -677,I7N3G6G,677,1,no description,60.2,1 -678,I7N3G6G,678,1,no description,60.2,1 -679,I7N3G6G,679,1,no description,60.2,1 -680,I7N3G6G,680,1,no description,60.2,1 -681,I7N3G6G,681,1,no description,60.2,1 -682,I7N3G6G,682,1,no description,60.2,1 -683,I7N3G6G,683,1,no description,60.2,1 -684,I7N3G6G,684,1,no description,60.2,1 -685,I7N3G6G,685,1,no description,60.2,1 -686,I7N3G6G,686,1,no description,60.2,1 -687,I7N3G6G,687,1,no description,60.2,1 -688,I7N3G6G,688,1,no description,60.2,1 -689,I7N3G6G,689,1,no description,60.2,1 -690,I7N3G6G,690,1,no description,60.2,1 -691,I7N3G6G,691,1,no description,60.2,1 -692,I7N3G6G,692,1,no description,60.2,1 -693,I7N3G6G,693,1,no description,60.2,1 -694,I7N3G6G,694,1,no description,60.2,1 -695,I7N3G6G,695,1,no description,60.2,1 -696,I7N3G6G,696,1,no description,60.2,1 -697,I7N3G6G,697,1,no description,60.2,1 -698,I7N3G6G,698,1,no description,60.2,1 -699,I7N3G6G,699,1,no description,60.2,1 -700,I7N3G6G,700,1,no description,60.2,1 -701,I7N3G6G,701,1,no description,60.2,1 -702,I7N3G6G,702,1,no description,60.2,1 -703,I7N3G6G,703,1,no description,60.2,1 -704,I7N3G6G,704,1,no description,60.2,1 -705,I7N3G6G,705,1,no description,60.2,1 -706,I7N3G6G,706,1,no description,60.2,1 -707,I7N3G6G,707,1,no description,60.2,1 -708,I7N3G6G,708,1,no description,60.2,1 -709,I7N3G6G,709,1,no description,60.2,1 -710,I7N3G6G,710,1,no description,60.2,1 -711,I7N3G6G,711,1,no description,60.2,1 -712,I7N3G6G,712,1,no description,60.2,1 -713,I7N3G6G,713,1,no description,60.2,1 -714,I7N3G6G,714,1,no description,60.2,1 -715,I7N3G6G,715,1,no description,60.2,1 -716,I7N3G6G,716,1,no description,60.2,1 -717,I7N3G6G,717,1,no description,60.2,1 -718,I7N3G6G,718,1,no description,60.2,1 -719,I7N3G6G,719,1,no description,60.2,1 -720,I7N3G6G,720,1,no description,60.2,1 -721,I7N3G6G,721,1,no description,60.2,1 -722,I7N3G6G,722,1,no description,60.2,1 -723,I7N3G6G,723,1,no description,60.2,1 -724,I7N3G6G,724,1,no description,60.2,1 -725,I7N3G6G,725,1,no description,60.2,1 -726,I7N3G6G,726,1,no description,60.2,1 -727,I7N3G6G,727,1,no description,60.2,1 -728,I7N3G6G,728,1,no description,60.2,1 -729,I7N3G6G,729,1,no description,60.2,1 -730,I7N3G6G,730,1,no description,60.2,1 -731,I7N3G6G,731,1,no description,60.2,1 -732,I7N3G6G,732,1,no description,60.2,1 -733,I7N3G6G,733,1,no description,60.2,1 -734,I7N3G6G,734,1,no description,60.2,1 -735,I7N3G6G,735,1,no description,60.2,1 -736,I7N3G6G,736,1,no description,60.2,1 -737,I7N3G6G,737,1,no description,60.2,1 -738,I7N3G6G,738,1,no description,60.2,1 -739,I7N3G6G,739,1,no description,60.2,1 -740,I7N3G6G,740,1,no description,60.2,1 -741,I7N3G6G,741,1,no description,60.2,1 -742,I7N3G6G,742,1,no description,60.2,1 -743,I7N3G6G,743,1,no description,60.2,1 -744,I7N3G6G,744,1,no description,60.2,1 -745,I7N3G6G,745,1,no description,60.2,1 -746,I7N3G6G,746,1,no description,60.2,1 -747,I7N3G6G,747,1,no description,60.2,1 -748,I7N3G6G,748,1,no description,60.2,1 -749,I7N3G6G,749,1,no description,60.2,1 -750,I7N3G6G,750,1,no description,60.2,1 -751,I7N3G6G,751,1,no description,60.2,1 -752,I7N3G6G,752,1,no description,60.2,1 -753,I7N3G6G,753,1,no description,60.2,1 -754,I7N3G6G,754,1,no description,60.2,1 -755,I7N3G6G,755,1,no description,60.2,1 -756,I7N3G6G,756,1,no description,60.2,1 -757,I7N3G6G,757,1,no description,60.2,1 -758,I7N3G6G,758,1,no description,60.2,1 -759,I7N3G6G,759,1,no description,60.2,1 -760,I7N3G6G,760,1,no description,60.2,1 -761,I7N3G6G,761,1,no description,60.2,1 -762,I7N3G6G,762,1,no description,60.2,1 -763,I7N3G6G,763,1,no description,60.2,1 -764,I7N3G6G,764,1,no description,60.2,1 -765,I7N3G6G,765,1,no description,60.2,1 -766,I7N3G6G,766,1,no description,60.2,1 -767,I7N3G6G,767,1,no description,60.2,1 -768,I7N3G6G,768,1,no description,60.2,1 -769,I7N3G6G,769,1,no description,60.2,1 -770,I7N3G6G,770,1,no description,60.2,1 -771,I7N3G6G,771,1,no description,60.2,1 -772,I7N3G6G,772,1,no description,60.2,1 -773,I7N3G6G,773,1,no description,60.2,1 -774,I7N3G6G,774,1,no description,60.2,1 -775,I7N3G6G,775,1,no description,60.2,1 -776,I7N3G6G,776,1,no description,60.2,1 -777,I7N3G6G,777,1,no description,60.2,1 -778,I7N3G6G,778,1,no description,60.2,1 -779,I7N3G6G,779,1,no description,60.2,1 -780,I7N3G6G,780,1,no description,60.2,1 -781,I7N3G6G,781,1,no description,60.2,1 -782,I7N3G6G,782,1,no description,60.2,1 -783,I7N3G6G,783,1,no description,60.2,1 -784,I7N3G6G,784,1,no description,60.2,1 -785,I7N3G6G,785,1,no description,60.2,1 -786,I7N3G6G,786,1,no description,60.2,1 -787,I7N3G6G,787,1,no description,60.2,1 -788,I7N3G6G,788,1,no description,60.2,1 -789,I7N3G6G,789,1,no description,60.2,1 -790,I7N3G6G,790,1,no description,60.2,1 -791,I7N3G6G,791,1,no description,60.2,1 -792,I7N3G6G,792,1,no description,60.2,1 -793,I7N3G6G,793,1,no description,60.2,1 -794,I7N3G6G,794,1,no description,60.2,1 -795,I7N3G6G,795,1,no description,60.2,1 -796,I7N3G6G,796,1,no description,60.2,1 -797,I7N3G6G,797,1,no description,60.2,1 -798,I7N3G6G,798,1,no description,60.2,1 -799,I7N3G6G,799,1,no description,60.2,1 -800,I7N3G6G,800,1,no description,60.2,1 -801,I7N3G6G,801,1,no description,60.2,1 -802,I7N3G6G,802,1,no description,60.2,1 -803,I7N3G6G,803,1,no description,60.2,1 -804,I7N3G6G,804,1,no description,60.2,1 -805,I7N3G6G,805,1,no description,60.2,1 -806,I7N3G6G,806,1,no description,60.2,1 -807,I7N3G6G,807,1,no description,60.2,1 -808,I7N3G6G,808,1,no description,60.2,1 -809,I7N3G6G,809,1,no description,60.2,1 -810,I7N3G6G,810,1,no description,60.2,1 -811,I7N3G6G,811,1,no description,60.2,1 -812,I7N3G6G,812,1,no description,60.2,1 -813,I7N3G6G,813,1,no description,60.2,1 -814,I7N3G6G,814,1,no description,60.2,1 -815,I7N3G6G,815,1,no description,60.2,1 -816,I7N3G6G,816,1,no description,60.2,1 -817,I7N3G6G,817,1,no description,60.2,1 -818,I7N3G6G,818,1,no description,60.2,1 -819,I7N3G6G,819,1,no description,60.2,1 -820,I7N3G6G,820,1,no description,60.2,1 -821,I7N3G6G,821,1,no description,60.2,1 -822,I7N3G6G,822,1,no description,60.2,1 -823,I7N3G6G,823,1,no description,60.2,1 -824,I7N3G6G,824,1,no description,60.2,1 -825,I7N3G6G,825,1,no description,60.2,1 -826,I7N3G6G,826,1,no description,60.2,1 -827,I7N3G6G,827,1,no description,60.2,1 -828,I7N3G6G,828,1,no description,60.2,1 -829,I7N3G6G,829,1,no description,60.2,1 -830,I7N3G6G,830,1,no description,60.2,1 -831,I7N3G6G,831,1,no description,60.2,1 -832,I7N3G6G,832,1,no description,60.2,1 -833,I7N3G6G,833,1,no description,60.2,1 -834,I7N3G6G,834,1,no description,60.2,1 -835,I7N3G6G,835,1,no description,60.2,1 -836,I7N3G6G,836,1,no description,60.2,1 -837,I7N3G6G,837,1,no description,60.2,1 -838,I7N3G6G,838,1,no description,60.2,1 -839,I7N3G6G,839,1,no description,60.2,1 -840,I7N3G6G,840,1,no description,60.2,1 -841,I7N3G6G,841,1,no description,60.2,1 -842,I7N3G6G,842,1,no description,60.2,1 -843,I7N3G6G,843,1,no description,60.2,1 -844,I7N3G6G,844,1,no description,60.2,1 -845,I7N3G6G,845,1,no description,60.2,1 -846,I7N3G6G,846,1,no description,60.2,1 -847,I7N3G6G,847,1,no description,60.2,1 -848,I7N3G6G,848,1,no description,60.2,1 -849,I7N3G6G,849,1,no description,60.2,1 -850,I7N3G6G,850,1,no description,60.2,1 -851,I7N3G6G,851,1,no description,60.2,1 -852,I7N3G6G,852,1,no description,60.2,1 -853,I7N3G6G,853,1,no description,60.2,1 -854,I7N3G6G,854,1,no description,60.2,1 -855,I7N3G6G,855,1,no description,60.2,1 -856,I7N3G6G,856,1,no description,60.2,1 -857,I7N3G6G,857,1,no description,60.2,1 -858,I7N3G6G,858,1,no description,60.2,1 -859,I7N3G6G,859,1,no description,60.2,1 -860,I7N3G6G,860,1,no description,60.2,1 -861,I7N3G6G,861,1,no description,60.2,1 -862,I7N3G6G,862,1,no description,60.2,1 -863,I7N3G6G,863,1,no description,60.2,1 -864,I7N3G6G,864,1,no description,60.2,1 -865,I7N3G6G,865,1,no description,60.2,1 -866,I7N3G6G,866,1,no description,60.2,1 -867,I7N3G6G,867,1,no description,60.2,1 -868,I7N3G6G,868,1,no description,60.2,1 -869,I7N3G6G,869,1,no description,60.2,1 -870,I7N3G6G,870,1,no description,60.2,1 -871,I7N3G6G,871,1,no description,60.2,1 -872,I7N3G6G,872,1,no description,60.2,1 -873,I7N3G6G,873,1,no description,60.2,1 -874,I7N3G6G,874,1,no description,60.2,1 -875,I7N3G6G,875,1,no description,60.2,1 -876,I7N3G6G,876,1,no description,60.2,1 -877,I7N3G6G,877,1,no description,60.2,1 -878,I7N3G6G,878,1,no description,60.2,1 -879,I7N3G6G,879,1,no description,60.2,1 -880,I7N3G6G,880,1,no description,60.2,1 -881,I7N3G6G,881,1,no description,60.2,1 -882,I7N3G6G,882,1,no description,60.2,1 -883,I7N3G6G,883,1,no description,60.2,1 -884,I7N3G6G,884,1,no description,60.2,1 -885,I7N3G6G,885,1,no description,60.2,1 -886,I7N3G6G,886,1,no description,60.2,1 -887,I7N3G6G,887,1,no description,60.2,1 -888,I7N3G6G,888,1,no description,60.2,1 -889,I7N3G6G,889,1,no description,60.2,1 -890,I7N3G6G,890,1,no description,60.2,1 -891,I7N3G6G,891,1,no description,60.2,1 -892,I7N3G6G,892,1,no description,60.2,1 -893,I7N3G6G,893,1,no description,60.2,1 -894,I7N3G6G,894,1,no description,60.2,1 -895,I7N3G6G,895,1,no description,60.2,1 -896,I7N3G6G,896,1,no description,60.2,1 -897,I7N3G6G,897,1,no description,60.2,1 -898,I7N3G6G,898,1,no description,60.2,1 -899,I7N3G6G,899,1,no description,60.2,1 -900,I7N3G6G,900,1,no description,60.2,1 -901,I7N3G6G,901,1,no description,60.2,1 -902,I7N3G6G,902,1,no description,60.2,1 -903,I7N3G6G,903,1,no description,60.2,1 -904,I7N3G6G,904,1,no description,60.2,1 -905,I7N3G6G,905,1,no description,60.2,1 -906,I7N3G6G,906,1,no description,60.2,1 -907,I7N3G6G,907,1,no description,60.2,1 -908,I7N3G6G,908,1,no description,60.2,1 -909,I7N3G6G,909,1,no description,60.2,1 -910,I7N3G6G,910,1,no description,60.2,1 -911,I7N3G6G,911,1,no description,60.2,1 -912,I7N3G6G,912,1,no description,60.2,1 -913,I7N3G6G,913,1,no description,60.2,1 -914,I7N3G6G,914,1,no description,60.2,1 -915,I7N3G6G,915,1,no description,60.2,1 -916,I7N3G6G,916,1,no description,60.2,1 -917,I7N3G6G,917,1,no description,60.2,1 -918,I7N3G6G,918,1,no description,60.2,1 -919,I7N3G6G,919,1,no description,60.2,1 -920,I7N3G6G,920,1,no description,60.2,1 -921,I7N3G6G,921,1,no description,60.2,1 -922,I7N3G6G,922,1,no description,60.2,1 -923,I7N3G6G,923,1,no description,60.2,1 -924,I7N3G6G,924,1,no description,60.2,1 -925,I7N3G6G,925,1,no description,60.2,1 -926,I7N3G6G,926,1,no description,60.2,1 -927,I7N3G6G,927,1,no description,60.2,1 -928,I7N3G6G,928,1,no description,60.2,1 -929,I7N3G6G,929,1,no description,60.2,1 -930,I7N3G6G,930,1,no description,60.2,1 -931,I7N3G6G,931,1,no description,60.2,1 -932,I7N3G6G,932,1,no description,60.2,1 -933,I7N3G6G,933,1,no description,60.2,1 -934,I7N3G6G,934,1,no description,60.2,1 -935,I7N3G6G,935,1,no description,60.2,1 -936,I7N3G6G,936,1,no description,60.2,1 -937,I7N3G6G,937,1,no description,60.2,1 -938,I7N3G6G,938,1,no description,60.2,1 -939,I7N3G6G,939,1,no description,60.2,1 -940,I7N3G6G,940,1,no description,60.2,1 -941,I7N3G6G,941,1,no description,60.2,1 -942,I7N3G6G,942,1,no description,60.2,1 -943,I7N3G6G,943,1,no description,60.2,1 -944,I7N3G6G,944,1,no description,60.2,1 -945,I7N3G6G,945,1,no description,60.2,1 -946,I7N3G6G,946,1,no description,60.2,1 -947,I7N3G6G,947,1,no description,60.2,1 -948,I7N3G6G,948,1,no description,60.2,1 -949,I7N3G6G,949,1,no description,60.2,1 -950,I7N3G6G,950,1,no description,60.2,1 -951,I7N3G6G,951,1,no description,60.2,1 -952,I7N3G6G,952,1,no description,60.2,1 -953,I7N3G6G,953,1,no description,60.2,1 -954,I7N3G6G,954,1,no description,60.2,1 -955,I7N3G6G,955,1,no description,60.2,1 -956,I7N3G6G,956,1,no description,60.2,1 -957,I7N3G6G,957,1,no description,60.2,1 -958,I7N3G6G,958,1,no description,60.2,1 -959,I7N3G6G,959,1,no description,60.2,1 -960,I7N3G6G,960,1,no description,60.2,1 -961,I7N3G6G,961,1,no description,60.2,1 -962,I7N3G6G,962,1,no description,60.2,1 -963,I7N3G6G,963,1,no description,60.2,1 -964,I7N3G6G,964,1,no description,60.2,1 -965,I7N3G6G,965,1,no description,60.2,1 -966,I7N3G6G,966,1,no description,60.2,1 -967,I7N3G6G,967,1,no description,60.2,1 -968,I7N3G6G,968,1,no description,60.2,1 -969,I7N3G6G,969,1,no description,60.2,1 -970,I7N3G6G,970,1,no description,60.2,1 -971,I7N3G6G,971,1,no description,60.2,1 -972,I7N3G6G,972,1,no description,60.2,1 -973,I7N3G6G,973,1,no description,60.2,1 -974,I7N3G6G,974,1,no description,60.2,1 -975,I7N3G6G,975,1,no description,60.2,1 -976,I7N3G6G,976,1,no description,60.2,1 -977,I7N3G6G,977,1,no description,60.2,1 -978,I7N3G6G,978,1,no description,60.2,1 -979,I7N3G6G,979,1,no description,60.2,1 -980,I7N3G6G,980,1,no description,60.2,1 -981,I7N3G6G,981,1,no description,60.2,1 -982,I7N3G6G,982,1,no description,60.2,1 -983,I7N3G6G,983,1,no description,60.2,1 -984,I7N3G6G,984,1,no description,60.2,1 -985,I7N3G6G,985,1,no description,60.2,1 -986,I7N3G6G,986,1,no description,60.2,1 -987,I7N3G6G,987,1,no description,60.2,1 -988,I7N3G6G,988,1,no description,60.2,1 -989,I7N3G6G,989,1,no description,60.2,1 -990,I7N3G6G,990,1,no description,60.2,1 -991,I7N3G6G,991,1,no description,60.2,1 -992,I7N3G6G,992,1,no description,60.2,1 -993,I7N3G6G,993,1,no description,60.2,1 -994,I7N3G6G,994,1,no description,60.2,1 -995,I7N3G6G,995,1,no description,60.2,1 -996,I7N3G6G,996,1,no description,60.2,1 -997,I7N3G6G,997,1,no description,60.2,1 -998,I7N3G6G,998,1,no description,60.2,1 -999,I7N3G6G,999,1,no description,60.2,1 -1000,I7N3G6G,1000,1,no description,60.2,1 -1001,I7N3G6G,1001,1,no description,60.2,1 -1002,I7N3G6G,1002,1,no description,60.2,1 -1003,I7N3G6G,1003,1,no description,60.2,1 -1004,I7N3G6G,1004,1,no description,60.2,1 -1005,I7N3G6G,1005,1,no description,60.2,1 -1006,I7N3G6G,1006,1,no description,60.2,1 -1007,I7N3G6G,1007,1,no description,60.2,1 -1008,I7N3G6G,1008,1,no description,60.2,1 -1009,I7N3G6G,1009,1,no description,60.2,1 -1010,I7N3G6G,1010,1,no description,60.2,1 -1011,I7N3G6G,1011,1,no description,60.2,1 -1012,I7N3G6G,1012,1,no description,60.2,1 -1013,I7N3G6G,1013,1,no description,60.2,1 -1014,I7N3G6G,1014,1,no description,60.2,1 -1015,I7N3G6G,1015,1,no description,60.2,1 -1016,I7N3G6G,1016,1,no description,60.2,1 -1017,I7N3G6G,1017,1,no description,60.2,1 -1018,I7N3G6G,1018,1,no description,60.2,1 -1019,I7N3G6G,1019,1,no description,60.2,1 -1020,I7N3G6G,1020,1,no description,60.2,1 -1021,I7N3G6G,1021,1,no description,60.2,1 -1022,I7N3G6G,1022,1,no description,60.2,1 -1023,I7N3G6G,1023,1,no description,60.2,1 -1024,I7N3G6G,1024,1,no description,60.2,1 -1025,I7N3G6G,1025,1,no description,60.2,1 -1026,I7N3G6G,1026,1,no description,60.2,1 -1027,I7N3G6G,1027,1,no description,60.2,1 -1028,I7N3G6G,1028,1,no description,60.2,1 -1029,I7N3G6G,1029,1,no description,60.2,1 -1030,I7N3G6G,1030,1,no description,60.2,1 -1031,I7N3G6G,1031,1,no description,60.2,1 -1032,I7N3G6G,1032,1,no description,60.2,1 -1033,I7N3G6G,1033,1,no description,60.2,1 -1034,I7N3G6G,1034,1,no description,60.2,1 -1035,I7N3G6G,1035,1,no description,60.2,1 -1036,I7N3G6G,1036,1,no description,60.2,1 -1037,I7N3G6G,1037,1,no description,60.2,1 -1038,I7N3G6G,1038,1,no description,60.2,1 -1039,I7N3G6G,1039,1,no description,60.2,1 -1040,I7N3G6G,1040,1,no description,60.2,1 -1041,I7N3G6G,1041,1,no description,60.2,1 -1042,I7N3G6G,1042,1,no description,60.2,1 -1043,I7N3G6G,1043,1,no description,60.2,1 -1044,I7N3G6G,1044,1,no description,60.2,1 -1045,I7N3G6G,1045,1,no description,60.2,1 -1046,I7N3G6G,1046,1,no description,60.2,1 -1047,I7N3G6G,1047,1,no description,60.2,1 -1048,I7N3G6G,1048,1,no description,60.2,1 -1049,I7N3G6G,1049,1,no description,60.2,1 -1050,I7N3G6G,1050,1,no description,60.2,1 -1051,I7N3G6G,1051,1,no description,60.2,1 -1052,I7N3G6G,1052,1,no description,60.2,1 -1053,I7N3G6G,1053,1,no description,60.2,1 -1054,I7N3G6G,1054,1,no description,60.2,1 -1055,I7N3G6G,1055,1,no description,60.2,1 -1056,I7N3G6G,1056,1,no description,60.2,1 -1057,I7N3G6G,1057,1,no description,60.2,1 -1058,I7N3G6G,1058,1,no description,60.2,1 -1059,I7N3G6G,1059,1,no description,60.2,1 -1060,I7N3G6G,1060,1,no description,60.2,1 -1061,I7N3G6G,1061,1,no description,60.2,1 -1062,I7N3G6G,1062,1,no description,60.2,1 -1063,I7N3G6G,1063,1,no description,60.2,1 -1064,I7N3G6G,1064,1,no description,60.2,1 -1065,I7N3G6G,1065,1,no description,60.2,1 -1066,I7N3G6G,1066,1,no description,60.2,1 -1067,I7N3G6G,1067,1,no description,60.2,1 -1068,I7N3G6G,1068,1,no description,60.2,1 -1069,I7N3G6G,1069,1,no description,60.2,1 -1070,I7N3G6G,1070,1,no description,60.2,1 -1071,I7N3G6G,1071,1,no description,60.2,1 -1072,I7N3G6G,1072,1,no description,60.2,1 -1073,I7N3G6G,1073,1,no description,60.2,1 -1074,I7N3G6G,1074,1,no description,60.2,1 -1075,I7N3G6G,1075,1,no description,60.2,1 -1076,I7N3G6G,1076,1,no description,60.2,1 -1077,I7N3G6G,1077,1,no description,60.2,1 -1078,I7N3G6G,1078,1,no description,60.2,1 -1079,I7N3G6G,1079,1,no description,60.2,1 -1080,I7N3G6G,1080,1,no description,60.2,1 -1081,I7N3G6G,1081,1,no description,60.2,1 -1082,I7N3G6G,1082,1,no description,60.2,1 -1083,I7N3G6G,1083,1,no description,60.2,1 -1084,I7N3G6G,1084,1,no description,60.2,1 -1085,I7N3G6G,1085,1,no description,60.2,1 -1086,I7N3G6G,1086,1,no description,60.2,1 -1087,I7N3G6G,1087,1,no description,60.2,1 -1088,I7N3G6G,1088,1,no description,60.2,1 -1089,I7N3G6G,1089,1,no description,60.2,1 -1090,I7N3G6G,1090,1,no description,60.2,1 -1091,I7N3G6G,1091,1,no description,60.2,1 -1092,I7N3G6G,1092,1,no description,60.2,1 -1093,I7N3G6G,1093,1,no description,60.2,1 -1094,I7N3G6G,1094,1,no description,60.2,1 -1095,I7N3G6G,1095,1,no description,60.2,1 -1096,I7N3G6G,1096,1,no description,60.2,1 -1097,I7N3G6G,1097,1,no description,60.2,1 -1098,I7N3G6G,1098,1,no description,60.2,1 -1099,I7N3G6G,1099,1,no description,60.2,1 -1100,I7N3G6G,1100,1,no description,60.2,1 -1101,I7N3G6G,1101,1,no description,60.2,1 -1102,I7N3G6G,1102,1,no description,60.2,1 -1103,I7N3G6G,1103,1,no description,60.2,1 -1104,I7N3G6G,1104,1,no description,60.2,1 -1105,I7N3G6G,1105,1,no description,60.2,1 -1106,I7N3G6G,1106,1,no description,60.2,1 -1107,I7N3G6G,1107,1,no description,60.2,1 -1108,I7N3G6G,1108,1,no description,60.2,1 -1109,I7N3G6G,1109,1,no description,60.2,1 -1110,I7N3G6G,1110,1,no description,60.2,1 -1111,I7N3G6G,1111,1,no description,60.2,1 -1112,I7N3G6G,1112,1,no description,60.2,1 -1113,I7N3G6G,1113,1,no description,60.2,1 -1114,I7N3G6G,1114,1,no description,60.2,1 -1115,I7N3G6G,1115,1,no description,60.2,1 -1116,I7N3G6G,1116,1,no description,60.2,1 -1117,I7N3G6G,1117,1,no description,60.2,1 -1118,I7N3G6G,1118,1,no description,60.2,1 -1119,I7N3G6G,1119,1,no description,60.2,1 -1120,I7N3G6G,1120,1,no description,60.2,1 -1121,I7N3G6G,1121,1,no description,60.2,1 -1122,I7N3G6G,1122,1,no description,60.2,1 -1123,I7N3G6G,1123,1,no description,60.2,1 -1124,I7N3G6G,1124,1,no description,60.2,1 -1125,I7N3G6G,1125,1,no description,60.2,1 -1126,I7N3G6G,1126,1,no description,60.2,1 -1127,I7N3G6G,1127,1,no description,60.2,1 -1128,I7N3G6G,1128,1,no description,60.2,1 -1129,I7N3G6G,1129,1,no description,60.2,1 -1130,I7N3G6G,1130,1,no description,60.2,1 -1131,I7N3G6G,1131,1,no description,60.2,1 -1132,I7N3G6G,1132,1,no description,60.2,1 -1133,I7N3G6G,1133,1,no description,60.2,1 -1134,I7N3G6G,1134,1,no description,60.2,1 -1135,I7N3G6G,1135,1,no description,60.2,1 -1136,I7N3G6G,1136,1,no description,60.2,1 -1137,I7N3G6G,1137,1,no description,60.2,1 -1138,I7N3G6G,1138,1,no description,60.2,1 -1139,I7N3G6G,1139,1,no description,60.2,1 -1140,I7N3G6G,1140,1,no description,60.2,1 -1141,I7N3G6G,1141,1,no description,60.2,1 -1142,I7N3G6G,1142,1,no description,60.2,1 -1143,I7N3G6G,1143,1,no description,60.2,1 -1144,I7N3G6G,1144,1,no description,60.2,1 -1145,I7N3G6G,1145,1,no description,60.2,1 -1146,I7N3G6G,1146,1,no description,60.2,1 -1147,I7N3G6G,1147,1,no description,60.2,1 -1148,I7N3G6G,1148,1,no description,60.2,1 -1149,I7N3G6G,1149,1,no description,60.2,1 -1150,I7N3G6G,1150,1,no description,60.2,1 -1151,I7N3G6G,1151,1,no description,60.2,1 -1152,I7N3G6G,1152,1,no description,60.2,1 -1153,I7N3G6G,1153,1,no description,60.2,1 -1154,I7N3G6G,1154,1,no description,60.2,1 -1155,I7N3G6G,1155,1,no description,60.2,1 -1156,I7N3G6G,1156,1,no description,60.2,1 -1157,I7N3G6G,1157,1,no description,60.2,1 -1158,I7N3G6G,1158,1,no description,60.2,1 -1159,I7N3G6G,1159,1,no description,60.2,1 -1160,I7N3G6G,1160,1,no description,60.2,1 -1161,I7N3G6G,1161,1,no description,60.2,1 -1162,I7N3G6G,1162,1,no description,60.2,1 -1163,I7N3G6G,1163,1,no description,60.2,1 -1164,I7N3G6G,1164,1,no description,60.2,1 -1165,I7N3G6G,1165,1,no description,60.2,1 -1166,I7N3G6G,1166,1,no description,60.2,1 -1167,I7N3G6G,1167,1,no description,60.2,1 -1168,I7N3G6G,1168,1,no description,60.2,1 -1169,I7N3G6G,1169,1,no description,60.2,1 -1170,I7N3G6G,1170,1,no description,60.2,1 -1171,I7N3G6G,1171,1,no description,60.2,1 -1172,I7N3G6G,1172,1,no description,60.2,1 -1173,I7N3G6G,1173,1,no description,60.2,1 -1174,I7N3G6G,1174,1,no description,60.2,1 -1175,I7N3G6G,1175,1,no description,60.2,1 -1176,I7N3G6G,1176,1,no description,60.2,1 -1177,I7N3G6G,1177,1,no description,60.2,1 -1178,I7N3G6G,1178,1,no description,60.2,1 -1179,I7N3G6G,1179,1,no description,60.2,1 -1180,I7N3G6G,1180,1,no description,60.2,1 -1181,I7N3G6G,1181,1,no description,60.2,1 -1182,I7N3G6G,1182,1,no description,60.2,1 -1183,I7N3G6G,1183,1,no description,60.2,1 -1184,I7N3G6G,1184,1,no description,60.2,1 -1185,I7N3G6G,1185,1,no description,60.2,1 -1186,I7N3G6G,1186,1,no description,60.2,1 -1187,I7N3G6G,1187,1,no description,60.2,1 -1188,I7N3G6G,1188,1,no description,60.2,1 -1189,I7N3G6G,1189,1,no description,60.2,1 -1190,I7N3G6G,1190,1,no description,60.2,1 -1191,I7N3G6G,1191,1,no description,60.2,1 -1192,I7N3G6G,1192,1,no description,60.2,1 -1193,I7N3G6G,1193,1,no description,60.2,1 -1194,I7N3G6G,1194,1,no description,60.2,1 -1195,I7N3G6G,1195,1,no description,60.2,1 -1196,I7N3G6G,1196,1,no description,60.2,1 -1197,I7N3G6G,1197,1,no description,60.2,1 -1198,I7N3G6G,1198,1,no description,60.2,1 -1199,I7N3G6G,1199,1,no description,60.2,1 -1200,I7N3G6G,1200,1,no description,60.2,1 -1201,I7N3G6G,1201,1,no description,60.2,1 -1202,I7N3G6G,1202,1,no description,60.2,1 -1203,I7N3G6G,1203,1,no description,60.2,1 -1204,I7N3G6G,1204,1,no description,60.2,1 -1205,I7N3G6G,1205,1,no description,60.2,1 -1206,I7N3G6G,1206,1,no description,60.2,1 -1207,I7N3G6G,1207,1,no description,60.2,1 -1208,I7N3G6G,1208,1,no description,60.2,1 -1209,I7N3G6G,1209,1,no description,60.2,1 -1210,I7N3G6G,1210,1,no description,60.2,1 -1211,I7N3G6G,1211,1,no description,60.2,1 -1212,I7N3G6G,1212,1,no description,60.2,1 -1213,I7N3G6G,1213,1,no description,60.2,1 -1214,I7N3G6G,1214,1,no description,60.2,1 -1215,I7N3G6G,1215,1,no description,60.2,1 -1216,I7N3G6G,1216,1,no description,60.2,1 -1217,I7N3G6G,1217,1,no description,60.2,1 -1218,I7N3G6G,1218,1,no description,60.2,1 -1219,I7N3G6G,1219,1,no description,60.2,1 -1220,I7N3G6G,1220,1,no description,60.2,1 -1221,I7N3G6G,1221,1,no description,60.2,1 -1222,I7N3G6G,1222,1,no description,60.2,1 -1223,I7N3G6G,1223,1,no description,60.2,1 -1224,I7N3G6G,1224,1,no description,60.2,1 -1225,I7N3G6G,1225,1,no description,60.2,1 -1226,I7N3G6G,1226,1,no description,60.2,1 -1227,I7N3G6G,1227,1,no description,60.2,1 -1228,I7N3G6G,1228,1,no description,60.2,1 -1229,I7N3G6G,1229,1,no description,60.2,1 -1230,I7N3G6G,1230,1,no description,60.2,1 -1231,I7N3G6G,1231,1,no description,60.2,1 -1232,I7N3G6G,1232,1,no description,60.2,1 -1233,I7N3G6G,1233,1,no description,60.2,1 -1234,I7N3G6G,1234,1,no description,60.2,1 -1235,I7N3G6G,1235,1,no description,60.2,1 -1236,I7N3G6G,1236,1,no description,60.2,1 -1237,I7N3G6G,1237,1,no description,60.2,1 -1238,I7N3G6G,1238,1,no description,60.2,1 -1239,I7N3G6G,1239,1,no description,60.2,1 -1240,I7N3G6G,1240,1,no description,60.2,1 -1241,I7N3G6G,1241,1,no description,60.2,1 -1242,I7N3G6G,1242,1,no description,60.2,1 -1243,I7N3G6G,1243,1,no description,60.2,1 -1244,I7N3G6G,1244,1,no description,60.2,1 -1245,I7N3G6G,1245,1,no description,60.2,1 -1246,I7N3G6G,1246,1,no description,60.2,1 -1247,I7N3G6G,1247,1,no description,60.2,1 -1248,I7N3G6G,1248,1,no description,60.2,1 -1249,I7N3G6G,1249,1,no description,60.2,1 -1250,I7N3G6G,1250,1,no description,60.2,1 -1251,I7N3G6G,1251,1,no description,60.2,1 -1252,I7N3G6G,1252,1,no description,60.2,1 -1253,I7N3G6G,1253,1,no description,60.2,1 -1254,I7N3G6G,1254,1,no description,60.2,1 -1255,I7N3G6G,1255,1,no description,60.2,1 -1256,I7N3G6G,1256,1,no description,60.2,1 -1257,I7N3G6G,1257,1,no description,60.2,1 -1258,I7N3G6G,1258,1,no description,60.2,1 -1259,I7N3G6G,1259,1,no description,60.2,1 -1260,I7N3G6G,1260,1,no description,60.2,1 -1261,I7N3G6G,1261,1,no description,60.2,1 -1262,I7N3G6G,1262,1,no description,60.2,1 -1263,I7N3G6G,1263,1,no description,60.2,1 -1264,I7N3G6G,1264,1,no description,60.2,1 -1265,I7N3G6G,1265,1,no description,60.2,1 -1266,I7N3G6G,1266,1,no description,60.2,1 -1267,I7N3G6G,1267,1,no description,60.2,1 -1268,I7N3G6G,1268,1,no description,60.2,1 -1269,I7N3G6G,1269,1,no description,60.2,1 -1270,I7N3G6G,1270,1,no description,60.2,1 -1271,I7N3G6G,1271,1,no description,60.2,1 -1272,I7N3G6G,1272,1,no description,60.2,1 -1273,I7N3G6G,1273,1,no description,60.2,1 -1274,I7N3G6G,1274,1,no description,60.2,1 -1275,I7N3G6G,1275,1,no description,60.2,1 -1276,I7N3G6G,1276,1,no description,60.2,1 -1277,I7N3G6G,1277,1,no description,60.2,1 -1278,I7N3G6G,1278,1,no description,60.2,1 -1279,I7N3G6G,1279,1,no description,60.2,1 -1280,I7N3G6G,1280,1,no description,60.2,1 -1281,I7N3G6G,1281,1,no description,60.2,1 -1282,I7N3G6G,1282,1,no description,60.2,1 -1283,I7N3G6G,1283,1,no description,60.2,1 -1284,I7N3G6G,1284,1,no description,60.2,1 -1285,I7N3G6G,1285,1,no description,60.2,1 -1286,I7N3G6G,1286,1,no description,60.2,1 -1287,I7N3G6G,1287,1,no description,60.2,1 -1288,I7N3G6G,1288,1,no description,60.2,1 -1289,I7N3G6G,1289,1,no description,60.2,1 -1290,I7N3G6G,1290,1,no description,60.2,1 -1291,I7N3G6G,1291,1,no description,60.2,1 -1292,I7N3G6G,1292,1,no description,60.2,1 -1293,I7N3G6G,1293,1,no description,60.2,1 -1294,I7N3G6G,1294,1,no description,60.2,1 -1295,I7N3G6G,1295,1,no description,60.2,1 -1296,I7N3G6G,1296,1,no description,60.2,1 -1297,I7N3G6G,1297,1,no description,60.2,1 -1298,I7N3G6G,1298,1,no description,60.2,1 -1299,I7N3G6G,1299,1,no description,60.2,1 -1300,I7N3G6G,1300,1,no description,60.2,1 -1301,I7N3G6G,1301,1,no description,60.2,1 -1302,I7N3G6G,1302,1,no description,60.2,1 -1303,I7N3G6G,1303,1,no description,60.2,1 -1304,I7N3G6G,1304,1,no description,60.2,1 -1305,I7N3G6G,1305,1,no description,60.2,1 -1306,I7N3G6G,1306,1,no description,60.2,1 -1307,I7N3G6G,1307,1,no description,60.2,1 -1308,I7N3G6G,1308,1,no description,60.2,1 -1309,I7N3G6G,1309,1,no description,60.2,1 -1310,I7N3G6G,1310,1,no description,60.2,1 -1311,I7N3G6G,1311,1,no description,60.2,1 -1312,I7N3G6G,1312,1,no description,60.2,1 -1313,I7N3G6G,1313,1,no description,60.2,1 -1314,I7N3G6G,1314,1,no description,60.2,1 -1315,I7N3G6G,1315,1,no description,60.2,1 -1316,I7N3G6G,1316,1,no description,60.2,1 -1317,I7N3G6G,1317,1,no description,60.2,1 -1318,I7N3G6G,1318,1,no description,60.2,1 -1319,I7N3G6G,1319,1,no description,60.2,1 -1320,I7N3G6G,1320,1,no description,60.2,1 -1321,I7N3G6G,1321,1,no description,60.2,1 -1322,I7N3G6G,1322,1,no description,60.2,1 -1323,I7N3G6G,1323,1,no description,60.2,1 -1324,I7N3G6G,1324,1,no description,60.2,1 -1325,I7N3G6G,1325,1,no description,60.2,1 -1326,I7N3G6G,1326,1,no description,60.2,1 -1327,I7N3G6G,1327,1,no description,60.2,1 -1328,I7N3G6G,1328,1,no description,60.2,1 -1329,I7N3G6G,1329,1,no description,60.2,1 -1330,I7N3G6G,1330,1,no description,60.2,1 -1331,I7N3G6G,1331,1,no description,60.2,1 -1332,I7N3G6G,1332,1,no description,60.2,1 -1333,I7N3G6G,1333,1,no description,60.2,1 -1334,I7N3G6G,1334,1,no description,60.2,1 -1335,I7N3G6G,1335,1,no description,60.2,1 -1336,I7N3G6G,1336,1,no description,60.2,1 -1337,I7N3G6G,1337,1,no description,60.2,1 -1338,I7N3G6G,1338,1,no description,60.2,1 -1339,I7N3G6G,1339,1,no description,60.2,1 -1340,I7N3G6G,1340,1,no description,60.2,1 -1341,I7N3G6G,1341,1,no description,60.2,1 -1342,I7N3G6G,1342,1,no description,60.2,1 -1343,I7N3G6G,1343,1,no description,60.2,1 -1344,I7N3G6G,1344,1,no description,60.2,1 -1345,I7N3G6G,1345,1,no description,60.2,1 -1346,I7N3G6G,1346,1,no description,60.2,1 -1347,I7N3G6G,1347,1,no description,60.2,1 -1348,I7N3G6G,1348,1,no description,60.2,1 -1349,I7N3G6G,1349,1,no description,60.2,1 -1350,I7N3G6G,1350,1,no description,60.2,1 -1351,I7N3G6G,1351,1,no description,60.2,1 -1352,I7N3G6G,1352,1,no description,60.2,1 -1353,I7N3G6G,1353,1,no description,60.2,1 -1354,I7N3G6G,1354,1,no description,60.2,1 -1355,I7N3G6G,1355,1,no description,60.2,1 -1356,I7N3G6G,1356,1,no description,60.2,1 -1357,I7N3G6G,1357,1,no description,60.2,1 -1358,I7N3G6G,1358,1,no description,60.2,1 -1359,I7N3G6G,1359,1,no description,60.2,1 -1360,I7N3G6G,1360,1,no description,60.2,1 -1361,I7N3G6G,1361,1,no description,60.2,1 -1362,I7N3G6G,1362,1,no description,60.2,1 -1363,I7N3G6G,1363,1,no description,60.2,1 -1364,I7N3G6G,1364,1,no description,60.2,1 -1365,I7N3G6G,1365,1,no description,60.2,1 -1366,I7N3G6G,1366,1,no description,60.2,1 -1367,I7N3G6G,1367,1,no description,60.2,1 -1368,I7N3G6G,1368,1,no description,60.2,1 -1369,I7N3G6G,1369,1,no description,60.2,1 -1370,I7N3G6G,1370,1,no description,60.2,1 -1371,I7N3G6G,1371,1,no description,60.2,1 -1372,I7N3G6G,1372,1,no description,60.2,1 -1373,I7N3G6G,1373,1,no description,60.2,1 -1374,I7N3G6G,1374,1,no description,60.2,1 -1375,I7N3G6G,1375,1,no description,60.2,1 -1376,I7N3G6G,1376,1,no description,60.2,1 -1377,I7N3G6G,1377,1,no description,60.2,1 -1378,I7N3G6G,1378,1,no description,60.2,1 -1379,I7N3G6G,1379,1,no description,60.2,1 -1380,I7N3G6G,1380,1,no description,60.2,1 -1381,I7N3G6G,1381,1,no description,60.2,1 -1382,I7N3G6G,1382,1,no description,60.2,1 -1383,I7N3G6G,1383,1,no description,60.2,1 -1384,I7N3G6G,1384,1,no description,60.2,1 -1385,I7N3G6G,1385,1,no description,60.2,1 -1386,I7N3G6G,1386,1,no description,60.2,1 -1387,I7N3G6G,1387,1,no description,60.2,1 -1388,I7N3G6G,1388,1,no description,60.2,1 -1389,I7N3G6G,1389,1,no description,60.2,1 -1390,I7N3G6G,1390,1,no description,60.2,1 -1391,I7N3G6G,1391,1,no description,60.2,1 -1392,I7N3G6G,1392,1,no description,60.2,1 -1393,I7N3G6G,1393,1,no description,60.2,1 -1394,I7N3G6G,1394,1,no description,60.2,1 -1395,I7N3G6G,1395,1,no description,60.2,1 -1396,I7N3G6G,1396,1,no description,60.2,1 -1397,I7N3G6G,1397,1,no description,60.2,1 -1398,I7N3G6G,1398,1,no description,60.2,1 -1399,I7N3G6G,1399,1,no description,60.2,1 -1400,I7N3G6G,1400,1,no description,60.2,1 -1401,I7N3G6G,1401,1,no description,60.2,1 -1402,I7N3G6G,1402,1,no description,60.2,1 -1403,I7N3G6G,1403,1,no description,60.2,1 -1404,I7N3G6G,1404,1,no description,60.2,1 -1405,I7N3G6G,1405,1,no description,60.2,1 -1406,I7N3G6G,1406,1,no description,60.2,1 -1407,I7N3G6G,1407,1,no description,60.2,1 -1408,I7N3G6G,1408,1,no description,60.2,1 -1409,I7N3G6G,1409,1,no description,60.2,1 -1410,I7N3G6G,1410,1,no description,60.2,1 -1411,I7N3G6G,1411,1,no description,60.2,1 -1412,I7N3G6G,1412,1,no description,60.2,1 -1413,I7N3G6G,1413,1,no description,60.2,1 -1414,I7N3G6G,1414,1,no description,60.2,1 -1415,I7N3G6G,1415,1,no description,60.2,1 -1416,I7N3G6G,1416,1,no description,60.2,1 -1417,I7N3G6G,1417,1,no description,60.2,1 -1418,I7N3G6G,1418,1,no description,60.2,1 -1419,I7N3G6G,1419,1,no description,60.2,1 -1420,I7N3G6G,1420,1,no description,60.2,1 -1421,I7N3G6G,1421,1,no description,60.2,1 -1422,I7N3G6G,1422,1,no description,60.2,1 -1423,I7N3G6G,1423,1,no description,60.2,1 -1424,I7N3G6G,1424,1,no description,60.2,1 -1425,I7N3G6G,1425,1,no description,60.2,1 -1426,I7N3G6G,1426,1,no description,60.2,1 -1427,I7N3G6G,1427,1,no description,60.2,1 -1428,I7N3G6G,1428,1,no description,60.2,1 -1429,I7N3G6G,1429,1,no description,60.2,1 -1430,I7N3G6G,1430,1,no description,60.2,1 -1431,I7N3G6G,1431,1,no description,60.2,1 -1432,I7N3G6G,1432,1,no description,60.2,1 -1433,I7N3G6G,1433,1,no description,60.2,1 -1434,I7N3G6G,1434,1,no description,60.2,1 -1435,I7N3G6G,1435,1,no description,60.2,1 -1436,I7N3G6G,1436,1,no description,60.2,1 -1437,I7N3G6G,1437,1,no description,60.2,1 -1438,I7N3G6G,1438,1,no description,60.2,1 -1439,I7N3G6G,1439,1,no description,60.2,1 -1440,I7N3G6G,1440,1,no description,60.2,1 -1441,I7N3G6G,1441,1,no description,60.2,1 -1442,I7N3G6G,1442,1,no description,60.2,1 -1443,I7N3G6G,1443,1,no description,60.2,1 -1444,I7N3G6G,1444,1,no description,60.2,1 -1445,I7N3G6G,1445,1,no description,60.2,1 -1446,I7N3G6G,1446,1,no description,60.2,1 -1447,I7N3G6G,1447,1,no description,60.2,1 -1448,I7N3G6G,1448,1,no description,60.2,1 -1449,I7N3G6G,1449,1,no description,60.2,1 -1450,I7N3G6G,1450,1,no description,60.2,1 -1451,I7N3G6G,1451,1,no description,60.2,1 -1452,I7N3G6G,1452,1,no description,60.2,1 -1453,I7N3G6G,1453,1,no description,60.2,1 -1454,I7N3G6G,1454,1,no description,60.2,1 -1455,I7N3G6G,1455,1,no description,60.2,1 -1456,I7N3G6G,1456,1,no description,60.2,1 -1457,I7N3G6G,1457,1,no description,60.2,1 -1458,I7N3G6G,1458,1,no description,60.2,1 -1459,I7N3G6G,1459,1,no description,60.2,1 -1460,I7N3G6G,1460,1,no description,60.2,1 -1461,I7N3G6G,1461,1,no description,60.2,1 -1462,I7N3G6G,1462,1,no description,60.2,1 -1463,I7N3G6G,1463,1,no description,60.2,1 -1464,I7N3G6G,1464,1,no description,60.2,1 -1465,I7N3G6G,1465,1,no description,60.2,1 -1466,I7N3G6G,1466,1,no description,60.2,1 -1467,I7N3G6G,1467,1,no description,60.2,1 -1468,I7N3G6G,1468,1,no description,60.2,1 -1469,I7N3G6G,1469,1,no description,60.2,1 -1470,I7N3G6G,1470,1,no description,60.2,1 -1471,I7N3G6G,1471,1,no description,60.2,1 -1472,I7N3G6G,1472,1,no description,60.2,1 -1473,I7N3G6G,1473,1,no description,60.2,1 -1474,I7N3G6G,1474,1,no description,60.2,1 -1475,I7N3G6G,1475,1,no description,60.2,1 -1476,I7N3G6G,1476,1,no description,60.2,1 -1477,I7N3G6G,1477,1,no description,60.2,1 -1478,I7N3G6G,1478,1,no description,60.2,1 -1479,I7N3G6G,1479,1,no description,60.2,1 -1480,I7N3G6G,1480,1,no description,60.2,1 -1481,I7N3G6G,1481,1,no description,60.2,1 -1482,I7N3G6G,1482,1,no description,60.2,1 -1483,I7N3G6G,1483,1,no description,60.2,1 -1484,I7N3G6G,1484,1,no description,60.2,1 -1485,I7N3G6G,1485,1,no description,60.2,1 -1486,I7N3G6G,1486,1,no description,60.2,1 -1487,I7N3G6G,1487,1,no description,60.2,1 -1488,I7N3G6G,1488,1,no description,60.2,1 -1489,I7N3G6G,1489,1,no description,60.2,1 -1490,I7N3G6G,1490,1,no description,60.2,1 -1491,I7N3G6G,1491,1,no description,60.2,1 -1492,I7N3G6G,1492,1,no description,60.2,1 -1493,I7N3G6G,1493,1,no description,60.2,1 -1494,I7N3G6G,1494,1,no description,60.2,1 -1495,I7N3G6G,1495,1,no description,60.2,1 -1496,I7N3G6G,1496,1,no description,60.2,1 -1497,I7N3G6G,1497,1,no description,60.2,1 -1498,I7N3G6G,1498,1,no description,60.2,1 -1499,I7N3G6G,1499,1,no description,60.2,1 -1500,I7N3G6G,1500,1,no description,60.2,1 -1501,I7N3G6G,1501,1,no description,60.2,1 -1502,I7N3G6G,1502,1,no description,60.2,1 -1503,I7N3G6G,1503,1,no description,60.2,1 -1504,I7N3G6G,1504,1,no description,60.2,1 -1505,I7N3G6G,1505,1,no description,60.2,1 -1506,I7N3G6G,1506,1,no description,60.2,1 -1507,I7N3G6G,1507,1,no description,60.2,1 -1508,I7N3G6G,1508,1,no description,60.2,1 -1509,I7N3G6G,1509,1,no description,60.2,1 -1510,I7N3G6G,1510,1,no description,60.2,1 -1511,I7N3G6G,1511,1,no description,60.2,1 -1512,I7N3G6G,1512,1,no description,60.2,1 -1513,I7N3G6G,1513,1,no description,60.2,1 -1514,I7N3G6G,1514,1,no description,60.2,1 -1515,I7N3G6G,1515,1,no description,60.2,1 -1516,I7N3G6G,1516,1,no description,60.2,1 -1517,I7N3G6G,1517,1,no description,60.2,1 -1518,I7N3G6G,1518,1,no description,60.2,1 -1519,I7N3G6G,1519,1,no description,60.2,1 -1520,I7N3G6G,1520,1,no description,60.2,1 -1521,I7N3G6G,1521,1,no description,60.2,1 -1522,I7N3G6G,1522,1,no description,60.2,1 -1523,I7N3G6G,1523,1,no description,60.2,1 -1524,I7N3G6G,1524,1,no description,60.2,1 -1525,I7N3G6G,1525,1,no description,60.2,1 -1526,I7N3G6G,1526,1,no description,60.2,1 -1527,I7N3G6G,1527,1,no description,60.2,1 -1528,I7N3G6G,1528,1,no description,60.2,1 -1529,I7N3G6G,1529,1,no description,60.2,1 -1530,I7N3G6G,1530 -1531,I7N3G6G,1531,1 -1532,I7N3G6G,1532,1,no description,60.2,1 -1533,I7N3G6G,1533,1,no description,60.2,1 -1534,I7N3G6G,1534,1,no description,60.2,1 -1535,I7N3G6G,1535,1,no description,60.2,1 -1536,I7N3G6G,1536,1,no description,60.2,1 -1537,I7N3G6G,1537,1,no description,60.2,1 -1538,I7N3G6G,1538,1,no description,60.2,1 -1539,I7N3G6G,1539,1,no description,60.2,1 -1540,I7N3G6G,1540,1,no description,60.2,1 -1541,I7N3G6G,1541,1,no description,60.2,1 -1542,I7N3G6G,1542,1,no description,60.2,1 -1543,I7N3G6G,1543,1,no description,60.2,1 -1544,I7N3G6G,1544,1,no description,60.2,1 -1545,I7N3G6G,1545,1,no description,60.2,1 -1546,I7N3G6G,1546,1,no description,60.2,1 -1547,I7N3G6G,1547,1,no description,60.2,1 -1548,I7N3G6G,1548,1,no description,60.2,1 -1549,I7N3G6G,1549,1,no description,60.2,1 -1550,I7N3G6G,1550,1,no description,60.2,1 -1551,I7N3G6G,1551,1,no description,60.2,1 -1552,I7N3G6G,1552,1,no description,60.2,1 -1553,I7N3G6G,1553,1,no description,60.2,1 -1554,I7N3G6G,1554,1,no description,60.2,1 -1555,I7N3G6G,1555,1,no description,60.2,1 -1556,I7N3G6G,1556,1,no description,60.2,1 -1557,I7N3G6G,1557,1,no description,60.2,1 -1558,I7N3G6G,1558,1,no description,60.2,1 -1559,I7N3G6G,1559,1,no description,60.2,1 -1560,I7N3G6G,1560,1,no description,60.2,1 -1561,I7N3G6G,1561,1,no description,60.2,1 -1562,I7N3G6G,1562,1,no description,60.2,1 -1563,I7N3G6G,1563,1,no description,60.2,1 -1564,I7N3G6G,1564,1,no description,60.2,1 -1565,I7N3G6G,1565,1,no description,60.2,1 -1566,I7N3G6G,1566,1,no description,60.2,1 -1567,I7N3G6G,1567,1,no description,60.2,1 -1568,I7N3G6G,1568,1,no description,60.2,1 -1569,I7N3G6G,1569,1,no description,60.2,1 -1570,I7N3G6G,1570,1,no description,60.2,1 -1571,I7N3G6G,1571,1,no description,60.2,1 -1572,I7N3G6G,1572,1,no description,60.2,1 -1573,I7N3G6G,1573,1,no description,60.2,1 -1574,I7N3G6G,1574,1,no description,60.2,1 -1575,I7N3G6G,1575,1,no description,60.2,1 -1576,I7N3G6G,1576,1,no description,60.2,1 -1577,I7N3G6G,1577,1,no description,60.2,1 -1578,I7N3G6G,1578,1,no description,60.2,1 -1579,I7N3G6G,1579,1,no description,60.2,1 -1580,I7N3G6G,1580,1,no description,60.2,1 -1581,I7N3G6G,1581,1,no description,60.2,1 -1582,I7N3G6G,1582,1,no description,60.2,1 -1583,I7N3G6G,1583,1,no description,60.2,1 -1584,I7N3G6G,1584,1,no description,60.2,1 -1585,I7N3G6G,1585,1,no description,60.2,1 -1586,I7N3G6G,1586,1,no description,60.2,1 -1587,I7N3G6G,1587,1,no description,60.2,1 -1588,I7N3G6G,1588,1,no description,60.2,1 -1589,I7N3G6G,1589,1,no description,60.2,1 -1590,I7N3G6G,1590,1,no description,60.2,1 -1591,I7N3G6G,1591,1,no description,60.2,1 -1592,I7N3G6G,1592,1,no description,60.2,1 -1593,I7N3G6G,1593,1,no description,60.2,1 -1594,I7N3G6G,1594,1,no description,60.2,1 -1595,I7N3G6G,1595,1,no description,60.2,1 -1596,I7N3G6G,1596,1,no description,60.2,1 -1597,I7N3G6G,1597,1,no description,60.2,1 -1598,I7N3G6G,1598,1,no description,60.2,1 -1599,I7N3G6G,1599,1,no description,60.2,1 -1600,I7N3G6G,1600,1,no description,60.2,1 -1601,I7N3G6G,1601,1,no description,60.2,1 -1602,I7N3G6G,1602,1,no description,60.2,1 -1603,I7N3G6G,1603,1,no description,60.2,1 -1604,I7N3G6G,1604,1,no description,60.2,1 -1605,I7N3G6G,1605,1,no description,60.2,1 -1606,I7N3G6G,1606,1,no description,60.2,1 -1607,I7N3G6G,1607,1,no description,60.2,1 -1608,I7N3G6G,1608,1,no description,60.2,1 -1609,I7N3G6G,1609,1,no description,60.2,1 -1610,I7N3G6G,1610,1,no description,60.2,1 -1611,I7N3G6G,1611,1,no description,60.2,1 -1612,I7N3G6G,1612,1,no description,60.2,1 -1613,I7N3G6G,1613,1,no description,60.2,1 -1614,I7N3G6G,1614,1,no description,60.2,1 -1615,I7N3G6G,1615,1,no description,60.2,1 -1616,I7N3G6G,1616,1,no description,60.2,1 -1617,I7N3G6G,1617,1,no description,60.2,1 -1618,I7N3G6G,1618,1,no description,60.2,1 -1619,I7N3G6G,1619,1,no description,60.2,1 -1620,I7N3G6G,1620,1,no description,60.2,1 -1621,I7N3G6G,1621,1,no description,60.2,1 -1622,I7N3G6G,1622,1,no description,60.2,1 -1623,I7N3G6G,1623,1,no description,60.2,1 -1624,I7N3G6G,1624,1,no description,60.2,1 -1625,I7N3G6G,1625,1,no description,60.2,1 -1626,I7N3G6G,1626,1,no description,60.2,1 -1627,I7N3G6G,1627,1,no description,60.2,1 -1628,I7N3G6G,1628,1,no description,60.2,1 -1629,I7N3G6G,1629,1,no description,60.2,1 -1630,I7N3G6G,1630,1,no description,60.2,1 -1631,I7N3G6G,1631,1,no description,60.2,1 -1632,I7N3G6G,1632,1,no description,60.2,1 -1633,I7N3G6G,1633,1,no description,60.2,1 -1634,I7N3G6G,1634,1,no description,60.2,1 -1635,I7N3G6G,1635,1,no description,60.2,1 -1636,I7N3G6G,1636,1,no description,60.2,1 -1637,I7N3G6G,1637,1,no description,60.2,1 -1638,I7N3G6G,1638,1,no description,60.2,1 -1639,I7N3G6G,1639,1,no description,60.2,1 -1640,I7N3G6G,1640,1,no description,60.2,1 -1641,I7N3G6G,1641,1,no description,60.2,1 -1642,I7N3G6G,1642,1,no description,60.2,1 -1643,I7N3G6G,1643,1,no description,60.2,1 -1644,I7N3G6G,1644,1,no description,60.2,1 -1645,I7N3G6G,1645,1,no description,60.2,1 -1646,I7N3G6G,1646,1,no description,60.2,1 -1647,I7N3G6G,1647,1,no description,60.2,1 -1648,I7N3G6G,1648,1,no description,60.2,1 -1649,I7N3G6G,1649,1,no description,60.2,1 -1650,I7N3G6G,1650,1,no description,60.2,1 -1651,I7N3G6G,1651,1,no description,60.2,1 -1652,I7N3G6G,1652,1,no description,60.2,1 -1653,I7N3G6G,1653,1,no description,60.2,1 -1654,I7N3G6G,1654,1,no description,60.2,1 -1655,I7N3G6G,1655,1,no description,60.2,1 -1656,I7N3G6G,1656,1,no description,60.2,1 -1657,I7N3G6G,1657,1,no description,60.2,1 -1658,I7N3G6G,1658,1,no description,60.2,1 -1659,I7N3G6G,1659,1,no description,60.2,1 -1660,I7N3G6G,1660,1,no description,60.2,1 -1661,I7N3G6G,1661,1,no description,60.2,1 -1662,I7N3G6G,1662,1,no description,60.2,1 -1663,I7N3G6G,1663,1,no description,60.2,1 -1664,I7N3G6G,1664,1,no description,60.2,1 -1665,I7N3G6G,1665,1,no description,60.2,1 -1666,I7N3G6G,1666,1,no description,60.2,1 -1667,I7N3G6G,1667,1,no description,60.2,1 -1668,I7N3G6G,1668,1,no description,60.2,1 -1669,I7N3G6G,1669,1,no description,60.2,1 -1670,I7N3G6G,1670,1,no description,60.2,1 -1671,I7N3G6G,1671,1,no description,60.2,1 -1672,I7N3G6G,1672,1,no description,60.2,1 -1673,I7N3G6G,1673,1,no description,60.2,1 -1674,I7N3G6G,1674,1,no description,60.2,1 -1675,I7N3G6G,1675,1,no description,60.2,1 -1676,I7N3G6G,1676,1,no description,60.2,1 -1677,I7N3G6G,1677,1,no description,60.2,1 -1678,I7N3G6G,1678,1,no description,60.2,1 -1679,I7N3G6G,1679,1,no description,60.2,1 -1680,I7N3G6G,1680,1,no description,60.2,1 -1681,I7N3G6G,1681,1,no description,60.2,1 -1682,I7N3G6G,1682,1,no description,60.2,1 -1683,I7N3G6G,1683,1,no description,60.2,1 -1684,I7N3G6G,1684,1,no description,60.2,1 -1685,I7N3G6G,1685,1,no description,60.2,1 -1686,I7N3G6G,1686,1,no description,60.2,1 -1687,I7N3G6G,1687,1,no description,60.2,1 -1688,I7N3G6G,1688,1,no description,60.2,1 -1689,I7N3G6G,1689,1,no description,60.2,1 -1690,I7N3G6G,1690,1,no description,60.2,1 -1691,I7N3G6G,1691,1,no description,60.2,1 -1692,I7N3G6G,1692,1,no description,60.2,1 -1693,I7N3G6G,1693,1,no description,60.2,1 -1694,I7N3G6G,1694,1,no description,60.2,1 -1695,I7N3G6G,1695,1,no description,60.2,1 -1696,I7N3G6G,1696,1,no description,60.2,1 -1697,I7N3G6G,1697,1,no description,60.2,1 -1698,I7N3G6G,1698,1,no description,60.2,1 -1699,I7N3G6G,1699,1,no description,60.2,1 -1700,I7N3G6G,1700,1,no description,60.2,1 -1701,I7N3G6G,1701,1,no description,60.2,1 -1702,I7N3G6G,1702,1,no description,60.2,1 -1703,I7N3G6G,1703,1,no description,60.2,1 -1704,I7N3G6G,1704,1,no description,60.2,1 -1705,I7N3G6G,1705,1,no description,60.2,1 -1706,I7N3G6G,1706,1,no description,60.2,1 -1707,I7N3G6G,1707,1,no description,60.2,1 -1708,I7N3G6G,1708,1,no description,60.2,1 -1709,I7N3G6G,1709,1,no description,60.2,1 -1710,I7N3G6G,1710,1,no description,60.2,1 -1711,I7N3G6G,1711,1,no description,60.2,1 -1712,I7N3G6G,1712,1,no description,60.2,1 -1713,I7N3G6G,1713,1,no description,60.2,1 -1714,I7N3G6G,1714,1,no description,60.2,1 -1715,I7N3G6G,1715,1,no description,60.2,1 -1716,I7N3G6G,1716,1,no description,60.2,1 -1717,I7N3G6G,1717,1,no description,60.2,1 -1718,I7N3G6G,1718,1,no description,60.2,1 -1719,I7N3G6G,1719,1,no description,60.2,1 -1720,I7N3G6G,1720,1,no description,60.2,1 -1721,I7N3G6G,1721,1,no description,60.2,1 -1722,I7N3G6G,1722,1,no description,60.2,1 -1723,I7N3G6G,1723,1,no description,60.2,1 -1724,I7N3G6G,1724,1,no description,60.2,1 -1725,I7N3G6G,1725,1,no description,60.2,1 -1726,I7N3G6G,1726,1,no description,60.2,1 -1727,I7N3G6G,1727,1,no description,60.2,1 -1728,I7N3G6G,1728,1,no description,60.2,1 -1729,I7N3G6G,1729,1,no description,60.2,1 -1730,I7N3G6G,1730,1,no description,60.2,1 -1731,I7N3G6G,1731,1,no description,60.2,1 -1732,I7N3G6G,1732,1,no description,60.2,1 -1733,I7N3G6G,1733,1,no description,60.2,1 -1734,I7N3G6G,1734,1,no description,60.2,1 -1735,I7N3G6G,1735,1,no description,60.2,1 -1736,I7N3G6G,1736,1,no description,60.2,1 -1737,I7N3G6G,1737,1,no description,60.2,1 -1738,I7N3G6G,1738,1,no description,60.2,1 -1739,I7N3G6G,1739,1,no description,60.2,1 -1740,I7N3G6G,1740,1,no description,60.2,1 -1741,I7N3G6G,1741,1,no description,60.2,1 -1742,I7N3G6G,1742,1,no description,60.2,1 -1743,I7N3G6G,1743,1,no description,60.2,1 -1744,I7N3G6G,1744,1,no description,60.2,1 -1745,I7N3G6G,1745,1,no description,60.2,1 -1746,I7N3G6G,1746,1,no description,60.2,1 -1747,I7N3G6G,1747,1,no description,60.2,1 -1748,I7N3G6G,1748,1,no description,60.2,1 -1749,I7N3G6G,1749,1,no description,60.2,1 -1750,I7N3G6G,1750,1,no description,60.2,1 -1751,I7N3G6G,1751,1,no description,60.2,1 -1752,I7N3G6G,1752,1,no description,60.2,1 -1753,I7N3G6G,1753,1,no description,60.2,1 -1754,I7N3G6G,1754,1,no description,60.2,1 -1755,I7N3G6G,1755,1,no description,60.2,1 -1756,I7N3G6G,1756,1,no description,60.2,1 -1757,I7N3G6G,1757,1,no description,60.2,1 -1758,I7N3G6G,1758,1,no description,60.2,1 -1759,I7N3G6G,1759,1,no description,60.2,1 -1760,I7N3G6G,1760,1,no description,60.2,1 -1761,I7N3G6G,1761,1,no description,60.2,1 -1762,I7N3G6G,1762,1,no description,60.2,1 -1763,I7N3G6G,1763,1,no description,60.2,1 -1764,I7N3G6G,1764,1,no description,60.2,1 -1765,I7N3G6G,1765,1,no description,60.2,1 -1766,I7N3G6G,1766,1,no description,60.2,1 -1767,I7N3G6G,1767,1,no description,60.2,1 -1768,I7N3G6G,1768,1,no description,60.2,1 -1769,I7N3G6G,1769,1,no description,60.2,1 -1770,I7N3G6G,1770,1,no description,60.2,1 -1771,I7N3G6G,1771,1,no description,60.2,1 -1772,I7N3G6G,1772,1,no description,60.2,1 -1773,I7N3G6G,1773,1,no description,60.2,1 -1774,I7N3G6G,1774,1,no description,60.2,1 -1775,I7N3G6G,1775,1,no description,60.2,1 -1776,I7N3G6G,1776,1,no description,60.2,1 -1777,I7N3G6G,1777,1,no description,60.2,1 -1778,I7N3G6G,1778,1,no description,60.2,1 -1779,I7N3G6G,1779,1,no description,60.2,1 -1780,I7N3G6G,1780,1,no description,60.2,1 -1781,I7N3G6G,1781,1,no description,60.2,1 -1782,I7N3G6G,1782,1,no description,60.2,1 -1783,I7N3G6G,1783,1,no description,60.2,1 -1784,I7N3G6G,1784,1,no description,60.2,1 -1785,I7N3G6G,1785,1,no description,60.2,1 -1786,I7N3G6G,1786,1,no description,60.2,1 -1787,I7N3G6G,1787,1,no description,60.2,1 -1788,I7N3G6G,1788,1,no description,60.2,1 -1789,I7N3G6G,1789,1,no description,60.2,1 -1790,I7N3G6G,1790,1,no description,60.2,1 -1791,I7N3G6G,1791,1,no description,60.2,1 -1792,I7N3G6G,1792,1,no description,60.2,1 -1793,I7N3G6G,1793,1,no description,60.2,1 -1794,I7N3G6G,1794,1,no description,60.2,1 -1795,I7N3G6G,1795,1,no description,60.2,1 -1796,I7N3G6G,1796,1,no description,60.2,1 -1797,I7N3G6G,1797,1,no description,60.2,1 -1798,I7N3G6G,1798,1,no description,60.2,1 -1799,I7N3G6G,1799,1,no description,60.2,1 -1800,I7N3G6G,1800,1,no description,60.2,1 -1801,I7N3G6G,1801,1,no description,60.2,1 -1802,I7N3G6G,1802,1,no description,60.2,1 -1803,I7N3G6G,1803,1,no description,60.2,1 -1804,I7N3G6G,1804,1,no description,60.2,1 -1805,I7N3G6G,1805,1,no description,60.2,1 -1806,I7N3G6G,1806,1,no description,60.2,1 -1807,I7N3G6G,1807,1,no description,60.2,1 -1808,I7N3G6G,1808,1,no description,60.2,1 -1809,I7N3G6G,1809,1,no description,60.2,1 -1810,I7N3G6G,1810,1,no description,60.2,1 -1811,I7N3G6G,1811,1,no description,60.2,1 -1812,I7N3G6G,1812,1,no description,60.2,1 -1813,I7N3G6G,1813,1,no description,60.2,1 -1814,I7N3G6G,1814,1,no description,60.2,1 -1815,I7N3G6G,1815,1,no description,60.2,1 -1816,I7N3G6G,1816,1,no description,60.2,1 -1817,I7N3G6G,1817,1,no description,60.2,1 -1818,I7N3G6G,1818,1,no description,60.2,1 -1819,I7N3G6G,1819,1,no description,60.2,1 -1820,I7N3G6G,1820,1,no description,60.2,1 -1821,I7N3G6G,1821,1,no description,60.2,1 -1822,I7N3G6G,1822,1,no description,60.2,1 -1823,I7N3G6G,1823,1,no description,60.2,1 -1824,I7N3G6G,1824,1,no description,60.2,1 -1825,I7N3G6G,1825,1,no description,60.2,1 -1826,I7N3G6G,1826,1,no description,60.2,1 -1827,I7N3G6G,1827,1,no description,60.2,1 -1828,I7N3G6G,1828,1,no description,60.2,1 -1829,I7N3G6G,1829,1,no description,60.2,1 -1830,I7N3G6G,1830,1,no description,60.2,1 -1831,I7N3G6G,1831,1,no description,60.2,1 -1832,I7N3G6G,1832,1,no description,60.2,1 -1833,I7N3G6G,1833,1,no description,60.2,1 -1834,I7N3G6G,1834,1,no description,60.2,1 -1835,I7N3G6G,1835,1,no description,60.2,1 -1836,I7N3G6G,1836,1,no description,60.2,1 -1837,I7N3G6G,1837,1,no description,60.2,1 -1838,I7N3G6G,1838,1,no description,60.2,1 -1839,I7N3G6G,1839,1,no description,60.2,1 -1840,I7N3G6G,1840,1,no description,60.2,1 -1841,I7N3G6G,1841,1,no description,60.2,1 -1842,I7N3G6G,1842,1,no description,60.2,1 -1843,I7N3G6G,1843,1,no description,60.2,1 -1844,I7N3G6G,1844,1,no description,60.2,1 -1845,I7N3G6G,1845,1,no description,60.2,1 -1846,I7N3G6G,1846,1,no description,60.2,1 -1847,I7N3G6G,1847,1,no description,60.2,1 -1848,I7N3G6G,1848,1,no description,60.2,1 -1849,I7N3G6G,1849,1,no description,60.2,1 -1850,I7N3G6G,1850,1,no description,60.2,1 -1851,I7N3G6G,1851,1,no description,60.2,1 -1852,I7N3G6G,1852,1,no description,60.2,1 -1853,I7N3G6G,1853,1,no description,60.2,1 -1854,I7N3G6G,1854,1,no description,60.2,1 -1855,I7N3G6G,1855,1,no description,60.2,1 -1856,I7N3G6G,1856,1,no description,60.2,1 -1857,I7N3G6G,1857,1,no description,60.2,1 -1858,I7N3G6G,1858,1,no description,60.2,1 -1859,I7N3G6G,1859,1,no description,60.2,1 -1860,I7N3G6G,1860,1,no description,60.2,1 -1861,I7N3G6G,1861,1,no description,60.2,1 -1862,I7N3G6G,1862,1,no description,60.2,1 -1863,I7N3G6G,1863,1,no description,60.2,1 -1864,I7N3G6G,1864,1,no description,60.2,1 -1865,I7N3G6G,1865,1,no description,60.2,1 -1866,I7N3G6G,1866,1,no description,60.2,1 -1867,I7N3G6G,1867,1,no description,60.2,1 -1868,I7N3G6G,1868,1,no description,60.2,1 -1869,I7N3G6G,1869,1,no description,60.2,1 -1870,I7N3G6G,1870,1,no description,60.2,1 -1871,I7N3G6G,1871,1,no description,60.2,1 -1872,I7N3G6G,1872,1,no description,60.2,1 -1873,I7N3G6G,1873,1,no description,60.2,1 -1874,I7N3G6G,1874,1,no description,60.2,1 -1875,I7N3G6G,1875,1,no description,60.2,1 -1876,I7N3G6G,1876,1,no description,60.2,1 -1877,I7N3G6G,1877,1,no description,60.2,1 -1878,I7N3G6G,1878,1,no description,60.2,1 -1879,I7N3G6G,1879,1,no description,60.2,1 -1880,I7N3G6G,1880,1,no description,60.2,1 -1881,I7N3G6G,1881,1,no description,60.2,1 -1882,I7N3G6G,1882,1,no description,60.2,1 -1883,I7N3G6G,1883,1,no description,60.2,1 -1884,I7N3G6G,1884,1,no description,60.2,1 -1885,I7N3G6G,1885,1,no description,60.2,1 -1886,I7N3G6G,1886,1,no description,60.2,1 -1887,I7N3G6G,1887,1,no description,60.2,1 -1888,I7N3G6G,1888,1,no description,60.2,1 -1889,I7N3G6G,1889,1,no description,60.2,1 -1890,I7N3G6G,1890,1,no description,60.2,1 -1891,I7N3G6G,1891,1,no description,60.2,1 -1892,I7N3G6G,1892,1,no description,60.2,1 -1893,I7N3G6G,1893,1,no description,60.2,1 -1894,I7N3G6G,1894,1,no description,60.2,1 -1895,I7N3G6G,1895,1,no description,60.2,1 -1896,I7N3G6G,1896,1,no description,60.2,1 -1897,I7N3G6G,1897,1,no description,60.2,1 -1898,I7N3G6G,1898,1,no description,60.2,1 -1899,I7N3G6G,1899,1,no description,60.2,1 -1900,I7N3G6G,1900,1,no description,60.2,1 -1901,I7N3G6G,1901,1,no description,60.2,1 -1902,I7N3G6G,1902,1,no description,60.2,1 -1903,I7N3G6G,1903,1,no description,60.2,1 -1904,I7N3G6G,1904,1,no description,60.2,1 -1905,I7N3G6G,1905,1,no description,60.2,1 -1906,I7N3G6G,1906,1,no description,60.2,1 -1907,I7N3G6G,1907,1,no description,60.2,1 -1908,I7N3G6G,1908,1,no description,60.2,1 -1909,I7N3G6G,1909,1,no description,60.2,1 -1910,I7N3G6G,1910,1,no description,60.2,1 -1911,I7N3G6G,1911,1,no description,60.2,1 -1912,I7N3G6G,1912,1,no description,60.2,1 -1913,I7N3G6G,1913,1,no description,60.2,1 -1914,I7N3G6G,1914,1,no description,60.2,1 -1915,I7N3G6G,1915,1,no description,60.2,1 -1916,I7N3G6G,1916,1,no description,60.2,1 -1917,I7N3G6G,1917,1,no description,60.2,1 -1918,I7N3G6G,1918,1,no description,60.2,1 -1919,I7N3G6G,1919,1,no description,60.2,1 -1920,I7N3G6G,1920,1,no description,60.2,1 -1921,I7N3G6G,1921,1,no description,60.2,1 -1922,I7N3G6G,1922,1,no description,60.2,1 -1923,I7N3G6G,1923,1,no description,60.2,1 -1924,I7N3G6G,1924,1,no description,60.2,1 -1925,I7N3G6G,1925,1,no description,60.2,1 -1926,I7N3G6G,1926,1,no description,60.2,1 -1927,I7N3G6G,1927,1,no description,60.2,1 -1928,I7N3G6G,1928,1,no description,60.2,1 -1929,I7N3G6G,1929,1,no description,60.2,1 -1930,I7N3G6G,1930,1,no description,60.2,1 -1931,I7N3G6G,1931,1,no description,60.2,1 -1932,I7N3G6G,1932,1,no description,60.2,1 -1933,I7N3G6G,1933,1,no description,60.2,1 -1934,I7N3G6G,1934,1,no description,60.2,1 -1935,I7N3G6G,1935,1,no description,60.2,1 -1936,I7N3G6G,1936,1,no description,60.2,1 -1937,I7N3G6G,1937,1,no description,60.2,1 -1938,I7N3G6G,1938,1,no description,60.2,1 -1939,I7N3G6G,1939,1,no description,60.2,1 -1940,I7N3G6G,1940,1,no description,60.2,1 -1941,I7N3G6G,1941,1,no description,60.2,1 -1942,I7N3G6G,1942,1,no description,60.2,1 -1943,I7N3G6G,1943,1,no description,60.2,1 -1944,I7N3G6G,1944,1,no description,60.2,1 -1945,I7N3G6G,1945,1,no description,60.2,1 -1946,I7N3G6G,1946,1,no description,60.2,1 -1947,I7N3G6G,1947,1,no description,60.2,1 -1948,I7N3G6G,1948,1,no description,60.2,1 -1949,I7N3G6G,1949,1,no description,60.2,1 -1950,I7N3G6G,1950,1,no description,60.2,1 -1951,I7N3G6G,1951,1,no description,60.2,1 -1952,I7N3G6G,1952,1,no description,60.2,1 -1953,I7N3G6G,1953,1,no description,60.2,1 -1954,I7N3G6G,1954,1,no description,60.2,1 -1955,I7N3G6G,1955,1,no description,60.2,1 -1956,I7N3G6G,1956,1,no description,60.2,1 -1957,I7N3G6G,1957,1,no description,60.2,1 -1958,I7N3G6G,1958,1,no description,60.2,1 -1959,I7N3G6G,1959,1,no description,60.2,1 -1960,I7N3G6G,1960,1,no description,60.2,1 -1961,I7N3G6G,1961,1,no description,60.2,1 -1962,I7N3G6G,1962,1,no description,60.2,1 -1963,I7N3G6G,1963,1,no description,60.2,1 -1964,I7N3G6G,1964,1,no description,60.2,1 -1965,I7N3G6G,1965,1,no description,60.2,1 -1966,I7N3G6G,1966,1,no description,60.2,1 -1967,I7N3G6G,1967,1,no description,60.2,1 -1968,I7N3G6G,1968,1,no description,60.2,1 -1969,I7N3G6G,1969,1,no description,60.2,1 -1970,I7N3G6G,1970,1,no description,60.2,1 -1971,I7N3G6G,1971,1,no description,60.2,1 -1972,I7N3G6G,1972,1,no description,60.2,1 -1973,I7N3G6G,1973,1,no description,60.2,1 -1974,I7N3G6G,1974,1,no description,60.2,1 -1975,I7N3G6G,1975,1,no description,60.2,1 -1976,I7N3G6G,1976,1,no description,60.2,1 -1977,I7N3G6G,1977,1,no description,60.2,1 -1978,I7N3G6G,1978,1,no description,60.2,1 -1979,I7N3G6G,1979,1,no description,60.2,1 -1980,I7N3G6G,1980,1,no description,60.2,1 -1981,I7N3G6G,1981,1,no description,60.2,1 -1982,I7N3G6G,1982,1,no description,60.2,1 -1983,I7N3G6G,1983,1,no description,60.2,1 -1984,I7N3G6G,1984,1,no description,60.2,1 -1985,I7N3G6G,1985,1,no description,60.2,1 -1986,I7N3G6G,1986,1,no description,60.2,1 -1987,I7N3G6G,1987,1,no description,60.2,1 -1988,I7N3G6G,1988,1,no description,60.2,1 -1989,I7N3G6G,1989,1,no description,60.2,1 -1990,I7N3G6G,1990,1,no description,60.2,1 -1991,I7N3G6G,1991,1,no description,60.2,1 -1992,I7N3G6G,1992,1,no description,60.2,1 -1993,I7N3G6G,1993,1,no description,60.2,1 -1994,I7N3G6G,1994,1,no description,60.2,1 -1995,I7N3G6G,1995,1,no description,60.2,1 -1996,I7N3G6G,1996,1,no description,60.2,1 -1997,I7N3G6G,1997,1,no description,60.2,1 -1998,I7N3G6G,1998,1,no description,60.2,1 -1999,I7N3G6G,1999,1,no description,60.2,1 -2000,I7N3G6G,2000,1,no description,60.2,1 -2001,I7N3G6G,2001,1,no description,60.2,1 -2002,I7N3G6G,2002,1,no description,60.2,1 -2003,I7N3G6G,2003,1,no description,60.2,1 -2004,I7N3G6G,2004,1,no description,60.2,1 -2005,I7N3G6G,2005,1,no description,60.2,1 -2006,I7N3G6G,2006,1,no description,60.2,1 -2007,I7N3G6G,2007,1,no description,60.2,1 -2008,I7N3G6G,2008,1,no description,60.2,1 -2009,I7N3G6G,2009,1,no description,60.2,1 -2010,I7N3G6G,2010,1,no description,60.2,1 -2011,I7N3G6G,2011,1,no description,60.2,1 -2012,I7N3G6G,2012,1,no description,60.2,1 -2013,I7N3G6G,2013,1,no description,60.2,1 -2014,I7N3G6G,2014,1,no description,60.2,1 -2015,I7N3G6G,2015,1,no description,60.2,1 -2016,I7N3G6G,2016,1,no description,60.2,1 -2017,I7N3G6G,2017,1,no description,60.2,1 -2018,I7N3G6G,2018,1,no description,60.2,1 -2019,I7N3G6G,2019,1,no description,60.2,1 -2020,I7N3G6G,2020,1,no description,60.2,1 -2021,I7N3G6G,2021,1,no description,60.2,1 -2022,I7N3G6G,2022,1,no description,60.2,1 -2023,I7N3G6G,2023,1,no description,60.2,1 -2024,I7N3G6G,2024,1,no description,60.2,1 -2025,I7N3G6G,2025,1,no description,60.2,1 -2026,I7N3G6G,2026,1,no description,60.2,1 -2027,I7N3G6G,2027,1,no description,60.2,1 -2028,I7N3G6G,2028,1,no description,60.2,1 -2029,I7N3G6G,2029,1,no description,60.2,1 -2030,I7N3G6G,2030,1,no description,60.2,1 -2031,I7N3G6G,2031,1,no description,60.2,1 -2032,I7N3G6G,2032,1,no description,60.2,1 -2033,I7N3G6G,2033,1,no description,60.2,1 -2034,I7N3G6G,2034,1,no description,60.2,1 -2035,I7N3G6G,2035,1,no description,60.2,1 -2036,I7N3G6G,2036,1,no description,60.2,1 -2037,I7N3G6G,2037,1,no description,60.2,1 -2038,I7N3G6G,2038,1,no description,60.2,1 -2039,I7N3G6G,2039,1,no description,60.2,1 -2040,I7N3G6G,2040,1,no description,60.2,1 -2041,I7N3G6G,2041,1,no description,60.2,1 -2042,I7N3G6G,2042,1,no description,60.2,1 -2043,I7N3G6G,2043,1,no description,60.2,1 -2044,I7N3G6G,2044,1,no description,60.2,1 -2045,I7N3G6G,2045,1,no description,60.2,1 -2046,I7N3G6G,2046,1,no description,60.2,1 -2047,I7N3G6G,2047,1,no description,60.2,1 -2048,I7N3G6G,2048,1,no description,60.2,1 -2049,I7N3G6G,2049,1,no description,60.2,1 -2050,I7N3G6G,2050,1,no description,60.2,1 -2051,I7N3G6G,2051,1,no description,60.2,1 -2052,I7N3G6G,2052,1,no description,60.2,1 -2053,I7N3G6G,2053,1,no description,60.2,1 -2054,I7N3G6G,2054,1,no description,60.2,1 -2055,I7N3G6G,2055,1,no description,60.2,1 -2056,I7N3G6G,2056,1,no description,60.2,1 -2057,I7N3G6G,2057,1,no description,60.2,1 -2058,I7N3G6G,2058,1,no description,60.2,1 -2059,I7N3G6G,2059,1,no description,60.2,1 -2060,I7N3G6G,2060,1,no description,60.2,1 -2061,I7N3G6G,2061,1,no description,60.2,1 -2062,I7N3G6G,2062,1,no description,60.2,1 -2063,I7N3G6G,2063,1,no description,60.2,1 -2064,I7N3G6G,2064,1,no description,60.2,1 -2065,I7N3G6G,2065,1,no description,60.2,1 -2066,I7N3G6G,2066,1,no description,60.2,1 -2067,I7N3G6G,2067,1,no description,60.2,1 -2068,I7N3G6G,2068,1,no description,60.2,1 -2069,I7N3G6G,2069,1,no description,60.2,1 -2070,I7N3G6G,2070,1,no description,60.2,1 -2071,I7N3G6G,2071,1,no description,60.2,1 -2072,I7N3G6G,2072,1,no description,60.2,1 -2073,I7N3G6G,2073,1,no description,60.2,1 -2074,I7N3G6G,2074,1,no description,60.2,1 -2075,I7N3G6G,2075,1,no description,60.2,1 -2076,I7N3G6G,2076,1,no description,60.2,1 -2077,I7N3G6G,2077,1,no description,60.2,1 -2078,I7N3G6G,2078,1,no description,60.2,1 -2079,I7N3G6G,2079,1,no description,60.2,1 -2080,I7N3G6G,2080,1,no description,60.2,1 -2081,I7N3G6G,2081,1,no description,60.2,1 -2082,I7N3G6G,2082,1,no description,60.2,1 -2083,I7N3G6G,2083,1,no description,60.2,1 -2084,I7N3G6G,2084,1,no description,60.2,1 -2085,I7N3G6G,2085,1,no description,60.2,1 -2086,I7N3G6G,2086,1,no description,60.2,1 -2087,I7N3G6G,2087,1,no description,60.2,1 -2088,I7N3G6G,2088,1,no description,60.2,1 -2089,I7N3G6G,2089,1,no description,60.2,1 -2090,I7N3G6G,2090,1,no description,60.2,1 -2091,I7N3G6G,2091,1,no description,60.2,1 -2092,I7N3G6G,2092,1,no description,60.2,1 -2093,I7N3G6G,2093,1,no description,60.2,1 -2094,I7N3G6G,2094,1,no description,60.2,1 -2095,I7N3G6G,2095,1,no description,60.2,1 -2096,I7N3G6G,2096,1,no description,60.2,1 -2097,I7N3G6G,2097,1,no description,60.2,1 -2098,I7N3G6G,2098,1,no description,60.2,1 -2099,I7N3G6G,2099,1,no description,60.2,1 -2100,I7N3G6G,2100,1,no description,60.2,1 -2101,I7N3G6G,2101,1,no description,60.2,1 -2102,I7N3G6G,2102,1,no description,60.2,1 -2103,I7N3G6G,2103,1,no description,60.2,1 -2104,I7N3G6G,2104,1,no description,60.2,1 -2105,I7N3G6G,2105,1,no description,60.2,1 -2106,I7N3G6G,2106,1,no description,60.2,1 -2107,I7N3G6G,2107,1,no description,60.2,1 -2108,I7N3G6G,2108,1,no description,60.2,1 -2109,I7N3G6G,2109,1,no description,60.2,1 -2110,I7N3G6G,2110,1,no description,60.2,1 -2111,I7N3G6G,2111,1,no description,60.2,1 -2112,I7N3G6G,2112,1,no description,60.2,1 -2113,I7N3G6G,2113,1,no description,60.2,1 -2114,I7N3G6G,2114,1,no description,60.2,1 -2115,I7N3G6G,2115,1,no description,60.2,1 -2116,I7N3G6G,2116,1,no description,60.2,1 -2117,I7N3G6G,2117,1,no description,60.2,1 -2118,I7N3G6G,2118,1,no description,60.2,1 -2119,I7N3G6G,2119,1,no description,60.2,1 -2120,I7N3G6G,2120,1,no description,60.2,1 -2121,I7N3G6G,2121,1,no description,60.2,1 -2122,I7N3G6G,2122,1,no description,60.2,1 -2123,I7N3G6G,2123,1,no description,60.2,1 -2124,I7N3G6G,2124,1,no description,60.2,1 -2125,I7N3G6G,2125,1,no description,60.2,1 -2126,I7N3G6G,2126,1,no description,60.2,1 -2127,I7N3G6G,2127,1,no description,60.2,1 -2128,I7N3G6G,2128,1,no description,60.2,1 -2129,I7N3G6G,2129,1,no description,60.2,1 -2130,I7N3G6G,2130,1,no description,60.2,1 -2131,I7N3G6G,2131,1,no description,60.2,1 -2132,I7N3G6G,2132,1,no description,60.2,1 -2133,I7N3G6G,2133,1,no description,60.2,1 -2134,I7N3G6G,2134,1,no description,60.2,1 -2135,I7N3G6G,2135,1,no description,60.2,1 -2136,I7N3G6G,2136,1,no description,60.2,1 -2137,I7N3G6G,2137,1,no description,60.2,1 -2138,I7N3G6G,2138,1,no description,60.2,1 -2139,I7N3G6G,2139,1,no description,60.2,1 -2140,I7N3G6G,2140,1,no description,60.2,1 -2141,I7N3G6G,2141,1,no description,60.2,1 -2142,I7N3G6G,2142,1,no description,60.2,1 -2143,I7N3G6G,2143,1,no description,60.2,1 -2144,I7N3G6G,2144,1,no description,60.2,1 -2145,I7N3G6G,2145,1,no description,60.2,1 -2146,I7N3G6G,2146,1,no description,60.2,1 -2147,I7N3G6G,2147,1,no description,60.2,1 -2148,I7N3G6G,2148,1,no description,60.2,1 -2149,I7N3G6G,2149,1,no description,60.2,1 -2150,I7N3G6G,2150,1,no description,60.2,1 -2151,I7N3G6G,2151,1,no description,60.2,1 -2152,I7N3G6G,2152,1,no description,60.2,1 -2153,I7N3G6G,2153,1,no description,60.2,1 -2154,I7N3G6G,2154,1,no description,60.2,1 -2155,I7N3G6G,2155,1,no description,60.2,1 -2156,I7N3G6G,2156,1,no description,60.2,1 -2157,I7N3G6G,2157,1,no description,60.2,1 -2158,I7N3G6G,2158,1,no description,60.2,1 -2159,I7N3G6G,2159,1,no description,60.2,1 -2160,I7N3G6G,2160,1,no description,60.2,1 -2161,I7N3G6G,2161,1,no description,60.2,1 -2162,I7N3G6G,2162,1,no description,60.2,1 -2163,I7N3G6G,2163,1,no description,60.2,1 -2164,I7N3G6G,2164,1,no description,60.2,1 -2165,I7N3G6G,2165,1,no description,60.2,1 -2166,I7N3G6G,2166,1,no description,60.2,1 -2167,I7N3G6G,2167,1,no description,60.2,1 -2168,I7N3G6G,2168,1,no description,60.2,1 -2169,I7N3G6G,2169,1,no description,60.2,1 -2170,I7N3G6G,2170,1,no description,60.2,1 -2171,I7N3G6G,2171,1,no description,60.2,1 -2172,I7N3G6G,2172,1,no description,60.2,1 -2173,I7N3G6G,2173,1,no description,60.2,1 -2174,I7N3G6G,2174,1,no description,60.2,1 -2175,I7N3G6G,2175,1,no description,60.2,1 -2176,I7N3G6G,2176,1,no description,60.2,1 -2177,I7N3G6G,2177,1,no description,60.2,1 -2178,I7N3G6G,2178,1,no description,60.2,1 -2179,I7N3G6G,2179,1,no description,60.2,1 -2180,I7N3G6G,2180,1,no description,60.2,1 -2181,I7N3G6G,2181,1,no description,60.2,1 -2182,I7N3G6G,2182,1,no description,60.2,1 -2183,I7N3G6G,2183,1,no description,60.2,1 -2184,I7N3G6G,2184,1,no description,60.2,1 -2185,I7N3G6G,2185,1,no description,60.2,1 -2186,I7N3G6G,2186,1,no description,60.2,1 -2187,I7N3G6G,2187,1,no description,60.2,1 -2188,I7N3G6G,2188,1,no description,60.2,1 -2189,I7N3G6G,2189,1,no description,60.2,1 -2190,I7N3G6G,2190,1,no description,60.2,1 -2191,I7N3G6G,2191,1,no description,60.2,1 -2192,I7N3G6G,2192,1,no description,60.2,1 -2193,I7N3G6G,2193,1,no description,60.2,1 -2194,I7N3G6G,2194,1,no description,60.2,1 -2195,I7N3G6G,2195,1,no description,60.2,1 -2196,I7N3G6G,2196,1,no description,60.2,1 -2197,I7N3G6G,2197,1,no description,60.2,1 -2198,I7N3G6G,2198,1,no description,60.2,1 -2199,I7N3G6G,2199,1,no description,60.2,1 -2200,I7N3G6G,2200,1,no description,60.2,1 -2201,I7N3G6G,2201,1,no description,60.2,1 -2202,I7N3G6G,2202,1,no description,60.2,1 -2203,I7N3G6G,2203,1,no description,60.2,1 -2204,I7N3G6G,2204,1,no description,60.2,1 -2205,I7N3G6G,2205,1,no description,60.2,1 -2206,I7N3G6G,2206,1,no description,60.2,1 -2207,I7N3G6G,2207,1,no description,60.2,1 -2208,I7N3G6G,2208,1,no description,60.2,1 -2209,I7N3G6G,2209,1,no description,60.2,1 -2210,I7N3G6G,2210,1,no description,60.2,1 -2211,I7N3G6G,2211,1,no description,60.2,1 -2212,I7N3G6G,2212,1,no description,60.2,1 -2213,I7N3G6G,2213,1,no description,60.2,1 -2214,I7N3G6G,2214,1,no description,60.2,1 -2215,I7N3G6G,2215,1,no description,60.2,1 -2216,I7N3G6G,2216,1,no description,60.2,1 -2217,I7N3G6G,2217,1,no description,60.2,1 -2218,I7N3G6G,2218,1,no description,60.2,1 -2219,I7N3G6G,2219,1,no description,60.2,1 -2220,I7N3G6G,2220,1,no description,60.2,1 -2221,I7N3G6G,2221,1,no description,60.2,1 -2222,I7N3G6G,2222,1,no description,60.2,1 -2223,I7N3G6G,2223,1,no description,60.2,1 -2224,I7N3G6G,2224,1,no description,60.2,1 -2225,I7N3G6G,2225,1,no description,60.2,1 -2226,I7N3G6G,2226,1,no description,60.2,1 diff --git a/packages/itmat-job-executor/test/testFiles/FieldCurator.tsv b/packages/itmat-job-executor/test/testFiles/FieldCurator.tsv deleted file mode 100644 index b21108422..000000000 --- a/packages/itmat-job-executor/test/testFiles/FieldCurator.tsv +++ /dev/null @@ -1,27 +0,0 @@ -ID,Database,TableName,TableID,SequentialOrder,QuestionNumber,Fieldname,Label,Label_DE,Label_NL,Label_IT,Label_ES,Label_PL,Label_F,EligibleAnswer,IneligibleAnswer,Validation,DataType,ControlType,SystemGenerated,ValueList,Length,DisplayFormat,Nullable,Required,Mandatory,CollectIf,NotMapped,DefaultValue,RegEx,RegExErrorMsg,ShowOnIndexView,Comments -1,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -2,IDEAFAST,Participants,,,,GenderID,Gender ID,,,,,,,,,,int,,False,,,,True,False,True,,False,,,,False, -3,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -4,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -5,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -6,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -7,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -8,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -9,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -10,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -11,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -12,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -13,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -14,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -15,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -16,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -17,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -18,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -19,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -20,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -21,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -22,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -23,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -24,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -25,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -26,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, \ No newline at end of file diff --git a/packages/itmat-job-executor/test/testFiles/FieldCurator_1000.tsv b/packages/itmat-job-executor/test/testFiles/FieldCurator_1000.tsv deleted file mode 100644 index 14c8b7523..000000000 --- a/packages/itmat-job-executor/test/testFiles/FieldCurator_1000.tsv +++ /dev/null @@ -1,1227 +0,0 @@ -ID,Database,TableName,TableID,SequentialOrder,QuestionNumber,Fieldname,Label,Label_DE,Label_NL,Label_IT,Label_ES,Label_PL,Label_F,EligibleAnswer,IneligibleAnswer,Validation,DataType,ControlType,SystemGenerated,ValueList,Length,DisplayFormat,Nullable,Required,Mandatory,CollectIf,NotMapped,DefaultValue,RegEx,RegExErrorMsg,ShowOnIndexView,Comments -1,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -2,IDEAFAST,Participants,,,,GenderID,Gender ID,,,,,,,,,,int,,False,,,,True,False,True,,False,,,,False, -3,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -4,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -5,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -6,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -7,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -8,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -9,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -10,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -11,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -12,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -13,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -14,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -15,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -16,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -17,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -18,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -19,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -20,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -21,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -22,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -23,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -24,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -25,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -26,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -27,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -28,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -29,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -30,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -31,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -32,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -33,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -34,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -35,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -36,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -37,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -38,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -39,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -40,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -41,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -42,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -43,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -44,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -45,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -46,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -47,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -48,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -49,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -50,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -51,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -52,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -53,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -54,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -55,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -56,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -57,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -58,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -59,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -60,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -61,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -62,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -63,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -64,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -65,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -66,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -67,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -68,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -69,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -70,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -71,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -72,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -73,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -74,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -75,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -76,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -77,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -78,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -79,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -80,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -81,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -82,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -83,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -84,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -85,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -86,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -87,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -88,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -89,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -90,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -91,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -92,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -93,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -94,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -95,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -96,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -97,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -98,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -99,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -100,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -101,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -102,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -103,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -104,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -105,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -106,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -107,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -108,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -109,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -110,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -111,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -112,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -113,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -114,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -115,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -116,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -117,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -118,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -119,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -120,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -121,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -122,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -123,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -124,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -125,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -126,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -127,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -128,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -129,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -130,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -131,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -132,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -133,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -134,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -135,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -136,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -137,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -138,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -139,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -140,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -141,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -142,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -143,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -144,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -145,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -146,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -147,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -148,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -149,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -150,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -151,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -152,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -153,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -154,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -155,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -156,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -157,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -158,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -159,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -160,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -161,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -162,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -163,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -164,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -165,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -166,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -167,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -168,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -169,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -170,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -171,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -172,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -173,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -174,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -175,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -176,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -177,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -178,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -179,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -180,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -181,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -182,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -183,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -184,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -185,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -186,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -187,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -188,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -189,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -190,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -191,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -192,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -193,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -194,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -195,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -196,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -197,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -198,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -199,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -200,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -201,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -202,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -203,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -204,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -205,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -206,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -207,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -208,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -209,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -210,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -211,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -212,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -213,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -214,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -215,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -216,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -217,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -218,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -219,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -220,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -221,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -222,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -223,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -224,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -225,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -226,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -227,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -228,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -229,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -230,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -231,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -232,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -233,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -234,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -235,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -236,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -237,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -238,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -239,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -240,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -241,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -242,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -243,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -244,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -245,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -246,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -247,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -248,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -249,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -250,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -251,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -252,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -253,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -254,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -255,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -256,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -257,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -258,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -259,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -260,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -261,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -262,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -263,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -264,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -265,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -266,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -267,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -268,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -269,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -270,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -271,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -272,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -273,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -274,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -275,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -276,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -277,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -278,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -279,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -280,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -281,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -282,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -283,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -284,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -285,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -286,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -287,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -288,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -289,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -290,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -291,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -292,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -293,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -294,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -295,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -296,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -297,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -298,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -299,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -300,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -301,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -302,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -303,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -304,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -305,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -306,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -307,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -308,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -309,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -310,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -311,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -312,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -313,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -314,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -315,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -316,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -317,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -318,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -319,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -320,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -321,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -322,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -323,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -324,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -325,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -326,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -327,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -328,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -329,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -330,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -331,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -332,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -333,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -334,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -335,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -336,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -337,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -338,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -339,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -340,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -341,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -342,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -343,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -344,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -345,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -346,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -347,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -348,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -349,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -350,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -351,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -352,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -353,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -354,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -355,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -356,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -357,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -358,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -359,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -360,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -361,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -362,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -363,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -364,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -365,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -366,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -367,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -368,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -369,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -370,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -371,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -372,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -373,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -374,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -375,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -376,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -377,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -378,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -379,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -380,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -381,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -382,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -383,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -384,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -385,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -386,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -387,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -388,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -389,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -390,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -391,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -392,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -393,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -394,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -395,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -396,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -397,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -398,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -399,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -400,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -401,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -402,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -403,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -404,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -405,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -406,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -407,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -408,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -409,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -410,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -411,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -412,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -413,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -414,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -415,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -416,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -417,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -418,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -419,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -420,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -421,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -422,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -423,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -424,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -425,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -426,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -427,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -428,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -429,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -430,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -431,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -432,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -433,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -434,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -435,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -436,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -437,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -438,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -439,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -440,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -441,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -442,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -443,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -444,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -445,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -446,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -447,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -448,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -449,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -450,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -451,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -452,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -453,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -454,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -455,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -456,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -457,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -458,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -459,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -460,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -461,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -462,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -463,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -464,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -465,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -466,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -467,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -468,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -469,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -470,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -471,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -472,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -473,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -474,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -475,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -476,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -477,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -478,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -479,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -480,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -481,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -482,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -483,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -484,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -485,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -486,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -487,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -488,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -489,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -490,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -491,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -492,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -493,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -494,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -495,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -496,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -497,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -498,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -499,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -500,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -501,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -502,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -503,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -504,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -505,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -506,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -507,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -508,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -509,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -510,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -511,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -512,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -513,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -514,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -515,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -516,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -517,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -518,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -519,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -520,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -521,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -522,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -523,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -524,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -525,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -526,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -527,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -528,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -529,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -530,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -531,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -532,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -533,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -534,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -535,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -536,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -537,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -538,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -539,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -540,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -541,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -542,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -543,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -544,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -545,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -546,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -547,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -548,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -549,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -550,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -551,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -552,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -553,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -554,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -555,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -556,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -557,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -558,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -559,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -560,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -561,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -562,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -563,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -564,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -565,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -566,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -567,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -568,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -569,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -570,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -571,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -572,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -573,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -574,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -575,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -576,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -577,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -578,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -579,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -580,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -581,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -582,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -583,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -584,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -585,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -586,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -587,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -588,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -589,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -590,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -591,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -592,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -593,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -594,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -595,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -596,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -597,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -598,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -599,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -600,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -601,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -602,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -603,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -604,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -605,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -606,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -607,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -608,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -609,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -610,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -611,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -612,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -613,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -614,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -615,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -616,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -617,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -618,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -619,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -620,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -621,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -622,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -623,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -624,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -625,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -626,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -627,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -628,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -629,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -630,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -631,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -632,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -633,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -634,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -635,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -636,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -637,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -638,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -639,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -640,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -641,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -642,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -643,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -644,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -645,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -646,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -647,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -648,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -649,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -650,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -651,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -652,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -653,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -654,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -655,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -656,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -657,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -658,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -659,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -660,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -661,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -662,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -663,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -664,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -665,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -666,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -667,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -668,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -669,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -670,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -671,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -672,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -673,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -674,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -675,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -676,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -677,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -678,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -679,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -680,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -681,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -682,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -683,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -684,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -685,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -686,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -687,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -688,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -689,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -690,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -691,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -692,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -693,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -694,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -695,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -696,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -697,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -698,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -699,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -700,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -701,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -702,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -703,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -704,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -705,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -706,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -707,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -708,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -709,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -710,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -711,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -712,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -713,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -714,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -715,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -716,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -717,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -718,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -719,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -720,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -721,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -722,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -723,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -724,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -725,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -726,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -727,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -728,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -729,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -730,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -731,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -732,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -733,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -734,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -735,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -736,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -737,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -738,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -739,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -740,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -741,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -742,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -743,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -744,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -745,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -746,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -747,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -748,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -749,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -750,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -751,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -752,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -753,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -754,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -755,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -756,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -757,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -758,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -759,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -760,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -761,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -762,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -763,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -764,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -765,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -766,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -767,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -768,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -769,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -770,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -771,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -772,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -773,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -774,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -775,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -776,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -777,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -778,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -779,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -780,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -781,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -782,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -783,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -784,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -785,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -786,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -787,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -788,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -789,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -790,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -791,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -792,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -793,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -794,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -795,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -796,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -797,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -798,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -799,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -800,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -801,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -802,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -803,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -804,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -805,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -806,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -807,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -808,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -809,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -810,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -811,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -812,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -813,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -814,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -815,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -816,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -817,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -818,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -819,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -820,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -821,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -822,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -823,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -824,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -825,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -826,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -827,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -828,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -829,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -830,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -831,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -832,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -833,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -834,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -835,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -836,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -837,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -838,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -839,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -840,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -841,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -842,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -843,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -844,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -845,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -846,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -847,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -848,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -849,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -850,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -851,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -852,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -853,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -854,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -855,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -856,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -857,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -858,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -859,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -860,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -861,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -862,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -863,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -864,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -865,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -866,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -867,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -868,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -869,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -870,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -871,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -872,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -873,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -874,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -875,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -876,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -877,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -878,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -879,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -880,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -881,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -882,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -883,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -884,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -885,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -886,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -887,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -888,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -889,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -890,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -891,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -892,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -893,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -894,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -895,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -896,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -897,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -898,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -899,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -900,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -901,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -902,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -903,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -904,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -905,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -906,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -907,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -908,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -909,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -910,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -911,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -912,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -913,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -914,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -915,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -916,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -917,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -918,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -919,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -920,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -921,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -922,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -923,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -924,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -925,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -926,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -927,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -928,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -929,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -930,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -931,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -932,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -933,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -934,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -935,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -936,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -937,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -938,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -939,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -940,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -941,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -942,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -943,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -944,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -945,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -946,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -947,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -948,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -949,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -950,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -951,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -952,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -953,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -954,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -955,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -956,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -957,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -958,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -959,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -960,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -961,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -962,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -963,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -964,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -965,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -966,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -967,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -968,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -969,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -970,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -971,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -972,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -973,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -974,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -975,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -976,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -977,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -978,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -979,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -980,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -981,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -982,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -983,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -984,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -985,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -986,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -987,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -988,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -989,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -990,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -991,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -992,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -993,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -994,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -995,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -996,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -997,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -998,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -999,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1000,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1001,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1002,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1003,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1004,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1005,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1006,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1007,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1008,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1009,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1010,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1011,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1012,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1013,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1014,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1015,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1016,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1017,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1018,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1019,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1020,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1021,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1022,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1023,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1024,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1025,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1026,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1027,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1028,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1029,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1030,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1031,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1032,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1033,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1034,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1035,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1036,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1037,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1038,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1039,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1040,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1041,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1042,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1043,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1044,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1045,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1046,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1047,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1048,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1049,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1050,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1051,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1052,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1053,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1054,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1055,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1056,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1057,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1058,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1059,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1060,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1061,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1062,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1063,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1064,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1065,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1066,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1067,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1068,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1069,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1070,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1071,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1072,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1073,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1074,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1075,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1076,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1077,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1078,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1079,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1080,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1081,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1082,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1083,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1084,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1085,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1086,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1087,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1088,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1089,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1090,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1091,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1092,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1093,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1094,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1095,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1096,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1097,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1098,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1099,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1100,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1101,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1102,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1103,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1104,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1105,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1106,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1107,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1108,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1109,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1110,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1111,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1112,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1113,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1114,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1115,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1116,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1117,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1118,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1119,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1120,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1121,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1122,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1123,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1124,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1125,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1126,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1127,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1128,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1129,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1130,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1131,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1132,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1133,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1134,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1135,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1136,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1137,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1138,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1139,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1140,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1141,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1142,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1143,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1144,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1145,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1146,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1147,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1148,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1149,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1150,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1151,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1152,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1153,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1154,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1155,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1156,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1157,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1158,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1159,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1160,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1161,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1162,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1163,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1164,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1165,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1166,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1167,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1168,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1169,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1170,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1171,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1172,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1173,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1174,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1175,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1176,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1177,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1178,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1179,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1180,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1181,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1182,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1183,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1184,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1185,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1186,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1187,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1188,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1189,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1190,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1191,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1192,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1193,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1194,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1195,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1196,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1197,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1198,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1199,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1200,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1201,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1202,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1203,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1204,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1205,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1206,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1207,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1208,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1209,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1210,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1211,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1212,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1213,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1214,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1215,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1216,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1217,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1218,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1219,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1220,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1221,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1222,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1223,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1224,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1225,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1226,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, \ No newline at end of file diff --git a/packages/itmat-job-executor/test/testFiles/FieldCurator_error1.tsv b/packages/itmat-job-executor/test/testFiles/FieldCurator_error1.tsv deleted file mode 100644 index deefc3a80..000000000 --- a/packages/itmat-job-executor/test/testFiles/FieldCurator_error1.tsv +++ /dev/null @@ -1,1227 +0,0 @@ -ID,Database,TableName,TableID,SequentialOrder,QuestionNumber,Fieldname,Label,Label_DE,Label_NL,Label_IT,Label_ES,Label_PL,Label_F,EligibleAnswer,IneligibleAnswer,Validation,DataType,ControlType,SystemGenerated,ValueList,Length,DisplayFormat,Nullable,Required,Mandatory,CollectIf,NotMapped,DefaultValue,RegEx,RegExErrorMsg,ShowOnIndexView,Comments -1,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -2,IDEAFAST,Participants,,,,GenderID,Gender ID,,,,,,,,,,int,,False,,,,True,False,True,,False,,,,False, -3,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -4,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -5,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -6,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -7,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -8,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -9,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -10,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -11,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -12,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -13,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -13,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -15,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -16,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -17,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -18,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -19,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -20,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -21,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -22,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -23,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -24,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -25,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -26,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -27,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -28,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -29,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -30,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -31,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -32,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -33,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -34,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -35,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -36,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -37,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -38,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -39,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -40,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -41,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -42,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -43,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -44,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -45,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -46,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -47,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -48,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -49,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -50,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -51,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -52,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -53,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -54,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -55,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -56,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -57,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -58,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -59,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -60,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -61,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -62,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -63,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -64,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -65,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -66,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -67,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -68,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -69,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -70,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -71,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -72,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -73,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -74,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -75,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -76,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -77,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -78,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -79,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -80,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -81,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -82,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -83,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -84,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -85,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -86,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -87,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -88,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -89,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -90,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -91,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -92,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -93,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -94,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -95,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -96,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -97,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -98,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -99,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -100,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -101,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -102,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -103,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -104,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -105,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -106,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -107,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -108,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -109,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -110,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -111,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -112,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -113,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -114,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -115,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -116,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -117,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -118,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -119,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -120,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -121,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -122,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -123,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -124,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -125,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -126,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -127,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -128,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -129,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -130,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -131,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -132,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -133,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -134,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -135,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -136,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -137,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -138,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -139,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -140,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -141,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -142,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -143,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -144,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -145,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -146,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -147,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -148,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -149,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -150,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -151,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -152,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -153,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -154,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -155,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -156,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -157,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -158,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -159,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -160,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -161,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -162,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -163,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -164,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -165,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -166,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -167,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -168,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -169,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -170,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -171,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -172,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -173,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -174,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -175,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -176,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -177,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -178,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -179,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -180,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -181,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -182,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -183,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -184,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -185,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -186,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -187,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -188,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -189,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -190,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -191,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -192,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -193,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -194,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -195,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -196,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -197,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -198,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -199,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -200,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -201,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -202,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -203,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -204,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -205,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -206,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -207,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -208,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -209,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -210,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -211,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -212,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -213,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -214,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -215,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -216,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -217,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -218,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -219,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -220,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -221,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -222,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -223,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -224,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -225,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -226,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -227,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -228,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -229,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -230,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -231,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -232,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -233,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -234,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -235,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -236,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -237,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -238,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -239,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -240,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -241,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -242,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -243,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -244,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -245,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -246,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -247,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -248,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -249,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -250,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -251,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -252,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -253,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -254,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -255,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -256,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -257,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -258,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -259,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -260,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -261,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -262,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -263,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -264,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -265,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -266,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -267,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -268,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -269,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -270,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -271,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -272,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -273,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -274,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -275,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -276,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -277,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -278,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -279,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -280,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -281,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -282,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -283,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -284,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -285,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -286,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -287,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -288,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -289,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -290,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -291,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -292,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -293,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -294,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -295,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -296,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -297,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -298,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -299,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -300,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -301,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -302,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -303,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -304,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -305,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -306,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -307,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -308,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -309,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -310,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -311,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -312,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -313,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -314,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -315,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -316,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -317,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -318,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -319,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -320,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -321,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -322,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -323,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -324,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -325,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -326,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -327,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -328,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -329,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -330,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -331,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -332,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -333,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -334,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -335,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -336,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -337,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -338,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -339,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -340,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -341,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -342,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -343,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -344,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -345,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -346,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -347,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -348,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -349,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -350,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -351,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -352,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -353,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -354,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -355,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -356,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -357,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -358,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -359,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -360,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -361,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -362,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -363,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -364,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -365,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -366,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -367,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -368,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -369,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -370,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -371,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -372,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -373,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -374,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -375,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -376,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -377,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -378,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -379,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -380,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -381,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -382,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -383,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -384,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -385,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -386,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -387,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -388,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -389,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -390,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -391,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -392,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -393,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -394,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -395,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -396,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -397,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -398,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -399,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -400,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -401,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -402,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -403,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -404,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -405,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -406,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -407,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -408,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -409,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -410,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -411,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -412,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -413,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -414,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -415,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -416,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -417,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -418,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -419,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -420,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -421,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -422,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -423,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -424,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -425,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -426,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -427,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -428,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -429,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -430,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -431,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -432,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -433,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -434,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -435,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -436,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -437,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -438,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -439,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -440,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -441,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -442,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -443,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -444,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -445,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -446,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -447,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -448,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -449,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -450,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -451,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -452,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -453,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -454,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -455,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -456,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -457,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -458,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -459,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -460,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -461,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -462,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -463,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -464,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -465,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -466,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -467,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -468,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -469,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -470,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -471,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -472,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -473,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -474,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -475,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -476,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -477,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -478,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -479,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -480,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -481,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -482,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -483,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -484,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -485,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -486,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -487,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -488,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -489,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -490,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -491,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -492,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -493,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -494,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -495,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -496,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -497,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -498,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -499,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -500,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -501,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -502,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -503,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -504,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -505,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -506,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -507,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -508,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -509,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -510,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -511,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -512,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -513,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -514,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -515,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -516,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -517,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -518,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -519,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -520,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -521,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -522,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -523,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -524,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -525,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -526,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -527,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -528,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -529,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -530,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -531,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -532,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -533,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -534,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -535,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -536,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -537,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -538,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -539,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -540,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -541,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -542,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -543,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -544,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -545,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -546,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -547,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -548,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -549,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -550,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -551,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -552,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -553,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -554,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -555,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -556,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -557,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -558,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -559,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -560,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -561,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -562,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -563,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -564,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -565,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -566,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -567,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -568,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -569,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -570,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -571,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -572,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -573,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -574,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -575,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -576,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -577,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -578,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -579,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -580,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -581,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -582,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -583,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -584,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -585,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -586,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -587,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -588,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -589,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -590,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -591,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -592,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -593,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -594,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -595,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -596,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -597,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -598,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -599,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -600,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -601,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -602,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -603,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -604,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -605,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -606,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -607,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -608,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -609,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -610,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -611,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -612,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -613,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -614,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -615,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -616,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -617,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -618,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -619,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -620,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -621,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -622,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -623,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -624,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -625,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -626,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -627,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -628,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -629,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -630,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -631,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -632,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -633,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -634,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -635,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -636,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -637,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -638,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -639,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -640,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -641,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -642,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -643,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -644,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -645,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -646,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -647,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -648,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -649,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -650,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -651,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -652,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -653,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -654,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -655,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -656,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -657,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -658,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -659,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -660,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -661,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -662,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -663,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -664,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -665,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -666,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -667,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -668,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -669,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -670,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -671,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -672,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -673,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -674,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -675,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -676,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -677,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -678,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -679,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -680,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -681,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -682,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -683,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -684,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -685,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -686,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -687,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -688,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -689,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -690,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -691,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -692,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -693,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -694,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -695,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -696,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -697,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -698,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -699,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -700,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -701,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -702,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -703,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -704,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -705,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -706,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -707,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -708,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -709,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -710,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -711,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -712,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -713,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -714,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -715,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -716,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -717,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -718,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -719,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -720,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -721,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -722,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -723,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -724,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -725,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -726,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -727,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -728,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -729,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -730,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -731,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -732,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -733,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -734,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -735,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -736,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -737,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -738,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -739,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -740,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -741,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -742,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -743,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -744,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -745,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -746,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -747,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -748,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -749,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -750,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -751,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -752,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -753,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -754,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -755,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -756,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -757,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -758,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -759,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -760,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -761,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -762,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -763,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -764,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -765,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -766,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -767,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -768,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -769,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -770,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -771,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -772,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -773,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -774,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -775,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -776,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -777,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -778,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -779,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -780,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -781,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -782,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -783,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -784,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -785,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -786,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -787,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -788,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -789,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -790,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -791,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -792,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -793,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -794,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -795,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -796,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -797,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -798,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -799,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -800,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -801,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -802,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -803,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -804,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -805,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -806,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -807,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -808,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -809,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -810,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -811,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -812,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -813,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -814,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -815,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -816,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -817,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -818,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -819,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -820,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -821,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -822,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -823,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -824,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -825,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -826,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -827,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -828,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -829,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -830,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -831,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -832,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -833,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -834,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -835,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -836,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -837,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -838,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -839,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -840,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -841,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -842,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -843,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -844,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -845,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -846,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -847,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -848,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -849,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -850,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -851,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -852,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -853,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -854,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -855,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -856,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -857,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -858,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -859,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -860,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -861,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -862,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -863,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -864,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -865,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -866,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -867,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -868,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -869,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -870,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -871,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -872,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -873,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -874,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -875,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -876,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -877,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -878,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -879,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -880,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -881,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -882,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -883,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -884,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -885,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -886,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -887,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -888,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -889,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -890,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -891,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -892,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -893,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -894,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -895,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -896,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -897,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -898,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -899,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -900,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -901,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -902,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -903,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -904,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -905,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -906,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -907,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -908,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -909,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -910,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -911,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -912,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -913,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -914,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -915,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -916,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -917,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -918,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -919,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -920,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -921,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -922,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -923,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -924,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -925,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -926,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -927,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -928,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -929,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -930,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -931,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -932,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -933,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -934,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -935,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -936,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -937,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -938,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -939,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -940,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -941,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -942,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -943,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -944,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -945,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -946,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -947,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -948,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -949,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -950,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -951,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -952,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -953,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -954,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -955,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -956,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -957,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -958,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -959,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -960,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -961,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -962,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -963,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -964,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -965,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -966,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -967,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -968,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -969,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -970,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -971,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -972,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -973,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -974,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -975,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -976,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -977,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -978,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -979,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -980,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -981,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -982,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -983,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -984,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -985,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -986,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -987,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -988,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -989,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -990,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -991,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -992,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -993,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -994,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -995,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -996,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -997,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -998,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -999,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1000,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1001,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1002,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1003,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1004,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1005,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1006,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1007,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1008,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1009,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1010,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1011,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1012,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1013,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1014,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1015,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1016,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1017,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1018,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1019,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1020,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1021,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1022,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1023,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1024,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1025,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1026,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1027,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1028,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1029,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1030,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1031,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1032,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1033,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1034,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1035,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1036,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1037,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1038,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1039,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1040,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1041,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1042,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1043,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1044,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1045,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1046,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1047,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1048,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1049,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1050,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1051,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1052,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1053,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1054,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1055,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1056,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1057,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1058,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1059,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1060,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1061,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1062,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1063,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1064,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1065,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1066,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1067,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1068,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1069,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1070,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1071,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1072,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1073,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1074,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1075,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1076,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1077,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1078,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1079,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1080,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1081,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1082,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1083,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1084,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1085,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1086,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1087,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1088,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1089,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1090,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1091,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1092,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1093,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1094,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1095,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1096,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1097,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1098,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1099,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1100,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1101,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1102,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1103,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1104,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1105,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1106,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1107,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1108,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1109,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1110,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1111,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1112,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1113,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1114,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1115,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1116,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1117,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1118,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1119,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1120,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1121,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1122,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1123,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1124,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1125,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1126,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1127,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1128,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1129,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1130,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1131,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1132,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1133,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1134,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1135,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1136,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1137,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1138,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1139,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1140,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1141,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1142,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1143,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1144,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1145,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1146,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1147,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1148,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1149,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1150,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1151,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1152,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1153,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1154,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1155,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1156,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1157,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1158,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1159,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1160,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1161,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1162,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1163,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1164,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1165,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1166,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1167,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1168,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1169,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1170,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1171,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1172,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1173,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1174,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1175,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1176,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1177,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1178,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1179,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1180,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1181,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1182,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1183,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1184,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1185,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1186,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1187,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1188,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1189,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1190,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1191,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1192,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1193,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1194,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1195,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1196,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1197,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1198,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1199,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1200,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1201,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1202,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1203,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1204,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1205,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1206,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1207,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1208,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1209,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1210,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1211,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1212,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1213,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1214,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1215,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1216,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1217,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1218,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1219,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1220,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1221,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1222,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1223,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1224,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1225,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1226,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, \ No newline at end of file diff --git a/packages/itmat-job-executor/test/testFiles/FieldCurator_error2.tsv b/packages/itmat-job-executor/test/testFiles/FieldCurator_error2.tsv deleted file mode 100644 index 8a075b56b..000000000 --- a/packages/itmat-job-executor/test/testFiles/FieldCurator_error2.tsv +++ /dev/null @@ -1,1227 +0,0 @@ -ID,Database,TableName,TableID,SequentialOrder,QuestionNumber,Fieldname,Label,Label_DE,Label_NL,Label_IT,Label_ES,Label_PL,Label_F,EligibleAnswer,IneligibleAnswer,Validation,DataType,ControlType,SystemGenerated,ValueList,Length,DisplayFormat,Nullable,Required,Mandatory,CollectIf,NotMapped,DefaultValue,RegEx,RegExErrorMsg,ShowOnIndexView,Comments -1,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -2,IDEAFAST,Participants,,,,GenderID,Gender ID,,,,,,,,,,int,,False,,,,True,False,True,,False,,,,False, -3,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -4,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -5,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -6,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -7,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -8,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -9,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -10,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -11,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -12,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -13,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -14,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -15,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -16,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -17,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -18,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -19,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -20,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -21,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -22,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -23,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -24,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -25,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -26,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -27,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -28,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -29,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -30,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -31,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -32,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -33,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -34,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -35,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -36,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -37,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -38,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -39,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -40,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -41,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -42,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -43,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -44,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -45,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -46,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -47,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -48,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -49,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -50,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -51,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -52,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -53,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -54,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -55,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -56,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -57,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -58,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -59,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -60,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -61,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -62,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -63,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -64,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -65,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -66,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -67,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -68,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -69,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -70,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -71,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -72,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -73,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -74,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -75,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -76,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -77,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -78,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -79,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -80,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -81,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -82,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -83,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -84,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -85,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -86,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -87,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -88,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -89,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -90,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -91,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -92,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -93,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -94,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -95,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -96,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -97,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -98,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -99,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -100,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -101,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -102,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -103,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -104,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -105,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -106,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -107,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -108,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -109,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -110,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -111,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -112,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -113,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -114,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -115,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -116,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -117,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -118,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -119,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -120,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -121,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -122,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -123,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -124,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -125,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -126,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -127,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -128,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -129,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -130,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -131,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -132,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -133,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -134,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -135,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -136,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -137,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -138,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -139,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -140,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -141,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -142,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -143,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -144,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -145,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -146,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -147,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -148,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -149,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -150,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -151,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -152,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -153,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -154,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -155,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -156,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -157,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -158,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -159,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -160,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -161,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -162,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -163,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -164,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -165,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -166,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -167,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -168,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -169,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -170,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -171,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -172,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -173,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -174,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -175,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -176,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -177,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -178,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -179,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -180,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -181,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -182,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -183,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -184,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -185,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -186,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -187,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -188,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -189,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -190,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -191,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -192,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -193,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -194,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -195,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -196,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -197,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -198,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -199,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -200,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -201,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -202,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -203,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -204,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -205,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -206,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -207,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -208,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -209,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -210,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -211,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -212,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -213,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -214,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -215,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -216,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -217,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -218,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -219,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -220,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -221,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -222,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -223,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -224,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -225,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -226,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -227,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -228,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -229,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -230,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -231,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -232,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -233,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -234,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -235,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -236,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -237,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -238,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -239,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -240,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -241,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -242,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -243,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -244,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -245,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -246,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -247,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -248,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -249,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -250,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -251,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -252,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -253,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -254,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -255,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -256,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -257,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -258,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -259,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -260,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -261,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -262,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -263,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -264,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -265,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -266,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -267,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -268,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -269,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -270,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -271,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -272,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -273,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -274,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -275,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -276,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -277,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -278,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -279,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -280,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -281,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -282,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -283,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -284,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -285,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -286,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -287,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -288,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -289,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -290,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -291,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -292,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -293,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -294,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -295,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -296,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -297,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -298,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -299,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -300,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -301,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -302,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -303,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -304,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -305,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -306,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -307,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -308,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -309,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -310,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -311,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -312,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -313,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -314,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -315,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -316,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -317,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -318,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -319,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -320,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -321,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -322,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -323,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -324,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -325,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -326,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -327,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -328,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -329,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -330,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -331,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -332,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -333,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -334,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -335,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -336,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -337,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -338,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -339,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -340,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -341,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -342,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -343,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -344,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -345,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -346,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -347,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -348,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -349,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -350,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -351,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -352,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -353,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -354,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -355,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -356,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -357,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -358,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -359,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -360,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -361,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -362,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -363,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -364,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -365,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -366,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -367,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -368,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -369,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -370,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -371,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -372,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -373,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -374,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -375,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -376,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -377,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -378,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -379,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -380,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -381,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -382,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -383,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -384,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -385,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -386,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -387,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -388,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -389,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -390,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -391,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -392,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -393,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -394,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -395,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -396,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -397,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -398,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -399,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -400,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -401,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -402,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -403,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -404,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -405,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -406,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -407,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -408,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -409,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -410,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -411,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -412,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -413,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -414,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -415,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -416,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -417,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -418,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -419,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -420,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -421,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -422,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -423,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -424,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -425,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -426,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -427,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -428,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -429,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -430,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -431,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -432,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -433,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -434,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -435,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -436,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -437,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -438,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -439,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -440,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -441,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -442,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -443,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -444,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -445,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -446,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -447,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -448,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -449,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -450,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -451,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -452,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -453,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -454,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -455,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -456,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -457,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -458,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -459,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -460,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -461,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -462,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -463,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -464,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -465,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -466,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -467,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -468,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -469,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -470,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -471,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -472,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -473,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -474,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -475,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -476,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -477,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -478,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -479,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -480,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -481,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -482,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -483,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -484,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -485,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -486,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -487,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -488,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -489,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -490,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -491,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -492,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -493,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -494,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -495,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -496,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -497,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -498,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -499,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -500,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -501,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -502,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -503,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -504,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -505,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -506,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -507,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -508,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -509,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -510,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -511,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -512,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -513,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -514,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -515,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -516,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -517,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -518,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -519,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -520,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -521,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -522,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -523,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -524,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -525,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -526,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -527,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -528,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -529,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -530,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -531,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -532,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -533,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -534,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -535,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -536,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -537,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -538,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -539,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -540,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -541,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -542,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -543,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -544,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -545,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -546,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -547,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -548,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -549,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -550,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -551,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -552,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -553,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -554,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -555,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -556,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -557,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -558,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -559,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -560,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -561,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -562,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -563,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -564,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -565,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -566,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -567,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -568,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -569,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -570,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -571,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -572,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -573,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -574,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -575,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -576,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -577,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -578,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -579,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -580,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -581,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -582,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -583,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -584,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -585,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -586,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -587,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -588,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -589,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -590,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -591,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -592,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -593,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -594,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -595,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -596,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -597,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -598,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -599,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -600,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -601,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -602,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -603,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -604,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -605,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -606,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -607,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -608,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -609,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -610,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -611,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -612,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -613,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -614,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -615,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -616,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -617,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -618,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -619,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -620,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -621,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -622,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -623,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -624,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -625,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -626,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -627,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -628,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -629,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -630,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -631,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -632,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -633,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -634,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -635,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -636,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -637,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -638,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -639,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -640,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -641,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -642,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -643,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -644,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -645,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -646,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -647,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -648,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -649,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -650,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -651,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -652,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -653,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -654,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -655,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -656,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -657,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -658,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -659,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -660,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -661,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -662,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -663,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -664,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -665,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -666,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -667,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -668,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -669,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -670,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -671,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -672,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -673,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -674,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -675,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -676,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -677,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -678,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -679,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -680,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -681,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -682,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -683,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -684,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -685,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -686,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -687,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -688,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -689,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -690,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -691,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -692,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -693,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -694,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -695,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -696,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -697,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -698,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -699,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -700,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -701,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -702,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -703,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -704,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -705,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -706,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -707,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -708,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -709,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -710,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -711,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -712,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -713,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -714,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -715,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -716,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -717,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -718,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -719,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -720,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -721,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -722,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -723,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -724,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -725,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -726,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -727,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -728,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -729,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -730,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -731,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -732,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -733,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -734,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -735,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -736,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -737,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -738,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -739,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -740,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -741,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -742,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -743,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -744,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -745,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -746,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -747,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -748,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -749,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -750,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -751,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -752,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -753,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -754,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -755,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -756,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -757,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -758,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -759,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -760,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -761,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -762,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -763,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -764,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -765,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -766,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -767,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -768,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -769,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -770,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -771,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -772,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -773,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -774,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -775,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -776,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -777,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -778,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -779,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -780,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -781,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -782,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -783,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -784,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -785,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -786,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -787,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -788,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -789,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -790,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -791,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -792,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -793,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -794,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -795,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -796,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -797,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -798,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -799,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -800,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -801,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -802,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -803,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -804,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -805,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -806,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -807,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -808,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -809,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -810,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -811,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -812,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -813,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -814,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -815,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -816,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -817,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -818,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -819,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -820,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -821,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -822,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -823,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -824,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -825,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -826,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -827,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -828,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -829,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -830,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -831,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -832,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -833,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -834,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -835,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -836,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -837,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -838,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -839,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -840,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -841,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -842,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -843,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -844,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -845,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -846,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -847,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -848,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -849,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -850,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -851,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -852,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -853,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -854,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -855,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -856,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -857,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -858,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -859,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -860,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -861,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -862,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -863,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -864,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -865,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -866,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -867,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -868,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -869,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -870,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -871,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -872,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -873,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -874,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -875,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -876,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -877,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -878,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -879,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -880,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -881,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -882,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -883,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -884,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -885,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -886,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -887,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -888,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -889,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -890,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -891,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -892,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -893,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -894,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -895,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -896,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -897,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -898,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -899,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -900,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -901,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -902,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -903,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -904,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -905,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -906,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -907,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -908,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -909,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -910,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -911,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -912,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -913,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -914,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -915,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -916,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -917,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -918,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -919,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -920,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -921,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -922,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -923,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -924,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -925,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -926,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -927,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -928,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -929,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -930,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -931,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -932,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -933,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -934,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -935,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -936,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -937,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -938,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -939,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -940,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -941,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -942,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -943,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -944,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -945,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -946,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -947,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -948,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -949,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -950,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -951,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -952,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -953,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -954,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -955,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -956,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -957,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -958,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -959,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -960,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -961,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -962,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -963,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -964,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -965,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -966,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -967,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -968,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -969,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -970,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -971,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -972,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -973,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -974,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -975,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -976,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -977,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -978,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -979,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -980,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -981,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -982,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -983,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -984,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -985,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -986,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -987,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -988,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -989,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -990,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -991,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -992,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -993,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -994,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -995,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -996,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -997,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -998,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -999,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1000,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1001,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1002,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1003,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1004,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1005,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1006,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1007,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1008,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1009,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1010,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1011,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1012,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1013,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1014,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1015,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1016,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1017,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1018,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1019,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1020,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1021,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1022,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1023,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1024,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1025,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1026,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1027,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1028,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1029,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1030,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1031,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1032,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1033,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1034,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1035,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1036,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1037,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1038,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1039,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1040,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1041,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1042,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1043,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1044,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1045,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1046,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1047,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1048,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1049,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1050,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1051,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1052,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1053,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1054,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1055,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1056,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1057,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1058,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1059,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1060,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1061,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1062,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1063,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1064,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1065,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1066,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1067,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1068,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1069,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1070,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1071,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1072,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1073,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1074,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1075,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1076,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1077,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1078,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1079,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1080,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1081,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1082,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1083,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1084,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1085,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1086,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1087,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1088,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1089,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1090,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1091,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1092,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1093,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1094,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1095,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1096,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1097,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1098,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1099,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1100,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1101,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1102,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1103,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1104,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1105,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1106,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1107,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1108,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1109,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1110,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1111,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1112,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1113,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1114,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1115,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1116,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1117,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1118,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1119,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1120,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1121,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1122,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1123,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1124,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1125,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1126,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1127,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1128,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1129,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1130,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1131,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1132,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1133,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1134,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1135,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1136,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1137,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1138,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1139,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1140,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1141,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1142,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1143,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1144,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1145,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1146,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1147,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1148,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1149,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1150,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1151,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1152,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1153,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1154,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1155,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1156,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1157,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1158,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1159,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1160,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1161,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1162,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1163,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1164,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1165,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1166,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1167,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1168,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1169,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1170,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1171,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1172,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1173,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1174,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1175,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1176,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1177,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1178,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1179,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1180,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1181,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1182,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1182,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1184,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1185,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1186,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1187,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1188,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1189,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1190,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1191,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1192,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1193,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1194,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1195,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1196,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1197,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1198,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1199,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1200,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1201,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1202,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1203,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1204,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1205,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1206,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1207,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1208,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1209,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1210,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1211,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1212,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1213,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1214,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1215,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1216,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1217,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1218,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1219,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1220,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1221,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1222,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1223,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1224,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1225,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1226,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, \ No newline at end of file diff --git a/packages/itmat-job-executor/test/testFiles/FieldCurator_error3.tsv b/packages/itmat-job-executor/test/testFiles/FieldCurator_error3.tsv deleted file mode 100644 index cb55577a1..000000000 --- a/packages/itmat-job-executor/test/testFiles/FieldCurator_error3.tsv +++ /dev/null @@ -1,1227 +0,0 @@ -ID,Database,TableName,TableID,SequentialOrder,QuestionNumber,Fieldname,Label,Label_DE,Label_NL,Label_IT,Label_ES,Label_PL,Label_F,EligibleAnswer,IneligibleAnswer,Validation,DataType,ControlType,SystemGenerated,ValueList,Length,DisplayFormat,Nullable,Required,Mandatory,CollectIf,NotMapped,DefaultValue,RegEx,RegExErrorMsg,ShowOnIndexView,Comments -1,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -2,IDEAFAST,Participants,,,,GenderID,Gender ID,,,,,,,,,,int,,False,,,,True,False,True,,False,,,,False, -3,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -4,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -5,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -6,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -7,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -8,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -9,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -10,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -11,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -12,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -13,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -14,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -15,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -16,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -17,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -18,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -19,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -20,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -21,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -22,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -23,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,, -24,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -25,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -26,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -27,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -28,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -29,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -30,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -31,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -32,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -33,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -34,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -35,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -36,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -37,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -38,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -39,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -40,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -41,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -42,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -43,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -44,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -45,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -46,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -47,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -48,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -49,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -50,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -51,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -52,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -53,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -54,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -55,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -56,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -57,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -58,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -59,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -60,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -61,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -62,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -63,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -64,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -65,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -66,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -67,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -68,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -69,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -70,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -71,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -72,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -73,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -74,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -75,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -76,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -77,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -78,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -79,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -80,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -81,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -82,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -83,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -84,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -85,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -86,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -87,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -88,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -89,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -90,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -91,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -92,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -93,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -94,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -95,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -96,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -97,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -98,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -99,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -100,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -101,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -102,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -103,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -104,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -105,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -106,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -107,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -108,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -109,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -110,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -111,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -112,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -113,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -114,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -115,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -116,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -117,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -118,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -119,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -120,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -121,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -122,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -123,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -124,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -125,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -126,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -127,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -128,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -129,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -130,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -131,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -132,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -133,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -134,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -135,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -136,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -137,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -138,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -139,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -140,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -141,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -142,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -143,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -144,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -145,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -146,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -147,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -148,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -149,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -150,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -151,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -152,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -153,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -154,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -155,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -156,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -157,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -158,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -159,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -160,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -161,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -162,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -163,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -164,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -165,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -166,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -167,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -168,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -169,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -170,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -171,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -172,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -173,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -174,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -175,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -176,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -177,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -178,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -179,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -180,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -181,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -182,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -183,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -184,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -185,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -186,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -187,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -188,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -189,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -190,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -191,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -192,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -193,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -194,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -195,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -196,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -197,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -198,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -199,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -200,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -201,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -202,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -203,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -204,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -205,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -206,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -207,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -208,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -209,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -210,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -211,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -212,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -213,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -214,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -215,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -216,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -217,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -218,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -219,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -220,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -221,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -222,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -223,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -224,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -225,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -226,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -227,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -228,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -229,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -230,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -231,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -232,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -233,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -234,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -235,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -236,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -237,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -238,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -239,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -240,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -241,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -242,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -243,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -244,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -245,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -246,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -247,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -248,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -249,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -250,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -251,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -252,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -253,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -254,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -255,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -256,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -257,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -258,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -259,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -260,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -261,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -262,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -263,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -264,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -265,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -266,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -267,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -268,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -269,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -270,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -271,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -272,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -273,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -274,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -275,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -276,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -277,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -278,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -279,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -280,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -281,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -282,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -283,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -284,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -285,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -286,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -287,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -288,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -289,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -290,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -291,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -292,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -293,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -294,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -295,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -296,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -297,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -298,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -299,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -300,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -301,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -302,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -303,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -304,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -305,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -306,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -307,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -308,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -309,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -310,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -311,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -312,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -313,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -314,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -315,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -316,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -317,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -318,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -319,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -320,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -321,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -322,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -323,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -324,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -325,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -326,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -327,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -328,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -329,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -330,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -331,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -332,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -333,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -334,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -335,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -336,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -337,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -338,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -339,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -340,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -341,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -342,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -343,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -344,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -345,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -346,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -347,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -348,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -349,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -350,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -351,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -352,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -353,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -354,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -355,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -356,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -357,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -358,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -359,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -360,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -361,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -362,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -363,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -364,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -365,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -366,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -367,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -368,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -369,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -370,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -371,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -372,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -373,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -374,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -375,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -376,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -377,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -378,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -379,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -380,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -381,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -382,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -383,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -384,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -385,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -386,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -387,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -388,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -389,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -390,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -391,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -392,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -393,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -394,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -395,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -396,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -397,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -398,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -399,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -400,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -401,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -402,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -403,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -404,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -405,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -406,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -407,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -408,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -409,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -410,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -411,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -412,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -413,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -414,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -415,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -416,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -417,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -418,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -419,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -420,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -421,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -422,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -423,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -424,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -425,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -426,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -427,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -428,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -429,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -430,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -431,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -432,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -433,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -434,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -435,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -436,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -437,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -438,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -439,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -440,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -441,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -442,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -443,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -444,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -445,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -446,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -447,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -448,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -449,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -450,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -451,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -452,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -453,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -454,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -455,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -456,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -457,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -458,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -459,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -460,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -461,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -462,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -463,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -464,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -465,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -466,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -467,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -468,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -469,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -470,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -471,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -472,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -473,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -474,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -475,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -476,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -477,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -478,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -479,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -480,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -481,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -482,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -483,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -484,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -485,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -486,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -487,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -488,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -489,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -490,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -491,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -492,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -493,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -494,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -495,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -496,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -497,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -498,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -499,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -500,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -501,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -502,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -503,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -504,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -505,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -506,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -507,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -508,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -509,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -510,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -511,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -512,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -513,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -514,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -515,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -516,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -517,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -518,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -519,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -520,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -521,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -522,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -523,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -524,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -525,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -526,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -527,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -528,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -529,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -530,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -531,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -532,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -533,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -534,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -535,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -536,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -537,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -538,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -539,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -540,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -541,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -542,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -543,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -544,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -545,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -546,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -547,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -548,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -549,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -550,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -551,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -552,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -553,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -554,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -555,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -556,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -557,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -558,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -559,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -560,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -561,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -562,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -563,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -564,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -565,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -566,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -567,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -568,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -569,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -570,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -571,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -572,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -573,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -574,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -575,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -576,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -577,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -578,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -579,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -580,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -581,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -582,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -583,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -584,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -585,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -586,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -587,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -588,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -589,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -590,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -591,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -592,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -593,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -594,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -595,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -596,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -597,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -598,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -599,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -600,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -601,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -602,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -603,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -604,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -605,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -606,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -607,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -608,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -609,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -610,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -611,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -612,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -613,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -614,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -615,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -616,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -617,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -618,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -619,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -620,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -621,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -622,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -623,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -624,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -625,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -626,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -627,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -628,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -629,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -630,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -631,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -632,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -633,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -634,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -635,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -636,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -637,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -638,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -639,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -640,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -641,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -642,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -643,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -644,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -645,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -646,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -647,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -648,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -649,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -650,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -651,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -652,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -653,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -654,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -655,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -656,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -657,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -658,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -659,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -660,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -661,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -662,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -663,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -664,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -665,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -666,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -667,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -668,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -669,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -670,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -671,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -672,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -673,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -674,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -675,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -676,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -677,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -678,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -679,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -680,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -681,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -682,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -683,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -684,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -685,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -686,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -687,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -688,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -689,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -690,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -691,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -692,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -693,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -694,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -695,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -696,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -697,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -698,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -699,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -700,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -701,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -702,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -703,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -704,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -705,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -706,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -707,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -708,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -709,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -710,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -711,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -712,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -713,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -714,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -715,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -716,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -717,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -718,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -719,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -720,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -721,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -722,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -723,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -724,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -725,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -726,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -727,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -728,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -729,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -730,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -731,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -732,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -733,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -734,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -735,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -736,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -737,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -738,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -739,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -740,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -741,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -742,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -743,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -744,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -745,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -746,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -747,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -748,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -749,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -750,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -751,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -752,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -753,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -754,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -755,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -756,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -757,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -758,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -759,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -760,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -761,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -762,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -763,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -764,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -765,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -766,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -767,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -768,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -769,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -770,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -771,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -772,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -773,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -774,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -775,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -776,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -777,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -778,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -779,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -780,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -781,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -782,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -783,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -784,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -785,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -786,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -787,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -788,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -789,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -790,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -791,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -792,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -793,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -794,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -795,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -796,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -797,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -798,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -799,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -800,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -801,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -802,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -803,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -804,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -805,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -806,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -807,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -808,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -809,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -810,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -811,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -812,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -813,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -814,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -815,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -816,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -817,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -818,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -819,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -820,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -821,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -822,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -823,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -824,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -825,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -826,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -827,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -828,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -829,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -830,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -831,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -832,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -833,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -834,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -835,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -836,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -837,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -838,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -839,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -840,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -841,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -842,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -843,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -844,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -845,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -846,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -847,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -848,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -849,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -850,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -851,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -852,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -853,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -854,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -855,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -856,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -857,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -858,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -859,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -860,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -861,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -862,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -863,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -864,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -865,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -866,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -867,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -868,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -869,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -870,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -871,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -872,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -873,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -874,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -875,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -876,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -877,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -878,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -879,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -880,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -881,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -882,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -883,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -884,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -885,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -886,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -887,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -888,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -889,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -890,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -891,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -892,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -893,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -894,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -895,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -896,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -897,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -898,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -899,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -900,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -901,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -902,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -903,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -904,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -905,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -906,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -907,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -908,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -909,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -910,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -911,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -912,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -913,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -914,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -915,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -916,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -917,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -918,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -919,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -920,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -921,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -922,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -923,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -924,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -925,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -926,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -927,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -928,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -929,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -930,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -931,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -932,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -933,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -934,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -935,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -936,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -937,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -938,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -939,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -940,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -941,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -942,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -943,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -944,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -945,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -946,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -947,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -948,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -949,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -950,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -951,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -952,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -953,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -954,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -955,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -956,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -957,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -958,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -959,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -960,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -961,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -962,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -963,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -964,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -965,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -966,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -967,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -968,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -969,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -970,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -971,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -972,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -973,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -974,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -975,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -976,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -977,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -978,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -979,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -980,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -981,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -982,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -983,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -984,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -985,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -986,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -987,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -988,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -989,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -990,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -991,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -992,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -993,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -994,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -995,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -996,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -997,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -998,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -999,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1000,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1001,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1002,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1003,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1004,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1005,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1006,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1007,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1008,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1009,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1010,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1011,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1012,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1013,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1014,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1015,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1016,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1017,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1018,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1019,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1020,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1021,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1022,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1023,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1024,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1025,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1026,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1027,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1028,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1029,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1030,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1031,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1032,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1033,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1034,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1035,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1036,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1037,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1038,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1039,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1040,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1041,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1042,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1043,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1044,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1045,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1046,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1047,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1048,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1049,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1050,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1051,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1052,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1053,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1054,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1055,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1056,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1057,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1058,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1059,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1060,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1061,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1062,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1063,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1064,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1065,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1066,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1067,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1068,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1069,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1070,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1071,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1072,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1073,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1074,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1075,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1076,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1077,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1078,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1079,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1080,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1081,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1082,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1083,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1084,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1085,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1086,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1087,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1088,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1089,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1090,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1091,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1092,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1093,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1094,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1095,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1096,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1097,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1098,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1099,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1100,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1101,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1102,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1103,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1104,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1105,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1106,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1107,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1108,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1109,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1110,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1111,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1112,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1113,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1114,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1115,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1116,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1117,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1118,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1119,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1120,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1121,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1122,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1123,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1124,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1125,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1126,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1127,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1128,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1129,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1130,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1131,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1132,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1133,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1134,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1135,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1136,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1137,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1138,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1139,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1140,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1141,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1142,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1143,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1144,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1145,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1146,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1147,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1148,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1149,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1150,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1151,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1152,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1153,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1154,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1155,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1156,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1157,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1158,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1159,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1160,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1161,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1162,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1163,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1164,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1165,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1166,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1167,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1168,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1169,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1170,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1171,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1172,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1173,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1174,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1175,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1176,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1177,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1178,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1179,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1180,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1181,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1182,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1183,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1184,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1185,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1186,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1187,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1188,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1189,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1190,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1191,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1192,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1193,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1194,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1195,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1196,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1197,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1198,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1199,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1200,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1201,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1202,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1203,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1204,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1205,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1206,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1207,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1208,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1209,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1210,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1211,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1212,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1213,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1214,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1215,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1216,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1217,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1218,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1219,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1220,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1221,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1222,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1223,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1224,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1225,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1226,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, \ No newline at end of file diff --git a/packages/itmat-job-executor/test/testFiles/FieldCurator_error4.tsv b/packages/itmat-job-executor/test/testFiles/FieldCurator_error4.tsv deleted file mode 100644 index bc92cfd68..000000000 --- a/packages/itmat-job-executor/test/testFiles/FieldCurator_error4.tsv +++ /dev/null @@ -1,1227 +0,0 @@ -ID,Database,TableName,TableID,SequentialOrder,QuestionNumber,Fieldname,Label,Label_DE,Label_NL,Label_IT,Label_ES,Label_PL,Label_F,EligibleAnswer,IneligibleAnswer,Validation,DataType,ControlType,SystemGenerated,ValueList,Length,DisplayFormat,Nullable,Required,Mandatory,CollectIf,NotMapped,DefaultValue,RegEx,RegExErrorMsg,ShowOnIndexView,Comments -1,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -2,IDEAFAST,Participants,,,,GenderID,Gender ID,,,,,,,,,,int,,False,,,,True,False,True,,False,,,,False, -3,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -4,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -5,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -6,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -7,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -8,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -9,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -10,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -11,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -12,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -13,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -14,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -15,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -16,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -17,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -18,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -19,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -20,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -21,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -22,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -23,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -24,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -25,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -26,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -27,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -28,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -29,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -30,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -31,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -32,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -33,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -34,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -35,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -36,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -37,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -38,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -39,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -40,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -41,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -42,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -43,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -44,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -45,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -46,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -47,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -48,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -49,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -50,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -51,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -52,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -53,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -54,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -55,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -56,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -57,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -58,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -59,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -60,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -61,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -62,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -63,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -64,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -65,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -66,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -67,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -68,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -69,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -70,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -71,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -72,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -73,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -74,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -75,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -76,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -77,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -78,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -79,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -80,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -81,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -82,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -83,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -84,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -85,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -86,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -87,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -88,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -89,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -90,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -91,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -92,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -93,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -94,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -95,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -96,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -97,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -98,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -99,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -100,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -101,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -102,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -103,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -104,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -105,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -106,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -107,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -108,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -109,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -110,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -111,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -112,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -113,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -114,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -115,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -116,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -117,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -118,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -119,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -120,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -121,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -122,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -123,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -124,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -125,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -126,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -127,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -128,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -129,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -130,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -131,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -132,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -133,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -134,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -135,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -136,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -137,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -138,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -139,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -140,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -141,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -142,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -143,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -144,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -145,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -146,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -147,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -148,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -149,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -150,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -151,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -152,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -153,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -154,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -155,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -156,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -157,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -158,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -159,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -160,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -161,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -162,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -163,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -164,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -165,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -166,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -167,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -168,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -169,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -170,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -171,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -172,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -173,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -174,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -175,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -176,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -177,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -178,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -179,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -180,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -181,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -182,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -183,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -184,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -185,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -186,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -187,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -188,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -189,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -190,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -191,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -192,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -193,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -194,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -195,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -196,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -197,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -198,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -199,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -200,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -201,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -202,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -203,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -204,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -205,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -206,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -207,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -208,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -209,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -210,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -211,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -212,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -213,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -214,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -215,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -216,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -217,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -218,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -219,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -220,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -221,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -222,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -223,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -224,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -225,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -226,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -227,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -228,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -229,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -230,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -231,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -232,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -233,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -234,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -235,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -236,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -237,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -238,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -239,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -240,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -241,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -242,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -243,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -244,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -245,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -246,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -247,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -248,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -249,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -250,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -251,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -252,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -253,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -254,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -255,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -256,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -257,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -258,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -259,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -260,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -261,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -262,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -263,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -264,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -265,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -266,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -267,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -268,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -269,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -270,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -271,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -272,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -273,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -274,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -275,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -276,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -277,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -278,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -279,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -280,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -281,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -282,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -283,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -284,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -285,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -286,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -287,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -288,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -289,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -290,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -291,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -292,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -293,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -294,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -295,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -296,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -297,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -298,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -299,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -300,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -301,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -302,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -303,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -304,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -305,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -306,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -307,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -308,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -309,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -310,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -311,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -312,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -313,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -314,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -315,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -316,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -317,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -318,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -319,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -320,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -321,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -322,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -323,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -324,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -325,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -326,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -327,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -328,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -329,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -330,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -331,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -332,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -333,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -334,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -335,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -336,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -337,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -338,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -339,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -340,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -341,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -342,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -343,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -344,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -345,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -346,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -347,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -348,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -349,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -350,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -351,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -352,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -353,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -354,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -355,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -356,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -357,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -358,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -359,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -360,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -361,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -362,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -363,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -364,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -365,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -366,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -367,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -368,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -369,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -370,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -371,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -372,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -373,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -374,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -375,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -376,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -377,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -378,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -379,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -380,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -381,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -382,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -383,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -384,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -385,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -386,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -387,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -388,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -389,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -390,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -391,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -392,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -393,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -394,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -395,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -396,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -397,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -398,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -399,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -400,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -401,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -402,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -403,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -404,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -405,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -406,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -407,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -408,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -409,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -410,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -411,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -412,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -413,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -414,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -415,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -416,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -417,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -418,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -419,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -420,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -421,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -422,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -423,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -424,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -425,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -426,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -427,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -428,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -429,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -430,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -431,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -432,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -433,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -434,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -435,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -436,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -437,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -438,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -439,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -440,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -441,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -442,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -443,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -444,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -445,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -446,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -447,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -448,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -449,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -450,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -451,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -452,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -453,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -454,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -455,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -456,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -457,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -458,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -459,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -460,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -461,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -462,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -463,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -464,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -465,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -466,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -467,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -468,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -469,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -470,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -471,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -472,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -473,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -474,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -475,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -476,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -477,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -478,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -479,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -480,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -481,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -482,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -483,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -484,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -485,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -486,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -487,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -488,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -489,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -490,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -491,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -492,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -493,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -494,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -495,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -496,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -497,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -498,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -499,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -500,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -501,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -502,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -503,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -504,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -505,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -506,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -507,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -508,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -509,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -510,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -511,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -512,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -513,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -514,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -515,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -516,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -517,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -518,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -519,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -520,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -521,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -522,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -523,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -524,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -525,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -526,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -527,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -528,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -529,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -530,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -531,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -532,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -533,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -534,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -535,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -536,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -537,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -538,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -539,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -540,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -541,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -542,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -543,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -544,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -545,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -546,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -547,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -548,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -549,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -550,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -551,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -552,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -553,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -554,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -555,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -556,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -557,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -558,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -559,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -560,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -561,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -562,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -563,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -564,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -565,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -566,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -567,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -568,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -569,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -570,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -571,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -572,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -573,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -574,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -575,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -576,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -577,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -578,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -579,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -580,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -581,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -582,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -583,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -584,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -585,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -586,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -587,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -588,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -589,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -590,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -591,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -592,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -593,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -594,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -595,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -596,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -597,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -598,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -599,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -600,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -601,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -602,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -603,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -604,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -605,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -606,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -607,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -608,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -609,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -610,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -611,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -612,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -613,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -614,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -615,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -616,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -617,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -618,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -619,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -620,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -621,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -622,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -623,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -624,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -625,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -626,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -627,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -628,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -629,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -630,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -631,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -632,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -633,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -634,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -635,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -636,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -637,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -638,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -639,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -640,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -641,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -642,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -643,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -644,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -645,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -646,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -647,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -648,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -649,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -650,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -651,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -652,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -653,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -654,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -655,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -656,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -657,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -658,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -659,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -660,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -661,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -662,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -663,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -664,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -665,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -666,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -667,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -668,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -669,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -670,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -671,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -672,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -673,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -674,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -675,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -676,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -677,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -678,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -679,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -680,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -681,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -682,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -683,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -684,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -685,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -686,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -687,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -688,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -689,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -690,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -691,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -692,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -693,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -694,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -695,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -696,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -697,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -698,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -699,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -700,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -701,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -702,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -703,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -704,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -705,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -706,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -707,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -708,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -709,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -710,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -711,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -712,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -713,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -714,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -715,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -716,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -717,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -718,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -719,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -720,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -721,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -722,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -723,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -724,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -725,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -726,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -727,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -728,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -729,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -730,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -731,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -732,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -733,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -734,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -735,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -736,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -737,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -738,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -739,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -740,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -741,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -742,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -743,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -744,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -745,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -746,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -747,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -748,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -749,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -750,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -751,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -752,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -753,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -754,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -755,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -756,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -757,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -758,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -759,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -760,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -761,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -762,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -763,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -764,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -765,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -766,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -767,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -768,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -769,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -770,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -771,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -772,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -773,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -774,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -775,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -776,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -777,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -778,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -779,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -780,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -781,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -782,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -783,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -784,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -785,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -786,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -787,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -788,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -789,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -790,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -791,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -792,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -793,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -794,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -795,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -796,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -797,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -798,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -799,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -800,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -801,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -802,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -803,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -804,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -805,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -806,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -807,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -808,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -809,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -810,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -811,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -812,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -813,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -814,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -815,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -816,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -817,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -818,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -819,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -820,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -821,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -822,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -823,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -824,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -825,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -826,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -827,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -828,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -829,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -830,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -831,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -832,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -833,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -834,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -835,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -836,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -837,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -838,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -839,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -840,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -841,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -842,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -843,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -844,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -845,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -846,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -847,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -848,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -849,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -850,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -851,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -852,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -853,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -854,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -855,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -856,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -857,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -858,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -859,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -860,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -861,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -862,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -863,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -864,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -865,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -866,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -867,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -868,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -869,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -870,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -871,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -872,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -873,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -874,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -875,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -876,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -877,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -878,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -879,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -880,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -881,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -882,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -883,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -884,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -885,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -886,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -887,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -888,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -889,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -890,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -891,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -892,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -893,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -894,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -895,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -896,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -897,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -898,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -899,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -900,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -901,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -902,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -903,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -904,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -905,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -906,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -907,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -908,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -909,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -910,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -911,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -912,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -913,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -914,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -915,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -916,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -917,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -918,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -919,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -920,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -921,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -922,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -923,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -924,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -925,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -926,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -927,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -928,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -929,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -930,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -931,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -932,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -933,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -934,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -935,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -936,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -937,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -938,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -939,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -940,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -941,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -942,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -943,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -944,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -945,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -946,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -947,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -948,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -949,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -950,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -951,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -952,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -953,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -954,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -955,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -956,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -957,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -958,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -959,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -960,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -961,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -962,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -963,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -964,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -965,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -966,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -967,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -968,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -969,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -970,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -971,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -972,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -973,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -974,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -975,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -976,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -977,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -978,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -979,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -980,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -981,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -982,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -983,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -984,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -985,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -986,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -987,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -988,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -989,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -990,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -991,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -992,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -993,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -994,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -995,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -996,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -997,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -998,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -999,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1000,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1001,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1002,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1003,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1004,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1005,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1006,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1007,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1008,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1009,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1010,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1011,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1012,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1013,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1014,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1015,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1016,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1017,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1018,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1019,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1020,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1021,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1022,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1023,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1024,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1025,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1026,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1027,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1028,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1029,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1030,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1031,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1032,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1033,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1034,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1035,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1036,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1037,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1038,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1039,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1040,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1041,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1042,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1043,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1044,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1045,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1046,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1047,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1048,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1049,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1050,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1051,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1052,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1053,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1054,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1055,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1056,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1057,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1058,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1059,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1060,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1061,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1062,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1063,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1064,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1065,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1066,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1067,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1068,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1069,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1070,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1071,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1072,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1073,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1074,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1075,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1076,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1077,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1078,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1079,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1080,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1081,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1082,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1083,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1084,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1085,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1086,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1087,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1088,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1089,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1090,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1091,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1092,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1093,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1094,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1095,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1096,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1097,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1098,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1099,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1100,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1101,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1102,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1103,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1104,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1105,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1106,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1107,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1108,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1109,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1110,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1111,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1112,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1113,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1114,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1115,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1116,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1117,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1118,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1119,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1120,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False -1121,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1122,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1123,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1124,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1125,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1126,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1127,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1128,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1129,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1130,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1131,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1132,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1133,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1134,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1135,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1136,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1137,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1138,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1139,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1140,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1141,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1142,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1143,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1144,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1145,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1146,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1147,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1148,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1149,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1150,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1151,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1152,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1153,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1154,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1155,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1156,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1157,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1158,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1159,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1160,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1161,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1162,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1163,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1164,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1165,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1166,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1167,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1168,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1169,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1170,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1171,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1172,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1173,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1174,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1175,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1176,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1177,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1178,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1179,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1180,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1181,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1182,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1183,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1184,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1185,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1186,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1187,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1188,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1189,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1190,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1191,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1192,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1193,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1194,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1195,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1196,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1197,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1198,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1199,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1200,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1201,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1202,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1203,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1204,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1205,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1206,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1207,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1208,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1209,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1210,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1211,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1212,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1213,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1214,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1215,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1216,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1217,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1218,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1219,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1220,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1221,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1222,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1223,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1224,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1225,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1226,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, \ No newline at end of file diff --git a/packages/itmat-job-executor/test/testFiles/FieldCurator_error5.tsv b/packages/itmat-job-executor/test/testFiles/FieldCurator_error5.tsv deleted file mode 100644 index f2c8265ee..000000000 --- a/packages/itmat-job-executor/test/testFiles/FieldCurator_error5.tsv +++ /dev/null @@ -1,1227 +0,0 @@ -ID,Database,TableName,TableID,SequentialOrder,QuestionNumber,Fieldname,Label,Label_DE,Label_NL,Label_IT,Label_ES,Label_PL,Label_F,EligibleAnswer,IneligibleAnswer,Validation,DataType,ControlType,SystemGenerated,ValueList,Length,DisplayFormat,Nullable,Required,Mandatory,CollectIf,NotMapped,DefaultValue,RegEx,RegExErrorMsg,ShowOnIndexView,Comments -1,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -2,IDEAFAST,Participants,,,,GenderID,Gender ID,,,,,,,,,,int,,False,,,,True,False,True,,False,,,,False, -3,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -4,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -5,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -6,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -7,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -8,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -9,IDEAFAST,Participants,,,,,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -10,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -11,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -12,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -13,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -14,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -15,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -16,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -17,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -18,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -19,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -20,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -21,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -22,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -23,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -24,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -25,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -26,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -27,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -28,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -29,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -30,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -31,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -32,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -33,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -34,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -35,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -36,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -37,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -38,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -39,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -40,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -41,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -42,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -43,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -44,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -45,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -46,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -47,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -48,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -49,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -50,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -51,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -52,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -53,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -54,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -55,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -56,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -57,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -58,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -59,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -60,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -61,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -62,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -63,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -64,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -65,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -66,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -67,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -68,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False -69,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -70,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -71,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -72,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -73,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -74,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -75,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -76,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -77,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -78,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -79,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -80,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -81,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -82,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -83,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -84,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -85,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -86,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -87,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -88,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -89,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -90,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -91,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -92,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -93,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -94,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -95,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -96,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -97,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -98,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -99,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -100,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -101,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -102,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -103,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -104,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -105,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -106,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -107,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -108,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -109,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -110,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -111,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -112,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -113,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -114,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -115,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -116,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -117,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -118,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -119,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -120,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -121,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -122,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -123,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -124,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -125,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -126,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -127,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -128,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -129,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -130,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -131,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -132,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -133,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -134,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -135,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -136,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -137,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -138,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -139,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -140,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -141,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -142,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -143,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -144,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -145,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -146,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -147,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -148,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -149,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -150,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -151,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -152,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -153,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -154,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -155,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -156,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -157,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -158,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -159,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -160,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -161,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -162,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -163,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -164,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -165,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -166,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -167,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -168,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -169,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -170,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -171,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -172,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -173,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -174,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -175,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -176,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -177,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -178,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -179,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -180,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -181,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -182,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -183,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -184,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -185,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -186,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -187,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -188,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -189,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -190,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -191,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -192,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -193,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -194,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -195,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -196,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -197,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -198,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -199,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -200,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -201,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -202,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -203,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -204,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -205,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -206,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -207,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -208,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -209,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -210,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -211,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -212,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -213,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -214,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -215,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -216,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -217,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -218,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -219,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -220,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -221,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -222,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -223,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -224,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -225,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -226,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -227,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -228,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -229,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -230,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -231,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -232,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -233,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -234,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -235,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -236,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -237,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -238,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -239,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -240,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -241,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -242,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -243,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -244,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -245,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -246,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -247,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -248,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -249,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -250,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -251,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -252,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -253,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -254,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -255,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -256,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -257,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -258,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -259,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -260,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -261,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -262,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -263,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -264,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -265,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -266,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -267,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -268,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -269,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -270,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -271,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -272,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -273,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -274,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -275,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -276,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -277,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -278,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -279,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -280,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -281,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -282,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -283,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -284,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -285,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -286,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -287,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -288,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -289,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -290,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -291,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -292,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -293,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -294,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -295,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -296,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -297,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -298,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -299,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -300,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -301,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -302,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -303,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -304,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -305,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -306,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -307,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -308,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -309,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -310,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -311,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -312,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -313,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -314,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -315,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -316,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -317,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -318,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -319,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -320,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -321,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -322,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -323,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -324,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -325,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -326,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -327,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -328,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -329,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -330,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,str,,False,,9,,True,False,True,,False,,,,False, -331,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -332,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -333,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -334,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -335,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -336,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -337,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -338,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -339,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -340,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -341,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -342,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -343,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -344,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -345,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -346,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -347,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -348,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -349,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -350,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -351,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -352,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -353,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -354,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -355,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -356,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -357,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -358,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -359,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -360,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -361,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -362,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -363,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -364,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -365,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -366,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -367,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -368,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -369,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -370,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -371,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -372,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -373,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -374,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -375,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -376,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -377,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -378,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -379,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -380,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -381,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -382,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -383,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -384,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -385,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -386,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -387,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -388,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -389,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -390,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -391,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -392,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -393,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -394,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -395,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -396,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -397,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -398,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -399,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -400,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -401,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -402,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -403,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -404,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -405,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -406,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -407,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -408,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -409,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -410,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -411,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -412,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -413,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -414,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -415,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -416,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -417,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -418,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -419,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -420,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -421,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -422,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -423,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -424,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -425,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -426,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -427,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -428,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -429,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -430,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -431,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -432,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -433,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -434,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -435,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -436,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -437,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -438,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -439,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -440,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -441,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -442,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -443,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -444,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -445,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -446,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -447,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -448,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -449,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -450,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -451,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -452,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -453,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -454,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -455,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -456,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -457,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -458,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -459,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -460,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -461,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -462,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -463,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -464,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -465,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -466,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -467,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -468,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -469,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -470,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -471,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -472,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -473,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -474,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -475,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -476,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -477,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -478,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -479,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -480,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -481,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -482,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -483,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -484,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -485,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -486,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -487,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -488,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -489,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -490,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -491,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -492,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -493,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -494,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -495,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -496,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -497,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -498,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -499,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -500,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -501,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -502,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -503,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -504,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -505,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -506,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -507,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -508,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -509,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -510,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -511,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -512,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -513,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -514,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -515,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -516,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -517,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -518,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -519,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -520,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -521,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -522,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -523,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -524,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -525,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -526,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -527,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -528,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -529,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -530,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -531,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -532,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -533,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -534,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -535,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -536,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -537,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -538,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -539,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -540,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -541,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -542,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -543,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -544,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -545,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -546,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -547,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -548,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -549,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -550,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -551,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -552,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -553,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -554,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -555,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -556,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -557,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -558,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -559,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -560,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -561,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -562,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -563,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -564,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -565,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -566,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -567,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -568,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -569,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -570,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -571,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -572,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -573,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -574,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -575,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -576,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -577,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -578,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -579,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -580,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -581,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -582,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -583,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -584,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -585,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -586,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -587,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -588,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -589,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -590,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -591,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -592,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -593,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -594,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -595,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -596,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -597,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -598,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -599,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -600,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -601,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -602,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -603,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -604,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -605,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -606,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -607,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -608,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -609,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -610,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -611,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -612,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -613,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -614,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -615,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -616,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -617,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -618,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -619,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -620,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -621,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -622,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -623,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -624,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -625,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -626,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -627,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -628,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -629,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -630,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -631,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -632,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -633,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -634,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -635,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -636,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -637,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -638,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -639,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -640,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -641,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -642,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -643,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -644,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -645,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -646,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -647,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -648,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -649,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -650,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -651,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -652,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -653,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -654,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -655,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -656,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -657,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -658,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -659,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -660,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -661,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -662,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -663,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -664,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -665,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -666,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -667,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -668,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -669,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -670,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -671,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -672,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -673,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -674,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -675,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -676,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -677,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -678,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -679,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -680,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -681,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -682,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -683,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -684,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -685,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -686,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -687,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -688,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -689,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -690,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -691,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -692,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -693,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -694,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -695,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -696,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -697,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -698,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -699,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -700,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -701,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -702,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -703,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -704,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -705,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -706,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -707,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -708,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -709,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -710,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -711,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -712,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -713,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -714,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -715,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -716,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -717,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -718,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -719,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -720,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -721,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -722,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -723,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -724,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -725,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -726,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -727,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -728,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -729,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -730,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -731,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -732,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -733,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -734,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -735,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -736,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -737,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -738,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -739,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -740,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -741,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -742,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -743,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -744,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -745,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -746,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -747,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -748,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -749,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -750,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -751,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -752,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -753,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -754,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -755,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -756,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -757,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -758,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -759,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -760,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -761,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -762,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -763,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -764,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -765,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -766,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -767,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -768,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -769,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -770,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -771,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -772,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -773,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -774,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -775,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -776,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -777,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -778,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -779,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -780,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -781,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -782,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -783,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -784,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -785,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -786,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -787,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -788,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -789,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -790,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -791,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -792,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -793,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -794,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -795,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -796,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -797,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -798,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -799,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -800,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -801,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -802,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -803,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -804,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -805,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -806,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -807,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -808,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -809,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -810,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -811,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -812,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -813,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -814,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -815,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -816,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -817,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -818,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -819,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -820,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -821,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -822,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -823,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -824,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -825,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -826,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -827,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -828,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -829,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -830,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -831,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -832,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -833,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -834,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -835,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -836,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -837,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -838,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -839,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -840,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -841,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -842,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -843,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -844,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -845,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -846,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -847,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -848,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -849,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -850,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -851,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -852,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -853,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -854,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -855,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -856,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -857,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -858,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -859,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -860,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -861,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -862,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -863,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -864,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -865,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -866,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -867,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -868,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -869,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -870,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -871,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -872,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -873,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -874,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -875,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -876,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -877,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -878,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -879,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -880,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -881,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -882,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -883,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -884,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -885,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -886,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -887,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -888,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -889,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -890,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -891,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -892,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -893,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -894,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -895,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -896,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -897,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -898,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -899,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -900,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -901,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -902,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -903,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -904,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -905,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -906,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -907,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -908,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -909,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -910,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -911,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -912,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -913,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -914,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -915,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -916,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -917,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -918A,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -919,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -920,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -921,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -922,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -923,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -924,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -925,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -926,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -927,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -928,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -929,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -930,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -931,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -932,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -933,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -934,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -935,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -936,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -937,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -938,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -939,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -940,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -941,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -942,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -943,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -944,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -945,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -946,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -947,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -948,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -949,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -950,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -951,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -952,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -953,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -954,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -955,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -956,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -957,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -958,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -959,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -960,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -961,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -962,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -963,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -964,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -965,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -966,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -967,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -968,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -969,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -970,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -971,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -972,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -973,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -974,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -975,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -976,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -977,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -978,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -979,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -980,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -981,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -982,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -983,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -984,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -985,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -986,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -987,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -988,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -989,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -990,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -991,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -992,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -993,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -994,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -995,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -996,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -997,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -998,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -999,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1000,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1001,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1002,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1003,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1004,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1005,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1006,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1007,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1008,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1009,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1010,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1011,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1012,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1013,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1014,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1015,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1016,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1017,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1018,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1019,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1020,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1021,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1022,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1023,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1024,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1025,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1026,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1027,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1028,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1029,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1030,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1031,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1032,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1033,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1034,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1035,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1036,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1037,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1038,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1039,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1040,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1041,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1042,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1043,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1044,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1045,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1046,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1047,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1048,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1049,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1050,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1051,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1052,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1053,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1054,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1055,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1056,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1057,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1058,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1059,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1060,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1061,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1062,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1063,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1064,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1065,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1066,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1067,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1068,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1069,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1070,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1071,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1072,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1073,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1074,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1075,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1076,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1077,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1078,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1079,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1080,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1081,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1082,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1083,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1084,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1085,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1086,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1087,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1088,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1089,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1090,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1091,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1092,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1093,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1094,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1095,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1096,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1097,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1098,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1099,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1100,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1101,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1102,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1103,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1104,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1105,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1106,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1107,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1108,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1109,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1110,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1111,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1112,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1113,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1114,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1115,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1116,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1117,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1118,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1119,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1120,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1121,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1122,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1123,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1124,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1125,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1126,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1127,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1128,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1129,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1130,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1131,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1132,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1133,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1134,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1135,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1136,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1137,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1138,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1139,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1140,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1141,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1142,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1143,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1144,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1145,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1146,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1147,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1148,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1149,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1150,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1150,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1152,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1153,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1154,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1155,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1156,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1157,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1158,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1159,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1160,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1161,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1162,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1163,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1164,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1165,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1166,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1167,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1168,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1169,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1170,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1171,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1172,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1173,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1174,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1175,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1176,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1177,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1178,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1179,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1180,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1181,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1182,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1183,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1184,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1185,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1186,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1187,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1188,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1189,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1190,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1191,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1192,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1193,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1194,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1195,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1196,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1197,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1198,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1199,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1200,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1201,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1202,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1203,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1204,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1205,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1206,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1207,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1208,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1209,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1210,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1211,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1212,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1213,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1214,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1215,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1216,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1217,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1218,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1219,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1220,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1221,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1222,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1223,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1224,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1225,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, -1226,IDEAFAST,Participants,,,,SubjectID,Subject ID,,,,,,,,,,nvarchar,,False,,9,,True,False,True,,False,,,,False, \ No newline at end of file diff --git a/packages/itmat-job-executor/test/testFiles/JSONCurator.json b/packages/itmat-job-executor/test/testFiles/JSONCurator.json deleted file mode 100644 index b6dec6c7c..000000000 --- a/packages/itmat-job-executor/test/testFiles/JSONCurator.json +++ /dev/null @@ -1 +0,0 @@ -[["ID", "SubjectID", "VisitID", "Sex", "Description", "Weight"], ["1", "I7N3G6G", "1", 0, "no description", 60.2], ["2", "I7N3G6G", "2", 0, "no description", 60.2], ["3", "I7N3G6G", "3", 0, "no description", 60.2], ["4", "I7N3G6G", "4", 0, "no description", 60.2], ["5", "I7N3G6G", "5", 0, "no description", 60.2], ["6", "I7N3G6G", "6", 0, "no description", 60.2], ["7", "I7N3G6G", "7", 0, "no description", 60.2], ["8", "I7N3G6G", "8", 0, "no description", 60.2], ["9", "I7N3G6G", "9", 0, "no description", 60.2], ["10", "I7N3G6G", "10", 0, "no description", 60.2], ["11", "I7N3G6G", "11", 0, "no description", 60.2], ["12", "I7N3G6G", "12", 0, "no description", 60.2], ["13", "I7N3G6G", "13", 0, "no description", 60.2], ["14", "I7N3G6G", "14", 0, "no description", 60.2], ["15", "I7N3G6G", "15", 0, "no description", 60.2], ["16", "I7N3G6G", "16", 0, "no description", 60.2], ["17", "I7N3G6G", "17", 0, "no description", 60.2], ["18", "I7N3G6G", "18", 0, "no description", 60.2], ["19", "I7N3G6G", "19", 0, "no description", 60.2], ["20", "I7N3G6G", "20", 0, "no description", 60.2], ["21", "I7N3G6G", "21", 0, "no description", 60.2], ["22", "I7N3G6G", "22", 0, "no description", 60.2], ["23", "I7N3G6G", "23", 0, "no description", 60.2], ["24", "I7N3G6G", "24", 0, "no description", 60.2], ["25", "I7N3G6G", "25", 0, "no description", 60.2], ["26", "I7N3G6G", "26", 0, "no description", 60.2], ["27", "I7N3G6G", "27", 0, "no description", 60.2], ["28", "I7N3G6G", "28", 0, "no description", 60.2], ["29", "I7N3G6G", "29", 0, "no description", 60.2], ["30", "I7N3G6G", "30", 0, "no description", 60.2], ["31", "I7N3G6G", "31", 0, "no description", 60.2], ["32", "I7N3G6G", "32", 0, "no description", 60.2], ["33", "I7N3G6G", "33", 0, "no description", 60.2], ["34", "I7N3G6G", "34", 0, "no description", 60.2], ["35", "I7N3G6G", "35", 0, "no description", 60.2], ["36", "I7N3G6G", "36", 0, "no description", 60.2], ["37", "I7N3G6G", "37", 0, "no description", 60.2], ["38", "I7N3G6G", "38", 0, "no description", 60.2], ["39", "I7N3G6G", "39", 0, "no description", 60.2], ["40", "I7N3G6G", "40", 0, "no description", 60.2], ["41", "I7N3G6G", "41", 0, "no description", 60.2], ["42", "I7N3G6G", "42", 0, "no description", 60.2], ["43", "I7N3G6G", "43", 0, "no description", 60.2], ["44", "I7N3G6G", "44", 0, "no description", 60.2], ["45", "I7N3G6G", "45", 0, "no description", 60.2], ["46", "I7N3G6G", "46", 0, "no description", 60.2], ["47", "I7N3G6G", "47", 0, "no description", 60.2], ["48", "I7N3G6G", "48", 0, "no description", 60.2], ["49", "I7N3G6G", "49", 0, "no description", 60.2], ["50", "I7N3G6G", "50", 0, "no description", 60.2], ["51", "I7N3G6G", "51", 0, "no description", 60.2], ["52", "I7N3G6G", "52", 0, "no description", 60.2], ["53", "I7N3G6G", "53", 0, "no description", 60.2], ["54", "I7N3G6G", "54", 0, "no description", 60.2], ["55", "I7N3G6G", "55", 0, "no description", 60.2], ["56", "I7N3G6G", "56", 0, "no description", 60.2], ["57", "I7N3G6G", "57", 0, "no description", 60.2], ["58", "I7N3G6G", "58", 0, "no description", 60.2], ["59", "I7N3G6G", "59", 0, "no description", 60.2], ["60", "I7N3G6G", "60", 0, "no description", 60.2], ["61", "I7N3G6G", "61", 0, "no description", 60.2], ["62", "I7N3G6G", "62", 0, "no description", 60.2], ["63", "I7N3G6G", "63", 0, "no description", 60.2], ["64", "I7N3G6G", "64", 0, "no description", 60.2], ["65", "I7N3G6G", "65", 0, "no description", 60.2], ["66", "I7N3G6G", "66", 0, "no description", 60.2], ["67", "I7N3G6G", "67", 0, "no description", 60.2], ["68", "I7N3G6G", "68", 0, "no description", 60.2], ["69", "I7N3G6G", "69", 0, "no description", 60.2], ["70", "I7N3G6G", "70", 0, "no description", 60.2], ["71", "I7N3G6G", "71", 0, "no description", 60.2], ["72", "I7N3G6G", "72", 0, "no description", 60.2], ["73", "I7N3G6G", "73", 0, "no description", 60.2], ["74", "I7N3G6G", "74", 0, "no description", 60.2], ["75", "I7N3G6G", "75", 0, "no description", 60.2], ["76", "I7N3G6G", "76", 0, "no description", 60.2], ["77", "I7N3G6G", "77", 0, "no description", 60.2], ["78", "I7N3G6G", "78", 0, "no description", 60.2], ["79", "I7N3G6G", "79", 0, "no description", 60.2], ["80", "I7N3G6G", "80", 0, "no description", 60.2], ["81", "I7N3G6G", "81", 0, "no description", 60.2], ["82", "I7N3G6G", "82", 0, "no description", 60.2], ["83", "I7N3G6G", "83", 0, "no description", 60.2], ["84", "I7N3G6G", "84", 0, "no description", 60.2], ["85", "I7N3G6G", "85", 0, "no description", 60.2], ["86", "I7N3G6G", "86", 0, "no description", 60.2], ["87", "I7N3G6G", "87", 0, "no description", 60.2], ["88", "I7N3G6G", "88", 0, "no description", 60.2], ["89", "I7N3G6G", "89", 0, "no description", 60.2], ["90", "I7N3G6G", "90", 0, "no description", 60.2], ["91", "I7N3G6G", "91", 0, "no description", 60.2], ["92", "I7N3G6G", "92", 0, "no description", 60.2], ["93", "I7N3G6G", "93", 0, "no description", 60.2], ["94", "I7N3G6G", "94", 0, "no description", 60.2], ["95", "I7N3G6G", "95", 0, "no description", 60.2], ["96", "I7N3G6G", "96", 0, "no description", 60.2], ["97", "I7N3G6G", "97", 0, "no description", 60.2], ["98", "I7N3G6G", "98", 0, "no description", 60.2], ["99", "I7N3G6G", "99", 0, "no description", 60.2], ["100", "I7N3G6G", "100", 0, "no description", 60.2], ["101", "I7N3G6G", "101", 0, "no description", 60.2], ["102", "I7N3G6G", "102", 0, "no description", 60.2], ["103", "I7N3G6G", "103", 0, "no description", 60.2], ["104", "I7N3G6G", "104", 0, "no description", 60.2], ["105", "I7N3G6G", "105", 0, "no description", 60.2], ["106", "I7N3G6G", "106", 0, "no description", 60.2], ["107", "I7N3G6G", "107", 0, "no description", 60.2], ["108", "I7N3G6G", "108", 0, "no description", 60.2], ["109", "I7N3G6G", "109", 0, "no description", 60.2], ["110", "I7N3G6G", "110", 0, "no description", 60.2], ["111", "I7N3G6G", "111", 0, "no description", 60.2], ["112", "I7N3G6G", "112", 0, "no description", 60.2], ["113", "I7N3G6G", "113", 0, "no description", 60.2], ["114", "I7N3G6G", "114", 0, "no description", 60.2], ["115", "I7N3G6G", "115", 0, "no description", 60.2], ["116", "I7N3G6G", "116", 0, "no description", 60.2], ["117", "I7N3G6G", "117", 0, "no description", 60.2], ["118", "I7N3G6G", "118", 0, "no description", 60.2], ["119", "I7N3G6G", "119", 0, "no description", 60.2], ["120", "I7N3G6G", "120", 0, "no description", 60.2], ["121", "I7N3G6G", "121", 0, "no description", 60.2], ["122", "I7N3G6G", "122", 0, "no description", 60.2], ["123", "I7N3G6G", "123", 0, "no description", 60.2], ["124", "I7N3G6G", "124", 0, "no description", 60.2], ["125", "I7N3G6G", "125", 0, "no description", 60.2], ["126", "I7N3G6G", "126", 0, "no description", 60.2], ["127", "I7N3G6G", "127", 0, "no description", 60.2], ["128", "I7N3G6G", "128", 0, "no description", 60.2], ["129", "I7N3G6G", "129", 0, "no description", 60.2], ["130", "I7N3G6G", "130", 0, "no description", 60.2], ["131", "I7N3G6G", "131", 0, "no description", 60.2], ["132", "I7N3G6G", "132", 0, "no description", 60.2], ["133", "I7N3G6G", "133", 0, "no description", 60.2], ["134", "I7N3G6G", "134", 0, "no description", 60.2], ["135", "I7N3G6G", "135", 0, "no description", 60.2], ["136", "I7N3G6G", "136", 0, "no description", 60.2], ["137", "I7N3G6G", "137", 0, "no description", 60.2], ["138", "I7N3G6G", "138", 0, "no description", 60.2], ["139", "I7N3G6G", "139", 0, "no description", 60.2], ["140", "I7N3G6G", "140", 0, "no description", 60.2], ["141", "I7N3G6G", "141", 0, "no description", 60.2], ["142", "I7N3G6G", "142", 0, "no description", 60.2], ["143", "I7N3G6G", "143", 0, "no description", 60.2], ["144", "I7N3G6G", "144", 0, "no description", 60.2], ["145", "I7N3G6G", "145", 0, "no description", 60.2], ["146", "I7N3G6G", "146", 0, "no description", 60.2], ["147", "I7N3G6G", "147", 0, "no description", 60.2], ["148", "I7N3G6G", "148", 0, "no description", 60.2], ["149", "I7N3G6G", "149", 0, "no description", 60.2], ["150", "I7N3G6G", "150", 0, "no description", 60.2], ["151", "I7N3G6G", "151", 0, "no description", 60.2], ["152", "I7N3G6G", "152", 0, "no description", 60.2], ["153", "I7N3G6G", "153", 0, "no description", 60.2], ["154", "I7N3G6G", "154", 0, "no description", 60.2], ["155", "I7N3G6G", "155", 0, "no description", 60.2], ["156", "I7N3G6G", "156", 0, "no description", 60.2], ["157", "I7N3G6G", "157", 0, "no description", 60.2], ["158", "I7N3G6G", "158", 0, "no description", 60.2], ["159", "I7N3G6G", "159", 0, "no description", 60.2], ["160", "I7N3G6G", "160", 0, "no description", 60.2], ["161", "I7N3G6G", "161", 0, "no description", 60.2], ["162", "I7N3G6G", "162", 0, "no description", 60.2], ["163", "I7N3G6G", "163", 0, "no description", 60.2], ["164", "I7N3G6G", "164", 0, "no description", 60.2], ["165", "I7N3G6G", "165", 0, "no description", 60.2], ["166", "I7N3G6G", "166", 0, "no description", 60.2], ["167", "I7N3G6G", "167", 0, "no description", 60.2], ["168", "I7N3G6G", "168", 0, "no description", 60.2], ["169", "I7N3G6G", "169", 0, "no description", 60.2], ["170", "I7N3G6G", "170", 0, "no description", 60.2], ["171", "I7N3G6G", "171", 0, "no description", 60.2], ["172", "I7N3G6G", "172", 0, "no description", 60.2], ["173", "I7N3G6G", "173", 0, "no description", 60.2], ["174", "I7N3G6G", "174", 0, "no description", 60.2], ["175", "I7N3G6G", "175", 0, "no description", 60.2], ["176", "I7N3G6G", "176", 0, "no description", 60.2], ["177", "I7N3G6G", "177", 0, "no description", 60.2], ["178", "I7N3G6G", "178", 0, "no description", 60.2], ["179", "I7N3G6G", "179", 0, "no description", 60.2], ["180", "I7N3G6G", "180", 0, "no description", 60.2], ["181", "I7N3G6G", "181", 0, "no description", 60.2], ["182", "I7N3G6G", "182", 0, "no description", 60.2], ["183", "I7N3G6G", "183", 0, "no description", 60.2], ["184", "I7N3G6G", "184", 0, "no description", 60.2], ["185", "I7N3G6G", "185", 0, "no description", 60.2], ["186", "I7N3G6G", "186", 0, "no description", 60.2], ["187", "I7N3G6G", "187", 0, "no description", 60.2], ["188", "I7N3G6G", "188", 0, "no description", 60.2], ["189", "I7N3G6G", "189", 0, "no description", 60.2], ["190", "I7N3G6G", "190", 0, "no description", 60.2], ["191", "I7N3G6G", "191", 0, "no description", 60.2], ["192", "I7N3G6G", "192", 0, "no description", 60.2], ["193", "I7N3G6G", "193", 0, "no description", 60.2], ["194", "I7N3G6G", "194", 0, "no description", 60.2], ["195", "I7N3G6G", "195", 0, "no description", 60.2], ["196", "I7N3G6G", "196", 0, "no description", 60.2], ["197", "I7N3G6G", "197", 0, "no description", 60.2], ["198", "I7N3G6G", "198", 0, "no description", 60.2], ["199", "I7N3G6G", "199", 0, "no description", 60.2], ["200", "I7N3G6G", "200", 0, "no description", 60.2], ["201", "I7N3G6G", "201", 0, "no description", 60.2], ["202", "I7N3G6G", "202", 0, "no description", 60.2], ["203", "I7N3G6G", "203", 0, "no description", 60.2], ["204", "I7N3G6G", "204", 0, "no description", 60.2], ["205", "I7N3G6G", "205", 0, "no description", 60.2], ["206", "I7N3G6G", "206", 0, "no description", 60.2], ["207", "I7N3G6G", "207", 0, "no description", 60.2], ["208", "I7N3G6G", "208", 0, "no description", 60.2], ["209", "I7N3G6G", "209", 0, "no description", 60.2], ["210", "I7N3G6G", "210", 0, "no description", 60.2], ["211", "I7N3G6G", "211", 0, "no description", 60.2], ["212", "I7N3G6G", "212", 0, "no description", 60.2], ["213", "I7N3G6G", "213", 0, "no description", 60.2], ["214", "I7N3G6G", "214", 0, "no description", 60.2], ["215", "I7N3G6G", "215", 0, "no description", 60.2], ["216", "I7N3G6G", "216", 0, "no description", 60.2], ["217", "I7N3G6G", "217", 0, "no description", 60.2], ["218", "I7N3G6G", "218", 0, "no description", 60.2], ["219", "I7N3G6G", "219", 0, "no description", 60.2], ["220", "I7N3G6G", "220", 0, "no description", 60.2], ["221", "I7N3G6G", "221", 0, "no description", 60.2], ["222", "I7N3G6G", "222", 0, "no description", 60.2], ["223", "I7N3G6G", "223", 0, "no description", 60.2], ["224", "I7N3G6G", "224", 0, "no description", 60.2], ["225", "I7N3G6G", "225", 0, "no description", 60.2], ["226", "I7N3G6G", "226", 0, "no description", 60.2], ["227", "I7N3G6G", "227", 0, "no description", 60.2], ["228", "I7N3G6G", "228", 0, "no description", 60.2], ["229", "I7N3G6G", "229", 0, "no description", 60.2], ["230", "I7N3G6G", "230", 0, "no description", 60.2], ["231", "I7N3G6G", "231", 0, "no description", 60.2], ["232", "I7N3G6G", "232", 0, "no description", 60.2], ["233", "I7N3G6G", "233", 0, "no description", 60.2], ["234", "I7N3G6G", "234", 0, "no description", 60.2], ["235", "I7N3G6G", "235", 0, "no description", 60.2], ["236", "I7N3G6G", "236", 0, "no description", 60.2], ["237", "I7N3G6G", "237", 0, "no description", 60.2], ["238", "I7N3G6G", "238", 0, "no description", 60.2], ["239", "I7N3G6G", "239", 0, "no description", 60.2], ["240", "I7N3G6G", "240", 0, "no description", 60.2], ["241", "I7N3G6G", "241", 0, "no description", 60.2], ["242", "I7N3G6G", "242", 0, "no description", 60.2], ["243", "I7N3G6G", "243", 0, "no description", 60.2], ["244", "I7N3G6G", "244", 0, "no description", 60.2], ["245", "I7N3G6G", "245", 0, "no description", 60.2], ["246", "I7N3G6G", "246", 0, "no description", 60.2], ["247", "I7N3G6G", "247", 0, "no description", 60.2], ["248", "I7N3G6G", "248", 0, "no description", 60.2], ["249", "I7N3G6G", "249", 0, "no description", 60.2], ["250", "I7N3G6G", "250", 0, "no description", 60.2], ["251", "I7N3G6G", "251", 0, "no description", 60.2], ["252", "I7N3G6G", "252", 0, "no description", 60.2], ["253", "I7N3G6G", "253", 0, "no description", 60.2], ["254", "I7N3G6G", "254", 0, "no description", 60.2], ["255", "I7N3G6G", "255", 0, "no description", 60.2], ["256", "I7N3G6G", "256", 0, "no description", 60.2], ["257", "I7N3G6G", "257", 0, "no description", 60.2], ["258", "I7N3G6G", "258", 0, "no description", 60.2], ["259", "I7N3G6G", "259", 0, "no description", 60.2], ["260", "I7N3G6G", "260", 0, "no description", 60.2], ["261", "I7N3G6G", "261", 0, "no description", 60.2], ["262", "I7N3G6G", "262", 0, "no description", 60.2], ["263", "I7N3G6G", "263", 0, "no description", 60.2], ["264", "I7N3G6G", "264", 0, "no description", 60.2], ["265", "I7N3G6G", "265", 0, "no description", 60.2], ["266", "I7N3G6G", "266", 0, "no description", 60.2], ["267", "I7N3G6G", "267", 0, "no description", 60.2], ["268", "I7N3G6G", "268", 0, "no description", 60.2], ["269", "I7N3G6G", "269", 0, "no description", 60.2], ["270", "I7N3G6G", "270", 0, "no description", 60.2], ["271", "I7N3G6G", "271", 0, "no description", 60.2], ["272", "I7N3G6G", "272", 0, "no description", 60.2], ["273", "I7N3G6G", "273", 0, "no description", 60.2], ["274", "I7N3G6G", "274", 0, "no description", 60.2], ["275", "I7N3G6G", "275", 0, "no description", 60.2], ["276", "I7N3G6G", "276", 0, "no description", 60.2], ["277", "I7N3G6G", "277", 0, "no description", 60.2], ["278", "I7N3G6G", "278", 0, "no description", 60.2], ["279", "I7N3G6G", "279", 0, "no description", 60.2], ["280", "I7N3G6G", "280", 0, "no description", 60.2], ["281", "I7N3G6G", "281", 0, "no description", 60.2], ["282", "I7N3G6G", "282", 0, "no description", 60.2], ["283", "I7N3G6G", "283", 0, "no description", 60.2], ["284", "I7N3G6G", "284", 0, "no description", 60.2], ["285", "I7N3G6G", "285", 0, "no description", 60.2], ["286", "I7N3G6G", "286", 0, "no description", 60.2], ["287", "I7N3G6G", "287", 0, "no description", 60.2], ["288", "I7N3G6G", "288", 0, "no description", 60.2], ["289", "I7N3G6G", "289", 0, "no description", 60.2], ["290", "I7N3G6G", "290", 0, "no description", 60.2], ["291", "I7N3G6G", "291", 0, "no description", 60.2], ["292", "I7N3G6G", "292", 0, "no description", 60.2], ["293", "I7N3G6G", "293", 0, "no description", 60.2], ["294", "I7N3G6G", "294", 0, "no description", 60.2], ["295", "I7N3G6G", "295", 0, "no description", 60.2], ["296", "I7N3G6G", "296", 0, "no description", 60.2], ["297", "I7N3G6G", "297", 0, "no description", 60.2], ["298", "I7N3G6G", "298", 0, "no description", 60.2], ["299", "I7N3G6G", "299", 0, "no description", 60.2], ["300", "I7N3G6G", "300", 0, "no description", 60.2], ["301", "I7N3G6G", "301", 0, "no description", 60.2], ["302", "I7N3G6G", "302", 0, "no description", 60.2], ["303", "I7N3G6G", "303", 0, "no description", 60.2], ["304", "I7N3G6G", "304", 0, "no description", 60.2], ["305", "I7N3G6G", "305", 0, "no description", 60.2], ["306", "I7N3G6G", "306", 0, "no description", 60.2], ["307", "I7N3G6G", "307", 0, "no description", 60.2], ["308", "I7N3G6G", "308", 0, "no description", 60.2], ["309", "I7N3G6G", "309", 0, "no description", 60.2], ["310", "I7N3G6G", "310", 0, "no description", 60.2], ["311", "I7N3G6G", "311", 0, "no description", 60.2], ["312", "I7N3G6G", "312", 0, "no description", 60.2], ["313", "I7N3G6G", "313", 0, "no description", 60.2], ["314", "I7N3G6G", "314", 0, "no description", 60.2], ["315", "I7N3G6G", "315", 0, "no description", 60.2], ["316", "I7N3G6G", "316", 0, "no description", 60.2], ["317", "I7N3G6G", "317", 0, "no description", 60.2], ["318", "I7N3G6G", "318", 0, "no description", 60.2], ["319", "I7N3G6G", "319", 0, "no description", 60.2], ["320", "I7N3G6G", "320", 0, "no description", 60.2], ["321", "I7N3G6G", "321", 0, "no description", 60.2], ["322", "I7N3G6G", "322", 0, "no description", 60.2], ["323", "I7N3G6G", "323", 0, "no description", 60.2], ["324", "I7N3G6G", "324", 0, "no description", 60.2], ["325", "I7N3G6G", "325", 0, "no description", 60.2], ["326", "I7N3G6G", "326", 0, "no description", 60.2], ["327", "I7N3G6G", "327", 0, "no description", 60.2], ["328", "I7N3G6G", "328", 0, "no description", 60.2], ["329", "I7N3G6G", "329", 0, "no description", 60.2], ["330", "I7N3G6G", "330", 0, "no description", 60.2], ["331", "I7N3G6G", "331", 0, "no description", 60.2], ["332", "I7N3G6G", "332", 0, "no description", 60.2], ["333", "I7N3G6G", "333", 0, "no description", 60.2], ["334", "I7N3G6G", "334", 0, "no description", 60.2], ["335", "I7N3G6G", "335", 0, "no description", 60.2], ["336", "I7N3G6G", "336", 0, "no description", 60.2], ["337", "I7N3G6G", "337", 0, "no description", 60.2], ["338", "I7N3G6G", "338", 0, "no description", 60.2], ["339", "I7N3G6G", "339", 0, "no description", 60.2], ["340", "I7N3G6G", "340", 0, "no description", 60.2], ["341", "I7N3G6G", "341", 0, "no description", 60.2], ["342", "I7N3G6G", "342", 0, "no description", 60.2], ["343", "I7N3G6G", "343", 0, "no description", 60.2], ["344", "I7N3G6G", "344", 0, "no description", 60.2], ["345", "I7N3G6G", "345", 0, "no description", 60.2], ["346", "I7N3G6G", "346", 0, "no description", 60.2], ["347", "I7N3G6G", "347", 0, "no description", 60.2], ["348", "I7N3G6G", "348", 0, "no description", 60.2], ["349", "I7N3G6G", "349", 0, "no description", 60.2], ["350", "I7N3G6G", "350", 0, "no description", 60.2], ["351", "I7N3G6G", "351", 0, "no description", 60.2], ["352", "I7N3G6G", "352", 0, "no description", 60.2], ["353", "I7N3G6G", "353", 0, "no description", 60.2], ["354", "I7N3G6G", "354", 0, "no description", 60.2], ["355", "I7N3G6G", "355", 0, "no description", 60.2], ["356", "I7N3G6G", "356", 0, "no description", 60.2], ["357", "I7N3G6G", "357", 0, "no description", 60.2], ["358", "I7N3G6G", "358", 0, "no description", 60.2], ["359", "I7N3G6G", "359", 0, "no description", 60.2], ["360", "I7N3G6G", "360", 0, "no description", 60.2], ["361", "I7N3G6G", "361", 0, "no description", 60.2], ["362", "I7N3G6G", "362", 0, "no description", 60.2], ["363", "I7N3G6G", "363", 0, "no description", 60.2], ["364", "I7N3G6G", "364", 0, "no description", 60.2], ["365", "I7N3G6G", "365", 0, "no description", 60.2], ["366", "I7N3G6G", "366", 0, "no description", 60.2], ["367", "I7N3G6G", "367", 0, "no description", 60.2], ["368", "I7N3G6G", "368", 0, "no description", 60.2], ["369", "I7N3G6G", "369", 0, "no description", 60.2], ["370", "I7N3G6G", "370", 0, "no description", 60.2], ["371", "I7N3G6G", "371", 0, "no description", 60.2], ["372", "I7N3G6G", "372", 0, "no description", 60.2], ["373", "I7N3G6G", "373", 0, "no description", 60.2], ["374", "I7N3G6G", "374", 0, "no description", 60.2], ["375", "I7N3G6G", "375", 0, "no description", 60.2], ["376", "I7N3G6G", "376", 0, "no description", 60.2], ["377", "I7N3G6G", "377", 0, "no description", 60.2], ["378", "I7N3G6G", "378", 0, "no description", 60.2], ["379", "I7N3G6G", "379", 0, "no description", 60.2], ["380", "I7N3G6G", "380", 0, "no description", 60.2], ["381", "I7N3G6G", "381", 0, "no description", 60.2], ["382", "I7N3G6G", "382", 0, "no description", 60.2], ["383", "I7N3G6G", "383", 0, "no description", 60.2], ["384", "I7N3G6G", "384", 0, "no description", 60.2], ["385", "I7N3G6G", "385", 0, "no description", 60.2], ["386", "I7N3G6G", "386", 0, "no description", 60.2], ["387", "I7N3G6G", "387", 0, "no description", 60.2], ["388", "I7N3G6G", "388", 0, "no description", 60.2], ["389", "I7N3G6G", "389", 0, "no description", 60.2], ["390", "I7N3G6G", "390", 0, "no description", 60.2], ["391", "I7N3G6G", "391", 0, "no description", 60.2], ["392", "I7N3G6G", "392", 0, "no description", 60.2], ["393", "I7N3G6G", "393", 0, "no description", 60.2], ["394", "I7N3G6G", "394", 0, "no description", 60.2], ["395", "I7N3G6G", "395", 0, "no description", 60.2], ["396", "I7N3G6G", "396", 0, "no description", 60.2], ["397", "I7N3G6G", "397", 0, "no description", 60.2], ["398", "I7N3G6G", "398", 0, "no description", 60.2], ["399", "I7N3G6G", "399", 0, "no description", 60.2], ["400", "I7N3G6G", "400", 0, "no description", 60.2], ["401", "I7N3G6G", "401", 0, "no description", 60.2], ["402", "I7N3G6G", "402", 0, "no description", 60.2], ["403", "I7N3G6G", "403", 0, "no description", 60.2], ["404", "I7N3G6G", "404", 0, "no description", 60.2], ["405", "I7N3G6G", "405", 0, "no description", 60.2], ["406", "I7N3G6G", "406", 0, "no description", 60.2], ["407", "I7N3G6G", "407", 0, "no description", 60.2], ["408", "I7N3G6G", "408", 0, "no description", 60.2], ["409", "I7N3G6G", "409", 0, "no description", 60.2], ["410", "I7N3G6G", "410", 0, "no description", 60.2], ["411", "I7N3G6G", "411", 0, "no description", 60.2], ["412", "I7N3G6G", "412", 0, "no description", 60.2], ["413", "I7N3G6G", "413", 0, "no description", 60.2], ["414", "I7N3G6G", "414", 0, "no description", 60.2], ["415", "I7N3G6G", "415", 0, "no description", 60.2], ["416", "I7N3G6G", "416", 0, "no description", 60.2], ["417", "I7N3G6G", "417", 0, "no description", 60.2], ["418", "I7N3G6G", "418", 0, "no description", 60.2], ["419", "I7N3G6G", "419", 0, "no description", 60.2], ["420", "I7N3G6G", "420", 0, "no description", 60.2], ["421", "I7N3G6G", "421", 0, "no description", 60.2], ["422", "I7N3G6G", "422", 0, "no description", 60.2], ["423", "I7N3G6G", "423", 0, "no description", 60.2], ["424", "I7N3G6G", "424", 0, "no description", 60.2], ["425", "I7N3G6G", "425", 0, "no description", 60.2], ["426", "I7N3G6G", "426", 0, "no description", 60.2], ["427", "I7N3G6G", "427", 0, "no description", 60.2], ["428", "I7N3G6G", "428", 0, "no description", 60.2], ["429", "I7N3G6G", "429", 0, "no description", 60.2], ["430", "I7N3G6G", "430", 0, "no description", 60.2], ["431", "I7N3G6G", "431", 0, "no description", 60.2], ["432", "I7N3G6G", "432", 0, "no description", 60.2], ["433", "I7N3G6G", "433", 0, "no description", 60.2], ["434", "I7N3G6G", "434", 0, "no description", 60.2], ["435", "I7N3G6G", "435", 0, "no description", 60.2], ["436", "I7N3G6G", "436", 0, "no description", 60.2], ["437", "I7N3G6G", "437", 0, "no description", 60.2], ["438", "I7N3G6G", "438", 0, "no description", 60.2], ["439", "I7N3G6G", "439", 0, "no description", 60.2], ["440", "I7N3G6G", "440", 0, "no description", 60.2], ["441", "I7N3G6G", "441", 0, "no description", 60.2], ["442", "I7N3G6G", "442", 0, "no description", 60.2], ["443", "I7N3G6G", "443", 0, "no description", 60.2], ["444", "I7N3G6G", "444", 0, "no description", 60.2], ["445", "I7N3G6G", "445", 0, "no description", 60.2], ["446", "I7N3G6G", "446", 0, "no description", 60.2], ["447", "I7N3G6G", "447", 0, "no description", 60.2], ["448", "I7N3G6G", "448", 0, "no description", 60.2], ["449", "I7N3G6G", "449", 0, "no description", 60.2], ["450", "I7N3G6G", "450", 0, "no description", 60.2], ["451", "I7N3G6G", "451", 0, "no description", 60.2], ["452", "I7N3G6G", "452", 0, "no description", 60.2], ["453", "I7N3G6G", "453", 0, "no description", 60.2], ["454", "I7N3G6G", "454", 0, "no description", 60.2], ["455", "I7N3G6G", "455", 0, "no description", 60.2], ["456", "I7N3G6G", "456", 0, "no description", 60.2], ["457", "I7N3G6G", "457", 0, "no description", 60.2], ["458", "I7N3G6G", "458", 0, "no description", 60.2], ["459", "I7N3G6G", "459", 0, "no description", 60.2], ["460", "I7N3G6G", "460", 0, "no description", 60.2], ["461", "I7N3G6G", "461", 0, "no description", 60.2], ["462", "I7N3G6G", "462", 0, "no description", 60.2], ["463", "I7N3G6G", "463", 0, "no description", 60.2], ["464", "I7N3G6G", "464", 0, "no description", 60.2], ["465", "I7N3G6G", "465", 0, "no description", 60.2], ["466", "I7N3G6G", "466", 0, "no description", 60.2], ["467", "I7N3G6G", "467", 0, "no description", 60.2], ["468", "I7N3G6G", "468", 0, "no description", 60.2], ["469", "I7N3G6G", "469", 0, "no description", 60.2], ["470", "I7N3G6G", "470", 0, "no description", 60.2], ["471", "I7N3G6G", "471", 0, "no description", 60.2], ["472", "I7N3G6G", "472", 0, "no description", 60.2], ["473", "I7N3G6G", "473", 0, "no description", 60.2], ["474", "I7N3G6G", "474", 0, "no description", 60.2], ["475", "I7N3G6G", "475", 0, "no description", 60.2], ["476", "I7N3G6G", "476", 0, "no description", 60.2], ["477", "I7N3G6G", "477", 0, "no description", 60.2], ["478", "I7N3G6G", "478", 0, "no description", 60.2], ["479", "I7N3G6G", "479", 0, "no description", 60.2], ["480", "I7N3G6G", "480", 0, "no description", 60.2], ["481", "I7N3G6G", "481", 0, "no description", 60.2], ["482", "I7N3G6G", "482", 0, "no description", 60.2], ["483", "I7N3G6G", "483", 0, "no description", 60.2], ["484", "I7N3G6G", "484", 0, "no description", 60.2], ["485", "I7N3G6G", "485", 0, "no description", 60.2], ["486", "I7N3G6G", "486", 0, "no description", 60.2], ["487", "I7N3G6G", "487", 0, "no description", 60.2], ["488", "I7N3G6G", "488", 0, "no description", 60.2], ["489", "I7N3G6G", "489", 0, "no description", 60.2], ["490", "I7N3G6G", "490", 0, "no description", 60.2], ["491", "I7N3G6G", "491", 0, "no description", 60.2], ["492", "I7N3G6G", "492", 0, "no description", 60.2], ["493", "I7N3G6G", "493", 0, "no description", 60.2], ["494", "I7N3G6G", "494", 0, "no description", 60.2], ["495", "I7N3G6G", "495", 0, "no description", 60.2], ["496", "I7N3G6G", "496", 0, "no description", 60.2], ["497", "I7N3G6G", "497", 0, "no description", 60.2], ["498", "I7N3G6G", "498", 0, "no description", 60.2], ["499", "I7N3G6G", "499", 0, "no description", 60.2], ["500", "I7N3G6G", "500", 0, "no description", 60.2], ["501", "I7N3G6G", "501", 0, "no description", 60.2], ["502", "I7N3G6G", "502", 0, "no description", 60.2], ["503", "I7N3G6G", "503", 0, "no description", 60.2], ["504", "I7N3G6G", "504", 0, "no description", 60.2], ["505", "I7N3G6G", "505", 0, "no description", 60.2], ["506", "I7N3G6G", "506", 0, "no description", 60.2], ["507", "I7N3G6G", "507", 0, "no description", 60.2], ["508", "I7N3G6G", "508", 0, "no description", 60.2], ["509", "I7N3G6G", "509", 0, "no description", 60.2], ["510", "I7N3G6G", "510", 0, "no description", 60.2], ["511", "I7N3G6G", "511", 0, "no description", 60.2], ["512", "I7N3G6G", "512", 0, "no description", 60.2], ["513", "I7N3G6G", "513", 0, "no description", 60.2], ["514", "I7N3G6G", "514", 0, "no description", 60.2], ["515", "I7N3G6G", "515", 0, "no description", 60.2], ["516", "I7N3G6G", "516", 0, "no description", 60.2], ["517", "I7N3G6G", "517", 0, "no description", 60.2], ["518", "I7N3G6G", "518", 0, "no description", 60.2], ["519", "I7N3G6G", "519", 0, "no description", 60.2], ["520", "I7N3G6G", "520", 0, "no description", 60.2], ["521", "I7N3G6G", "521", 0, "no description", 60.2], ["522", "I7N3G6G", "522", 0, "no description", 60.2], ["523", "I7N3G6G", "523", 0, "no description", 60.2], ["524", "I7N3G6G", "524", 0, "no description", 60.2], ["525", "I7N3G6G", "525", 0, "no description", 60.2], ["526", "I7N3G6G", "526", 0, "no description", 60.2], ["527", "I7N3G6G", "527", 0, "no description", 60.2], ["528", "I7N3G6G", "528", 0, "no description", 60.2], ["529", "I7N3G6G", "529", 0, "no description", 60.2], ["530", "I7N3G6G", "530", 0, "no description", 60.2], ["531", "I7N3G6G", "531", 0, "no description", 60.2], ["532", "I7N3G6G", "532", 0, "no description", 60.2], ["533", "I7N3G6G", "533", 0, "no description", 60.2], ["534", "I7N3G6G", "534", 0, "no description", 60.2], ["535", "I7N3G6G", "535", 0, "no description", 60.2], ["536", "I7N3G6G", "536", 0, "no description", 60.2], ["537", "I7N3G6G", "537", 0, "no description", 60.2], ["538", "I7N3G6G", "538", 0, "no description", 60.2], ["539", "I7N3G6G", "539", 0, "no description", 60.2], ["540", "I7N3G6G", "540", 0, "no description", 60.2], ["541", "I7N3G6G", "541", 0, "no description", 60.2], ["542", "I7N3G6G", "542", 0, "no description", 60.2], ["543", "I7N3G6G", "543", 0, "no description", 60.2], ["544", "I7N3G6G", "544", 0, "no description", 60.2], ["545", "I7N3G6G", "545", 0, "no description", 60.2], ["546", "I7N3G6G", "546", 0, "no description", 60.2], ["547", "I7N3G6G", "547", 0, "no description", 60.2], ["548", "I7N3G6G", "548", 0, "no description", 60.2], ["549", "I7N3G6G", "549", 0, "no description", 60.2], ["550", "I7N3G6G", "550", 0, "no description", 60.2], ["551", "I7N3G6G", "551", 0, "no description", 60.2], ["552", "I7N3G6G", "552", 0, "no description", 60.2], ["553", "I7N3G6G", "553", 0, "no description", 60.2], ["554", "I7N3G6G", "554", 0, "no description", 60.2], ["555", "I7N3G6G", "555", 0, "no description", 60.2], ["556", "I7N3G6G", "556", 0, "no description", 60.2], ["557", "I7N3G6G", "557", 0, "no description", 60.2], ["558", "I7N3G6G", "558", 0, "no description", 60.2], ["559", "I7N3G6G", "559", 0, "no description", 60.2], ["560", "I7N3G6G", "560", 0, "no description", 60.2], ["561", "I7N3G6G", "561", 0, "no description", 60.2], ["562", "I7N3G6G", "562", 0, "no description", 60.2], ["563", "I7N3G6G", "563", 0, "no description", 60.2], ["564", "I7N3G6G", "564", 0, "no description", 60.2], ["565", "I7N3G6G", "565", 0, "no description", 60.2], ["566", "I7N3G6G", "566", 0, "no description", 60.2], ["567", "I7N3G6G", "567", 0, "no description", 60.2], ["568", "I7N3G6G", "568", 0, "no description", 60.2], ["569", "I7N3G6G", "569", 0, "no description", 60.2], ["570", "I7N3G6G", "570", 0, "no description", 60.2], ["571", "I7N3G6G", "571", 0, "no description", 60.2], ["572", "I7N3G6G", "572", 0, "no description", 60.2], ["573", "I7N3G6G", "573", 0, "no description", 60.2], ["574", "I7N3G6G", "574", 0, "no description", 60.2], ["575", "I7N3G6G", "575", 0, "no description", 60.2], ["576", "I7N3G6G", "576", 0, "no description", 60.2], ["577", "I7N3G6G", "577", 0, "no description", 60.2], ["578", "I7N3G6G", "578", 0, "no description", 60.2], ["579", "I7N3G6G", "579", 0, "no description", 60.2], ["580", "I7N3G6G", "580", 0, "no description", 60.2], ["581", "I7N3G6G", "581", 0, "no description", 60.2], ["582", "I7N3G6G", "582", 0, "no description", 60.2], ["583", "I7N3G6G", "583", 0, "no description", 60.2], ["584", "I7N3G6G", "584", 0, "no description", 60.2], ["585", "I7N3G6G", "585", 0, "no description", 60.2], ["586", "I7N3G6G", "586", 0, "no description", 60.2], ["587", "I7N3G6G", "587", 0, "no description", 60.2], ["588", "I7N3G6G", "588", 0, "no description", 60.2], ["589", "I7N3G6G", "589", 0, "no description", 60.2], ["590", "I7N3G6G", "590", 0, "no description", 60.2], ["591", "I7N3G6G", "591", 0, "no description", 60.2], ["592", "I7N3G6G", "592", 0, "no description", 60.2], ["593", "I7N3G6G", "593", 0, "no description", 60.2], ["594", "I7N3G6G", "594", 0, "no description", 60.2], ["595", "I7N3G6G", "595", 0, "no description", 60.2], ["596", "I7N3G6G", "596", 0, "no description", 60.2], ["597", "I7N3G6G", "597", 0, "no description", 60.2], ["598", "I7N3G6G", "598", 0, "no description", 60.2], ["599", "I7N3G6G", "599", 0, "no description", 60.2], ["600", "I7N3G6G", "600", 0, "no description", 60.2], ["601", "I7N3G6G", "601", 0, "no description", 60.2], ["602", "I7N3G6G", "602", 0, "no description", 60.2], ["603", "I7N3G6G", "603", 0, "no description", 60.2], ["604", "I7N3G6G", "604", 0, "no description", 60.2], ["605", "I7N3G6G", "605", 0, "no description", 60.2], ["606", "I7N3G6G", "606", 0, "no description", 60.2], ["607", "I7N3G6G", "607", 0, "no description", 60.2], ["608", "I7N3G6G", "608", 0, "no description", 60.2], ["609", "I7N3G6G", "609", 0, "no description", 60.2], ["610", "I7N3G6G", "610", 0, "no description", 60.2], ["611", "I7N3G6G", "611", 0, "no description", 60.2], ["612", "I7N3G6G", "612", 0, "no description", 60.2], ["613", "I7N3G6G", "613", 0, "no description", 60.2], ["614", "I7N3G6G", "614", 0, "no description", 60.2], ["615", "I7N3G6G", "615", 0, "no description", 60.2], ["616", "I7N3G6G", "616", 0, "no description", 60.2], ["617", "I7N3G6G", "617", 0, "no description", 60.2], ["618", "I7N3G6G", "618", 0, "no description", 60.2], ["619", "I7N3G6G", "619", 0, "no description", 60.2], ["620", "I7N3G6G", "620", 0, "no description", 60.2], ["621", "I7N3G6G", "621", 0, "no description", 60.2], ["622", "I7N3G6G", "622", 0, "no description", 60.2], ["623", "I7N3G6G", "623", 0, "no description", 60.2], ["624", "I7N3G6G", "624", 0, "no description", 60.2], ["625", "I7N3G6G", "625", 0, "no description", 60.2], ["626", "I7N3G6G", "626", 0, "no description", 60.2], ["627", "I7N3G6G", "627", 0, "no description", 60.2], ["628", "I7N3G6G", "628", 0, "no description", 60.2], ["629", "I7N3G6G", "629", 0, "no description", 60.2], ["630", "I7N3G6G", "630", 0, "no description", 60.2], ["631", "I7N3G6G", "631", 0, "no description", 60.2], ["632", "I7N3G6G", "632", 0, "no description", 60.2], ["633", "I7N3G6G", "633", 0, "no description", 60.2], ["634", "I7N3G6G", "634", 0, "no description", 60.2], ["635", "I7N3G6G", "635", 0, "no description", 60.2], ["636", "I7N3G6G", "636", 0, "no description", 60.2], ["637", "I7N3G6G", "637", 0, "no description", 60.2], ["638", "I7N3G6G", "638", 0, "no description", 60.2], ["639", "I7N3G6G", "639", 0, "no description", 60.2], ["640", "I7N3G6G", "640", 0, "no description", 60.2], ["641", "I7N3G6G", "641", 0, "no description", 60.2], ["642", "I7N3G6G", "642", 0, "no description", 60.2], ["643", "I7N3G6G", "643", 0, "no description", 60.2], ["644", "I7N3G6G", "644", 0, "no description", 60.2], ["645", "I7N3G6G", "645", 0, "no description", 60.2], ["646", "I7N3G6G", "646", 0, "no description", 60.2], ["647", "I7N3G6G", "647", 0, "no description", 60.2], ["648", "I7N3G6G", "648", 0, "no description", 60.2], ["649", "I7N3G6G", "649", 0, "no description", 60.2], ["650", "I7N3G6G", "650", 0, "no description", 60.2], ["651", "I7N3G6G", "651", 0, "no description", 60.2], ["652", "I7N3G6G", "652", 0, "no description", 60.2], ["653", "I7N3G6G", "653", 0, "no description", 60.2], ["654", "I7N3G6G", "654", 0, "no description", 60.2], ["655", "I7N3G6G", "655", 0, "no description", 60.2], ["656", "I7N3G6G", "656", 0, "no description", 60.2], ["657", "I7N3G6G", "657", 0, "no description", 60.2], ["658", "I7N3G6G", "658", 0, "no description", 60.2], ["659", "I7N3G6G", "659", 0, "no description", 60.2], ["660", "I7N3G6G", "660", 0, "no description", 60.2], ["661", "I7N3G6G", "661", 0, "no description", 60.2], ["662", "I7N3G6G", "662", 0, "no description", 60.2], ["663", "I7N3G6G", "663", 0, "no description", 60.2], ["664", "I7N3G6G", "664", 0, "no description", 60.2], ["665", "I7N3G6G", "665", 0, "no description", 60.2], ["666", "I7N3G6G", "666", 0, "no description", 60.2], ["667", "I7N3G6G", "667", 0, "no description", 60.2], ["668", "I7N3G6G", "668", 0, "no description", 60.2], ["669", "I7N3G6G", "669", 0, "no description", 60.2], ["670", "I7N3G6G", "670", 0, "no description", 60.2], ["671", "I7N3G6G", "671", 0, "no description", 60.2], ["672", "I7N3G6G", "672", 0, "no description", 60.2], ["673", "I7N3G6G", "673", 0, "no description", 60.2], ["674", "I7N3G6G", "674", 0, "no description", 60.2], ["675", "I7N3G6G", "675", 0, "no description", 60.2], ["676", "I7N3G6G", "676", 0, "no description", 60.2], ["677", "I7N3G6G", "677", 0, "no description", 60.2], ["678", "I7N3G6G", "678", 0, "no description", 60.2], ["679", "I7N3G6G", "679", 0, "no description", 60.2], ["680", "I7N3G6G", "680", 0, "no description", 60.2], ["681", "I7N3G6G", "681", 0, "no description", 60.2], ["682", "I7N3G6G", "682", 0, "no description", 60.2], ["683", "I7N3G6G", "683", 0, "no description", 60.2], ["684", "I7N3G6G", "684", 0, "no description", 60.2], ["685", "I7N3G6G", "685", 0, "no description", 60.2], ["686", "I7N3G6G", "686", 0, "no description", 60.2], ["687", "I7N3G6G", "687", 0, "no description", 60.2], ["688", "I7N3G6G", "688", 0, "no description", 60.2], ["689", "I7N3G6G", "689", 0, "no description", 60.2], ["690", "I7N3G6G", "690", 0, "no description", 60.2], ["691", "I7N3G6G", "691", 0, "no description", 60.2], ["692", "I7N3G6G", "692", 0, "no description", 60.2], ["693", "I7N3G6G", "693", 0, "no description", 60.2], ["694", "I7N3G6G", "694", 0, "no description", 60.2], ["695", "I7N3G6G", "695", 0, "no description", 60.2], ["696", "I7N3G6G", "696", 0, "no description", 60.2], ["697", "I7N3G6G", "697", 0, "no description", 60.2], ["698", "I7N3G6G", "698", 0, "no description", 60.2], ["699", "I7N3G6G", "699", 0, "no description", 60.2], ["700", "I7N3G6G", "700", 0, "no description", 60.2], ["701", "I7N3G6G", "701", 0, "no description", 60.2], ["702", "I7N3G6G", "702", 0, "no description", 60.2], ["703", "I7N3G6G", "703", 0, "no description", 60.2], ["704", "I7N3G6G", "704", 0, "no description", 60.2], ["705", "I7N3G6G", "705", 0, "no description", 60.2], ["706", "I7N3G6G", "706", 0, "no description", 60.2], ["707", "I7N3G6G", "707", 0, "no description", 60.2], ["708", "I7N3G6G", "708", 0, "no description", 60.2], ["709", "I7N3G6G", "709", 0, "no description", 60.2], ["710", "I7N3G6G", "710", 0, "no description", 60.2], ["711", "I7N3G6G", "711", 0, "no description", 60.2], ["712", "I7N3G6G", "712", 0, "no description", 60.2], ["713", "I7N3G6G", "713", 0, "no description", 60.2], ["714", "I7N3G6G", "714", 0, "no description", 60.2], ["715", "I7N3G6G", "715", 0, "no description", 60.2], ["716", "I7N3G6G", "716", 0, "no description", 60.2], ["717", "I7N3G6G", "717", 0, "no description", 60.2], ["718", "I7N3G6G", "718", 0, "no description", 60.2], ["719", "I7N3G6G", "719", 0, "no description", 60.2], ["720", "I7N3G6G", "720", 0, "no description", 60.2], ["721", "I7N3G6G", "721", 0, "no description", 60.2], ["722", "I7N3G6G", "722", 0, "no description", 60.2], ["723", "I7N3G6G", "723", 0, "no description", 60.2], ["724", "I7N3G6G", "724", 0, "no description", 60.2], ["725", "I7N3G6G", "725", 0, "no description", 60.2], ["726", "I7N3G6G", "726", 0, "no description", 60.2], ["727", "I7N3G6G", "727", 0, "no description", 60.2], ["728", "I7N3G6G", "728", 0, "no description", 60.2], ["729", "I7N3G6G", "729", 0, "no description", 60.2], ["730", "I7N3G6G", "730", 0, "no description", 60.2], ["731", "I7N3G6G", "731", 0, "no description", 60.2], ["732", "I7N3G6G", "732", 0, "no description", 60.2], ["733", "I7N3G6G", "733", 0, "no description", 60.2], ["734", "I7N3G6G", "734", 0, "no description", 60.2], ["735", "I7N3G6G", "735", 0, "no description", 60.2], ["736", "I7N3G6G", "736", 0, "no description", 60.2], ["737", "I7N3G6G", "737", 0, "no description", 60.2], ["738", "I7N3G6G", "738", 0, "no description", 60.2], ["739", "I7N3G6G", "739", 0, "no description", 60.2], ["740", "I7N3G6G", "740", 0, "no description", 60.2], ["741", "I7N3G6G", "741", 0, "no description", 60.2], ["742", "I7N3G6G", "742", 0, "no description", 60.2], ["743", "I7N3G6G", "743", 0, "no description", 60.2], ["744", "I7N3G6G", "744", 0, "no description", 60.2], ["745", "I7N3G6G", "745", 0, "no description", 60.2], ["746", "I7N3G6G", "746", 0, "no description", 60.2], ["747", "I7N3G6G", "747", 0, "no description", 60.2], ["748", "I7N3G6G", "748", 0, "no description", 60.2], ["749", "I7N3G6G", "749", 0, "no description", 60.2], ["750", "I7N3G6G", "750", 0, "no description", 60.2], ["751", "I7N3G6G", "751", 0, "no description", 60.2], ["752", "I7N3G6G", "752", 0, "no description", 60.2], ["753", "I7N3G6G", "753", 0, "no description", 60.2], ["754", "I7N3G6G", "754", 0, "no description", 60.2], ["755", "I7N3G6G", "755", 0, "no description", 60.2], ["756", "I7N3G6G", "756", 0, "no description", 60.2], ["757", "I7N3G6G", "757", 0, "no description", 60.2], ["758", "I7N3G6G", "758", 0, "no description", 60.2], ["759", "I7N3G6G", "759", 0, "no description", 60.2], ["760", "I7N3G6G", "760", 0, "no description", 60.2], ["761", "I7N3G6G", "761", 0, "no description", 60.2], ["762", "I7N3G6G", "762", 0, "no description", 60.2], ["763", "I7N3G6G", "763", 0, "no description", 60.2], ["764", "I7N3G6G", "764", 0, "no description", 60.2], ["765", "I7N3G6G", "765", 0, "no description", 60.2], ["766", "I7N3G6G", "766", 0, "no description", 60.2], ["767", "I7N3G6G", "767", 0, "no description", 60.2], ["768", "I7N3G6G", "768", 0, "no description", 60.2], ["769", "I7N3G6G", "769", 0, "no description", 60.2], ["770", "I7N3G6G", "770", 0, "no description", 60.2], ["771", "I7N3G6G", "771", 0, "no description", 60.2], ["772", "I7N3G6G", "772", 0, "no description", 60.2], ["773", "I7N3G6G", "773", 0, "no description", 60.2], ["774", "I7N3G6G", "774", 0, "no description", 60.2], ["775", "I7N3G6G", "775", 0, "no description", 60.2], ["776", "I7N3G6G", "776", 0, "no description", 60.2], ["777", "I7N3G6G", "777", 0, "no description", 60.2], ["778", "I7N3G6G", "778", 0, "no description", 60.2], ["779", "I7N3G6G", "779", 0, "no description", 60.2], ["780", "I7N3G6G", "780", 0, "no description", 60.2], ["781", "I7N3G6G", "781", 0, "no description", 60.2], ["782", "I7N3G6G", "782", 0, "no description", 60.2], ["783", "I7N3G6G", "783", 0, "no description", 60.2], ["784", "I7N3G6G", "784", 0, "no description", 60.2], ["785", "I7N3G6G", "785", 0, "no description", 60.2], ["786", "I7N3G6G", "786", 0, "no description", 60.2], ["787", "I7N3G6G", "787", 0, "no description", 60.2], ["788", "I7N3G6G", "788", 0, "no description", 60.2], ["789", "I7N3G6G", "789", 0, "no description", 60.2], ["790", "I7N3G6G", "790", 0, "no description", 60.2], ["791", "I7N3G6G", "791", 0, "no description", 60.2], ["792", "I7N3G6G", "792", 0, "no description", 60.2], ["793", "I7N3G6G", "793", 0, "no description", 60.2], ["794", "I7N3G6G", "794", 0, "no description", 60.2], ["795", "I7N3G6G", "795", 0, "no description", 60.2], ["796", "I7N3G6G", "796", 0, "no description", 60.2], ["797", "I7N3G6G", "797", 0, "no description", 60.2], ["798", "I7N3G6G", "798", 0, "no description", 60.2], ["799", "I7N3G6G", "799", 0, "no description", 60.2], ["800", "I7N3G6G", "800", 0, "no description", 60.2], ["801", "I7N3G6G", "801", 0, "no description", 60.2], ["802", "I7N3G6G", "802", 0, "no description", 60.2], ["803", "I7N3G6G", "803", 0, "no description", 60.2], ["804", "I7N3G6G", "804", 0, "no description", 60.2], ["805", "I7N3G6G", "805", 0, "no description", 60.2], ["806", "I7N3G6G", "806", 0, "no description", 60.2], ["807", "I7N3G6G", "807", 0, "no description", 60.2], ["808", "I7N3G6G", "808", 0, "no description", 60.2], ["809", "I7N3G6G", "809", 0, "no description", 60.2], ["810", "I7N3G6G", "810", 0, "no description", 60.2], ["811", "I7N3G6G", "811", 0, "no description", 60.2], ["812", "I7N3G6G", "812", 0, "no description", 60.2], ["813", "I7N3G6G", "813", 0, "no description", 60.2], ["814", "I7N3G6G", "814", 0, "no description", 60.2], ["815", "I7N3G6G", "815", 0, "no description", 60.2], ["816", "I7N3G6G", "816", 0, "no description", 60.2], ["817", "I7N3G6G", "817", 0, "no description", 60.2], ["818", "I7N3G6G", "818", 0, "no description", 60.2], ["819", "I7N3G6G", "819", 0, "no description", 60.2], ["820", "I7N3G6G", "820", 0, "no description", 60.2], ["821", "I7N3G6G", "821", 0, "no description", 60.2], ["822", "I7N3G6G", "822", 0, "no description", 60.2], ["823", "I7N3G6G", "823", 0, "no description", 60.2], ["824", "I7N3G6G", "824", 0, "no description", 60.2], ["825", "I7N3G6G", "825", 0, "no description", 60.2], ["826", "I7N3G6G", "826", 0, "no description", 60.2], ["827", "I7N3G6G", "827", 0, "no description", 60.2], ["828", "I7N3G6G", "828", 0, "no description", 60.2], ["829", "I7N3G6G", "829", 0, "no description", 60.2], ["830", "I7N3G6G", "830", 0, "no description", 60.2], ["831", "I7N3G6G", "831", 0, "no description", 60.2], ["832", "I7N3G6G", "832", 0, "no description", 60.2], ["833", "I7N3G6G", "833", 0, "no description", 60.2], ["834", "I7N3G6G", "834", 0, "no description", 60.2], ["835", "I7N3G6G", "835", 0, "no description", 60.2], ["836", "I7N3G6G", "836", 0, "no description", 60.2], ["837", "I7N3G6G", "837", 0, "no description", 60.2], ["838", "I7N3G6G", "838", 0, "no description", 60.2], ["839", "I7N3G6G", "839", 0, "no description", 60.2], ["840", "I7N3G6G", "840", 0, "no description", 60.2], ["841", "I7N3G6G", "841", 0, "no description", 60.2], ["842", "I7N3G6G", "842", 0, "no description", 60.2], ["843", "I7N3G6G", "843", 0, "no description", 60.2], ["844", "I7N3G6G", "844", 0, "no description", 60.2], ["845", "I7N3G6G", "845", 0, "no description", 60.2], ["846", "I7N3G6G", "846", 0, "no description", 60.2], ["847", "I7N3G6G", "847", 0, "no description", 60.2], ["848", "I7N3G6G", "848", 0, "no description", 60.2], ["849", "I7N3G6G", "849", 0, "no description", 60.2], ["850", "I7N3G6G", "850", 0, "no description", 60.2], ["851", "I7N3G6G", "851", 0, "no description", 60.2], ["852", "I7N3G6G", "852", 0, "no description", 60.2], ["853", "I7N3G6G", "853", 0, "no description", 60.2], ["854", "I7N3G6G", "854", 0, "no description", 60.2], ["855", "I7N3G6G", "855", 0, "no description", 60.2], ["856", "I7N3G6G", "856", 0, "no description", 60.2], ["857", "I7N3G6G", "857", 0, "no description", 60.2], ["858", "I7N3G6G", "858", 0, "no description", 60.2], ["859", "I7N3G6G", "859", 0, "no description", 60.2], ["860", "I7N3G6G", "860", 0, "no description", 60.2], ["861", "I7N3G6G", "861", 0, "no description", 60.2], ["862", "I7N3G6G", "862", 0, "no description", 60.2], ["863", "I7N3G6G", "863", 0, "no description", 60.2], ["864", "I7N3G6G", "864", 0, "no description", 60.2], ["865", "I7N3G6G", "865", 0, "no description", 60.2], ["866", "I7N3G6G", "866", 0, "no description", 60.2], ["867", "I7N3G6G", "867", 0, "no description", 60.2], ["868", "I7N3G6G", "868", 0, "no description", 60.2], ["869", "I7N3G6G", "869", 0, "no description", 60.2], ["870", "I7N3G6G", "870", 0, "no description", 60.2], ["871", "I7N3G6G", "871", 0, "no description", 60.2], ["872", "I7N3G6G", "872", 0, "no description", 60.2], ["873", "I7N3G6G", "873", 0, "no description", 60.2], ["874", "I7N3G6G", "874", 0, "no description", 60.2], ["875", "I7N3G6G", "875", 0, "no description", 60.2], ["876", "I7N3G6G", "876", 0, "no description", 60.2], ["877", "I7N3G6G", "877", 0, "no description", 60.2], ["878", "I7N3G6G", "878", 0, "no description", 60.2], ["879", "I7N3G6G", "879", 0, "no description", 60.2], ["880", "I7N3G6G", "880", 0, "no description", 60.2], ["881", "I7N3G6G", "881", 0, "no description", 60.2], ["882", "I7N3G6G", "882", 0, "no description", 60.2], ["883", "I7N3G6G", "883", 0, "no description", 60.2], ["884", "I7N3G6G", "884", 0, "no description", 60.2], ["885", "I7N3G6G", "885", 0, "no description", 60.2], ["886", "I7N3G6G", "886", 0, "no description", 60.2], ["887", "I7N3G6G", "887", 0, "no description", 60.2], ["888", "I7N3G6G", "888", 0, "no description", 60.2], ["889", "I7N3G6G", "889", 0, "no description", 60.2], ["890", "I7N3G6G", "890", 0, "no description", 60.2], ["891", "I7N3G6G", "891", 0, "no description", 60.2], ["892", "I7N3G6G", "892", 0, "no description", 60.2], ["893", "I7N3G6G", "893", 0, "no description", 60.2], ["894", "I7N3G6G", "894", 0, "no description", 60.2], ["895", "I7N3G6G", "895", 0, "no description", 60.2], ["896", "I7N3G6G", "896", 0, "no description", 60.2], ["897", "I7N3G6G", "897", 0, "no description", 60.2], ["898", "I7N3G6G", "898", 0, "no description", 60.2], ["899", "I7N3G6G", "899", 0, "no description", 60.2], ["900", "I7N3G6G", "900", 0, "no description", 60.2], ["901", "I7N3G6G", "901", 0, "no description", 60.2], ["902", "I7N3G6G", "902", 0, "no description", 60.2], ["903", "I7N3G6G", "903", 0, "no description", 60.2], ["904", "I7N3G6G", "904", 0, "no description", 60.2], ["905", "I7N3G6G", "905", 0, "no description", 60.2], ["906", "I7N3G6G", "906", 0, "no description", 60.2], ["907", "I7N3G6G", "907", 0, "no description", 60.2], ["908", "I7N3G6G", "908", 0, "no description", 60.2], ["909", "I7N3G6G", "909", 0, "no description", 60.2], ["910", "I7N3G6G", "910", 0, "no description", 60.2], ["911", "I7N3G6G", "911", 0, "no description", 60.2], ["912", "I7N3G6G", "912", 0, "no description", 60.2], ["913", "I7N3G6G", "913", 0, "no description", 60.2], ["914", "I7N3G6G", "914", 0, "no description", 60.2], ["915", "I7N3G6G", "915", 0, "no description", 60.2], ["916", "I7N3G6G", "916", 0, "no description", 60.2], ["917", "I7N3G6G", "917", 0, "no description", 60.2], ["918", "I7N3G6G", "918", 0, "no description", 60.2], ["919", "I7N3G6G", "919", 0, "no description", 60.2], ["920", "I7N3G6G", "920", 0, "no description", 60.2], ["921", "I7N3G6G", "921", 0, "no description", 60.2], ["922", "I7N3G6G", "922", 0, "no description", 60.2], ["923", "I7N3G6G", "923", 0, "no description", 60.2], ["924", "I7N3G6G", "924", 0, "no description", 60.2], ["925", "I7N3G6G", "925", 0, "no description", 60.2], ["926", "I7N3G6G", "926", 0, "no description", 60.2], ["927", "I7N3G6G", "927", 0, "no description", 60.2], ["928", "I7N3G6G", "928", 0, "no description", 60.2], ["929", "I7N3G6G", "929", 0, "no description", 60.2], ["930", "I7N3G6G", "930", 0, "no description", 60.2], ["931", "I7N3G6G", "931", 0, "no description", 60.2], ["932", "I7N3G6G", "932", 0, "no description", 60.2], ["933", "I7N3G6G", "933", 0, "no description", 60.2], ["934", "I7N3G6G", "934", 0, "no description", 60.2], ["935", "I7N3G6G", "935", 0, "no description", 60.2], ["936", "I7N3G6G", "936", 0, "no description", 60.2], ["937", "I7N3G6G", "937", 0, "no description", 60.2], ["938", "I7N3G6G", "938", 0, "no description", 60.2], ["939", "I7N3G6G", "939", 0, "no description", 60.2], ["940", "I7N3G6G", "940", 0, "no description", 60.2], ["941", "I7N3G6G", "941", 0, "no description", 60.2], ["942", "I7N3G6G", "942", 0, "no description", 60.2], ["943", "I7N3G6G", "943", 0, "no description", 60.2], ["944", "I7N3G6G", "944", 0, "no description", 60.2], ["945", "I7N3G6G", "945", 0, "no description", 60.2], ["946", "I7N3G6G", "946", 0, "no description", 60.2], ["947", "I7N3G6G", "947", 0, "no description", 60.2], ["948", "I7N3G6G", "948", 0, "no description", 60.2], ["949", "I7N3G6G", "949", 0, "no description", 60.2], ["950", "I7N3G6G", "950", 0, "no description", 60.2], ["951", "I7N3G6G", "951", 0, "no description", 60.2], ["952", "I7N3G6G", "952", 0, "no description", 60.2], ["953", "I7N3G6G", "953", 0, "no description", 60.2], ["954", "I7N3G6G", "954", 0, "no description", 60.2], ["955", "I7N3G6G", "955", 0, "no description", 60.2], ["956", "I7N3G6G", "956", 0, "no description", 60.2], ["957", "I7N3G6G", "957", 0, "no description", 60.2], ["958", "I7N3G6G", "958", 0, "no description", 60.2], ["959", "I7N3G6G", "959", 0, "no description", 60.2], ["960", "I7N3G6G", "960", 0, "no description", 60.2], ["961", "I7N3G6G", "961", 0, "no description", 60.2], ["962", "I7N3G6G", "962", 0, "no description", 60.2], ["963", "I7N3G6G", "963", 0, "no description", 60.2], ["964", "I7N3G6G", "964", 0, "no description", 60.2], ["965", "I7N3G6G", "965", 0, "no description", 60.2], ["966", "I7N3G6G", "966", 0, "no description", 60.2], ["967", "I7N3G6G", "967", 0, "no description", 60.2], ["968", "I7N3G6G", "968", 0, "no description", 60.2], ["969", "I7N3G6G", "969", 0, "no description", 60.2], ["970", "I7N3G6G", "970", 0, "no description", 60.2], ["971", "I7N3G6G", "971", 0, "no description", 60.2], ["972", "I7N3G6G", "972", 0, "no description", 60.2], ["973", "I7N3G6G", "973", 0, "no description", 60.2], ["974", "I7N3G6G", "974", 0, "no description", 60.2], ["975", "I7N3G6G", "975", 0, "no description", 60.2], ["976", "I7N3G6G", "976", 0, "no description", 60.2], ["977", "I7N3G6G", "977", 0, "no description", 60.2], ["978", "I7N3G6G", "978", 0, "no description", 60.2], ["979", "I7N3G6G", "979", 0, "no description", 60.2], ["980", "I7N3G6G", "980", 0, "no description", 60.2], ["981", "I7N3G6G", "981", 0, "no description", 60.2], ["982", "I7N3G6G", "982", 0, "no description", 60.2], ["983", "I7N3G6G", "983", 0, "no description", 60.2], ["984", "I7N3G6G", "984", 0, "no description", 60.2], ["985", "I7N3G6G", "985", 0, "no description", 60.2], ["986", "I7N3G6G", "986", 0, "no description", 60.2], ["987", "I7N3G6G", "987", 0, "no description", 60.2], ["988", "I7N3G6G", "988", 0, "no description", 60.2], ["989", "I7N3G6G", "989", 0, "no description", 60.2], ["990", "I7N3G6G", "990", 0, "no description", 60.2], ["991", "I7N3G6G", "991", 0, "no description", 60.2], ["992", "I7N3G6G", "992", 0, "no description", 60.2], ["993", "I7N3G6G", "993", 0, "no description", 60.2], ["994", "I7N3G6G", "994", 0, "no description", 60.2], ["995", "I7N3G6G", "995", 0, "no description", 60.2], ["996", "I7N3G6G", "996", 0, "no description", 60.2], ["997", "I7N3G6G", "997", 0, "no description", 60.2], ["998", "I7N3G6G", "998", 0, "no description", 60.2], ["999", "I7N3G6G", "999", 0, "no description", 60.2], ["1000", "I7N3G6G", "1000", 0, "no description", 60.2], ["1001", "I7N3G6G", "1001", 0, "no description", 60.2], ["1002", "I7N3G6G", "1002", 0, "no description", 60.2], ["1003", "I7N3G6G", "1003", 0, "no description", 60.2], ["1004", "I7N3G6G", "1004", 0, "no description", 60.2], ["1005", "I7N3G6G", "1005", 0, "no description", 60.2], ["1006", "I7N3G6G", "1006", 0, "no description", 60.2], ["1007", "I7N3G6G", "1007", 0, "no description", 60.2], ["1008", "I7N3G6G", "1008", 0, "no description", 60.2], ["1009", "I7N3G6G", "1009", 0, "no description", 60.2], ["1010", "I7N3G6G", "1010", 0, "no description", 60.2], ["1011", "I7N3G6G", "1011", 0, "no description", 60.2], ["1012", "I7N3G6G", "1012", 0, "no description", 60.2], ["1013", "I7N3G6G", "1013", 0, "no description", 60.2], ["1014", "I7N3G6G", "1014", 0, "no description", 60.2], ["1015", "I7N3G6G", "1015", 0, "no description", 60.2], ["1016", "I7N3G6G", "1016", 0, "no description", 60.2], ["1017", "I7N3G6G", "1017", 0, "no description", 60.2], ["1018", "I7N3G6G", "1018", 0, "no description", 60.2], ["1019", "I7N3G6G", "1019", 0, "no description", 60.2], ["1020", "I7N3G6G", "1020", 0, "no description", 60.2], ["1021", "I7N3G6G", "1021", 0, "no description", 60.2], ["1022", "I7N3G6G", "1022", 0, "no description", 60.2], ["1023", "I7N3G6G", "1023", 0, "no description", 60.2], ["1024", "I7N3G6G", "1024", 0, "no description", 60.2], ["1025", "I7N3G6G", "1025", 0, "no description", 60.2], ["1026", "I7N3G6G", "1026", 0, "no description", 60.2], ["1027", "I7N3G6G", "1027", 0, "no description", 60.2], ["1028", "I7N3G6G", "1028", 0, "no description", 60.2], ["1029", "I7N3G6G", "1029", 0, "no description", 60.2], ["1030", "I7N3G6G", "1030", 0, "no description", 60.2], ["1031", "I7N3G6G", "1031", 0, "no description", 60.2], ["1032", "I7N3G6G", "1032", 0, "no description", 60.2], ["1033", "I7N3G6G", "1033", 0, "no description", 60.2], ["1034", "I7N3G6G", "1034", 0, "no description", 60.2], ["1035", "I7N3G6G", "1035", 0, "no description", 60.2], ["1036", "I7N3G6G", "1036", 0, "no description", 60.2], ["1037", "I7N3G6G", "1037", 0, "no description", 60.2], ["1038", "I7N3G6G", "1038", 0, "no description", 60.2], ["1039", "I7N3G6G", "1039", 0, "no description", 60.2], ["1040", "I7N3G6G", "1040", 0, "no description", 60.2], ["1041", "I7N3G6G", "1041", 0, "no description", 60.2], ["1042", "I7N3G6G", "1042", 0, "no description", 60.2], ["1043", "I7N3G6G", "1043", 0, "no description", 60.2], ["1044", "I7N3G6G", "1044", 0, "no description", 60.2], ["1045", "I7N3G6G", "1045", 0, "no description", 60.2], ["1046", "I7N3G6G", "1046", 0, "no description", 60.2], ["1047", "I7N3G6G", "1047", 0, "no description", 60.2], ["1048", "I7N3G6G", "1048", 0, "no description", 60.2], ["1049", "I7N3G6G", "1049", 0, "no description", 60.2], ["1050", "I7N3G6G", "1050", 0, "no description", 60.2], ["1051", "I7N3G6G", "1051", 0, "no description", 60.2], ["1052", "I7N3G6G", "1052", 0, "no description", 60.2], ["1053", "I7N3G6G", "1053", 0, "no description", 60.2], ["1054", "I7N3G6G", "1054", 0, "no description", 60.2], ["1055", "I7N3G6G", "1055", 0, "no description", 60.2], ["1056", "I7N3G6G", "1056", 0, "no description", 60.2], ["1057", "I7N3G6G", "1057", 0, "no description", 60.2], ["1058", "I7N3G6G", "1058", 0, "no description", 60.2], ["1059", "I7N3G6G", "1059", 0, "no description", 60.2], ["1060", "I7N3G6G", "1060", 0, "no description", 60.2], ["1061", "I7N3G6G", "1061", 0, "no description", 60.2], ["1062", "I7N3G6G", "1062", 0, "no description", 60.2], ["1063", "I7N3G6G", "1063", 0, "no description", 60.2], ["1064", "I7N3G6G", "1064", 0, "no description", 60.2], ["1065", "I7N3G6G", "1065", 0, "no description", 60.2], ["1066", "I7N3G6G", "1066", 0, "no description", 60.2], ["1067", "I7N3G6G", "1067", 0, "no description", 60.2], ["1068", "I7N3G6G", "1068", 0, "no description", 60.2], ["1069", "I7N3G6G", "1069", 0, "no description", 60.2], ["1070", "I7N3G6G", "1070", 0, "no description", 60.2], ["1071", "I7N3G6G", "1071", 0, "no description", 60.2], ["1072", "I7N3G6G", "1072", 0, "no description", 60.2], ["1073", "I7N3G6G", "1073", 0, "no description", 60.2], ["1074", "I7N3G6G", "1074", 0, "no description", 60.2], ["1075", "I7N3G6G", "1075", 0, "no description", 60.2], ["1076", "I7N3G6G", "1076", 0, "no description", 60.2], ["1077", "I7N3G6G", "1077", 0, "no description", 60.2], ["1078", "I7N3G6G", "1078", 0, "no description", 60.2], ["1079", "I7N3G6G", "1079", 0, "no description", 60.2], ["1080", "I7N3G6G", "1080", 0, "no description", 60.2], ["1081", "I7N3G6G", "1081", 0, "no description", 60.2], ["1082", "I7N3G6G", "1082", 0, "no description", 60.2], ["1083", "I7N3G6G", "1083", 0, "no description", 60.2], ["1084", "I7N3G6G", "1084", 0, "no description", 60.2], ["1085", "I7N3G6G", "1085", 0, "no description", 60.2], ["1086", "I7N3G6G", "1086", 0, "no description", 60.2], ["1087", "I7N3G6G", "1087", 0, "no description", 60.2], ["1088", "I7N3G6G", "1088", 0, "no description", 60.2], ["1089", "I7N3G6G", "1089", 0, "no description", 60.2], ["1090", "I7N3G6G", "1090", 0, "no description", 60.2], ["1091", "I7N3G6G", "1091", 0, "no description", 60.2], ["1092", "I7N3G6G", "1092", 0, "no description", 60.2], ["1093", "I7N3G6G", "1093", 0, "no description", 60.2], ["1094", "I7N3G6G", "1094", 0, "no description", 60.2], ["1095", "I7N3G6G", "1095", 0, "no description", 60.2], ["1096", "I7N3G6G", "1096", 0, "no description", 60.2], ["1097", "I7N3G6G", "1097", 0, "no description", 60.2], ["1098", "I7N3G6G", "1098", 0, "no description", 60.2], ["1099", "I7N3G6G", "1099", 0, "no description", 60.2], ["1100", "I7N3G6G", "1100", 0, "no description", 60.2], ["1101", "I7N3G6G", "1101", 0, "no description", 60.2], ["1102", "I7N3G6G", "1102", 0, "no description", 60.2], ["1103", "I7N3G6G", "1103", 0, "no description", 60.2], ["1104", "I7N3G6G", "1104", 0, "no description", 60.2], ["1105", "I7N3G6G", "1105", 0, "no description", 60.2], ["1106", "I7N3G6G", "1106", 0, "no description", 60.2], ["1107", "I7N3G6G", "1107", 0, "no description", 60.2], ["1108", "I7N3G6G", "1108", 0, "no description", 60.2], ["1109", "I7N3G6G", "1109", 0, "no description", 60.2], ["1110", "I7N3G6G", "1110", 0, "no description", 60.2], ["1111", "I7N3G6G", "1111", 0, "no description", 60.2], ["1112", "I7N3G6G", "1112", 0, "no description", 60.2], ["1113", "I7N3G6G", "1113", 0, "no description", 60.2], ["1114", "I7N3G6G", "1114", 0, "no description", 60.2], ["1115", "I7N3G6G", "1115", 0, "no description", 60.2], ["1116", "I7N3G6G", "1116", 0, "no description", 60.2], ["1117", "I7N3G6G", "1117", 0, "no description", 60.2], ["1118", "I7N3G6G", "1118", 0, "no description", 60.2], ["1119", "I7N3G6G", "1119", 0, "no description", 60.2], ["1120", "I7N3G6G", "1120", 0, "no description", 60.2], ["1121", "I7N3G6G", "1121", 0, "no description", 60.2], ["1122", "I7N3G6G", "1122", 0, "no description", 60.2], ["1123", "I7N3G6G", "1123", 0, "no description", 60.2], ["1124", "I7N3G6G", "1124", 0, "no description", 60.2], ["1125", "I7N3G6G", "1125", 0, "no description", 60.2], ["1126", "I7N3G6G", "1126", 0, "no description", 60.2], ["1127", "I7N3G6G", "1127", 0, "no description", 60.2], ["1128", "I7N3G6G", "1128", 0, "no description", 60.2], ["1129", "I7N3G6G", "1129", 0, "no description", 60.2], ["1130", "I7N3G6G", "1130", 0, "no description", 60.2], ["1131", "I7N3G6G", "1131", 0, "no description", 60.2], ["1132", "I7N3G6G", "1132", 0, "no description", 60.2], ["1133", "I7N3G6G", "1133", 0, "no description", 60.2], ["1134", "I7N3G6G", "1134", 0, "no description", 60.2], ["1135", "I7N3G6G", "1135", 0, "no description", 60.2], ["1136", "I7N3G6G", "1136", 0, "no description", 60.2], ["1137", "I7N3G6G", "1137", 0, "no description", 60.2], ["1138", "I7N3G6G", "1138", 0, "no description", 60.2], ["1139", "I7N3G6G", "1139", 0, "no description", 60.2], ["1140", "I7N3G6G", "1140", 0, "no description", 60.2], ["1141", "I7N3G6G", "1141", 0, "no description", 60.2], ["1142", "I7N3G6G", "1142", 0, "no description", 60.2], ["1143", "I7N3G6G", "1143", 0, "no description", 60.2], ["1144", "I7N3G6G", "1144", 0, "no description", 60.2], ["1145", "I7N3G6G", "1145", 0, "no description", 60.2], ["1146", "I7N3G6G", "1146", 0, "no description", 60.2], ["1147", "I7N3G6G", "1147", 0, "no description", 60.2], ["1148", "I7N3G6G", "1148", 0, "no description", 60.2], ["1149", "I7N3G6G", "1149", 0, "no description", 60.2], ["1150", "I7N3G6G", "1150", 0, "no description", 60.2], ["1151", "I7N3G6G", "1151", 0, "no description", 60.2], ["1152", "I7N3G6G", "1152", 0, "no description", 60.2], ["1153", "I7N3G6G", "1153", 0, "no description", 60.2], ["1154", "I7N3G6G", "1154", 0, "no description", 60.2], ["1155", "I7N3G6G", "1155", 0, "no description", 60.2], ["1156", "I7N3G6G", "1156", 0, "no description", 60.2], ["1157", "I7N3G6G", "1157", 0, "no description", 60.2], ["1158", "I7N3G6G", "1158", 0, "no description", 60.2], ["1159", "I7N3G6G", "1159", 0, "no description", 60.2], ["1160", "I7N3G6G", "1160", 0, "no description", 60.2], ["1161", "I7N3G6G", "1161", 0, "no description", 60.2], ["1162", "I7N3G6G", "1162", 0, "no description", 60.2], ["1163", "I7N3G6G", "1163", 0, "no description", 60.2], ["1164", "I7N3G6G", "1164", 0, "no description", 60.2], ["1165", "I7N3G6G", "1165", 0, "no description", 60.2], ["1166", "I7N3G6G", "1166", 0, "no description", 60.2], ["1167", "I7N3G6G", "1167", 0, "no description", 60.2], ["1168", "I7N3G6G", "1168", 0, "no description", 60.2], ["1169", "I7N3G6G", "1169", 0, "no description", 60.2], ["1170", "I7N3G6G", "1170", 0, "no description", 60.2], ["1171", "I7N3G6G", "1171", 0, "no description", 60.2], ["1172", "I7N3G6G", "1172", 0, "no description", 60.2], ["1173", "I7N3G6G", "1173", 0, "no description", 60.2], ["1174", "I7N3G6G", "1174", 0, "no description", 60.2], ["1175", "I7N3G6G", "1175", 0, "no description", 60.2], ["1176", "I7N3G6G", "1176", 0, "no description", 60.2], ["1177", "I7N3G6G", "1177", 0, "no description", 60.2], ["1178", "I7N3G6G", "1178", 0, "no description", 60.2], ["1179", "I7N3G6G", "1179", 0, "no description", 60.2], ["1180", "I7N3G6G", "1180", 0, "no description", 60.2], ["1181", "I7N3G6G", "1181", 0, "no description", 60.2], ["1182", "I7N3G6G", "1182", 0, "no description", 60.2], ["1183", "I7N3G6G", "1183", 0, "no description", 60.2], ["1184", "I7N3G6G", "1184", 0, "no description", 60.2], ["1185", "I7N3G6G", "1185", 0, "no description", 60.2], ["1186", "I7N3G6G", "1186", 0, "no description", 60.2], ["1187", "I7N3G6G", "1187", 0, "no description", 60.2], ["1188", "I7N3G6G", "1188", 0, "no description", 60.2], ["1189", "I7N3G6G", "1189", 0, "no description", 60.2], ["1190", "I7N3G6G", "1190", 0, "no description", 60.2], ["1191", "I7N3G6G", "1191", 0, "no description", 60.2], ["1192", "I7N3G6G", "1192", 0, "no description", 60.2], ["1193", "I7N3G6G", "1193", 0, "no description", 60.2], ["1194", "I7N3G6G", "1194", 0, "no description", 60.2], ["1195", "I7N3G6G", "1195", 0, "no description", 60.2], ["1196", "I7N3G6G", "1196", 0, "no description", 60.2], ["1197", "I7N3G6G", "1197", 0, "no description", 60.2], ["1198", "I7N3G6G", "1198", 0, "no description", 60.2], ["1199", "I7N3G6G", "1199", 0, "no description", 60.2], ["1200", "I7N3G6G", "1200", 0, "no description", 60.2], ["1201", "I7N3G6G", "1201", 0, "no description", 60.2], ["1202", "I7N3G6G", "1202", 0, "no description", 60.2], ["1203", "I7N3G6G", "1203", 0, "no description", 60.2], ["1204", "I7N3G6G", "1204", 0, "no description", 60.2], ["1205", "I7N3G6G", "1205", 0, "no description", 60.2], ["1206", "I7N3G6G", "1206", 0, "no description", 60.2], ["1207", "I7N3G6G", "1207", 0, "no description", 60.2], ["1208", "I7N3G6G", "1208", 0, "no description", 60.2], ["1209", "I7N3G6G", "1209", 0, "no description", 60.2], ["1210", "I7N3G6G", "1210", 0, "no description", 60.2], ["1211", "I7N3G6G", "1211", 0, "no description", 60.2], ["1212", "I7N3G6G", "1212", 0, "no description", 60.2], ["1213", "I7N3G6G", "1213", 0, "no description", 60.2], ["1214", "I7N3G6G", "1214", 0, "no description", 60.2], ["1215", "I7N3G6G", "1215", 0, "no description", 60.2], ["1216", "I7N3G6G", "1216", 0, "no description", 60.2], ["1217", "I7N3G6G", "1217", 0, "no description", 60.2], ["1218", "I7N3G6G", "1218", 0, "no description", 60.2], ["1219", "I7N3G6G", "1219", 0, "no description", 60.2], ["1220", "I7N3G6G", "1220", 0, "no description", 60.2], ["1221", "I7N3G6G", "1221", 0, "no description", 60.2], ["1222", "I7N3G6G", "1222", 0, "no description", 60.2], ["1223", "I7N3G6G", "1223", 0, "no description", 60.2], ["1224", "I7N3G6G", "1224", 0, "no description", 60.2], ["1225", "I7N3G6G", "1225", 0, "no description", 60.2], ["1226", "I7N3G6G", "1226", 0, "no description", 60.2], ["1227", "I7N3G6G", "1227", 0, "no description", 60.2], ["1228", "I7N3G6G", "1228", 0, "no description", 60.2], ["1229", "I7N3G6G", "1229", 0, "no description", 60.2], ["1230", "I7N3G6G", "1230", 0, "no description", 60.2], ["1231", "I7N3G6G", "1231", 0, "no description", 60.2], ["1232", "I7N3G6G", "1232", 0, "no description", 60.2], ["1233", "I7N3G6G", "1233", 0, "no description", 60.2], ["1234", "I7N3G6G", "1234", 0, "no description", 60.2], ["1235", "I7N3G6G", "1235", 0, "no description", 60.2], ["1236", "I7N3G6G", "1236", 0, "no description", 60.2], ["1237", "I7N3G6G", "1237", 0, "no description", 60.2], ["1238", "I7N3G6G", "1238", 0, "no description", 60.2], ["1239", "I7N3G6G", "1239", 0, "no description", 60.2], ["1240", "I7N3G6G", "1240", 0, "no description", 60.2], ["1241", "I7N3G6G", "1241", 0, "no description", 60.2], ["1242", "I7N3G6G", "1242", 0, "no description", 60.2], ["1243", "I7N3G6G", "1243", 0, "no description", 60.2], ["1244", "I7N3G6G", "1244", 0, "no description", 60.2], ["1245", "I7N3G6G", "1245", 0, "no description", 60.2], ["1246", "I7N3G6G", "1246", 0, "no description", 60.2], ["1247", "I7N3G6G", "1247", 0, "no description", 60.2], ["1248", "I7N3G6G", "1248", 0, "no description", 60.2], ["1249", "I7N3G6G", "1249", 0, "no description", 60.2], ["1250", "I7N3G6G", "1250", 0, "no description", 60.2], ["1251", "I7N3G6G", "1251", 0, "no description", 60.2], ["1252", "I7N3G6G", "1252", 0, "no description", 60.2], ["1253", "I7N3G6G", "1253", 0, "no description", 60.2], ["1254", "I7N3G6G", "1254", 0, "no description", 60.2], ["1255", "I7N3G6G", "1255", 0, "no description", 60.2], ["1256", "I7N3G6G", "1256", 0, "no description", 60.2], ["1257", "I7N3G6G", "1257", 0, "no description", 60.2], ["1258", "I7N3G6G", "1258", 0, "no description", 60.2], ["1259", "I7N3G6G", "1259", 0, "no description", 60.2], ["1260", "I7N3G6G", "1260", 0, "no description", 60.2], ["1261", "I7N3G6G", "1261", 0, "no description", 60.2], ["1262", "I7N3G6G", "1262", 0, "no description", 60.2], ["1263", "I7N3G6G", "1263", 0, "no description", 60.2], ["1264", "I7N3G6G", "1264", 0, "no description", 60.2], ["1265", "I7N3G6G", "1265", 0, "no description", 60.2], ["1266", "I7N3G6G", "1266", 0, "no description", 60.2], ["1267", "I7N3G6G", "1267", 0, "no description", 60.2], ["1268", "I7N3G6G", "1268", 0, "no description", 60.2], ["1269", "I7N3G6G", "1269", 0, "no description", 60.2], ["1270", "I7N3G6G", "1270", 0, "no description", 60.2], ["1271", "I7N3G6G", "1271", 0, "no description", 60.2], ["1272", "I7N3G6G", "1272", 0, "no description", 60.2], ["1273", "I7N3G6G", "1273", 0, "no description", 60.2], ["1274", "I7N3G6G", "1274", 0, "no description", 60.2], ["1275", "I7N3G6G", "1275", 0, "no description", 60.2], ["1276", "I7N3G6G", "1276", 0, "no description", 60.2], ["1277", "I7N3G6G", "1277", 0, "no description", 60.2], ["1278", "I7N3G6G", "1278", 0, "no description", 60.2], ["1279", "I7N3G6G", "1279", 0, "no description", 60.2], ["1280", "I7N3G6G", "1280", 0, "no description", 60.2], ["1281", "I7N3G6G", "1281", 0, "no description", 60.2], ["1282", "I7N3G6G", "1282", 0, "no description", 60.2], ["1283", "I7N3G6G", "1283", 0, "no description", 60.2], ["1284", "I7N3G6G", "1284", 0, "no description", 60.2], ["1285", "I7N3G6G", "1285", 0, "no description", 60.2], ["1286", "I7N3G6G", "1286", 0, "no description", 60.2], ["1287", "I7N3G6G", "1287", 0, "no description", 60.2], ["1288", "I7N3G6G", "1288", 0, "no description", 60.2], ["1289", "I7N3G6G", "1289", 0, "no description", 60.2], ["1290", "I7N3G6G", "1290", 0, "no description", 60.2], ["1291", "I7N3G6G", "1291", 0, "no description", 60.2], ["1292", "I7N3G6G", "1292", 0, "no description", 60.2], ["1293", "I7N3G6G", "1293", 0, "no description", 60.2], ["1294", "I7N3G6G", "1294", 0, "no description", 60.2], ["1295", "I7N3G6G", "1295", 0, "no description", 60.2], ["1296", "I7N3G6G", "1296", 0, "no description", 60.2], ["1297", "I7N3G6G", "1297", 0, "no description", 60.2], ["1298", "I7N3G6G", "1298", 0, "no description", 60.2], ["1299", "I7N3G6G", "1299", 0, "no description", 60.2], ["1300", "I7N3G6G", "1300", 0, "no description", 60.2], ["1301", "I7N3G6G", "1301", 0, "no description", 60.2], ["1302", "I7N3G6G", "1302", 0, "no description", 60.2], ["1303", "I7N3G6G", "1303", 0, "no description", 60.2], ["1304", "I7N3G6G", "1304", 0, "no description", 60.2], ["1305", "I7N3G6G", "1305", 0, "no description", 60.2], ["1306", "I7N3G6G", "1306", 0, "no description", 60.2], ["1307", "I7N3G6G", "1307", 0, "no description", 60.2], ["1308", "I7N3G6G", "1308", 0, "no description", 60.2], ["1309", "I7N3G6G", "1309", 0, "no description", 60.2], ["1310", "I7N3G6G", "1310", 0, "no description", 60.2], ["1311", "I7N3G6G", "1311", 0, "no description", 60.2], ["1312", "I7N3G6G", "1312", 0, "no description", 60.2], ["1313", "I7N3G6G", "1313", 0, "no description", 60.2], ["1314", "I7N3G6G", "1314", 0, "no description", 60.2], ["1315", "I7N3G6G", "1315", 0, "no description", 60.2], ["1316", "I7N3G6G", "1316", 0, "no description", 60.2], ["1317", "I7N3G6G", "1317", 0, "no description", 60.2], ["1318", "I7N3G6G", "1318", 0, "no description", 60.2], ["1319", "I7N3G6G", "1319", 0, "no description", 60.2], ["1320", "I7N3G6G", "1320", 0, "no description", 60.2], ["1321", "I7N3G6G", "1321", 0, "no description", 60.2], ["1322", "I7N3G6G", "1322", 0, "no description", 60.2], ["1323", "I7N3G6G", "1323", 0, "no description", 60.2], ["1324", "I7N3G6G", "1324", 0, "no description", 60.2], ["1325", "I7N3G6G", "1325", 0, "no description", 60.2], ["1326", "I7N3G6G", "1326", 0, "no description", 60.2], ["1327", "I7N3G6G", "1327", 0, "no description", 60.2], ["1328", "I7N3G6G", "1328", 0, "no description", 60.2], ["1329", "I7N3G6G", "1329", 0, "no description", 60.2], ["1330", "I7N3G6G", "1330", 0, "no description", 60.2], ["1331", "I7N3G6G", "1331", 0, "no description", 60.2], ["1332", "I7N3G6G", "1332", 0, "no description", 60.2], ["1333", "I7N3G6G", "1333", 0, "no description", 60.2], ["1334", "I7N3G6G", "1334", 0, "no description", 60.2], ["1335", "I7N3G6G", "1335", 0, "no description", 60.2], ["1336", "I7N3G6G", "1336", 0, "no description", 60.2], ["1337", "I7N3G6G", "1337", 0, "no description", 60.2], ["1338", "I7N3G6G", "1338", 0, "no description", 60.2], ["1339", "I7N3G6G", "1339", 0, "no description", 60.2], ["1340", "I7N3G6G", "1340", 0, "no description", 60.2], ["1341", "I7N3G6G", "1341", 0, "no description", 60.2], ["1342", "I7N3G6G", "1342", 0, "no description", 60.2], ["1343", "I7N3G6G", "1343", 0, "no description", 60.2], ["1344", "I7N3G6G", "1344", 0, "no description", 60.2], ["1345", "I7N3G6G", "1345", 0, "no description", 60.2], ["1346", "I7N3G6G", "1346", 0, "no description", 60.2], ["1347", "I7N3G6G", "1347", 0, "no description", 60.2], ["1348", "I7N3G6G", "1348", 0, "no description", 60.2], ["1349", "I7N3G6G", "1349", 0, "no description", 60.2], ["1350", "I7N3G6G", "1350", 0, "no description", 60.2], ["1351", "I7N3G6G", "1351", 0, "no description", 60.2], ["1352", "I7N3G6G", "1352", 0, "no description", 60.2], ["1353", "I7N3G6G", "1353", 0, "no description", 60.2], ["1354", "I7N3G6G", "1354", 0, "no description", 60.2], ["1355", "I7N3G6G", "1355", 0, "no description", 60.2], ["1356", "I7N3G6G", "1356", 0, "no description", 60.2], ["1357", "I7N3G6G", "1357", 0, "no description", 60.2], ["1358", "I7N3G6G", "1358", 0, "no description", 60.2], ["1359", "I7N3G6G", "1359", 0, "no description", 60.2], ["1360", "I7N3G6G", "1360", 0, "no description", 60.2], ["1361", "I7N3G6G", "1361", 0, "no description", 60.2], ["1362", "I7N3G6G", "1362", 0, "no description", 60.2], ["1363", "I7N3G6G", "1363", 0, "no description", 60.2], ["1364", "I7N3G6G", "1364", 0, "no description", 60.2], ["1365", "I7N3G6G", "1365", 0, "no description", 60.2], ["1366", "I7N3G6G", "1366", 0, "no description", 60.2], ["1367", "I7N3G6G", "1367", 0, "no description", 60.2], ["1368", "I7N3G6G", "1368", 0, "no description", 60.2], ["1369", "I7N3G6G", "1369", 0, "no description", 60.2], ["1370", "I7N3G6G", "1370", 0, "no description", 60.2], ["1371", "I7N3G6G", "1371", 0, "no description", 60.2], ["1372", "I7N3G6G", "1372", 0, "no description", 60.2], ["1373", "I7N3G6G", "1373", 0, "no description", 60.2], ["1374", "I7N3G6G", "1374", 0, "no description", 60.2], ["1375", "I7N3G6G", "1375", 0, "no description", 60.2], ["1376", "I7N3G6G", "1376", 0, "no description", 60.2], ["1377", "I7N3G6G", "1377", 0, "no description", 60.2], ["1378", "I7N3G6G", "1378", 0, "no description", 60.2], ["1379", "I7N3G6G", "1379", 0, "no description", 60.2], ["1380", "I7N3G6G", "1380", 0, "no description", 60.2], ["1381", "I7N3G6G", "1381", 0, "no description", 60.2], ["1382", "I7N3G6G", "1382", 0, "no description", 60.2], ["1383", "I7N3G6G", "1383", 0, "no description", 60.2], ["1384", "I7N3G6G", "1384", 0, "no description", 60.2], ["1385", "I7N3G6G", "1385", 0, "no description", 60.2], ["1386", "I7N3G6G", "1386", 0, "no description", 60.2], ["1387", "I7N3G6G", "1387", 0, "no description", 60.2], ["1388", "I7N3G6G", "1388", 0, "no description", 60.2], ["1389", "I7N3G6G", "1389", 0, "no description", 60.2], ["1390", "I7N3G6G", "1390", 0, "no description", 60.2], ["1391", "I7N3G6G", "1391", 0, "no description", 60.2], ["1392", "I7N3G6G", "1392", 0, "no description", 60.2], ["1393", "I7N3G6G", "1393", 0, "no description", 60.2], ["1394", "I7N3G6G", "1394", 0, "no description", 60.2], ["1395", "I7N3G6G", "1395", 0, "no description", 60.2], ["1396", "I7N3G6G", "1396", 0, "no description", 60.2], ["1397", "I7N3G6G", "1397", 0, "no description", 60.2], ["1398", "I7N3G6G", "1398", 0, "no description", 60.2], ["1399", "I7N3G6G", "1399", 0, "no description", 60.2], ["1400", "I7N3G6G", "1400", 0, "no description", 60.2], ["1401", "I7N3G6G", "1401", 0, "no description", 60.2], ["1402", "I7N3G6G", "1402", 0, "no description", 60.2], ["1403", "I7N3G6G", "1403", 0, "no description", 60.2], ["1404", "I7N3G6G", "1404", 0, "no description", 60.2], ["1405", "I7N3G6G", "1405", 0, "no description", 60.2], ["1406", "I7N3G6G", "1406", 0, "no description", 60.2], ["1407", "I7N3G6G", "1407", 0, "no description", 60.2], ["1408", "I7N3G6G", "1408", 0, "no description", 60.2], ["1409", "I7N3G6G", "1409", 0, "no description", 60.2], ["1410", "I7N3G6G", "1410", 0, "no description", 60.2], ["1411", "I7N3G6G", "1411", 0, "no description", 60.2], ["1412", "I7N3G6G", "1412", 0, "no description", 60.2], ["1413", "I7N3G6G", "1413", 0, "no description", 60.2], ["1414", "I7N3G6G", "1414", 0, "no description", 60.2], ["1415", "I7N3G6G", "1415", 0, "no description", 60.2], ["1416", "I7N3G6G", "1416", 0, "no description", 60.2], ["1417", "I7N3G6G", "1417", 0, "no description", 60.2], ["1418", "I7N3G6G", "1418", 0, "no description", 60.2], ["1419", "I7N3G6G", "1419", 0, "no description", 60.2], ["1420", "I7N3G6G", "1420", 0, "no description", 60.2], ["1421", "I7N3G6G", "1421", 0, "no description", 60.2], ["1422", "I7N3G6G", "1422", 0, "no description", 60.2], ["1423", "I7N3G6G", "1423", 0, "no description", 60.2], ["1424", "I7N3G6G", "1424", 0, "no description", 60.2], ["1425", "I7N3G6G", "1425", 0, "no description", 60.2], ["1426", "I7N3G6G", "1426", 0, "no description", 60.2], ["1427", "I7N3G6G", "1427", 0, "no description", 60.2], ["1428", "I7N3G6G", "1428", 0, "no description", 60.2], ["1429", "I7N3G6G", "1429", 0, "no description", 60.2], ["1430", "I7N3G6G", "1430", 0, "no description", 60.2], ["1431", "I7N3G6G", "1431", 0, "no description", 60.2], ["1432", "I7N3G6G", "1432", 0, "no description", 60.2], ["1433", "I7N3G6G", "1433", 0, "no description", 60.2], ["1434", "I7N3G6G", "1434", 0, "no description", 60.2], ["1435", "I7N3G6G", "1435", 0, "no description", 60.2], ["1436", "I7N3G6G", "1436", 0, "no description", 60.2], ["1437", "I7N3G6G", "1437", 0, "no description", 60.2], ["1438", "I7N3G6G", "1438", 0, "no description", 60.2], ["1439", "I7N3G6G", "1439", 0, "no description", 60.2], ["1440", "I7N3G6G", "1440", 0, "no description", 60.2], ["1441", "I7N3G6G", "1441", 0, "no description", 60.2], ["1442", "I7N3G6G", "1442", 0, "no description", 60.2], ["1443", "I7N3G6G", "1443", 0, "no description", 60.2], ["1444", "I7N3G6G", "1444", 0, "no description", 60.2], ["1445", "I7N3G6G", "1445", 0, "no description", 60.2], ["1446", "I7N3G6G", "1446", 0, "no description", 60.2], ["1447", "I7N3G6G", "1447", 0, "no description", 60.2], ["1448", "I7N3G6G", "1448", 0, "no description", 60.2], ["1449", "I7N3G6G", "1449", 0, "no description", 60.2], ["1450", "I7N3G6G", "1450", 0, "no description", 60.2], ["1451", "I7N3G6G", "1451", 0, "no description", 60.2], ["1452", "I7N3G6G", "1452", 0, "no description", 60.2], ["1453", "I7N3G6G", "1453", 0, "no description", 60.2], ["1454", "I7N3G6G", "1454", 0, "no description", 60.2], ["1455", "I7N3G6G", "1455", 0, "no description", 60.2], ["1456", "I7N3G6G", "1456", 0, "no description", 60.2], ["1457", "I7N3G6G", "1457", 0, "no description", 60.2], ["1458", "I7N3G6G", "1458", 0, "no description", 60.2], ["1459", "I7N3G6G", "1459", 0, "no description", 60.2], ["1460", "I7N3G6G", "1460", 0, "no description", 60.2], ["1461", "I7N3G6G", "1461", 0, "no description", 60.2], ["1462", "I7N3G6G", "1462", 0, "no description", 60.2], ["1463", "I7N3G6G", "1463", 0, "no description", 60.2], ["1464", "I7N3G6G", "1464", 0, "no description", 60.2], ["1465", "I7N3G6G", "1465", 0, "no description", 60.2], ["1466", "I7N3G6G", "1466", 0, "no description", 60.2], ["1467", "I7N3G6G", "1467", 0, "no description", 60.2], ["1468", "I7N3G6G", "1468", 0, "no description", 60.2], ["1469", "I7N3G6G", "1469", 0, "no description", 60.2], ["1470", "I7N3G6G", "1470", 0, "no description", 60.2], ["1471", "I7N3G6G", "1471", 0, "no description", 60.2], ["1472", "I7N3G6G", "1472", 0, "no description", 60.2], ["1473", "I7N3G6G", "1473", 0, "no description", 60.2], ["1474", "I7N3G6G", "1474", 0, "no description", 60.2], ["1475", "I7N3G6G", "1475", 0, "no description", 60.2], ["1476", "I7N3G6G", "1476", 0, "no description", 60.2], ["1477", "I7N3G6G", "1477", 0, "no description", 60.2], ["1478", "I7N3G6G", "1478", 0, "no description", 60.2], ["1479", "I7N3G6G", "1479", 0, "no description", 60.2], ["1480", "I7N3G6G", "1480", 0, "no description", 60.2], ["1481", "I7N3G6G", "1481", 0, "no description", 60.2], ["1482", "I7N3G6G", "1482", 0, "no description", 60.2], ["1483", "I7N3G6G", "1483", 0, "no description", 60.2], ["1484", "I7N3G6G", "1484", 0, "no description", 60.2], ["1485", "I7N3G6G", "1485", 0, "no description", 60.2], ["1486", "I7N3G6G", "1486", 0, "no description", 60.2], ["1487", "I7N3G6G", "1487", 0, "no description", 60.2], ["1488", "I7N3G6G", "1488", 0, "no description", 60.2], ["1489", "I7N3G6G", "1489", 0, "no description", 60.2], ["1490", "I7N3G6G", "1490", 0, "no description", 60.2], ["1491", "I7N3G6G", "1491", 0, "no description", 60.2], ["1492", "I7N3G6G", "1492", 0, "no description", 60.2], ["1493", "I7N3G6G", "1493", 0, "no description", 60.2], ["1494", "I7N3G6G", "1494", 0, "no description", 60.2], ["1495", "I7N3G6G", "1495", 0, "no description", 60.2], ["1496", "I7N3G6G", "1496", 0, "no description", 60.2], ["1497", "I7N3G6G", "1497", 0, "no description", 60.2], ["1498", "I7N3G6G", "1498", 0, "no description", 60.2], ["1499", "I7N3G6G", "1499", 0, "no description", 60.2], ["1500", "I7N3G6G", "1500", 0, "no description", 60.2], ["1501", "I7N3G6G", "1501", 0, "no description", 60.2], ["1502", "I7N3G6G", "1502", 0, "no description", 60.2], ["1503", "I7N3G6G", "1503", 0, "no description", 60.2], ["1504", "I7N3G6G", "1504", 0, "no description", 60.2], ["1505", "I7N3G6G", "1505", 0, "no description", 60.2], ["1506", "I7N3G6G", "1506", 0, "no description", 60.2], ["1507", "I7N3G6G", "1507", 0, "no description", 60.2], ["1508", "I7N3G6G", "1508", 0, "no description", 60.2], ["1509", "I7N3G6G", "1509", 0, "no description", 60.2], ["1510", "I7N3G6G", "1510", 0, "no description", 60.2], ["1511", "I7N3G6G", "1511", 0, "no description", 60.2], ["1512", "I7N3G6G", "1512", 0, "no description", 60.2], ["1513", "I7N3G6G", "1513", 0, "no description", 60.2], ["1514", "I7N3G6G", "1514", 0, "no description", 60.2], ["1515", "I7N3G6G", "1515", 0, "no description", 60.2], ["1516", "I7N3G6G", "1516", 0, "no description", 60.2], ["1517", "I7N3G6G", "1517", 0, "no description", 60.2], ["1518", "I7N3G6G", "1518", 0, "no description", 60.2], ["1519", "I7N3G6G", "1519", 0, "no description", 60.2], ["1520", "I7N3G6G", "1520", 0, "no description", 60.2], ["1521", "I7N3G6G", "1521", 0, "no description", 60.2], ["1522", "I7N3G6G", "1522", 0, "no description", 60.2], ["1523", "I7N3G6G", "1523", 0, "no description", 60.2], ["1524", "I7N3G6G", "1524", 0, "no description", 60.2], ["1525", "I7N3G6G", "1525", 0, "no description", 60.2], ["1526", "I7N3G6G", "1526", 0, "no description", 60.2], ["1527", "I7N3G6G", "1527", 0, "no description", 60.2], ["1528", "I7N3G6G", "1528", 0, "no description", 60.2], ["1529", "I7N3G6G", "1529", 0, "no description", 60.2], ["1530", "I7N3G6G", "1530", 0, "no description", 60.2], ["1531", "I7N3G6G", "1531", 0, "no description", 60.2], ["1532", "I7N3G6G", "1532", 0, "no description", 60.2], ["1533", "I7N3G6G", "1533", 0, "no description", 60.2], ["1534", "I7N3G6G", "1534", 0, "no description", 60.2], ["1535", "I7N3G6G", "1535", 0, "no description", 60.2], ["1536", "I7N3G6G", "1536", 0, "no description", 60.2], ["1537", "I7N3G6G", "1537", 0, "no description", 60.2], ["1538", "I7N3G6G", "1538", 0, "no description", 60.2], ["1539", "I7N3G6G", "1539", 0, "no description", 60.2], ["1540", "I7N3G6G", "1540", 0, "no description", 60.2], ["1541", "I7N3G6G", "1541", 0, "no description", 60.2], ["1542", "I7N3G6G", "1542", 0, "no description", 60.2], ["1543", "I7N3G6G", "1543", 0, "no description", 60.2], ["1544", "I7N3G6G", "1544", 0, "no description", 60.2], ["1545", "I7N3G6G", "1545", 0, "no description", 60.2], ["1546", "I7N3G6G", "1546", 0, "no description", 60.2], ["1547", "I7N3G6G", "1547", 0, "no description", 60.2], ["1548", "I7N3G6G", "1548", 0, "no description", 60.2], ["1549", "I7N3G6G", "1549", 0, "no description", 60.2], ["1550", "I7N3G6G", "1550", 0, "no description", 60.2], ["1551", "I7N3G6G", "1551", 0, "no description", 60.2], ["1552", "I7N3G6G", "1552", 0, "no description", 60.2], ["1553", "I7N3G6G", "1553", 0, "no description", 60.2], ["1554", "I7N3G6G", "1554", 0, "no description", 60.2], ["1555", "I7N3G6G", "1555", 0, "no description", 60.2], ["1556", "I7N3G6G", "1556", 0, "no description", 60.2], ["1557", "I7N3G6G", "1557", 0, "no description", 60.2], ["1558", "I7N3G6G", "1558", 0, "no description", 60.2], ["1559", "I7N3G6G", "1559", 0, "no description", 60.2], ["1560", "I7N3G6G", "1560", 0, "no description", 60.2], ["1561", "I7N3G6G", "1561", 0, "no description", 60.2], ["1562", "I7N3G6G", "1562", 0, "no description", 60.2], ["1563", "I7N3G6G", "1563", 0, "no description", 60.2], ["1564", "I7N3G6G", "1564", 0, "no description", 60.2], ["1565", "I7N3G6G", "1565", 0, "no description", 60.2], ["1566", "I7N3G6G", "1566", 0, "no description", 60.2], ["1567", "I7N3G6G", "1567", 0, "no description", 60.2], ["1568", "I7N3G6G", "1568", 0, "no description", 60.2], ["1569", "I7N3G6G", "1569", 0, "no description", 60.2], ["1570", "I7N3G6G", "1570", 0, "no description", 60.2], ["1571", "I7N3G6G", "1571", 0, "no description", 60.2], ["1572", "I7N3G6G", "1572", 0, "no description", 60.2], ["1573", "I7N3G6G", "1573", 0, "no description", 60.2], ["1574", "I7N3G6G", "1574", 0, "no description", 60.2], ["1575", "I7N3G6G", "1575", 0, "no description", 60.2], ["1576", "I7N3G6G", "1576", 0, "no description", 60.2], ["1577", "I7N3G6G", "1577", 0, "no description", 60.2], ["1578", "I7N3G6G", "1578", 0, "no description", 60.2], ["1579", "I7N3G6G", "1579", 0, "no description", 60.2], ["1580", "I7N3G6G", "1580", 0, "no description", 60.2], ["1581", "I7N3G6G", "1581", 0, "no description", 60.2], ["1582", "I7N3G6G", "1582", 0, "no description", 60.2], ["1583", "I7N3G6G", "1583", 0, "no description", 60.2], ["1584", "I7N3G6G", "1584", 0, "no description", 60.2], ["1585", "I7N3G6G", "1585", 0, "no description", 60.2], ["1586", "I7N3G6G", "1586", 0, "no description", 60.2], ["1587", "I7N3G6G", "1587", 0, "no description", 60.2], ["1588", "I7N3G6G", "1588", 0, "no description", 60.2], ["1589", "I7N3G6G", "1589", 0, "no description", 60.2], ["1590", "I7N3G6G", "1590", 0, "no description", 60.2], ["1591", "I7N3G6G", "1591", 0, "no description", 60.2], ["1592", "I7N3G6G", "1592", 0, "no description", 60.2], ["1593", "I7N3G6G", "1593", 0, "no description", 60.2], ["1594", "I7N3G6G", "1594", 0, "no description", 60.2], ["1595", "I7N3G6G", "1595", 0, "no description", 60.2], ["1596", "I7N3G6G", "1596", 0, "no description", 60.2], ["1597", "I7N3G6G", "1597", 0, "no description", 60.2], ["1598", "I7N3G6G", "1598", 0, "no description", 60.2], ["1599", "I7N3G6G", "1599", 0, "no description", 60.2], ["1600", "I7N3G6G", "1600", 0, "no description", 60.2], ["1601", "I7N3G6G", "1601", 0, "no description", 60.2], ["1602", "I7N3G6G", "1602", 0, "no description", 60.2], ["1603", "I7N3G6G", "1603", 0, "no description", 60.2], ["1604", "I7N3G6G", "1604", 0, "no description", 60.2], ["1605", "I7N3G6G", "1605", 0, "no description", 60.2], ["1606", "I7N3G6G", "1606", 0, "no description", 60.2], ["1607", "I7N3G6G", "1607", 0, "no description", 60.2], ["1608", "I7N3G6G", "1608", 0, "no description", 60.2], ["1609", "I7N3G6G", "1609", 0, "no description", 60.2], ["1610", "I7N3G6G", "1610", 0, "no description", 60.2], ["1611", "I7N3G6G", "1611", 0, "no description", 60.2], ["1612", "I7N3G6G", "1612", 0, "no description", 60.2], ["1613", "I7N3G6G", "1613", 0, "no description", 60.2], ["1614", "I7N3G6G", "1614", 0, "no description", 60.2], ["1615", "I7N3G6G", "1615", 0, "no description", 60.2], ["1616", "I7N3G6G", "1616", 0, "no description", 60.2], ["1617", "I7N3G6G", "1617", 0, "no description", 60.2], ["1618", "I7N3G6G", "1618", 0, "no description", 60.2], ["1619", "I7N3G6G", "1619", 0, "no description", 60.2], ["1620", "I7N3G6G", "1620", 0, "no description", 60.2], ["1621", "I7N3G6G", "1621", 0, "no description", 60.2], ["1622", "I7N3G6G", "1622", 0, "no description", 60.2], ["1623", "I7N3G6G", "1623", 0, "no description", 60.2], ["1624", "I7N3G6G", "1624", 0, "no description", 60.2], ["1625", "I7N3G6G", "1625", 0, "no description", 60.2], ["1626", "I7N3G6G", "1626", 0, "no description", 60.2], ["1627", "I7N3G6G", "1627", 0, "no description", 60.2], ["1628", "I7N3G6G", "1628", 0, "no description", 60.2], ["1629", "I7N3G6G", "1629", 0, "no description", 60.2], ["1630", "I7N3G6G", "1630", 0, "no description", 60.2], ["1631", "I7N3G6G", "1631", 0, "no description", 60.2], ["1632", "I7N3G6G", "1632", 0, "no description", 60.2], ["1633", "I7N3G6G", "1633", 0, "no description", 60.2], ["1634", "I7N3G6G", "1634", 0, "no description", 60.2], ["1635", "I7N3G6G", "1635", 0, "no description", 60.2], ["1636", "I7N3G6G", "1636", 0, "no description", 60.2], ["1637", "I7N3G6G", "1637", 0, "no description", 60.2], ["1638", "I7N3G6G", "1638", 0, "no description", 60.2], ["1639", "I7N3G6G", "1639", 0, "no description", 60.2], ["1640", "I7N3G6G", "1640", 0, "no description", 60.2], ["1641", "I7N3G6G", "1641", 0, "no description", 60.2], ["1642", "I7N3G6G", "1642", 0, "no description", 60.2], ["1643", "I7N3G6G", "1643", 0, "no description", 60.2], ["1644", "I7N3G6G", "1644", 0, "no description", 60.2], ["1645", "I7N3G6G", "1645", 0, "no description", 60.2], ["1646", "I7N3G6G", "1646", 0, "no description", 60.2], ["1647", "I7N3G6G", "1647", 0, "no description", 60.2], ["1648", "I7N3G6G", "1648", 0, "no description", 60.2], ["1649", "I7N3G6G", "1649", 0, "no description", 60.2], ["1650", "I7N3G6G", "1650", 0, "no description", 60.2], ["1651", "I7N3G6G", "1651", 0, "no description", 60.2], ["1652", "I7N3G6G", "1652", 0, "no description", 60.2], ["1653", "I7N3G6G", "1653", 0, "no description", 60.2], ["1654", "I7N3G6G", "1654", 0, "no description", 60.2], ["1655", "I7N3G6G", "1655", 0, "no description", 60.2], ["1656", "I7N3G6G", "1656", 0, "no description", 60.2], ["1657", "I7N3G6G", "1657", 0, "no description", 60.2], ["1658", "I7N3G6G", "1658", 0, "no description", 60.2], ["1659", "I7N3G6G", "1659", 0, "no description", 60.2], ["1660", "I7N3G6G", "1660", 0, "no description", 60.2], ["1661", "I7N3G6G", "1661", 0, "no description", 60.2], ["1662", "I7N3G6G", "1662", 0, "no description", 60.2], ["1663", "I7N3G6G", "1663", 0, "no description", 60.2], ["1664", "I7N3G6G", "1664", 0, "no description", 60.2], ["1665", "I7N3G6G", "1665", 0, "no description", 60.2], ["1666", "I7N3G6G", "1666", 0, "no description", 60.2], ["1667", "I7N3G6G", "1667", 0, "no description", 60.2], ["1668", "I7N3G6G", "1668", 0, "no description", 60.2], ["1669", "I7N3G6G", "1669", 0, "no description", 60.2], ["1670", "I7N3G6G", "1670", 0, "no description", 60.2], ["1671", "I7N3G6G", "1671", 0, "no description", 60.2], ["1672", "I7N3G6G", "1672", 0, "no description", 60.2], ["1673", "I7N3G6G", "1673", 0, "no description", 60.2], ["1674", "I7N3G6G", "1674", 0, "no description", 60.2], ["1675", "I7N3G6G", "1675", 0, "no description", 60.2], ["1676", "I7N3G6G", "1676", 0, "no description", 60.2], ["1677", "I7N3G6G", "1677", 0, "no description", 60.2], ["1678", "I7N3G6G", "1678", 0, "no description", 60.2], ["1679", "I7N3G6G", "1679", 0, "no description", 60.2], ["1680", "I7N3G6G", "1680", 0, "no description", 60.2], ["1681", "I7N3G6G", "1681", 0, "no description", 60.2], ["1682", "I7N3G6G", "1682", 0, "no description", 60.2], ["1683", "I7N3G6G", "1683", 0, "no description", 60.2], ["1684", "I7N3G6G", "1684", 0, "no description", 60.2], ["1685", "I7N3G6G", "1685", 0, "no description", 60.2], ["1686", "I7N3G6G", "1686", 0, "no description", 60.2], ["1687", "I7N3G6G", "1687", 0, "no description", 60.2], ["1688", "I7N3G6G", "1688", 0, "no description", 60.2], ["1689", "I7N3G6G", "1689", 0, "no description", 60.2], ["1690", "I7N3G6G", "1690", 0, "no description", 60.2], ["1691", "I7N3G6G", "1691", 0, "no description", 60.2], ["1692", "I7N3G6G", "1692", 0, "no description", 60.2], ["1693", "I7N3G6G", "1693", 0, "no description", 60.2], ["1694", "I7N3G6G", "1694", 0, "no description", 60.2], ["1695", "I7N3G6G", "1695", 0, "no description", 60.2], ["1696", "I7N3G6G", "1696", 0, "no description", 60.2], ["1697", "I7N3G6G", "1697", 0, "no description", 60.2], ["1698", "I7N3G6G", "1698", 0, "no description", 60.2], ["1699", "I7N3G6G", "1699", 0, "no description", 60.2], ["1700", "I7N3G6G", "1700", 0, "no description", 60.2], ["1701", "I7N3G6G", "1701", 0, "no description", 60.2], ["1702", "I7N3G6G", "1702", 0, "no description", 60.2], ["1703", "I7N3G6G", "1703", 0, "no description", 60.2], ["1704", "I7N3G6G", "1704", 0, "no description", 60.2], ["1705", "I7N3G6G", "1705", 0, "no description", 60.2], ["1706", "I7N3G6G", "1706", 0, "no description", 60.2], ["1707", "I7N3G6G", "1707", 0, "no description", 60.2], ["1708", "I7N3G6G", "1708", 0, "no description", 60.2], ["1709", "I7N3G6G", "1709", 0, "no description", 60.2], ["1710", "I7N3G6G", "1710", 0, "no description", 60.2], ["1711", "I7N3G6G", "1711", 0, "no description", 60.2], ["1712", "I7N3G6G", "1712", 0, "no description", 60.2], ["1713", "I7N3G6G", "1713", 0, "no description", 60.2], ["1714", "I7N3G6G", "1714", 0, "no description", 60.2], ["1715", "I7N3G6G", "1715", 0, "no description", 60.2], ["1716", "I7N3G6G", "1716", 0, "no description", 60.2], ["1717", "I7N3G6G", "1717", 0, "no description", 60.2], ["1718", "I7N3G6G", "1718", 0, "no description", 60.2], ["1719", "I7N3G6G", "1719", 0, "no description", 60.2], ["1720", "I7N3G6G", "1720", 0, "no description", 60.2], ["1721", "I7N3G6G", "1721", 0, "no description", 60.2], ["1722", "I7N3G6G", "1722", 0, "no description", 60.2], ["1723", "I7N3G6G", "1723", 0, "no description", 60.2], ["1724", "I7N3G6G", "1724", 0, "no description", 60.2], ["1725", "I7N3G6G", "1725", 0, "no description", 60.2], ["1726", "I7N3G6G", "1726", 0, "no description", 60.2], ["1727", "I7N3G6G", "1727", 0, "no description", 60.2], ["1728", "I7N3G6G", "1728", 0, "no description", 60.2], ["1729", "I7N3G6G", "1729", 0, "no description", 60.2], ["1730", "I7N3G6G", "1730", 0, "no description", 60.2], ["1731", "I7N3G6G", "1731", 0, "no description", 60.2], ["1732", "I7N3G6G", "1732", 0, "no description", 60.2], ["1733", "I7N3G6G", "1733", 0, "no description", 60.2], ["1734", "I7N3G6G", "1734", 0, "no description", 60.2], ["1735", "I7N3G6G", "1735", 0, "no description", 60.2], ["1736", "I7N3G6G", "1736", 0, "no description", 60.2], ["1737", "I7N3G6G", "1737", 0, "no description", 60.2], ["1738", "I7N3G6G", "1738", 0, "no description", 60.2], ["1739", "I7N3G6G", "1739", 0, "no description", 60.2], ["1740", "I7N3G6G", "1740", 0, "no description", 60.2], ["1741", "I7N3G6G", "1741", 0, "no description", 60.2], ["1742", "I7N3G6G", "1742", 0, "no description", 60.2], ["1743", "I7N3G6G", "1743", 0, "no description", 60.2], ["1744", "I7N3G6G", "1744", 0, "no description", 60.2], ["1745", "I7N3G6G", "1745", 0, "no description", 60.2], ["1746", "I7N3G6G", "1746", 0, "no description", 60.2], ["1747", "I7N3G6G", "1747", 0, "no description", 60.2], ["1748", "I7N3G6G", "1748", 0, "no description", 60.2], ["1749", "I7N3G6G", "1749", 0, "no description", 60.2], ["1750", "I7N3G6G", "1750", 0, "no description", 60.2], ["1751", "I7N3G6G", "1751", 0, "no description", 60.2], ["1752", "I7N3G6G", "1752", 0, "no description", 60.2], ["1753", "I7N3G6G", "1753", 0, "no description", 60.2], ["1754", "I7N3G6G", "1754", 0, "no description", 60.2], ["1755", "I7N3G6G", "1755", 0, "no description", 60.2], ["1756", "I7N3G6G", "1756", 0, "no description", 60.2], ["1757", "I7N3G6G", "1757", 0, "no description", 60.2], ["1758", "I7N3G6G", "1758", 0, "no description", 60.2], ["1759", "I7N3G6G", "1759", 0, "no description", 60.2], ["1760", "I7N3G6G", "1760", 0, "no description", 60.2], ["1761", "I7N3G6G", "1761", 0, "no description", 60.2], ["1762", "I7N3G6G", "1762", 0, "no description", 60.2], ["1763", "I7N3G6G", "1763", 0, "no description", 60.2], ["1764", "I7N3G6G", "1764", 0, "no description", 60.2], ["1765", "I7N3G6G", "1765", 0, "no description", 60.2], ["1766", "I7N3G6G", "1766", 0, "no description", 60.2], ["1767", "I7N3G6G", "1767", 0, "no description", 60.2], ["1768", "I7N3G6G", "1768", 0, "no description", 60.2], ["1769", "I7N3G6G", "1769", 0, "no description", 60.2], ["1770", "I7N3G6G", "1770", 0, "no description", 60.2], ["1771", "I7N3G6G", "1771", 0, "no description", 60.2], ["1772", "I7N3G6G", "1772", 0, "no description", 60.2], ["1773", "I7N3G6G", "1773", 0, "no description", 60.2], ["1774", "I7N3G6G", "1774", 0, "no description", 60.2], ["1775", "I7N3G6G", "1775", 0, "no description", 60.2], ["1776", "I7N3G6G", "1776", 0, "no description", 60.2], ["1777", "I7N3G6G", "1777", 0, "no description", 60.2], ["1778", "I7N3G6G", "1778", 0, "no description", 60.2], ["1779", "I7N3G6G", "1779", 0, "no description", 60.2], ["1780", "I7N3G6G", "1780", 0, "no description", 60.2], ["1781", "I7N3G6G", "1781", 0, "no description", 60.2], ["1782", "I7N3G6G", "1782", 0, "no description", 60.2], ["1783", "I7N3G6G", "1783", 0, "no description", 60.2], ["1784", "I7N3G6G", "1784", 0, "no description", 60.2], ["1785", "I7N3G6G", "1785", 0, "no description", 60.2], ["1786", "I7N3G6G", "1786", 0, "no description", 60.2], ["1787", "I7N3G6G", "1787", 0, "no description", 60.2], ["1788", "I7N3G6G", "1788", 0, "no description", 60.2], ["1789", "I7N3G6G", "1789", 0, "no description", 60.2], ["1790", "I7N3G6G", "1790", 0, "no description", 60.2], ["1791", "I7N3G6G", "1791", 0, "no description", 60.2], ["1792", "I7N3G6G", "1792", 0, "no description", 60.2], ["1793", "I7N3G6G", "1793", 0, "no description", 60.2], ["1794", "I7N3G6G", "1794", 0, "no description", 60.2], ["1795", "I7N3G6G", "1795", 0, "no description", 60.2], ["1796", "I7N3G6G", "1796", 0, "no description", 60.2], ["1797", "I7N3G6G", "1797", 0, "no description", 60.2], ["1798", "I7N3G6G", "1798", 0, "no description", 60.2], ["1799", "I7N3G6G", "1799", 0, "no description", 60.2], ["1800", "I7N3G6G", "1800", 0, "no description", 60.2], ["1801", "I7N3G6G", "1801", 0, "no description", 60.2], ["1802", "I7N3G6G", "1802", 0, "no description", 60.2], ["1803", "I7N3G6G", "1803", 0, "no description", 60.2], ["1804", "I7N3G6G", "1804", 0, "no description", 60.2], ["1805", "I7N3G6G", "1805", 0, "no description", 60.2], ["1806", "I7N3G6G", "1806", 0, "no description", 60.2], ["1807", "I7N3G6G", "1807", 0, "no description", 60.2], ["1808", "I7N3G6G", "1808", 0, "no description", 60.2], ["1809", "I7N3G6G", "1809", 0, "no description", 60.2], ["1810", "I7N3G6G", "1810", 0, "no description", 60.2], ["1811", "I7N3G6G", "1811", 0, "no description", 60.2], ["1812", "I7N3G6G", "1812", 0, "no description", 60.2], ["1813", "I7N3G6G", "1813", 0, "no description", 60.2], ["1814", "I7N3G6G", "1814", 0, "no description", 60.2], ["1815", "I7N3G6G", "1815", 0, "no description", 60.2], ["1816", "I7N3G6G", "1816", 0, "no description", 60.2], ["1817", "I7N3G6G", "1817", 0, "no description", 60.2], ["1818", "I7N3G6G", "1818", 0, "no description", 60.2], ["1819", "I7N3G6G", "1819", 0, "no description", 60.2], ["1820", "I7N3G6G", "1820", 0, "no description", 60.2], ["1821", "I7N3G6G", "1821", 0, "no description", 60.2], ["1822", "I7N3G6G", "1822", 0, "no description", 60.2], ["1823", "I7N3G6G", "1823", 0, "no description", 60.2], ["1824", "I7N3G6G", "1824", 0, "no description", 60.2], ["1825", "I7N3G6G", "1825", 0, "no description", 60.2], ["1826", "I7N3G6G", "1826", 0, "no description", 60.2], ["1827", "I7N3G6G", "1827", 0, "no description", 60.2], ["1828", "I7N3G6G", "1828", 0, "no description", 60.2], ["1829", "I7N3G6G", "1829", 0, "no description", 60.2], ["1830", "I7N3G6G", "1830", 0, "no description", 60.2], ["1831", "I7N3G6G", "1831", 0, "no description", 60.2], ["1832", "I7N3G6G", "1832", 0, "no description", 60.2], ["1833", "I7N3G6G", "1833", 0, "no description", 60.2], ["1834", "I7N3G6G", "1834", 0, "no description", 60.2], ["1835", "I7N3G6G", "1835", 0, "no description", 60.2], ["1836", "I7N3G6G", "1836", 0, "no description", 60.2], ["1837", "I7N3G6G", "1837", 0, "no description", 60.2], ["1838", "I7N3G6G", "1838", 0, "no description", 60.2], ["1839", "I7N3G6G", "1839", 0, "no description", 60.2], ["1840", "I7N3G6G", "1840", 0, "no description", 60.2], ["1841", "I7N3G6G", "1841", 0, "no description", 60.2], ["1842", "I7N3G6G", "1842", 0, "no description", 60.2], ["1843", "I7N3G6G", "1843", 0, "no description", 60.2], ["1844", "I7N3G6G", "1844", 0, "no description", 60.2], ["1845", "I7N3G6G", "1845", 0, "no description", 60.2], ["1846", "I7N3G6G", "1846", 0, "no description", 60.2], ["1847", "I7N3G6G", "1847", 0, "no description", 60.2], ["1848", "I7N3G6G", "1848", 0, "no description", 60.2], ["1849", "I7N3G6G", "1849", 0, "no description", 60.2], ["1850", "I7N3G6G", "1850", 0, "no description", 60.2], ["1851", "I7N3G6G", "1851", 0, "no description", 60.2], ["1852", "I7N3G6G", "1852", 0, "no description", 60.2], ["1853", "I7N3G6G", "1853", 0, "no description", 60.2], ["1854", "I7N3G6G", "1854", 0, "no description", 60.2], ["1855", "I7N3G6G", "1855", 0, "no description", 60.2], ["1856", "I7N3G6G", "1856", 0, "no description", 60.2], ["1857", "I7N3G6G", "1857", 0, "no description", 60.2], ["1858", "I7N3G6G", "1858", 0, "no description", 60.2], ["1859", "I7N3G6G", "1859", 0, "no description", 60.2], ["1860", "I7N3G6G", "1860", 0, "no description", 60.2], ["1861", "I7N3G6G", "1861", 0, "no description", 60.2], ["1862", "I7N3G6G", "1862", 0, "no description", 60.2], ["1863", "I7N3G6G", "1863", 0, "no description", 60.2], ["1864", "I7N3G6G", "1864", 0, "no description", 60.2], ["1865", "I7N3G6G", "1865", 0, "no description", 60.2], ["1866", "I7N3G6G", "1866", 0, "no description", 60.2], ["1867", "I7N3G6G", "1867", 0, "no description", 60.2], ["1868", "I7N3G6G", "1868", 0, "no description", 60.2], ["1869", "I7N3G6G", "1869", 0, "no description", 60.2], ["1870", "I7N3G6G", "1870", 0, "no description", 60.2], ["1871", "I7N3G6G", "1871", 0, "no description", 60.2], ["1872", "I7N3G6G", "1872", 0, "no description", 60.2], ["1873", "I7N3G6G", "1873", 0, "no description", 60.2], ["1874", "I7N3G6G", "1874", 0, "no description", 60.2], ["1875", "I7N3G6G", "1875", 0, "no description", 60.2], ["1876", "I7N3G6G", "1876", 0, "no description", 60.2], ["1877", "I7N3G6G", "1877", 0, "no description", 60.2], ["1878", "I7N3G6G", "1878", 0, "no description", 60.2], ["1879", "I7N3G6G", "1879", 0, "no description", 60.2], ["1880", "I7N3G6G", "1880", 0, "no description", 60.2], ["1881", "I7N3G6G", "1881", 0, "no description", 60.2], ["1882", "I7N3G6G", "1882", 0, "no description", 60.2], ["1883", "I7N3G6G", "1883", 0, "no description", 60.2], ["1884", "I7N3G6G", "1884", 0, "no description", 60.2], ["1885", "I7N3G6G", "1885", 0, "no description", 60.2], ["1886", "I7N3G6G", "1886", 0, "no description", 60.2], ["1887", "I7N3G6G", "1887", 0, "no description", 60.2], ["1888", "I7N3G6G", "1888", 0, "no description", 60.2], ["1889", "I7N3G6G", "1889", 0, "no description", 60.2], ["1890", "I7N3G6G", "1890", 0, "no description", 60.2], ["1891", "I7N3G6G", "1891", 0, "no description", 60.2], ["1892", "I7N3G6G", "1892", 0, "no description", 60.2], ["1893", "I7N3G6G", "1893", 0, "no description", 60.2], ["1894", "I7N3G6G", "1894", 0, "no description", 60.2], ["1895", "I7N3G6G", "1895", 0, "no description", 60.2], ["1896", "I7N3G6G", "1896", 0, "no description", 60.2], ["1897", "I7N3G6G", "1897", 0, "no description", 60.2], ["1898", "I7N3G6G", "1898", 0, "no description", 60.2], ["1899", "I7N3G6G", "1899", 0, "no description", 60.2], ["1900", "I7N3G6G", "1900", 0, "no description", 60.2], ["1901", "I7N3G6G", "1901", 0, "no description", 60.2], ["1902", "I7N3G6G", "1902", 0, "no description", 60.2], ["1903", "I7N3G6G", "1903", 0, "no description", 60.2], ["1904", "I7N3G6G", "1904", 0, "no description", 60.2], ["1905", "I7N3G6G", "1905", 0, "no description", 60.2], ["1906", "I7N3G6G", "1906", 0, "no description", 60.2], ["1907", "I7N3G6G", "1907", 0, "no description", 60.2], ["1908", "I7N3G6G", "1908", 0, "no description", 60.2], ["1909", "I7N3G6G", "1909", 0, "no description", 60.2], ["1910", "I7N3G6G", "1910", 0, "no description", 60.2], ["1911", "I7N3G6G", "1911", 0, "no description", 60.2], ["1912", "I7N3G6G", "1912", 0, "no description", 60.2], ["1913", "I7N3G6G", "1913", 0, "no description", 60.2], ["1914", "I7N3G6G", "1914", 0, "no description", 60.2], ["1915", "I7N3G6G", "1915", 0, "no description", 60.2], ["1916", "I7N3G6G", "1916", 0, "no description", 60.2], ["1917", "I7N3G6G", "1917", 0, "no description", 60.2], ["1918", "I7N3G6G", "1918", 0, "no description", 60.2], ["1919", "I7N3G6G", "1919", 0, "no description", 60.2], ["1920", "I7N3G6G", "1920", 0, "no description", 60.2], ["1921", "I7N3G6G", "1921", 0, "no description", 60.2], ["1922", "I7N3G6G", "1922", 0, "no description", 60.2], ["1923", "I7N3G6G", "1923", 0, "no description", 60.2], ["1924", "I7N3G6G", "1924", 0, "no description", 60.2], ["1925", "I7N3G6G", "1925", 0, "no description", 60.2], ["1926", "I7N3G6G", "1926", 0, "no description", 60.2], ["1927", "I7N3G6G", "1927", 0, "no description", 60.2], ["1928", "I7N3G6G", "1928", 0, "no description", 60.2], ["1929", "I7N3G6G", "1929", 0, "no description", 60.2], ["1930", "I7N3G6G", "1930", 0, "no description", 60.2], ["1931", "I7N3G6G", "1931", 0, "no description", 60.2], ["1932", "I7N3G6G", "1932", 0, "no description", 60.2], ["1933", "I7N3G6G", "1933", 0, "no description", 60.2], ["1934", "I7N3G6G", "1934", 0, "no description", 60.2], ["1935", "I7N3G6G", "1935", 0, "no description", 60.2], ["1936", "I7N3G6G", "1936", 0, "no description", 60.2], ["1937", "I7N3G6G", "1937", 0, "no description", 60.2], ["1938", "I7N3G6G", "1938", 0, "no description", 60.2], ["1939", "I7N3G6G", "1939", 0, "no description", 60.2], ["1940", "I7N3G6G", "1940", 0, "no description", 60.2], ["1941", "I7N3G6G", "1941", 0, "no description", 60.2], ["1942", "I7N3G6G", "1942", 0, "no description", 60.2], ["1943", "I7N3G6G", "1943", 0, "no description", 60.2], ["1944", "I7N3G6G", "1944", 0, "no description", 60.2], ["1945", "I7N3G6G", "1945", 0, "no description", 60.2], ["1946", "I7N3G6G", "1946", 0, "no description", 60.2], ["1947", "I7N3G6G", "1947", 0, "no description", 60.2], ["1948", "I7N3G6G", "1948", 0, "no description", 60.2], ["1949", "I7N3G6G", "1949", 0, "no description", 60.2], ["1950", "I7N3G6G", "1950", 0, "no description", 60.2], ["1951", "I7N3G6G", "1951", 0, "no description", 60.2], ["1952", "I7N3G6G", "1952", 0, "no description", 60.2], ["1953", "I7N3G6G", "1953", 0, "no description", 60.2], ["1954", "I7N3G6G", "1954", 0, "no description", 60.2], ["1955", "I7N3G6G", "1955", 0, "no description", 60.2], ["1956", "I7N3G6G", "1956", 0, "no description", 60.2], ["1957", "I7N3G6G", "1957", 0, "no description", 60.2], ["1958", "I7N3G6G", "1958", 0, "no description", 60.2], ["1959", "I7N3G6G", "1959", 0, "no description", 60.2], ["1960", "I7N3G6G", "1960", 0, "no description", 60.2], ["1961", "I7N3G6G", "1961", 0, "no description", 60.2], ["1962", "I7N3G6G", "1962", 0, "no description", 60.2], ["1963", "I7N3G6G", "1963", 0, "no description", 60.2], ["1964", "I7N3G6G", "1964", 0, "no description", 60.2], ["1965", "I7N3G6G", "1965", 0, "no description", 60.2], ["1966", "I7N3G6G", "1966", 0, "no description", 60.2], ["1967", "I7N3G6G", "1967", 0, "no description", 60.2], ["1968", "I7N3G6G", "1968", 0, "no description", 60.2], ["1969", "I7N3G6G", "1969", 0, "no description", 60.2], ["1970", "I7N3G6G", "1970", 0, "no description", 60.2], ["1971", "I7N3G6G", "1971", 0, "no description", 60.2], ["1972", "I7N3G6G", "1972", 0, "no description", 60.2], ["1973", "I7N3G6G", "1973", 0, "no description", 60.2], ["1974", "I7N3G6G", "1974", 0, "no description", 60.2], ["1975", "I7N3G6G", "1975", 0, "no description", 60.2], ["1976", "I7N3G6G", "1976", 0, "no description", 60.2], ["1977", "I7N3G6G", "1977", 0, "no description", 60.2], ["1978", "I7N3G6G", "1978", 0, "no description", 60.2], ["1979", "I7N3G6G", "1979", 0, "no description", 60.2], ["1980", "I7N3G6G", "1980", 0, "no description", 60.2], ["1981", "I7N3G6G", "1981", 0, "no description", 60.2], ["1982", "I7N3G6G", "1982", 0, "no description", 60.2], ["1983", "I7N3G6G", "1983", 0, "no description", 60.2], ["1984", "I7N3G6G", "1984", 0, "no description", 60.2], ["1985", "I7N3G6G", "1985", 0, "no description", 60.2], ["1986", "I7N3G6G", "1986", 0, "no description", 60.2], ["1987", "I7N3G6G", "1987", 0, "no description", 60.2], ["1988", "I7N3G6G", "1988", 0, "no description", 60.2], ["1989", "I7N3G6G", "1989", 0, "no description", 60.2], ["1990", "I7N3G6G", "1990", 0, "no description", 60.2], ["1991", "I7N3G6G", "1991", 0, "no description", 60.2], ["1992", "I7N3G6G", "1992", 0, "no description", 60.2], ["1993", "I7N3G6G", "1993", 0, "no description", 60.2], ["1994", "I7N3G6G", "1994", 0, "no description", 60.2], ["1995", "I7N3G6G", "1995", 0, "no description", 60.2], ["1996", "I7N3G6G", "1996", 0, "no description", 60.2], ["1997", "I7N3G6G", "1997", 0, "no description", 60.2], ["1998", "I7N3G6G", "1998", 0, "no description", 60.2], ["1999", "I7N3G6G", "1999", 0, "no description", 60.2], ["2000", "I7N3G6G", "2000", 0, "no description", 60.2], ["2001", "I7N3G6G", "2001", 0, "no description", 60.2], ["2002", "I7N3G6G", "2002", 0, "no description", 60.2], ["2003", "I7N3G6G", "2003", 0, "no description", 60.2], ["2004", "I7N3G6G", "2004", 0, "no description", 60.2], ["2005", "I7N3G6G", "2005", 0, "no description", 60.2], ["2006", "I7N3G6G", "2006", 0, "no description", 60.2], ["2007", "I7N3G6G", "2007", 0, "no description", 60.2], ["2008", "I7N3G6G", "2008", 0, "no description", 60.2], ["2009", "I7N3G6G", "2009", 0, "no description", 60.2], ["2010", "I7N3G6G", "2010", 0, "no description", 60.2], ["2011", "I7N3G6G", "2011", 0, "no description", 60.2], ["2012", "I7N3G6G", "2012", 0, "no description", 60.2], ["2013", "I7N3G6G", "2013", 0, "no description", 60.2], ["2014", "I7N3G6G", "2014", 0, "no description", 60.2], ["2015", "I7N3G6G", "2015", 0, "no description", 60.2], ["2016", "I7N3G6G", "2016", 0, "no description", 60.2], ["2017", "I7N3G6G", "2017", 0, "no description", 60.2], ["2018", "I7N3G6G", "2018", 0, "no description", 60.2], ["2019", "I7N3G6G", "2019", 0, "no description", 60.2], ["2020", "I7N3G6G", "2020", 0, "no description", 60.2], ["2021", "I7N3G6G", "2021", 0, "no description", 60.2], ["2022", "I7N3G6G", "2022", 0, "no description", 60.2], ["2023", "I7N3G6G", "2023", 0, "no description", 60.2], ["2024", "I7N3G6G", "2024", 0, "no description", 60.2], ["2025", "I7N3G6G", "2025", 0, "no description", 60.2], ["2026", "I7N3G6G", "2026", 0, "no description", 60.2], ["2027", "I7N3G6G", "2027", 0, "no description", 60.2], ["2028", "I7N3G6G", "2028", 0, "no description", 60.2], ["2029", "I7N3G6G", "2029", 0, "no description", 60.2], ["2030", "I7N3G6G", "2030", 0, "no description", 60.2], ["2031", "I7N3G6G", "2031", 0, "no description", 60.2], ["2032", "I7N3G6G", "2032", 0, "no description", 60.2], ["2033", "I7N3G6G", "2033", 0, "no description", 60.2], ["2034", "I7N3G6G", "2034", 0, "no description", 60.2], ["2035", "I7N3G6G", "2035", 0, "no description", 60.2], ["2036", "I7N3G6G", "2036", 0, "no description", 60.2], ["2037", "I7N3G6G", "2037", 0, "no description", 60.2], ["2038", "I7N3G6G", "2038", 0, "no description", 60.2], ["2039", "I7N3G6G", "2039", 0, "no description", 60.2], ["2040", "I7N3G6G", "2040", 0, "no description", 60.2], ["2041", "I7N3G6G", "2041", 0, "no description", 60.2], ["2042", "I7N3G6G", "2042", 0, "no description", 60.2], ["2043", "I7N3G6G", "2043", 0, "no description", 60.2], ["2044", "I7N3G6G", "2044", 0, "no description", 60.2], ["2045", "I7N3G6G", "2045", 0, "no description", 60.2], ["2046", "I7N3G6G", "2046", 0, "no description", 60.2], ["2047", "I7N3G6G", "2047", 0, "no description", 60.2], ["2048", "I7N3G6G", "2048", 0, "no description", 60.2], ["2049", "I7N3G6G", "2049", 0, "no description", 60.2], ["2050", "I7N3G6G", "2050", 0, "no description", 60.2], ["2051", "I7N3G6G", "2051", 0, "no description", 60.2], ["2052", "I7N3G6G", "2052", 0, "no description", 60.2], ["2053", "I7N3G6G", "2053", 0, "no description", 60.2], ["2054", "I7N3G6G", "2054", 0, "no description", 60.2], ["2055", "I7N3G6G", "2055", 0, "no description", 60.2], ["2056", "I7N3G6G", "2056", 0, "no description", 60.2], ["2057", "I7N3G6G", "2057", 0, "no description", 60.2], ["2058", "I7N3G6G", "2058", 0, "no description", 60.2], ["2059", "I7N3G6G", "2059", 0, "no description", 60.2], ["2060", "I7N3G6G", "2060", 0, "no description", 60.2], ["2061", "I7N3G6G", "2061", 0, "no description", 60.2], ["2062", "I7N3G6G", "2062", 0, "no description", 60.2], ["2063", "I7N3G6G", "2063", 0, "no description", 60.2], ["2064", "I7N3G6G", "2064", 0, "no description", 60.2], ["2065", "I7N3G6G", "2065", 0, "no description", 60.2], ["2066", "I7N3G6G", "2066", 0, "no description", 60.2], ["2067", "I7N3G6G", "2067", 0, "no description", 60.2], ["2068", "I7N3G6G", "2068", 0, "no description", 60.2], ["2069", "I7N3G6G", "2069", 0, "no description", 60.2], ["2070", "I7N3G6G", "2070", 0, "no description", 60.2], ["2071", "I7N3G6G", "2071", 0, "no description", 60.2], ["2072", "I7N3G6G", "2072", 0, "no description", 60.2], ["2073", "I7N3G6G", "2073", 0, "no description", 60.2], ["2074", "I7N3G6G", "2074", 0, "no description", 60.2], ["2075", "I7N3G6G", "2075", 0, "no description", 60.2], ["2076", "I7N3G6G", "2076", 0, "no description", 60.2], ["2077", "I7N3G6G", "2077", 0, "no description", 60.2], ["2078", "I7N3G6G", "2078", 0, "no description", 60.2], ["2079", "I7N3G6G", "2079", 0, "no description", 60.2], ["2080", "I7N3G6G", "2080", 0, "no description", 60.2], ["2081", "I7N3G6G", "2081", 0, "no description", 60.2], ["2082", "I7N3G6G", "2082", 0, "no description", 60.2], ["2083", "I7N3G6G", "2083", 0, "no description", 60.2], ["2084", "I7N3G6G", "2084", 0, "no description", 60.2], ["2085", "I7N3G6G", "2085", 0, "no description", 60.2], ["2086", "I7N3G6G", "2086", 0, "no description", 60.2], ["2087", "I7N3G6G", "2087", 0, "no description", 60.2], ["2088", "I7N3G6G", "2088", 0, "no description", 60.2], ["2089", "I7N3G6G", "2089", 0, "no description", 60.2], ["2090", "I7N3G6G", "2090", 0, "no description", 60.2], ["2091", "I7N3G6G", "2091", 0, "no description", 60.2], ["2092", "I7N3G6G", "2092", 0, "no description", 60.2], ["2093", "I7N3G6G", "2093", 0, "no description", 60.2], ["2094", "I7N3G6G", "2094", 0, "no description", 60.2], ["2095", "I7N3G6G", "2095", 0, "no description", 60.2], ["2096", "I7N3G6G", "2096", 0, "no description", 60.2], ["2097", "I7N3G6G", "2097", 0, "no description", 60.2], ["2098", "I7N3G6G", "2098", 0, "no description", 60.2], ["2099", "I7N3G6G", "2099", 0, "no description", 60.2], ["2100", "I7N3G6G", "2100", 0, "no description", 60.2], ["2101", "I7N3G6G", "2101", 0, "no description", 60.2], ["2102", "I7N3G6G", "2102", 0, "no description", 60.2], ["2103", "I7N3G6G", "2103", 0, "no description", 60.2], ["2104", "I7N3G6G", "2104", 0, "no description", 60.2], ["2105", "I7N3G6G", "2105", 0, "no description", 60.2], ["2106", "I7N3G6G", "2106", 0, "no description", 60.2], ["2107", "I7N3G6G", "2107", 0, "no description", 60.2], ["2108", "I7N3G6G", "2108", 0, "no description", 60.2], ["2109", "I7N3G6G", "2109", 0, "no description", 60.2], ["2110", "I7N3G6G", "2110", 0, "no description", 60.2], ["2111", "I7N3G6G", "2111", 0, "no description", 60.2], ["2112", "I7N3G6G", "2112", 0, "no description", 60.2], ["2113", "I7N3G6G", "2113", 0, "no description", 60.2], ["2114", "I7N3G6G", "2114", 0, "no description", 60.2], ["2115", "I7N3G6G", "2115", 0, "no description", 60.2], ["2116", "I7N3G6G", "2116", 0, "no description", 60.2], ["2117", "I7N3G6G", "2117", 0, "no description", 60.2], ["2118", "I7N3G6G", "2118", 0, "no description", 60.2], ["2119", "I7N3G6G", "2119", 0, "no description", 60.2], ["2120", "I7N3G6G", "2120", 0, "no description", 60.2], ["2121", "I7N3G6G", "2121", 0, "no description", 60.2], ["2122", "I7N3G6G", "2122", 0, "no description", 60.2], ["2123", "I7N3G6G", "2123", 0, "no description", 60.2], ["2124", "I7N3G6G", "2124", 0, "no description", 60.2], ["2125", "I7N3G6G", "2125", 0, "no description", 60.2], ["2126", "I7N3G6G", "2126", 0, "no description", 60.2], ["2127", "I7N3G6G", "2127", 0, "no description", 60.2], ["2128", "I7N3G6G", "2128", 0, "no description", 60.2], ["2129", "I7N3G6G", "2129", 0, "no description", 60.2], ["2130", "I7N3G6G", "2130", 0, "no description", 60.2], ["2131", "I7N3G6G", "2131", 0, "no description", 60.2], ["2132", "I7N3G6G", "2132", 0, "no description", 60.2], ["2133", "I7N3G6G", "2133", 0, "no description", 60.2], ["2134", "I7N3G6G", "2134", 0, "no description", 60.2], ["2135", "I7N3G6G", "2135", 0, "no description", 60.2], ["2136", "I7N3G6G", "2136", 0, "no description", 60.2], ["2137", "I7N3G6G", "2137", 0, "no description", 60.2], ["2138", "I7N3G6G", "2138", 0, "no description", 60.2], ["2139", "I7N3G6G", "2139", 0, "no description", 60.2], ["2140", "I7N3G6G", "2140", 0, "no description", 60.2], ["2141", "I7N3G6G", "2141", 0, "no description", 60.2], ["2142", "I7N3G6G", "2142", 0, "no description", 60.2], ["2143", "I7N3G6G", "2143", 0, "no description", 60.2], ["2144", "I7N3G6G", "2144", 0, "no description", 60.2], ["2145", "I7N3G6G", "2145", 0, "no description", 60.2], ["2146", "I7N3G6G", "2146", 0, "no description", 60.2], ["2147", "I7N3G6G", "2147", 0, "no description", 60.2], ["2148", "I7N3G6G", "2148", 0, "no description", 60.2], ["2149", "I7N3G6G", "2149", 0, "no description", 60.2], ["2150", "I7N3G6G", "2150", 0, "no description", 60.2], ["2151", "I7N3G6G", "2151", 0, "no description", 60.2], ["2152", "I7N3G6G", "2152", 0, "no description", 60.2], ["2153", "I7N3G6G", "2153", 0, "no description", 60.2], ["2154", "I7N3G6G", "2154", 0, "no description", 60.2], ["2155", "I7N3G6G", "2155", 0, "no description", 60.2], ["2156", "I7N3G6G", "2156", 0, "no description", 60.2], ["2157", "I7N3G6G", "2157", 0, "no description", 60.2], ["2158", "I7N3G6G", "2158", 0, "no description", 60.2], ["2159", "I7N3G6G", "2159", 0, "no description", 60.2], ["2160", "I7N3G6G", "2160", 0, "no description", 60.2], ["2161", "I7N3G6G", "2161", 0, "no description", 60.2], ["2162", "I7N3G6G", "2162", 0, "no description", 60.2], ["2163", "I7N3G6G", "2163", 0, "no description", 60.2], ["2164", "I7N3G6G", "2164", 0, "no description", 60.2], ["2165", "I7N3G6G", "2165", 0, "no description", 60.2], ["2166", "I7N3G6G", "2166", 0, "no description", 60.2], ["2167", "I7N3G6G", "2167", 0, "no description", 60.2], ["2168", "I7N3G6G", "2168", 0, "no description", 60.2], ["2169", "I7N3G6G", "2169", 0, "no description", 60.2], ["2170", "I7N3G6G", "2170", 0, "no description", 60.2], ["2171", "I7N3G6G", "2171", 0, "no description", 60.2], ["2172", "I7N3G6G", "2172", 0, "no description", 60.2], ["2173", "I7N3G6G", "2173", 0, "no description", 60.2], ["2174", "I7N3G6G", "2174", 0, "no description", 60.2], ["2175", "I7N3G6G", "2175", 0, "no description", 60.2], ["2176", "I7N3G6G", "2176", 0, "no description", 60.2], ["2177", "I7N3G6G", "2177", 0, "no description", 60.2], ["2178", "I7N3G6G", "2178", 0, "no description", 60.2], ["2179", "I7N3G6G", "2179", 0, "no description", 60.2], ["2180", "I7N3G6G", "2180", 0, "no description", 60.2], ["2181", "I7N3G6G", "2181", 0, "no description", 60.2], ["2182", "I7N3G6G", "2182", 0, "no description", 60.2], ["2183", "I7N3G6G", "2183", 0, "no description", 60.2], ["2184", "I7N3G6G", "2184", 0, "no description", 60.2], ["2185", "I7N3G6G", "2185", 0, "no description", 60.2], ["2186", "I7N3G6G", "2186", 0, "no description", 60.2], ["2187", "I7N3G6G", "2187", 0, "no description", 60.2], ["2188", "I7N3G6G", "2188", 0, "no description", 60.2], ["2189", "I7N3G6G", "2189", 0, "no description", 60.2], ["2190", "I7N3G6G", "2190", 0, "no description", 60.2], ["2191", "I7N3G6G", "2191", 0, "no description", 60.2], ["2192", "I7N3G6G", "2192", 0, "no description", 60.2], ["2193", "I7N3G6G", "2193", 0, "no description", 60.2], ["2194", "I7N3G6G", "2194", 0, "no description", 60.2], ["2195", "I7N3G6G", "2195", 0, "no description", 60.2], ["2196", "I7N3G6G", "2196", 0, "no description", 60.2], ["2197", "I7N3G6G", "2197", 0, "no description", 60.2], ["2198", "I7N3G6G", "2198", 0, "no description", 60.2], ["2199", "I7N3G6G", "2199", 0, "no description", 60.2], ["2200", "I7N3G6G", "2200", 0, "no description", 60.2], ["2201", "I7N3G6G", "2201", 0, "no description", 60.2], ["2202", "I7N3G6G", "2202", 0, "no description", 60.2], ["2203", "I7N3G6G", "2203", 0, "no description", 60.2], ["2204", "I7N3G6G", "2204", 0, "no description", 60.2], ["2205", "I7N3G6G", "2205", 0, "no description", 60.2], ["2206", "I7N3G6G", "2206", 0, "no description", 60.2], ["2207", "I7N3G6G", "2207", 0, "no description", 60.2], ["2208", "I7N3G6G", "2208", 0, "no description", 60.2], ["2209", "I7N3G6G", "2209", 0, "no description", 60.2], ["2210", "I7N3G6G", "2210", 0, "no description", 60.2], ["2211", "I7N3G6G", "2211", 0, "no description", 60.2], ["2212", "I7N3G6G", "2212", 0, "no description", 60.2], ["2213", "I7N3G6G", "2213", 0, "no description", 60.2], ["2214", "I7N3G6G", "2214", 0, "no description", 60.2], ["2215", "I7N3G6G", "2215", 0, "no description", 60.2], ["2216", "I7N3G6G", "2216", 0, "no description", 60.2], ["2217", "I7N3G6G", "2217", 0, "no description", 60.2], ["2218", "I7N3G6G", "2218", 0, "no description", 60.2], ["2219", "I7N3G6G", "2219", 0, "no description", 60.2], ["2220", "I7N3G6G", "2220", 0, "no description", 60.2], ["2221", "I7N3G6G", "2221", 0, "no description", 60.2], ["2222", "I7N3G6G", "2222", 0, "no description", 60.2], ["2223", "I7N3G6G", "2223", 0, "no description", 60.2], ["2224", "I7N3G6G", "2224", 0, "no description", 60.2], ["2225", "I7N3G6G", "2225", 0, "no description", 60.2], ["2226", "I7N3G6G", "2226", 0, "no description", 60.2]] \ No newline at end of file diff --git a/packages/itmat-job-executor/test/testFiles/JSONCurator_error1.json b/packages/itmat-job-executor/test/testFiles/JSONCurator_error1.json deleted file mode 100644 index e4c702f12..000000000 --- a/packages/itmat-job-executor/test/testFiles/JSONCurator_error1.json +++ /dev/null @@ -1 +0,0 @@ -[["ID", "SubjectID", "VisitID", "Sex", "Description", "Weight", "UNKNOWN"], ["1", "I7N3G6G", "1", 0, "no description", 60.2, "random"], ["2", "I7N3G6G", "2", 0, "no description", 60.2, "random"], ["3", "I7N3G6G", "3", 0, "no description", 60.2, "random"], ["4", "I7N3G6G", "4", 0, "no description", 60.2, "random"], ["5", "I7N3G6G", "5", 0, "no description", 60.2, "random"], ["6", "I7N3G6G", "6", 0, "no description", 60.2, "random"], ["7", "I7N3G6G", "7", 0, "no description", 60.2, "random"], ["8", "I7N3G6G", "8", 0, "no description", 60.2, "random"], ["9", "I7N3G6G", "9", 0, "no description", 60.2, "random"], ["10", "I7N3G6G", "10", 0, "no description", 60.2, "random"], ["11", "I7N3G6G", "11", 0, "no description", 60.2, "random"], ["12", "I7N3G6G", "12", 0, "no description", 60.2, "random"], ["13", "I7N3G6G", "13", 0, "no description", 60.2, "random"], ["14", "I7N3G6G", "14", 0, "no description", 60.2, "random"], ["15", "I7N3G6G", "15", 0, "no description", 60.2, "random"], ["16", "I7N3G6G", "16", 0, "no description", 60.2, "random"], ["17", "I7N3G6G", "17", 0, "no description", 60.2, "random"], ["18", "I7N3G6G", "18", 0, "no description", 60.2, "random"], ["19", "I7N3G6G", "19", 0, "no description", 60.2, "random"], ["20", "I7N3G6G", "20", 0, "no description", 60.2, "random"], ["21", "I7N3G6G", "21", 0, "no description", 60.2, "random"], ["22", "I7N3G6G", "22", 0, "no description", 60.2, "random"], ["23", "I7N3G6G", "23", 0, "no description", 60.2, "random"], ["24", "I7N3G6G", "24", 0, "no description", 60.2, "random"], ["25", "I7N3G6G", "25", 0, "no description", 60.2, "random"], ["26", "I7N3G6G", "26", 0, "no description", 60.2, "random"], ["27", "I7N3G6G", "27", 0, "no description", 60.2, "random"], ["28", "I7N3G6G", "28", 0, "no description", 60.2, "random"], ["29", "I7N3G6G", "29", 0, "no description", 60.2, "random"], ["30", "I7N3G6G", "30", 0, "no description", 60.2, "random"], ["31", "I7N3G6G", "31", 0, "no description", 60.2, "random"], ["32", "I7N3G6G", "32", 0, "no description", 60.2, "random"], ["33", "I7N3G6G", "33", 0, "no description", 60.2, "random"], ["34", "I7N3G6G", "34", 0, "no description", 60.2, "random"], ["35", "I7N3G6G", "35", 0, "no description", 60.2, "random"], ["36", "I7N3G6G", "36", 0, "no description", 60.2, "random"], ["37", "I7N3G6G", "37", 0, "no description", 60.2, "random"], ["38", "I7N3G6G", "38", 0, "no description", 60.2, "random"], ["39", "I7N3G6G", "39", 0, "no description", 60.2, "random"], ["40", "I7N3G6G", "40", 0, "no description", 60.2, "random"], ["41", "I7N3G6G", "41", 0, "no description", 60.2, "random"], ["42", "I7N3G6G", "42", 0, "no description", 60.2, "random"], ["43", "I7N3G6G", "43", 0, "no description", 60.2, "random"], ["44", "I7N3G6G", "44", 0, "no description", 60.2, "random"], ["45", "I7N3G6G", "45", 0, "no description", 60.2, "random"], ["46", "I7N3G6G", "46", 0, "no description", 60.2, "random"], ["47", "I7N3G6G", "47", 0, "no description", 60.2, "random"], ["48", "I7N3G6G", "48", 0, "no description", 60.2, "random"], ["49", "I7N3G6G", "49", 0, "no description", 60.2, "random"], ["50", "I7N3G6G", "50", 0, "no description", 60.2, "random"], ["51", "I7N3G6G", "51", 0, "no description", 60.2, "random"], ["52", "I7N3G6G", "52", 0, "no description", 60.2, "random"], ["53", "I7N3G6G", "53", 0, "no description", 60.2, "random"], ["54", "I7N3G6G", "54", 0, "no description", 60.2, "random"], ["55", "I7N3G6G", "55", 0, "no description", 60.2, "random"], ["56", "I7N3G6G", "56", 0, "no description", 60.2, "random"], ["57", "I7N3G6G", "57", 0, "no description", 60.2, "random"], ["58", "I7N3G6G", "58", 0, "no description", 60.2, "random"], ["59", "I7N3G6G", "59", 0, "no description", 60.2, "random"], ["60", "I7N3G6G", "60", 0, "no description", 60.2, "random"], ["61", "I7N3G6G", "61", 0, "no description", 60.2, "random"], ["62", "I7N3G6G", "62", 0, "no description", 60.2, "random"], ["63", "I7N3G6G", "63", 0, "no description", 60.2, "random"], ["64", "I7N3G6G", "64", 0, "no description", 60.2, "random"], ["65", "I7N3G6G", "65", 0, "no description", 60.2, "random"], ["66", "I7N3G6G", "66", 0, "no description", 60.2, "random"], ["67", "I7N3G6G", "67", 0, "no description", 60.2, "random"], ["68", "I7N3G6G", "68", 0, "no description", 60.2, "random"], ["69", "I7N3G6G", "69", 0, "no description", 60.2, "random"], ["70", "I7N3G6G", "70", 0, "no description", 60.2, "random"], ["71", "I7N3G6G", "71", 0, "no description", 60.2, "random"], ["72", "I7N3G6G", "72", 0, "no description", 60.2, "random"], ["73", "I7N3G6G", "73", 0, "no description", 60.2, "random"], ["74", "I7N3G6G", "74", 0, "no description", 60.2, "random"], ["75", "I7N3G6G", "75", 0, "no description", 60.2, "random"], ["76", "I7N3G6G", "76", 0, "no description", 60.2, "random"], ["77", "I7N3G6G", "77", 0, "no description", 60.2, "random"], ["78", "I7N3G6G", "78", 0, "no description", 60.2, "random"], ["79", "I7N3G6G", "79", 0, "no description", 60.2, "random"], ["80", "I7N3G6G", "80", 0, "no description", 60.2, "random"], ["81", "I7N3G6G", "81", 0, "no description", 60.2, "random"], ["82", "I7N3G6G", "82", 0, "no description", 60.2, "random"], ["83", "I7N3G6G", "83", 0, "no description", 60.2, "random"], ["84", "I7N3G6G", "84", 0, "no description", 60.2, "random"], ["85", "I7N3G6G", "85", 0, "no description", 60.2, "random"], ["86", "I7N3G6G", "86", 0, "no description", 60.2, "random"], ["87", "I7N3G6G", "87", 0, "no description", 60.2, "random"], ["88", "I7N3G6G", "88", 0, "no description", 60.2, "random"], ["89", "I7N3G6G", "89", 0, "no description", 60.2, "random"], ["90", "I7N3G6G", "90", 0, "no description", 60.2, "random"], ["91", "I7N3G6G", "91", 0, "no description", 60.2, "random"], ["92", "I7N3G6G", "92", 0, "no description", 60.2, "random"], ["93", "I7N3G6G", "93", 0, "no description", 60.2, "random"], ["94", "I7N3G6G", "94", 0, "no description", 60.2, "random"], ["95", "I7N3G6G", "95", 0, "no description", 60.2, "random"], ["96", "I7N3G6G", "96", 0, "no description", 60.2, "random"], ["97", "I7N3G6G", "97", 0, "no description", 60.2, "random"], ["98", "I7N3G6G", "98", 0, "no description", 60.2, "random"], ["99", "I7N3G6G", "99", 0, "no description", 60.2, "random"], ["100", "I7N3G6G", "100", 0, "no description", 60.2, "random"], ["101", "I7N3G6G", "101", 0, "no description", 60.2, "random"], ["102", "I7N3G6G", "102", 0, "no description", 60.2, "random"], ["103", "I7N3G6G", "103", 0, "no description", 60.2, "random"], ["104", "I7N3G6G", "104", 0, "no description", 60.2, "random"], ["105", "I7N3G6G", "105", 0, "no description", 60.2, "random"], ["106", "I7N3G6G", "106", 0, "no description", 60.2, "random"], ["107", "I7N3G6G", "107", 0, "no description", 60.2, "random"], ["108", "I7N3G6G", "108", 0, "no description", 60.2, "random"], ["109", "I7N3G6G", "109", 0, "no description", 60.2, "random"], ["110", "I7N3G6G", "110", 0, "no description", 60.2, "random"], ["111", "I7N3G6G", "111", 0, "no description", 60.2, "random"], ["112", "I7N3G6G", "112", 0, "no description", 60.2, "random"], ["113", "I7N3G6G", "113", 0, "no description", 60.2, "random"], ["114", "I7N3G6G", "114", 0, "no description", 60.2, "random"], ["115", "I7N3G6G", "115", 0, "no description", 60.2, "random"], ["116", "I7N3G6G", "116", 0, "no description", 60.2, "random"], ["117", "I7N3G6G", "117", 0, "no description", 60.2, "random"], ["118", "I7N3G6G", "118", 0, "no description", 60.2, "random"], ["119", "I7N3G6G", "119", 0, "no description", 60.2, "random"], ["120", "I7N3G6G", "120", 0, "no description", 60.2, "random"], ["121", "I7N3G6G", "121", 0, "no description", 60.2, "random"], ["122", "I7N3G6G", "122", 0, "no description", 60.2, "random"], ["123", "I7N3G6G", "123", 0, "no description", 60.2, "random"], ["124", "I7N3G6G", "124", 0, "no description", 60.2, "random"], ["125", "I7N3G6G", "125", 0, "no description", 60.2, "random"], ["126", "I7N3G6G", "126", 0, "no description", 60.2, "random"], ["127", "I7N3G6G", "127", 0, "no description", 60.2, "random"], ["128", "I7N3G6G", "128", 0, "no description", 60.2, "random"], ["129", "I7N3G6G", "129", 0, "no description", 60.2, "random"], ["130", "I7N3G6G", "130", 0, "no description", 60.2, "random"], ["131", "I7N3G6G", "131", 0, "no description", 60.2, "random"], ["132", "I7N3G6G", "132", 0, "no description", 60.2, "random"], ["133", "I7N3G6G", "133", 0, "no description", 60.2, "random"], ["134", "I7N3G6G", "134", 0, "no description", 60.2, "random"], ["135", "I7N3G6G", "135", 0, "no description", 60.2, "random"], ["136", "I7N3G6G", "136", 0, "no description", 60.2, "random"], ["137", "I7N3G6G", "137", 0, "no description", 60.2, "random"], ["138", "I7N3G6G", "138", 0, "no description", 60.2, "random"], ["139", "I7N3G6G", "139", 0, "no description", 60.2, "random"], ["140", "I7N3G6G", "140", 0, "no description", 60.2, "random"], ["141", "I7N3G6G", "141", 0, "no description", 60.2, "random"], ["142", "I7N3G6G", "142", 0, "no description", 60.2, "random"], ["143", "I7N3G6G", "143", 0, "no description", 60.2, "random"], ["144", "I7N3G6G", "144", 0, "no description", 60.2, "random"], ["145", "I7N3G6G", "145", 0, "no description", 60.2, "random"], ["146", "I7N3G6G", "146", 0, "no description", 60.2, "random"], ["147", "I7N3G6G", "147", 0, "no description", 60.2, "random"], ["148", "I7N3G6G", "148", 0, "no description", 60.2, "random"], ["149", "I7N3G6G", "149", 0, "no description", 60.2, "random"], ["150", "I7N3G6G", "150", 0, "no description", 60.2, "random"], ["151", "I7N3G6G", "151", 0, "no description", 60.2, "random"], ["152", "I7N3G6G", "152", 0, "no description", 60.2, "random"], ["153", "I7N3G6G", "153", 0, "no description", 60.2, "random"], ["154", "I7N3G6G", "154", 0, "no description", 60.2, "random"], ["155", "I7N3G6G", "155", 0, "no description", 60.2, "random"], ["156", "I7N3G6G", "156", 0, "no description", 60.2, "random"], ["157", "I7N3G6G", "157", 0, "no description", 60.2, "random"], ["158", "I7N3G6G", "158", 0, "no description", 60.2, "random"], ["159", "I7N3G6G", "159", 0, "no description", 60.2, "random"], ["160", "I7N3G6G", "160", 0, "no description", 60.2, "random"], ["161", "I7N3G6G", "161", 0, "no description", 60.2, "random"], ["162", "I7N3G6G", "162", 0, "no description", 60.2, "random"], ["163", "I7N3G6G", "163", 0, "no description", 60.2, "random"], ["164", "I7N3G6G", "164", 0, "no description", 60.2, "random"], ["165", "I7N3G6G", "165", 0, "no description", 60.2, "random"], ["166", "I7N3G6G", "166", 0, "no description", 60.2, "random"], ["167", "I7N3G6G", "167", 0, "no description", 60.2, "random"], ["168", "I7N3G6G", "168", 0, "no description", 60.2, "random"], ["169", "I7N3G6G", "169", 0, "no description", 60.2, "random"], ["170", "I7N3G6G", "170", 0, "no description", 60.2, "random"], ["171", "I7N3G6G", "171", 0, "no description", 60.2, "random"], ["172", "I7N3G6G", "172", 0, "no description", 60.2, "random"], ["173", "I7N3G6G", "173", 0, "no description", 60.2, "random"], ["174", "I7N3G6G", "174", 0, "no description", 60.2, "random"], ["175", "I7N3G6G", "175", 0, "no description", 60.2, "random"], ["176", "I7N3G6G", "176", 0, "no description", 60.2, "random"], ["177", "I7N3G6G", "177", 0, "no description", 60.2, "random"], ["178", "I7N3G6G", "178", 0, "no description", 60.2, "random"], ["179", "I7N3G6G", "179", 0, "no description", 60.2, "random"], ["180", "I7N3G6G", "180", 0, "no description", 60.2, "random"], ["181", "I7N3G6G", "181", 0, "no description", 60.2, "random"], ["182", "I7N3G6G", "182", 0, "no description", 60.2, "random"], ["183", "I7N3G6G", "183", 0, "no description", 60.2, "random"], ["184", "I7N3G6G", "184", 0, "no description", 60.2, "random"], ["185", "I7N3G6G", "185", 0, "no description", 60.2, "random"], ["186", "I7N3G6G", "186", 0, "no description", 60.2, "random"], ["187", "I7N3G6G", "187", 0, "no description", 60.2, "random"], ["188", "I7N3G6G", "188", 0, "no description", 60.2, "random"], ["189", "I7N3G6G", "189", 0, "no description", 60.2, "random"], ["190", "I7N3G6G", "190", 0, "no description", 60.2, "random"], ["191", "I7N3G6G", "191", 0, "no description", 60.2, "random"], ["192", "I7N3G6G", "192", 0, "no description", 60.2, "random"], ["193", "I7N3G6G", "193", 0, "no description", 60.2, "random"], ["194", "I7N3G6G", "194", 0, "no description", 60.2, "random"], ["195", "I7N3G6G", "195", 0, "no description", 60.2, "random"], ["196", "I7N3G6G", "196", 0, "no description", 60.2, "random"], ["197", "I7N3G6G", "197", 0, "no description", 60.2, "random"], ["198", "I7N3G6G", "198", 0, "no description", 60.2, "random"], ["199", "I7N3G6G", "199", 0, "no description", 60.2, "random"], ["200", "I7N3G6G", "200", 0, "no description", 60.2, "random"], ["201", "I7N3G6G", "201", 0, "no description", 60.2, "random"], ["202", "I7N3G6G", "202", 0, "no description", 60.2, "random"], ["203", "I7N3G6G", "203", 0, "no description", 60.2, "random"], ["204", "I7N3G6G", "204", 0, "no description", 60.2, "random"], ["205", "I7N3G6G", "205", 0, "no description", 60.2, "random"], ["206", "I7N3G6G", "206", 0, "no description", 60.2, "random"], ["207", "I7N3G6G", "207", 0, "no description", 60.2, "random"], ["208", "I7N3G6G", "208", 0, "no description", 60.2, "random"], ["209", "I7N3G6G", "209", 0, "no description", 60.2, "random"], ["210", "I7N3G6G", "210", 0, "no description", 60.2, "random"], ["211", "I7N3G6G", "211", 0, "no description", 60.2, "random"], ["212", "I7N3G6G", "212", 0, "no description", 60.2, "random"], ["213", "I7N3G6G", "213", 0, "no description", 60.2, "random"], ["214", "I7N3G6G", "214", 0, "no description", 60.2, "random"], ["215", "I7N3G6G", "215", 0, "no description", 60.2, "random"], ["216", "I7N3G6G", "216", 0, "no description", 60.2, "random"], ["217", "I7N3G6G", "217", 0, "no description", 60.2, "random"], ["218", "I7N3G6G", "218", 0, "no description", 60.2, "random"], ["219", "I7N3G6G", "219", 0, "no description", 60.2, "random"], ["220", "I7N3G6G", "220", 0, "no description", 60.2, "random"], ["221", "I7N3G6G", "221", 0, "no description", 60.2, "random"], ["222", "I7N3G6G", "222", 0, "no description", 60.2, "random"], ["223", "I7N3G6G", "223", 0, "no description", 60.2, "random"], ["224", "I7N3G6G", "224", 0, "no description", 60.2, "random"], ["225", "I7N3G6G", "225", 0, "no description", 60.2, "random"], ["226", "I7N3G6G", "226", 0, "no description", 60.2, "random"], ["227", "I7N3G6G", "227", 0, "no description", 60.2, "random"], ["228", "I7N3G6G", "228", 0, "no description", 60.2, "random"], ["229", "I7N3G6G", "229", 0, "no description", 60.2, "random"], ["230", "I7N3G6G", "230", 0, "no description", 60.2, "random"], ["231", "I7N3G6G", "231", 0, "no description", 60.2, "random"], ["232", "I7N3G6G", "232", 0, "no description", 60.2, "random"], ["233", "I7N3G6G", "233", 0, "no description", 60.2, "random"], ["234", "I7N3G6G", "234", 0, "no description", 60.2, "random"], ["235", "I7N3G6G", "235", 0, "no description", 60.2, "random"], ["236", "I7N3G6G", "236", 0, "no description", 60.2, "random"], ["237", "I7N3G6G", "237", 0, "no description", 60.2, "random"], ["238", "I7N3G6G", "238", 0, "no description", 60.2, "random"], ["239", "I7N3G6G", "239", 0, "no description", 60.2, "random"], ["240", "I7N3G6G", "240", 0, "no description", 60.2, "random"], ["241", "I7N3G6G", "241", 0, "no description", 60.2, "random"], ["242", "I7N3G6G", "242", 0, "no description", 60.2, "random"], ["243", "I7N3G6G", "243", 0, "no description", 60.2, "random"], ["244", "I7N3G6G", "244", 0, "no description", 60.2, "random"], ["245", "I7N3G6G", "245", 0, "no description", 60.2, "random"], ["246", "I7N3G6G", "246", 0, "no description", 60.2, "random"], ["247", "I7N3G6G", "247", 0, "no description", 60.2, "random"], ["248", "I7N3G6G", "248", 0, "no description", 60.2, "random"], ["249", "I7N3G6G", "249", 0, "no description", 60.2, "random"], ["250", "I7N3G6G", "250", 0, "no description", 60.2, "random"], ["251", "I7N3G6G", "251", 0, "no description", 60.2, "random"], ["252", "I7N3G6G", "252", 0, "no description", 60.2, "random"], ["253", "I7N3G6G", "253", 0, "no description", 60.2, "random"], ["254", "I7N3G6G", "254", 0, "no description", 60.2, "random"], ["255", "I7N3G6G", "255", 0, "no description", 60.2, "random"], ["256", "I7N3G6G", "256", 0, "no description", 60.2, "random"], ["257", "I7N3G6G", "257", 0, "no description", 60.2, "random"], ["258", "I7N3G6G", "258", 0, "no description", 60.2, "random"], ["259", "I7N3G6G", "259", 0, "no description", 60.2, "random"], ["260", "I7N3G6G", "260", 0, "no description", 60.2, "random"], ["261", "I7N3G6G", "261", 0, "no description", 60.2, "random"], ["262", "I7N3G6G", "262", 0, "no description", 60.2, "random"], ["263", "I7N3G6G", "263", 0, "no description", 60.2, "random"], ["264", "I7N3G6G", "264", 0, "no description", 60.2, "random"], ["265", "I7N3G6G", "265", 0, "no description", 60.2, "random"], ["266", "I7N3G6G", "266", 0, "no description", 60.2, "random"], ["267", "I7N3G6G", "267", 0, "no description", 60.2, "random"], ["268", "I7N3G6G", "268", 0, "no description", 60.2, "random"], ["269", "I7N3G6G", "269", 0, "no description", 60.2, "random"], ["270", "I7N3G6G", "270", 0, "no description", 60.2, "random"], ["271", "I7N3G6G", "271", 0, "no description", 60.2, "random"], ["272", "I7N3G6G", "272", 0, "no description", 60.2, "random"], ["273", "I7N3G6G", "273", 0, "no description", 60.2, "random"], ["274", "I7N3G6G", "274", 0, "no description", 60.2, "random"], ["275", "I7N3G6G", "275", 0, "no description", 60.2, "random"], ["276", "I7N3G6G", "276", 0, "no description", 60.2, "random"], ["277", "I7N3G6G", "277", 0, "no description", 60.2, "random"], ["278", "I7N3G6G", "278", 0, "no description", 60.2, "random"], ["279", "I7N3G6G", "279", 0, "no description", 60.2, "random"], ["280", "I7N3G6G", "280", 0, "no description", 60.2, "random"], ["281", "I7N3G6G", "281", 0, "no description", 60.2, "random"], ["282", "I7N3G6G", "282", 0, "no description", 60.2, "random"], ["283", "I7N3G6G", "283", 0, "no description", 60.2, "random"], ["284", "I7N3G6G", "284", 0, "no description", 60.2, "random"], ["285", "I7N3G6G", "285", 0, "no description", 60.2, "random"], ["286", "I7N3G6G", "286", 0, "no description", 60.2, "random"], ["287", "I7N3G6G", "287", 0, "no description", 60.2, "random"], ["288", "I7N3G6G", "288", 0, "no description", 60.2, "random"], ["289", "I7N3G6G", "289", 0, "no description", 60.2, "random"], ["290", "I7N3G6G", "290", 0, "no description", 60.2, "random"], ["291", "I7N3G6G", "291", 0, "no description", 60.2, "random"], ["292", "I7N3G6G", "292", 0, "no description", 60.2, "random"], ["293", "I7N3G6G", "293", 0, "no description", 60.2, "random"], ["294", "I7N3G6G", "294", 0, "no description", 60.2, "random"], ["295", "I7N3G6G", "295", 0, "no description", 60.2, "random"], ["296", "I7N3G6G", "296", 0, "no description", 60.2, "random"], ["297", "I7N3G6G", "297", 0, "no description", 60.2, "random"], ["298", "I7N3G6G", "298", 0, "no description", 60.2, "random"], ["299", "I7N3G6G", "299", 0, "no description", 60.2, "random"], ["300", "I7N3G6G", "300", 0, "no description", 60.2, "random"], ["301", "I7N3G6G", "301", 0, "no description", 60.2, "random"], ["302", "I7N3G6G", "302", 0, "no description", 60.2, "random"], ["303", "I7N3G6G", "303", 0, "no description", 60.2, "random"], ["304", "I7N3G6G", "304", 0, "no description", 60.2, "random"], ["305", "I7N3G6G", "305", 0, "no description", 60.2, "random"], ["306", "I7N3G6G", "306", 0, "no description", 60.2, "random"], ["307", "I7N3G6G", "307", 0, "no description", 60.2, "random"], ["308", "I7N3G6G", "308", 0, "no description", 60.2, "random"], ["309", "I7N3G6G", "309", 0, "no description", 60.2, "random"], ["310", "I7N3G6G", "310", 0, "no description", 60.2, "random"], ["311", "I7N3G6G", "311", 0, "no description", 60.2, "random"], ["312", "I7N3G6G", "312", 0, "no description", 60.2, "random"], ["313", "I7N3G6G", "313", 0, "no description", 60.2, "random"], ["314", "I7N3G6G", "314", 0, "no description", 60.2, "random"], ["315", "I7N3G6G", "315", 0, "no description", 60.2, "random"], ["316", "I7N3G6G", "316", 0, "no description", 60.2, "random"], ["317", "I7N3G6G", "317", 0, "no description", 60.2, "random"], ["318", "I7N3G6G", "318", 0, "no description", 60.2, "random"], ["319", "I7N3G6G", "319", 0, "no description", 60.2, "random"], ["320", "I7N3G6G", "320", 0, "no description", 60.2, "random"], ["321", "I7N3G6G", "321", 0, "no description", 60.2, "random"], ["322", "I7N3G6G", "322", 0, "no description", 60.2, "random"], ["323", "I7N3G6G", "323", 0, "no description", 60.2, "random"], ["324", "I7N3G6G", "324", 0, "no description", 60.2, "random"], ["325", "I7N3G6G", "325", 0, "no description", 60.2, "random"], ["326", "I7N3G6G", "326", 0, "no description", 60.2, "random"], ["327", "I7N3G6G", "327", 0, "no description", 60.2, "random"], ["328", "I7N3G6G", "328", 0, "no description", 60.2, "random"], ["329", "I7N3G6G", "329", 0, "no description", 60.2, "random"], ["330", "I7N3G6G", "330", 0, "no description", 60.2, "random"], ["331", "I7N3G6G", "331", 0, "no description", 60.2, "random"], ["332", "I7N3G6G", "332", 0, "no description", 60.2, "random"], ["333", "I7N3G6G", "333", 0, "no description", 60.2, "random"], ["334", "I7N3G6G", "334", 0, "no description", 60.2, "random"], ["335", "I7N3G6G", "335", 0, "no description", 60.2, "random"], ["336", "I7N3G6G", "336", 0, "no description", 60.2, "random"], ["337", "I7N3G6G", "337", 0, "no description", 60.2, "random"], ["338", "I7N3G6G", "338", 0, "no description", 60.2, "random"], ["339", "I7N3G6G", "339", 0, "no description", 60.2, "random"], ["340", "I7N3G6G", "340", 0, "no description", 60.2, "random"], ["341", "I7N3G6G", "341", 0, "no description", 60.2, "random"], ["342", "I7N3G6G", "342", 0, "no description", 60.2, "random"], ["343", "I7N3G6G", "343", 0, "no description", 60.2, "random"], ["344", "I7N3G6G", "344", 0, "no description", 60.2, "random"], ["345", "I7N3G6G", "345", 0, "no description", 60.2, "random"], ["346", "I7N3G6G", "346", 0, "no description", 60.2, "random"], ["347", "I7N3G6G", "347", 0, "no description", 60.2, "random"], ["348", "I7N3G6G", "348", 0, "no description", 60.2, "random"], ["349", "I7N3G6G", "349", 0, "no description", 60.2, "random"], ["350", "I7N3G6G", "350", 0, "no description", 60.2, "random"], ["351", "I7N3G6G", "351", 0, "no description", 60.2, "random"], ["352", "I7N3G6G", "352", 0, "no description", 60.2, "random"], ["353", "I7N3G6G", "353", 0, "no description", 60.2, "random"], ["354", "I7N3G6G", "354", 0, "no description", 60.2, "random"], ["355", "I7N3G6G", "355", 0, "no description", 60.2, "random"], ["356", "I7N3G6G", "356", 0, "no description", 60.2, "random"], ["357", "I7N3G6G", "357", 0, "no description", 60.2, "random"], ["358", "I7N3G6G", "358", 0, "no description", 60.2, "random"], ["359", "I7N3G6G", "359", 0, "no description", 60.2, "random"], ["360", "I7N3G6G", "360", 0, "no description", 60.2, "random"], ["361", "I7N3G6G", "361", 0, "no description", 60.2, "random"], ["362", "I7N3G6G", "362", 0, "no description", 60.2, "random"], ["363", "I7N3G6G", "363", 0, "no description", 60.2, "random"], ["364", "I7N3G6G", "364", 0, "no description", 60.2, "random"], ["365", "I7N3G6G", "365", 0, "no description", 60.2, "random"], ["366", "I7N3G6G", "366", 0, "no description", 60.2, "random"], ["367", "I7N3G6G", "367", 0, "no description", 60.2, "random"], ["368", "I7N3G6G", "368", 0, "no description", 60.2, "random"], ["369", "I7N3G6G", "369", 0, "no description", 60.2, "random"], ["370", "I7N3G6G", "370", 0, "no description", 60.2, "random"], ["371", "I7N3G6G", "371", 0, "no description", 60.2, "random"], ["372", "I7N3G6G", "372", 0, "no description", 60.2, "random"], ["373", "I7N3G6G", "373", 0, "no description", 60.2, "random"], ["374", "I7N3G6G", "374", 0, "no description", 60.2, "random"], ["375", "I7N3G6G", "375", 0, "no description", 60.2, "random"], ["376", "I7N3G6G", "376", 0, "no description", 60.2, "random"], ["377", "I7N3G6G", "377", 0, "no description", 60.2, "random"], ["378", "I7N3G6G", "378", 0, "no description", 60.2, "random"], ["379", "I7N3G6G", "379", 0, "no description", 60.2, "random"], ["380", "I7N3G6G", "380", 0, "no description", 60.2, "random"], ["381", "I7N3G6G", "381", 0, "no description", 60.2, "random"], ["382", "I7N3G6G", "382", 0, "no description", 60.2, "random"], ["383", "I7N3G6G", "383", 0, "no description", 60.2, "random"], ["384", "I7N3G6G", "384", 0, "no description", 60.2, "random"], ["385", "I7N3G6G", "385", 0, "no description", 60.2, "random"], ["386", "I7N3G6G", "386", 0, "no description", 60.2, "random"], ["387", "I7N3G6G", "387", 0, "no description", 60.2, "random"], ["388", "I7N3G6G", "388", 0, "no description", 60.2, "random"], ["389", "I7N3G6G", "389", 0, "no description", 60.2, "random"], ["390", "I7N3G6G", "390", 0, "no description", 60.2, "random"], ["391", "I7N3G6G", "391", 0, "no description", 60.2, "random"], ["392", "I7N3G6G", "392", 0, "no description", 60.2, "random"], ["393", "I7N3G6G", "393", 0, "no description", 60.2, "random"], ["394", "I7N3G6G", "394", 0, "no description", 60.2, "random"], ["395", "I7N3G6G", "395", 0, "no description", 60.2, "random"], ["396", "I7N3G6G", "396", 0, "no description", 60.2, "random"], ["397", "I7N3G6G", "397", 0, "no description", 60.2, "random"], ["398", "I7N3G6G", "398", 0, "no description", 60.2, "random"], ["399", "I7N3G6G", "399", 0, "no description", 60.2, "random"], ["400", "I7N3G6G", "400", 0, "no description", 60.2, "random"], ["401", "I7N3G6G", "401", 0, "no description", 60.2, "random"], ["402", "I7N3G6G", "402", 0, "no description", 60.2, "random"], ["403", "I7N3G6G", "403", 0, "no description", 60.2, "random"], ["404", "I7N3G6G", "404", 0, "no description", 60.2, "random"], ["405", "I7N3G6G", "405", 0, "no description", 60.2, "random"], ["406", "I7N3G6G", "406", 0, "no description", 60.2, "random"], ["407", "I7N3G6G", "407", 0, "no description", 60.2, "random"], ["408", "I7N3G6G", "408", 0, "no description", 60.2, "random"], ["409", "I7N3G6G", "409", 0, "no description", 60.2, "random"], ["410", "I7N3G6G", "410", 0, "no description", 60.2, "random"], ["411", "I7N3G6G", "411", 0, "no description", 60.2, "random"], ["412", "I7N3G6G", "412", 0, "no description", 60.2, "random"], ["413", "I7N3G6G", "413", 0, "no description", 60.2, "random"], ["414", "I7N3G6G", "414", 0, "no description", 60.2, "random"], ["415", "I7N3G6G", "415", 0, "no description", 60.2, "random"], ["416", "I7N3G6G", "416", 0, "no description", 60.2, "random"], ["417", "I7N3G6G", "417", 0, "no description", 60.2, "random"], ["418", "I7N3G6G", "418", 0, "no description", 60.2, "random"], ["419", "I7N3G6G", "419", 0, "no description", 60.2, "random"], ["420", "I7N3G6G", "420", 0, "no description", 60.2, "random"], ["421", "I7N3G6G", "421", 0, "no description", 60.2, "random"], ["422", "I7N3G6G", "422", 0, "no description", 60.2, "random"], ["423", "I7N3G6G", "423", 0, "no description", 60.2, "random"], ["424", "I7N3G6G", "424", 0, "no description", 60.2, "random"], ["425", "I7N3G6G", "425", 0, "no description", 60.2, "random"], ["426", "I7N3G6G", "426", 0, "no description", 60.2, "random"], ["427", "I7N3G6G", "427", 0, "no description", 60.2, "random"], ["428", "I7N3G6G", "428", 0, "no description", 60.2, "random"], ["429", "I7N3G6G", "429", 0, "no description", 60.2, "random"], ["430", "I7N3G6G", "430", 0, "no description", 60.2, "random"], ["431", "I7N3G6G", "431", 0, "no description", 60.2, "random"], ["432", "I7N3G6G", "432", 0, "no description", 60.2, "random"], ["433", "I7N3G6G", "433", 0, "no description", 60.2, "random"], ["434", "I7N3G6G", "434", 0, "no description", 60.2, "random"], ["435", "I7N3G6G", "435", 0, "no description", 60.2, "random"], ["436", "I7N3G6G", "436", 0, "no description", 60.2, "random"], ["437", "I7N3G6G", "437", 0, "no description", 60.2, "random"], ["438", "I7N3G6G", "438", 0, "no description", 60.2, "random"], ["439", "I7N3G6G", "439", 0, "no description", 60.2, "random"], ["440", "I7N3G6G", "440", 0, "no description", 60.2, "random"], ["441", "I7N3G6G", "441", 0, "no description", 60.2, "random"], ["442", "I7N3G6G", "442", 0, "no description", 60.2, "random"], ["443", "I7N3G6G", "443", 0, "no description", 60.2, "random"], ["444", "I7N3G6G", "444", 0, "no description", 60.2, "random"], ["445", "I7N3G6G", "445", 0, "no description", 60.2, "random"], ["446", "I7N3G6G", "446", 0, "no description", 60.2, "random"], ["447", "I7N3G6G", "447", 0, "no description", 60.2, "random"], ["448", "I7N3G6G", "448", 0, "no description", 60.2, "random"], ["449", "I7N3G6G", "449", 0, "no description", 60.2, "random"], ["450", "I7N3G6G", "450", 0, "no description", 60.2, "random"], ["451", "I7N3G6G", "451", 0, "no description", 60.2, "random"], ["452", "I7N3G6G", "452", 0, "no description", 60.2, "random"], ["453", "I7N3G6G", "453", 0, "no description", 60.2, "random"], ["454", "I7N3G6G", "454", 0, "no description", 60.2, "random"], ["455", "I7N3G6G", "455", 0, "no description", 60.2, "random"], ["456", "I7N3G6G", "456", 0, "no description", 60.2, "random"], ["457", "I7N3G6G", "457", 0, "no description", 60.2, "random"], ["458", "I7N3G6G", "458", 0, "no description", 60.2, "random"], ["459", "I7N3G6G", "459", 0, "no description", 60.2, "random"], ["460", "I7N3G6G", "460", 0, "no description", 60.2, "random"], ["461", "I7N3G6G", "461", 0, "no description", 60.2, "random"], ["462", "I7N3G6G", "462", 0, "no description", 60.2, "random"], ["463", "I7N3G6G", "463", 0, "no description", 60.2, "random"], ["464", "I7N3G6G", "464", 0, "no description", 60.2, "random"], ["465", "I7N3G6G", "465", 0, "no description", 60.2, "random"], ["466", "I7N3G6G", "466", 0, "no description", 60.2, "random"], ["467", "I7N3G6G", "467", 0, "no description", 60.2, "random"], ["468", "I7N3G6G", "468", 0, "no description", 60.2, "random"], ["469", "I7N3G6G", "469", 0, "no description", 60.2, "random"], ["470", "I7N3G6G", "470", 0, "no description", 60.2, "random"], ["471", "I7N3G6G", "471", 0, "no description", 60.2, "random"], ["472", "I7N3G6G", "472", 0, "no description", 60.2, "random"], ["473", "I7N3G6G", "473", 0, "no description", 60.2, "random"], ["474", "I7N3G6G", "474", 0, "no description", 60.2, "random"], ["475", "I7N3G6G", "475", 0, "no description", 60.2, "random"], ["476", "I7N3G6G", "476", 0, "no description", 60.2, "random"], ["477", "I7N3G6G", "477", 0, "no description", 60.2, "random"], ["478", "I7N3G6G", "478", 0, "no description", 60.2, "random"], ["479", "I7N3G6G", "479", 0, "no description", 60.2, "random"], ["480", "I7N3G6G", "480", 0, "no description", 60.2, "random"], ["481", "I7N3G6G", "481", 0, "no description", 60.2, "random"], ["482", "I7N3G6G", "482", 0, "no description", 60.2, "random"], ["483", "I7N3G6G", "483", 0, "no description", 60.2, "random"], ["484", "I7N3G6G", "484", 0, "no description", 60.2, "random"], ["485", "I7N3G6G", "485", 0, "no description", 60.2, "random"], ["486", "I7N3G6G", "486", 0, "no description", 60.2, "random"], ["487", "I7N3G6G", "487", 0, "no description", 60.2, "random"], ["488", "I7N3G6G", "488", 0, "no description", 60.2, "random"], ["489", "I7N3G6G", "489", 0, "no description", 60.2, "random"], ["490", "I7N3G6G", "490", 0, "no description", 60.2, "random"], ["491", "I7N3G6G", "491", 0, "no description", 60.2, "random"], ["492", "I7N3G6G", "492", 0, "no description", 60.2, "random"], ["493", "I7N3G6G", "493", 0, "no description", 60.2, "random"], ["494", "I7N3G6G", "494", 0, "no description", 60.2, "random"], ["495", "I7N3G6G", "495", 0, "no description", 60.2, "random"], ["496", "I7N3G6G", "496", 0, "no description", 60.2, "random"], ["497", "I7N3G6G", "497", 0, "no description", 60.2, "random"], ["498", "I7N3G6G", "498", 0, "no description", 60.2, "random"], ["499", "I7N3G6G", "499", 0, "no description", 60.2, "random"], ["500", "I7N3G6G", "500", 0, "no description", 60.2, "random"], ["501", "I7N3G6G", "501", 0, "no description", 60.2, "random"], ["502", "I7N3G6G", "502", 0, "no description", 60.2, "random"], ["503", "I7N3G6G", "503", 0, "no description", 60.2, "random"], ["504", "I7N3G6G", "504", 0, "no description", 60.2, "random"], ["505", "I7N3G6G", "505", 0, "no description", 60.2, "random"], ["506", "I7N3G6G", "506", 0, "no description", 60.2, "random"], ["507", "I7N3G6G", "507", 0, "no description", 60.2, "random"], ["508", "I7N3G6G", "508", 0, "no description", 60.2, "random"], ["509", "I7N3G6G", "509", 0, "no description", 60.2, "random"], ["510", "I7N3G6G", "510", 0, "no description", 60.2, "random"], ["511", "I7N3G6G", "511", 0, "no description", 60.2, "random"], ["512", "I7N3G6G", "512", 0, "no description", 60.2, "random"], ["513", "I7N3G6G", "513", 0, "no description", 60.2, "random"], ["514", "I7N3G6G", "514", 0, "no description", 60.2, "random"], ["515", "I7N3G6G", "515", 0, "no description", 60.2, "random"], ["516", "I7N3G6G", "516", 0, "no description", 60.2, "random"], ["517", "I7N3G6G", "517", 0, "no description", 60.2, "random"], ["518", "I7N3G6G", "518", 0, "no description", 60.2, "random"], ["519", "I7N3G6G", "519", 0, "no description", 60.2, "random"], ["520", "I7N3G6G", "520", 0, "no description", 60.2, "random"], ["521", "I7N3G6G", "521", 0, "no description", 60.2, "random"], ["522", "I7N3G6G", "522", 0, "no description", 60.2, "random"], ["523", "I7N3G6G", "523", 0, "no description", 60.2, "random"], ["524", "I7N3G6G", "524", 0, "no description", 60.2, "random"], ["525", "I7N3G6G", "525", 0, "no description", 60.2, "random"], ["526", "I7N3G6G", "526", 0, "no description", 60.2, "random"], ["527", "I7N3G6G", "527", 0, "no description", 60.2, "random"], ["528", "I7N3G6G", "528", 0, "no description", 60.2, "random"], ["529", "I7N3G6G", "529", 0, "no description", 60.2, "random"], ["530", "I7N3G6G", "530", 0, "no description", 60.2, "random"], ["531", "I7N3G6G", "531", 0, "no description", 60.2, "random"], ["532", "I7N3G6G", "532", 0, "no description", 60.2, "random"], ["533", "I7N3G6G", "533", 0, "no description", 60.2, "random"], ["534", "I7N3G6G", "534", 0, "no description", 60.2, "random"], ["535", "I7N3G6G", "535", 0, "no description", 60.2, "random"], ["536", "I7N3G6G", "536", 0, "no description", 60.2, "random"], ["537", "I7N3G6G", "537", 0, "no description", 60.2, "random"], ["538", "I7N3G6G", "538", 0, "no description", 60.2, "random"], ["539", "I7N3G6G", "539", 0, "no description", 60.2, "random"], ["540", "I7N3G6G", "540", 0, "no description", 60.2, "random"], ["541", "I7N3G6G", "541", 0, "no description", 60.2, "random"], ["542", "I7N3G6G", "542", 0, "no description", 60.2, "random"], ["543", "I7N3G6G", "543", 0, "no description", 60.2, "random"], ["544", "I7N3G6G", "544", 0, "no description", 60.2, "random"], ["545", "I7N3G6G", "545", 0, "no description", 60.2, "random"], ["546", "I7N3G6G", "546", 0, "no description", 60.2, "random"], ["547", "I7N3G6G", "547", 0, "no description", 60.2, "random"], ["548", "I7N3G6G", "548", 0, "no description", 60.2, "random"], ["549", "I7N3G6G", "549", 0, "no description", 60.2, "random"], ["550", "I7N3G6G", "550", 0, "no description", 60.2, "random"], ["551", "I7N3G6G", "551", 0, "no description", 60.2, "random"], ["552", "I7N3G6G", "552", 0, "no description", 60.2, "random"], ["553", "I7N3G6G", "553", 0, "no description", 60.2, "random"], ["554", "I7N3G6G", "554", 0, "no description", 60.2, "random"], ["555", "I7N3G6G", "555", 0, "no description", 60.2, "random"], ["556", "I7N3G6G", "556", 0, "no description", 60.2, "random"], ["557", "I7N3G6G", "557", 0, "no description", 60.2, "random"], ["558", "I7N3G6G", "558", 0, "no description", 60.2, "random"], ["559", "I7N3G6G", "559", 0, "no description", 60.2, "random"], ["560", "I7N3G6G", "560", 0, "no description", 60.2, "random"], ["561", "I7N3G6G", "561", 0, "no description", 60.2, "random"], ["562", "I7N3G6G", "562", 0, "no description", 60.2, "random"], ["563", "I7N3G6G", "563", 0, "no description", 60.2, "random"], ["564", "I7N3G6G", "564", 0, "no description", 60.2, "random"], ["565", "I7N3G6G", "565", 0, "no description", 60.2, "random"], ["566", "I7N3G6G", "566", 0, "no description", 60.2, "random"], ["567", "I7N3G6G", "567", 0, "no description", 60.2, "random"], ["568", "I7N3G6G", "568", 0, "no description", 60.2, "random"], ["569", "I7N3G6G", "569", 0, "no description", 60.2, "random"], ["570", "I7N3G6G", "570", 0, "no description", 60.2, "random"], ["571", "I7N3G6G", "571", 0, "no description", 60.2, "random"], ["572", "I7N3G6G", "572", 0, "no description", 60.2, "random"], ["573", "I7N3G6G", "573", 0, "no description", 60.2, "random"], ["574", "I7N3G6G", "574", 0, "no description", 60.2, "random"], ["575", "I7N3G6G", "575", 0, "no description", 60.2, "random"], ["576", "I7N3G6G", "576", 0, "no description", 60.2, "random"], ["577", "I7N3G6G", "577", 0, "no description", 60.2, "random"], ["578", "I7N3G6G", "578", 0, "no description", 60.2, "random"], ["579", "I7N3G6G", "579", 0, "no description", 60.2, "random"], ["580", "I7N3G6G", "580", 0, "no description", 60.2, "random"], ["581", "I7N3G6G", "581", 0, "no description", 60.2, "random"], ["582", "I7N3G6G", "582", 0, "no description", 60.2, "random"], ["583", "I7N3G6G", "583", 0, "no description", 60.2, "random"], ["584", "I7N3G6G", "584", 0, "no description", 60.2, "random"], ["585", "I7N3G6G", "585", 0, "no description", 60.2, "random"], ["586", "I7N3G6G", "586", 0, "no description", 60.2, "random"], ["587", "I7N3G6G", "587", 0, "no description", 60.2, "random"], ["588", "I7N3G6G", "588", 0, "no description", 60.2, "random"], ["589", "I7N3G6G", "589", 0, "no description", 60.2, "random"], ["590", "I7N3G6G", "590", 0, "no description", 60.2, "random"], ["591", "I7N3G6G", "591", 0, "no description", 60.2, "random"], ["592", "I7N3G6G", "592", 0, "no description", 60.2, "random"], ["593", "I7N3G6G", "593", 0, "no description", 60.2, "random"], ["594", "I7N3G6G", "594", 0, "no description", 60.2, "random"], ["595", "I7N3G6G", "595", 0, "no description", 60.2, "random"], ["596", "I7N3G6G", "596", 0, "no description", 60.2, "random"], ["597", "I7N3G6G", "597", 0, "no description", 60.2, "random"], ["598", "I7N3G6G", "598", 0, "no description", 60.2, "random"], ["599", "I7N3G6G", "599", 0, "no description", 60.2, "random"], ["600", "I7N3G6G", "600", 0, "no description", 60.2, "random"], ["601", "I7N3G6G", "601", 0, "no description", 60.2, "random"], ["602", "I7N3G6G", "602", 0, "no description", 60.2, "random"], ["603", "I7N3G6G", "603", 0, "no description", 60.2, "random"], ["604", "I7N3G6G", "604", 0, "no description", 60.2, "random"], ["605", "I7N3G6G", "605", 0, "no description", 60.2, "random"], ["606", "I7N3G6G", "606", 0, "no description", 60.2, "random"], ["607", "I7N3G6G", "607", 0, "no description", 60.2, "random"], ["608", "I7N3G6G", "608", 0, "no description", 60.2, "random"], ["609", "I7N3G6G", "609", 0, "no description", 60.2, "random"], ["610", "I7N3G6G", "610", 0, "no description", 60.2, "random"], ["611", "I7N3G6G", "611", 0, "no description", 60.2, "random"], ["612", "I7N3G6G", "612", 0, "no description", 60.2, "random"], ["613", "I7N3G6G", "613", 0, "no description", 60.2, "random"], ["614", "I7N3G6G", "614", 0, "no description", 60.2, "random"], ["615", "I7N3G6G", "615", 0, "no description", 60.2, "random"], ["616", "I7N3G6G", "616", 0, "no description", 60.2, "random"], ["617", "I7N3G6G", "617", 0, "no description", 60.2, "random"], ["618", "I7N3G6G", "618", 0, "no description", 60.2, "random"], ["619", "I7N3G6G", "619", 0, "no description", 60.2, "random"], ["620", "I7N3G6G", "620", 0, "no description", 60.2, "random"], ["621", "I7N3G6G", "621", 0, "no description", 60.2, "random"], ["622", "I7N3G6G", "622", 0, "no description", 60.2, "random"], ["623", "I7N3G6G", "623", 0, "no description", 60.2, "random"], ["624", "I7N3G6G", "624", 0, "no description", 60.2, "random"], ["625", "I7N3G6G", "625", 0, "no description", 60.2, "random"], ["626", "I7N3G6G", "626", 0, "no description", 60.2, "random"], ["627", "I7N3G6G", "627", 0, "no description", 60.2, "random"], ["628", "I7N3G6G", "628", 0, "no description", 60.2, "random"], ["629", "I7N3G6G", "629", 0, "no description", 60.2, "random"], ["630", "I7N3G6G", "630", 0, "no description", 60.2, "random"], ["631", "I7N3G6G", "631", 0, "no description", 60.2, "random"], ["632", "I7N3G6G", "632", 0, "no description", 60.2, "random"], ["633", "I7N3G6G", "633", 0, "no description", 60.2, "random"], ["634", "I7N3G6G", "634", 0, "no description", 60.2, "random"], ["635", "I7N3G6G", "635", 0, "no description", 60.2, "random"], ["636", "I7N3G6G", "636", 0, "no description", 60.2, "random"], ["637", "I7N3G6G", "637", 0, "no description", 60.2, "random"], ["638", "I7N3G6G", "638", 0, "no description", 60.2, "random"], ["639", "I7N3G6G", "639", 0, "no description", 60.2, "random"], ["640", "I7N3G6G", "640", 0, "no description", 60.2, "random"], ["641", "I7N3G6G", "641", 0, "no description", 60.2, "random"], ["642", "I7N3G6G", "642", 0, "no description", 60.2, "random"], ["643", "I7N3G6G", "643", 0, "no description", 60.2, "random"], ["644", "I7N3G6G", "644", 0, "no description", 60.2, "random"], ["645", "I7N3G6G", "645", 0, "no description", 60.2, "random"], ["646", "I7N3G6G", "646", 0, "no description", 60.2, "random"], ["647", "I7N3G6G", "647", 0, "no description", 60.2, "random"], ["648", "I7N3G6G", "648", 0, "no description", 60.2, "random"], ["649", "I7N3G6G", "649", 0, "no description", 60.2, "random"], ["650", "I7N3G6G", "650", 0, "no description", 60.2, "random"], ["651", "I7N3G6G", "651", 0, "no description", 60.2, "random"], ["652", "I7N3G6G", "652", 0, "no description", 60.2, "random"], ["653", "I7N3G6G", "653", 0, "no description", 60.2, "random"], ["654", "I7N3G6G", "654", 0, "no description", 60.2, "random"], ["655", "I7N3G6G", "655", 0, "no description", 60.2, "random"], ["656", "I7N3G6G", "656", 0, "no description", 60.2, "random"], ["657", "I7N3G6G", "657", 0, "no description", 60.2, "random"], ["658", "I7N3G6G", "658", 0, "no description", 60.2, "random"], ["659", "I7N3G6G", "659", 0, "no description", 60.2, "random"], ["660", "I7N3G6G", "660", 0, "no description", 60.2, "random"], ["661", "I7N3G6G", "661", 0, "no description", 60.2, "random"], ["662", "I7N3G6G", "662", 0, "no description", 60.2, "random"], ["663", "I7N3G6G", "663", 0, "no description", 60.2, "random"], ["664", "I7N3G6G", "664", 0, "no description", 60.2, "random"], ["665", "I7N3G6G", "665", 0, "no description", 60.2, "random"], ["666", "I7N3G6G", "666", 0, "no description", 60.2, "random"], ["667", "I7N3G6G", "667", 0, "no description", 60.2, "random"], ["668", "I7N3G6G", "668", 0, "no description", 60.2, "random"], ["669", "I7N3G6G", "669", 0, "no description", 60.2, "random"], ["670", "I7N3G6G", "670", 0, "no description", 60.2, "random"], ["671", "I7N3G6G", "671", 0, "no description", 60.2, "random"], ["672", "I7N3G6G", "672", 0, "no description", 60.2, "random"], ["673", "I7N3G6G", "673", 0, "no description", 60.2, "random"], ["674", "I7N3G6G", "674", 0, "no description", 60.2, "random"], ["675", "I7N3G6G", "675", 0, "no description", 60.2, "random"], ["676", "I7N3G6G", "676", 0, "no description", 60.2, "random"], ["677", "I7N3G6G", "677", 0, "no description", 60.2, "random"], ["678", "I7N3G6G", "678", 0, "no description", 60.2, "random"], ["679", "I7N3G6G", "679", 0, "no description", 60.2, "random"], ["680", "I7N3G6G", "680", 0, "no description", 60.2, "random"], ["681", "I7N3G6G", "681", 0, "no description", 60.2, "random"], ["682", "I7N3G6G", "682", 0, "no description", 60.2, "random"], ["683", "I7N3G6G", "683", 0, "no description", 60.2, "random"], ["684", "I7N3G6G", "684", 0, "no description", 60.2, "random"], ["685", "I7N3G6G", "685", 0, "no description", 60.2, "random"], ["686", "I7N3G6G", "686", 0, "no description", 60.2, "random"], ["687", "I7N3G6G", "687", 0, "no description", 60.2, "random"], ["688", "I7N3G6G", "688", 0, "no description", 60.2, "random"], ["689", "I7N3G6G", "689", 0, "no description", 60.2, "random"], ["690", "I7N3G6G", "690", 0, "no description", 60.2, "random"], ["691", "I7N3G6G", "691", 0, "no description", 60.2, "random"], ["692", "I7N3G6G", "692", 0, "no description", 60.2, "random"], ["693", "I7N3G6G", "693", 0, "no description", 60.2, "random"], ["694", "I7N3G6G", "694", 0, "no description", 60.2, "random"], ["695", "I7N3G6G", "695", 0, "no description", 60.2, "random"], ["696", "I7N3G6G", "696", 0, "no description", 60.2, "random"], ["697", "I7N3G6G", "697", 0, "no description", 60.2, "random"], ["698", "I7N3G6G", "698", 0, "no description", 60.2, "random"], ["699", "I7N3G6G", "699", 0, "no description", 60.2, "random"], ["700", "I7N3G6G", "700", 0, "no description", 60.2, "random"], ["701", "I7N3G6G", "701", 0, "no description", 60.2, "random"], ["702", "I7N3G6G", "702", 0, "no description", 60.2, "random"], ["703", "I7N3G6G", "703", 0, "no description", 60.2, "random"], ["704", "I7N3G6G", "704", 0, "no description", 60.2, "random"], ["705", "I7N3G6G", "705", 0, "no description", 60.2, "random"], ["706", "I7N3G6G", "706", 0, "no description", 60.2, "random"], ["707", "I7N3G6G", "707", 0, "no description", 60.2, "random"], ["708", "I7N3G6G", "708", 0, "no description", 60.2, "random"], ["709", "I7N3G6G", "709", 0, "no description", 60.2, "random"], ["710", "I7N3G6G", "710", 0, "no description", 60.2, "random"], ["711", "I7N3G6G", "711", 0, "no description", 60.2, "random"], ["712", "I7N3G6G", "712", 0, "no description", 60.2, "random"], ["713", "I7N3G6G", "713", 0, "no description", 60.2, "random"], ["714", "I7N3G6G", "714", 0, "no description", 60.2, "random"], ["715", "I7N3G6G", "715", 0, "no description", 60.2, "random"], ["716", "I7N3G6G", "716", 0, "no description", 60.2, "random"], ["717", "I7N3G6G", "717", 0, "no description", 60.2, "random"], ["718", "I7N3G6G", "718", 0, "no description", 60.2, "random"], ["719", "I7N3G6G", "719", 0, "no description", 60.2, "random"], ["720", "I7N3G6G", "720", 0, "no description", 60.2, "random"], ["721", "I7N3G6G", "721", 0, "no description", 60.2, "random"], ["722", "I7N3G6G", "722", 0, "no description", 60.2, "random"], ["723", "I7N3G6G", "723", 0, "no description", 60.2, "random"], ["724", "I7N3G6G", "724", 0, "no description", 60.2, "random"], ["725", "I7N3G6G", "725", 0, "no description", 60.2, "random"], ["726", "I7N3G6G", "726", 0, "no description", 60.2, "random"], ["727", "I7N3G6G", "727", 0, "no description", 60.2, "random"], ["728", "I7N3G6G", "728", 0, "no description", 60.2, "random"], ["729", "I7N3G6G", "729", 0, "no description", 60.2, "random"], ["730", "I7N3G6G", "730", 0, "no description", 60.2, "random"], ["731", "I7N3G6G", "731", 0, "no description", 60.2, "random"], ["732", "I7N3G6G", "732", 0, "no description", 60.2, "random"], ["733", "I7N3G6G", "733", 0, "no description", 60.2, "random"], ["734", "I7N3G6G", "734", 0, "no description", 60.2, "random"], ["735", "I7N3G6G", "735", 0, "no description", 60.2, "random"], ["736", "I7N3G6G", "736", 0, "no description", 60.2, "random"], ["737", "I7N3G6G", "737", 0, "no description", 60.2, "random"], ["738", "I7N3G6G", "738", 0, "no description", 60.2, "random"], ["739", "I7N3G6G", "739", 0, "no description", 60.2, "random"], ["740", "I7N3G6G", "740", 0, "no description", 60.2, "random"], ["741", "I7N3G6G", "741", 0, "no description", 60.2, "random"], ["742", "I7N3G6G", "742", 0, "no description", 60.2, "random"], ["743", "I7N3G6G", "743", 0, "no description", 60.2, "random"], ["744", "I7N3G6G", "744", 0, "no description", 60.2, "random"], ["745", "I7N3G6G", "745", 0, "no description", 60.2, "random"], ["746", "I7N3G6G", "746", 0, "no description", 60.2, "random"], ["747", "I7N3G6G", "747", 0, "no description", 60.2, "random"], ["748", "I7N3G6G", "748", 0, "no description", 60.2, "random"], ["749", "I7N3G6G", "749", 0, "no description", 60.2, "random"], ["750", "I7N3G6G", "750", 0, "no description", 60.2, "random"], ["751", "I7N3G6G", "751", 0, "no description", 60.2, "random"], ["752", "I7N3G6G", "752", 0, "no description", 60.2, "random"], ["753", "I7N3G6G", "753", 0, "no description", 60.2, "random"], ["754", "I7N3G6G", "754", 0, "no description", 60.2, "random"], ["755", "I7N3G6G", "755", 0, "no description", 60.2, "random"], ["756", "I7N3G6G", "756", 0, "no description", 60.2, "random"], ["757", "I7N3G6G", "757", 0, "no description", 60.2, "random"], ["758", "I7N3G6G", "758", 0, "no description", 60.2, "random"], ["759", "I7N3G6G", "759", 0, "no description", 60.2, "random"], ["760", "I7N3G6G", "760", 0, "no description", 60.2, "random"], ["761", "I7N3G6G", "761", 0, "no description", 60.2, "random"], ["762", "I7N3G6G", "762", 0, "no description", 60.2, "random"], ["763", "I7N3G6G", "763", 0, "no description", 60.2, "random"], ["764", "I7N3G6G", "764", 0, "no description", 60.2, "random"], ["765", "I7N3G6G", "765", 0, "no description", 60.2, "random"], ["766", "I7N3G6G", "766", 0, "no description", 60.2, "random"], ["767", "I7N3G6G", "767", 0, "no description", 60.2, "random"], ["768", "I7N3G6G", "768", 0, "no description", 60.2, "random"], ["769", "I7N3G6G", "769", 0, "no description", 60.2, "random"], ["770", "I7N3G6G", "770", 0, "no description", 60.2, "random"], ["771", "I7N3G6G", "771", 0, "no description", 60.2, "random"], ["772", "I7N3G6G", "772", 0, "no description", 60.2, "random"], ["773", "I7N3G6G", "773", 0, "no description", 60.2, "random"], ["774", "I7N3G6G", "774", 0, "no description", 60.2, "random"], ["775", "I7N3G6G", "775", 0, "no description", 60.2, "random"], ["776", "I7N3G6G", "776", 0, "no description", 60.2, "random"], ["777", "I7N3G6G", "777", 0, "no description", 60.2, "random"], ["778", "I7N3G6G", "778", 0, "no description", 60.2, "random"], ["779", "I7N3G6G", "779", 0, "no description", 60.2, "random"], ["780", "I7N3G6G", "780", 0, "no description", 60.2, "random"], ["781", "I7N3G6G", "781", 0, "no description", 60.2, "random"], ["782", "I7N3G6G", "782", 0, "no description", 60.2, "random"], ["783", "I7N3G6G", "783", 0, "no description", 60.2, "random"], ["784", "I7N3G6G", "784", 0, "no description", 60.2, "random"], ["785", "I7N3G6G", "785", 0, "no description", 60.2, "random"], ["786", "I7N3G6G", "786", 0, "no description", 60.2, "random"], ["787", "I7N3G6G", "787", 0, "no description", 60.2, "random"], ["788", "I7N3G6G", "788", 0, "no description", 60.2, "random"], ["789", "I7N3G6G", "789", 0, "no description", 60.2, "random"], ["790", "I7N3G6G", "790", 0, "no description", 60.2, "random"], ["791", "I7N3G6G", "791", 0, "no description", 60.2, "random"], ["792", "I7N3G6G", "792", 0, "no description", 60.2, "random"], ["793", "I7N3G6G", "793", 0, "no description", 60.2, "random"], ["794", "I7N3G6G", "794", 0, "no description", 60.2, "random"], ["795", "I7N3G6G", "795", 0, "no description", 60.2, "random"], ["796", "I7N3G6G", "796", 0, "no description", 60.2, "random"], ["797", "I7N3G6G", "797", 0, "no description", 60.2, "random"], ["798", "I7N3G6G", "798", 0, "no description", 60.2, "random"], ["799", "I7N3G6G", "799", 0, "no description", 60.2, "random"], ["800", "I7N3G6G", "800", 0, "no description", 60.2, "random"], ["801", "I7N3G6G", "801", 0, "no description", 60.2, "random"], ["802", "I7N3G6G", "802", 0, "no description", 60.2, "random"], ["803", "I7N3G6G", "803", 0, "no description", 60.2, "random"], ["804", "I7N3G6G", "804", 0, "no description", 60.2, "random"], ["805", "I7N3G6G", "805", 0, "no description", 60.2, "random"], ["806", "I7N3G6G", "806", 0, "no description", 60.2, "random"], ["807", "I7N3G6G", "807", 0, "no description", 60.2, "random"], ["808", "I7N3G6G", "808", 0, "no description", 60.2, "random"], ["809", "I7N3G6G", "809", 0, "no description", 60.2, "random"], ["810", "I7N3G6G", "810", 0, "no description", 60.2, "random"], ["811", "I7N3G6G", "811", 0, "no description", 60.2, "random"], ["812", "I7N3G6G", "812", 0, "no description", 60.2, "random"], ["813", "I7N3G6G", "813", 0, "no description", 60.2, "random"], ["814", "I7N3G6G", "814", 0, "no description", 60.2, "random"], ["815", "I7N3G6G", "815", 0, "no description", 60.2, "random"], ["816", "I7N3G6G", "816", 0, "no description", 60.2, "random"], ["817", "I7N3G6G", "817", 0, "no description", 60.2, "random"], ["818", "I7N3G6G", "818", 0, "no description", 60.2, "random"], ["819", "I7N3G6G", "819", 0, "no description", 60.2, "random"], ["820", "I7N3G6G", "820", 0, "no description", 60.2, "random"], ["821", "I7N3G6G", "821", 0, "no description", 60.2, "random"], ["822", "I7N3G6G", "822", 0, "no description", 60.2, "random"], ["823", "I7N3G6G", "823", 0, "no description", 60.2, "random"], ["824", "I7N3G6G", "824", 0, "no description", 60.2, "random"], ["825", "I7N3G6G", "825", 0, "no description", 60.2, "random"], ["826", "I7N3G6G", "826", 0, "no description", 60.2, "random"], ["827", "I7N3G6G", "827", 0, "no description", 60.2, "random"], ["828", "I7N3G6G", "828", 0, "no description", 60.2, "random"], ["829", "I7N3G6G", "829", 0, "no description", 60.2, "random"], ["830", "I7N3G6G", "830", 0, "no description", 60.2, "random"], ["831", "I7N3G6G", "831", 0, "no description", 60.2, "random"], ["832", "I7N3G6G", "832", 0, "no description", 60.2, "random"], ["833", "I7N3G6G", "833", 0, "no description", 60.2, "random"], ["834", "I7N3G6G", "834", 0, "no description", 60.2, "random"], ["835", "I7N3G6G", "835", 0, "no description", 60.2, "random"], ["836", "I7N3G6G", "836", 0, "no description", 60.2, "random"], ["837", "I7N3G6G", "837", 0, "no description", 60.2, "random"], ["838", "I7N3G6G", "838", 0, "no description", 60.2, "random"], ["839", "I7N3G6G", "839", 0, "no description", 60.2, "random"], ["840", "I7N3G6G", "840", 0, "no description", 60.2, "random"], ["841", "I7N3G6G", "841", 0, "no description", 60.2, "random"], ["842", "I7N3G6G", "842", 0, "no description", 60.2, "random"], ["843", "I7N3G6G", "843", 0, "no description", 60.2, "random"], ["844", "I7N3G6G", "844", 0, "no description", 60.2, "random"], ["845", "I7N3G6G", "845", 0, "no description", 60.2, "random"], ["846", "I7N3G6G", "846", 0, "no description", 60.2, "random"], ["847", "I7N3G6G", "847", 0, "no description", 60.2, "random"], ["848", "I7N3G6G", "848", 0, "no description", 60.2, "random"], ["849", "I7N3G6G", "849", 0, "no description", 60.2, "random"], ["850", "I7N3G6G", "850", 0, "no description", 60.2, "random"], ["851", "I7N3G6G", "851", 0, "no description", 60.2, "random"], ["852", "I7N3G6G", "852", 0, "no description", 60.2, "random"], ["853", "I7N3G6G", "853", 0, "no description", 60.2, "random"], ["854", "I7N3G6G", "854", 0, "no description", 60.2, "random"], ["855", "I7N3G6G", "855", 0, "no description", 60.2, "random"], ["856", "I7N3G6G", "856", 0, "no description", 60.2, "random"], ["857", "I7N3G6G", "857", 0, "no description", 60.2, "random"], ["858", "I7N3G6G", "858", 0, "no description", 60.2, "random"], ["859", "I7N3G6G", "859", 0, "no description", 60.2, "random"], ["860", "I7N3G6G", "860", 0, "no description", 60.2, "random"], ["861", "I7N3G6G", "861", 0, "no description", 60.2, "random"], ["862", "I7N3G6G", "862", 0, "no description", 60.2, "random"], ["863", "I7N3G6G", "863", 0, "no description", 60.2, "random"], ["864", "I7N3G6G", "864", 0, "no description", 60.2, "random"], ["865", "I7N3G6G", "865", 0, "no description", 60.2, "random"], ["866", "I7N3G6G", "866", 0, "no description", 60.2, "random"], ["867", "I7N3G6G", "867", 0, "no description", 60.2, "random"], ["868", "I7N3G6G", "868", 0, "no description", 60.2, "random"], ["869", "I7N3G6G", "869", 0, "no description", 60.2, "random"], ["870", "I7N3G6G", "870", 0, "no description", 60.2, "random"], ["871", "I7N3G6G", "871", 0, "no description", 60.2, "random"], ["872", "I7N3G6G", "872", 0, "no description", 60.2, "random"], ["873", "I7N3G6G", "873", 0, "no description", 60.2, "random"], ["874", "I7N3G6G", "874", 0, "no description", 60.2, "random"], ["875", "I7N3G6G", "875", 0, "no description", 60.2, "random"], ["876", "I7N3G6G", "876", 0, "no description", 60.2, "random"], ["877", "I7N3G6G", "877", 0, "no description", 60.2, "random"], ["878", "I7N3G6G", "878", 0, "no description", 60.2, "random"], ["879", "I7N3G6G", "879", 0, "no description", 60.2, "random"], ["880", "I7N3G6G", "880", 0, "no description", 60.2, "random"], ["881", "I7N3G6G", "881", 0, "no description", 60.2, "random"], ["882", "I7N3G6G", "882", 0, "no description", 60.2, "random"], ["883", "I7N3G6G", "883", 0, "no description", 60.2, "random"], ["884", "I7N3G6G", "884", 0, "no description", 60.2, "random"], ["885", "I7N3G6G", "885", 0, "no description", 60.2, "random"], ["886", "I7N3G6G", "886", 0, "no description", 60.2, "random"], ["887", "I7N3G6G", "887", 0, "no description", 60.2, "random"], ["888", "I7N3G6G", "888", 0, "no description", 60.2, "random"], ["889", "I7N3G6G", "889", 0, "no description", 60.2, "random"], ["890", "I7N3G6G", "890", 0, "no description", 60.2, "random"], ["891", "I7N3G6G", "891", 0, "no description", 60.2, "random"], ["892", "I7N3G6G", "892", 0, "no description", 60.2, "random"], ["893", "I7N3G6G", "893", 0, "no description", 60.2, "random"], ["894", "I7N3G6G", "894", 0, "no description", 60.2, "random"], ["895", "I7N3G6G", "895", 0, "no description", 60.2, "random"], ["896", "I7N3G6G", "896", 0, "no description", 60.2, "random"], ["897", "I7N3G6G", "897", 0, "no description", 60.2, "random"], ["898", "I7N3G6G", "898", 0, "no description", 60.2, "random"], ["899", "I7N3G6G", "899", 0, "no description", 60.2, "random"], ["900", "I7N3G6G", "900", 0, "no description", 60.2, "random"], ["901", "I7N3G6G", "901", 0, "no description", 60.2, "random"], ["902", "I7N3G6G", "902", 0, "no description", 60.2, "random"], ["903", "I7N3G6G", "903", 0, "no description", 60.2, "random"], ["904", "I7N3G6G", "904", 0, "no description", 60.2, "random"], ["905", "I7N3G6G", "905", 0, "no description", 60.2, "random"], ["906", "I7N3G6G", "906", 0, "no description", 60.2, "random"], ["907", "I7N3G6G", "907", 0, "no description", 60.2, "random"], ["908", "I7N3G6G", "908", 0, "no description", 60.2, "random"], ["909", "I7N3G6G", "909", 0, "no description", 60.2, "random"], ["910", "I7N3G6G", "910", 0, "no description", 60.2, "random"], ["911", "I7N3G6G", "911", 0, "no description", 60.2, "random"], ["912", "I7N3G6G", "912", 0, "no description", 60.2, "random"], ["913", "I7N3G6G", "913", 0, "no description", 60.2, "random"], ["914", "I7N3G6G", "914", 0, "no description", 60.2, "random"], ["915", "I7N3G6G", "915", 0, "no description", 60.2, "random"], ["916", "I7N3G6G", "916", 0, "no description", 60.2, "random"], ["917", "I7N3G6G", "917", 0, "no description", 60.2, "random"], ["918", "I7N3G6G", "918", 0, "no description", 60.2, "random"], ["919", "I7N3G6G", "919", 0, "no description", 60.2, "random"], ["920", "I7N3G6G", "920", 0, "no description", 60.2, "random"], ["921", "I7N3G6G", "921", 0, "no description", 60.2, "random"], ["922", "I7N3G6G", "922", 0, "no description", 60.2, "random"], ["923", "I7N3G6G", "923", 0, "no description", 60.2, "random"], ["924", "I7N3G6G", "924", 0, "no description", 60.2, "random"], ["925", "I7N3G6G", "925", 0, "no description", 60.2, "random"], ["926", "I7N3G6G", "926", 0, "no description", 60.2, "random"], ["927", "I7N3G6G", "927", 0, "no description", 60.2, "random"], ["928", "I7N3G6G", "928", 0, "no description", 60.2, "random"], ["929", "I7N3G6G", "929", 0, "no description", 60.2, "random"], ["930", "I7N3G6G", "930", 0, "no description", 60.2, "random"], ["931", "I7N3G6G", "931", 0, "no description", 60.2, "random"], ["932", "I7N3G6G", "932", 0, "no description", 60.2, "random"], ["933", "I7N3G6G", "933", 0, "no description", 60.2, "random"], ["934", "I7N3G6G", "934", 0, "no description", 60.2, "random"], ["935", "I7N3G6G", "935", 0, "no description", 60.2, "random"], ["936", "I7N3G6G", "936", 0, "no description", 60.2, "random"], ["937", "I7N3G6G", "937", 0, "no description", 60.2, "random"], ["938", "I7N3G6G", "938", 0, "no description", 60.2, "random"], ["939", "I7N3G6G", "939", 0, "no description", 60.2, "random"], ["940", "I7N3G6G", "940", 0, "no description", 60.2, "random"], ["941", "I7N3G6G", "941", 0, "no description", 60.2, "random"], ["942", "I7N3G6G", "942", 0, "no description", 60.2, "random"], ["943", "I7N3G6G", "943", 0, "no description", 60.2, "random"], ["944", "I7N3G6G", "944", 0, "no description", 60.2, "random"], ["945", "I7N3G6G", "945", 0, "no description", 60.2, "random"], ["946", "I7N3G6G", "946", 0, "no description", 60.2, "random"], ["947", "I7N3G6G", "947", 0, "no description", 60.2, "random"], ["948", "I7N3G6G", "948", 0, "no description", 60.2, "random"], ["949", "I7N3G6G", "949", 0, "no description", 60.2, "random"], ["950", "I7N3G6G", "950", 0, "no description", 60.2, "random"], ["951", "I7N3G6G", "951", 0, "no description", 60.2, "random"], ["952", "I7N3G6G", "952", 0, "no description", 60.2, "random"], ["953", "I7N3G6G", "953", 0, "no description", 60.2, "random"], ["954", "I7N3G6G", "954", 0, "no description", 60.2, "random"], ["955", "I7N3G6G", "955", 0, "no description", 60.2, "random"], ["956", "I7N3G6G", "956", 0, "no description", 60.2, "random"], ["957", "I7N3G6G", "957", 0, "no description", 60.2, "random"], ["958", "I7N3G6G", "958", 0, "no description", 60.2, "random"], ["959", "I7N3G6G", "959", 0, "no description", 60.2, "random"], ["960", "I7N3G6G", "960", 0, "no description", 60.2, "random"], ["961", "I7N3G6G", "961", 0, "no description", 60.2, "random"], ["962", "I7N3G6G", "962", 0, "no description", 60.2, "random"], ["963", "I7N3G6G", "963", 0, "no description", 60.2, "random"], ["964", "I7N3G6G", "964", 0, "no description", 60.2, "random"], ["965", "I7N3G6G", "965", 0, "no description", 60.2, "random"], ["966", "I7N3G6G", "966", 0, "no description", 60.2, "random"], ["967", "I7N3G6G", "967", 0, "no description", 60.2, "random"], ["968", "I7N3G6G", "968", 0, "no description", 60.2, "random"], ["969", "I7N3G6G", "969", 0, "no description", 60.2, "random"], ["970", "I7N3G6G", "970", 0, "no description", 60.2, "random"], ["971", "I7N3G6G", "971", 0, "no description", 60.2, "random"], ["972", "I7N3G6G", "972", 0, "no description", 60.2, "random"], ["973", "I7N3G6G", "973", 0, "no description", 60.2, "random"], ["974", "I7N3G6G", "974", 0, "no description", 60.2, "random"], ["975", "I7N3G6G", "975", 0, "no description", 60.2, "random"], ["976", "I7N3G6G", "976", 0, "no description", 60.2, "random"], ["977", "I7N3G6G", "977", 0, "no description", 60.2, "random"], ["978", "I7N3G6G", "978", 0, "no description", 60.2, "random"], ["979", "I7N3G6G", "979", 0, "no description", 60.2, "random"], ["980", "I7N3G6G", "980", 0, "no description", 60.2, "random"], ["981", "I7N3G6G", "981", 0, "no description", 60.2, "random"], ["982", "I7N3G6G", "982", 0, "no description", 60.2, "random"], ["983", "I7N3G6G", "983", 0, "no description", 60.2, "random"], ["984", "I7N3G6G", "984", 0, "no description", 60.2, "random"], ["985", "I7N3G6G", "985", 0, "no description", 60.2, "random"], ["986", "I7N3G6G", "986", 0, "no description", 60.2, "random"], ["987", "I7N3G6G", "987", 0, "no description", 60.2, "random"], ["988", "I7N3G6G", "988", 0, "no description", 60.2, "random"], ["989", "I7N3G6G", "989", 0, "no description", 60.2, "random"], ["990", "I7N3G6G", "990", 0, "no description", 60.2, "random"], ["991", "I7N3G6G", "991", 0, "no description", 60.2, "random"], ["992", "I7N3G6G", "992", 0, "no description", 60.2, "random"], ["993", "I7N3G6G", "993", 0, "no description", 60.2, "random"], ["994", "I7N3G6G", "994", 0, "no description", 60.2, "random"], ["995", "I7N3G6G", "995", 0, "no description", 60.2, "random"], ["996", "I7N3G6G", "996", 0, "no description", 60.2, "random"], ["997", "I7N3G6G", "997", 0, "no description", 60.2, "random"], ["998", "I7N3G6G", "998", 0, "no description", 60.2, "random"], ["999", "I7N3G6G", "999", 0, "no description", 60.2, "random"], ["1000", "I7N3G6G", "1000", 0, "no description", 60.2, "random"], ["1001", "I7N3G6G", "1001", 0, "no description", 60.2, "random"], ["1002", "I7N3G6G", "1002", 0, "no description", 60.2, "random"], ["1003", "I7N3G6G", "1003", 0, "no description", 60.2, "random"], ["1004", "I7N3G6G", "1004", 0, "no description", 60.2, "random"], ["1005", "I7N3G6G", "1005", 0, "no description", 60.2, "random"], ["1006", "I7N3G6G", "1006", 0, "no description", 60.2, "random"], ["1007", "I7N3G6G", "1007", 0, "no description", 60.2, "random"], ["1008", "I7N3G6G", "1008", 0, "no description", 60.2, "random"], ["1009", "I7N3G6G", "1009", 0, "no description", 60.2, "random"], ["1010", "I7N3G6G", "1010", 0, "no description", 60.2, "random"], ["1011", "I7N3G6G", "1011", 0, "no description", 60.2, "random"], ["1012", "I7N3G6G", "1012", 0, "no description", 60.2, "random"], ["1013", "I7N3G6G", "1013", 0, "no description", 60.2, "random"], ["1014", "I7N3G6G", "1014", 0, "no description", 60.2, "random"], ["1015", "I7N3G6G", "1015", 0, "no description", 60.2, "random"], ["1016", "I7N3G6G", "1016", 0, "no description", 60.2, "random"], ["1017", "I7N3G6G", "1017", 0, "no description", 60.2, "random"], ["1018", "I7N3G6G", "1018", 0, "no description", 60.2, "random"], ["1019", "I7N3G6G", "1019", 0, "no description", 60.2, "random"], ["1020", "I7N3G6G", "1020", 0, "no description", 60.2, "random"], ["1021", "I7N3G6G", "1021", 0, "no description", 60.2, "random"], ["1022", "I7N3G6G", "1022", 0, "no description", 60.2, "random"], ["1023", "I7N3G6G", "1023", 0, "no description", 60.2, "random"], ["1024", "I7N3G6G", "1024", 0, "no description", 60.2, "random"], ["1025", "I7N3G6G", "1025", 0, "no description", 60.2, "random"], ["1026", "I7N3G6G", "1026", 0, "no description", 60.2, "random"], ["1027", "I7N3G6G", "1027", 0, "no description", 60.2, "random"], ["1028", "I7N3G6G", "1028", 0, "no description", 60.2, "random"], ["1029", "I7N3G6G", "1029", 0, "no description", 60.2, "random"], ["1030", "I7N3G6G", "1030", 0, "no description", 60.2, "random"], ["1031", "I7N3G6G", "1031", 0, "no description", 60.2, "random"], ["1032", "I7N3G6G", "1032", 0, "no description", 60.2, "random"], ["1033", "I7N3G6G", "1033", 0, "no description", 60.2, "random"], ["1034", "I7N3G6G", "1034", 0, "no description", 60.2, "random"], ["1035", "I7N3G6G", "1035", 0, "no description", 60.2, "random"], ["1036", "I7N3G6G", "1036", 0, "no description", 60.2, "random"], ["1037", "I7N3G6G", "1037", 0, "no description", 60.2, "random"], ["1038", "I7N3G6G", "1038", 0, "no description", 60.2, "random"], ["1039", "I7N3G6G", "1039", 0, "no description", 60.2, "random"], ["1040", "I7N3G6G", "1040", 0, "no description", 60.2, "random"], ["1041", "I7N3G6G", "1041", 0, "no description", 60.2, "random"], ["1042", "I7N3G6G", "1042", 0, "no description", 60.2, "random"], ["1043", "I7N3G6G", "1043", 0, "no description", 60.2, "random"], ["1044", "I7N3G6G", "1044", 0, "no description", 60.2, "random"], ["1045", "I7N3G6G", "1045", 0, "no description", 60.2, "random"], ["1046", "I7N3G6G", "1046", 0, "no description", 60.2, "random"], ["1047", "I7N3G6G", "1047", 0, "no description", 60.2, "random"], ["1048", "I7N3G6G", "1048", 0, "no description", 60.2, "random"], ["1049", "I7N3G6G", "1049", 0, "no description", 60.2, "random"], ["1050", "I7N3G6G", "1050", 0, "no description", 60.2, "random"], ["1051", "I7N3G6G", "1051", 0, "no description", 60.2, "random"], ["1052", "I7N3G6G", "1052", 0, "no description", 60.2, "random"], ["1053", "I7N3G6G", "1053", 0, "no description", 60.2, "random"], ["1054", "I7N3G6G", "1054", 0, "no description", 60.2, "random"], ["1055", "I7N3G6G", "1055", 0, "no description", 60.2, "random"], ["1056", "I7N3G6G", "1056", 0, "no description", 60.2, "random"], ["1057", "I7N3G6G", "1057", 0, "no description", 60.2, "random"], ["1058", "I7N3G6G", "1058", 0, "no description", 60.2, "random"], ["1059", "I7N3G6G", "1059", 0, "no description", 60.2, "random"], ["1060", "I7N3G6G", "1060", 0, "no description", 60.2, "random"], ["1061", "I7N3G6G", "1061", 0, "no description", 60.2, "random"], ["1062", "I7N3G6G", "1062", 0, "no description", 60.2, "random"], ["1063", "I7N3G6G", "1063", 0, "no description", 60.2, "random"], ["1064", "I7N3G6G", "1064", 0, "no description", 60.2, "random"], ["1065", "I7N3G6G", "1065", 0, "no description", 60.2, "random"], ["1066", "I7N3G6G", "1066", 0, "no description", 60.2, "random"], ["1067", "I7N3G6G", "1067", 0, "no description", 60.2, "random"], ["1068", "I7N3G6G", "1068", 0, "no description", 60.2, "random"], ["1069", "I7N3G6G", "1069", 0, "no description", 60.2, "random"], ["1070", "I7N3G6G", "1070", 0, "no description", 60.2, "random"], ["1071", "I7N3G6G", "1071", 0, "no description", 60.2, "random"], ["1072", "I7N3G6G", "1072", 0, "no description", 60.2, "random"], ["1073", "I7N3G6G", "1073", 0, "no description", 60.2, "random"], ["1074", "I7N3G6G", "1074", 0, "no description", 60.2, "random"], ["1075", "I7N3G6G", "1075", 0, "no description", 60.2, "random"], ["1076", "I7N3G6G", "1076", 0, "no description", 60.2, "random"], ["1077", "I7N3G6G", "1077", 0, "no description", 60.2, "random"], ["1078", "I7N3G6G", "1078", 0, "no description", 60.2, "random"], ["1079", "I7N3G6G", "1079", 0, "no description", 60.2, "random"], ["1080", "I7N3G6G", "1080", 0, "no description", 60.2, "random"], ["1081", "I7N3G6G", "1081", 0, "no description", 60.2, "random"], ["1082", "I7N3G6G", "1082", 0, "no description", 60.2, "random"], ["1083", "I7N3G6G", "1083", 0, "no description", 60.2, "random"], ["1084", "I7N3G6G", "1084", 0, "no description", 60.2, "random"], ["1085", "I7N3G6G", "1085", 0, "no description", 60.2, "random"], ["1086", "I7N3G6G", "1086", 0, "no description", 60.2, "random"], ["1087", "I7N3G6G", "1087", 0, "no description", 60.2, "random"], ["1088", "I7N3G6G", "1088", 0, "no description", 60.2, "random"], ["1089", "I7N3G6G", "1089", 0, "no description", 60.2, "random"], ["1090", "I7N3G6G", "1090", 0, "no description", 60.2, "random"], ["1091", "I7N3G6G", "1091", 0, "no description", 60.2, "random"], ["1092", "I7N3G6G", "1092", 0, "no description", 60.2, "random"], ["1093", "I7N3G6G", "1093", 0, "no description", 60.2, "random"], ["1094", "I7N3G6G", "1094", 0, "no description", 60.2, "random"], ["1095", "I7N3G6G", "1095", 0, "no description", 60.2, "random"], ["1096", "I7N3G6G", "1096", 0, "no description", 60.2, "random"], ["1097", "I7N3G6G", "1097", 0, "no description", 60.2, "random"], ["1098", "I7N3G6G", "1098", 0, "no description", 60.2, "random"], ["1099", "I7N3G6G", "1099", 0, "no description", 60.2, "random"], ["1100", "I7N3G6G", "1100", 0, "no description", 60.2, "random"], ["1101", "I7N3G6G", "1101", 0, "no description", 60.2, "random"], ["1102", "I7N3G6G", "1102", 0, "no description", 60.2, "random"], ["1103", "I7N3G6G", "1103", 0, "no description", 60.2, "random"], ["1104", "I7N3G6G", "1104", 0, "no description", 60.2, "random"], ["1105", "I7N3G6G", "1105", 0, "no description", 60.2, "random"], ["1106", "I7N3G6G", "1106", 0, "no description", 60.2, "random"], ["1107", "I7N3G6G", "1107", 0, "no description", 60.2, "random"], ["1108", "I7N3G6G", "1108", 0, "no description", 60.2, "random"], ["1109", "I7N3G6G", "1109", 0, "no description", 60.2, "random"], ["1110", "I7N3G6G", "1110", 0, "no description", 60.2, "random"], ["1111", "I7N3G6G", "1111", 0, "no description", 60.2, "random"], ["1112", "I7N3G6G", "1112", 0, "no description", 60.2, "random"], ["1113", "I7N3G6G", "1113", 0, "no description", 60.2, "random"], ["1114", "I7N3G6G", "1114", 0, "no description", 60.2, "random"], ["1115", "I7N3G6G", "1115", 0, "no description", 60.2, "random"], ["1116", "I7N3G6G", "1116", 0, "no description", 60.2, "random"], ["1117", "I7N3G6G", "1117", 0, "no description", 60.2, "random"], ["1118", "I7N3G6G", "1118", 0, "no description", 60.2, "random"], ["1119", "I7N3G6G", "1119", 0, "no description", 60.2, "random"], ["1120", "I7N3G6G", "1120", 0, "no description", 60.2, "random"], ["1121", "I7N3G6G", "1121", 0, "no description", 60.2, "random"], ["1122", "I7N3G6G", "1122", 0, "no description", 60.2, "random"], ["1123", "I7N3G6G", "1123", 0, "no description", 60.2, "random"], ["1124", "I7N3G6G", "1124", 0, "no description", 60.2, "random"], ["1125", "I7N3G6G", "1125", 0, "no description", 60.2, "random"], ["1126", "I7N3G6G", "1126", 0, "no description", 60.2, "random"], ["1127", "I7N3G6G", "1127", 0, "no description", 60.2, "random"], ["1128", "I7N3G6G", "1128", 0, "no description", 60.2, "random"], ["1129", "I7N3G6G", "1129", 0, "no description", 60.2, "random"], ["1130", "I7N3G6G", "1130", 0, "no description", 60.2, "random"], ["1131", "I7N3G6G", "1131", 0, "no description", 60.2, "random"], ["1132", "I7N3G6G", "1132", 0, "no description", 60.2, "random"], ["1133", "I7N3G6G", "1133", 0, "no description", 60.2, "random"], ["1134", "I7N3G6G", "1134", 0, "no description", 60.2, "random"], ["1135", "I7N3G6G", "1135", 0, "no description", 60.2, "random"], ["1136", "I7N3G6G", "1136", 0, "no description", 60.2, "random"], ["1137", "I7N3G6G", "1137", 0, "no description", 60.2, "random"], ["1138", "I7N3G6G", "1138", 0, "no description", 60.2, "random"], ["1139", "I7N3G6G", "1139", 0, "no description", 60.2, "random"], ["1140", "I7N3G6G", "1140", 0, "no description", 60.2, "random"], ["1141", "I7N3G6G", "1141", 0, "no description", 60.2, "random"], ["1142", "I7N3G6G", "1142", 0, "no description", 60.2, "random"], ["1143", "I7N3G6G", "1143", 0, "no description", 60.2, "random"], ["1144", "I7N3G6G", "1144", 0, "no description", 60.2, "random"], ["1145", "I7N3G6G", "1145", 0, "no description", 60.2, "random"], ["1146", "I7N3G6G", "1146", 0, "no description", 60.2, "random"], ["1147", "I7N3G6G", "1147", 0, "no description", 60.2, "random"], ["1148", "I7N3G6G", "1148", 0, "no description", 60.2, "random"], ["1149", "I7N3G6G", "1149", 0, "no description", 60.2, "random"], ["1150", "I7N3G6G", "1150", 0, "no description", 60.2, "random"], ["1151", "I7N3G6G", "1151", 0, "no description", 60.2, "random"], ["1152", "I7N3G6G", "1152", 0, "no description", 60.2, "random"], ["1153", "I7N3G6G", "1153", 0, "no description", 60.2, "random"], ["1154", "I7N3G6G", "1154", 0, "no description", 60.2, "random"], ["1155", "I7N3G6G", "1155", 0, "no description", 60.2, "random"], ["1156", "I7N3G6G", "1156", 0, "no description", 60.2, "random"], ["1157", "I7N3G6G", "1157", 0, "no description", 60.2, "random"], ["1158", "I7N3G6G", "1158", 0, "no description", 60.2, "random"], ["1159", "I7N3G6G", "1159", 0, "no description", 60.2, "random"], ["1160", "I7N3G6G", "1160", 0, "no description", 60.2, "random"], ["1161", "I7N3G6G", "1161", 0, "no description", 60.2, "random"], ["1162", "I7N3G6G", "1162", 0, "no description", 60.2, "random"], ["1163", "I7N3G6G", "1163", 0, "no description", 60.2, "random"], ["1164", "I7N3G6G", "1164", 0, "no description", 60.2, "random"], ["1165", "I7N3G6G", "1165", 0, "no description", 60.2, "random"], ["1166", "I7N3G6G", "1166", 0, "no description", 60.2, "random"], ["1167", "I7N3G6G", "1167", 0, "no description", 60.2, "random"], ["1168", "I7N3G6G", "1168", 0, "no description", 60.2, "random"], ["1169", "I7N3G6G", "1169", 0, "no description", 60.2, "random"], ["1170", "I7N3G6G", "1170", 0, "no description", 60.2, "random"], ["1171", "I7N3G6G", "1171", 0, "no description", 60.2, "random"], ["1172", "I7N3G6G", "1172", 0, "no description", 60.2, "random"], ["1173", "I7N3G6G", "1173", 0, "no description", 60.2, "random"], ["1174", "I7N3G6G", "1174", 0, "no description", 60.2, "random"], ["1175", "I7N3G6G", "1175", 0, "no description", 60.2, "random"], ["1176", "I7N3G6G", "1176", 0, "no description", 60.2, "random"], ["1177", "I7N3G6G", "1177", 0, "no description", 60.2, "random"], ["1178", "I7N3G6G", "1178", 0, "no description", 60.2, "random"], ["1179", "I7N3G6G", "1179", 0, "no description", 60.2, "random"], ["1180", "I7N3G6G", "1180", 0, "no description", 60.2, "random"], ["1181", "I7N3G6G", "1181", 0, "no description", 60.2, "random"], ["1182", "I7N3G6G", "1182", 0, "no description", 60.2, "random"], ["1183", "I7N3G6G", "1183", 0, "no description", 60.2, "random"], ["1184", "I7N3G6G", "1184", 0, "no description", 60.2, "random"], ["1185", "I7N3G6G", "1185", 0, "no description", 60.2, "random"], ["1186", "I7N3G6G", "1186", 0, "no description", 60.2, "random"], ["1187", "I7N3G6G", "1187", 0, "no description", 60.2, "random"], ["1188", "I7N3G6G", "1188", 0, "no description", 60.2, "random"], ["1189", "I7N3G6G", "1189", 0, "no description", 60.2, "random"], ["1190", "I7N3G6G", "1190", 0, "no description", 60.2, "random"], ["1191", "I7N3G6G", "1191", 0, "no description", 60.2, "random"], ["1192", "I7N3G6G", "1192", 0, "no description", 60.2, "random"], ["1193", "I7N3G6G", "1193", 0, "no description", 60.2, "random"], ["1194", "I7N3G6G", "1194", 0, "no description", 60.2, "random"], ["1195", "I7N3G6G", "1195", 0, "no description", 60.2, "random"], ["1196", "I7N3G6G", "1196", 0, "no description", 60.2, "random"], ["1197", "I7N3G6G", "1197", 0, "no description", 60.2, "random"], ["1198", "I7N3G6G", "1198", 0, "no description", 60.2, "random"], ["1199", "I7N3G6G", "1199", 0, "no description", 60.2, "random"], ["1200", "I7N3G6G", "1200", 0, "no description", 60.2, "random"], ["1201", "I7N3G6G", "1201", 0, "no description", 60.2, "random"], ["1202", "I7N3G6G", "1202", 0, "no description", 60.2, "random"], ["1203", "I7N3G6G", "1203", 0, "no description", 60.2, "random"], ["1204", "I7N3G6G", "1204", 0, "no description", 60.2, "random"], ["1205", "I7N3G6G", "1205", 0, "no description", 60.2, "random"], ["1206", "I7N3G6G", "1206", 0, "no description", 60.2, "random"], ["1207", "I7N3G6G", "1207", 0, "no description", 60.2, "random"], ["1208", "I7N3G6G", "1208", 0, "no description", 60.2, "random"], ["1209", "I7N3G6G", "1209", 0, "no description", 60.2, "random"], ["1210", "I7N3G6G", "1210", 0, "no description", 60.2, "random"], ["1211", "I7N3G6G", "1211", 0, "no description", 60.2, "random"], ["1212", "I7N3G6G", "1212", 0, "no description", 60.2, "random"], ["1213", "I7N3G6G", "1213", 0, "no description", 60.2, "random"], ["1214", "I7N3G6G", "1214", 0, "no description", 60.2, "random"], ["1215", "I7N3G6G", "1215", 0, "no description", 60.2, "random"], ["1216", "I7N3G6G", "1216", 0, "no description", 60.2, "random"], ["1217", "I7N3G6G", "1217", 0, "no description", 60.2, "random"], ["1218", "I7N3G6G", "1218", 0, "no description", 60.2, "random"], ["1219", "I7N3G6G", "1219", 0, "no description", 60.2, "random"], ["1220", "I7N3G6G", "1220", 0, "no description", 60.2, "random"], ["1221", "I7N3G6G", "1221", 0, "no description", 60.2, "random"], ["1222", "I7N3G6G", "1222", 0, "no description", 60.2, "random"], ["1223", "I7N3G6G", "1223", 0, "no description", 60.2, "random"], ["1224", "I7N3G6G", "1224", 0, "no description", 60.2, "random"], ["1225", "I7N3G6G", "1225", 0, "no description", 60.2, "random"], ["1226", "I7N3G6G", "1226", 0, "no description", 60.2, "random"], ["1227", "I7N3G6G", "1227", 0, "no description", 60.2, "random"], ["1228", "I7N3G6G", "1228", 0, "no description", 60.2, "random"], ["1229", "I7N3G6G", "1229", 0, "no description", 60.2, "random"], ["1230", "I7N3G6G", "1230", 0, "no description", 60.2, "random"], ["1231", "I7N3G6G", "1231", 0, "no description", 60.2, "random"], ["1232", "I7N3G6G", "1232", 0, "no description", 60.2, "random"], ["1233", "I7N3G6G", "1233", 0, "no description", 60.2, "random"], ["1234", "I7N3G6G", "1234", 0, "no description", 60.2, "random"], ["1235", "I7N3G6G", "1235", 0, "no description", 60.2, "random"], ["1236", "I7N3G6G", "1236", 0, "no description", 60.2, "random"], ["1237", "I7N3G6G", "1237", 0, "no description", 60.2, "random"], ["1238", "I7N3G6G", "1238", 0, "no description", 60.2, "random"], ["1239", "I7N3G6G", "1239", 0, "no description", 60.2, "random"], ["1240", "I7N3G6G", "1240", 0, "no description", 60.2, "random"], ["1241", "I7N3G6G", "1241", 0, "no description", 60.2, "random"], ["1242", "I7N3G6G", "1242", 0, "no description", 60.2, "random"], ["1243", "I7N3G6G", "1243", 0, "no description", 60.2, "random"], ["1244", "I7N3G6G", "1244", 0, "no description", 60.2, "random"], ["1245", "I7N3G6G", "1245", 0, "no description", 60.2, "random"], ["1246", "I7N3G6G", "1246", 0, "no description", 60.2, "random"], ["1247", "I7N3G6G", "1247", 0, "no description", 60.2, "random"], ["1248", "I7N3G6G", "1248", 0, "no description", 60.2, "random"], ["1249", "I7N3G6G", "1249", 0, "no description", 60.2, "random"], ["1250", "I7N3G6G", "1250", 0, "no description", 60.2, "random"], ["1251", "I7N3G6G", "1251", 0, "no description", 60.2, "random"], ["1252", "I7N3G6G", "1252", 0, "no description", 60.2, "random"], ["1253", "I7N3G6G", "1253", 0, "no description", 60.2, "random"], ["1254", "I7N3G6G", "1254", 0, "no description", 60.2, "random"], ["1255", "I7N3G6G", "1255", 0, "no description", 60.2, "random"], ["1256", "I7N3G6G", "1256", 0, "no description", 60.2, "random"], ["1257", "I7N3G6G", "1257", 0, "no description", 60.2, "random"], ["1258", "I7N3G6G", "1258", 0, "no description", 60.2, "random"], ["1259", "I7N3G6G", "1259", 0, "no description", 60.2, "random"], ["1260", "I7N3G6G", "1260", 0, "no description", 60.2, "random"], ["1261", "I7N3G6G", "1261", 0, "no description", 60.2, "random"], ["1262", "I7N3G6G", "1262", 0, "no description", 60.2, "random"], ["1263", "I7N3G6G", "1263", 0, "no description", 60.2, "random"], ["1264", "I7N3G6G", "1264", 0, "no description", 60.2, "random"], ["1265", "I7N3G6G", "1265", 0, "no description", 60.2, "random"], ["1266", "I7N3G6G", "1266", 0, "no description", 60.2, "random"], ["1267", "I7N3G6G", "1267", 0, "no description", 60.2, "random"], ["1268", "I7N3G6G", "1268", 0, "no description", 60.2, "random"], ["1269", "I7N3G6G", "1269", 0, "no description", 60.2, "random"], ["1270", "I7N3G6G", "1270", 0, "no description", 60.2, "random"], ["1271", "I7N3G6G", "1271", 0, "no description", 60.2, "random"], ["1272", "I7N3G6G", "1272", 0, "no description", 60.2, "random"], ["1273", "I7N3G6G", "1273", 0, "no description", 60.2, "random"], ["1274", "I7N3G6G", "1274", 0, "no description", 60.2, "random"], ["1275", "I7N3G6G", "1275", 0, "no description", 60.2, "random"], ["1276", "I7N3G6G", "1276", 0, "no description", 60.2, "random"], ["1277", "I7N3G6G", "1277", 0, "no description", 60.2, "random"], ["1278", "I7N3G6G", "1278", 0, "no description", 60.2, "random"], ["1279", "I7N3G6G", "1279", 0, "no description", 60.2, "random"], ["1280", "I7N3G6G", "1280", 0, "no description", 60.2, "random"], ["1281", "I7N3G6G", "1281", 0, "no description", 60.2, "random"], ["1282", "I7N3G6G", "1282", 0, "no description", 60.2, "random"], ["1283", "I7N3G6G", "1283", 0, "no description", 60.2, "random"], ["1284", "I7N3G6G", "1284", 0, "no description", 60.2, "random"], ["1285", "I7N3G6G", "1285", 0, "no description", 60.2, "random"], ["1286", "I7N3G6G", "1286", 0, "no description", 60.2, "random"], ["1287", "I7N3G6G", "1287", 0, "no description", 60.2, "random"], ["1288", "I7N3G6G", "1288", 0, "no description", 60.2, "random"], ["1289", "I7N3G6G", "1289", 0, "no description", 60.2, "random"], ["1290", "I7N3G6G", "1290", 0, "no description", 60.2, "random"], ["1291", "I7N3G6G", "1291", 0, "no description", 60.2, "random"], ["1292", "I7N3G6G", "1292", 0, "no description", 60.2, "random"], ["1293", "I7N3G6G", "1293", 0, "no description", 60.2, "random"], ["1294", "I7N3G6G", "1294", 0, "no description", 60.2, "random"], ["1295", "I7N3G6G", "1295", 0, "no description", 60.2, "random"], ["1296", "I7N3G6G", "1296", 0, "no description", 60.2, "random"], ["1297", "I7N3G6G", "1297", 0, "no description", 60.2, "random"], ["1298", "I7N3G6G", "1298", 0, "no description", 60.2, "random"], ["1299", "I7N3G6G", "1299", 0, "no description", 60.2, "random"], ["1300", "I7N3G6G", "1300", 0, "no description", 60.2, "random"], ["1301", "I7N3G6G", "1301", 0, "no description", 60.2, "random"], ["1302", "I7N3G6G", "1302", 0, "no description", 60.2, "random"], ["1303", "I7N3G6G", "1303", 0, "no description", 60.2, "random"], ["1304", "I7N3G6G", "1304", 0, "no description", 60.2, "random"], ["1305", "I7N3G6G", "1305", 0, "no description", 60.2, "random"], ["1306", "I7N3G6G", "1306", 0, "no description", 60.2, "random"], ["1307", "I7N3G6G", "1307", 0, "no description", 60.2, "random"], ["1308", "I7N3G6G", "1308", 0, "no description", 60.2, "random"], ["1309", "I7N3G6G", "1309", 0, "no description", 60.2, "random"], ["1310", "I7N3G6G", "1310", 0, "no description", 60.2, "random"], ["1311", "I7N3G6G", "1311", 0, "no description", 60.2, "random"], ["1312", "I7N3G6G", "1312", 0, "no description", 60.2, "random"], ["1313", "I7N3G6G", "1313", 0, "no description", 60.2, "random"], ["1314", "I7N3G6G", "1314", 0, "no description", 60.2, "random"], ["1315", "I7N3G6G", "1315", 0, "no description", 60.2, "random"], ["1316", "I7N3G6G", "1316", 0, "no description", 60.2, "random"], ["1317", "I7N3G6G", "1317", 0, "no description", 60.2, "random"], ["1318", "I7N3G6G", "1318", 0, "no description", 60.2, "random"], ["1319", "I7N3G6G", "1319", 0, "no description", 60.2, "random"], ["1320", "I7N3G6G", "1320", 0, "no description", 60.2, "random"], ["1321", "I7N3G6G", "1321", 0, "no description", 60.2, "random"], ["1322", "I7N3G6G", "1322", 0, "no description", 60.2, "random"], ["1323", "I7N3G6G", "1323", 0, "no description", 60.2, "random"], ["1324", "I7N3G6G", "1324", 0, "no description", 60.2, "random"], ["1325", "I7N3G6G", "1325", 0, "no description", 60.2, "random"], ["1326", "I7N3G6G", "1326", 0, "no description", 60.2, "random"], ["1327", "I7N3G6G", "1327", 0, "no description", 60.2, "random"], ["1328", "I7N3G6G", "1328", 0, "no description", 60.2, "random"], ["1329", "I7N3G6G", "1329", 0, "no description", 60.2, "random"], ["1330", "I7N3G6G", "1330", 0, "no description", 60.2, "random"], ["1331", "I7N3G6G", "1331", 0, "no description", 60.2, "random"], ["1332", "I7N3G6G", "1332", 0, "no description", 60.2, "random"], ["1333", "I7N3G6G", "1333", 0, "no description", 60.2, "random"], ["1334", "I7N3G6G", "1334", 0, "no description", 60.2, "random"], ["1335", "I7N3G6G", "1335", 0, "no description", 60.2, "random"], ["1336", "I7N3G6G", "1336", 0, "no description", 60.2, "random"], ["1337", "I7N3G6G", "1337", 0, "no description", 60.2, "random"], ["1338", "I7N3G6G", "1338", 0, "no description", 60.2, "random"], ["1339", "I7N3G6G", "1339", 0, "no description", 60.2, "random"], ["1340", "I7N3G6G", "1340", 0, "no description", 60.2, "random"], ["1341", "I7N3G6G", "1341", 0, "no description", 60.2, "random"], ["1342", "I7N3G6G", "1342", 0, "no description", 60.2, "random"], ["1343", "I7N3G6G", "1343", 0, "no description", 60.2, "random"], ["1344", "I7N3G6G", "1344", 0, "no description", 60.2, "random"], ["1345", "I7N3G6G", "1345", 0, "no description", 60.2, "random"], ["1346", "I7N3G6G", "1346", 0, "no description", 60.2, "random"], ["1347", "I7N3G6G", "1347", 0, "no description", 60.2, "random"], ["1348", "I7N3G6G", "1348", 0, "no description", 60.2, "random"], ["1349", "I7N3G6G", "1349", 0, "no description", 60.2, "random"], ["1350", "I7N3G6G", "1350", 0, "no description", 60.2, "random"], ["1351", "I7N3G6G", "1351", 0, "no description", 60.2, "random"], ["1352", "I7N3G6G", "1352", 0, "no description", 60.2, "random"], ["1353", "I7N3G6G", "1353", 0, "no description", 60.2, "random"], ["1354", "I7N3G6G", "1354", 0, "no description", 60.2, "random"], ["1355", "I7N3G6G", "1355", 0, "no description", 60.2, "random"], ["1356", "I7N3G6G", "1356", 0, "no description", 60.2, "random"], ["1357", "I7N3G6G", "1357", 0, "no description", 60.2, "random"], ["1358", "I7N3G6G", "1358", 0, "no description", 60.2, "random"], ["1359", "I7N3G6G", "1359", 0, "no description", 60.2, "random"], ["1360", "I7N3G6G", "1360", 0, "no description", 60.2, "random"], ["1361", "I7N3G6G", "1361", 0, "no description", 60.2, "random"], ["1362", "I7N3G6G", "1362", 0, "no description", 60.2, "random"], ["1363", "I7N3G6G", "1363", 0, "no description", 60.2, "random"], ["1364", "I7N3G6G", "1364", 0, "no description", 60.2, "random"], ["1365", "I7N3G6G", "1365", 0, "no description", 60.2, "random"], ["1366", "I7N3G6G", "1366", 0, "no description", 60.2, "random"], ["1367", "I7N3G6G", "1367", 0, "no description", 60.2, "random"], ["1368", "I7N3G6G", "1368", 0, "no description", 60.2, "random"], ["1369", "I7N3G6G", "1369", 0, "no description", 60.2, "random"], ["1370", "I7N3G6G", "1370", 0, "no description", 60.2, "random"], ["1371", "I7N3G6G", "1371", 0, "no description", 60.2, "random"], ["1372", "I7N3G6G", "1372", 0, "no description", 60.2, "random"], ["1373", "I7N3G6G", "1373", 0, "no description", 60.2, "random"], ["1374", "I7N3G6G", "1374", 0, "no description", 60.2, "random"], ["1375", "I7N3G6G", "1375", 0, "no description", 60.2, "random"], ["1376", "I7N3G6G", "1376", 0, "no description", 60.2, "random"], ["1377", "I7N3G6G", "1377", 0, "no description", 60.2, "random"], ["1378", "I7N3G6G", "1378", 0, "no description", 60.2, "random"], ["1379", "I7N3G6G", "1379", 0, "no description", 60.2, "random"], ["1380", "I7N3G6G", "1380", 0, "no description", 60.2, "random"], ["1381", "I7N3G6G", "1381", 0, "no description", 60.2, "random"], ["1382", "I7N3G6G", "1382", 0, "no description", 60.2, "random"], ["1383", "I7N3G6G", "1383", 0, "no description", 60.2, "random"], ["1384", "I7N3G6G", "1384", 0, "no description", 60.2, "random"], ["1385", "I7N3G6G", "1385", 0, "no description", 60.2, "random"], ["1386", "I7N3G6G", "1386", 0, "no description", 60.2, "random"], ["1387", "I7N3G6G", "1387", 0, "no description", 60.2, "random"], ["1388", "I7N3G6G", "1388", 0, "no description", 60.2, "random"], ["1389", "I7N3G6G", "1389", 0, "no description", 60.2, "random"], ["1390", "I7N3G6G", "1390", 0, "no description", 60.2, "random"], ["1391", "I7N3G6G", "1391", 0, "no description", 60.2, "random"], ["1392", "I7N3G6G", "1392", 0, "no description", 60.2, "random"], ["1393", "I7N3G6G", "1393", 0, "no description", 60.2, "random"], ["1394", "I7N3G6G", "1394", 0, "no description", 60.2, "random"], ["1395", "I7N3G6G", "1395", 0, "no description", 60.2, "random"], ["1396", "I7N3G6G", "1396", 0, "no description", 60.2, "random"], ["1397", "I7N3G6G", "1397", 0, "no description", 60.2, "random"], ["1398", "I7N3G6G", "1398", 0, "no description", 60.2, "random"], ["1399", "I7N3G6G", "1399", 0, "no description", 60.2, "random"], ["1400", "I7N3G6G", "1400", 0, "no description", 60.2, "random"], ["1401", "I7N3G6G", "1401", 0, "no description", 60.2, "random"], ["1402", "I7N3G6G", "1402", 0, "no description", 60.2, "random"], ["1403", "I7N3G6G", "1403", 0, "no description", 60.2, "random"], ["1404", "I7N3G6G", "1404", 0, "no description", 60.2, "random"], ["1405", "I7N3G6G", "1405", 0, "no description", 60.2, "random"], ["1406", "I7N3G6G", "1406", 0, "no description", 60.2, "random"], ["1407", "I7N3G6G", "1407", 0, "no description", 60.2, "random"], ["1408", "I7N3G6G", "1408", 0, "no description", 60.2, "random"], ["1409", "I7N3G6G", "1409", 0, "no description", 60.2, "random"], ["1410", "I7N3G6G", "1410", 0, "no description", 60.2, "random"], ["1411", "I7N3G6G", "1411", 0, "no description", 60.2, "random"], ["1412", "I7N3G6G", "1412", 0, "no description", 60.2, "random"], ["1413", "I7N3G6G", "1413", 0, "no description", 60.2, "random"], ["1414", "I7N3G6G", "1414", 0, "no description", 60.2, "random"], ["1415", "I7N3G6G", "1415", 0, "no description", 60.2, "random"], ["1416", "I7N3G6G", "1416", 0, "no description", 60.2, "random"], ["1417", "I7N3G6G", "1417", 0, "no description", 60.2, "random"], ["1418", "I7N3G6G", "1418", 0, "no description", 60.2, "random"], ["1419", "I7N3G6G", "1419", 0, "no description", 60.2, "random"], ["1420", "I7N3G6G", "1420", 0, "no description", 60.2, "random"], ["1421", "I7N3G6G", "1421", 0, "no description", 60.2, "random"], ["1422", "I7N3G6G", "1422", 0, "no description", 60.2, "random"], ["1423", "I7N3G6G", "1423", 0, "no description", 60.2, "random"], ["1424", "I7N3G6G", "1424", 0, "no description", 60.2, "random"], ["1425", "I7N3G6G", "1425", 0, "no description", 60.2, "random"], ["1426", "I7N3G6G", "1426", 0, "no description", 60.2, "random"], ["1427", "I7N3G6G", "1427", 0, "no description", 60.2, "random"], ["1428", "I7N3G6G", "1428", 0, "no description", 60.2, "random"], ["1429", "I7N3G6G", "1429", 0, "no description", 60.2, "random"], ["1430", "I7N3G6G", "1430", 0, "no description", 60.2, "random"], ["1431", "I7N3G6G", "1431", 0, "no description", 60.2, "random"], ["1432", "I7N3G6G", "1432", 0, "no description", 60.2, "random"], ["1433", "I7N3G6G", "1433", 0, "no description", 60.2, "random"], ["1434", "I7N3G6G", "1434", 0, "no description", 60.2, "random"], ["1435", "I7N3G6G", "1435", 0, "no description", 60.2, "random"], ["1436", "I7N3G6G", "1436", 0, "no description", 60.2, "random"], ["1437", "I7N3G6G", "1437", 0, "no description", 60.2, "random"], ["1438", "I7N3G6G", "1438", 0, "no description", 60.2, "random"], ["1439", "I7N3G6G", "1439", 0, "no description", 60.2, "random"], ["1440", "I7N3G6G", "1440", 0, "no description", 60.2, "random"], ["1441", "I7N3G6G", "1441", 0, "no description", 60.2, "random"], ["1442", "I7N3G6G", "1442", 0, "no description", 60.2, "random"], ["1443", "I7N3G6G", "1443", 0, "no description", 60.2, "random"], ["1444", "I7N3G6G", "1444", 0, "no description", 60.2, "random"], ["1445", "I7N3G6G", "1445", 0, "no description", 60.2, "random"], ["1446", "I7N3G6G", "1446", 0, "no description", 60.2, "random"], ["1447", "I7N3G6G", "1447", 0, "no description", 60.2, "random"], ["1448", "I7N3G6G", "1448", 0, "no description", 60.2, "random"], ["1449", "I7N3G6G", "1449", 0, "no description", 60.2, "random"], ["1450", "I7N3G6G", "1450", 0, "no description", 60.2, "random"], ["1451", "I7N3G6G", "1451", 0, "no description", 60.2, "random"], ["1452", "I7N3G6G", "1452", 0, "no description", 60.2, "random"], ["1453", "I7N3G6G", "1453", 0, "no description", 60.2, "random"], ["1454", "I7N3G6G", "1454", 0, "no description", 60.2, "random"], ["1455", "I7N3G6G", "1455", 0, "no description", 60.2, "random"], ["1456", "I7N3G6G", "1456", 0, "no description", 60.2, "random"], ["1457", "I7N3G6G", "1457", 0, "no description", 60.2, "random"], ["1458", "I7N3G6G", "1458", 0, "no description", 60.2, "random"], ["1459", "I7N3G6G", "1459", 0, "no description", 60.2, "random"], ["1460", "I7N3G6G", "1460", 0, "no description", 60.2, "random"], ["1461", "I7N3G6G", "1461", 0, "no description", 60.2, "random"], ["1462", "I7N3G6G", "1462", 0, "no description", 60.2, "random"], ["1463", "I7N3G6G", "1463", 0, "no description", 60.2, "random"], ["1464", "I7N3G6G", "1464", 0, "no description", 60.2, "random"], ["1465", "I7N3G6G", "1465", 0, "no description", 60.2, "random"], ["1466", "I7N3G6G", "1466", 0, "no description", 60.2, "random"], ["1467", "I7N3G6G", "1467", 0, "no description", 60.2, "random"], ["1468", "I7N3G6G", "1468", 0, "no description", 60.2, "random"], ["1469", "I7N3G6G", "1469", 0, "no description", 60.2, "random"], ["1470", "I7N3G6G", "1470", 0, "no description", 60.2, "random"], ["1471", "I7N3G6G", "1471", 0, "no description", 60.2, "random"], ["1472", "I7N3G6G", "1472", 0, "no description", 60.2, "random"], ["1473", "I7N3G6G", "1473", 0, "no description", 60.2, "random"], ["1474", "I7N3G6G", "1474", 0, "no description", 60.2, "random"], ["1475", "I7N3G6G", "1475", 0, "no description", 60.2, "random"], ["1476", "I7N3G6G", "1476", 0, "no description", 60.2, "random"], ["1477", "I7N3G6G", "1477", 0, "no description", 60.2, "random"], ["1478", "I7N3G6G", "1478", 0, "no description", 60.2, "random"], ["1479", "I7N3G6G", "1479", 0, "no description", 60.2, "random"], ["1480", "I7N3G6G", "1480", 0, "no description", 60.2, "random"], ["1481", "I7N3G6G", "1481", 0, "no description", 60.2, "random"], ["1482", "I7N3G6G", "1482", 0, "no description", 60.2, "random"], ["1483", "I7N3G6G", "1483", 0, "no description", 60.2, "random"], ["1484", "I7N3G6G", "1484", 0, "no description", 60.2, "random"], ["1485", "I7N3G6G", "1485", 0, "no description", 60.2, "random"], ["1486", "I7N3G6G", "1486", 0, "no description", 60.2, "random"], ["1487", "I7N3G6G", "1487", 0, "no description", 60.2, "random"], ["1488", "I7N3G6G", "1488", 0, "no description", 60.2, "random"], ["1489", "I7N3G6G", "1489", 0, "no description", 60.2, "random"], ["1490", "I7N3G6G", "1490", 0, "no description", 60.2, "random"], ["1491", "I7N3G6G", "1491", 0, "no description", 60.2, "random"], ["1492", "I7N3G6G", "1492", 0, "no description", 60.2, "random"], ["1493", "I7N3G6G", "1493", 0, "no description", 60.2, "random"], ["1494", "I7N3G6G", "1494", 0, "no description", 60.2, "random"], ["1495", "I7N3G6G", "1495", 0, "no description", 60.2, "random"], ["1496", "I7N3G6G", "1496", 0, "no description", 60.2, "random"], ["1497", "I7N3G6G", "1497", 0, "no description", 60.2, "random"], ["1498", "I7N3G6G", "1498", 0, "no description", 60.2, "random"], ["1499", "I7N3G6G", "1499", 0, "no description", 60.2, "random"], ["1500", "I7N3G6G", "1500", 0, "no description", 60.2, "random"], ["1501", "I7N3G6G", "1501", 0, "no description", 60.2, "random"], ["1502", "I7N3G6G", "1502", 0, "no description", 60.2, "random"], ["1503", "I7N3G6G", "1503", 0, "no description", 60.2, "random"], ["1504", "I7N3G6G", "1504", 0, "no description", 60.2, "random"], ["1505", "I7N3G6G", "1505", 0, "no description", 60.2, "random"], ["1506", "I7N3G6G", "1506", 0, "no description", 60.2, "random"], ["1507", "I7N3G6G", "1507", 0, "no description", 60.2, "random"], ["1508", "I7N3G6G", "1508", 0, "no description", 60.2, "random"], ["1509", "I7N3G6G", "1509", 0, "no description", 60.2, "random"], ["1510", "I7N3G6G", "1510", 0, "no description", 60.2, "random"], ["1511", "I7N3G6G", "1511", 0, "no description", 60.2, "random"], ["1512", "I7N3G6G", "1512", 0, "no description", 60.2, "random"], ["1513", "I7N3G6G", "1513", 0, "no description", 60.2, "random"], ["1514", "I7N3G6G", "1514", 0, "no description", 60.2, "random"], ["1515", "I7N3G6G", "1515", 0, "no description", 60.2, "random"], ["1516", "I7N3G6G", "1516", 0, "no description", 60.2, "random"], ["1517", "I7N3G6G", "1517", 0, "no description", 60.2, "random"], ["1518", "I7N3G6G", "1518", 0, "no description", 60.2, "random"], ["1519", "I7N3G6G", "1519", 0, "no description", 60.2, "random"], ["1520", "I7N3G6G", "1520", 0, "no description", 60.2, "random"], ["1521", "I7N3G6G", "1521", 0, "no description", 60.2, "random"], ["1522", "I7N3G6G", "1522", 0, "no description", 60.2, "random"], ["1523", "I7N3G6G", "1523", 0, "no description", 60.2, "random"], ["1524", "I7N3G6G", "1524", 0, "no description", 60.2, "random"], ["1525", "I7N3G6G", "1525", 0, "no description", 60.2, "random"], ["1526", "I7N3G6G", "1526", 0, "no description", 60.2, "random"], ["1527", "I7N3G6G", "1527", 0, "no description", 60.2, "random"], ["1528", "I7N3G6G", "1528", 0, "no description", 60.2, "random"], ["1529", "I7N3G6G", "1529", 0, "no description", 60.2, "random"], ["1530", "I7N3G6G", "1530", 0, "no description", 60.2, "random"], ["1531", "I7N3G6G", "1531", 0, "no description", 60.2, "random"], ["1532", "I7N3G6G", "1532", 0, "no description", 60.2, "random"], ["1533", "I7N3G6G", "1533", 0, "no description", 60.2, "random"], ["1534", "I7N3G6G", "1534", 0, "no description", 60.2, "random"], ["1535", "I7N3G6G", "1535", 0, "no description", 60.2, "random"], ["1536", "I7N3G6G", "1536", 0, "no description", 60.2, "random"], ["1537", "I7N3G6G", "1537", 0, "no description", 60.2, "random"], ["1538", "I7N3G6G", "1538", 0, "no description", 60.2, "random"], ["1539", "I7N3G6G", "1539", 0, "no description", 60.2, "random"], ["1540", "I7N3G6G", "1540", 0, "no description", 60.2, "random"], ["1541", "I7N3G6G", "1541", 0, "no description", 60.2, "random"], ["1542", "I7N3G6G", "1542", 0, "no description", 60.2, "random"], ["1543", "I7N3G6G", "1543", 0, "no description", 60.2, "random"], ["1544", "I7N3G6G", "1544", 0, "no description", 60.2, "random"], ["1545", "I7N3G6G", "1545", 0, "no description", 60.2, "random"], ["1546", "I7N3G6G", "1546", 0, "no description", 60.2, "random"], ["1547", "I7N3G6G", "1547", 0, "no description", 60.2, "random"], ["1548", "I7N3G6G", "1548", 0, "no description", 60.2, "random"], ["1549", "I7N3G6G", "1549", 0, "no description", 60.2, "random"], ["1550", "I7N3G6G", "1550", 0, "no description", 60.2, "random"], ["1551", "I7N3G6G", "1551", 0, "no description", 60.2, "random"], ["1552", "I7N3G6G", "1552", 0, "no description", 60.2, "random"], ["1553", "I7N3G6G", "1553", 0, "no description", 60.2, "random"], ["1554", "I7N3G6G", "1554", 0, "no description", 60.2, "random"], ["1555", "I7N3G6G", "1555", 0, "no description", 60.2, "random"], ["1556", "I7N3G6G", "1556", 0, "no description", 60.2, "random"], ["1557", "I7N3G6G", "1557", 0, "no description", 60.2, "random"], ["1558", "I7N3G6G", "1558", 0, "no description", 60.2, "random"], ["1559", "I7N3G6G", "1559", 0, "no description", 60.2, "random"], ["1560", "I7N3G6G", "1560", 0, "no description", 60.2, "random"], ["1561", "I7N3G6G", "1561", 0, "no description", 60.2, "random"], ["1562", "I7N3G6G", "1562", 0, "no description", 60.2, "random"], ["1563", "I7N3G6G", "1563", 0, "no description", 60.2, "random"], ["1564", "I7N3G6G", "1564", 0, "no description", 60.2, "random"], ["1565", "I7N3G6G", "1565", 0, "no description", 60.2, "random"], ["1566", "I7N3G6G", "1566", 0, "no description", 60.2, "random"], ["1567", "I7N3G6G", "1567", 0, "no description", 60.2, "random"], ["1568", "I7N3G6G", "1568", 0, "no description", 60.2, "random"], ["1569", "I7N3G6G", "1569", 0, "no description", 60.2, "random"], ["1570", "I7N3G6G", "1570", 0, "no description", 60.2, "random"], ["1571", "I7N3G6G", "1571", 0, "no description", 60.2, "random"], ["1572", "I7N3G6G", "1572", 0, "no description", 60.2, "random"], ["1573", "I7N3G6G", "1573", 0, "no description", 60.2, "random"], ["1574", "I7N3G6G", "1574", 0, "no description", 60.2, "random"], ["1575", "I7N3G6G", "1575", 0, "no description", 60.2, "random"], ["1576", "I7N3G6G", "1576", 0, "no description", 60.2, "random"], ["1577", "I7N3G6G", "1577", 0, "no description", 60.2, "random"], ["1578", "I7N3G6G", "1578", 0, "no description", 60.2, "random"], ["1579", "I7N3G6G", "1579", 0, "no description", 60.2, "random"], ["1580", "I7N3G6G", "1580", 0, "no description", 60.2, "random"], ["1581", "I7N3G6G", "1581", 0, "no description", 60.2, "random"], ["1582", "I7N3G6G", "1582", 0, "no description", 60.2, "random"], ["1583", "I7N3G6G", "1583", 0, "no description", 60.2, "random"], ["1584", "I7N3G6G", "1584", 0, "no description", 60.2, "random"], ["1585", "I7N3G6G", "1585", 0, "no description", 60.2, "random"], ["1586", "I7N3G6G", "1586", 0, "no description", 60.2, "random"], ["1587", "I7N3G6G", "1587", 0, "no description", 60.2, "random"], ["1588", "I7N3G6G", "1588", 0, "no description", 60.2, "random"], ["1589", "I7N3G6G", "1589", 0, "no description", 60.2, "random"], ["1590", "I7N3G6G", "1590", 0, "no description", 60.2, "random"], ["1591", "I7N3G6G", "1591", 0, "no description", 60.2, "random"], ["1592", "I7N3G6G", "1592", 0, "no description", 60.2, "random"], ["1593", "I7N3G6G", "1593", 0, "no description", 60.2, "random"], ["1594", "I7N3G6G", "1594", 0, "no description", 60.2, "random"], ["1595", "I7N3G6G", "1595", 0, "no description", 60.2, "random"], ["1596", "I7N3G6G", "1596", 0, "no description", 60.2, "random"], ["1597", "I7N3G6G", "1597", 0, "no description", 60.2, "random"], ["1598", "I7N3G6G", "1598", 0, "no description", 60.2, "random"], ["1599", "I7N3G6G", "1599", 0, "no description", 60.2, "random"], ["1600", "I7N3G6G", "1600", 0, "no description", 60.2, "random"], ["1601", "I7N3G6G", "1601", 0, "no description", 60.2, "random"], ["1602", "I7N3G6G", "1602", 0, "no description", 60.2, "random"], ["1603", "I7N3G6G", "1603", 0, "no description", 60.2, "random"], ["1604", "I7N3G6G", "1604", 0, "no description", 60.2, "random"], ["1605", "I7N3G6G", "1605", 0, "no description", 60.2, "random"], ["1606", "I7N3G6G", "1606", 0, "no description", 60.2, "random"], ["1607", "I7N3G6G", "1607", 0, "no description", 60.2, "random"], ["1608", "I7N3G6G", "1608", 0, "no description", 60.2, "random"], ["1609", "I7N3G6G", "1609", 0, "no description", 60.2, "random"], ["1610", "I7N3G6G", "1610", 0, "no description", 60.2, "random"], ["1611", "I7N3G6G", "1611", 0, "no description", 60.2, "random"], ["1612", "I7N3G6G", "1612", 0, "no description", 60.2, "random"], ["1613", "I7N3G6G", "1613", 0, "no description", 60.2, "random"], ["1614", "I7N3G6G", "1614", 0, "no description", 60.2, "random"], ["1615", "I7N3G6G", "1615", 0, "no description", 60.2, "random"], ["1616", "I7N3G6G", "1616", 0, "no description", 60.2, "random"], ["1617", "I7N3G6G", "1617", 0, "no description", 60.2, "random"], ["1618", "I7N3G6G", "1618", 0, "no description", 60.2, "random"], ["1619", "I7N3G6G", "1619", 0, "no description", 60.2, "random"], ["1620", "I7N3G6G", "1620", 0, "no description", 60.2, "random"], ["1621", "I7N3G6G", "1621", 0, "no description", 60.2, "random"], ["1622", "I7N3G6G", "1622", 0, "no description", 60.2, "random"], ["1623", "I7N3G6G", "1623", 0, "no description", 60.2, "random"], ["1624", "I7N3G6G", "1624", 0, "no description", 60.2, "random"], ["1625", "I7N3G6G", "1625", 0, "no description", 60.2, "random"], ["1626", "I7N3G6G", "1626", 0, "no description", 60.2, "random"], ["1627", "I7N3G6G", "1627", 0, "no description", 60.2, "random"], ["1628", "I7N3G6G", "1628", 0, "no description", 60.2, "random"], ["1629", "I7N3G6G", "1629", 0, "no description", 60.2, "random"], ["1630", "I7N3G6G", "1630", 0, "no description", 60.2, "random"], ["1631", "I7N3G6G", "1631", 0, "no description", 60.2, "random"], ["1632", "I7N3G6G", "1632", 0, "no description", 60.2, "random"], ["1633", "I7N3G6G", "1633", 0, "no description", 60.2, "random"], ["1634", "I7N3G6G", "1634", 0, "no description", 60.2, "random"], ["1635", "I7N3G6G", "1635", 0, "no description", 60.2, "random"], ["1636", "I7N3G6G", "1636", 0, "no description", 60.2, "random"], ["1637", "I7N3G6G", "1637", 0, "no description", 60.2, "random"], ["1638", "I7N3G6G", "1638", 0, "no description", 60.2, "random"], ["1639", "I7N3G6G", "1639", 0, "no description", 60.2, "random"], ["1640", "I7N3G6G", "1640", 0, "no description", 60.2, "random"], ["1641", "I7N3G6G", "1641", 0, "no description", 60.2, "random"], ["1642", "I7N3G6G", "1642", 0, "no description", 60.2, "random"], ["1643", "I7N3G6G", "1643", 0, "no description", 60.2, "random"], ["1644", "I7N3G6G", "1644", 0, "no description", 60.2, "random"], ["1645", "I7N3G6G", "1645", 0, "no description", 60.2, "random"], ["1646", "I7N3G6G", "1646", 0, "no description", 60.2, "random"], ["1647", "I7N3G6G", "1647", 0, "no description", 60.2, "random"], ["1648", "I7N3G6G", "1648", 0, "no description", 60.2, "random"], ["1649", "I7N3G6G", "1649", 0, "no description", 60.2, "random"], ["1650", "I7N3G6G", "1650", 0, "no description", 60.2, "random"], ["1651", "I7N3G6G", "1651", 0, "no description", 60.2, "random"], ["1652", "I7N3G6G", "1652", 0, "no description", 60.2, "random"], ["1653", "I7N3G6G", "1653", 0, "no description", 60.2, "random"], ["1654", "I7N3G6G", "1654", 0, "no description", 60.2, "random"], ["1655", "I7N3G6G", "1655", 0, "no description", 60.2, "random"], ["1656", "I7N3G6G", "1656", 0, "no description", 60.2, "random"], ["1657", "I7N3G6G", "1657", 0, "no description", 60.2, "random"], ["1658", "I7N3G6G", "1658", 0, "no description", 60.2, "random"], ["1659", "I7N3G6G", "1659", 0, "no description", 60.2, "random"], ["1660", "I7N3G6G", "1660", 0, "no description", 60.2, "random"], ["1661", "I7N3G6G", "1661", 0, "no description", 60.2, "random"], ["1662", "I7N3G6G", "1662", 0, "no description", 60.2, "random"], ["1663", "I7N3G6G", "1663", 0, "no description", 60.2, "random"], ["1664", "I7N3G6G", "1664", 0, "no description", 60.2, "random"], ["1665", "I7N3G6G", "1665", 0, "no description", 60.2, "random"], ["1666", "I7N3G6G", "1666", 0, "no description", 60.2, "random"], ["1667", "I7N3G6G", "1667", 0, "no description", 60.2, "random"], ["1668", "I7N3G6G", "1668", 0, "no description", 60.2, "random"], ["1669", "I7N3G6G", "1669", 0, "no description", 60.2, "random"], ["1670", "I7N3G6G", "1670", 0, "no description", 60.2, "random"], ["1671", "I7N3G6G", "1671", 0, "no description", 60.2, "random"], ["1672", "I7N3G6G", "1672", 0, "no description", 60.2, "random"], ["1673", "I7N3G6G", "1673", 0, "no description", 60.2, "random"], ["1674", "I7N3G6G", "1674", 0, "no description", 60.2, "random"], ["1675", "I7N3G6G", "1675", 0, "no description", 60.2, "random"], ["1676", "I7N3G6G", "1676", 0, "no description", 60.2, "random"], ["1677", "I7N3G6G", "1677", 0, "no description", 60.2, "random"], ["1678", "I7N3G6G", "1678", 0, "no description", 60.2, "random"], ["1679", "I7N3G6G", "1679", 0, "no description", 60.2, "random"], ["1680", "I7N3G6G", "1680", 0, "no description", 60.2, "random"], ["1681", "I7N3G6G", "1681", 0, "no description", 60.2, "random"], ["1682", "I7N3G6G", "1682", 0, "no description", 60.2, "random"], ["1683", "I7N3G6G", "1683", 0, "no description", 60.2, "random"], ["1684", "I7N3G6G", "1684", 0, "no description", 60.2, "random"], ["1685", "I7N3G6G", "1685", 0, "no description", 60.2, "random"], ["1686", "I7N3G6G", "1686", 0, "no description", 60.2, "random"], ["1687", "I7N3G6G", "1687", 0, "no description", 60.2, "random"], ["1688", "I7N3G6G", "1688", 0, "no description", 60.2, "random"], ["1689", "I7N3G6G", "1689", 0, "no description", 60.2, "random"], ["1690", "I7N3G6G", "1690", 0, "no description", 60.2, "random"], ["1691", "I7N3G6G", "1691", 0, "no description", 60.2, "random"], ["1692", "I7N3G6G", "1692", 0, "no description", 60.2, "random"], ["1693", "I7N3G6G", "1693", 0, "no description", 60.2, "random"], ["1694", "I7N3G6G", "1694", 0, "no description", 60.2, "random"], ["1695", "I7N3G6G", "1695", 0, "no description", 60.2, "random"], ["1696", "I7N3G6G", "1696", 0, "no description", 60.2, "random"], ["1697", "I7N3G6G", "1697", 0, "no description", 60.2, "random"], ["1698", "I7N3G6G", "1698", 0, "no description", 60.2, "random"], ["1699", "I7N3G6G", "1699", 0, "no description", 60.2, "random"], ["1700", "I7N3G6G", "1700", 0, "no description", 60.2, "random"], ["1701", "I7N3G6G", "1701", 0, "no description", 60.2, "random"], ["1702", "I7N3G6G", "1702", 0, "no description", 60.2, "random"], ["1703", "I7N3G6G", "1703", 0, "no description", 60.2, "random"], ["1704", "I7N3G6G", "1704", 0, "no description", 60.2, "random"], ["1705", "I7N3G6G", "1705", 0, "no description", 60.2, "random"], ["1706", "I7N3G6G", "1706", 0, "no description", 60.2, "random"], ["1707", "I7N3G6G", "1707", 0, "no description", 60.2, "random"], ["1708", "I7N3G6G", "1708", 0, "no description", 60.2, "random"], ["1709", "I7N3G6G", "1709", 0, "no description", 60.2, "random"], ["1710", "I7N3G6G", "1710", 0, "no description", 60.2, "random"], ["1711", "I7N3G6G", "1711", 0, "no description", 60.2, "random"], ["1712", "I7N3G6G", "1712", 0, "no description", 60.2, "random"], ["1713", "I7N3G6G", "1713", 0, "no description", 60.2, "random"], ["1714", "I7N3G6G", "1714", 0, "no description", 60.2, "random"], ["1715", "I7N3G6G", "1715", 0, "no description", 60.2, "random"], ["1716", "I7N3G6G", "1716", 0, "no description", 60.2, "random"], ["1717", "I7N3G6G", "1717", 0, "no description", 60.2, "random"], ["1718", "I7N3G6G", "1718", 0, "no description", 60.2, "random"], ["1719", "I7N3G6G", "1719", 0, "no description", 60.2, "random"], ["1720", "I7N3G6G", "1720", 0, "no description", 60.2, "random"], ["1721", "I7N3G6G", "1721", 0, "no description", 60.2, "random"], ["1722", "I7N3G6G", "1722", 0, "no description", 60.2, "random"], ["1723", "I7N3G6G", "1723", 0, "no description", 60.2, "random"], ["1724", "I7N3G6G", "1724", 0, "no description", 60.2, "random"], ["1725", "I7N3G6G", "1725", 0, "no description", 60.2, "random"], ["1726", "I7N3G6G", "1726", 0, "no description", 60.2, "random"], ["1727", "I7N3G6G", "1727", 0, "no description", 60.2, "random"], ["1728", "I7N3G6G", "1728", 0, "no description", 60.2, "random"], ["1729", "I7N3G6G", "1729", 0, "no description", 60.2, "random"], ["1730", "I7N3G6G", "1730", 0, "no description", 60.2, "random"], ["1731", "I7N3G6G", "1731", 0, "no description", 60.2, "random"], ["1732", "I7N3G6G", "1732", 0, "no description", 60.2, "random"], ["1733", "I7N3G6G", "1733", 0, "no description", 60.2, "random"], ["1734", "I7N3G6G", "1734", 0, "no description", 60.2, "random"], ["1735", "I7N3G6G", "1735", 0, "no description", 60.2, "random"], ["1736", "I7N3G6G", "1736", 0, "no description", 60.2, "random"], ["1737", "I7N3G6G", "1737", 0, "no description", 60.2, "random"], ["1738", "I7N3G6G", "1738", 0, "no description", 60.2, "random"], ["1739", "I7N3G6G", "1739", 0, "no description", 60.2, "random"], ["1740", "I7N3G6G", "1740", 0, "no description", 60.2, "random"], ["1741", "I7N3G6G", "1741", 0, "no description", 60.2, "random"], ["1742", "I7N3G6G", "1742", 0, "no description", 60.2, "random"], ["1743", "I7N3G6G", "1743", 0, "no description", 60.2, "random"], ["1744", "I7N3G6G", "1744", 0, "no description", 60.2, "random"], ["1745", "I7N3G6G", "1745", 0, "no description", 60.2, "random"], ["1746", "I7N3G6G", "1746", 0, "no description", 60.2, "random"], ["1747", "I7N3G6G", "1747", 0, "no description", 60.2, "random"], ["1748", "I7N3G6G", "1748", 0, "no description", 60.2, "random"], ["1749", "I7N3G6G", "1749", 0, "no description", 60.2, "random"], ["1750", "I7N3G6G", "1750", 0, "no description", 60.2, "random"], ["1751", "I7N3G6G", "1751", 0, "no description", 60.2, "random"], ["1752", "I7N3G6G", "1752", 0, "no description", 60.2, "random"], ["1753", "I7N3G6G", "1753", 0, "no description", 60.2, "random"], ["1754", "I7N3G6G", "1754", 0, "no description", 60.2, "random"], ["1755", "I7N3G6G", "1755", 0, "no description", 60.2, "random"], ["1756", "I7N3G6G", "1756", 0, "no description", 60.2, "random"], ["1757", "I7N3G6G", "1757", 0, "no description", 60.2, "random"], ["1758", "I7N3G6G", "1758", 0, "no description", 60.2, "random"], ["1759", "I7N3G6G", "1759", 0, "no description", 60.2, "random"], ["1760", "I7N3G6G", "1760", 0, "no description", 60.2, "random"], ["1761", "I7N3G6G", "1761", 0, "no description", 60.2, "random"], ["1762", "I7N3G6G", "1762", 0, "no description", 60.2, "random"], ["1763", "I7N3G6G", "1763", 0, "no description", 60.2, "random"], ["1764", "I7N3G6G", "1764", 0, "no description", 60.2, "random"], ["1765", "I7N3G6G", "1765", 0, "no description", 60.2, "random"], ["1766", "I7N3G6G", "1766", 0, "no description", 60.2, "random"], ["1767", "I7N3G6G", "1767", 0, "no description", 60.2, "random"], ["1768", "I7N3G6G", "1768", 0, "no description", 60.2, "random"], ["1769", "I7N3G6G", "1769", 0, "no description", 60.2, "random"], ["1770", "I7N3G6G", "1770", 0, "no description", 60.2, "random"], ["1771", "I7N3G6G", "1771", 0, "no description", 60.2, "random"], ["1772", "I7N3G6G", "1772", 0, "no description", 60.2, "random"], ["1773", "I7N3G6G", "1773", 0, "no description", 60.2, "random"], ["1774", "I7N3G6G", "1774", 0, "no description", 60.2, "random"], ["1775", "I7N3G6G", "1775", 0, "no description", 60.2, "random"], ["1776", "I7N3G6G", "1776", 0, "no description", 60.2, "random"], ["1777", "I7N3G6G", "1777", 0, "no description", 60.2, "random"], ["1778", "I7N3G6G", "1778", 0, "no description", 60.2, "random"], ["1779", "I7N3G6G", "1779", 0, "no description", 60.2, "random"], ["1780", "I7N3G6G", "1780", 0, "no description", 60.2, "random"], ["1781", "I7N3G6G", "1781", 0, "no description", 60.2, "random"], ["1782", "I7N3G6G", "1782", 0, "no description", 60.2, "random"], ["1783", "I7N3G6G", "1783", 0, "no description", 60.2, "random"], ["1784", "I7N3G6G", "1784", 0, "no description", 60.2, "random"], ["1785", "I7N3G6G", "1785", 0, "no description", 60.2, "random"], ["1786", "I7N3G6G", "1786", 0, "no description", 60.2, "random"], ["1787", "I7N3G6G", "1787", 0, "no description", 60.2, "random"], ["1788", "I7N3G6G", "1788", 0, "no description", 60.2, "random"], ["1789", "I7N3G6G", "1789", 0, "no description", 60.2, "random"], ["1790", "I7N3G6G", "1790", 0, "no description", 60.2, "random"], ["1791", "I7N3G6G", "1791", 0, "no description", 60.2, "random"], ["1792", "I7N3G6G", "1792", 0, "no description", 60.2, "random"], ["1793", "I7N3G6G", "1793", 0, "no description", 60.2, "random"], ["1794", "I7N3G6G", "1794", 0, "no description", 60.2, "random"], ["1795", "I7N3G6G", "1795", 0, "no description", 60.2, "random"], ["1796", "I7N3G6G", "1796", 0, "no description", 60.2, "random"], ["1797", "I7N3G6G", "1797", 0, "no description", 60.2, "random"], ["1798", "I7N3G6G", "1798", 0, "no description", 60.2, "random"], ["1799", "I7N3G6G", "1799", 0, "no description", 60.2, "random"], ["1800", "I7N3G6G", "1800", 0, "no description", 60.2, "random"], ["1801", "I7N3G6G", "1801", 0, "no description", 60.2, "random"], ["1802", "I7N3G6G", "1802", 0, "no description", 60.2, "random"], ["1803", "I7N3G6G", "1803", 0, "no description", 60.2, "random"], ["1804", "I7N3G6G", "1804", 0, "no description", 60.2, "random"], ["1805", "I7N3G6G", "1805", 0, "no description", 60.2, "random"], ["1806", "I7N3G6G", "1806", 0, "no description", 60.2, "random"], ["1807", "I7N3G6G", "1807", 0, "no description", 60.2, "random"], ["1808", "I7N3G6G", "1808", 0, "no description", 60.2, "random"], ["1809", "I7N3G6G", "1809", 0, "no description", 60.2, "random"], ["1810", "I7N3G6G", "1810", 0, "no description", 60.2, "random"], ["1811", "I7N3G6G", "1811", 0, "no description", 60.2, "random"], ["1812", "I7N3G6G", "1812", 0, "no description", 60.2, "random"], ["1813", "I7N3G6G", "1813", 0, "no description", 60.2, "random"], ["1814", "I7N3G6G", "1814", 0, "no description", 60.2, "random"], ["1815", "I7N3G6G", "1815", 0, "no description", 60.2, "random"], ["1816", "I7N3G6G", "1816", 0, "no description", 60.2, "random"], ["1817", "I7N3G6G", "1817", 0, "no description", 60.2, "random"], ["1818", "I7N3G6G", "1818", 0, "no description", 60.2, "random"], ["1819", "I7N3G6G", "1819", 0, "no description", 60.2, "random"], ["1820", "I7N3G6G", "1820", 0, "no description", 60.2, "random"], ["1821", "I7N3G6G", "1821", 0, "no description", 60.2, "random"], ["1822", "I7N3G6G", "1822", 0, "no description", 60.2, "random"], ["1823", "I7N3G6G", "1823", 0, "no description", 60.2, "random"], ["1824", "I7N3G6G", "1824", 0, "no description", 60.2, "random"], ["1825", "I7N3G6G", "1825", 0, "no description", 60.2, "random"], ["1826", "I7N3G6G", "1826", 0, "no description", 60.2, "random"], ["1827", "I7N3G6G", "1827", 0, "no description", 60.2, "random"], ["1828", "I7N3G6G", "1828", 0, "no description", 60.2, "random"], ["1829", "I7N3G6G", "1829", 0, "no description", 60.2, "random"], ["1830", "I7N3G6G", "1830", 0, "no description", 60.2, "random"], ["1831", "I7N3G6G", "1831", 0, "no description", 60.2, "random"], ["1832", "I7N3G6G", "1832", 0, "no description", 60.2, "random"], ["1833", "I7N3G6G", "1833", 0, "no description", 60.2, "random"], ["1834", "I7N3G6G", "1834", 0, "no description", 60.2, "random"], ["1835", "I7N3G6G", "1835", 0, "no description", 60.2, "random"], ["1836", "I7N3G6G", "1836", 0, "no description", 60.2, "random"], ["1837", "I7N3G6G", "1837", 0, "no description", 60.2, "random"], ["1838", "I7N3G6G", "1838", 0, "no description", 60.2, "random"], ["1839", "I7N3G6G", "1839", 0, "no description", 60.2, "random"], ["1840", "I7N3G6G", "1840", 0, "no description", 60.2, "random"], ["1841", "I7N3G6G", "1841", 0, "no description", 60.2, "random"], ["1842", "I7N3G6G", "1842", 0, "no description", 60.2, "random"], ["1843", "I7N3G6G", "1843", 0, "no description", 60.2, "random"], ["1844", "I7N3G6G", "1844", 0, "no description", 60.2, "random"], ["1845", "I7N3G6G", "1845", 0, "no description", 60.2, "random"], ["1846", "I7N3G6G", "1846", 0, "no description", 60.2, "random"], ["1847", "I7N3G6G", "1847", 0, "no description", 60.2, "random"], ["1848", "I7N3G6G", "1848", 0, "no description", 60.2, "random"], ["1849", "I7N3G6G", "1849", 0, "no description", 60.2, "random"], ["1850", "I7N3G6G", "1850", 0, "no description", 60.2, "random"], ["1851", "I7N3G6G", "1851", 0, "no description", 60.2, "random"], ["1852", "I7N3G6G", "1852", 0, "no description", 60.2, "random"], ["1853", "I7N3G6G", "1853", 0, "no description", 60.2, "random"], ["1854", "I7N3G6G", "1854", 0, "no description", 60.2, "random"], ["1855", "I7N3G6G", "1855", 0, "no description", 60.2, "random"], ["1856", "I7N3G6G", "1856", 0, "no description", 60.2, "random"], ["1857", "I7N3G6G", "1857", 0, "no description", 60.2, "random"], ["1858", "I7N3G6G", "1858", 0, "no description", 60.2, "random"], ["1859", "I7N3G6G", "1859", 0, "no description", 60.2, "random"], ["1860", "I7N3G6G", "1860", 0, "no description", 60.2, "random"], ["1861", "I7N3G6G", "1861", 0, "no description", 60.2, "random"], ["1862", "I7N3G6G", "1862", 0, "no description", 60.2, "random"], ["1863", "I7N3G6G", "1863", 0, "no description", 60.2, "random"], ["1864", "I7N3G6G", "1864", 0, "no description", 60.2, "random"], ["1865", "I7N3G6G", "1865", 0, "no description", 60.2, "random"], ["1866", "I7N3G6G", "1866", 0, "no description", 60.2, "random"], ["1867", "I7N3G6G", "1867", 0, "no description", 60.2, "random"], ["1868", "I7N3G6G", "1868", 0, "no description", 60.2, "random"], ["1869", "I7N3G6G", "1869", 0, "no description", 60.2, "random"], ["1870", "I7N3G6G", "1870", 0, "no description", 60.2, "random"], ["1871", "I7N3G6G", "1871", 0, "no description", 60.2, "random"], ["1872", "I7N3G6G", "1872", 0, "no description", 60.2, "random"], ["1873", "I7N3G6G", "1873", 0, "no description", 60.2, "random"], ["1874", "I7N3G6G", "1874", 0, "no description", 60.2, "random"], ["1875", "I7N3G6G", "1875", 0, "no description", 60.2, "random"], ["1876", "I7N3G6G", "1876", 0, "no description", 60.2, "random"], ["1877", "I7N3G6G", "1877", 0, "no description", 60.2, "random"], ["1878", "I7N3G6G", "1878", 0, "no description", 60.2, "random"], ["1879", "I7N3G6G", "1879", 0, "no description", 60.2, "random"], ["1880", "I7N3G6G", "1880", 0, "no description", 60.2, "random"], ["1881", "I7N3G6G", "1881", 0, "no description", 60.2, "random"], ["1882", "I7N3G6G", "1882", 0, "no description", 60.2, "random"], ["1883", "I7N3G6G", "1883", 0, "no description", 60.2, "random"], ["1884", "I7N3G6G", "1884", 0, "no description", 60.2, "random"], ["1885", "I7N3G6G", "1885", 0, "no description", 60.2, "random"], ["1886", "I7N3G6G", "1886", 0, "no description", 60.2, "random"], ["1887", "I7N3G6G", "1887", 0, "no description", 60.2, "random"], ["1888", "I7N3G6G", "1888", 0, "no description", 60.2, "random"], ["1889", "I7N3G6G", "1889", 0, "no description", 60.2, "random"], ["1890", "I7N3G6G", "1890", 0, "no description", 60.2, "random"], ["1891", "I7N3G6G", "1891", 0, "no description", 60.2, "random"], ["1892", "I7N3G6G", "1892", 0, "no description", 60.2, "random"], ["1893", "I7N3G6G", "1893", 0, "no description", 60.2, "random"], ["1894", "I7N3G6G", "1894", 0, "no description", 60.2, "random"], ["1895", "I7N3G6G", "1895", 0, "no description", 60.2, "random"], ["1896", "I7N3G6G", "1896", 0, "no description", 60.2, "random"], ["1897", "I7N3G6G", "1897", 0, "no description", 60.2, "random"], ["1898", "I7N3G6G", "1898", 0, "no description", 60.2, "random"], ["1899", "I7N3G6G", "1899", 0, "no description", 60.2, "random"], ["1900", "I7N3G6G", "1900", 0, "no description", 60.2, "random"], ["1901", "I7N3G6G", "1901", 0, "no description", 60.2, "random"], ["1902", "I7N3G6G", "1902", 0, "no description", 60.2, "random"], ["1903", "I7N3G6G", "1903", 0, "no description", 60.2, "random"], ["1904", "I7N3G6G", "1904", 0, "no description", 60.2, "random"], ["1905", "I7N3G6G", "1905", 0, "no description", 60.2, "random"], ["1906", "I7N3G6G", "1906", 0, "no description", 60.2, "random"], ["1907", "I7N3G6G", "1907", 0, "no description", 60.2, "random"], ["1908", "I7N3G6G", "1908", 0, "no description", 60.2, "random"], ["1909", "I7N3G6G", "1909", 0, "no description", 60.2, "random"], ["1910", "I7N3G6G", "1910", 0, "no description", 60.2, "random"], ["1911", "I7N3G6G", "1911", 0, "no description", 60.2, "random"], ["1912", "I7N3G6G", "1912", 0, "no description", 60.2, "random"], ["1913", "I7N3G6G", "1913", 0, "no description", 60.2, "random"], ["1914", "I7N3G6G", "1914", 0, "no description", 60.2, "random"], ["1915", "I7N3G6G", "1915", 0, "no description", 60.2, "random"], ["1916", "I7N3G6G", "1916", 0, "no description", 60.2, "random"], ["1917", "I7N3G6G", "1917", 0, "no description", 60.2, "random"], ["1918", "I7N3G6G", "1918", 0, "no description", 60.2, "random"], ["1919", "I7N3G6G", "1919", 0, "no description", 60.2, "random"], ["1920", "I7N3G6G", "1920", 0, "no description", 60.2, "random"], ["1921", "I7N3G6G", "1921", 0, "no description", 60.2, "random"], ["1922", "I7N3G6G", "1922", 0, "no description", 60.2, "random"], ["1923", "I7N3G6G", "1923", 0, "no description", 60.2, "random"], ["1924", "I7N3G6G", "1924", 0, "no description", 60.2, "random"], ["1925", "I7N3G6G", "1925", 0, "no description", 60.2, "random"], ["1926", "I7N3G6G", "1926", 0, "no description", 60.2, "random"], ["1927", "I7N3G6G", "1927", 0, "no description", 60.2, "random"], ["1928", "I7N3G6G", "1928", 0, "no description", 60.2, "random"], ["1929", "I7N3G6G", "1929", 0, "no description", 60.2, "random"], ["1930", "I7N3G6G", "1930", 0, "no description", 60.2, "random"], ["1931", "I7N3G6G", "1931", 0, "no description", 60.2, "random"], ["1932", "I7N3G6G", "1932", 0, "no description", 60.2, "random"], ["1933", "I7N3G6G", "1933", 0, "no description", 60.2, "random"], ["1934", "I7N3G6G", "1934", 0, "no description", 60.2, "random"], ["1935", "I7N3G6G", "1935", 0, "no description", 60.2, "random"], ["1936", "I7N3G6G", "1936", 0, "no description", 60.2, "random"], ["1937", "I7N3G6G", "1937", 0, "no description", 60.2, "random"], ["1938", "I7N3G6G", "1938", 0, "no description", 60.2, "random"], ["1939", "I7N3G6G", "1939", 0, "no description", 60.2, "random"], ["1940", "I7N3G6G", "1940", 0, "no description", 60.2, "random"], ["1941", "I7N3G6G", "1941", 0, "no description", 60.2, "random"], ["1942", "I7N3G6G", "1942", 0, "no description", 60.2, "random"], ["1943", "I7N3G6G", "1943", 0, "no description", 60.2, "random"], ["1944", "I7N3G6G", "1944", 0, "no description", 60.2, "random"], ["1945", "I7N3G6G", "1945", 0, "no description", 60.2, "random"], ["1946", "I7N3G6G", "1946", 0, "no description", 60.2, "random"], ["1947", "I7N3G6G", "1947", 0, "no description", 60.2, "random"], ["1948", "I7N3G6G", "1948", 0, "no description", 60.2, "random"], ["1949", "I7N3G6G", "1949", 0, "no description", 60.2, "random"], ["1950", "I7N3G6G", "1950", 0, "no description", 60.2, "random"], ["1951", "I7N3G6G", "1951", 0, "no description", 60.2, "random"], ["1952", "I7N3G6G", "1952", 0, "no description", 60.2, "random"], ["1953", "I7N3G6G", "1953", 0, "no description", 60.2, "random"], ["1954", "I7N3G6G", "1954", 0, "no description", 60.2, "random"], ["1955", "I7N3G6G", "1955", 0, "no description", 60.2, "random"], ["1956", "I7N3G6G", "1956", 0, "no description", 60.2, "random"], ["1957", "I7N3G6G", "1957", 0, "no description", 60.2, "random"], ["1958", "I7N3G6G", "1958", 0, "no description", 60.2, "random"], ["1959", "I7N3G6G", "1959", 0, "no description", 60.2, "random"], ["1960", "I7N3G6G", "1960", 0, "no description", 60.2, "random"], ["1961", "I7N3G6G", "1961", 0, "no description", 60.2, "random"], ["1962", "I7N3G6G", "1962", 0, "no description", 60.2, "random"], ["1963", "I7N3G6G", "1963", 0, "no description", 60.2, "random"], ["1964", "I7N3G6G", "1964", 0, "no description", 60.2, "random"], ["1965", "I7N3G6G", "1965", 0, "no description", 60.2, "random"], ["1966", "I7N3G6G", "1966", 0, "no description", 60.2, "random"], ["1967", "I7N3G6G", "1967", 0, "no description", 60.2, "random"], ["1968", "I7N3G6G", "1968", 0, "no description", 60.2, "random"], ["1969", "I7N3G6G", "1969", 0, "no description", 60.2, "random"], ["1970", "I7N3G6G", "1970", 0, "no description", 60.2, "random"], ["1971", "I7N3G6G", "1971", 0, "no description", 60.2, "random"], ["1972", "I7N3G6G", "1972", 0, "no description", 60.2, "random"], ["1973", "I7N3G6G", "1973", 0, "no description", 60.2, "random"], ["1974", "I7N3G6G", "1974", 0, "no description", 60.2, "random"], ["1975", "I7N3G6G", "1975", 0, "no description", 60.2, "random"], ["1976", "I7N3G6G", "1976", 0, "no description", 60.2, "random"], ["1977", "I7N3G6G", "1977", 0, "no description", 60.2, "random"], ["1978", "I7N3G6G", "1978", 0, "no description", 60.2, "random"], ["1979", "I7N3G6G", "1979", 0, "no description", 60.2, "random"], ["1980", "I7N3G6G", "1980", 0, "no description", 60.2, "random"], ["1981", "I7N3G6G", "1981", 0, "no description", 60.2, "random"], ["1982", "I7N3G6G", "1982", 0, "no description", 60.2, "random"], ["1983", "I7N3G6G", "1983", 0, "no description", 60.2, "random"], ["1984", "I7N3G6G", "1984", 0, "no description", 60.2, "random"], ["1985", "I7N3G6G", "1985", 0, "no description", 60.2, "random"], ["1986", "I7N3G6G", "1986", 0, "no description", 60.2, "random"], ["1987", "I7N3G6G", "1987", 0, "no description", 60.2, "random"], ["1988", "I7N3G6G", "1988", 0, "no description", 60.2, "random"], ["1989", "I7N3G6G", "1989", 0, "no description", 60.2, "random"], ["1990", "I7N3G6G", "1990", 0, "no description", 60.2, "random"], ["1991", "I7N3G6G", "1991", 0, "no description", 60.2, "random"], ["1992", "I7N3G6G", "1992", 0, "no description", 60.2, "random"], ["1993", "I7N3G6G", "1993", 0, "no description", 60.2, "random"], ["1994", "I7N3G6G", "1994", 0, "no description", 60.2, "random"], ["1995", "I7N3G6G", "1995", 0, "no description", 60.2, "random"], ["1996", "I7N3G6G", "1996", 0, "no description", 60.2, "random"], ["1997", "I7N3G6G", "1997", 0, "no description", 60.2, "random"], ["1998", "I7N3G6G", "1998", 0, "no description", 60.2, "random"], ["1999", "I7N3G6G", "1999", 0, "no description", 60.2, "random"], ["2000", "I7N3G6G", "2000", 0, "no description", 60.2, "random"], ["2001", "I7N3G6G", "2001", 0, "no description", 60.2, "random"], ["2002", "I7N3G6G", "2002", 0, "no description", 60.2, "random"], ["2003", "I7N3G6G", "2003", 0, "no description", 60.2, "random"], ["2004", "I7N3G6G", "2004", 0, "no description", 60.2, "random"], ["2005", "I7N3G6G", "2005", 0, "no description", 60.2, "random"], ["2006", "I7N3G6G", "2006", 0, "no description", 60.2, "random"], ["2007", "I7N3G6G", "2007", 0, "no description", 60.2, "random"], ["2008", "I7N3G6G", "2008", 0, "no description", 60.2, "random"], ["2009", "I7N3G6G", "2009", 0, "no description", 60.2, "random"], ["2010", "I7N3G6G", "2010", 0, "no description", 60.2, "random"], ["2011", "I7N3G6G", "2011", 0, "no description", 60.2, "random"], ["2012", "I7N3G6G", "2012", 0, "no description", 60.2, "random"], ["2013", "I7N3G6G", "2013", 0, "no description", 60.2, "random"], ["2014", "I7N3G6G", "2014", 0, "no description", 60.2, "random"], ["2015", "I7N3G6G", "2015", 0, "no description", 60.2, "random"], ["2016", "I7N3G6G", "2016", 0, "no description", 60.2, "random"], ["2017", "I7N3G6G", "2017", 0, "no description", 60.2, "random"], ["2018", "I7N3G6G", "2018", 0, "no description", 60.2, "random"], ["2019", "I7N3G6G", "2019", 0, "no description", 60.2, "random"], ["2020", "I7N3G6G", "2020", 0, "no description", 60.2, "random"], ["2021", "I7N3G6G", "2021", 0, "no description", 60.2, "random"], ["2022", "I7N3G6G", "2022", 0, "no description", 60.2, "random"], ["2023", "I7N3G6G", "2023", 0, "no description", 60.2, "random"], ["2024", "I7N3G6G", "2024", 0, "no description", 60.2, "random"], ["2025", "I7N3G6G", "2025", 0, "no description", 60.2, "random"], ["2026", "I7N3G6G", "2026", 0, "no description", 60.2, "random"], ["2027", "I7N3G6G", "2027", 0, "no description", 60.2, "random"], ["2028", "I7N3G6G", "2028", 0, "no description", 60.2, "random"], ["2029", "I7N3G6G", "2029", 0, "no description", 60.2, "random"], ["2030", "I7N3G6G", "2030", 0, "no description", 60.2, "random"], ["2031", "I7N3G6G", "2031", 0, "no description", 60.2, "random"], ["2032", "I7N3G6G", "2032", 0, "no description", 60.2, "random"], ["2033", "I7N3G6G", "2033", 0, "no description", 60.2, "random"], ["2034", "I7N3G6G", "2034", 0, "no description", 60.2, "random"], ["2035", "I7N3G6G", "2035", 0, "no description", 60.2, "random"], ["2036", "I7N3G6G", "2036", 0, "no description", 60.2, "random"], ["2037", "I7N3G6G", "2037", 0, "no description", 60.2, "random"], ["2038", "I7N3G6G", "2038", 0, "no description", 60.2, "random"], ["2039", "I7N3G6G", "2039", 0, "no description", 60.2, "random"], ["2040", "I7N3G6G", "2040", 0, "no description", 60.2, "random"], ["2041", "I7N3G6G", "2041", 0, "no description", 60.2, "random"], ["2042", "I7N3G6G", "2042", 0, "no description", 60.2, "random"], ["2043", "I7N3G6G", "2043", 0, "no description", 60.2, "random"], ["2044", "I7N3G6G", "2044", 0, "no description", 60.2, "random"], ["2045", "I7N3G6G", "2045", 0, "no description", 60.2, "random"], ["2046", "I7N3G6G", "2046", 0, "no description", 60.2, "random"], ["2047", "I7N3G6G", "2047", 0, "no description", 60.2, "random"], ["2048", "I7N3G6G", "2048", 0, "no description", 60.2, "random"], ["2049", "I7N3G6G", "2049", 0, "no description", 60.2, "random"], ["2050", "I7N3G6G", "2050", 0, "no description", 60.2, "random"], ["2051", "I7N3G6G", "2051", 0, "no description", 60.2, "random"], ["2052", "I7N3G6G", "2052", 0, "no description", 60.2, "random"], ["2053", "I7N3G6G", "2053", 0, "no description", 60.2, "random"], ["2054", "I7N3G6G", "2054", 0, "no description", 60.2, "random"], ["2055", "I7N3G6G", "2055", 0, "no description", 60.2, "random"], ["2056", "I7N3G6G", "2056", 0, "no description", 60.2, "random"], ["2057", "I7N3G6G", "2057", 0, "no description", 60.2, "random"], ["2058", "I7N3G6G", "2058", 0, "no description", 60.2, "random"], ["2059", "I7N3G6G", "2059", 0, "no description", 60.2, "random"], ["2060", "I7N3G6G", "2060", 0, "no description", 60.2, "random"], ["2061", "I7N3G6G", "2061", 0, "no description", 60.2, "random"], ["2062", "I7N3G6G", "2062", 0, "no description", 60.2, "random"], ["2063", "I7N3G6G", "2063", 0, "no description", 60.2, "random"], ["2064", "I7N3G6G", "2064", 0, "no description", 60.2, "random"], ["2065", "I7N3G6G", "2065", 0, "no description", 60.2, "random"], ["2066", "I7N3G6G", "2066", 0, "no description", 60.2, "random"], ["2067", "I7N3G6G", "2067", 0, "no description", 60.2, "random"], ["2068", "I7N3G6G", "2068", 0, "no description", 60.2, "random"], ["2069", "I7N3G6G", "2069", 0, "no description", 60.2, "random"], ["2070", "I7N3G6G", "2070", 0, "no description", 60.2, "random"], ["2071", "I7N3G6G", "2071", 0, "no description", 60.2, "random"], ["2072", "I7N3G6G", "2072", 0, "no description", 60.2, "random"], ["2073", "I7N3G6G", "2073", 0, "no description", 60.2, "random"], ["2074", "I7N3G6G", "2074", 0, "no description", 60.2, "random"], ["2075", "I7N3G6G", "2075", 0, "no description", 60.2, "random"], ["2076", "I7N3G6G", "2076", 0, "no description", 60.2, "random"], ["2077", "I7N3G6G", "2077", 0, "no description", 60.2, "random"], ["2078", "I7N3G6G", "2078", 0, "no description", 60.2, "random"], ["2079", "I7N3G6G", "2079", 0, "no description", 60.2, "random"], ["2080", "I7N3G6G", "2080", 0, "no description", 60.2, "random"], ["2081", "I7N3G6G", "2081", 0, "no description", 60.2, "random"], ["2082", "I7N3G6G", "2082", 0, "no description", 60.2, "random"], ["2083", "I7N3G6G", "2083", 0, "no description", 60.2, "random"], ["2084", "I7N3G6G", "2084", 0, "no description", 60.2, "random"], ["2085", "I7N3G6G", "2085", 0, "no description", 60.2, "random"], ["2086", "I7N3G6G", "2086", 0, "no description", 60.2, "random"], ["2087", "I7N3G6G", "2087", 0, "no description", 60.2, "random"], ["2088", "I7N3G6G", "2088", 0, "no description", 60.2, "random"], ["2089", "I7N3G6G", "2089", 0, "no description", 60.2, "random"], ["2090", "I7N3G6G", "2090", 0, "no description", 60.2, "random"], ["2091", "I7N3G6G", "2091", 0, "no description", 60.2, "random"], ["2092", "I7N3G6G", "2092", 0, "no description", 60.2, "random"], ["2093", "I7N3G6G", "2093", 0, "no description", 60.2, "random"], ["2094", "I7N3G6G", "2094", 0, "no description", 60.2, "random"], ["2095", "I7N3G6G", "2095", 0, "no description", 60.2, "random"], ["2096", "I7N3G6G", "2096", 0, "no description", 60.2, "random"], ["2097", "I7N3G6G", "2097", 0, "no description", 60.2, "random"], ["2098", "I7N3G6G", "2098", 0, "no description", 60.2, "random"], ["2099", "I7N3G6G", "2099", 0, "no description", 60.2, "random"], ["2100", "I7N3G6G", "2100", 0, "no description", 60.2, "random"], ["2101", "I7N3G6G", "2101", 0, "no description", 60.2, "random"], ["2102", "I7N3G6G", "2102", 0, "no description", 60.2, "random"], ["2103", "I7N3G6G", "2103", 0, "no description", 60.2, "random"], ["2104", "I7N3G6G", "2104", 0, "no description", 60.2, "random"], ["2105", "I7N3G6G", "2105", 0, "no description", 60.2, "random"], ["2106", "I7N3G6G", "2106", 0, "no description", 60.2, "random"], ["2107", "I7N3G6G", "2107", 0, "no description", 60.2, "random"], ["2108", "I7N3G6G", "2108", 0, "no description", 60.2, "random"], ["2109", "I7N3G6G", "2109", 0, "no description", 60.2, "random"], ["2110", "I7N3G6G", "2110", 0, "no description", 60.2, "random"], ["2111", "I7N3G6G", "2111", 0, "no description", 60.2, "random"], ["2112", "I7N3G6G", "2112", 0, "no description", 60.2, "random"], ["2113", "I7N3G6G", "2113", 0, "no description", 60.2, "random"], ["2114", "I7N3G6G", "2114", 0, "no description", 60.2, "random"], ["2115", "I7N3G6G", "2115", 0, "no description", 60.2, "random"], ["2116", "I7N3G6G", "2116", 0, "no description", 60.2, "random"], ["2117", "I7N3G6G", "2117", 0, "no description", 60.2, "random"], ["2118", "I7N3G6G", "2118", 0, "no description", 60.2, "random"], ["2119", "I7N3G6G", "2119", 0, "no description", 60.2, "random"], ["2120", "I7N3G6G", "2120", 0, "no description", 60.2, "random"], ["2121", "I7N3G6G", "2121", 0, "no description", 60.2, "random"], ["2122", "I7N3G6G", "2122", 0, "no description", 60.2, "random"], ["2123", "I7N3G6G", "2123", 0, "no description", 60.2, "random"], ["2124", "I7N3G6G", "2124", 0, "no description", 60.2, "random"], ["2125", "I7N3G6G", "2125", 0, "no description", 60.2, "random"], ["2126", "I7N3G6G", "2126", 0, "no description", 60.2, "random"], ["2127", "I7N3G6G", "2127", 0, "no description", 60.2, "random"], ["2128", "I7N3G6G", "2128", 0, "no description", 60.2, "random"], ["2129", "I7N3G6G", "2129", 0, "no description", 60.2, "random"], ["2130", "I7N3G6G", "2130", 0, "no description", 60.2, "random"], ["2131", "I7N3G6G", "2131", 0, "no description", 60.2, "random"], ["2132", "I7N3G6G", "2132", 0, "no description", 60.2, "random"], ["2133", "I7N3G6G", "2133", 0, "no description", 60.2, "random"], ["2134", "I7N3G6G", "2134", 0, "no description", 60.2, "random"], ["2135", "I7N3G6G", "2135", 0, "no description", 60.2, "random"], ["2136", "I7N3G6G", "2136", 0, "no description", 60.2, "random"], ["2137", "I7N3G6G", "2137", 0, "no description", 60.2, "random"], ["2138", "I7N3G6G", "2138", 0, "no description", 60.2, "random"], ["2139", "I7N3G6G", "2139", 0, "no description", 60.2, "random"], ["2140", "I7N3G6G", "2140", 0, "no description", 60.2, "random"], ["2141", "I7N3G6G", "2141", 0, "no description", 60.2, "random"], ["2142", "I7N3G6G", "2142", 0, "no description", 60.2, "random"], ["2143", "I7N3G6G", "2143", 0, "no description", 60.2, "random"], ["2144", "I7N3G6G", "2144", 0, "no description", 60.2, "random"], ["2145", "I7N3G6G", "2145", 0, "no description", 60.2, "random"], ["2146", "I7N3G6G", "2146", 0, "no description", 60.2, "random"], ["2147", "I7N3G6G", "2147", 0, "no description", 60.2, "random"], ["2148", "I7N3G6G", "2148", 0, "no description", 60.2, "random"], ["2149", "I7N3G6G", "2149", 0, "no description", 60.2, "random"], ["2150", "I7N3G6G", "2150", 0, "no description", 60.2, "random"], ["2151", "I7N3G6G", "2151", 0, "no description", 60.2, "random"], ["2152", "I7N3G6G", "2152", 0, "no description", 60.2, "random"], ["2153", "I7N3G6G", "2153", 0, "no description", 60.2, "random"], ["2154", "I7N3G6G", "2154", 0, "no description", 60.2, "random"], ["2155", "I7N3G6G", "2155", 0, "no description", 60.2, "random"], ["2156", "I7N3G6G", "2156", 0, "no description", 60.2, "random"], ["2157", "I7N3G6G", "2157", 0, "no description", 60.2, "random"], ["2158", "I7N3G6G", "2158", 0, "no description", 60.2, "random"], ["2159", "I7N3G6G", "2159", 0, "no description", 60.2, "random"], ["2160", "I7N3G6G", "2160", 0, "no description", 60.2, "random"], ["2161", "I7N3G6G", "2161", 0, "no description", 60.2, "random"], ["2162", "I7N3G6G", "2162", 0, "no description", 60.2, "random"], ["2163", "I7N3G6G", "2163", 0, "no description", 60.2, "random"], ["2164", "I7N3G6G", "2164", 0, "no description", 60.2, "random"], ["2165", "I7N3G6G", "2165", 0, "no description", 60.2, "random"], ["2166", "I7N3G6G", "2166", 0, "no description", 60.2, "random"], ["2167", "I7N3G6G", "2167", 0, "no description", 60.2, "random"], ["2168", "I7N3G6G", "2168", 0, "no description", 60.2, "random"], ["2169", "I7N3G6G", "2169", 0, "no description", 60.2, "random"], ["2170", "I7N3G6G", "2170", 0, "no description", 60.2, "random"], ["2171", "I7N3G6G", "2171", 0, "no description", 60.2, "random"], ["2172", "I7N3G6G", "2172", 0, "no description", 60.2, "random"], ["2173", "I7N3G6G", "2173", 0, "no description", 60.2, "random"], ["2174", "I7N3G6G", "2174", 0, "no description", 60.2, "random"], ["2175", "I7N3G6G", "2175", 0, "no description", 60.2, "random"], ["2176", "I7N3G6G", "2176", 0, "no description", 60.2, "random"], ["2177", "I7N3G6G", "2177", 0, "no description", 60.2, "random"], ["2178", "I7N3G6G", "2178", 0, "no description", 60.2, "random"], ["2179", "I7N3G6G", "2179", 0, "no description", 60.2, "random"], ["2180", "I7N3G6G", "2180", 0, "no description", 60.2, "random"], ["2181", "I7N3G6G", "2181", 0, "no description", 60.2, "random"], ["2182", "I7N3G6G", "2182", 0, "no description", 60.2, "random"], ["2183", "I7N3G6G", "2183", 0, "no description", 60.2, "random"], ["2184", "I7N3G6G", "2184", 0, "no description", 60.2, "random"], ["2185", "I7N3G6G", "2185", 0, "no description", 60.2, "random"], ["2186", "I7N3G6G", "2186", 0, "no description", 60.2, "random"], ["2187", "I7N3G6G", "2187", 0, "no description", 60.2, "random"], ["2188", "I7N3G6G", "2188", 0, "no description", 60.2, "random"], ["2189", "I7N3G6G", "2189", 0, "no description", 60.2, "random"], ["2190", "I7N3G6G", "2190", 0, "no description", 60.2, "random"], ["2191", "I7N3G6G", "2191", 0, "no description", 60.2, "random"], ["2192", "I7N3G6G", "2192", 0, "no description", 60.2, "random"], ["2193", "I7N3G6G", "2193", 0, "no description", 60.2, "random"], ["2194", "I7N3G6G", "2194", 0, "no description", 60.2, "random"], ["2195", "I7N3G6G", "2195", 0, "no description", 60.2, "random"], ["2196", "I7N3G6G", "2196", 0, "no description", 60.2, "random"], ["2197", "I7N3G6G", "2197", 0, "no description", 60.2, "random"], ["2198", "I7N3G6G", "2198", 0, "no description", 60.2, "random"], ["2199", "I7N3G6G", "2199", 0, "no description", 60.2, "random"], ["2200", "I7N3G6G", "2200", 0, "no description", 60.2, "random"], ["2201", "I7N3G6G", "2201", 0, "no description", 60.2, "random"], ["2202", "I7N3G6G", "2202", 0, "no description", 60.2, "random"], ["2203", "I7N3G6G", "2203", 0, "no description", 60.2, "random"], ["2204", "I7N3G6G", "2204", 0, "no description", 60.2, "random"], ["2205", "I7N3G6G", "2205", 0, "no description", 60.2, "random"], ["2206", "I7N3G6G", "2206", 0, "no description", 60.2, "random"], ["2207", "I7N3G6G", "2207", 0, "no description", 60.2, "random"], ["2208", "I7N3G6G", "2208", 0, "no description", 60.2, "random"], ["2209", "I7N3G6G", "2209", 0, "no description", 60.2, "random"], ["2210", "I7N3G6G", "2210", 0, "no description", 60.2, "random"], ["2211", "I7N3G6G", "2211", 0, "no description", 60.2, "random"], ["2212", "I7N3G6G", "2212", 0, "no description", 60.2, "random"], ["2213", "I7N3G6G", "2213", 0, "no description", 60.2, "random"], ["2214", "I7N3G6G", "2214", 0, "no description", 60.2, "random"], ["2215", "I7N3G6G", "2215", 0, "no description", 60.2, "random"], ["2216", "I7N3G6G", "2216", 0, "no description", 60.2, "random"], ["2217", "I7N3G6G", "2217", 0, "no description", 60.2, "random"], ["2218", "I7N3G6G", "2218", 0, "no description", 60.2, "random"], ["2219", "I7N3G6G", "2219", 0, "no description", 60.2, "random"], ["2220", "I7N3G6G", "2220", 0, "no description", 60.2, "random"], ["2221", "I7N3G6G", "2221", 0, "no description", 60.2, "random"], ["2222", "I7N3G6G", "2222", 0, "no description", 60.2, "random"], ["2223", "I7N3G6G", "2223", 0, "no description", 60.2, "random"], ["2224", "I7N3G6G", "2224", 0, "no description", 60.2, "random"], ["2225", "I7N3G6G", "2225", 0, "no description", 60.2, "random"], ["2226", "I7N3G6G", "2226", 0, "no description", 60.2, "random"]] \ No newline at end of file diff --git a/packages/itmat-job-executor/test/testFiles/JSONCurator_error2.json b/packages/itmat-job-executor/test/testFiles/JSONCurator_error2.json deleted file mode 100644 index 69d365f42..000000000 --- a/packages/itmat-job-executor/test/testFiles/JSONCurator_error2.json +++ /dev/null @@ -1 +0,0 @@ -[["ID", "SubjectID", "VisitID", "Sex", "Description", "Description"], ["1", "I7N3G6G", "1", 0, "no description", 60.2], ["2", "I7N3G6G", "2", 0, "no description", 60.2], ["3", "I7N3G6G", "3", 0, "no description", 60.2], ["4", "I7N3G6G", "4", 0, "no description", 60.2], ["5", "I7N3G6G", "5", 0, "no description", 60.2], ["6", "I7N3G6G", "6", 0, "no description", 60.2], ["7", "I7N3G6G", "7", 0, "no description", 60.2], ["8", "I7N3G6G", "8", 0, "no description", 60.2], ["9", "I7N3G6G", "9", 0, "no description", 60.2], ["10", "I7N3G6G", "10", 0, "no description", 60.2], ["11", "I7N3G6G", "11", 0, "no description", 60.2], ["12", "I7N3G6G", "12", 0, "no description", 60.2], ["13", "I7N3G6G", "13", 0, "no description", 60.2], ["14", "I7N3G6G", "14", 0, "no description", 60.2], ["15", "I7N3G6G", "15", 0, "no description", 60.2], ["16", "I7N3G6G", "16", 0, "no description", 60.2], ["17", "I7N3G6G", "17", 0, "no description", 60.2], ["18", "I7N3G6G", "18", 0, "no description", 60.2], ["19", "I7N3G6G", "19", 0, "no description", 60.2], ["20", "I7N3G6G", "20", 0, "no description", 60.2], ["21", "I7N3G6G", "21", 0, "no description", 60.2], ["22", "I7N3G6G", "22", 0, "no description", 60.2], ["23", "I7N3G6G", "23", 0, "no description", 60.2], ["24", "I7N3G6G", "24", 0, "no description", 60.2], ["25", "I7N3G6G", "25", 0, "no description", 60.2], ["26", "I7N3G6G", "26", 0, "no description", 60.2], ["27", "I7N3G6G", "27", 0, "no description", 60.2], ["28", "I7N3G6G", "28", 0, "no description", 60.2], ["29", "I7N3G6G", "29", 0, "no description", 60.2], ["30", "I7N3G6G", "30", 0, "no description", 60.2], ["31", "I7N3G6G", "31", 0, "no description", 60.2], ["32", "I7N3G6G", "32", 0, "no description", 60.2], ["33", "I7N3G6G", "33", 0, "no description", 60.2], ["34", "I7N3G6G", "34", 0, "no description", 60.2], ["35", "I7N3G6G", "35", 0, "no description", 60.2], ["36", "I7N3G6G", "36", 0, "no description", 60.2], ["37", "I7N3G6G", "37", 0, "no description", 60.2], ["38", "I7N3G6G", "38", 0, "no description", 60.2], ["39", "I7N3G6G", "39", 0, "no description", 60.2], ["40", "I7N3G6G", "40", 0, "no description", 60.2], ["41", "I7N3G6G", "41", 0, "no description", 60.2], ["42", "I7N3G6G", "42", 0, "no description", 60.2], ["43", "I7N3G6G", "43", 0, "no description", 60.2], ["44", "I7N3G6G", "44", 0, "no description", 60.2], ["45", "I7N3G6G", "45", 0, "no description", 60.2], ["46", "I7N3G6G", "46", 0, "no description", 60.2], ["47", "I7N3G6G", "47", 0, "no description", 60.2], ["48", "I7N3G6G", "48", 0, "no description", 60.2], ["49", "I7N3G6G", "49", 0, "no description", 60.2], ["50", "I7N3G6G", "50", 0, "no description", 60.2], ["51", "I7N3G6G", "51", 0, "no description", 60.2], ["52", "I7N3G6G", "52", 0, "no description", 60.2], ["53", "I7N3G6G", "53", 0, "no description", 60.2], ["54", "I7N3G6G", "54", 0, "no description", 60.2], ["55", "I7N3G6G", "55", 0, "no description", 60.2], ["56", "I7N3G6G", "56", 0, "no description", 60.2], ["57", "I7N3G6G", "57", 0, "no description", 60.2], ["58", "I7N3G6G", "58", 0, "no description", 60.2], ["59", "I7N3G6G", "59", 0, "no description", 60.2], ["60", "I7N3G6G", "60", 0, "no description", 60.2], ["61", "I7N3G6G", "61", 0, "no description", 60.2], ["62", "I7N3G6G", "62", 0, "no description", 60.2], ["63", "I7N3G6G", "63", 0, "no description", 60.2], ["64", "I7N3G6G", "64", 0, "no description", 60.2], ["65", "I7N3G6G", "65", 0, "no description", 60.2], ["66", "I7N3G6G", "66", 0, "no description", 60.2], ["67", "I7N3G6G", "67", 0, "no description", 60.2], ["68", "I7N3G6G", "68", 0, "no description", 60.2], ["69", "I7N3G6G", "69", 0, "no description", 60.2], ["70", "I7N3G6G", "70", 0, "no description", 60.2], ["71", "I7N3G6G", "71", 0, "no description", 60.2], ["72", "I7N3G6G", "72", 0, "no description", 60.2], ["73", "I7N3G6G", "73", 0, "no description", 60.2], ["74", "I7N3G6G", "74", 0, "no description", 60.2], ["75", "I7N3G6G", "75", 0, "no description", 60.2], ["76", "I7N3G6G", "76", 0, "no description", 60.2], ["77", "I7N3G6G", "77", 0, "no description", 60.2], ["78", "I7N3G6G", "78", 0, "no description", 60.2], ["79", "I7N3G6G", "79", 0, "no description", 60.2], ["80", "I7N3G6G", "80", 0, "no description", 60.2], ["81", "I7N3G6G", "81", 0, "no description", 60.2], ["82", "I7N3G6G", "82", 0, "no description", 60.2], ["83", "I7N3G6G", "83", 0, "no description", 60.2], ["84", "I7N3G6G", "84", 0, "no description", 60.2], ["85", "I7N3G6G", "85", 0, "no description", 60.2], ["86", "I7N3G6G", "86", 0, "no description", 60.2], ["87", "I7N3G6G", "87", 0, "no description", 60.2], ["88", "I7N3G6G", "88", 0, "no description", 60.2], ["89", "I7N3G6G", "89", 0, "no description", 60.2], ["90", "I7N3G6G", "90", 0, "no description", 60.2], ["91", "I7N3G6G", "91", 0, "no description", 60.2], ["92", "I7N3G6G", "92", 0, "no description", 60.2], ["93", "I7N3G6G", "93", 0, "no description", 60.2], ["94", "I7N3G6G", "94", 0, "no description", 60.2], ["95", "I7N3G6G", "95", 0, "no description", 60.2], ["96", "I7N3G6G", "96", 0, "no description", 60.2], ["97", "I7N3G6G", "97", 0, "no description", 60.2], ["98", "I7N3G6G", "98", 0, "no description", 60.2], ["99", "I7N3G6G", "99", 0, "no description", 60.2], ["100", "I7N3G6G", "100", 0, "no description", 60.2], ["101", "I7N3G6G", "101", 0, "no description", 60.2], ["102", "I7N3G6G", "102", 0, "no description", 60.2], ["103", "I7N3G6G", "103", 0, "no description", 60.2], ["104", "I7N3G6G", "104", 0, "no description", 60.2], ["105", "I7N3G6G", "105", 0, "no description", 60.2], ["106", "I7N3G6G", "106", 0, "no description", 60.2], ["107", "I7N3G6G", "107", 0, "no description", 60.2], ["108", "I7N3G6G", "108", 0, "no description", 60.2], ["109", "I7N3G6G", "109", 0, "no description", 60.2], ["110", "I7N3G6G", "110", 0, "no description", 60.2], ["111", "I7N3G6G", "111", 0, "no description", 60.2], ["112", "I7N3G6G", "112", 0, "no description", 60.2], ["113", "I7N3G6G", "113", 0, "no description", 60.2], ["114", "I7N3G6G", "114", 0, "no description", 60.2], ["115", "I7N3G6G", "115", 0, "no description", 60.2], ["116", "I7N3G6G", "116", 0, "no description", 60.2], ["117", "I7N3G6G", "117", 0, "no description", 60.2], ["118", "I7N3G6G", "118", 0, "no description", 60.2], ["119", "I7N3G6G", "119", 0, "no description", 60.2], ["120", "I7N3G6G", "120", 0, "no description", 60.2], ["121", "I7N3G6G", "121", 0, "no description", 60.2], ["122", "I7N3G6G", "122", 0, "no description", 60.2], ["123", "I7N3G6G", "123", 0, "no description", 60.2], ["124", "I7N3G6G", "124", 0, "no description", 60.2], ["125", "I7N3G6G", "125", 0, "no description", 60.2], ["126", "I7N3G6G", "126", 0, "no description", 60.2], ["127", "I7N3G6G", "127", 0, "no description", 60.2], ["128", "I7N3G6G", "128", 0, "no description", 60.2], ["129", "I7N3G6G", "129", 0, "no description", 60.2], ["130", "I7N3G6G", "130", 0, "no description", 60.2], ["131", "I7N3G6G", "131", 0, "no description", 60.2], ["132", "I7N3G6G", "132", 0, "no description", 60.2], ["133", "I7N3G6G", "133", 0, "no description", 60.2], ["134", "I7N3G6G", "134", 0, "no description", 60.2], ["135", "I7N3G6G", "135", 0, "no description", 60.2], ["136", "I7N3G6G", "136", 0, "no description", 60.2], ["137", "I7N3G6G", "137", 0, "no description", 60.2], ["138", "I7N3G6G", "138", 0, "no description", 60.2], ["139", "I7N3G6G", "139", 0, "no description", 60.2], ["140", "I7N3G6G", "140", 0, "no description", 60.2], ["141", "I7N3G6G", "141", 0, "no description", 60.2], ["142", "I7N3G6G", "142", 0, "no description", 60.2], ["143", "I7N3G6G", "143", 0, "no description", 60.2], ["144", "I7N3G6G", "144", 0, "no description", 60.2], ["145", "I7N3G6G", "145", 0, "no description", 60.2], ["146", "I7N3G6G", "146", 0, "no description", 60.2], ["147", "I7N3G6G", "147", 0, "no description", 60.2], ["148", "I7N3G6G", "148", 0, "no description", 60.2], ["149", "I7N3G6G", "149", 0, "no description", 60.2], ["150", "I7N3G6G", "150", 0, "no description", 60.2], ["151", "I7N3G6G", "151", 0, "no description", 60.2], ["152", "I7N3G6G", "152", 0, "no description", 60.2], ["153", "I7N3G6G", "153", 0, "no description", 60.2], ["154", "I7N3G6G", "154", 0, "no description", 60.2], ["155", "I7N3G6G", "155", 0, "no description", 60.2], ["156", "I7N3G6G", "156", 0, "no description", 60.2], ["157", "I7N3G6G", "157", 0, "no description", 60.2], ["158", "I7N3G6G", "158", 0, "no description", 60.2], ["159", "I7N3G6G", "159", 0, "no description", 60.2], ["160", "I7N3G6G", "160", 0, "no description", 60.2], ["161", "I7N3G6G", "161", 0, "no description", 60.2], ["162", "I7N3G6G", "162", 0, "no description", 60.2], ["163", "I7N3G6G", "163", 0, "no description", 60.2], ["164", "I7N3G6G", "164", 0, "no description", 60.2], ["165", "I7N3G6G", "165", 0, "no description", 60.2], ["166", "I7N3G6G", "166", 0, "no description", 60.2], ["167", "I7N3G6G", "167", 0, "no description", 60.2], ["168", "I7N3G6G", "168", 0, "no description", 60.2], ["169", "I7N3G6G", "169", 0, "no description", 60.2], ["170", "I7N3G6G", "170", 0, "no description", 60.2], ["171", "I7N3G6G", "171", 0, "no description", 60.2], ["172", "I7N3G6G", "172", 0, "no description", 60.2], ["173", "I7N3G6G", "173", 0, "no description", 60.2], ["174", "I7N3G6G", "174", 0, "no description", 60.2], ["175", "I7N3G6G", "175", 0, "no description", 60.2], ["176", "I7N3G6G", "176", 0, "no description", 60.2], ["177", "I7N3G6G", "177", 0, "no description", 60.2], ["178", "I7N3G6G", "178", 0, "no description", 60.2], ["179", "I7N3G6G", "179", 0, "no description", 60.2], ["180", "I7N3G6G", "180", 0, "no description", 60.2], ["181", "I7N3G6G", "181", 0, "no description", 60.2], ["182", "I7N3G6G", "182", 0, "no description", 60.2], ["183", "I7N3G6G", "183", 0, "no description", 60.2], ["184", "I7N3G6G", "184", 0, "no description", 60.2], ["185", "I7N3G6G", "185", 0, "no description", 60.2], ["186", "I7N3G6G", "186", 0, "no description", 60.2], ["187", "I7N3G6G", "187", 0, "no description", 60.2], ["188", "I7N3G6G", "188", 0, "no description", 60.2], ["189", "I7N3G6G", "189", 0, "no description", 60.2], ["190", "I7N3G6G", "190", 0, "no description", 60.2], ["191", "I7N3G6G", "191", 0, "no description", 60.2], ["192", "I7N3G6G", "192", 0, "no description", 60.2], ["193", "I7N3G6G", "193", 0, "no description", 60.2], ["194", "I7N3G6G", "194", 0, "no description", 60.2], ["195", "I7N3G6G", "195", 0, "no description", 60.2], ["196", "I7N3G6G", "196", 0, "no description", 60.2], ["197", "I7N3G6G", "197", 0, "no description", 60.2], ["198", "I7N3G6G", "198", 0, "no description", 60.2], ["199", "I7N3G6G", "199", 0, "no description", 60.2], ["200", "I7N3G6G", "200", 0, "no description", 60.2], ["201", "I7N3G6G", "201", 0, "no description", 60.2], ["202", "I7N3G6G", "202", 0, "no description", 60.2], ["203", "I7N3G6G", "203", 0, "no description", 60.2], ["204", "I7N3G6G", "204", 0, "no description", 60.2], ["205", "I7N3G6G", "205", 0, "no description", 60.2], ["206", "I7N3G6G", "206", 0, "no description", 60.2], ["207", "I7N3G6G", "207", 0, "no description", 60.2], ["208", "I7N3G6G", "208", 0, "no description", 60.2], ["209", "I7N3G6G", "209", 0, "no description", 60.2], ["210", "I7N3G6G", "210", 0, "no description", 60.2], ["211", "I7N3G6G", "211", 0, "no description", 60.2], ["212", "I7N3G6G", "212", 0, "no description", 60.2], ["213", "I7N3G6G", "213", 0, "no description", 60.2], ["214", "I7N3G6G", "214", 0, "no description", 60.2], ["215", "I7N3G6G", "215", 0, "no description", 60.2], ["216", "I7N3G6G", "216", 0, "no description", 60.2], ["217", "I7N3G6G", "217", 0, "no description", 60.2], ["218", "I7N3G6G", "218", 0, "no description", 60.2], ["219", "I7N3G6G", "219", 0, "no description", 60.2], ["220", "I7N3G6G", "220", 0, "no description", 60.2], ["221", "I7N3G6G", "221", 0, "no description", 60.2], ["222", "I7N3G6G", "222", 0, "no description", 60.2], ["223", "I7N3G6G", "223", 0, "no description", 60.2], ["224", "I7N3G6G", "224", 0, "no description", 60.2], ["225", "I7N3G6G", "225", 0, "no description", 60.2], ["226", "I7N3G6G", "226", 0, "no description", 60.2], ["227", "I7N3G6G", "227", 0, "no description", 60.2], ["228", "I7N3G6G", "228", 0, "no description", 60.2], ["229", "I7N3G6G", "229", 0, "no description", 60.2], ["230", "I7N3G6G", "230", 0, "no description", 60.2], ["231", "I7N3G6G", "231", 0, "no description", 60.2], ["232", "I7N3G6G", "232", 0, "no description", 60.2], ["233", "I7N3G6G", "233", 0, "no description", 60.2], ["234", "I7N3G6G", "234", 0, "no description", 60.2], ["235", "I7N3G6G", "235", 0, "no description", 60.2], ["236", "I7N3G6G", "236", 0, "no description", 60.2], ["237", "I7N3G6G", "237", 0, "no description", 60.2], ["238", "I7N3G6G", "238", 0, "no description", 60.2], ["239", "I7N3G6G", "239", 0, "no description", 60.2], ["240", "I7N3G6G", "240", 0, "no description", 60.2], ["241", "I7N3G6G", "241", 0, "no description", 60.2], ["242", "I7N3G6G", "242", 0, "no description", 60.2], ["243", "I7N3G6G", "243", 0, "no description", 60.2], ["244", "I7N3G6G", "244", 0, "no description", 60.2], ["245", "I7N3G6G", "245", 0, "no description", 60.2], ["246", "I7N3G6G", "246", 0, "no description", 60.2], ["247", "I7N3G6G", "247", 0, "no description", 60.2], ["248", "I7N3G6G", "248", 0, "no description", 60.2], ["249", "I7N3G6G", "249", 0, "no description", 60.2], ["250", "I7N3G6G", "250", 0, "no description", 60.2], ["251", "I7N3G6G", "251", 0, "no description", 60.2], ["252", "I7N3G6G", "252", 0, "no description", 60.2], ["253", "I7N3G6G", "253", 0, "no description", 60.2], ["254", "I7N3G6G", "254", 0, "no description", 60.2], ["255", "I7N3G6G", "255", 0, "no description", 60.2], ["256", "I7N3G6G", "256", 0, "no description", 60.2], ["257", "I7N3G6G", "257", 0, "no description", 60.2], ["258", "I7N3G6G", "258", 0, "no description", 60.2], ["259", "I7N3G6G", "259", 0, "no description", 60.2], ["260", "I7N3G6G", "260", 0, "no description", 60.2], ["261", "I7N3G6G", "261", 0, "no description", 60.2], ["262", "I7N3G6G", "262", 0, "no description", 60.2], ["263", "I7N3G6G", "263", 0, "no description", 60.2], ["264", "I7N3G6G", "264", 0, "no description", 60.2], ["265", "I7N3G6G", "265", 0, "no description", 60.2], ["266", "I7N3G6G", "266", 0, "no description", 60.2], ["267", "I7N3G6G", "267", 0, "no description", 60.2], ["268", "I7N3G6G", "268", 0, "no description", 60.2], ["269", "I7N3G6G", "269", 0, "no description", 60.2], ["270", "I7N3G6G", "270", 0, "no description", 60.2], ["271", "I7N3G6G", "271", 0, "no description", 60.2], ["272", "I7N3G6G", "272", 0, "no description", 60.2], ["273", "I7N3G6G", "273", 0, "no description", 60.2], ["274", "I7N3G6G", "274", 0, "no description", 60.2], ["275", "I7N3G6G", "275", 0, "no description", 60.2], ["276", "I7N3G6G", "276", 0, "no description", 60.2], ["277", "I7N3G6G", "277", 0, "no description", 60.2], ["278", "I7N3G6G", "278", 0, "no description", 60.2], ["279", "I7N3G6G", "279", 0, "no description", 60.2], ["280", "I7N3G6G", "280", 0, "no description", 60.2], ["281", "I7N3G6G", "281", 0, "no description", 60.2], ["282", "I7N3G6G", "282", 0, "no description", 60.2], ["283", "I7N3G6G", "283", 0, "no description", 60.2], ["284", "I7N3G6G", "284", 0, "no description", 60.2], ["285", "I7N3G6G", "285", 0, "no description", 60.2], ["286", "I7N3G6G", "286", 0, "no description", 60.2], ["287", "I7N3G6G", "287", 0, "no description", 60.2], ["288", "I7N3G6G", "288", 0, "no description", 60.2], ["289", "I7N3G6G", "289", 0, "no description", 60.2], ["290", "I7N3G6G", "290", 0, "no description", 60.2], ["291", "I7N3G6G", "291", 0, "no description", 60.2], ["292", "I7N3G6G", "292", 0, "no description", 60.2], ["293", "I7N3G6G", "293", 0, "no description", 60.2], ["294", "I7N3G6G", "294", 0, "no description", 60.2], ["295", "I7N3G6G", "295", 0, "no description", 60.2], ["296", "I7N3G6G", "296", 0, "no description", 60.2], ["297", "I7N3G6G", "297", 0, "no description", 60.2], ["298", "I7N3G6G", "298", 0, "no description", 60.2], ["299", "I7N3G6G", "299", 0, "no description", 60.2], ["300", "I7N3G6G", "300", 0, "no description", 60.2], ["301", "I7N3G6G", "301", 0, "no description", 60.2], ["302", "I7N3G6G", "302", 0, "no description", 60.2], ["303", "I7N3G6G", "303", 0, "no description", 60.2], ["304", "I7N3G6G", "304", 0, "no description", 60.2], ["305", "I7N3G6G", "305", 0, "no description", 60.2], ["306", "I7N3G6G", "306", 0, "no description", 60.2], ["307", "I7N3G6G", "307", 0, "no description", 60.2], ["308", "I7N3G6G", "308", 0, "no description", 60.2], ["309", "I7N3G6G", "309", 0, "no description", 60.2], ["310", "I7N3G6G", "310", 0, "no description", 60.2], ["311", "I7N3G6G", "311", 0, "no description", 60.2], ["312", "I7N3G6G", "312", 0, "no description", 60.2], ["313", "I7N3G6G", "313", 0, "no description", 60.2], ["314", "I7N3G6G", "314", 0, "no description", 60.2], ["315", "I7N3G6G", "315", 0, "no description", 60.2], ["316", "I7N3G6G", "316", 0, "no description", 60.2], ["317", "I7N3G6G", "317", 0, "no description", 60.2], ["318", "I7N3G6G", "318", 0, "no description", 60.2], ["319", "I7N3G6G", "319", 0, "no description", 60.2], ["320", "I7N3G6G", "320", 0, "no description", 60.2], ["321", "I7N3G6G", "321", 0, "no description", 60.2], ["322", "I7N3G6G", "322", 0, "no description", 60.2], ["323", "I7N3G6G", "323", 0, "no description", 60.2], ["324", "I7N3G6G", "324", 0, "no description", 60.2], ["325", "I7N3G6G", "325", 0, "no description", 60.2], ["326", "I7N3G6G", "326", 0, "no description", 60.2], ["327", "I7N3G6G", "327", 0, "no description", 60.2], ["328", "I7N3G6G", "328", 0, "no description", 60.2], ["329", "I7N3G6G", "329", 0, "no description", 60.2], ["330", "I7N3G6G", "330", 0, "no description", 60.2], ["331", "I7N3G6G", "331", 0, "no description", 60.2], ["332", "I7N3G6G", "332", 0, "no description", 60.2], ["333", "I7N3G6G", "333", 0, "no description", 60.2], ["334", "I7N3G6G", "334", 0, "no description", 60.2], ["335", "I7N3G6G", "335", 0, "no description", 60.2], ["336", "I7N3G6G", "336", 0, "no description", 60.2], ["337", "I7N3G6G", "337", 0, "no description", 60.2], ["338", "I7N3G6G", "338", 0, "no description", 60.2], ["339", "I7N3G6G", "339", 0, "no description", 60.2], ["340", "I7N3G6G", "340", 0, "no description", 60.2], ["341", "I7N3G6G", "341", 0, "no description", 60.2], ["342", "I7N3G6G", "342", 0, "no description", 60.2], ["343", "I7N3G6G", "343", 0, "no description", 60.2], ["344", "I7N3G6G", "344", 0, "no description", 60.2], ["345", "I7N3G6G", "345", 0, "no description", 60.2], ["346", "I7N3G6G", "346", 0, "no description", 60.2], ["347", "I7N3G6G", "347", 0, "no description", 60.2], ["348", "I7N3G6G", "348", 0, "no description", 60.2], ["349", "I7N3G6G", "349", 0, "no description", 60.2], ["350", "I7N3G6G", "350", 0, "no description", 60.2], ["351", "I7N3G6G", "351", 0, "no description", 60.2], ["352", "I7N3G6G", "352", 0, "no description", 60.2], ["353", "I7N3G6G", "353", 0, "no description", 60.2], ["354", "I7N3G6G", "354", 0, "no description", 60.2], ["355", "I7N3G6G", "355", 0, "no description", 60.2], ["356", "I7N3G6G", "356", 0, "no description", 60.2], ["357", "I7N3G6G", "357", 0, "no description", 60.2], ["358", "I7N3G6G", "358", 0, "no description", 60.2], ["359", "I7N3G6G", "359", 0, "no description", 60.2], ["360", "I7N3G6G", "360", 0, "no description", 60.2], ["361", "I7N3G6G", "361", 0, "no description", 60.2], ["362", "I7N3G6G", "362", 0, "no description", 60.2], ["363", "I7N3G6G", "363", 0, "no description", 60.2], ["364", "I7N3G6G", "364", 0, "no description", 60.2], ["365", "I7N3G6G", "365", 0, "no description", 60.2], ["366", "I7N3G6G", "366", 0, "no description", 60.2], ["367", "I7N3G6G", "367", 0, "no description", 60.2], ["368", "I7N3G6G", "368", 0, "no description", 60.2], ["369", "I7N3G6G", "369", 0, "no description", 60.2], ["370", "I7N3G6G", "370", 0, "no description", 60.2], ["371", "I7N3G6G", "371", 0, "no description", 60.2], ["372", "I7N3G6G", "372", 0, "no description", 60.2], ["373", "I7N3G6G", "373", 0, "no description", 60.2], ["374", "I7N3G6G", "374", 0, "no description", 60.2], ["375", "I7N3G6G", "375", 0, "no description", 60.2], ["376", "I7N3G6G", "376", 0, "no description", 60.2], ["377", "I7N3G6G", "377", 0, "no description", 60.2], ["378", "I7N3G6G", "378", 0, "no description", 60.2], ["379", "I7N3G6G", "379", 0, "no description", 60.2], ["380", "I7N3G6G", "380", 0, "no description", 60.2], ["381", "I7N3G6G", "381", 0, "no description", 60.2], ["382", "I7N3G6G", "382", 0, "no description", 60.2], ["383", "I7N3G6G", "383", 0, "no description", 60.2], ["384", "I7N3G6G", "384", 0, "no description", 60.2], ["385", "I7N3G6G", "385", 0, "no description", 60.2], ["386", "I7N3G6G", "386", 0, "no description", 60.2], ["387", "I7N3G6G", "387", 0, "no description", 60.2], ["388", "I7N3G6G", "388", 0, "no description", 60.2], ["389", "I7N3G6G", "389", 0, "no description", 60.2], ["390", "I7N3G6G", "390", 0, "no description", 60.2], ["391", "I7N3G6G", "391", 0, "no description", 60.2], ["392", "I7N3G6G", "392", 0, "no description", 60.2], ["393", "I7N3G6G", "393", 0, "no description", 60.2], ["394", "I7N3G6G", "394", 0, "no description", 60.2], ["395", "I7N3G6G", "395", 0, "no description", 60.2], ["396", "I7N3G6G", "396", 0, "no description", 60.2], ["397", "I7N3G6G", "397", 0, "no description", 60.2], ["398", "I7N3G6G", "398", 0, "no description", 60.2], ["399", "I7N3G6G", "399", 0, "no description", 60.2], ["400", "I7N3G6G", "400", 0, "no description", 60.2], ["401", "I7N3G6G", "401", 0, "no description", 60.2], ["402", "I7N3G6G", "402", 0, "no description", 60.2], ["403", "I7N3G6G", "403", 0, "no description", 60.2], ["404", "I7N3G6G", "404", 0, "no description", 60.2], ["405", "I7N3G6G", "405", 0, "no description", 60.2], ["406", "I7N3G6G", "406", 0, "no description", 60.2], ["407", "I7N3G6G", "407", 0, "no description", 60.2], ["408", "I7N3G6G", "408", 0, "no description", 60.2], ["409", "I7N3G6G", "409", 0, "no description", 60.2], ["410", "I7N3G6G", "410", 0, "no description", 60.2], ["411", "I7N3G6G", "411", 0, "no description", 60.2], ["412", "I7N3G6G", "412", 0, "no description", 60.2], ["413", "I7N3G6G", "413", 0, "no description", 60.2], ["414", "I7N3G6G", "414", 0, "no description", 60.2], ["415", "I7N3G6G", "415", 0, "no description", 60.2], ["416", "I7N3G6G", "416", 0, "no description", 60.2], ["417", "I7N3G6G", "417", 0, "no description", 60.2], ["418", "I7N3G6G", "418", 0, "no description", 60.2], ["419", "I7N3G6G", "419", 0, "no description", 60.2], ["420", "I7N3G6G", "420", 0, "no description", 60.2], ["421", "I7N3G6G", "421", 0, "no description", 60.2], ["422", "I7N3G6G", "422", 0, "no description", 60.2], ["423", "I7N3G6G", "423", 0, "no description", 60.2], ["424", "I7N3G6G", "424", 0, "no description", 60.2], ["425", "I7N3G6G", "425", 0, "no description", 60.2], ["426", "I7N3G6G", "426", 0, "no description", 60.2], ["427", "I7N3G6G", "427", 0, "no description", 60.2], ["428", "I7N3G6G", "428", 0, "no description", 60.2], ["429", "I7N3G6G", "429", 0, "no description", 60.2], ["430", "I7N3G6G", "430", 0, "no description", 60.2], ["431", "I7N3G6G", "431", 0, "no description", 60.2], ["432", "I7N3G6G", "432", 0, "no description", 60.2], ["433", "I7N3G6G", "433", 0, "no description", 60.2], ["434", "I7N3G6G", "434", 0, "no description", 60.2], ["435", "I7N3G6G", "435", 0, "no description", 60.2], ["436", "I7N3G6G", "436", 0, "no description", 60.2], ["437", "I7N3G6G", "437", 0, "no description", 60.2], ["438", "I7N3G6G", "438", 0, "no description", 60.2], ["439", "I7N3G6G", "439", 0, "no description", 60.2], ["440", "I7N3G6G", "440", 0, "no description", 60.2], ["441", "I7N3G6G", "441", 0, "no description", 60.2], ["442", "I7N3G6G", "442", 0, "no description", 60.2], ["443", "I7N3G6G", "443", 0, "no description", 60.2], ["444", "I7N3G6G", "444", 0, "no description", 60.2], ["445", "I7N3G6G", "445", 0, "no description", 60.2], ["446", "I7N3G6G", "446", 0, "no description", 60.2], ["447", "I7N3G6G", "447", 0, "no description", 60.2], ["448", "I7N3G6G", "448", 0, "no description", 60.2], ["449", "I7N3G6G", "449", 0, "no description", 60.2], ["450", "I7N3G6G", "450", 0, "no description", 60.2], ["451", "I7N3G6G", "451", 0, "no description", 60.2], ["452", "I7N3G6G", "452", 0, "no description", 60.2], ["453", "I7N3G6G", "453", 0, "no description", 60.2], ["454", "I7N3G6G", "454", 0, "no description", 60.2], ["455", "I7N3G6G", "455", 0, "no description", 60.2], ["456", "I7N3G6G", "456", 0, "no description", 60.2], ["457", "I7N3G6G", "457", 0, "no description", 60.2], ["458", "I7N3G6G", "458", 0, "no description", 60.2], ["459", "I7N3G6G", "459", 0, "no description", 60.2], ["460", "I7N3G6G", "460", 0, "no description", 60.2], ["461", "I7N3G6G", "461", 0, "no description", 60.2], ["462", "I7N3G6G", "462", 0, "no description", 60.2], ["463", "I7N3G6G", "463", 0, "no description", 60.2], ["464", "I7N3G6G", "464", 0, "no description", 60.2], ["465", "I7N3G6G", "465", 0, "no description", 60.2], ["466", "I7N3G6G", "466", 0, "no description", 60.2], ["467", "I7N3G6G", "467", 0, "no description", 60.2], ["468", "I7N3G6G", "468", 0, "no description", 60.2], ["469", "I7N3G6G", "469", 0, "no description", 60.2], ["470", "I7N3G6G", "470", 0, "no description", 60.2], ["471", "I7N3G6G", "471", 0, "no description", 60.2], ["472", "I7N3G6G", "472", 0, "no description", 60.2], ["473", "I7N3G6G", "473", 0, "no description", 60.2], ["474", "I7N3G6G", "474", 0, "no description", 60.2], ["475", "I7N3G6G", "475", 0, "no description", 60.2], ["476", "I7N3G6G", "476", 0, "no description", 60.2], ["477", "I7N3G6G", "477", 0, "no description", 60.2], ["478", "I7N3G6G", "478", 0, "no description", 60.2], ["479", "I7N3G6G", "479", 0, "no description", 60.2], ["480", "I7N3G6G", "480", 0, "no description", 60.2], ["481", "I7N3G6G", "481", 0, "no description", 60.2], ["482", "I7N3G6G", "482", 0, "no description", 60.2], ["483", "I7N3G6G", "483", 0, "no description", 60.2], ["484", "I7N3G6G", "484", 0, "no description", 60.2], ["485", "I7N3G6G", "485", 0, "no description", 60.2], ["486", "I7N3G6G", "486", 0, "no description", 60.2], ["487", "I7N3G6G", "487", 0, "no description", 60.2], ["488", "I7N3G6G", "488", 0, "no description", 60.2], ["489", "I7N3G6G", "489", 0, "no description", 60.2], ["490", "I7N3G6G", "490", 0, "no description", 60.2], ["491", "I7N3G6G", "491", 0, "no description", 60.2], ["492", "I7N3G6G", "492", 0, "no description", 60.2], ["493", "I7N3G6G", "493", 0, "no description", 60.2], ["494", "I7N3G6G", "494", 0, "no description", 60.2], ["495", "I7N3G6G", "495", 0, "no description", 60.2], ["496", "I7N3G6G", "496", 0, "no description", 60.2], ["497", "I7N3G6G", "497", 0, "no description", 60.2], ["498", "I7N3G6G", "498", 0, "no description", 60.2], ["499", "I7N3G6G", "499", 0, "no description", 60.2], ["500", "I7N3G6G", "500", 0, "no description", 60.2], ["501", "I7N3G6G", "501", 0, "no description", 60.2], ["502", "I7N3G6G", "502", 0, "no description", 60.2], ["503", "I7N3G6G", "503", 0, "no description", 60.2], ["504", "I7N3G6G", "504", 0, "no description", 60.2], ["505", "I7N3G6G", "505", 0, "no description", 60.2], ["506", "I7N3G6G", "506", 0, "no description", 60.2], ["507", "I7N3G6G", "507", 0, "no description", 60.2], ["508", "I7N3G6G", "508", 0, "no description", 60.2], ["509", "I7N3G6G", "509", 0, "no description", 60.2], ["510", "I7N3G6G", "510", 0, "no description", 60.2], ["511", "I7N3G6G", "511", 0, "no description", 60.2], ["512", "I7N3G6G", "512", 0, "no description", 60.2], ["513", "I7N3G6G", "513", 0, "no description", 60.2], ["514", "I7N3G6G", "514", 0, "no description", 60.2], ["515", "I7N3G6G", "515", 0, "no description", 60.2], ["516", "I7N3G6G", "516", 0, "no description", 60.2], ["517", "I7N3G6G", "517", 0, "no description", 60.2], ["518", "I7N3G6G", "518", 0, "no description", 60.2], ["519", "I7N3G6G", "519", 0, "no description", 60.2], ["520", "I7N3G6G", "520", 0, "no description", 60.2], ["521", "I7N3G6G", "521", 0, "no description", 60.2], ["522", "I7N3G6G", "522", 0, "no description", 60.2], ["523", "I7N3G6G", "523", 0, "no description", 60.2], ["524", "I7N3G6G", "524", 0, "no description", 60.2], ["525", "I7N3G6G", "525", 0, "no description", 60.2], ["526", "I7N3G6G", "526", 0, "no description", 60.2], ["527", "I7N3G6G", "527", 0, "no description", 60.2], ["528", "I7N3G6G", "528", 0, "no description", 60.2], ["529", "I7N3G6G", "529", 0, "no description", 60.2], ["530", "I7N3G6G", "530", 0, "no description", 60.2], ["531", "I7N3G6G", "531", 0, "no description", 60.2], ["532", "I7N3G6G", "532", 0, "no description", 60.2], ["533", "I7N3G6G", "533", 0, "no description", 60.2], ["534", "I7N3G6G", "534", 0, "no description", 60.2], ["535", "I7N3G6G", "535", 0, "no description", 60.2], ["536", "I7N3G6G", "536", 0, "no description", 60.2], ["537", "I7N3G6G", "537", 0, "no description", 60.2], ["538", "I7N3G6G", "538", 0, "no description", 60.2], ["539", "I7N3G6G", "539", 0, "no description", 60.2], ["540", "I7N3G6G", "540", 0, "no description", 60.2], ["541", "I7N3G6G", "541", 0, "no description", 60.2], ["542", "I7N3G6G", "542", 0, "no description", 60.2], ["543", "I7N3G6G", "543", 0, "no description", 60.2], ["544", "I7N3G6G", "544", 0, "no description", 60.2], ["545", "I7N3G6G", "545", 0, "no description", 60.2], ["546", "I7N3G6G", "546", 0, "no description", 60.2], ["547", "I7N3G6G", "547", 0, "no description", 60.2], ["548", "I7N3G6G", "548", 0, "no description", 60.2], ["549", "I7N3G6G", "549", 0, "no description", 60.2], ["550", "I7N3G6G", "550", 0, "no description", 60.2], ["551", "I7N3G6G", "551", 0, "no description", 60.2], ["552", "I7N3G6G", "552", 0, "no description", 60.2], ["553", "I7N3G6G", "553", 0, "no description", 60.2], ["554", "I7N3G6G", "554", 0, "no description", 60.2], ["555", "I7N3G6G", "555", 0, "no description", 60.2], ["556", "I7N3G6G", "556", 0, "no description", 60.2], ["557", "I7N3G6G", "557", 0, "no description", 60.2], ["558", "I7N3G6G", "558", 0, "no description", 60.2], ["559", "I7N3G6G", "559", 0, "no description", 60.2], ["560", "I7N3G6G", "560", 0, "no description", 60.2], ["561", "I7N3G6G", "561", 0, "no description", 60.2], ["562", "I7N3G6G", "562", 0, "no description", 60.2], ["563", "I7N3G6G", "563", 0, "no description", 60.2], ["564", "I7N3G6G", "564", 0, "no description", 60.2], ["565", "I7N3G6G", "565", 0, "no description", 60.2], ["566", "I7N3G6G", "566", 0, "no description", 60.2], ["567", "I7N3G6G", "567", 0, "no description", 60.2], ["568", "I7N3G6G", "568", 0, "no description", 60.2], ["569", "I7N3G6G", "569", 0, "no description", 60.2], ["570", "I7N3G6G", "570", 0, "no description", 60.2], ["571", "I7N3G6G", "571", 0, "no description", 60.2], ["572", "I7N3G6G", "572", 0, "no description", 60.2], ["573", "I7N3G6G", "573", 0, "no description", 60.2], ["574", "I7N3G6G", "574", 0, "no description", 60.2], ["575", "I7N3G6G", "575", 0, "no description", 60.2], ["576", "I7N3G6G", "576", 0, "no description", 60.2], ["577", "I7N3G6G", "577", 0, "no description", 60.2], ["578", "I7N3G6G", "578", 0, "no description", 60.2], ["579", "I7N3G6G", "579", 0, "no description", 60.2], ["580", "I7N3G6G", "580", 0, "no description", 60.2], ["581", "I7N3G6G", "581", 0, "no description", 60.2], ["582", "I7N3G6G", "582", 0, "no description", 60.2], ["583", "I7N3G6G", "583", 0, "no description", 60.2], ["584", "I7N3G6G", "584", 0, "no description", 60.2], ["585", "I7N3G6G", "585", 0, "no description", 60.2], ["586", "I7N3G6G", "586", 0, "no description", 60.2], ["587", "I7N3G6G", "587", 0, "no description", 60.2], ["588", "I7N3G6G", "588", 0, "no description", 60.2], ["589", "I7N3G6G", "589", 0, "no description", 60.2], ["590", "I7N3G6G", "590", 0, "no description", 60.2], ["591", "I7N3G6G", "591", 0, "no description", 60.2], ["592", "I7N3G6G", "592", 0, "no description", 60.2], ["593", "I7N3G6G", "593", 0, "no description", 60.2], ["594", "I7N3G6G", "594", 0, "no description", 60.2], ["595", "I7N3G6G", "595", 0, "no description", 60.2], ["596", "I7N3G6G", "596", 0, "no description", 60.2], ["597", "I7N3G6G", "597", 0, "no description", 60.2], ["598", "I7N3G6G", "598", 0, "no description", 60.2], ["599", "I7N3G6G", "599", 0, "no description", 60.2], ["600", "I7N3G6G", "600", 0, "no description", 60.2], ["601", "I7N3G6G", "601", 0, "no description", 60.2], ["602", "I7N3G6G", "602", 0, "no description", 60.2], ["603", "I7N3G6G", "603", 0, "no description", 60.2], ["604", "I7N3G6G", "604", 0, "no description", 60.2], ["605", "I7N3G6G", "605", 0, "no description", 60.2], ["606", "I7N3G6G", "606", 0, "no description", 60.2], ["607", "I7N3G6G", "607", 0, "no description", 60.2], ["608", "I7N3G6G", "608", 0, "no description", 60.2], ["609", "I7N3G6G", "609", 0, "no description", 60.2], ["610", "I7N3G6G", "610", 0, "no description", 60.2], ["611", "I7N3G6G", "611", 0, "no description", 60.2], ["612", "I7N3G6G", "612", 0, "no description", 60.2], ["613", "I7N3G6G", "613", 0, "no description", 60.2], ["614", "I7N3G6G", "614", 0, "no description", 60.2], ["615", "I7N3G6G", "615", 0, "no description", 60.2], ["616", "I7N3G6G", "616", 0, "no description", 60.2], ["617", "I7N3G6G", "617", 0, "no description", 60.2], ["618", "I7N3G6G", "618", 0, "no description", 60.2], ["619", "I7N3G6G", "619", 0, "no description", 60.2], ["620", "I7N3G6G", "620", 0, "no description", 60.2], ["621", "I7N3G6G", "621", 0, "no description", 60.2], ["622", "I7N3G6G", "622", 0, "no description", 60.2], ["623", "I7N3G6G", "623", 0, "no description", 60.2], ["624", "I7N3G6G", "624", 0, "no description", 60.2], ["625", "I7N3G6G", "625", 0, "no description", 60.2], ["626", "I7N3G6G", "626", 0, "no description", 60.2], ["627", "I7N3G6G", "627", 0, "no description", 60.2], ["628", "I7N3G6G", "628", 0, "no description", 60.2], ["629", "I7N3G6G", "629", 0, "no description", 60.2], ["630", "I7N3G6G", "630", 0, "no description", 60.2], ["631", "I7N3G6G", "631", 0, "no description", 60.2], ["632", "I7N3G6G", "632", 0, "no description", 60.2], ["633", "I7N3G6G", "633", 0, "no description", 60.2], ["634", "I7N3G6G", "634", 0, "no description", 60.2], ["635", "I7N3G6G", "635", 0, "no description", 60.2], ["636", "I7N3G6G", "636", 0, "no description", 60.2], ["637", "I7N3G6G", "637", 0, "no description", 60.2], ["638", "I7N3G6G", "638", 0, "no description", 60.2], ["639", "I7N3G6G", "639", 0, "no description", 60.2], ["640", "I7N3G6G", "640", 0, "no description", 60.2], ["641", "I7N3G6G", "641", 0, "no description", 60.2], ["642", "I7N3G6G", "642", 0, "no description", 60.2], ["643", "I7N3G6G", "643", 0, "no description", 60.2], ["644", "I7N3G6G", "644", 0, "no description", 60.2], ["645", "I7N3G6G", "645", 0, "no description", 60.2], ["646", "I7N3G6G", "646", 0, "no description", 60.2], ["647", "I7N3G6G", "647", 0, "no description", 60.2], ["648", "I7N3G6G", "648", 0, "no description", 60.2], ["649", "I7N3G6G", "649", 0, "no description", 60.2], ["650", "I7N3G6G", "650", 0, "no description", 60.2], ["651", "I7N3G6G", "651", 0, "no description", 60.2], ["652", "I7N3G6G", "652", 0, "no description", 60.2], ["653", "I7N3G6G", "653", 0, "no description", 60.2], ["654", "I7N3G6G", "654", 0, "no description", 60.2], ["655", "I7N3G6G", "655", 0, "no description", 60.2], ["656", "I7N3G6G", "656", 0, "no description", 60.2], ["657", "I7N3G6G", "657", 0, "no description", 60.2], ["658", "I7N3G6G", "658", 0, "no description", 60.2], ["659", "I7N3G6G", "659", 0, "no description", 60.2], ["660", "I7N3G6G", "660", 0, "no description", 60.2], ["661", "I7N3G6G", "661", 0, "no description", 60.2], ["662", "I7N3G6G", "662", 0, "no description", 60.2], ["663", "I7N3G6G", "663", 0, "no description", 60.2], ["664", "I7N3G6G", "664", 0, "no description", 60.2], ["665", "I7N3G6G", "665", 0, "no description", 60.2], ["666", "I7N3G6G", "666", 0, "no description", 60.2], ["667", "I7N3G6G", "667", 0, "no description", 60.2], ["668", "I7N3G6G", "668", 0, "no description", 60.2], ["669", "I7N3G6G", "669", 0, "no description", 60.2], ["670", "I7N3G6G", "670", 0, "no description", 60.2], ["671", "I7N3G6G", "671", 0, "no description", 60.2], ["672", "I7N3G6G", "672", 0, "no description", 60.2], ["673", "I7N3G6G", "673", 0, "no description", 60.2], ["674", "I7N3G6G", "674", 0, "no description", 60.2], ["675", "I7N3G6G", "675", 0, "no description", 60.2], ["676", "I7N3G6G", "676", 0, "no description", 60.2], ["677", "I7N3G6G", "677", 0, "no description", 60.2], ["678", "I7N3G6G", "678", 0, "no description", 60.2], ["679", "I7N3G6G", "679", 0, "no description", 60.2], ["680", "I7N3G6G", "680", 0, "no description", 60.2], ["681", "I7N3G6G", "681", 0, "no description", 60.2], ["682", "I7N3G6G", "682", 0, "no description", 60.2], ["683", "I7N3G6G", "683", 0, "no description", 60.2], ["684", "I7N3G6G", "684", 0, "no description", 60.2], ["685", "I7N3G6G", "685", 0, "no description", 60.2], ["686", "I7N3G6G", "686", 0, "no description", 60.2], ["687", "I7N3G6G", "687", 0, "no description", 60.2], ["688", "I7N3G6G", "688", 0, "no description", 60.2], ["689", "I7N3G6G", "689", 0, "no description", 60.2], ["690", "I7N3G6G", "690", 0, "no description", 60.2], ["691", "I7N3G6G", "691", 0, "no description", 60.2], ["692", "I7N3G6G", "692", 0, "no description", 60.2], ["693", "I7N3G6G", "693", 0, "no description", 60.2], ["694", "I7N3G6G", "694", 0, "no description", 60.2], ["695", "I7N3G6G", "695", 0, "no description", 60.2], ["696", "I7N3G6G", "696", 0, "no description", 60.2], ["697", "I7N3G6G", "697", 0, "no description", 60.2], ["698", "I7N3G6G", "698", 0, "no description", 60.2], ["699", "I7N3G6G", "699", 0, "no description", 60.2], ["700", "I7N3G6G", "700", 0, "no description", 60.2], ["701", "I7N3G6G", "701", 0, "no description", 60.2], ["702", "I7N3G6G", "702", 0, "no description", 60.2], ["703", "I7N3G6G", "703", 0, "no description", 60.2], ["704", "I7N3G6G", "704", 0, "no description", 60.2], ["705", "I7N3G6G", "705", 0, "no description", 60.2], ["706", "I7N3G6G", "706", 0, "no description", 60.2], ["707", "I7N3G6G", "707", 0, "no description", 60.2], ["708", "I7N3G6G", "708", 0, "no description", 60.2], ["709", "I7N3G6G", "709", 0, "no description", 60.2], ["710", "I7N3G6G", "710", 0, "no description", 60.2], ["711", "I7N3G6G", "711", 0, "no description", 60.2], ["712", "I7N3G6G", "712", 0, "no description", 60.2], ["713", "I7N3G6G", "713", 0, "no description", 60.2], ["714", "I7N3G6G", "714", 0, "no description", 60.2], ["715", "I7N3G6G", "715", 0, "no description", 60.2], ["716", "I7N3G6G", "716", 0, "no description", 60.2], ["717", "I7N3G6G", "717", 0, "no description", 60.2], ["718", "I7N3G6G", "718", 0, "no description", 60.2], ["719", "I7N3G6G", "719", 0, "no description", 60.2], ["720", "I7N3G6G", "720", 0, "no description", 60.2], ["721", "I7N3G6G", "721", 0, "no description", 60.2], ["722", "I7N3G6G", "722", 0, "no description", 60.2], ["723", "I7N3G6G", "723", 0, "no description", 60.2], ["724", "I7N3G6G", "724", 0, "no description", 60.2], ["725", "I7N3G6G", "725", 0, "no description", 60.2], ["726", "I7N3G6G", "726", 0, "no description", 60.2], ["727", "I7N3G6G", "727", 0, "no description", 60.2], ["728", "I7N3G6G", "728", 0, "no description", 60.2], ["729", "I7N3G6G", "729", 0, "no description", 60.2], ["730", "I7N3G6G", "730", 0, "no description", 60.2], ["731", "I7N3G6G", "731", 0, "no description", 60.2], ["732", "I7N3G6G", "732", 0, "no description", 60.2], ["733", "I7N3G6G", "733", 0, "no description", 60.2], ["734", "I7N3G6G", "734", 0, "no description", 60.2], ["735", "I7N3G6G", "735", 0, "no description", 60.2], ["736", "I7N3G6G", "736", 0, "no description", 60.2], ["737", "I7N3G6G", "737", 0, "no description", 60.2], ["738", "I7N3G6G", "738", 0, "no description", 60.2], ["739", "I7N3G6G", "739", 0, "no description", 60.2], ["740", "I7N3G6G", "740", 0, "no description", 60.2], ["741", "I7N3G6G", "741", 0, "no description", 60.2], ["742", "I7N3G6G", "742", 0, "no description", 60.2], ["743", "I7N3G6G", "743", 0, "no description", 60.2], ["744", "I7N3G6G", "744", 0, "no description", 60.2], ["745", "I7N3G6G", "745", 0, "no description", 60.2], ["746", "I7N3G6G", "746", 0, "no description", 60.2], ["747", "I7N3G6G", "747", 0, "no description", 60.2], ["748", "I7N3G6G", "748", 0, "no description", 60.2], ["749", "I7N3G6G", "749", 0, "no description", 60.2], ["750", "I7N3G6G", "750", 0, "no description", 60.2], ["751", "I7N3G6G", "751", 0, "no description", 60.2], ["752", "I7N3G6G", "752", 0, "no description", 60.2], ["753", "I7N3G6G", "753", 0, "no description", 60.2], ["754", "I7N3G6G", "754", 0, "no description", 60.2], ["755", "I7N3G6G", "755", 0, "no description", 60.2], ["756", "I7N3G6G", "756", 0, "no description", 60.2], ["757", "I7N3G6G", "757", 0, "no description", 60.2], ["758", "I7N3G6G", "758", 0, "no description", 60.2], ["759", "I7N3G6G", "759", 0, "no description", 60.2], ["760", "I7N3G6G", "760", 0, "no description", 60.2], ["761", "I7N3G6G", "761", 0, "no description", 60.2], ["762", "I7N3G6G", "762", 0, "no description", 60.2], ["763", "I7N3G6G", "763", 0, "no description", 60.2], ["764", "I7N3G6G", "764", 0, "no description", 60.2], ["765", "I7N3G6G", "765", 0, "no description", 60.2], ["766", "I7N3G6G", "766", 0, "no description", 60.2], ["767", "I7N3G6G", "767", 0, "no description", 60.2], ["768", "I7N3G6G", "768", 0, "no description", 60.2], ["769", "I7N3G6G", "769", 0, "no description", 60.2], ["770", "I7N3G6G", "770", 0, "no description", 60.2], ["771", "I7N3G6G", "771", 0, "no description", 60.2], ["772", "I7N3G6G", "772", 0, "no description", 60.2], ["773", "I7N3G6G", "773", 0, "no description", 60.2], ["774", "I7N3G6G", "774", 0, "no description", 60.2], ["775", "I7N3G6G", "775", 0, "no description", 60.2], ["776", "I7N3G6G", "776", 0, "no description", 60.2], ["777", "I7N3G6G", "777", 0, "no description", 60.2], ["778", "I7N3G6G", "778", 0, "no description", 60.2], ["779", "I7N3G6G", "779", 0, "no description", 60.2], ["780", "I7N3G6G", "780", 0, "no description", 60.2], ["781", "I7N3G6G", "781", 0, "no description", 60.2], ["782", "I7N3G6G", "782", 0, "no description", 60.2], ["783", "I7N3G6G", "783", 0, "no description", 60.2], ["784", "I7N3G6G", "784", 0, "no description", 60.2], ["785", "I7N3G6G", "785", 0, "no description", 60.2], ["786", "I7N3G6G", "786", 0, "no description", 60.2], ["787", "I7N3G6G", "787", 0, "no description", 60.2], ["788", "I7N3G6G", "788", 0, "no description", 60.2], ["789", "I7N3G6G", "789", 0, "no description", 60.2], ["790", "I7N3G6G", "790", 0, "no description", 60.2], ["791", "I7N3G6G", "791", 0, "no description", 60.2], ["792", "I7N3G6G", "792", 0, "no description", 60.2], ["793", "I7N3G6G", "793", 0, "no description", 60.2], ["794", "I7N3G6G", "794", 0, "no description", 60.2], ["795", "I7N3G6G", "795", 0, "no description", 60.2], ["796", "I7N3G6G", "796", 0, "no description", 60.2], ["797", "I7N3G6G", "797", 0, "no description", 60.2], ["798", "I7N3G6G", "798", 0, "no description", 60.2], ["799", "I7N3G6G", "799", 0, "no description", 60.2], ["800", "I7N3G6G", "800", 0, "no description", 60.2], ["801", "I7N3G6G", "801", 0, "no description", 60.2], ["802", "I7N3G6G", "802", 0, "no description", 60.2], ["803", "I7N3G6G", "803", 0, "no description", 60.2], ["804", "I7N3G6G", "804", 0, "no description", 60.2], ["805", "I7N3G6G", "805", 0, "no description", 60.2], ["806", "I7N3G6G", "806", 0, "no description", 60.2], ["807", "I7N3G6G", "807", 0, "no description", 60.2], ["808", "I7N3G6G", "808", 0, "no description", 60.2], ["809", "I7N3G6G", "809", 0, "no description", 60.2], ["810", "I7N3G6G", "810", 0, "no description", 60.2], ["811", "I7N3G6G", "811", 0, "no description", 60.2], ["812", "I7N3G6G", "812", 0, "no description", 60.2], ["813", "I7N3G6G", "813", 0, "no description", 60.2], ["814", "I7N3G6G", "814", 0, "no description", 60.2], ["815", "I7N3G6G", "815", 0, "no description", 60.2], ["816", "I7N3G6G", "816", 0, "no description", 60.2], ["817", "I7N3G6G", "817", 0, "no description", 60.2], ["818", "I7N3G6G", "818", 0, "no description", 60.2], ["819", "I7N3G6G", "819", 0, "no description", 60.2], ["820", "I7N3G6G", "820", 0, "no description", 60.2], ["821", "I7N3G6G", "821", 0, "no description", 60.2], ["822", "I7N3G6G", "822", 0, "no description", 60.2], ["823", "I7N3G6G", "823", 0, "no description", 60.2], ["824", "I7N3G6G", "824", 0, "no description", 60.2], ["825", "I7N3G6G", "825", 0, "no description", 60.2], ["826", "I7N3G6G", "826", 0, "no description", 60.2], ["827", "I7N3G6G", "827", 0, "no description", 60.2], ["828", "I7N3G6G", "828", 0, "no description", 60.2], ["829", "I7N3G6G", "829", 0, "no description", 60.2], ["830", "I7N3G6G", "830", 0, "no description", 60.2], ["831", "I7N3G6G", "831", 0, "no description", 60.2], ["832", "I7N3G6G", "832", 0, "no description", 60.2], ["833", "I7N3G6G", "833", 0, "no description", 60.2], ["834", "I7N3G6G", "834", 0, "no description", 60.2], ["835", "I7N3G6G", "835", 0, "no description", 60.2], ["836", "I7N3G6G", "836", 0, "no description", 60.2], ["837", "I7N3G6G", "837", 0, "no description", 60.2], ["838", "I7N3G6G", "838", 0, "no description", 60.2], ["839", "I7N3G6G", "839", 0, "no description", 60.2], ["840", "I7N3G6G", "840", 0, "no description", 60.2], ["841", "I7N3G6G", "841", 0, "no description", 60.2], ["842", "I7N3G6G", "842", 0, "no description", 60.2], ["843", "I7N3G6G", "843", 0, "no description", 60.2], ["844", "I7N3G6G", "844", 0, "no description", 60.2], ["845", "I7N3G6G", "845", 0, "no description", 60.2], ["846", "I7N3G6G", "846", 0, "no description", 60.2], ["847", "I7N3G6G", "847", 0, "no description", 60.2], ["848", "I7N3G6G", "848", 0, "no description", 60.2], ["849", "I7N3G6G", "849", 0, "no description", 60.2], ["850", "I7N3G6G", "850", 0, "no description", 60.2], ["851", "I7N3G6G", "851", 0, "no description", 60.2], ["852", "I7N3G6G", "852", 0, "no description", 60.2], ["853", "I7N3G6G", "853", 0, "no description", 60.2], ["854", "I7N3G6G", "854", 0, "no description", 60.2], ["855", "I7N3G6G", "855", 0, "no description", 60.2], ["856", "I7N3G6G", "856", 0, "no description", 60.2], ["857", "I7N3G6G", "857", 0, "no description", 60.2], ["858", "I7N3G6G", "858", 0, "no description", 60.2], ["859", "I7N3G6G", "859", 0, "no description", 60.2], ["860", "I7N3G6G", "860", 0, "no description", 60.2], ["861", "I7N3G6G", "861", 0, "no description", 60.2], ["862", "I7N3G6G", "862", 0, "no description", 60.2], ["863", "I7N3G6G", "863", 0, "no description", 60.2], ["864", "I7N3G6G", "864", 0, "no description", 60.2], ["865", "I7N3G6G", "865", 0, "no description", 60.2], ["866", "I7N3G6G", "866", 0, "no description", 60.2], ["867", "I7N3G6G", "867", 0, "no description", 60.2], ["868", "I7N3G6G", "868", 0, "no description", 60.2], ["869", "I7N3G6G", "869", 0, "no description", 60.2], ["870", "I7N3G6G", "870", 0, "no description", 60.2], ["871", "I7N3G6G", "871", 0, "no description", 60.2], ["872", "I7N3G6G", "872", 0, "no description", 60.2], ["873", "I7N3G6G", "873", 0, "no description", 60.2], ["874", "I7N3G6G", "874", 0, "no description", 60.2], ["875", "I7N3G6G", "875", 0, "no description", 60.2], ["876", "I7N3G6G", "876", 0, "no description", 60.2], ["877", "I7N3G6G", "877", 0, "no description", 60.2], ["878", "I7N3G6G", "878", 0, "no description", 60.2], ["879", "I7N3G6G", "879", 0, "no description", 60.2], ["880", "I7N3G6G", "880", 0, "no description", 60.2], ["881", "I7N3G6G", "881", 0, "no description", 60.2], ["882", "I7N3G6G", "882", 0, "no description", 60.2], ["883", "I7N3G6G", "883", 0, "no description", 60.2], ["884", "I7N3G6G", "884", 0, "no description", 60.2], ["885", "I7N3G6G", "885", 0, "no description", 60.2], ["886", "I7N3G6G", "886", 0, "no description", 60.2], ["887", "I7N3G6G", "887", 0, "no description", 60.2], ["888", "I7N3G6G", "888", 0, "no description", 60.2], ["889", "I7N3G6G", "889", 0, "no description", 60.2], ["890", "I7N3G6G", "890", 0, "no description", 60.2], ["891", "I7N3G6G", "891", 0, "no description", 60.2], ["892", "I7N3G6G", "892", 0, "no description", 60.2], ["893", "I7N3G6G", "893", 0, "no description", 60.2], ["894", "I7N3G6G", "894", 0, "no description", 60.2], ["895", "I7N3G6G", "895", 0, "no description", 60.2], ["896", "I7N3G6G", "896", 0, "no description", 60.2], ["897", "I7N3G6G", "897", 0, "no description", 60.2], ["898", "I7N3G6G", "898", 0, "no description", 60.2], ["899", "I7N3G6G", "899", 0, "no description", 60.2], ["900", "I7N3G6G", "900", 0, "no description", 60.2], ["901", "I7N3G6G", "901", 0, "no description", 60.2], ["902", "I7N3G6G", "902", 0, "no description", 60.2], ["903", "I7N3G6G", "903", 0, "no description", 60.2], ["904", "I7N3G6G", "904", 0, "no description", 60.2], ["905", "I7N3G6G", "905", 0, "no description", 60.2], ["906", "I7N3G6G", "906", 0, "no description", 60.2], ["907", "I7N3G6G", "907", 0, "no description", 60.2], ["908", "I7N3G6G", "908", 0, "no description", 60.2], ["909", "I7N3G6G", "909", 0, "no description", 60.2], ["910", "I7N3G6G", "910", 0, "no description", 60.2], ["911", "I7N3G6G", "911", 0, "no description", 60.2], ["912", "I7N3G6G", "912", 0, "no description", 60.2], ["913", "I7N3G6G", "913", 0, "no description", 60.2], ["914", "I7N3G6G", "914", 0, "no description", 60.2], ["915", "I7N3G6G", "915", 0, "no description", 60.2], ["916", "I7N3G6G", "916", 0, "no description", 60.2], ["917", "I7N3G6G", "917", 0, "no description", 60.2], ["918", "I7N3G6G", "918", 0, "no description", 60.2], ["919", "I7N3G6G", "919", 0, "no description", 60.2], ["920", "I7N3G6G", "920", 0, "no description", 60.2], ["921", "I7N3G6G", "921", 0, "no description", 60.2], ["922", "I7N3G6G", "922", 0, "no description", 60.2], ["923", "I7N3G6G", "923", 0, "no description", 60.2], ["924", "I7N3G6G", "924", 0, "no description", 60.2], ["925", "I7N3G6G", "925", 0, "no description", 60.2], ["926", "I7N3G6G", "926", 0, "no description", 60.2], ["927", "I7N3G6G", "927", 0, "no description", 60.2], ["928", "I7N3G6G", "928", 0, "no description", 60.2], ["929", "I7N3G6G", "929", 0, "no description", 60.2], ["930", "I7N3G6G", "930", 0, "no description", 60.2], ["931", "I7N3G6G", "931", 0, "no description", 60.2], ["932", "I7N3G6G", "932", 0, "no description", 60.2], ["933", "I7N3G6G", "933", 0, "no description", 60.2], ["934", "I7N3G6G", "934", 0, "no description", 60.2], ["935", "I7N3G6G", "935", 0, "no description", 60.2], ["936", "I7N3G6G", "936", 0, "no description", 60.2], ["937", "I7N3G6G", "937", 0, "no description", 60.2], ["938", "I7N3G6G", "938", 0, "no description", 60.2], ["939", "I7N3G6G", "939", 0, "no description", 60.2], ["940", "I7N3G6G", "940", 0, "no description", 60.2], ["941", "I7N3G6G", "941", 0, "no description", 60.2], ["942", "I7N3G6G", "942", 0, "no description", 60.2], ["943", "I7N3G6G", "943", 0, "no description", 60.2], ["944", "I7N3G6G", "944", 0, "no description", 60.2], ["945", "I7N3G6G", "945", 0, "no description", 60.2], ["946", "I7N3G6G", "946", 0, "no description", 60.2], ["947", "I7N3G6G", "947", 0, "no description", 60.2], ["948", "I7N3G6G", "948", 0, "no description", 60.2], ["949", "I7N3G6G", "949", 0, "no description", 60.2], ["950", "I7N3G6G", "950", 0, "no description", 60.2], ["951", "I7N3G6G", "951", 0, "no description", 60.2], ["952", "I7N3G6G", "952", 0, "no description", 60.2], ["953", "I7N3G6G", "953", 0, "no description", 60.2], ["954", "I7N3G6G", "954", 0, "no description", 60.2], ["955", "I7N3G6G", "955", 0, "no description", 60.2], ["956", "I7N3G6G", "956", 0, "no description", 60.2], ["957", "I7N3G6G", "957", 0, "no description", 60.2], ["958", "I7N3G6G", "958", 0, "no description", 60.2], ["959", "I7N3G6G", "959", 0, "no description", 60.2], ["960", "I7N3G6G", "960", 0, "no description", 60.2], ["961", "I7N3G6G", "961", 0, "no description", 60.2], ["962", "I7N3G6G", "962", 0, "no description", 60.2], ["963", "I7N3G6G", "963", 0, "no description", 60.2], ["964", "I7N3G6G", "964", 0, "no description", 60.2], ["965", "I7N3G6G", "965", 0, "no description", 60.2], ["966", "I7N3G6G", "966", 0, "no description", 60.2], ["967", "I7N3G6G", "967", 0, "no description", 60.2], ["968", "I7N3G6G", "968", 0, "no description", 60.2], ["969", "I7N3G6G", "969", 0, "no description", 60.2], ["970", "I7N3G6G", "970", 0, "no description", 60.2], ["971", "I7N3G6G", "971", 0, "no description", 60.2], ["972", "I7N3G6G", "972", 0, "no description", 60.2], ["973", "I7N3G6G", "973", 0, "no description", 60.2], ["974", "I7N3G6G", "974", 0, "no description", 60.2], ["975", "I7N3G6G", "975", 0, "no description", 60.2], ["976", "I7N3G6G", "976", 0, "no description", 60.2], ["977", "I7N3G6G", "977", 0, "no description", 60.2], ["978", "I7N3G6G", "978", 0, "no description", 60.2], ["979", "I7N3G6G", "979", 0, "no description", 60.2], ["980", "I7N3G6G", "980", 0, "no description", 60.2], ["981", "I7N3G6G", "981", 0, "no description", 60.2], ["982", "I7N3G6G", "982", 0, "no description", 60.2], ["983", "I7N3G6G", "983", 0, "no description", 60.2], ["984", "I7N3G6G", "984", 0, "no description", 60.2], ["985", "I7N3G6G", "985", 0, "no description", 60.2], ["986", "I7N3G6G", "986", 0, "no description", 60.2], ["987", "I7N3G6G", "987", 0, "no description", 60.2], ["988", "I7N3G6G", "988", 0, "no description", 60.2], ["989", "I7N3G6G", "989", 0, "no description", 60.2], ["990", "I7N3G6G", "990", 0, "no description", 60.2], ["991", "I7N3G6G", "991", 0, "no description", 60.2], ["992", "I7N3G6G", "992", 0, "no description", 60.2], ["993", "I7N3G6G", "993", 0, "no description", 60.2], ["994", "I7N3G6G", "994", 0, "no description", 60.2], ["995", "I7N3G6G", "995", 0, "no description", 60.2], ["996", "I7N3G6G", "996", 0, "no description", 60.2], ["997", "I7N3G6G", "997", 0, "no description", 60.2], ["998", "I7N3G6G", "998", 0, "no description", 60.2], ["999", "I7N3G6G", "999", 0, "no description", 60.2], ["1000", "I7N3G6G", "1000", 0, "no description", 60.2], ["1001", "I7N3G6G", "1001", 0, "no description", 60.2], ["1002", "I7N3G6G", "1002", 0, "no description", 60.2], ["1003", "I7N3G6G", "1003", 0, "no description", 60.2], ["1004", "I7N3G6G", "1004", 0, "no description", 60.2], ["1005", "I7N3G6G", "1005", 0, "no description", 60.2], ["1006", "I7N3G6G", "1006", 0, "no description", 60.2], ["1007", "I7N3G6G", "1007", 0, "no description", 60.2], ["1008", "I7N3G6G", "1008", 0, "no description", 60.2], ["1009", "I7N3G6G", "1009", 0, "no description", 60.2], ["1010", "I7N3G6G", "1010", 0, "no description", 60.2], ["1011", "I7N3G6G", "1011", 0, "no description", 60.2], ["1012", "I7N3G6G", "1012", 0, "no description", 60.2], ["1013", "I7N3G6G", "1013", 0, "no description", 60.2], ["1014", "I7N3G6G", "1014", 0, "no description", 60.2], ["1015", "I7N3G6G", "1015", 0, "no description", 60.2], ["1016", "I7N3G6G", "1016", 0, "no description", 60.2], ["1017", "I7N3G6G", "1017", 0, "no description", 60.2], ["1018", "I7N3G6G", "1018", 0, "no description", 60.2], ["1019", "I7N3G6G", "1019", 0, "no description", 60.2], ["1020", "I7N3G6G", "1020", 0, "no description", 60.2], ["1021", "I7N3G6G", "1021", 0, "no description", 60.2], ["1022", "I7N3G6G", "1022", 0, "no description", 60.2], ["1023", "I7N3G6G", "1023", 0, "no description", 60.2], ["1024", "I7N3G6G", "1024", 0, "no description", 60.2], ["1025", "I7N3G6G", "1025", 0, "no description", 60.2], ["1026", "I7N3G6G", "1026", 0, "no description", 60.2], ["1027", "I7N3G6G", "1027", 0, "no description", 60.2], ["1028", "I7N3G6G", "1028", 0, "no description", 60.2], ["1029", "I7N3G6G", "1029", 0, "no description", 60.2], ["1030", "I7N3G6G", "1030", 0, "no description", 60.2], ["1031", "I7N3G6G", "1031", 0, "no description", 60.2], ["1032", "I7N3G6G", "1032", 0, "no description", 60.2], ["1033", "I7N3G6G", "1033", 0, "no description", 60.2], ["1034", "I7N3G6G", "1034", 0, "no description", 60.2], ["1035", "I7N3G6G", "1035", 0, "no description", 60.2], ["1036", "I7N3G6G", "1036", 0, "no description", 60.2], ["1037", "I7N3G6G", "1037", 0, "no description", 60.2], ["1038", "I7N3G6G", "1038", 0, "no description", 60.2], ["1039", "I7N3G6G", "1039", 0, "no description", 60.2], ["1040", "I7N3G6G", "1040", 0, "no description", 60.2], ["1041", "I7N3G6G", "1041", 0, "no description", 60.2], ["1042", "I7N3G6G", "1042", 0, "no description", 60.2], ["1043", "I7N3G6G", "1043", 0, "no description", 60.2], ["1044", "I7N3G6G", "1044", 0, "no description", 60.2], ["1045", "I7N3G6G", "1045", 0, "no description", 60.2], ["1046", "I7N3G6G", "1046", 0, "no description", 60.2], ["1047", "I7N3G6G", "1047", 0, "no description", 60.2], ["1048", "I7N3G6G", "1048", 0, "no description", 60.2], ["1049", "I7N3G6G", "1049", 0, "no description", 60.2], ["1050", "I7N3G6G", "1050", 0, "no description", 60.2], ["1051", "I7N3G6G", "1051", 0, "no description", 60.2], ["1052", "I7N3G6G", "1052", 0, "no description", 60.2], ["1053", "I7N3G6G", "1053", 0, "no description", 60.2], ["1054", "I7N3G6G", "1054", 0, "no description", 60.2], ["1055", "I7N3G6G", "1055", 0, "no description", 60.2], ["1056", "I7N3G6G", "1056", 0, "no description", 60.2], ["1057", "I7N3G6G", "1057", 0, "no description", 60.2], ["1058", "I7N3G6G", "1058", 0, "no description", 60.2], ["1059", "I7N3G6G", "1059", 0, "no description", 60.2], ["1060", "I7N3G6G", "1060", 0, "no description", 60.2], ["1061", "I7N3G6G", "1061", 0, "no description", 60.2], ["1062", "I7N3G6G", "1062", 0, "no description", 60.2], ["1063", "I7N3G6G", "1063", 0, "no description", 60.2], ["1064", "I7N3G6G", "1064", 0, "no description", 60.2], ["1065", "I7N3G6G", "1065", 0, "no description", 60.2], ["1066", "I7N3G6G", "1066", 0, "no description", 60.2], ["1067", "I7N3G6G", "1067", 0, "no description", 60.2], ["1068", "I7N3G6G", "1068", 0, "no description", 60.2], ["1069", "I7N3G6G", "1069", 0, "no description", 60.2], ["1070", "I7N3G6G", "1070", 0, "no description", 60.2], ["1071", "I7N3G6G", "1071", 0, "no description", 60.2], ["1072", "I7N3G6G", "1072", 0, "no description", 60.2], ["1073", "I7N3G6G", "1073", 0, "no description", 60.2], ["1074", "I7N3G6G", "1074", 0, "no description", 60.2], ["1075", "I7N3G6G", "1075", 0, "no description", 60.2], ["1076", "I7N3G6G", "1076", 0, "no description", 60.2], ["1077", "I7N3G6G", "1077", 0, "no description", 60.2], ["1078", "I7N3G6G", "1078", 0, "no description", 60.2], ["1079", "I7N3G6G", "1079", 0, "no description", 60.2], ["1080", "I7N3G6G", "1080", 0, "no description", 60.2], ["1081", "I7N3G6G", "1081", 0, "no description", 60.2], ["1082", "I7N3G6G", "1082", 0, "no description", 60.2], ["1083", "I7N3G6G", "1083", 0, "no description", 60.2], ["1084", "I7N3G6G", "1084", 0, "no description", 60.2], ["1085", "I7N3G6G", "1085", 0, "no description", 60.2], ["1086", "I7N3G6G", "1086", 0, "no description", 60.2], ["1087", "I7N3G6G", "1087", 0, "no description", 60.2], ["1088", "I7N3G6G", "1088", 0, "no description", 60.2], ["1089", "I7N3G6G", "1089", 0, "no description", 60.2], ["1090", "I7N3G6G", "1090", 0, "no description", 60.2], ["1091", "I7N3G6G", "1091", 0, "no description", 60.2], ["1092", "I7N3G6G", "1092", 0, "no description", 60.2], ["1093", "I7N3G6G", "1093", 0, "no description", 60.2], ["1094", "I7N3G6G", "1094", 0, "no description", 60.2], ["1095", "I7N3G6G", "1095", 0, "no description", 60.2], ["1096", "I7N3G6G", "1096", 0, "no description", 60.2], ["1097", "I7N3G6G", "1097", 0, "no description", 60.2], ["1098", "I7N3G6G", "1098", 0, "no description", 60.2], ["1099", "I7N3G6G", "1099", 0, "no description", 60.2], ["1100", "I7N3G6G", "1100", 0, "no description", 60.2], ["1101", "I7N3G6G", "1101", 0, "no description", 60.2], ["1102", "I7N3G6G", "1102", 0, "no description", 60.2], ["1103", "I7N3G6G", "1103", 0, "no description", 60.2], ["1104", "I7N3G6G", "1104", 0, "no description", 60.2], ["1105", "I7N3G6G", "1105", 0, "no description", 60.2], ["1106", "I7N3G6G", "1106", 0, "no description", 60.2], ["1107", "I7N3G6G", "1107", 0, "no description", 60.2], ["1108", "I7N3G6G", "1108", 0, "no description", 60.2], ["1109", "I7N3G6G", "1109", 0, "no description", 60.2], ["1110", "I7N3G6G", "1110", 0, "no description", 60.2], ["1111", "I7N3G6G", "1111", 0, "no description", 60.2], ["1112", "I7N3G6G", "1112", 0, "no description", 60.2], ["1113", "I7N3G6G", "1113", 0, "no description", 60.2], ["1114", "I7N3G6G", "1114", 0, "no description", 60.2], ["1115", "I7N3G6G", "1115", 0, "no description", 60.2], ["1116", "I7N3G6G", "1116", 0, "no description", 60.2], ["1117", "I7N3G6G", "1117", 0, "no description", 60.2], ["1118", "I7N3G6G", "1118", 0, "no description", 60.2], ["1119", "I7N3G6G", "1119", 0, "no description", 60.2], ["1120", "I7N3G6G", "1120", 0, "no description", 60.2], ["1121", "I7N3G6G", "1121", 0, "no description", 60.2], ["1122", "I7N3G6G", "1122", 0, "no description", 60.2], ["1123", "I7N3G6G", "1123", 0, "no description", 60.2], ["1124", "I7N3G6G", "1124", 0, "no description", 60.2], ["1125", "I7N3G6G", "1125", 0, "no description", 60.2], ["1126", "I7N3G6G", "1126", 0, "no description", 60.2], ["1127", "I7N3G6G", "1127", 0, "no description", 60.2], ["1128", "I7N3G6G", "1128", 0, "no description", 60.2], ["1129", "I7N3G6G", "1129", 0, "no description", 60.2], ["1130", "I7N3G6G", "1130", 0, "no description", 60.2], ["1131", "I7N3G6G", "1131", 0, "no description", 60.2], ["1132", "I7N3G6G", "1132", 0, "no description", 60.2], ["1133", "I7N3G6G", "1133", 0, "no description", 60.2], ["1134", "I7N3G6G", "1134", 0, "no description", 60.2], ["1135", "I7N3G6G", "1135", 0, "no description", 60.2], ["1136", "I7N3G6G", "1136", 0, "no description", 60.2], ["1137", "I7N3G6G", "1137", 0, "no description", 60.2], ["1138", "I7N3G6G", "1138", 0, "no description", 60.2], ["1139", "I7N3G6G", "1139", 0, "no description", 60.2], ["1140", "I7N3G6G", "1140", 0, "no description", 60.2], ["1141", "I7N3G6G", "1141", 0, "no description", 60.2], ["1142", "I7N3G6G", "1142", 0, "no description", 60.2], ["1143", "I7N3G6G", "1143", 0, "no description", 60.2], ["1144", "I7N3G6G", "1144", 0, "no description", 60.2], ["1145", "I7N3G6G", "1145", 0, "no description", 60.2], ["1146", "I7N3G6G", "1146", 0, "no description", 60.2], ["1147", "I7N3G6G", "1147", 0, "no description", 60.2], ["1148", "I7N3G6G", "1148", 0, "no description", 60.2], ["1149", "I7N3G6G", "1149", 0, "no description", 60.2], ["1150", "I7N3G6G", "1150", 0, "no description", 60.2], ["1151", "I7N3G6G", "1151", 0, "no description", 60.2], ["1152", "I7N3G6G", "1152", 0, "no description", 60.2], ["1153", "I7N3G6G", "1153", 0, "no description", 60.2], ["1154", "I7N3G6G", "1154", 0, "no description", 60.2], ["1155", "I7N3G6G", "1155", 0, "no description", 60.2], ["1156", "I7N3G6G", "1156", 0, "no description", 60.2], ["1157", "I7N3G6G", "1157", 0, "no description", 60.2], ["1158", "I7N3G6G", "1158", 0, "no description", 60.2], ["1159", "I7N3G6G", "1159", 0, "no description", 60.2], ["1160", "I7N3G6G", "1160", 0, "no description", 60.2], ["1161", "I7N3G6G", "1161", 0, "no description", 60.2], ["1162", "I7N3G6G", "1162", 0, "no description", 60.2], ["1163", "I7N3G6G", "1163", 0, "no description", 60.2], ["1164", "I7N3G6G", "1164", 0, "no description", 60.2], ["1165", "I7N3G6G", "1165", 0, "no description", 60.2], ["1166", "I7N3G6G", "1166", 0, "no description", 60.2], ["1167", "I7N3G6G", "1167", 0, "no description", 60.2], ["1168", "I7N3G6G", "1168", 0, "no description", 60.2], ["1169", "I7N3G6G", "1169", 0, "no description", 60.2], ["1170", "I7N3G6G", "1170", 0, "no description", 60.2], ["1171", "I7N3G6G", "1171", 0, "no description", 60.2], ["1172", "I7N3G6G", "1172", 0, "no description", 60.2], ["1173", "I7N3G6G", "1173", 0, "no description", 60.2], ["1174", "I7N3G6G", "1174", 0, "no description", 60.2], ["1175", "I7N3G6G", "1175", 0, "no description", 60.2], ["1176", "I7N3G6G", "1176", 0, "no description", 60.2], ["1177", "I7N3G6G", "1177", 0, "no description", 60.2], ["1178", "I7N3G6G", "1178", 0, "no description", 60.2], ["1179", "I7N3G6G", "1179", 0, "no description", 60.2], ["1180", "I7N3G6G", "1180", 0, "no description", 60.2], ["1181", "I7N3G6G", "1181", 0, "no description", 60.2], ["1182", "I7N3G6G", "1182", 0, "no description", 60.2], ["1183", "I7N3G6G", "1183", 0, "no description", 60.2], ["1184", "I7N3G6G", "1184", 0, "no description", 60.2], ["1185", "I7N3G6G", "1185", 0, "no description", 60.2], ["1186", "I7N3G6G", "1186", 0, "no description", 60.2], ["1187", "I7N3G6G", "1187", 0, "no description", 60.2], ["1188", "I7N3G6G", "1188", 0, "no description", 60.2], ["1189", "I7N3G6G", "1189", 0, "no description", 60.2], ["1190", "I7N3G6G", "1190", 0, "no description", 60.2], ["1191", "I7N3G6G", "1191", 0, "no description", 60.2], ["1192", "I7N3G6G", "1192", 0, "no description", 60.2], ["1193", "I7N3G6G", "1193", 0, "no description", 60.2], ["1194", "I7N3G6G", "1194", 0, "no description", 60.2], ["1195", "I7N3G6G", "1195", 0, "no description", 60.2], ["1196", "I7N3G6G", "1196", 0, "no description", 60.2], ["1197", "I7N3G6G", "1197", 0, "no description", 60.2], ["1198", "I7N3G6G", "1198", 0, "no description", 60.2], ["1199", "I7N3G6G", "1199", 0, "no description", 60.2], ["1200", "I7N3G6G", "1200", 0, "no description", 60.2], ["1201", "I7N3G6G", "1201", 0, "no description", 60.2], ["1202", "I7N3G6G", "1202", 0, "no description", 60.2], ["1203", "I7N3G6G", "1203", 0, "no description", 60.2], ["1204", "I7N3G6G", "1204", 0, "no description", 60.2], ["1205", "I7N3G6G", "1205", 0, "no description", 60.2], ["1206", "I7N3G6G", "1206", 0, "no description", 60.2], ["1207", "I7N3G6G", "1207", 0, "no description", 60.2], ["1208", "I7N3G6G", "1208", 0, "no description", 60.2], ["1209", "I7N3G6G", "1209", 0, "no description", 60.2], ["1210", "I7N3G6G", "1210", 0, "no description", 60.2], ["1211", "I7N3G6G", "1211", 0, "no description", 60.2], ["1212", "I7N3G6G", "1212", 0, "no description", 60.2], ["1213", "I7N3G6G", "1213", 0, "no description", 60.2], ["1214", "I7N3G6G", "1214", 0, "no description", 60.2], ["1215", "I7N3G6G", "1215", 0, "no description", 60.2], ["1216", "I7N3G6G", "1216", 0, "no description", 60.2], ["1217", "I7N3G6G", "1217", 0, "no description", 60.2], ["1218", "I7N3G6G", "1218", 0, "no description", 60.2], ["1219", "I7N3G6G", "1219", 0, "no description", 60.2], ["1220", "I7N3G6G", "1220", 0, "no description", 60.2], ["1221", "I7N3G6G", "1221", 0, "no description", 60.2], ["1222", "I7N3G6G", "1222", 0, "no description", 60.2], ["1223", "I7N3G6G", "1223", 0, "no description", 60.2], ["1224", "I7N3G6G", "1224", 0, "no description", 60.2], ["1225", "I7N3G6G", "1225", 0, "no description", 60.2], ["1226", "I7N3G6G", "1226", 0, "no description", 60.2], ["1227", "I7N3G6G", "1227", 0, "no description", 60.2], ["1228", "I7N3G6G", "1228", 0, "no description", 60.2], ["1229", "I7N3G6G", "1229", 0, "no description", 60.2], ["1230", "I7N3G6G", "1230", 0, "no description", 60.2], ["1231", "I7N3G6G", "1231", 0, "no description", 60.2], ["1232", "I7N3G6G", "1232", 0, "no description", 60.2], ["1233", "I7N3G6G", "1233", 0, "no description", 60.2], ["1234", "I7N3G6G", "1234", 0, "no description", 60.2], ["1235", "I7N3G6G", "1235", 0, "no description", 60.2], ["1236", "I7N3G6G", "1236", 0, "no description", 60.2], ["1237", "I7N3G6G", "1237", 0, "no description", 60.2], ["1238", "I7N3G6G", "1238", 0, "no description", 60.2], ["1239", "I7N3G6G", "1239", 0, "no description", 60.2], ["1240", "I7N3G6G", "1240", 0, "no description", 60.2], ["1241", "I7N3G6G", "1241", 0, "no description", 60.2], ["1242", "I7N3G6G", "1242", 0, "no description", 60.2], ["1243", "I7N3G6G", "1243", 0, "no description", 60.2], ["1244", "I7N3G6G", "1244", 0, "no description", 60.2], ["1245", "I7N3G6G", "1245", 0, "no description", 60.2], ["1246", "I7N3G6G", "1246", 0, "no description", 60.2], ["1247", "I7N3G6G", "1247", 0, "no description", 60.2], ["1248", "I7N3G6G", "1248", 0, "no description", 60.2], ["1249", "I7N3G6G", "1249", 0, "no description", 60.2], ["1250", "I7N3G6G", "1250", 0, "no description", 60.2], ["1251", "I7N3G6G", "1251", 0, "no description", 60.2], ["1252", "I7N3G6G", "1252", 0, "no description", 60.2], ["1253", "I7N3G6G", "1253", 0, "no description", 60.2], ["1254", "I7N3G6G", "1254", 0, "no description", 60.2], ["1255", "I7N3G6G", "1255", 0, "no description", 60.2], ["1256", "I7N3G6G", "1256", 0, "no description", 60.2], ["1257", "I7N3G6G", "1257", 0, "no description", 60.2], ["1258", "I7N3G6G", "1258", 0, "no description", 60.2], ["1259", "I7N3G6G", "1259", 0, "no description", 60.2], ["1260", "I7N3G6G", "1260", 0, "no description", 60.2], ["1261", "I7N3G6G", "1261", 0, "no description", 60.2], ["1262", "I7N3G6G", "1262", 0, "no description", 60.2], ["1263", "I7N3G6G", "1263", 0, "no description", 60.2], ["1264", "I7N3G6G", "1264", 0, "no description", 60.2], ["1265", "I7N3G6G", "1265", 0, "no description", 60.2], ["1266", "I7N3G6G", "1266", 0, "no description", 60.2], ["1267", "I7N3G6G", "1267", 0, "no description", 60.2], ["1268", "I7N3G6G", "1268", 0, "no description", 60.2], ["1269", "I7N3G6G", "1269", 0, "no description", 60.2], ["1270", "I7N3G6G", "1270", 0, "no description", 60.2], ["1271", "I7N3G6G", "1271", 0, "no description", 60.2], ["1272", "I7N3G6G", "1272", 0, "no description", 60.2], ["1273", "I7N3G6G", "1273", 0, "no description", 60.2], ["1274", "I7N3G6G", "1274", 0, "no description", 60.2], ["1275", "I7N3G6G", "1275", 0, "no description", 60.2], ["1276", "I7N3G6G", "1276", 0, "no description", 60.2], ["1277", "I7N3G6G", "1277", 0, "no description", 60.2], ["1278", "I7N3G6G", "1278", 0, "no description", 60.2], ["1279", "I7N3G6G", "1279", 0, "no description", 60.2], ["1280", "I7N3G6G", "1280", 0, "no description", 60.2], ["1281", "I7N3G6G", "1281", 0, "no description", 60.2], ["1282", "I7N3G6G", "1282", 0, "no description", 60.2], ["1283", "I7N3G6G", "1283", 0, "no description", 60.2], ["1284", "I7N3G6G", "1284", 0, "no description", 60.2], ["1285", "I7N3G6G", "1285", 0, "no description", 60.2], ["1286", "I7N3G6G", "1286", 0, "no description", 60.2], ["1287", "I7N3G6G", "1287", 0, "no description", 60.2], ["1288", "I7N3G6G", "1288", 0, "no description", 60.2], ["1289", "I7N3G6G", "1289", 0, "no description", 60.2], ["1290", "I7N3G6G", "1290", 0, "no description", 60.2], ["1291", "I7N3G6G", "1291", 0, "no description", 60.2], ["1292", "I7N3G6G", "1292", 0, "no description", 60.2], ["1293", "I7N3G6G", "1293", 0, "no description", 60.2], ["1294", "I7N3G6G", "1294", 0, "no description", 60.2], ["1295", "I7N3G6G", "1295", 0, "no description", 60.2], ["1296", "I7N3G6G", "1296", 0, "no description", 60.2], ["1297", "I7N3G6G", "1297", 0, "no description", 60.2], ["1298", "I7N3G6G", "1298", 0, "no description", 60.2], ["1299", "I7N3G6G", "1299", 0, "no description", 60.2], ["1300", "I7N3G6G", "1300", 0, "no description", 60.2], ["1301", "I7N3G6G", "1301", 0, "no description", 60.2], ["1302", "I7N3G6G", "1302", 0, "no description", 60.2], ["1303", "I7N3G6G", "1303", 0, "no description", 60.2], ["1304", "I7N3G6G", "1304", 0, "no description", 60.2], ["1305", "I7N3G6G", "1305", 0, "no description", 60.2], ["1306", "I7N3G6G", "1306", 0, "no description", 60.2], ["1307", "I7N3G6G", "1307", 0, "no description", 60.2], ["1308", "I7N3G6G", "1308", 0, "no description", 60.2], ["1309", "I7N3G6G", "1309", 0, "no description", 60.2], ["1310", "I7N3G6G", "1310", 0, "no description", 60.2], ["1311", "I7N3G6G", "1311", 0, "no description", 60.2], ["1312", "I7N3G6G", "1312", 0, "no description", 60.2], ["1313", "I7N3G6G", "1313", 0, "no description", 60.2], ["1314", "I7N3G6G", "1314", 0, "no description", 60.2], ["1315", "I7N3G6G", "1315", 0, "no description", 60.2], ["1316", "I7N3G6G", "1316", 0, "no description", 60.2], ["1317", "I7N3G6G", "1317", 0, "no description", 60.2], ["1318", "I7N3G6G", "1318", 0, "no description", 60.2], ["1319", "I7N3G6G", "1319", 0, "no description", 60.2], ["1320", "I7N3G6G", "1320", 0, "no description", 60.2], ["1321", "I7N3G6G", "1321", 0, "no description", 60.2], ["1322", "I7N3G6G", "1322", 0, "no description", 60.2], ["1323", "I7N3G6G", "1323", 0, "no description", 60.2], ["1324", "I7N3G6G", "1324", 0, "no description", 60.2], ["1325", "I7N3G6G", "1325", 0, "no description", 60.2], ["1326", "I7N3G6G", "1326", 0, "no description", 60.2], ["1327", "I7N3G6G", "1327", 0, "no description", 60.2], ["1328", "I7N3G6G", "1328", 0, "no description", 60.2], ["1329", "I7N3G6G", "1329", 0, "no description", 60.2], ["1330", "I7N3G6G", "1330", 0, "no description", 60.2], ["1331", "I7N3G6G", "1331", 0, "no description", 60.2], ["1332", "I7N3G6G", "1332", 0, "no description", 60.2], ["1333", "I7N3G6G", "1333", 0, "no description", 60.2], ["1334", "I7N3G6G", "1334", 0, "no description", 60.2], ["1335", "I7N3G6G", "1335", 0, "no description", 60.2], ["1336", "I7N3G6G", "1336", 0, "no description", 60.2], ["1337", "I7N3G6G", "1337", 0, "no description", 60.2], ["1338", "I7N3G6G", "1338", 0, "no description", 60.2], ["1339", "I7N3G6G", "1339", 0, "no description", 60.2], ["1340", "I7N3G6G", "1340", 0, "no description", 60.2], ["1341", "I7N3G6G", "1341", 0, "no description", 60.2], ["1342", "I7N3G6G", "1342", 0, "no description", 60.2], ["1343", "I7N3G6G", "1343", 0, "no description", 60.2], ["1344", "I7N3G6G", "1344", 0, "no description", 60.2], ["1345", "I7N3G6G", "1345", 0, "no description", 60.2], ["1346", "I7N3G6G", "1346", 0, "no description", 60.2], ["1347", "I7N3G6G", "1347", 0, "no description", 60.2], ["1348", "I7N3G6G", "1348", 0, "no description", 60.2], ["1349", "I7N3G6G", "1349", 0, "no description", 60.2], ["1350", "I7N3G6G", "1350", 0, "no description", 60.2], ["1351", "I7N3G6G", "1351", 0, "no description", 60.2], ["1352", "I7N3G6G", "1352", 0, "no description", 60.2], ["1353", "I7N3G6G", "1353", 0, "no description", 60.2], ["1354", "I7N3G6G", "1354", 0, "no description", 60.2], ["1355", "I7N3G6G", "1355", 0, "no description", 60.2], ["1356", "I7N3G6G", "1356", 0, "no description", 60.2], ["1357", "I7N3G6G", "1357", 0, "no description", 60.2], ["1358", "I7N3G6G", "1358", 0, "no description", 60.2], ["1359", "I7N3G6G", "1359", 0, "no description", 60.2], ["1360", "I7N3G6G", "1360", 0, "no description", 60.2], ["1361", "I7N3G6G", "1361", 0, "no description", 60.2], ["1362", "I7N3G6G", "1362", 0, "no description", 60.2], ["1363", "I7N3G6G", "1363", 0, "no description", 60.2], ["1364", "I7N3G6G", "1364", 0, "no description", 60.2], ["1365", "I7N3G6G", "1365", 0, "no description", 60.2], ["1366", "I7N3G6G", "1366", 0, "no description", 60.2], ["1367", "I7N3G6G", "1367", 0, "no description", 60.2], ["1368", "I7N3G6G", "1368", 0, "no description", 60.2], ["1369", "I7N3G6G", "1369", 0, "no description", 60.2], ["1370", "I7N3G6G", "1370", 0, "no description", 60.2], ["1371", "I7N3G6G", "1371", 0, "no description", 60.2], ["1372", "I7N3G6G", "1372", 0, "no description", 60.2], ["1373", "I7N3G6G", "1373", 0, "no description", 60.2], ["1374", "I7N3G6G", "1374", 0, "no description", 60.2], ["1375", "I7N3G6G", "1375", 0, "no description", 60.2], ["1376", "I7N3G6G", "1376", 0, "no description", 60.2], ["1377", "I7N3G6G", "1377", 0, "no description", 60.2], ["1378", "I7N3G6G", "1378", 0, "no description", 60.2], ["1379", "I7N3G6G", "1379", 0, "no description", 60.2], ["1380", "I7N3G6G", "1380", 0, "no description", 60.2], ["1381", "I7N3G6G", "1381", 0, "no description", 60.2], ["1382", "I7N3G6G", "1382", 0, "no description", 60.2], ["1383", "I7N3G6G", "1383", 0, "no description", 60.2], ["1384", "I7N3G6G", "1384", 0, "no description", 60.2], ["1385", "I7N3G6G", "1385", 0, "no description", 60.2], ["1386", "I7N3G6G", "1386", 0, "no description", 60.2], ["1387", "I7N3G6G", "1387", 0, "no description", 60.2], ["1388", "I7N3G6G", "1388", 0, "no description", 60.2], ["1389", "I7N3G6G", "1389", 0, "no description", 60.2], ["1390", "I7N3G6G", "1390", 0, "no description", 60.2], ["1391", "I7N3G6G", "1391", 0, "no description", 60.2], ["1392", "I7N3G6G", "1392", 0, "no description", 60.2], ["1393", "I7N3G6G", "1393", 0, "no description", 60.2], ["1394", "I7N3G6G", "1394", 0, "no description", 60.2], ["1395", "I7N3G6G", "1395", 0, "no description", 60.2], ["1396", "I7N3G6G", "1396", 0, "no description", 60.2], ["1397", "I7N3G6G", "1397", 0, "no description", 60.2], ["1398", "I7N3G6G", "1398", 0, "no description", 60.2], ["1399", "I7N3G6G", "1399", 0, "no description", 60.2], ["1400", "I7N3G6G", "1400", 0, "no description", 60.2], ["1401", "I7N3G6G", "1401", 0, "no description", 60.2], ["1402", "I7N3G6G", "1402", 0, "no description", 60.2], ["1403", "I7N3G6G", "1403", 0, "no description", 60.2], ["1404", "I7N3G6G", "1404", 0, "no description", 60.2], ["1405", "I7N3G6G", "1405", 0, "no description", 60.2], ["1406", "I7N3G6G", "1406", 0, "no description", 60.2], ["1407", "I7N3G6G", "1407", 0, "no description", 60.2], ["1408", "I7N3G6G", "1408", 0, "no description", 60.2], ["1409", "I7N3G6G", "1409", 0, "no description", 60.2], ["1410", "I7N3G6G", "1410", 0, "no description", 60.2], ["1411", "I7N3G6G", "1411", 0, "no description", 60.2], ["1412", "I7N3G6G", "1412", 0, "no description", 60.2], ["1413", "I7N3G6G", "1413", 0, "no description", 60.2], ["1414", "I7N3G6G", "1414", 0, "no description", 60.2], ["1415", "I7N3G6G", "1415", 0, "no description", 60.2], ["1416", "I7N3G6G", "1416", 0, "no description", 60.2], ["1417", "I7N3G6G", "1417", 0, "no description", 60.2], ["1418", "I7N3G6G", "1418", 0, "no description", 60.2], ["1419", "I7N3G6G", "1419", 0, "no description", 60.2], ["1420", "I7N3G6G", "1420", 0, "no description", 60.2], ["1421", "I7N3G6G", "1421", 0, "no description", 60.2], ["1422", "I7N3G6G", "1422", 0, "no description", 60.2], ["1423", "I7N3G6G", "1423", 0, "no description", 60.2], ["1424", "I7N3G6G", "1424", 0, "no description", 60.2], ["1425", "I7N3G6G", "1425", 0, "no description", 60.2], ["1426", "I7N3G6G", "1426", 0, "no description", 60.2], ["1427", "I7N3G6G", "1427", 0, "no description", 60.2], ["1428", "I7N3G6G", "1428", 0, "no description", 60.2], ["1429", "I7N3G6G", "1429", 0, "no description", 60.2], ["1430", "I7N3G6G", "1430", 0, "no description", 60.2], ["1431", "I7N3G6G", "1431", 0, "no description", 60.2], ["1432", "I7N3G6G", "1432", 0, "no description", 60.2], ["1433", "I7N3G6G", "1433", 0, "no description", 60.2], ["1434", "I7N3G6G", "1434", 0, "no description", 60.2], ["1435", "I7N3G6G", "1435", 0, "no description", 60.2], ["1436", "I7N3G6G", "1436", 0, "no description", 60.2], ["1437", "I7N3G6G", "1437", 0, "no description", 60.2], ["1438", "I7N3G6G", "1438", 0, "no description", 60.2], ["1439", "I7N3G6G", "1439", 0, "no description", 60.2], ["1440", "I7N3G6G", "1440", 0, "no description", 60.2], ["1441", "I7N3G6G", "1441", 0, "no description", 60.2], ["1442", "I7N3G6G", "1442", 0, "no description", 60.2], ["1443", "I7N3G6G", "1443", 0, "no description", 60.2], ["1444", "I7N3G6G", "1444", 0, "no description", 60.2], ["1445", "I7N3G6G", "1445", 0, "no description", 60.2], ["1446", "I7N3G6G", "1446", 0, "no description", 60.2], ["1447", "I7N3G6G", "1447", 0, "no description", 60.2], ["1448", "I7N3G6G", "1448", 0, "no description", 60.2], ["1449", "I7N3G6G", "1449", 0, "no description", 60.2], ["1450", "I7N3G6G", "1450", 0, "no description", 60.2], ["1451", "I7N3G6G", "1451", 0, "no description", 60.2], ["1452", "I7N3G6G", "1452", 0, "no description", 60.2], ["1453", "I7N3G6G", "1453", 0, "no description", 60.2], ["1454", "I7N3G6G", "1454", 0, "no description", 60.2], ["1455", "I7N3G6G", "1455", 0, "no description", 60.2], ["1456", "I7N3G6G", "1456", 0, "no description", 60.2], ["1457", "I7N3G6G", "1457", 0, "no description", 60.2], ["1458", "I7N3G6G", "1458", 0, "no description", 60.2], ["1459", "I7N3G6G", "1459", 0, "no description", 60.2], ["1460", "I7N3G6G", "1460", 0, "no description", 60.2], ["1461", "I7N3G6G", "1461", 0, "no description", 60.2], ["1462", "I7N3G6G", "1462", 0, "no description", 60.2], ["1463", "I7N3G6G", "1463", 0, "no description", 60.2], ["1464", "I7N3G6G", "1464", 0, "no description", 60.2], ["1465", "I7N3G6G", "1465", 0, "no description", 60.2], ["1466", "I7N3G6G", "1466", 0, "no description", 60.2], ["1467", "I7N3G6G", "1467", 0, "no description", 60.2], ["1468", "I7N3G6G", "1468", 0, "no description", 60.2], ["1469", "I7N3G6G", "1469", 0, "no description", 60.2], ["1470", "I7N3G6G", "1470", 0, "no description", 60.2], ["1471", "I7N3G6G", "1471", 0, "no description", 60.2], ["1472", "I7N3G6G", "1472", 0, "no description", 60.2], ["1473", "I7N3G6G", "1473", 0, "no description", 60.2], ["1474", "I7N3G6G", "1474", 0, "no description", 60.2], ["1475", "I7N3G6G", "1475", 0, "no description", 60.2], ["1476", "I7N3G6G", "1476", 0, "no description", 60.2], ["1477", "I7N3G6G", "1477", 0, "no description", 60.2], ["1478", "I7N3G6G", "1478", 0, "no description", 60.2], ["1479", "I7N3G6G", "1479", 0, "no description", 60.2], ["1480", "I7N3G6G", "1480", 0, "no description", 60.2], ["1481", "I7N3G6G", "1481", 0, "no description", 60.2], ["1482", "I7N3G6G", "1482", 0, "no description", 60.2], ["1483", "I7N3G6G", "1483", 0, "no description", 60.2], ["1484", "I7N3G6G", "1484", 0, "no description", 60.2], ["1485", "I7N3G6G", "1485", 0, "no description", 60.2], ["1486", "I7N3G6G", "1486", 0, "no description", 60.2], ["1487", "I7N3G6G", "1487", 0, "no description", 60.2], ["1488", "I7N3G6G", "1488", 0, "no description", 60.2], ["1489", "I7N3G6G", "1489", 0, "no description", 60.2], ["1490", "I7N3G6G", "1490", 0, "no description", 60.2], ["1491", "I7N3G6G", "1491", 0, "no description", 60.2], ["1492", "I7N3G6G", "1492", 0, "no description", 60.2], ["1493", "I7N3G6G", "1493", 0, "no description", 60.2], ["1494", "I7N3G6G", "1494", 0, "no description", 60.2], ["1495", "I7N3G6G", "1495", 0, "no description", 60.2], ["1496", "I7N3G6G", "1496", 0, "no description", 60.2], ["1497", "I7N3G6G", "1497", 0, "no description", 60.2], ["1498", "I7N3G6G", "1498", 0, "no description", 60.2], ["1499", "I7N3G6G", "1499", 0, "no description", 60.2], ["1500", "I7N3G6G", "1500", 0, "no description", 60.2], ["1501", "I7N3G6G", "1501", 0, "no description", 60.2], ["1502", "I7N3G6G", "1502", 0, "no description", 60.2], ["1503", "I7N3G6G", "1503", 0, "no description", 60.2], ["1504", "I7N3G6G", "1504", 0, "no description", 60.2], ["1505", "I7N3G6G", "1505", 0, "no description", 60.2], ["1506", "I7N3G6G", "1506", 0, "no description", 60.2], ["1507", "I7N3G6G", "1507", 0, "no description", 60.2], ["1508", "I7N3G6G", "1508", 0, "no description", 60.2], ["1509", "I7N3G6G", "1509", 0, "no description", 60.2], ["1510", "I7N3G6G", "1510", 0, "no description", 60.2], ["1511", "I7N3G6G", "1511", 0, "no description", 60.2], ["1512", "I7N3G6G", "1512", 0, "no description", 60.2], ["1513", "I7N3G6G", "1513", 0, "no description", 60.2], ["1514", "I7N3G6G", "1514", 0, "no description", 60.2], ["1515", "I7N3G6G", "1515", 0, "no description", 60.2], ["1516", "I7N3G6G", "1516", 0, "no description", 60.2], ["1517", "I7N3G6G", "1517", 0, "no description", 60.2], ["1518", "I7N3G6G", "1518", 0, "no description", 60.2], ["1519", "I7N3G6G", "1519", 0, "no description", 60.2], ["1520", "I7N3G6G", "1520", 0, "no description", 60.2], ["1521", "I7N3G6G", "1521", 0, "no description", 60.2], ["1522", "I7N3G6G", "1522", 0, "no description", 60.2], ["1523", "I7N3G6G", "1523", 0, "no description", 60.2], ["1524", "I7N3G6G", "1524", 0, "no description", 60.2], ["1525", "I7N3G6G", "1525", 0, "no description", 60.2], ["1526", "I7N3G6G", "1526", 0, "no description", 60.2], ["1527", "I7N3G6G", "1527", 0, "no description", 60.2], ["1528", "I7N3G6G", "1528", 0, "no description", 60.2], ["1529", "I7N3G6G", "1529", 0, "no description", 60.2], ["1530", "I7N3G6G", "1530", 0, "no description", 60.2], ["1531", "I7N3G6G", "1531", 0, "no description", 60.2], ["1532", "I7N3G6G", "1532", 0, "no description", 60.2], ["1533", "I7N3G6G", "1533", 0, "no description", 60.2], ["1534", "I7N3G6G", "1534", 0, "no description", 60.2], ["1535", "I7N3G6G", "1535", 0, "no description", 60.2], ["1536", "I7N3G6G", "1536", 0, "no description", 60.2], ["1537", "I7N3G6G", "1537", 0, "no description", 60.2], ["1538", "I7N3G6G", "1538", 0, "no description", 60.2], ["1539", "I7N3G6G", "1539", 0, "no description", 60.2], ["1540", "I7N3G6G", "1540", 0, "no description", 60.2], ["1541", "I7N3G6G", "1541", 0, "no description", 60.2], ["1542", "I7N3G6G", "1542", 0, "no description", 60.2], ["1543", "I7N3G6G", "1543", 0, "no description", 60.2], ["1544", "I7N3G6G", "1544", 0, "no description", 60.2], ["1545", "I7N3G6G", "1545", 0, "no description", 60.2], ["1546", "I7N3G6G", "1546", 0, "no description", 60.2], ["1547", "I7N3G6G", "1547", 0, "no description", 60.2], ["1548", "I7N3G6G", "1548", 0, "no description", 60.2], ["1549", "I7N3G6G", "1549", 0, "no description", 60.2], ["1550", "I7N3G6G", "1550", 0, "no description", 60.2], ["1551", "I7N3G6G", "1551", 0, "no description", 60.2], ["1552", "I7N3G6G", "1552", 0, "no description", 60.2], ["1553", "I7N3G6G", "1553", 0, "no description", 60.2], ["1554", "I7N3G6G", "1554", 0, "no description", 60.2], ["1555", "I7N3G6G", "1555", 0, "no description", 60.2], ["1556", "I7N3G6G", "1556", 0, "no description", 60.2], ["1557", "I7N3G6G", "1557", 0, "no description", 60.2], ["1558", "I7N3G6G", "1558", 0, "no description", 60.2], ["1559", "I7N3G6G", "1559", 0, "no description", 60.2], ["1560", "I7N3G6G", "1560", 0, "no description", 60.2], ["1561", "I7N3G6G", "1561", 0, "no description", 60.2], ["1562", "I7N3G6G", "1562", 0, "no description", 60.2], ["1563", "I7N3G6G", "1563", 0, "no description", 60.2], ["1564", "I7N3G6G", "1564", 0, "no description", 60.2], ["1565", "I7N3G6G", "1565", 0, "no description", 60.2], ["1566", "I7N3G6G", "1566", 0, "no description", 60.2], ["1567", "I7N3G6G", "1567", 0, "no description", 60.2], ["1568", "I7N3G6G", "1568", 0, "no description", 60.2], ["1569", "I7N3G6G", "1569", 0, "no description", 60.2], ["1570", "I7N3G6G", "1570", 0, "no description", 60.2], ["1571", "I7N3G6G", "1571", 0, "no description", 60.2], ["1572", "I7N3G6G", "1572", 0, "no description", 60.2], ["1573", "I7N3G6G", "1573", 0, "no description", 60.2], ["1574", "I7N3G6G", "1574", 0, "no description", 60.2], ["1575", "I7N3G6G", "1575", 0, "no description", 60.2], ["1576", "I7N3G6G", "1576", 0, "no description", 60.2], ["1577", "I7N3G6G", "1577", 0, "no description", 60.2], ["1578", "I7N3G6G", "1578", 0, "no description", 60.2], ["1579", "I7N3G6G", "1579", 0, "no description", 60.2], ["1580", "I7N3G6G", "1580", 0, "no description", 60.2], ["1581", "I7N3G6G", "1581", 0, "no description", 60.2], ["1582", "I7N3G6G", "1582", 0, "no description", 60.2], ["1583", "I7N3G6G", "1583", 0, "no description", 60.2], ["1584", "I7N3G6G", "1584", 0, "no description", 60.2], ["1585", "I7N3G6G", "1585", 0, "no description", 60.2], ["1586", "I7N3G6G", "1586", 0, "no description", 60.2], ["1587", "I7N3G6G", "1587", 0, "no description", 60.2], ["1588", "I7N3G6G", "1588", 0, "no description", 60.2], ["1589", "I7N3G6G", "1589", 0, "no description", 60.2], ["1590", "I7N3G6G", "1590", 0, "no description", 60.2], ["1591", "I7N3G6G", "1591", 0, "no description", 60.2], ["1592", "I7N3G6G", "1592", 0, "no description", 60.2], ["1593", "I7N3G6G", "1593", 0, "no description", 60.2], ["1594", "I7N3G6G", "1594", 0, "no description", 60.2], ["1595", "I7N3G6G", "1595", 0, "no description", 60.2], ["1596", "I7N3G6G", "1596", 0, "no description", 60.2], ["1597", "I7N3G6G", "1597", 0, "no description", 60.2], ["1598", "I7N3G6G", "1598", 0, "no description", 60.2], ["1599", "I7N3G6G", "1599", 0, "no description", 60.2], ["1600", "I7N3G6G", "1600", 0, "no description", 60.2], ["1601", "I7N3G6G", "1601", 0, "no description", 60.2], ["1602", "I7N3G6G", "1602", 0, "no description", 60.2], ["1603", "I7N3G6G", "1603", 0, "no description", 60.2], ["1604", "I7N3G6G", "1604", 0, "no description", 60.2], ["1605", "I7N3G6G", "1605", 0, "no description", 60.2], ["1606", "I7N3G6G", "1606", 0, "no description", 60.2], ["1607", "I7N3G6G", "1607", 0, "no description", 60.2], ["1608", "I7N3G6G", "1608", 0, "no description", 60.2], ["1609", "I7N3G6G", "1609", 0, "no description", 60.2], ["1610", "I7N3G6G", "1610", 0, "no description", 60.2], ["1611", "I7N3G6G", "1611", 0, "no description", 60.2], ["1612", "I7N3G6G", "1612", 0, "no description", 60.2], ["1613", "I7N3G6G", "1613", 0, "no description", 60.2], ["1614", "I7N3G6G", "1614", 0, "no description", 60.2], ["1615", "I7N3G6G", "1615", 0, "no description", 60.2], ["1616", "I7N3G6G", "1616", 0, "no description", 60.2], ["1617", "I7N3G6G", "1617", 0, "no description", 60.2], ["1618", "I7N3G6G", "1618", 0, "no description", 60.2], ["1619", "I7N3G6G", "1619", 0, "no description", 60.2], ["1620", "I7N3G6G", "1620", 0, "no description", 60.2], ["1621", "I7N3G6G", "1621", 0, "no description", 60.2], ["1622", "I7N3G6G", "1622", 0, "no description", 60.2], ["1623", "I7N3G6G", "1623", 0, "no description", 60.2], ["1624", "I7N3G6G", "1624", 0, "no description", 60.2], ["1625", "I7N3G6G", "1625", 0, "no description", 60.2], ["1626", "I7N3G6G", "1626", 0, "no description", 60.2], ["1627", "I7N3G6G", "1627", 0, "no description", 60.2], ["1628", "I7N3G6G", "1628", 0, "no description", 60.2], ["1629", "I7N3G6G", "1629", 0, "no description", 60.2], ["1630", "I7N3G6G", "1630", 0, "no description", 60.2], ["1631", "I7N3G6G", "1631", 0, "no description", 60.2], ["1632", "I7N3G6G", "1632", 0, "no description", 60.2], ["1633", "I7N3G6G", "1633", 0, "no description", 60.2], ["1634", "I7N3G6G", "1634", 0, "no description", 60.2], ["1635", "I7N3G6G", "1635", 0, "no description", 60.2], ["1636", "I7N3G6G", "1636", 0, "no description", 60.2], ["1637", "I7N3G6G", "1637", 0, "no description", 60.2], ["1638", "I7N3G6G", "1638", 0, "no description", 60.2], ["1639", "I7N3G6G", "1639", 0, "no description", 60.2], ["1640", "I7N3G6G", "1640", 0, "no description", 60.2], ["1641", "I7N3G6G", "1641", 0, "no description", 60.2], ["1642", "I7N3G6G", "1642", 0, "no description", 60.2], ["1643", "I7N3G6G", "1643", 0, "no description", 60.2], ["1644", "I7N3G6G", "1644", 0, "no description", 60.2], ["1645", "I7N3G6G", "1645", 0, "no description", 60.2], ["1646", "I7N3G6G", "1646", 0, "no description", 60.2], ["1647", "I7N3G6G", "1647", 0, "no description", 60.2], ["1648", "I7N3G6G", "1648", 0, "no description", 60.2], ["1649", "I7N3G6G", "1649", 0, "no description", 60.2], ["1650", "I7N3G6G", "1650", 0, "no description", 60.2], ["1651", "I7N3G6G", "1651", 0, "no description", 60.2], ["1652", "I7N3G6G", "1652", 0, "no description", 60.2], ["1653", "I7N3G6G", "1653", 0, "no description", 60.2], ["1654", "I7N3G6G", "1654", 0, "no description", 60.2], ["1655", "I7N3G6G", "1655", 0, "no description", 60.2], ["1656", "I7N3G6G", "1656", 0, "no description", 60.2], ["1657", "I7N3G6G", "1657", 0, "no description", 60.2], ["1658", "I7N3G6G", "1658", 0, "no description", 60.2], ["1659", "I7N3G6G", "1659", 0, "no description", 60.2], ["1660", "I7N3G6G", "1660", 0, "no description", 60.2], ["1661", "I7N3G6G", "1661", 0, "no description", 60.2], ["1662", "I7N3G6G", "1662", 0, "no description", 60.2], ["1663", "I7N3G6G", "1663", 0, "no description", 60.2], ["1664", "I7N3G6G", "1664", 0, "no description", 60.2], ["1665", "I7N3G6G", "1665", 0, "no description", 60.2], ["1666", "I7N3G6G", "1666", 0, "no description", 60.2], ["1667", "I7N3G6G", "1667", 0, "no description", 60.2], ["1668", "I7N3G6G", "1668", 0, "no description", 60.2], ["1669", "I7N3G6G", "1669", 0, "no description", 60.2], ["1670", "I7N3G6G", "1670", 0, "no description", 60.2], ["1671", "I7N3G6G", "1671", 0, "no description", 60.2], ["1672", "I7N3G6G", "1672", 0, "no description", 60.2], ["1673", "I7N3G6G", "1673", 0, "no description", 60.2], ["1674", "I7N3G6G", "1674", 0, "no description", 60.2], ["1675", "I7N3G6G", "1675", 0, "no description", 60.2], ["1676", "I7N3G6G", "1676", 0, "no description", 60.2], ["1677", "I7N3G6G", "1677", 0, "no description", 60.2], ["1678", "I7N3G6G", "1678", 0, "no description", 60.2], ["1679", "I7N3G6G", "1679", 0, "no description", 60.2], ["1680", "I7N3G6G", "1680", 0, "no description", 60.2], ["1681", "I7N3G6G", "1681", 0, "no description", 60.2], ["1682", "I7N3G6G", "1682", 0, "no description", 60.2], ["1683", "I7N3G6G", "1683", 0, "no description", 60.2], ["1684", "I7N3G6G", "1684", 0, "no description", 60.2], ["1685", "I7N3G6G", "1685", 0, "no description", 60.2], ["1686", "I7N3G6G", "1686", 0, "no description", 60.2], ["1687", "I7N3G6G", "1687", 0, "no description", 60.2], ["1688", "I7N3G6G", "1688", 0, "no description", 60.2], ["1689", "I7N3G6G", "1689", 0, "no description", 60.2], ["1690", "I7N3G6G", "1690", 0, "no description", 60.2], ["1691", "I7N3G6G", "1691", 0, "no description", 60.2], ["1692", "I7N3G6G", "1692", 0, "no description", 60.2], ["1693", "I7N3G6G", "1693", 0, "no description", 60.2], ["1694", "I7N3G6G", "1694", 0, "no description", 60.2], ["1695", "I7N3G6G", "1695", 0, "no description", 60.2], ["1696", "I7N3G6G", "1696", 0, "no description", 60.2], ["1697", "I7N3G6G", "1697", 0, "no description", 60.2], ["1698", "I7N3G6G", "1698", 0, "no description", 60.2], ["1699", "I7N3G6G", "1699", 0, "no description", 60.2], ["1700", "I7N3G6G", "1700", 0, "no description", 60.2], ["1701", "I7N3G6G", "1701", 0, "no description", 60.2], ["1702", "I7N3G6G", "1702", 0, "no description", 60.2], ["1703", "I7N3G6G", "1703", 0, "no description", 60.2], ["1704", "I7N3G6G", "1704", 0, "no description", 60.2], ["1705", "I7N3G6G", "1705", 0, "no description", 60.2], ["1706", "I7N3G6G", "1706", 0, "no description", 60.2], ["1707", "I7N3G6G", "1707", 0, "no description", 60.2], ["1708", "I7N3G6G", "1708", 0, "no description", 60.2], ["1709", "I7N3G6G", "1709", 0, "no description", 60.2], ["1710", "I7N3G6G", "1710", 0, "no description", 60.2], ["1711", "I7N3G6G", "1711", 0, "no description", 60.2], ["1712", "I7N3G6G", "1712", 0, "no description", 60.2], ["1713", "I7N3G6G", "1713", 0, "no description", 60.2], ["1714", "I7N3G6G", "1714", 0, "no description", 60.2], ["1715", "I7N3G6G", "1715", 0, "no description", 60.2], ["1716", "I7N3G6G", "1716", 0, "no description", 60.2], ["1717", "I7N3G6G", "1717", 0, "no description", 60.2], ["1718", "I7N3G6G", "1718", 0, "no description", 60.2], ["1719", "I7N3G6G", "1719", 0, "no description", 60.2], ["1720", "I7N3G6G", "1720", 0, "no description", 60.2], ["1721", "I7N3G6G", "1721", 0, "no description", 60.2], ["1722", "I7N3G6G", "1722", 0, "no description", 60.2], ["1723", "I7N3G6G", "1723", 0, "no description", 60.2], ["1724", "I7N3G6G", "1724", 0, "no description", 60.2], ["1725", "I7N3G6G", "1725", 0, "no description", 60.2], ["1726", "I7N3G6G", "1726", 0, "no description", 60.2], ["1727", "I7N3G6G", "1727", 0, "no description", 60.2], ["1728", "I7N3G6G", "1728", 0, "no description", 60.2], ["1729", "I7N3G6G", "1729", 0, "no description", 60.2], ["1730", "I7N3G6G", "1730", 0, "no description", 60.2], ["1731", "I7N3G6G", "1731", 0, "no description", 60.2], ["1732", "I7N3G6G", "1732", 0, "no description", 60.2], ["1733", "I7N3G6G", "1733", 0, "no description", 60.2], ["1734", "I7N3G6G", "1734", 0, "no description", 60.2], ["1735", "I7N3G6G", "1735", 0, "no description", 60.2], ["1736", "I7N3G6G", "1736", 0, "no description", 60.2], ["1737", "I7N3G6G", "1737", 0, "no description", 60.2], ["1738", "I7N3G6G", "1738", 0, "no description", 60.2], ["1739", "I7N3G6G", "1739", 0, "no description", 60.2], ["1740", "I7N3G6G", "1740", 0, "no description", 60.2], ["1741", "I7N3G6G", "1741", 0, "no description", 60.2], ["1742", "I7N3G6G", "1742", 0, "no description", 60.2], ["1743", "I7N3G6G", "1743", 0, "no description", 60.2], ["1744", "I7N3G6G", "1744", 0, "no description", 60.2], ["1745", "I7N3G6G", "1745", 0, "no description", 60.2], ["1746", "I7N3G6G", "1746", 0, "no description", 60.2], ["1747", "I7N3G6G", "1747", 0, "no description", 60.2], ["1748", "I7N3G6G", "1748", 0, "no description", 60.2], ["1749", "I7N3G6G", "1749", 0, "no description", 60.2], ["1750", "I7N3G6G", "1750", 0, "no description", 60.2], ["1751", "I7N3G6G", "1751", 0, "no description", 60.2], ["1752", "I7N3G6G", "1752", 0, "no description", 60.2], ["1753", "I7N3G6G", "1753", 0, "no description", 60.2], ["1754", "I7N3G6G", "1754", 0, "no description", 60.2], ["1755", "I7N3G6G", "1755", 0, "no description", 60.2], ["1756", "I7N3G6G", "1756", 0, "no description", 60.2], ["1757", "I7N3G6G", "1757", 0, "no description", 60.2], ["1758", "I7N3G6G", "1758", 0, "no description", 60.2], ["1759", "I7N3G6G", "1759", 0, "no description", 60.2], ["1760", "I7N3G6G", "1760", 0, "no description", 60.2], ["1761", "I7N3G6G", "1761", 0, "no description", 60.2], ["1762", "I7N3G6G", "1762", 0, "no description", 60.2], ["1763", "I7N3G6G", "1763", 0, "no description", 60.2], ["1764", "I7N3G6G", "1764", 0, "no description", 60.2], ["1765", "I7N3G6G", "1765", 0, "no description", 60.2], ["1766", "I7N3G6G", "1766", 0, "no description", 60.2], ["1767", "I7N3G6G", "1767", 0, "no description", 60.2], ["1768", "I7N3G6G", "1768", 0, "no description", 60.2], ["1769", "I7N3G6G", "1769", 0, "no description", 60.2], ["1770", "I7N3G6G", "1770", 0, "no description", 60.2], ["1771", "I7N3G6G", "1771", 0, "no description", 60.2], ["1772", "I7N3G6G", "1772", 0, "no description", 60.2], ["1773", "I7N3G6G", "1773", 0, "no description", 60.2], ["1774", "I7N3G6G", "1774", 0, "no description", 60.2], ["1775", "I7N3G6G", "1775", 0, "no description", 60.2], ["1776", "I7N3G6G", "1776", 0, "no description", 60.2], ["1777", "I7N3G6G", "1777", 0, "no description", 60.2], ["1778", "I7N3G6G", "1778", 0, "no description", 60.2], ["1779", "I7N3G6G", "1779", 0, "no description", 60.2], ["1780", "I7N3G6G", "1780", 0, "no description", 60.2], ["1781", "I7N3G6G", "1781", 0, "no description", 60.2], ["1782", "I7N3G6G", "1782", 0, "no description", 60.2], ["1783", "I7N3G6G", "1783", 0, "no description", 60.2], ["1784", "I7N3G6G", "1784", 0, "no description", 60.2], ["1785", "I7N3G6G", "1785", 0, "no description", 60.2], ["1786", "I7N3G6G", "1786", 0, "no description", 60.2], ["1787", "I7N3G6G", "1787", 0, "no description", 60.2], ["1788", "I7N3G6G", "1788", 0, "no description", 60.2], ["1789", "I7N3G6G", "1789", 0, "no description", 60.2], ["1790", "I7N3G6G", "1790", 0, "no description", 60.2], ["1791", "I7N3G6G", "1791", 0, "no description", 60.2], ["1792", "I7N3G6G", "1792", 0, "no description", 60.2], ["1793", "I7N3G6G", "1793", 0, "no description", 60.2], ["1794", "I7N3G6G", "1794", 0, "no description", 60.2], ["1795", "I7N3G6G", "1795", 0, "no description", 60.2], ["1796", "I7N3G6G", "1796", 0, "no description", 60.2], ["1797", "I7N3G6G", "1797", 0, "no description", 60.2], ["1798", "I7N3G6G", "1798", 0, "no description", 60.2], ["1799", "I7N3G6G", "1799", 0, "no description", 60.2], ["1800", "I7N3G6G", "1800", 0, "no description", 60.2], ["1801", "I7N3G6G", "1801", 0, "no description", 60.2], ["1802", "I7N3G6G", "1802", 0, "no description", 60.2], ["1803", "I7N3G6G", "1803", 0, "no description", 60.2], ["1804", "I7N3G6G", "1804", 0, "no description", 60.2], ["1805", "I7N3G6G", "1805", 0, "no description", 60.2], ["1806", "I7N3G6G", "1806", 0, "no description", 60.2], ["1807", "I7N3G6G", "1807", 0, "no description", 60.2], ["1808", "I7N3G6G", "1808", 0, "no description", 60.2], ["1809", "I7N3G6G", "1809", 0, "no description", 60.2], ["1810", "I7N3G6G", "1810", 0, "no description", 60.2], ["1811", "I7N3G6G", "1811", 0, "no description", 60.2], ["1812", "I7N3G6G", "1812", 0, "no description", 60.2], ["1813", "I7N3G6G", "1813", 0, "no description", 60.2], ["1814", "I7N3G6G", "1814", 0, "no description", 60.2], ["1815", "I7N3G6G", "1815", 0, "no description", 60.2], ["1816", "I7N3G6G", "1816", 0, "no description", 60.2], ["1817", "I7N3G6G", "1817", 0, "no description", 60.2], ["1818", "I7N3G6G", "1818", 0, "no description", 60.2], ["1819", "I7N3G6G", "1819", 0, "no description", 60.2], ["1820", "I7N3G6G", "1820", 0, "no description", 60.2], ["1821", "I7N3G6G", "1821", 0, "no description", 60.2], ["1822", "I7N3G6G", "1822", 0, "no description", 60.2], ["1823", "I7N3G6G", "1823", 0, "no description", 60.2], ["1824", "I7N3G6G", "1824", 0, "no description", 60.2], ["1825", "I7N3G6G", "1825", 0, "no description", 60.2], ["1826", "I7N3G6G", "1826", 0, "no description", 60.2], ["1827", "I7N3G6G", "1827", 0, "no description", 60.2], ["1828", "I7N3G6G", "1828", 0, "no description", 60.2], ["1829", "I7N3G6G", "1829", 0, "no description", 60.2], ["1830", "I7N3G6G", "1830", 0, "no description", 60.2], ["1831", "I7N3G6G", "1831", 0, "no description", 60.2], ["1832", "I7N3G6G", "1832", 0, "no description", 60.2], ["1833", "I7N3G6G", "1833", 0, "no description", 60.2], ["1834", "I7N3G6G", "1834", 0, "no description", 60.2], ["1835", "I7N3G6G", "1835", 0, "no description", 60.2], ["1836", "I7N3G6G", "1836", 0, "no description", 60.2], ["1837", "I7N3G6G", "1837", 0, "no description", 60.2], ["1838", "I7N3G6G", "1838", 0, "no description", 60.2], ["1839", "I7N3G6G", "1839", 0, "no description", 60.2], ["1840", "I7N3G6G", "1840", 0, "no description", 60.2], ["1841", "I7N3G6G", "1841", 0, "no description", 60.2], ["1842", "I7N3G6G", "1842", 0, "no description", 60.2], ["1843", "I7N3G6G", "1843", 0, "no description", 60.2], ["1844", "I7N3G6G", "1844", 0, "no description", 60.2], ["1845", "I7N3G6G", "1845", 0, "no description", 60.2], ["1846", "I7N3G6G", "1846", 0, "no description", 60.2], ["1847", "I7N3G6G", "1847", 0, "no description", 60.2], ["1848", "I7N3G6G", "1848", 0, "no description", 60.2], ["1849", "I7N3G6G", "1849", 0, "no description", 60.2], ["1850", "I7N3G6G", "1850", 0, "no description", 60.2], ["1851", "I7N3G6G", "1851", 0, "no description", 60.2], ["1852", "I7N3G6G", "1852", 0, "no description", 60.2], ["1853", "I7N3G6G", "1853", 0, "no description", 60.2], ["1854", "I7N3G6G", "1854", 0, "no description", 60.2], ["1855", "I7N3G6G", "1855", 0, "no description", 60.2], ["1856", "I7N3G6G", "1856", 0, "no description", 60.2], ["1857", "I7N3G6G", "1857", 0, "no description", 60.2], ["1858", "I7N3G6G", "1858", 0, "no description", 60.2], ["1859", "I7N3G6G", "1859", 0, "no description", 60.2], ["1860", "I7N3G6G", "1860", 0, "no description", 60.2], ["1861", "I7N3G6G", "1861", 0, "no description", 60.2], ["1862", "I7N3G6G", "1862", 0, "no description", 60.2], ["1863", "I7N3G6G", "1863", 0, "no description", 60.2], ["1864", "I7N3G6G", "1864", 0, "no description", 60.2], ["1865", "I7N3G6G", "1865", 0, "no description", 60.2], ["1866", "I7N3G6G", "1866", 0, "no description", 60.2], ["1867", "I7N3G6G", "1867", 0, "no description", 60.2], ["1868", "I7N3G6G", "1868", 0, "no description", 60.2], ["1869", "I7N3G6G", "1869", 0, "no description", 60.2], ["1870", "I7N3G6G", "1870", 0, "no description", 60.2], ["1871", "I7N3G6G", "1871", 0, "no description", 60.2], ["1872", "I7N3G6G", "1872", 0, "no description", 60.2], ["1873", "I7N3G6G", "1873", 0, "no description", 60.2], ["1874", "I7N3G6G", "1874", 0, "no description", 60.2], ["1875", "I7N3G6G", "1875", 0, "no description", 60.2], ["1876", "I7N3G6G", "1876", 0, "no description", 60.2], ["1877", "I7N3G6G", "1877", 0, "no description", 60.2], ["1878", "I7N3G6G", "1878", 0, "no description", 60.2], ["1879", "I7N3G6G", "1879", 0, "no description", 60.2], ["1880", "I7N3G6G", "1880", 0, "no description", 60.2], ["1881", "I7N3G6G", "1881", 0, "no description", 60.2], ["1882", "I7N3G6G", "1882", 0, "no description", 60.2], ["1883", "I7N3G6G", "1883", 0, "no description", 60.2], ["1884", "I7N3G6G", "1884", 0, "no description", 60.2], ["1885", "I7N3G6G", "1885", 0, "no description", 60.2], ["1886", "I7N3G6G", "1886", 0, "no description", 60.2], ["1887", "I7N3G6G", "1887", 0, "no description", 60.2], ["1888", "I7N3G6G", "1888", 0, "no description", 60.2], ["1889", "I7N3G6G", "1889", 0, "no description", 60.2], ["1890", "I7N3G6G", "1890", 0, "no description", 60.2], ["1891", "I7N3G6G", "1891", 0, "no description", 60.2], ["1892", "I7N3G6G", "1892", 0, "no description", 60.2], ["1893", "I7N3G6G", "1893", 0, "no description", 60.2], ["1894", "I7N3G6G", "1894", 0, "no description", 60.2], ["1895", "I7N3G6G", "1895", 0, "no description", 60.2], ["1896", "I7N3G6G", "1896", 0, "no description", 60.2], ["1897", "I7N3G6G", "1897", 0, "no description", 60.2], ["1898", "I7N3G6G", "1898", 0, "no description", 60.2], ["1899", "I7N3G6G", "1899", 0, "no description", 60.2], ["1900", "I7N3G6G", "1900", 0, "no description", 60.2], ["1901", "I7N3G6G", "1901", 0, "no description", 60.2], ["1902", "I7N3G6G", "1902", 0, "no description", 60.2], ["1903", "I7N3G6G", "1903", 0, "no description", 60.2], ["1904", "I7N3G6G", "1904", 0, "no description", 60.2], ["1905", "I7N3G6G", "1905", 0, "no description", 60.2], ["1906", "I7N3G6G", "1906", 0, "no description", 60.2], ["1907", "I7N3G6G", "1907", 0, "no description", 60.2], ["1908", "I7N3G6G", "1908", 0, "no description", 60.2], ["1909", "I7N3G6G", "1909", 0, "no description", 60.2], ["1910", "I7N3G6G", "1910", 0, "no description", 60.2], ["1911", "I7N3G6G", "1911", 0, "no description", 60.2], ["1912", "I7N3G6G", "1912", 0, "no description", 60.2], ["1913", "I7N3G6G", "1913", 0, "no description", 60.2], ["1914", "I7N3G6G", "1914", 0, "no description", 60.2], ["1915", "I7N3G6G", "1915", 0, "no description", 60.2], ["1916", "I7N3G6G", "1916", 0, "no description", 60.2], ["1917", "I7N3G6G", "1917", 0, "no description", 60.2], ["1918", "I7N3G6G", "1918", 0, "no description", 60.2], ["1919", "I7N3G6G", "1919", 0, "no description", 60.2], ["1920", "I7N3G6G", "1920", 0, "no description", 60.2], ["1921", "I7N3G6G", "1921", 0, "no description", 60.2], ["1922", "I7N3G6G", "1922", 0, "no description", 60.2], ["1923", "I7N3G6G", "1923", 0, "no description", 60.2], ["1924", "I7N3G6G", "1924", 0, "no description", 60.2], ["1925", "I7N3G6G", "1925", 0, "no description", 60.2], ["1926", "I7N3G6G", "1926", 0, "no description", 60.2], ["1927", "I7N3G6G", "1927", 0, "no description", 60.2], ["1928", "I7N3G6G", "1928", 0, "no description", 60.2], ["1929", "I7N3G6G", "1929", 0, "no description", 60.2], ["1930", "I7N3G6G", "1930", 0, "no description", 60.2], ["1931", "I7N3G6G", "1931", 0, "no description", 60.2], ["1932", "I7N3G6G", "1932", 0, "no description", 60.2], ["1933", "I7N3G6G", "1933", 0, "no description", 60.2], ["1934", "I7N3G6G", "1934", 0, "no description", 60.2], ["1935", "I7N3G6G", "1935", 0, "no description", 60.2], ["1936", "I7N3G6G", "1936", 0, "no description", 60.2], ["1937", "I7N3G6G", "1937", 0, "no description", 60.2], ["1938", "I7N3G6G", "1938", 0, "no description", 60.2], ["1939", "I7N3G6G", "1939", 0, "no description", 60.2], ["1940", "I7N3G6G", "1940", 0, "no description", 60.2], ["1941", "I7N3G6G", "1941", 0, "no description", 60.2], ["1942", "I7N3G6G", "1942", 0, "no description", 60.2], ["1943", "I7N3G6G", "1943", 0, "no description", 60.2], ["1944", "I7N3G6G", "1944", 0, "no description", 60.2], ["1945", "I7N3G6G", "1945", 0, "no description", 60.2], ["1946", "I7N3G6G", "1946", 0, "no description", 60.2], ["1947", "I7N3G6G", "1947", 0, "no description", 60.2], ["1948", "I7N3G6G", "1948", 0, "no description", 60.2], ["1949", "I7N3G6G", "1949", 0, "no description", 60.2], ["1950", "I7N3G6G", "1950", 0, "no description", 60.2], ["1951", "I7N3G6G", "1951", 0, "no description", 60.2], ["1952", "I7N3G6G", "1952", 0, "no description", 60.2], ["1953", "I7N3G6G", "1953", 0, "no description", 60.2], ["1954", "I7N3G6G", "1954", 0, "no description", 60.2], ["1955", "I7N3G6G", "1955", 0, "no description", 60.2], ["1956", "I7N3G6G", "1956", 0, "no description", 60.2], ["1957", "I7N3G6G", "1957", 0, "no description", 60.2], ["1958", "I7N3G6G", "1958", 0, "no description", 60.2], ["1959", "I7N3G6G", "1959", 0, "no description", 60.2], ["1960", "I7N3G6G", "1960", 0, "no description", 60.2], ["1961", "I7N3G6G", "1961", 0, "no description", 60.2], ["1962", "I7N3G6G", "1962", 0, "no description", 60.2], ["1963", "I7N3G6G", "1963", 0, "no description", 60.2], ["1964", "I7N3G6G", "1964", 0, "no description", 60.2], ["1965", "I7N3G6G", "1965", 0, "no description", 60.2], ["1966", "I7N3G6G", "1966", 0, "no description", 60.2], ["1967", "I7N3G6G", "1967", 0, "no description", 60.2], ["1968", "I7N3G6G", "1968", 0, "no description", 60.2], ["1969", "I7N3G6G", "1969", 0, "no description", 60.2], ["1970", "I7N3G6G", "1970", 0, "no description", 60.2], ["1971", "I7N3G6G", "1971", 0, "no description", 60.2], ["1972", "I7N3G6G", "1972", 0, "no description", 60.2], ["1973", "I7N3G6G", "1973", 0, "no description", 60.2], ["1974", "I7N3G6G", "1974", 0, "no description", 60.2], ["1975", "I7N3G6G", "1975", 0, "no description", 60.2], ["1976", "I7N3G6G", "1976", 0, "no description", 60.2], ["1977", "I7N3G6G", "1977", 0, "no description", 60.2], ["1978", "I7N3G6G", "1978", 0, "no description", 60.2], ["1979", "I7N3G6G", "1979", 0, "no description", 60.2], ["1980", "I7N3G6G", "1980", 0, "no description", 60.2], ["1981", "I7N3G6G", "1981", 0, "no description", 60.2], ["1982", "I7N3G6G", "1982", 0, "no description", 60.2], ["1983", "I7N3G6G", "1983", 0, "no description", 60.2], ["1984", "I7N3G6G", "1984", 0, "no description", 60.2], ["1985", "I7N3G6G", "1985", 0, "no description", 60.2], ["1986", "I7N3G6G", "1986", 0, "no description", 60.2], ["1987", "I7N3G6G", "1987", 0, "no description", 60.2], ["1988", "I7N3G6G", "1988", 0, "no description", 60.2], ["1989", "I7N3G6G", "1989", 0, "no description", 60.2], ["1990", "I7N3G6G", "1990", 0, "no description", 60.2], ["1991", "I7N3G6G", "1991", 0, "no description", 60.2], ["1992", "I7N3G6G", "1992", 0, "no description", 60.2], ["1993", "I7N3G6G", "1993", 0, "no description", 60.2], ["1994", "I7N3G6G", "1994", 0, "no description", 60.2], ["1995", "I7N3G6G", "1995", 0, "no description", 60.2], ["1996", "I7N3G6G", "1996", 0, "no description", 60.2], ["1997", "I7N3G6G", "1997", 0, "no description", 60.2], ["1998", "I7N3G6G", "1998", 0, "no description", 60.2], ["1999", "I7N3G6G", "1999", 0, "no description", 60.2], ["2000", "I7N3G6G", "2000", 0, "no description", 60.2], ["2001", "I7N3G6G", "2001", 0, "no description", 60.2], ["2002", "I7N3G6G", "2002", 0, "no description", 60.2], ["2003", "I7N3G6G", "2003", 0, "no description", 60.2], ["2004", "I7N3G6G", "2004", 0, "no description", 60.2], ["2005", "I7N3G6G", "2005", 0, "no description", 60.2], ["2006", "I7N3G6G", "2006", 0, "no description", 60.2], ["2007", "I7N3G6G", "2007", 0, "no description", 60.2], ["2008", "I7N3G6G", "2008", 0, "no description", 60.2], ["2009", "I7N3G6G", "2009", 0, "no description", 60.2], ["2010", "I7N3G6G", "2010", 0, "no description", 60.2], ["2011", "I7N3G6G", "2011", 0, "no description", 60.2], ["2012", "I7N3G6G", "2012", 0, "no description", 60.2], ["2013", "I7N3G6G", "2013", 0, "no description", 60.2], ["2014", "I7N3G6G", "2014", 0, "no description", 60.2], ["2015", "I7N3G6G", "2015", 0, "no description", 60.2], ["2016", "I7N3G6G", "2016", 0, "no description", 60.2], ["2017", "I7N3G6G", "2017", 0, "no description", 60.2], ["2018", "I7N3G6G", "2018", 0, "no description", 60.2], ["2019", "I7N3G6G", "2019", 0, "no description", 60.2], ["2020", "I7N3G6G", "2020", 0, "no description", 60.2], ["2021", "I7N3G6G", "2021", 0, "no description", 60.2], ["2022", "I7N3G6G", "2022", 0, "no description", 60.2], ["2023", "I7N3G6G", "2023", 0, "no description", 60.2], ["2024", "I7N3G6G", "2024", 0, "no description", 60.2], ["2025", "I7N3G6G", "2025", 0, "no description", 60.2], ["2026", "I7N3G6G", "2026", 0, "no description", 60.2], ["2027", "I7N3G6G", "2027", 0, "no description", 60.2], ["2028", "I7N3G6G", "2028", 0, "no description", 60.2], ["2029", "I7N3G6G", "2029", 0, "no description", 60.2], ["2030", "I7N3G6G", "2030", 0, "no description", 60.2], ["2031", "I7N3G6G", "2031", 0, "no description", 60.2], ["2032", "I7N3G6G", "2032", 0, "no description", 60.2], ["2033", "I7N3G6G", "2033", 0, "no description", 60.2], ["2034", "I7N3G6G", "2034", 0, "no description", 60.2], ["2035", "I7N3G6G", "2035", 0, "no description", 60.2], ["2036", "I7N3G6G", "2036", 0, "no description", 60.2], ["2037", "I7N3G6G", "2037", 0, "no description", 60.2], ["2038", "I7N3G6G", "2038", 0, "no description", 60.2], ["2039", "I7N3G6G", "2039", 0, "no description", 60.2], ["2040", "I7N3G6G", "2040", 0, "no description", 60.2], ["2041", "I7N3G6G", "2041", 0, "no description", 60.2], ["2042", "I7N3G6G", "2042", 0, "no description", 60.2], ["2043", "I7N3G6G", "2043", 0, "no description", 60.2], ["2044", "I7N3G6G", "2044", 0, "no description", 60.2], ["2045", "I7N3G6G", "2045", 0, "no description", 60.2], ["2046", "I7N3G6G", "2046", 0, "no description", 60.2], ["2047", "I7N3G6G", "2047", 0, "no description", 60.2], ["2048", "I7N3G6G", "2048", 0, "no description", 60.2], ["2049", "I7N3G6G", "2049", 0, "no description", 60.2], ["2050", "I7N3G6G", "2050", 0, "no description", 60.2], ["2051", "I7N3G6G", "2051", 0, "no description", 60.2], ["2052", "I7N3G6G", "2052", 0, "no description", 60.2], ["2053", "I7N3G6G", "2053", 0, "no description", 60.2], ["2054", "I7N3G6G", "2054", 0, "no description", 60.2], ["2055", "I7N3G6G", "2055", 0, "no description", 60.2], ["2056", "I7N3G6G", "2056", 0, "no description", 60.2], ["2057", "I7N3G6G", "2057", 0, "no description", 60.2], ["2058", "I7N3G6G", "2058", 0, "no description", 60.2], ["2059", "I7N3G6G", "2059", 0, "no description", 60.2], ["2060", "I7N3G6G", "2060", 0, "no description", 60.2], ["2061", "I7N3G6G", "2061", 0, "no description", 60.2], ["2062", "I7N3G6G", "2062", 0, "no description", 60.2], ["2063", "I7N3G6G", "2063", 0, "no description", 60.2], ["2064", "I7N3G6G", "2064", 0, "no description", 60.2], ["2065", "I7N3G6G", "2065", 0, "no description", 60.2], ["2066", "I7N3G6G", "2066", 0, "no description", 60.2], ["2067", "I7N3G6G", "2067", 0, "no description", 60.2], ["2068", "I7N3G6G", "2068", 0, "no description", 60.2], ["2069", "I7N3G6G", "2069", 0, "no description", 60.2], ["2070", "I7N3G6G", "2070", 0, "no description", 60.2], ["2071", "I7N3G6G", "2071", 0, "no description", 60.2], ["2072", "I7N3G6G", "2072", 0, "no description", 60.2], ["2073", "I7N3G6G", "2073", 0, "no description", 60.2], ["2074", "I7N3G6G", "2074", 0, "no description", 60.2], ["2075", "I7N3G6G", "2075", 0, "no description", 60.2], ["2076", "I7N3G6G", "2076", 0, "no description", 60.2], ["2077", "I7N3G6G", "2077", 0, "no description", 60.2], ["2078", "I7N3G6G", "2078", 0, "no description", 60.2], ["2079", "I7N3G6G", "2079", 0, "no description", 60.2], ["2080", "I7N3G6G", "2080", 0, "no description", 60.2], ["2081", "I7N3G6G", "2081", 0, "no description", 60.2], ["2082", "I7N3G6G", "2082", 0, "no description", 60.2], ["2083", "I7N3G6G", "2083", 0, "no description", 60.2], ["2084", "I7N3G6G", "2084", 0, "no description", 60.2], ["2085", "I7N3G6G", "2085", 0, "no description", 60.2], ["2086", "I7N3G6G", "2086", 0, "no description", 60.2], ["2087", "I7N3G6G", "2087", 0, "no description", 60.2], ["2088", "I7N3G6G", "2088", 0, "no description", 60.2], ["2089", "I7N3G6G", "2089", 0, "no description", 60.2], ["2090", "I7N3G6G", "2090", 0, "no description", 60.2], ["2091", "I7N3G6G", "2091", 0, "no description", 60.2], ["2092", "I7N3G6G", "2092", 0, "no description", 60.2], ["2093", "I7N3G6G", "2093", 0, "no description", 60.2], ["2094", "I7N3G6G", "2094", 0, "no description", 60.2], ["2095", "I7N3G6G", "2095", 0, "no description", 60.2], ["2096", "I7N3G6G", "2096", 0, "no description", 60.2], ["2097", "I7N3G6G", "2097", 0, "no description", 60.2], ["2098", "I7N3G6G", "2098", 0, "no description", 60.2], ["2099", "I7N3G6G", "2099", 0, "no description", 60.2], ["2100", "I7N3G6G", "2100", 0, "no description", 60.2], ["2101", "I7N3G6G", "2101", 0, "no description", 60.2], ["2102", "I7N3G6G", "2102", 0, "no description", 60.2], ["2103", "I7N3G6G", "2103", 0, "no description", 60.2], ["2104", "I7N3G6G", "2104", 0, "no description", 60.2], ["2105", "I7N3G6G", "2105", 0, "no description", 60.2], ["2106", "I7N3G6G", "2106", 0, "no description", 60.2], ["2107", "I7N3G6G", "2107", 0, "no description", 60.2], ["2108", "I7N3G6G", "2108", 0, "no description", 60.2], ["2109", "I7N3G6G", "2109", 0, "no description", 60.2], ["2110", "I7N3G6G", "2110", 0, "no description", 60.2], ["2111", "I7N3G6G", "2111", 0, "no description", 60.2], ["2112", "I7N3G6G", "2112", 0, "no description", 60.2], ["2113", "I7N3G6G", "2113", 0, "no description", 60.2], ["2114", "I7N3G6G", "2114", 0, "no description", 60.2], ["2115", "I7N3G6G", "2115", 0, "no description", 60.2], ["2116", "I7N3G6G", "2116", 0, "no description", 60.2], ["2117", "I7N3G6G", "2117", 0, "no description", 60.2], ["2118", "I7N3G6G", "2118", 0, "no description", 60.2], ["2119", "I7N3G6G", "2119", 0, "no description", 60.2], ["2120", "I7N3G6G", "2120", 0, "no description", 60.2], ["2121", "I7N3G6G", "2121", 0, "no description", 60.2], ["2122", "I7N3G6G", "2122", 0, "no description", 60.2], ["2123", "I7N3G6G", "2123", 0, "no description", 60.2], ["2124", "I7N3G6G", "2124", 0, "no description", 60.2], ["2125", "I7N3G6G", "2125", 0, "no description", 60.2], ["2126", "I7N3G6G", "2126", 0, "no description", 60.2], ["2127", "I7N3G6G", "2127", 0, "no description", 60.2], ["2128", "I7N3G6G", "2128", 0, "no description", 60.2], ["2129", "I7N3G6G", "2129", 0, "no description", 60.2], ["2130", "I7N3G6G", "2130", 0, "no description", 60.2], ["2131", "I7N3G6G", "2131", 0, "no description", 60.2], ["2132", "I7N3G6G", "2132", 0, "no description", 60.2], ["2133", "I7N3G6G", "2133", 0, "no description", 60.2], ["2134", "I7N3G6G", "2134", 0, "no description", 60.2], ["2135", "I7N3G6G", "2135", 0, "no description", 60.2], ["2136", "I7N3G6G", "2136", 0, "no description", 60.2], ["2137", "I7N3G6G", "2137", 0, "no description", 60.2], ["2138", "I7N3G6G", "2138", 0, "no description", 60.2], ["2139", "I7N3G6G", "2139", 0, "no description", 60.2], ["2140", "I7N3G6G", "2140", 0, "no description", 60.2], ["2141", "I7N3G6G", "2141", 0, "no description", 60.2], ["2142", "I7N3G6G", "2142", 0, "no description", 60.2], ["2143", "I7N3G6G", "2143", 0, "no description", 60.2], ["2144", "I7N3G6G", "2144", 0, "no description", 60.2], ["2145", "I7N3G6G", "2145", 0, "no description", 60.2], ["2146", "I7N3G6G", "2146", 0, "no description", 60.2], ["2147", "I7N3G6G", "2147", 0, "no description", 60.2], ["2148", "I7N3G6G", "2148", 0, "no description", 60.2], ["2149", "I7N3G6G", "2149", 0, "no description", 60.2], ["2150", "I7N3G6G", "2150", 0, "no description", 60.2], ["2151", "I7N3G6G", "2151", 0, "no description", 60.2], ["2152", "I7N3G6G", "2152", 0, "no description", 60.2], ["2153", "I7N3G6G", "2153", 0, "no description", 60.2], ["2154", "I7N3G6G", "2154", 0, "no description", 60.2], ["2155", "I7N3G6G", "2155", 0, "no description", 60.2], ["2156", "I7N3G6G", "2156", 0, "no description", 60.2], ["2157", "I7N3G6G", "2157", 0, "no description", 60.2], ["2158", "I7N3G6G", "2158", 0, "no description", 60.2], ["2159", "I7N3G6G", "2159", 0, "no description", 60.2], ["2160", "I7N3G6G", "2160", 0, "no description", 60.2], ["2161", "I7N3G6G", "2161", 0, "no description", 60.2], ["2162", "I7N3G6G", "2162", 0, "no description", 60.2], ["2163", "I7N3G6G", "2163", 0, "no description", 60.2], ["2164", "I7N3G6G", "2164", 0, "no description", 60.2], ["2165", "I7N3G6G", "2165", 0, "no description", 60.2], ["2166", "I7N3G6G", "2166", 0, "no description", 60.2], ["2167", "I7N3G6G", "2167", 0, "no description", 60.2], ["2168", "I7N3G6G", "2168", 0, "no description", 60.2], ["2169", "I7N3G6G", "2169", 0, "no description", 60.2], ["2170", "I7N3G6G", "2170", 0, "no description", 60.2], ["2171", "I7N3G6G", "2171", 0, "no description", 60.2], ["2172", "I7N3G6G", "2172", 0, "no description", 60.2], ["2173", "I7N3G6G", "2173", 0, "no description", 60.2], ["2174", "I7N3G6G", "2174", 0, "no description", 60.2], ["2175", "I7N3G6G", "2175", 0, "no description", 60.2], ["2176", "I7N3G6G", "2176", 0, "no description", 60.2], ["2177", "I7N3G6G", "2177", 0, "no description", 60.2], ["2178", "I7N3G6G", "2178", 0, "no description", 60.2], ["2179", "I7N3G6G", "2179", 0, "no description", 60.2], ["2180", "I7N3G6G", "2180", 0, "no description", 60.2], ["2181", "I7N3G6G", "2181", 0, "no description", 60.2], ["2182", "I7N3G6G", "2182", 0, "no description", 60.2], ["2183", "I7N3G6G", "2183", 0, "no description", 60.2], ["2184", "I7N3G6G", "2184", 0, "no description", 60.2], ["2185", "I7N3G6G", "2185", 0, "no description", 60.2], ["2186", "I7N3G6G", "2186", 0, "no description", 60.2], ["2187", "I7N3G6G", "2187", 0, "no description", 60.2], ["2188", "I7N3G6G", "2188", 0, "no description", 60.2], ["2189", "I7N3G6G", "2189", 0, "no description", 60.2], ["2190", "I7N3G6G", "2190", 0, "no description", 60.2], ["2191", "I7N3G6G", "2191", 0, "no description", 60.2], ["2192", "I7N3G6G", "2192", 0, "no description", 60.2], ["2193", "I7N3G6G", "2193", 0, "no description", 60.2], ["2194", "I7N3G6G", "2194", 0, "no description", 60.2], ["2195", "I7N3G6G", "2195", 0, "no description", 60.2], ["2196", "I7N3G6G", "2196", 0, "no description", 60.2], ["2197", "I7N3G6G", "2197", 0, "no description", 60.2], ["2198", "I7N3G6G", "2198", 0, "no description", 60.2], ["2199", "I7N3G6G", "2199", 0, "no description", 60.2], ["2200", "I7N3G6G", "2200", 0, "no description", 60.2], ["2201", "I7N3G6G", "2201", 0, "no description", 60.2], ["2202", "I7N3G6G", "2202", 0, "no description", 60.2], ["2203", "I7N3G6G", "2203", 0, "no description", 60.2], ["2204", "I7N3G6G", "2204", 0, "no description", 60.2], ["2205", "I7N3G6G", "2205", 0, "no description", 60.2], ["2206", "I7N3G6G", "2206", 0, "no description", 60.2], ["2207", "I7N3G6G", "2207", 0, "no description", 60.2], ["2208", "I7N3G6G", "2208", 0, "no description", 60.2], ["2209", "I7N3G6G", "2209", 0, "no description", 60.2], ["2210", "I7N3G6G", "2210", 0, "no description", 60.2], ["2211", "I7N3G6G", "2211", 0, "no description", 60.2], ["2212", "I7N3G6G", "2212", 0, "no description", 60.2], ["2213", "I7N3G6G", "2213", 0, "no description", 60.2], ["2214", "I7N3G6G", "2214", 0, "no description", 60.2], ["2215", "I7N3G6G", "2215", 0, "no description", 60.2], ["2216", "I7N3G6G", "2216", 0, "no description", 60.2], ["2217", "I7N3G6G", "2217", 0, "no description", 60.2], ["2218", "I7N3G6G", "2218", 0, "no description", 60.2], ["2219", "I7N3G6G", "2219", 0, "no description", 60.2], ["2220", "I7N3G6G", "2220", 0, "no description", 60.2], ["2221", "I7N3G6G", "2221", 0, "no description", 60.2], ["2222", "I7N3G6G", "2222", 0, "no description", 60.2], ["2223", "I7N3G6G", "2223", 0, "no description", 60.2], ["2224", "I7N3G6G", "2224", 0, "no description", 60.2], ["2225", "I7N3G6G", "2225", 0, "no description", 60.2], ["2226", "I7N3G6G", "2226", 0, "no description", 60.2]] \ No newline at end of file diff --git a/packages/itmat-job-executor/test/testFiles/JSONCurator_error3.json b/packages/itmat-job-executor/test/testFiles/JSONCurator_error3.json deleted file mode 100644 index 96ba9a544..000000000 --- a/packages/itmat-job-executor/test/testFiles/JSONCurator_error3.json +++ /dev/null @@ -1 +0,0 @@ -[["ID", "SubjectID", "VisitID", "Sex", "Description", "Weight"], ["1", "I7N3G6G", "1", 0, "no description", 60.2], ["2", "I7N3G6G", "2", 0, "no description", 60.2], ["3", "I7N3G6G", "3", 0, "no description", 60.2], ["4", "I7N3G6G", "4", 0, "no description", 60.2], ["5", "I7N3G6G", "5", 0, "no description", 60.2], ["6", "I7N3G6G", "6", 0, "no description", 60.2], ["7", "I7N3G6G", "7", 0, "no description", 60.2], ["8", "I7N3G6G", "8", 0, "no description", 60.2], ["9", "I7N3G6G", "9", 0, "no description", 60.2], ["10", "I7N3G6G", "10", 0, "no description", 60.2], ["11", "I7N3G6G", "11", 0, "no description", 60.2], ["12", "I7N3G6G", "12", 0, "no description", 60.2], ["13", "I7N3G6G", "13", 0, "no description", 60.2], ["14", "I7N3G6G", "14", 0, "no description", 60.2], ["15", "I7N3G6G", "15", 0, "no description", 60.2], ["16", "I7N3G6G", "16", 0, "no description", 60.2], ["17", "I7N3G6G", "17", 0, "no description", 60.2], ["18", "I7N3G6G", "18", 0, "no description", 60.2], ["19", "I7N3G6G", "19", 0, "no description", 60.2], ["20", "I7N3G6G", "20", 0, "no description", 60.2], ["21", "I7N3G6G", "21", 0, "no description", 60.2], ["22", "I7N3G6G", "22", 0, "no description", 60.2], ["23", "I7N3G6G", "23", 0, "no description", 60.2], ["24", "I7N3G6G", "24", 0, "no description", 60.2], ["25", "I7N3G6G", "25", 0, "no description", 60.2], ["26", "I7N3G6G", "26", 0, "no description", 60.2], ["27", "I7N3G6G", "27", 0, "no description"], ["28", "I7N3G6G", "28", 0, "no description", 60.2], ["29", "I7N3G6G", "29", 0], ["30", "I7N3G6G", "30", 0, "no description", 60.2], ["31", "I7N3G6G", "31", 0, "no description", 60.2], ["32", "I7N3G6G", "32", 0, "no description", 60.2], ["33", "I7N3G6G", "33", 0, "no description", 60.2], ["34", "I7N3G6G", "34", 0, "no description", 60.2], ["35", "I7N3G6G", "35", 0, "no description", 60.2], ["36", "I7N3G6G", "36", 0, "no description", 60.2], ["37", "I7N3G6G", "37", 0, "no description", 60.2], ["38", "I7N3G6G", "38", 0, "no description", 60.2], ["39", "I7N3G6G", "39", 0, "no description", 60.2], ["40", "I7N3G6G", "40", 0, "no description", 60.2], ["41", "I7N3G6G", "41", 0, "no description", 60.2], ["42", "I7N3G6G", "42", 0, "no description", 60.2], ["43", "I7N3G6G", "43", 0, "no description", 60.2], ["44", "I7N3G6G", "44", 0, "no description", 60.2], ["45", "I7N3G6G", "45", 0, "no description", 60.2], ["46", "I7N3G6G", "46", 0, "no description", 60.2], ["47", "I7N3G6G", "47", 0, "no description", 60.2], ["48", "I7N3G6G", "48", 0, "no description", 60.2], ["49", "I7N3G6G", "49", 0, "no description", 60.2], ["50", "I7N3G6G", "50", 0, "no description", 60.2], ["51", "I7N3G6G", "51", 0, "no description", 60.2], ["52", "I7N3G6G", "52", 0, "no description", 60.2], ["53", "I7N3G6G", "53", 0, "no description", 60.2], ["54", "I7N3G6G", "54", 0, "no description", 60.2], ["55", "I7N3G6G", "55", 0, "no description", 60.2], ["56", "I7N3G6G", "56", 0, "no description", 60.2], ["57", "I7N3G6G", "57", 0, "no description", 60.2], ["58", "I7N3G6G", "58", 0, "no description", 60.2], ["59", "I7N3G6G", "59", 0, "no description", 60.2], ["60", "I7N3G6G", "60", 0, "no description", 60.2], ["61", "I7N3G6G", "61", 0, "no description", 60.2], ["62", "I7N3G6G", "62", 0, "no description", 60.2], ["63", "I7N3G6G", "63", 0, "no description", 60.2], ["64", "I7N3G6G", "64", 0, "no description", 60.2], ["65", "I7N3G6G", "65", 0, "no description", 60.2], ["66", "I7N3G6G", "66", 0, "no description", 60.2], ["67", "I7N3G6G", "67", 0, "no description", 60.2], ["68", "I7N3G6G", "68", 0, "no description", 60.2], ["69", "I7N3G6G", "69", 0, "no description", 60.2], ["70", "I7N3G6G", "70", 0, "no description", 60.2], ["71", "I7N3G6G", "71", 0, "no description", 60.2], ["72", "I7N3G6G", "72", 0, "no description", 60.2], ["73", "I7N3G6G", "73", 0, "no description", 60.2], ["74", "I7N3G6G", "74", 0, "no description", 60.2], ["75", "I7N3G6G", "75", 0, "no description", 60.2], ["76", "I7N3G6G", "76", 0, "no description", 60.2], ["77", "I7N3G6G", "77", 0, "no description", 60.2], ["78", "I7N3G6G", "78", 0, "no description", 60.2], ["79", "I7N3G6G", "79", 0, "no description", 60.2], ["80", "I7N3G6G", "80", 0, "no description", 60.2], ["81", "I7N3G6G", "81", 0, "no description", 60.2], ["82", "I7N3G6G", "82", 0, "no description", 60.2], ["83", "I7N3G6G", "83", 0, "no description", 60.2], ["84", "I7N3G6G", "84", 0, "no description", 60.2], ["85", "I7N3G6G", "85", 0, "no description", 60.2], ["86", "I7N3G6G", "86", 0, "no description", 60.2], ["87", "I7N3G6G", "87", 0, "no description", 60.2], ["88", "I7N3G6G", "88", 0, "no description", 60.2], ["89", "I7N3G6G", "89", 0, "no description", 60.2], ["90", "I7N3G6G", "90", 0, "no description", 60.2], ["91", "I7N3G6G", "91", 0, "no description", 60.2], ["92", "I7N3G6G", "92", 0, "no description", 60.2], ["93", "I7N3G6G", "93", 0, "no description", 60.2], ["94", "I7N3G6G", "94", 0, "no description", 60.2], ["95", "I7N3G6G", "95", 0, "no description", 60.2], ["96", "I7N3G6G", "96", 0, "no description", 60.2], ["97", "I7N3G6G", "97", 0, "no description", 60.2], ["98", "I7N3G6G", "98", 0, "no description", 60.2], ["99", "I7N3G6G", "99", 0, "no description", 60.2], ["100", "I7N3G6G", "100", 0, "no description", 60.2], ["101", "I7N3G6G", "101", 0, "no description", 60.2], ["102", "I7N3G6G", "102", 0, "no description", 60.2], ["103", "I7N3G6G", "103", 0, "no description", 60.2], ["104", "I7N3G6G", "104", 0, "no description", 60.2], ["105", "I7N3G6G", "105", 0, "no description", 60.2], ["106", "I7N3G6G", "106", 0, "no description", 60.2], ["107", "I7N3G6G", "107", 0, "no description", 60.2], ["108", "I7N3G6G", "108", 0, "no description", 60.2], ["109", "I7N3G6G", "109", 0, "no description", 60.2], ["110", "I7N3G6G", "110", 0, "no description", 60.2], ["111", "I7N3G6G", "111", 0, "no description", 60.2], ["112", "I7N3G6G", "112", 0, "no description", 60.2], ["113", "I7N3G6G", "113", 0, "no description", 60.2], ["114", "I7N3G6G", "114", 0, "no description", 60.2], ["115", "I7N3G6G", "115", 0, "no description", 60.2], ["116", "I7N3G6G", "116", 0, "no description", 60.2], ["117", "I7N3G6G", "117", 0, "no description", 60.2], ["118", "I7N3G6G", "118", 0, "no description", 60.2], ["119", "I7N3G6G", "119", 0, "no description", 60.2], ["120", "I7N3G6G", "120", 0, "no description", 60.2], ["121", "I7N3G6G", "121", 0, "no description", 60.2], ["122", "I7N3G6G", "122", 0, "no description", 60.2], ["123", "I7N3G6G", "123", 0, "no description", 60.2], ["124", "I7N3G6G", "124", 0, "no description", 60.2], ["125", "I7N3G6G", "125", 0, "no description", 60.2], ["126", "I7N3G6G", "126", 0, "no description", 60.2], ["127", "I7N3G6G", "127", 0, "no description", 60.2], ["128", "I7N3G6G", "128", 0, "no description", 60.2], ["129", "I7N3G6G", "129", 0, "no description", 60.2], ["130", "I7N3G6G", "130", 0, "no description", 60.2], ["131", "I7N3G6G", "131", 0, "no description", 60.2], ["132", "I7N3G6G", "132", 0, "no description", 60.2], ["133", "I7N3G6G", "133", 0, "no description", 60.2], ["134", "I7N3G6G", "134", 0, "no description", 60.2], ["135", "I7N3G6G", "135", 0, "no description", 60.2], ["136", "I7N3G6G", "136", 0, "no description", 60.2], ["137", "I7N3G6G", "137", 0, "no description", 60.2], ["138", "I7N3G6G", "138", 0, "no description", 60.2], ["139", "I7N3G6G", "139", 0, "no description", 60.2], ["140", "I7N3G6G", "140", 0, "no description", 60.2], ["141", "I7N3G6G", "141", 0, "no description", 60.2], ["142", "I7N3G6G", "142", 0, "no description", 60.2], ["143", "I7N3G6G", "143", 0, "no description", 60.2], ["144", "I7N3G6G", "144", 0, "no description", 60.2], ["145", "I7N3G6G", "145", 0, "no description", 60.2], ["146", "I7N3G6G", "146", 0, "no description", 60.2], ["147", "I7N3G6G", "147", 0, "no description", 60.2], ["148", "I7N3G6G", "148", 0, "no description", 60.2], ["149", "I7N3G6G", "149", 0, "no description", 60.2], ["150", "I7N3G6G", "150", 0, "no description", 60.2], ["151", "I7N3G6G", "151", 0, "no description", 60.2], ["152", "I7N3G6G", "152", 0, "no description", 60.2], ["153", "I7N3G6G", "153", 0, "no description", 60.2], ["154", "I7N3G6G", "154", 0, "no description", 60.2], ["155", "I7N3G6G", "155", 0, "no description", 60.2], ["156", "I7N3G6G", "156", 0, "no description", 60.2], ["157", "I7N3G6G", "157", 0, "no description", 60.2], ["158", "I7N3G6G", "158", 0, "no description", 60.2], ["159", "I7N3G6G", "159", 0, "no description", 60.2], ["160", "I7N3G6G", "160", 0, "no description", 60.2], ["161", "I7N3G6G", "161", 0, "no description", 60.2], ["162", "I7N3G6G", "162", 0, "no description", 60.2], ["163", "I7N3G6G", "163", 0, "no description", 60.2], ["164", "I7N3G6G", "164", 0, "no description", 60.2], ["165", "I7N3G6G", "165", 0, "no description", 60.2], ["166", "I7N3G6G", "166", 0, "no description", 60.2], ["167", "I7N3G6G", "167", 0, "no description", 60.2], ["168", "I7N3G6G", "168", 0, "no description", 60.2], ["169", "I7N3G6G", "169", 0, "no description", 60.2], ["170", "I7N3G6G", "170", 0, "no description", 60.2], ["171", "I7N3G6G", "171", 0, "no description", 60.2], ["172", "I7N3G6G", "172", 0, "no description", 60.2], ["173", "I7N3G6G", "173", 0, "no description", 60.2], ["174", "I7N3G6G", "174", 0, "no description", 60.2], ["175", "I7N3G6G", "175", 0, "no description", 60.2], ["176", "I7N3G6G", "176", 0, "no description", 60.2], ["177", "I7N3G6G", "177", 0, "no description", 60.2], ["178", "I7N3G6G", "178", 0, "no description", 60.2], ["179", "I7N3G6G", "179", 0, "no description", 60.2], ["180", "I7N3G6G", "180", 0, "no description", 60.2], ["181", "I7N3G6G", "181", 0, "no description", 60.2], ["182", "I7N3G6G", "182", 0, "no description", 60.2], ["183", "I7N3G6G", "183", 0, "no description", 60.2], ["184", "I7N3G6G", "184", 0, "no description", 60.2], ["185", "I7N3G6G", "185", 0, "no description", 60.2], ["186", "I7N3G6G", "186", 0, "no description", 60.2], ["187", "I7N3G6G", "187", 0, "no description", 60.2], ["188", "I7N3G6G", "188", 0, "no description", 60.2], ["189", "I7N3G6G", "189", 0, "no description", 60.2], ["190", "I7N3G6G", "190", 0, "no description", 60.2], ["191", "I7N3G6G", "191", 0, "no description", 60.2], ["192", "I7N3G6G", "192", 0, "no description", 60.2], ["193", "I7N3G6G", "193", 0, "no description", 60.2], ["194", "I7N3G6G", "194", 0, "no description", 60.2], ["195", "I7N3G6G", "195", 0, "no description", 60.2], ["196", "I7N3G6G", "196", 0, "no description", 60.2], ["197", "I7N3G6G", "197", 0, "no description", 60.2], ["198", "I7N3G6G", "198", 0, "no description", 60.2], ["199", "I7N3G6G", "199", 0, "no description", 60.2], ["200", "I7N3G6G", "200", 0, "no description", 60.2], ["201", "I7N3G6G", "201", 0, "no description", 60.2], ["202", "I7N3G6G", "202", 0, "no description", 60.2], ["203", "I7N3G6G", "203", 0, "no description", 60.2], ["204", "I7N3G6G", "204", 0, "no description", 60.2], ["205", "I7N3G6G", "205", 0, "no description", 60.2], ["206", "I7N3G6G", "206", 0, "no description", 60.2], ["207", "I7N3G6G", "207", 0, "no description", 60.2], ["208", "I7N3G6G", "208", 0, "no description", 60.2], ["209", "I7N3G6G", "209", 0, "no description", 60.2], ["210", "I7N3G6G", "210", 0, "no description", 60.2], ["211", "I7N3G6G", "211", 0, "no description", 60.2], ["212", "I7N3G6G", "212", 0, "no description", 60.2], ["213", "I7N3G6G", "213", 0, "no description", 60.2], ["214", "I7N3G6G", "214", 0, "no description", 60.2], ["215", "I7N3G6G", "215", 0, "no description", 60.2], ["216", "I7N3G6G", "216", 0, "no description", 60.2], ["217", "I7N3G6G", "217", 0, "no description", 60.2], ["218", "I7N3G6G", "218", 0, "no description", 60.2], ["219", "I7N3G6G", "219", 0, "no description", 60.2], ["220", "I7N3G6G", "220", 0, "no description", 60.2], ["221", "I7N3G6G", "221", 0, "no description", 60.2], ["222", "I7N3G6G", "222", 0, "no description", 60.2], ["223", "I7N3G6G", "223", 0, "no description", 60.2], ["224", "I7N3G6G", "224", 0, "no description", 60.2], ["225", "I7N3G6G", "225", 0, "no description", 60.2], ["226", "I7N3G6G", "226", 0, "no description", 60.2], ["227", "I7N3G6G", "227", 0, "no description", 60.2], ["228", "I7N3G6G", "228", 0, "no description", 60.2], ["229", "I7N3G6G", "229", 0, "no description", 60.2], ["230", "I7N3G6G", "230", 0, "no description", 60.2], ["231", "I7N3G6G", "231", 0, "no description", 60.2], ["232", "I7N3G6G", "232", 0, "no description", 60.2], ["233", "I7N3G6G", "233", 0, "no description", 60.2], ["234", "I7N3G6G", "234", 0, "no description", 60.2], ["235", "I7N3G6G", "235", 0, "no description", 60.2], ["236", "I7N3G6G", "236", 0, "no description", 60.2], ["237", "I7N3G6G", "237", 0, "no description", 60.2], ["238", "I7N3G6G", "238", 0, "no description", 60.2], ["239", "I7N3G6G", "239", 0, "no description", 60.2], ["240", "I7N3G6G", "240", 0, "no description", 60.2], ["241", "I7N3G6G", "241", 0, "no description", 60.2], ["242", "I7N3G6G", "242", 0, "no description", 60.2], ["243", "I7N3G6G", "243", 0, "no description", 60.2], ["244", "I7N3G6G", "244", 0, "no description", 60.2], ["245", "I7N3G6G", "245", 0, "no description", 60.2], ["246", "I7N3G6G", "246", 0, "no description", 60.2], ["247", "I7N3G6G", "247", 0, "no description", 60.2], ["248", "I7N3G6G", "248", 0, "no description", 60.2], ["249", "I7N3G6G", "249", 0, "no description", 60.2], ["250", "I7N3G6G", "250", 0, "no description", 60.2], ["251", "I7N3G6G", "251", 0, "no description", 60.2], ["252", "I7N3G6G", "252", 0, "no description", 60.2], ["253", "I7N3G6G", "253", 0, "no description", 60.2], ["254", "I7N3G6G", "254", 0, "no description", 60.2], ["255", "I7N3G6G", "255", 0, "no description", 60.2], ["256", "I7N3G6G", "256", 0, "no description", 60.2], ["257", "I7N3G6G", "257", 0, "no description", 60.2], ["258", "I7N3G6G", "258", 0, "no description", 60.2], ["259", "I7N3G6G", "259", 0, "no description", 60.2], ["260", "I7N3G6G", "260", 0, "no description", 60.2], ["261", "I7N3G6G", "261", 0, "no description", 60.2], ["262", "I7N3G6G", "262", 0, "no description", 60.2], ["263", "I7N3G6G", "263", 0, "no description", 60.2], ["264", "I7N3G6G", "264", 0, "no description", 60.2], ["265", "I7N3G6G", "265", 0, "no description", 60.2], ["266", "I7N3G6G", "266", 0, "no description", 60.2], ["267", "I7N3G6G", "267", 0, "no description", 60.2], ["268", "I7N3G6G", "268", 0, "no description", 60.2], ["269", "I7N3G6G", "269", 0, "no description", 60.2], ["270", "I7N3G6G", "270", 0, "no description", 60.2], ["271", "I7N3G6G", "271", 0, "no description", 60.2], ["272", "I7N3G6G", "272", 0, "no description", 60.2], ["273", "I7N3G6G", "273", 0, "no description", 60.2], ["274", "I7N3G6G", "274", 0, "no description", 60.2], ["275", "I7N3G6G", "275", 0, "no description", 60.2], ["276", "I7N3G6G", "276", 0, "no description", 60.2], ["277", "I7N3G6G", "277", 0, "no description", 60.2], ["278", "I7N3G6G", "278", 0, "no description", 60.2], ["279", "I7N3G6G", "279", 0, "no description", 60.2], ["280", "I7N3G6G", "280", 0, "no description", 60.2], ["281", "I7N3G6G", "281", 0, "no description", 60.2], ["282", "I7N3G6G", "282", 0, "no description", 60.2], ["283", "I7N3G6G", "283", 0, "no description", 60.2], ["284", "I7N3G6G", "284", 0, "no description", 60.2], ["285", "I7N3G6G", "285", 0, "no description", 60.2], ["286", "I7N3G6G", "286", 0, "no description", 60.2], ["287", "I7N3G6G", "287", 0, "no description", 60.2], ["288", "I7N3G6G", "288", 0, "no description", 60.2], ["289", "I7N3G6G", "289", 0, "no description", 60.2], ["290", "I7N3G6G", "290", 0, "no description", 60.2], ["291", "I7N3G6G", "291", 0, "no description", 60.2], ["292", "I7N3G6G", "292", 0, "no description", 60.2], ["293", "I7N3G6G", "293", 0, "no description", 60.2], ["294", "I7N3G6G", "294", 0, "no description", 60.2], ["295", "I7N3G6G", "295", 0, "no description", 60.2], ["296", "I7N3G6G", "296", 0, "no description", 60.2], ["297", "I7N3G6G", "297", 0, "no description", 60.2], ["298", "I7N3G6G", "298", 0, "no description", 60.2], ["299", "I7N3G6G", "299", 0, "no description", 60.2], ["300", "I7N3G6G", "300", 0, "no description", 60.2], ["301", "I7N3G6G", "301", 0, "no description", 60.2], ["302", "I7N3G6G", "302", 0, "no description", 60.2], ["303", "I7N3G6G", "303", 0, "no description", 60.2], ["304", "I7N3G6G", "304", 0, "no description", 60.2], ["305", "I7N3G6G", "305", 0, "no description", 60.2], ["306", "I7N3G6G", "306", 0, "no description", 60.2], ["307", "I7N3G6G", "307", 0, "no description", 60.2], ["308", "I7N3G6G", "308", 0, "no description", 60.2], ["309", "I7N3G6G", "309", 0, "no description", 60.2], ["310", "I7N3G6G", "310", 0, "no description", 60.2], ["311", "I7N3G6G", "311", 0, "no description", 60.2], ["312", "I7N3G6G", "312", 0, "no description", 60.2], ["313", "I7N3G6G", "313", 0, "no description", 60.2], ["314", "I7N3G6G", "314", 0, "no description", 60.2], ["315", "I7N3G6G", "315", 0, "no description", 60.2], ["316", "I7N3G6G", "316", 0, "no description", 60.2], ["317", "I7N3G6G", "317", 0, "no description", 60.2], ["318", "I7N3G6G", "318", 0, "no description", 60.2], ["319", "I7N3G6G", "319", 0, "no description", 60.2], ["320", "I7N3G6G", "320", 0, "no description", 60.2], ["321", "I7N3G6G", "321", 0, "no description", 60.2], ["322", "I7N3G6G", "322", 0, "no description", 60.2], ["323", "I7N3G6G", "323", 0, "no description", 60.2], ["324", "I7N3G6G", "324", 0, "no description", 60.2], ["325", "I7N3G6G", "325", 0, "no description", 60.2], ["326", "I7N3G6G", "326", 0, "no description", 60.2], ["327", "I7N3G6G", "327", 0, "no description", 60.2], ["328", "I7N3G6G", "328", 0, "no description", 60.2], ["329", "I7N3G6G", "329", 0, "no description", 60.2], ["330", "I7N3G6G", "330", 0, "no description", 60.2], ["331", "I7N3G6G", "331", 0, "no description", 60.2], ["332", "I7N3G6G", "332", 0, "no description", 60.2], ["333", "I7N3G6G", "333", 0, "no description", 60.2], ["334", "I7N3G6G", "334", 0, "no description", 60.2], ["335", "I7N3G6G", "335", 0, "no description", 60.2], ["336", "I7N3G6G", "336", 0, "no description", 60.2], ["337", "I7N3G6G", "337", 0, "no description", 60.2], ["338", "I7N3G6G", "338", 0, "no description", 60.2], ["339", "I7N3G6G", "339", 0, "no description", 60.2], ["340", "I7N3G6G", "340", 0, "no description", 60.2], ["341", "I7N3G6G", "341", 0, "no description", 60.2], ["342", "I7N3G6G", "342", 0, "no description", 60.2], ["343", "I7N3G6G", "343", 0, "no description", 60.2], ["344", "I7N3G6G", "344", 0, "no description", 60.2], ["345", "I7N3G6G", "345", 0, "no description", 60.2], ["346", "I7N3G6G", "346", 0, "no description", 60.2], ["347", "I7N3G6G", "347", 0, "no description", 60.2], ["348", "I7N3G6G", "348", 0, "no description", 60.2], ["349", "I7N3G6G", "349", 0, "no description", 60.2], ["350", "I7N3G6G", "350", 0, "no description", 60.2], ["351", "I7N3G6G", "351", 0, "no description", 60.2], ["352", "I7N3G6G", "352", 0, "no description", 60.2], ["353", "I7N3G6G", "353", 0, "no description", 60.2], ["354", "I7N3G6G", "354", 0, "no description", 60.2], ["355", "I7N3G6G", "355", 0, "no description", 60.2], ["356", "I7N3G6G", "356", 0, "no description", 60.2], ["357", "I7N3G6G", "357", 0, "no description", 60.2], ["358", "I7N3G6G", "358", 0, "no description", 60.2], ["359", "I7N3G6G", "359", 0, "no description", 60.2], ["360", "I7N3G6G", "360", 0, "no description", 60.2], ["361", "I7N3G6G", "361", 0, "no description", 60.2], ["362", "I7N3G6G", "362", 0, "no description", 60.2], ["363", "I7N3G6G", "363", 0, "no description", 60.2], ["364", "I7N3G6G", "364", 0, "no description", 60.2], ["365", "I7N3G6G", "365", 0, "no description", 60.2], ["366", "I7N3G6G", "366", 0, "no description", 60.2], ["367", "I7N3G6G", "367", 0, "no description", 60.2], ["368", "I7N3G6G", "368", 0, "no description", 60.2], ["369", "I7N3G6G", "369", 0, "no description", 60.2], ["370", "I7N3G6G", "370", 0, "no description", 60.2], ["371", "I7N3G6G", "371", 0, "no description", 60.2], ["372", "I7N3G6G", "372", 0, "no description", 60.2], ["373", "I7N3G6G", "373", 0, "no description", 60.2], ["374", "I7N3G6G", "374", 0, "no description", 60.2], ["375", "I7N3G6G", "375", 0, "no description", 60.2], ["376", "I7N3G6G", "376", 0, "no description", 60.2], ["377", "I7N3G6G", "377", 0, "no description", 60.2], ["378", "I7N3G6G", "378", 0, "no description", 60.2], ["379", "I7N3G6G", "379", 0, "no description", 60.2], ["380", "I7N3G6G", "380", 0, "no description", 60.2], ["381", "I7N3G6G", "381", 0, "no description", 60.2], ["382", "I7N3G6G", "382", 0, "no description", 60.2], ["383", "I7N3G6G", "383", 0, "no description", 60.2], ["384", "I7N3G6G", "384", 0, "no description", 60.2], ["385", "I7N3G6G", "385", 0, "no description", 60.2], ["386", "I7N3G6G", "386", 0, "no description", 60.2], ["387", "I7N3G6G", "387", 0, "no description", 60.2], ["388", "I7N3G6G", "388", 0, "no description", 60.2], ["389", "I7N3G6G", "389", 0, "no description", 60.2], ["390", "I7N3G6G", "390", 0, "no description", 60.2], ["391", "I7N3G6G", "391", 0, "no description", 60.2], ["392", "I7N3G6G", "392", 0, "no description", 60.2], ["393", "I7N3G6G", "393", 0, "no description", 60.2], ["394", "I7N3G6G", "394", 0, "no description", 60.2], ["395", "I7N3G6G", "395", 0, "no description", 60.2], ["396", "I7N3G6G", "396", 0, "no description", 60.2], ["397", "I7N3G6G", "397", 0, "no description", 60.2], ["398", "I7N3G6G", "398", 0, "no description", 60.2], ["399", "I7N3G6G", "399", 0, "no description", 60.2], ["400", "I7N3G6G", "400", 0, "no description", 60.2], ["401", "I7N3G6G", "401", 0, "no description", 60.2], ["402", "I7N3G6G", "402", 0, "no description", 60.2], ["403", "I7N3G6G", "403", 0, "no description", 60.2], ["404", "I7N3G6G", "404", 0, "no description", 60.2], ["405", "I7N3G6G", "405", 0, "no description", 60.2], ["406", "I7N3G6G", "406", 0, "no description", 60.2], ["407", "I7N3G6G", "407", 0, "no description", 60.2], ["408", "I7N3G6G", "408", 0, "no description", 60.2], ["409", "I7N3G6G", "409", 0, "no description", 60.2], ["410", "I7N3G6G", "410", 0, "no description", 60.2], ["411", "I7N3G6G", "411", 0, "no description", 60.2], ["412", "I7N3G6G", "412", 0, "no description", 60.2], ["413", "I7N3G6G", "413", 0, "no description", 60.2], ["414", "I7N3G6G", "414", 0, "no description", 60.2], ["415", "I7N3G6G", "415", 0, "no description", 60.2], ["416", "I7N3G6G", "416", 0, "no description", 60.2], ["417", "I7N3G6G", "417", 0, "no description", 60.2], ["418", "I7N3G6G", "418", 0, "no description", 60.2], ["419", "I7N3G6G", "419", 0, "no description", 60.2], ["420", "I7N3G6G", "420", 0, "no description", 60.2], ["421", "I7N3G6G", "421", 0, "no description", 60.2], ["422", "I7N3G6G", "422", 0, "no description", 60.2], ["423", "I7N3G6G", "423", 0, "no description", 60.2], ["424", "I7N3G6G", "424", 0, "no description", 60.2], ["425", "I7N3G6G", "425", 0, "no description", 60.2], ["426", "I7N3G6G", "426", 0, "no description", 60.2], ["427", "I7N3G6G", "427", 0, "no description", 60.2], ["428", "I7N3G6G", "428", 0, "no description", 60.2], ["429", "I7N3G6G", "429", 0, "no description", 60.2], ["430", "I7N3G6G", "430", 0, "no description", 60.2], ["431", "I7N3G6G", "431", 0, "no description", 60.2], ["432", "I7N3G6G", "432", 0, "no description", 60.2], ["433", "I7N3G6G", "433", 0, "no description", 60.2], ["434", "I7N3G6G", "434", 0, "no description", 60.2], ["435", "I7N3G6G", "435", 0, "no description", 60.2], ["436", "I7N3G6G", "436", 0, "no description", 60.2], ["437", "I7N3G6G", "437", 0, "no description", 60.2], ["438", "I7N3G6G", "438", 0, "no description", 60.2], ["439", "I7N3G6G", "439", 0, "no description", 60.2], ["440", "I7N3G6G", "440", 0, "no description", 60.2], ["441", "I7N3G6G", "441", 0, "no description", 60.2], ["442", "I7N3G6G", "442", 0, "no description", 60.2], ["443", "I7N3G6G", "443", 0, "no description", 60.2], ["444", "I7N3G6G", "444", 0, "no description", 60.2], ["445", "I7N3G6G", "445", 0, "no description", 60.2], ["446", "I7N3G6G", "446", 0, "no description", 60.2], ["447", "I7N3G6G", "447", 0, "no description", 60.2], ["448", "I7N3G6G", "448", 0, "no description", 60.2], ["449", "I7N3G6G", "449", 0, "no description", 60.2], ["450", "I7N3G6G", "450", 0, "no description", 60.2], ["451", "I7N3G6G", "451", 0, "no description", 60.2], ["452", "I7N3G6G", "452", 0, "no description", 60.2], ["453", "I7N3G6G", "453", 0, "no description", 60.2], ["454", "I7N3G6G", "454", 0, "no description", 60.2], ["455", "I7N3G6G", "455", 0, "no description", 60.2], ["456", "I7N3G6G", "456", 0, "no description", 60.2], ["457", "I7N3G6G", "457", 0, "no description", 60.2], ["458", "I7N3G6G", "458", 0, "no description", 60.2], ["459", "I7N3G6G", "459", 0, "no description", 60.2], ["460", "I7N3G6G", "460", 0, "no description", 60.2], ["461", "I7N3G6G", "461", 0, "no description", 60.2], ["462", "I7N3G6G", "462", 0, "no description", 60.2], ["463", "I7N3G6G", "463", 0, "no description", 60.2], ["464", "I7N3G6G", "464", 0, "no description", 60.2], ["465", "I7N3G6G", "465", 0, "no description", 60.2], ["466", "I7N3G6G", "466", 0, "no description", 60.2], ["467", "I7N3G6G", "467", 0, "no description", 60.2], ["468", "I7N3G6G", "468", 0, "no description", 60.2], ["469", "I7N3G6G", "469", 0, "no description", 60.2], ["470", "I7N3G6G", "470", 0, "no description", 60.2], ["471", "I7N3G6G", "471", 0, "no description", 60.2], ["472", "I7N3G6G", "472", 0, "no description", 60.2], ["473", "I7N3G6G", "473", 0, "no description", 60.2], ["474", "I7N3G6G", "474", 0, "no description", 60.2], ["475", "I7N3G6G", "475", 0, "no description", 60.2], ["476", "I7N3G6G", "476", 0, "no description", 60.2], ["477", "I7N3G6G", "477", 0, "no description", 60.2], ["478", "I7N3G6G", "478", 0, "no description", 60.2], ["479", "I7N3G6G", "479", 0, "no description", 60.2], ["480", "I7N3G6G", "480", 0, "no description", 60.2], ["481", "I7N3G6G", "481", 0, "no description", 60.2], ["482", "I7N3G6G", "482", 0, "no description", 60.2], ["483", "I7N3G6G", "483", 0, "no description", 60.2], ["484", "I7N3G6G", "484", 0, "no description", 60.2], ["485", "I7N3G6G", "485", 0, "no description", 60.2], ["486", "I7N3G6G", "486", 0, "no description", 60.2], ["487", "I7N3G6G", "487", 0, "no description", 60.2], ["488", "I7N3G6G", "488", 0, "no description", 60.2], ["489", "I7N3G6G", "489", 0, "no description", 60.2], ["490", "I7N3G6G", "490", 0, "no description", 60.2], ["491", "I7N3G6G", "491", 0, "no description", 60.2], ["492", "I7N3G6G", "492", 0, "no description", 60.2], ["493", "I7N3G6G", "493", 0, "no description", 60.2], ["494", "I7N3G6G", "494", 0, "no description", 60.2], ["495", "I7N3G6G", "495", 0, "no description", 60.2], ["496", "I7N3G6G", "496", 0, "no description", 60.2], ["497", "I7N3G6G", "497", 0, "no description", 60.2], ["498", "I7N3G6G", "498", 0, "no description", 60.2], ["499", "I7N3G6G", "499", 0, "no description", 60.2], ["500", "I7N3G6G", "500", 0, "no description", 60.2], ["501", "I7N3G6G", "501", 0, "no description", 60.2], ["502", "I7N3G6G", "502", 0, "no description", 60.2], ["503", "I7N3G6G", "503", 0, "no description", 60.2], ["504", "I7N3G6G", "504", 0, "no description", 60.2], ["505", "I7N3G6G", "505", 0, "no description", 60.2], ["506", "I7N3G6G", "506", 0, "no description", 60.2], ["507", "I7N3G6G", "507", 0, "no description", 60.2], ["508", "I7N3G6G", "508", 0, "no description", 60.2], ["509", "I7N3G6G", "509", 0, "no description", 60.2], ["510", "I7N3G6G", "510", 0, "no description", 60.2], ["511", "I7N3G6G", "511", 0, "no description", 60.2], ["512", "I7N3G6G", "512", 0, "no description", 60.2], ["513", "I7N3G6G", "513", 0, "no description", 60.2], ["514", "I7N3G6G", "514", 0, "no description", 60.2], ["515", "I7N3G6G", "515", 0, "no description", 60.2], ["516", "I7N3G6G", "516", 0, "no description", 60.2], ["517", "I7N3G6G", "517", 0, "no description", 60.2], ["518", "I7N3G6G", "518", 0, "no description", 60.2], ["519", "I7N3G6G", "519", 0, "no description", 60.2], ["520", "I7N3G6G", "520", 0, "no description", 60.2], ["521", "I7N3G6G", "521", 0, "no description", 60.2], ["522", "I7N3G6G", "522", 0, "no description", 60.2], ["523", "I7N3G6G", "523", 0, "no description", 60.2], ["524", "I7N3G6G", "524", 0, "no description", 60.2], ["525", "I7N3G6G", "525", 0, "no description", 60.2], ["526", "I7N3G6G", "526", 0, "no description", 60.2], ["527", "I7N3G6G", "527", 0, "no description", 60.2], ["528", "I7N3G6G", "528", 0, "no description", 60.2], ["529", "I7N3G6G", "529", 0, "no description", 60.2], ["530", "I7N3G6G", "530", 0, "no description", 60.2], ["531", "I7N3G6G", "531", 0, "no description", 60.2], ["532", "I7N3G6G", "532", 0, "no description", 60.2], ["533", "I7N3G6G", "533", 0, "no description", 60.2], ["534", "I7N3G6G", "534", 0, "no description", 60.2], ["535", "I7N3G6G", "535", 0, "no description", 60.2], ["536", "I7N3G6G", "536", 0, "no description", 60.2], ["537", "I7N3G6G", "537", 0, "no description", 60.2], ["538", "I7N3G6G", "538", 0, "no description", 60.2], ["539", "I7N3G6G", "539", 0, "no description", 60.2], ["540", "I7N3G6G", "540", 0, "no description", 60.2], ["541", "I7N3G6G", "541", 0, "no description", 60.2], ["542", "I7N3G6G", "542", 0, "no description", 60.2], ["543", "I7N3G6G", "543", 0, "no description", 60.2], ["544", "I7N3G6G", "544", 0, "no description", 60.2], ["545", "I7N3G6G", "545", 0, "no description", 60.2], ["546", "I7N3G6G", "546", 0, "no description", 60.2], ["547", "I7N3G6G", "547", 0, "no description", 60.2], ["548", "I7N3G6G", "548", 0, "no description", 60.2], ["549", "I7N3G6G", "549", 0, "no description", 60.2], ["550", "I7N3G6G", "550", 0, "no description", 60.2], ["551", "I7N3G6G", "551", 0, "no description", 60.2], ["552", "I7N3G6G", "552", 0, "no description", 60.2], ["553", "I7N3G6G", "553", 0, "no description", 60.2], ["554", "I7N3G6G", "554", 0, "no description", 60.2], ["555", "I7N3G6G", "555", 0, "no description", 60.2], ["556", "I7N3G6G", "556", 0, "no description", 60.2], ["557", "I7N3G6G", "557", 0, "no description", 60.2], ["558", "I7N3G6G", "558", 0, "no description", 60.2], ["559", "I7N3G6G", "559", 0, "no description", 60.2], ["560", "I7N3G6G", "560", 0, "no description", 60.2], ["561", "I7N3G6G", "561", 0, "no description", 60.2], ["562", "I7N3G6G", "562", 0, "no description", 60.2], ["563", "I7N3G6G", "563", 0, "no description", 60.2], ["564", "I7N3G6G", "564", 0, "no description", 60.2], ["565", "I7N3G6G", "565", 0, "no description", 60.2], ["566", "I7N3G6G", "566", 0, "no description", 60.2], ["567", "I7N3G6G", "567", 0, "no description", 60.2], ["568", "I7N3G6G", "568", 0, "no description", 60.2], ["569", "I7N3G6G", "569", 0, "no description", 60.2], ["570", "I7N3G6G", "570", 0, "no description", 60.2], ["571", "I7N3G6G", "571", 0, "no description", 60.2], ["572", "I7N3G6G", "572", 0, "no description", 60.2], ["573", "I7N3G6G", "573", 0, "no description", 60.2], ["574", "I7N3G6G", "574", 0, "no description", 60.2], ["575", "I7N3G6G", "575", 0, "no description", 60.2], ["576", "I7N3G6G", "576", 0, "no description", 60.2], ["577", "I7N3G6G", "577", 0, "no description", 60.2], ["578", "I7N3G6G", "578", 0, "no description", 60.2], ["579", "I7N3G6G", "579", 0, "no description", 60.2], ["580", "I7N3G6G", "580", 0, "no description", 60.2], ["581", "I7N3G6G", "581", 0, "no description", 60.2], ["582", "I7N3G6G", "582", 0, "no description", 60.2], ["583", "I7N3G6G", "583", 0, "no description", 60.2], ["584", "I7N3G6G", "584", 0, "no description", 60.2], ["585", "I7N3G6G", "585", 0, "no description", 60.2], ["586", "I7N3G6G", "586", 0, "no description", 60.2], ["587", "I7N3G6G", "587", 0, "no description", 60.2], ["588", "I7N3G6G", "588", 0, "no description", 60.2], ["589", "I7N3G6G", "589", 0, "no description", 60.2], ["590", "I7N3G6G", "590", 0, "no description", 60.2], ["591", "I7N3G6G", "591", 0, "no description", 60.2], ["592", "I7N3G6G", "592", 0, "no description", 60.2], ["593", "I7N3G6G", "593", 0, "no description", 60.2], ["594", "I7N3G6G", "594", 0, "no description", 60.2], ["595", "I7N3G6G", "595", 0, "no description", 60.2], ["596", "I7N3G6G", "596", 0, "no description", 60.2], ["597", "I7N3G6G", "597", 0, "no description", 60.2], ["598", "I7N3G6G", "598", 0, "no description", 60.2], ["599", "I7N3G6G", "599", 0, "no description", 60.2], ["600", "I7N3G6G", "600", 0, "no description", 60.2], ["601", "I7N3G6G", "601", 0, "no description", 60.2], ["602", "I7N3G6G", "602", 0, "no description", 60.2], ["603", "I7N3G6G", "603", 0, "no description", 60.2], ["604", "I7N3G6G", "604", 0, "no description", 60.2], ["605", "I7N3G6G", "605", 0, "no description", 60.2], ["606", "I7N3G6G", "606", 0, "no description", 60.2], ["607", "I7N3G6G", "607", 0, "no description", 60.2], ["608", "I7N3G6G", "608", 0, "no description", 60.2], ["609", "I7N3G6G", "609", 0, "no description", 60.2], ["610", "I7N3G6G", "610", 0, "no description", 60.2], ["611", "I7N3G6G", "611", 0, "no description", 60.2], ["612", "I7N3G6G", "612", 0, "no description", 60.2], ["613", "I7N3G6G", "613", 0, "no description", 60.2], ["614", "I7N3G6G", "614", 0, "no description", 60.2], ["615", "I7N3G6G", "615", 0, "no description", 60.2], ["616", "I7N3G6G", "616", 0, "no description", 60.2], ["617", "I7N3G6G", "617", 0, "no description", 60.2], ["618", "I7N3G6G", "618", 0, "no description", 60.2], ["619", "I7N3G6G", "619", 0, "no description", 60.2], ["620", "I7N3G6G", "620", 0, "no description", 60.2], ["621", "I7N3G6G", "621", 0, "no description", 60.2], ["622", "I7N3G6G", "622", 0, "no description", 60.2], ["623", "I7N3G6G", "623", 0, "no description", 60.2], ["624", "I7N3G6G", "624", 0, "no description", 60.2], ["625", "I7N3G6G", "625", 0, "no description", 60.2], ["626", "I7N3G6G", "626", 0, "no description", 60.2], ["627", "I7N3G6G", "627", 0, "no description", 60.2], ["628", "I7N3G6G", "628", 0, "no description", 60.2], ["629", "I7N3G6G", "629", 0, "no description", 60.2], ["630", "I7N3G6G", "630", 0, "no description", 60.2], ["631", "I7N3G6G", "631", 0, "no description", 60.2], ["632", "I7N3G6G", "632", 0, "no description", 60.2], ["633", "I7N3G6G", "633", 0, "no description", 60.2], ["634", "I7N3G6G", "634", 0, "no description", 60.2], ["635", "I7N3G6G", "635", 0, "no description", 60.2], ["636", "I7N3G6G", "636", 0, "no description", 60.2], ["637", "I7N3G6G", "637", 0, "no description", 60.2], ["638", "I7N3G6G", "638", 0, "no description", 60.2], ["639", "I7N3G6G", "639", 0, "no description", 60.2], ["640", "I7N3G6G", "640", 0, "no description", 60.2], ["641", "I7N3G6G", "641", 0, "no description", 60.2], ["642", "I7N3G6G", "642", 0, "no description", 60.2], ["643", "I7N3G6G", "643", 0, "no description", 60.2], ["644", "I7N3G6G", "644", 0, "no description", 60.2], ["645", "I7N3G6G", "645", 0, "no description", 60.2], ["646", "I7N3G6G", "646", 0, "no description", 60.2], ["647", "I7N3G6G", "647", 0, "no description", 60.2], ["648", "I7N3G6G", "648", 0, "no description", 60.2], ["649", "I7N3G6G", "649", 0, "no description", 60.2], ["650", "I7N3G6G", "650", 0, "no description", 60.2], ["651", "I7N3G6G", "651", 0, "no description", 60.2], ["652", "I7N3G6G", "652", 0, "no description", 60.2], ["653", "I7N3G6G", "653", 0, "no description", 60.2], ["654", "I7N3G6G", "654", 0, "no description", 60.2], ["655", "I7N3G6G", "655", 0, "no description", 60.2], ["656", "I7N3G6G", "656", 0, "no description", 60.2], ["657", "I7N3G6G", "657", 0, "no description", 60.2], ["658", "I7N3G6G", "658", 0, "no description", 60.2], ["659", "I7N3G6G", "659", 0, "no description", 60.2], ["660", "I7N3G6G", "660", 0, "no description", 60.2], ["661", "I7N3G6G", "661", 0, "no description", 60.2], ["662", "I7N3G6G", "662", 0, "no description", 60.2], ["663", "I7N3G6G", "663", 0, "no description", 60.2], ["664", "I7N3G6G", "664", 0, "no description", 60.2], ["665", "I7N3G6G", "665", 0, "no description", 60.2], ["666", "I7N3G6G", "666", 0, "no description", 60.2], ["667", "I7N3G6G", "667", 0, "no description", 60.2], ["668", "I7N3G6G", "668", 0, "no description", 60.2], ["669", "I7N3G6G", "669", 0, "no description", 60.2], ["670", "I7N3G6G", "670", 0, "no description", 60.2], ["671", "I7N3G6G", "671", 0, "no description", 60.2], ["672", "I7N3G6G", "672", 0, "no description", 60.2], ["673", "I7N3G6G", "673", 0, "no description", 60.2], ["674", "I7N3G6G", "674", 0, "no description", 60.2], ["675", "I7N3G6G", "675", 0, "no description", 60.2], ["676", "I7N3G6G", "676", 0, "no description", 60.2], ["677", "I7N3G6G", "677", 0, "no description", 60.2], ["678", "I7N3G6G", "678", 0, "no description", 60.2], ["679", "I7N3G6G", "679", 0, "no description", 60.2], ["680", "I7N3G6G", "680", 0, "no description", 60.2], ["681", "I7N3G6G", "681", 0, "no description", 60.2], ["682", "I7N3G6G", "682", 0, "no description", 60.2], ["683", "I7N3G6G", "683", 0, "no description", 60.2], ["684", "I7N3G6G", "684", 0, "no description", 60.2], ["685", "I7N3G6G", "685", 0, "no description", 60.2], ["686", "I7N3G6G", "686", 0, "no description", 60.2], ["687", "I7N3G6G", "687", 0, "no description", 60.2], ["688", "I7N3G6G", "688", 0, "no description", 60.2], ["689", "I7N3G6G", "689", 0, "no description", 60.2], ["690", "I7N3G6G", "690", 0, "no description", 60.2], ["691", "I7N3G6G", "691", 0, "no description", 60.2], ["692", "I7N3G6G", "692", 0, "no description", 60.2], ["693", "I7N3G6G", "693", 0, "no description", 60.2], ["694", "I7N3G6G", "694", 0, "no description", 60.2], ["695", "I7N3G6G", "695", 0, "no description", 60.2], ["696", "I7N3G6G", "696", 0, "no description", 60.2], ["697", "I7N3G6G", "697", 0, "no description", 60.2], ["698", "I7N3G6G", "698", 0, "no description", 60.2], ["699", "I7N3G6G", "699", 0, "no description", 60.2], ["700", "I7N3G6G", "700", 0, "no description", 60.2], ["701", "I7N3G6G", "701", 0, "no description", 60.2], ["702", "I7N3G6G", "702", 0, "no description", 60.2], ["703", "I7N3G6G", "703", 0, "no description", 60.2], ["704", "I7N3G6G", "704", 0, "no description", 60.2], ["705", "I7N3G6G", "705", 0, "no description", 60.2], ["706", "I7N3G6G", "706", 0, "no description", 60.2], ["707", "I7N3G6G", "707", 0, "no description", 60.2], ["708", "I7N3G6G", "708", 0, "no description", 60.2], ["709", "I7N3G6G", "709", 0, "no description", 60.2], ["710", "I7N3G6G", "710", 0, "no description", 60.2], ["711", "I7N3G6G", "711", 0, "no description", 60.2], ["712", "I7N3G6G", "712", 0, "no description", 60.2], ["713", "I7N3G6G", "713", 0, "no description", 60.2], ["714", "I7N3G6G", "714", 0, "no description", 60.2], ["715", "I7N3G6G", "715", 0, "no description", 60.2], ["716", "I7N3G6G", "716", 0, "no description", 60.2], ["717", "I7N3G6G", "717", 0, "no description", 60.2], ["718", "I7N3G6G", "718", 0, "no description", 60.2], ["719", "I7N3G6G", "719", 0, "no description", 60.2], ["720", "I7N3G6G", "720", 0, "no description", 60.2], ["721", "I7N3G6G", "721", 0, "no description", 60.2], ["722", "I7N3G6G", "722", 0, "no description", 60.2], ["723", "I7N3G6G", "723", 0, "no description", 60.2], ["724", "I7N3G6G", "724", 0, "no description", 60.2], ["725", "I7N3G6G", "725", 0, "no description", 60.2], ["726", "I7N3G6G", "726", 0, "no description", 60.2], ["727", "I7N3G6G", "727", 0, "no description", 60.2], ["728", "I7N3G6G", "728", 0, "no description", 60.2], ["729", "I7N3G6G", "729", 0, "no description", 60.2], ["730", "I7N3G6G", "730", 0, "no description", 60.2], ["731", "I7N3G6G", "731", 0, "no description", 60.2], ["732", "I7N3G6G", "732", 0, "no description", 60.2], ["733", "I7N3G6G", "733", 0, "no description", 60.2], ["734", "I7N3G6G", "734", 0, "no description", 60.2], ["735", "I7N3G6G", "735", 0, "no description", 60.2], ["736", "I7N3G6G", "736", 0, "no description", 60.2], ["737", "I7N3G6G", "737", 0, "no description", 60.2], ["738", "I7N3G6G", "738", 0, "no description", 60.2], ["739", "I7N3G6G", "739", 0, "no description", 60.2], ["740", "I7N3G6G", "740", 0, "no description", 60.2], ["741", "I7N3G6G", "741", 0, "no description", 60.2], ["742", "I7N3G6G", "742", 0, "no description", 60.2], ["743", "I7N3G6G", "743", 0, "no description", 60.2], ["744", "I7N3G6G", "744", 0, "no description", 60.2], ["745", "I7N3G6G", "745", 0, "no description", 60.2], ["746", "I7N3G6G", "746", 0, "no description", 60.2], ["747", "I7N3G6G", "747", 0, "no description", 60.2], ["748", "I7N3G6G", "748", 0, "no description", 60.2], ["749", "I7N3G6G", "749", 0, "no description", 60.2], ["750", "I7N3G6G", "750", 0, "no description", 60.2], ["751", "I7N3G6G", "751", 0, "no description", 60.2], ["752", "I7N3G6G", "752", 0, "no description", 60.2], ["753", "I7N3G6G", "753", 0, "no description", 60.2], ["754", "I7N3G6G", "754", 0, "no description", 60.2], ["755", "I7N3G6G", "755", 0, "no description", 60.2], ["756", "I7N3G6G", "756", 0, "no description", 60.2], ["757", "I7N3G6G", "757", 0, "no description", 60.2], ["758", "I7N3G6G", "758", 0, "no description", 60.2], ["759", "I7N3G6G", "759", 0, "no description", 60.2], ["760", "I7N3G6G", "760", 0, "no description", 60.2], ["761", "I7N3G6G", "761", 0, "no description", 60.2], ["762", "I7N3G6G", "762", 0, "no description", 60.2], ["763", "I7N3G6G", "763", 0, "no description", 60.2], ["764", "I7N3G6G", "764", 0, "no description", 60.2], ["765", "I7N3G6G", "765", 0, "no description", 60.2], ["766", "I7N3G6G", "766", 0, "no description", 60.2], ["767", "I7N3G6G", "767", 0, "no description", 60.2], ["768", "I7N3G6G", "768", 0, "no description", 60.2], ["769", "I7N3G6G", "769", 0, "no description", 60.2], ["770", "I7N3G6G", "770", 0, "no description", 60.2], ["771", "I7N3G6G", "771", 0, "no description", 60.2], ["772", "I7N3G6G", "772", 0, "no description", 60.2], ["773", "I7N3G6G", "773", 0, "no description", 60.2], ["774", "I7N3G6G", "774", 0, "no description", 60.2], ["775", "I7N3G6G", "775", 0, "no description", 60.2], ["776", "I7N3G6G", "776", 0, "no description", 60.2], ["777", "I7N3G6G", "777", 0, "no description", 60.2], ["778", "I7N3G6G", "778", 0, "no description", 60.2], ["779", "I7N3G6G", "779", 0, "no description", 60.2], ["780", "I7N3G6G", "780", 0, "no description", 60.2], ["781", "I7N3G6G", "781", 0, "no description", 60.2], ["782", "I7N3G6G", "782", 0, "no description", 60.2], ["783", "I7N3G6G", "783", 0, "no description", 60.2], ["784", "I7N3G6G", "784", 0, "no description", 60.2], ["785", "I7N3G6G", "785", 0, "no description", 60.2], ["786", "I7N3G6G", "786", 0, "no description", 60.2], ["787", "I7N3G6G", "787", 0, "no description", 60.2], ["788", "I7N3G6G", "788", 0, "no description", 60.2], ["789", "I7N3G6G", "789", 0, "no description", 60.2], ["790", "I7N3G6G", "790", 0, "no description", 60.2], ["791", "I7N3G6G", "791", 0, "no description", 60.2], ["792", "I7N3G6G", "792", 0, "no description", 60.2], ["793", "I7N3G6G", "793", 0, "no description", 60.2], ["794", "I7N3G6G", "794", 0, "no description", 60.2], ["795", "I7N3G6G", "795", 0, "no description", 60.2], ["796", "I7N3G6G", "796", 0, "no description", 60.2], ["797", "I7N3G6G", "797", 0, "no description", 60.2], ["798", "I7N3G6G", "798", 0, "no description", 60.2], ["799", "I7N3G6G", "799", 0, "no description", 60.2], ["800", "I7N3G6G", "800", 0, "no description", 60.2], ["801", "I7N3G6G", "801", 0, "no description", 60.2], ["802", "I7N3G6G", "802", 0, "no description", 60.2], ["803", "I7N3G6G", "803", 0, "no description", 60.2], ["804", "I7N3G6G", "804", 0, "no description", 60.2], ["805", "I7N3G6G", "805", 0, "no description", 60.2], ["806", "I7N3G6G", "806", 0, "no description", 60.2], ["807", "I7N3G6G", "807", 0, "no description", 60.2], ["808", "I7N3G6G", "808", 0, "no description", 60.2], ["809", "I7N3G6G", "809", 0, "no description", 60.2], ["810", "I7N3G6G", "810", 0, "no description", 60.2], ["811", "I7N3G6G", "811", 0, "no description", 60.2], ["812", "I7N3G6G", "812", 0, "no description", 60.2], ["813", "I7N3G6G", "813", 0, "no description", 60.2], ["814", "I7N3G6G", "814", 0, "no description", 60.2], ["815", "I7N3G6G", "815", 0, "no description", 60.2], ["816", "I7N3G6G", "816", 0, "no description", 60.2], ["817", "I7N3G6G", "817", 0, "no description", 60.2], ["818", "I7N3G6G", "818", 0, "no description", 60.2], ["819", "I7N3G6G", "819", 0, "no description", 60.2], ["820", "I7N3G6G", "820", 0, "no description", 60.2], ["821", "I7N3G6G", "821", 0, "no description", 60.2], ["822", "I7N3G6G", "822", 0, "no description", 60.2], ["823", "I7N3G6G", "823", 0, "no description", 60.2], ["824", "I7N3G6G", "824", 0, "no description", 60.2], ["825", "I7N3G6G", "825", 0, "no description", 60.2], ["826", "I7N3G6G", "826", 0, "no description", 60.2], ["827", "I7N3G6G", "827", 0, "no description", 60.2], ["828", "I7N3G6G", "828", 0, "no description", 60.2], ["829", "I7N3G6G", "829", 0, "no description", 60.2], ["830", "I7N3G6G", "830", 0, "no description", 60.2], ["831", "I7N3G6G", "831", 0, "no description", 60.2], ["832", "I7N3G6G", "832", 0, "no description", 60.2], ["833", "I7N3G6G", "833", 0, "no description", 60.2], ["834", "I7N3G6G", "834", 0, "no description", 60.2], ["835", "I7N3G6G", "835", 0, "no description", 60.2], ["836", "I7N3G6G", "836", 0, "no description", 60.2], ["837", "I7N3G6G", "837", 0, "no description", 60.2], ["838", "I7N3G6G", "838", 0, "no description", 60.2], ["839", "I7N3G6G", "839", 0, "no description", 60.2], ["840", "I7N3G6G", "840", 0, "no description", 60.2], ["841", "I7N3G6G", "841", 0, "no description", 60.2], ["842", "I7N3G6G", "842", 0, "no description", 60.2], ["843", "I7N3G6G", "843", 0, "no description", 60.2], ["844", "I7N3G6G", "844", 0, "no description", 60.2], ["845", "I7N3G6G", "845", 0, "no description", 60.2], ["846", "I7N3G6G", "846", 0, "no description", 60.2], ["847", "I7N3G6G", "847", 0, "no description", 60.2], ["848", "I7N3G6G", "848", 0, "no description", 60.2], ["849", "I7N3G6G", "849", 0, "no description", 60.2], ["850", "I7N3G6G", "850", 0, "no description", 60.2], ["851", "I7N3G6G", "851", 0, "no description", 60.2], ["852", "I7N3G6G", "852", 0, "no description", 60.2], ["853", "I7N3G6G", "853", 0, "no description", 60.2], ["854", "I7N3G6G", "854", 0, "no description", 60.2], ["855", "I7N3G6G", "855", 0, "no description", 60.2], ["856", "I7N3G6G", "856", 0, "no description", 60.2], ["857", "I7N3G6G", "857", 0, "no description", 60.2], ["858", "I7N3G6G", "858", 0, "no description", 60.2], ["859", "I7N3G6G", "859", 0, "no description", 60.2], ["860", "I7N3G6G", "860", 0, "no description", 60.2], ["861", "I7N3G6G", "861", 0, "no description", 60.2], ["862", "I7N3G6G", "862", 0, "no description", 60.2], ["863", "I7N3G6G", "863", 0, "no description", 60.2], ["864", "I7N3G6G", "864", 0, "no description", 60.2], ["865", "I7N3G6G", "865", 0, "no description", 60.2], ["866", "I7N3G6G", "866", 0, "no description", 60.2], ["867", "I7N3G6G", "867", 0, "no description", 60.2], ["868", "I7N3G6G", "868", 0, "no description", 60.2], ["869", "I7N3G6G", "869", 0, "no description", 60.2], ["870", "I7N3G6G", "870", 0, "no description", 60.2], ["871", "I7N3G6G", "871", 0, "no description", 60.2], ["872", "I7N3G6G", "872", 0, "no description", 60.2], ["873", "I7N3G6G", "873", 0, "no description", 60.2], ["874", "I7N3G6G", "874", 0, "no description", 60.2], ["875", "I7N3G6G", "875", 0, "no description", 60.2], ["876", "I7N3G6G", "876", 0, "no description", 60.2], ["877", "I7N3G6G", "877", 0, "no description", 60.2], ["878", "I7N3G6G", "878", 0, "no description", 60.2], ["879", "I7N3G6G", "879", 0, "no description", 60.2], ["880", "I7N3G6G", "880", 0, "no description", 60.2], ["881", "I7N3G6G", "881", 0, "no description", 60.2], ["882", "I7N3G6G", "882", 0, "no description", 60.2], ["883", "I7N3G6G", "883", 0, "no description", 60.2], ["884", "I7N3G6G", "884", 0, "no description", 60.2], ["885", "I7N3G6G", "885", 0, "no description", 60.2], ["886", "I7N3G6G", "886", 0, "no description", 60.2], ["887", "I7N3G6G", "887", 0, "no description", 60.2], ["888", "I7N3G6G", "888", 0, "no description", 60.2], ["889", "I7N3G6G", "889", 0, "no description", 60.2], ["890", "I7N3G6G", "890", 0, "no description", 60.2], ["891", "I7N3G6G", "891", 0, "no description", 60.2], ["892", "I7N3G6G", "892", 0, "no description", 60.2], ["893", "I7N3G6G", "893", 0, "no description", 60.2], ["894", "I7N3G6G", "894", 0, "no description", 60.2], ["895", "I7N3G6G", "895", 0, "no description", 60.2], ["896", "I7N3G6G", "896", 0, "no description", 60.2], ["897", "I7N3G6G", "897", 0, "no description", 60.2], ["898", "I7N3G6G", "898", 0, "no description", 60.2], ["899", "I7N3G6G", "899", 0, "no description", 60.2], ["900", "I7N3G6G", "900", 0, "no description", 60.2], ["901", "I7N3G6G", "901", 0, "no description", 60.2], ["902", "I7N3G6G", "902", 0, "no description", 60.2], ["903", "I7N3G6G", "903", 0, "no description", 60.2], ["904", "I7N3G6G", "904", 0, "no description", 60.2], ["905", "I7N3G6G", "905", 0, "no description", 60.2], ["906", "I7N3G6G", "906", 0, "no description", 60.2], ["907", "I7N3G6G", "907", 0, "no description", 60.2], ["908", "I7N3G6G", "908", 0, "no description", 60.2], ["909", "I7N3G6G", "909", 0, "no description", 60.2], ["910", "I7N3G6G", "910", 0, "no description", 60.2], ["911", "I7N3G6G", "911", 0, "no description", 60.2], ["912", "I7N3G6G", "912", 0, "no description", 60.2], ["913", "I7N3G6G", "913", 0, "no description", 60.2], ["914", "I7N3G6G", "914", 0, "no description", 60.2], ["915", "I7N3G6G", "915", 0, "no description", 60.2], ["916", "I7N3G6G", "916", 0, "no description", 60.2], ["917", "I7N3G6G", "917", 0, "no description", 60.2], ["918", "I7N3G6G", "918", 0, "no description", 60.2], ["919", "I7N3G6G", "919", 0, "no description", 60.2], ["920", "I7N3G6G", "920", 0, "no description", 60.2], ["921", "I7N3G6G", "921", 0, "no description", 60.2], ["922", "I7N3G6G", "922", 0, "no description", 60.2], ["923", "I7N3G6G", "923", 0, "no description", 60.2], ["924", "I7N3G6G", "924", 0, "no description", 60.2], ["925", "I7N3G6G", "925", 0, "no description", 60.2], ["926", "I7N3G6G", "926", 0, "no description", 60.2], ["927", "I7N3G6G", "927", 0, "no description", 60.2], ["928", "I7N3G6G", "928", 0, "no description", 60.2], ["929", "I7N3G6G", "929", 0, "no description", 60.2], ["930", "I7N3G6G", "930", 0, "no description", 60.2], ["931", "I7N3G6G", "931", 0, "no description", 60.2], ["932", "I7N3G6G", "932", 0, "no description", 60.2], ["933", "I7N3G6G", "933", 0, "no description", 60.2], ["934", "I7N3G6G", "934", 0, "no description", 60.2], ["935", "I7N3G6G", "935", 0, "no description", 60.2], ["936", "I7N3G6G", "936", 0, "no description", 60.2], ["937", "I7N3G6G", "937", 0, "no description", 60.2], ["938", "I7N3G6G", "938", 0, "no description", 60.2], ["939", "I7N3G6G", "939", 0, "no description", 60.2], ["940", "I7N3G6G", "940", 0, "no description", 60.2], ["941", "I7N3G6G", "941", 0, "no description", 60.2], ["942", "I7N3G6G", "942", 0, "no description", 60.2], ["943", "I7N3G6G", "943", 0, "no description", 60.2], ["944", "I7N3G6G", "944", 0, "no description", 60.2], ["945", "I7N3G6G", "945", 0, "no description", 60.2], ["946", "I7N3G6G", "946", 0, "no description", 60.2], ["947", "I7N3G6G", "947", 0, "no description", 60.2], ["948", "I7N3G6G", "948", 0, "no description", 60.2], ["949", "I7N3G6G", "949", 0, "no description", 60.2], ["950", "I7N3G6G", "950", 0, "no description", 60.2], ["951", "I7N3G6G", "951", 0, "no description", 60.2], ["952", "I7N3G6G", "952", 0, "no description", 60.2], ["953", "I7N3G6G", "953", 0, "no description", 60.2], ["954", "I7N3G6G", "954", 0, "no description", 60.2], ["955", "I7N3G6G", "955", 0, "no description", 60.2], ["956", "I7N3G6G", "956", 0, "no description", 60.2], ["957", "I7N3G6G", "957", 0, "no description", 60.2], ["958", "I7N3G6G", "958", 0, "no description", 60.2], ["959", "I7N3G6G", "959", 0, "no description", 60.2], ["960", "I7N3G6G", "960", 0, "no description", 60.2], ["961", "I7N3G6G", "961", 0, "no description", 60.2], ["962", "I7N3G6G", "962", 0, "no description", 60.2], ["963", "I7N3G6G", "963", 0, "no description", 60.2], ["964", "I7N3G6G", "964", 0, "no description", 60.2], ["965", "I7N3G6G", "965", 0, "no description", 60.2], ["966", "I7N3G6G", "966", 0, "no description", 60.2], ["967", "I7N3G6G", "967", 0, "no description", 60.2], ["968", "I7N3G6G", "968", 0, "no description", 60.2], ["969", "I7N3G6G", "969", 0, "no description", 60.2], ["970", "I7N3G6G", "970", 0, "no description", 60.2], ["971", "I7N3G6G", "971", 0, "no description", 60.2], ["972", "I7N3G6G", "972", 0, "no description", 60.2], ["973", "I7N3G6G", "973", 0, "no description", 60.2], ["974", "I7N3G6G", "974", 0, "no description", 60.2], ["975", "I7N3G6G", "975", 0, "no description", 60.2], ["976", "I7N3G6G", "976", 0, "no description", 60.2], ["977", "I7N3G6G", "977", 0, "no description", 60.2], ["978", "I7N3G6G", "978", 0, "no description", 60.2], ["979", "I7N3G6G", "979", 0, "no description", 60.2], ["980", "I7N3G6G", "980", 0, "no description", 60.2], ["981", "I7N3G6G", "981", 0, "no description", 60.2], ["982", "I7N3G6G", "982", 0, "no description", 60.2], ["983", "I7N3G6G", "983", 0, "no description", 60.2], ["984", "I7N3G6G", "984", 0, "no description", 60.2], ["985", "I7N3G6G", "985", 0, "no description", 60.2], ["986", "I7N3G6G", "986", 0, "no description", 60.2], ["987", "I7N3G6G", "987", 0, "no description", 60.2], ["988", "I7N3G6G", "988", 0, "no description", 60.2], ["989", "I7N3G6G", "989", 0, "no description", 60.2], ["990", "I7N3G6G", "990", 0, "no description", 60.2], ["991", "I7N3G6G", "991", 0, "no description", 60.2], ["992", "I7N3G6G", "992", 0, "no description", 60.2], ["993", "I7N3G6G", "993", 0, "no description", 60.2], ["994", "I7N3G6G", "994", 0, "no description", 60.2], ["995", "I7N3G6G", "995", 0, "no description", 60.2], ["996", "I7N3G6G", "996", 0, "no description", 60.2], ["997", "I7N3G6G", "997", 0, "no description", 60.2], ["998", "I7N3G6G", "998", 0, "no description", 60.2], ["999", "I7N3G6G", "999", 0, "no description", 60.2], ["1000", "I7N3G6G", "1000", 0, "no description", 60.2], ["1001", "I7N3G6G", "1001", 0, "no description", 60.2], ["1002", "I7N3G6G", "1002", 0, "no description", 60.2], ["1003", "I7N3G6G", "1003", 0, "no description", 60.2], ["1004", "I7N3G6G", "1004", 0, "no description", 60.2], ["1005", "I7N3G6G", "1005", 0, "no description", 60.2], ["1006", "I7N3G6G", "1006", 0, "no description", 60.2], ["1007", "I7N3G6G", "1007", 0, "no description", 60.2], ["1008", "I7N3G6G", "1008", 0, "no description", 60.2], ["1009", "I7N3G6G", "1009", 0, "no description", 60.2], ["1010", "I7N3G6G", "1010", 0, "no description", 60.2], ["1011", "I7N3G6G", "1011", 0, "no description", 60.2], ["1012", "I7N3G6G", "1012", 0, "no description", 60.2], ["1013", "I7N3G6G", "1013", 0, "no description", 60.2], ["1014", "I7N3G6G", "1014", 0, "no description", 60.2], ["1015", "I7N3G6G", "1015", 0, "no description", 60.2], ["1016", "I7N3G6G", "1016", 0, "no description", 60.2], ["1017", "I7N3G6G", "1017", 0, "no description", 60.2], ["1018", "I7N3G6G", "1018", 0, "no description", 60.2], ["1019", "I7N3G6G", "1019", 0, "no description", 60.2], ["1020", "I7N3G6G", "1020", 0, "no description", 60.2], ["1021", "I7N3G6G", "1021", 0, "no description", 60.2], ["1022", "I7N3G6G", "1022", 0, "no description", 60.2], ["1023", "I7N3G6G", "1023", 0, "no description", 60.2], ["1024", "I7N3G6G", "1024", 0, "no description", 60.2], ["1025", "I7N3G6G", "1025", 0, "no description", 60.2], ["1026", "I7N3G6G", "1026", 0, "no description", 60.2], ["1027", "I7N3G6G", "1027", 0, "no description", 60.2], ["1028", "I7N3G6G", "1028", 0, "no description", 60.2], ["1029", "I7N3G6G", "1029", 0, "no description", 60.2], ["1030", "I7N3G6G", "1030", 0, "no description", 60.2], ["1031", "I7N3G6G", "1031", 0, "no description", 60.2], ["1032", "I7N3G6G", "1032", 0, "no description", 60.2], ["1033", "I7N3G6G", "1033", 0, "no description", 60.2], ["1034", "I7N3G6G", "1034", 0, "no description", 60.2], ["1035", "I7N3G6G", "1035", 0, "no description", 60.2], ["1036", "I7N3G6G", "1036", 0, "no description", 60.2], ["1037", "I7N3G6G", "1037", 0, "no description", 60.2], ["1038", "I7N3G6G", "1038", 0, "no description", 60.2], ["1039", "I7N3G6G", "1039", 0, "no description", 60.2], ["1040", "I7N3G6G", "1040", 0, "no description", 60.2], ["1041", "I7N3G6G", "1041", 0, "no description", 60.2], ["1042", "I7N3G6G", "1042", 0, "no description", 60.2], ["1043", "I7N3G6G", "1043", 0, "no description", 60.2], ["1044", "I7N3G6G", "1044", 0, "no description", 60.2], ["1045", "I7N3G6G", "1045", 0, "no description", 60.2], ["1046", "I7N3G6G", "1046", 0, "no description", 60.2], ["1047", "I7N3G6G", "1047", 0, "no description", 60.2], ["1048", "I7N3G6G", "1048", 0, "no description", 60.2], ["1049", "I7N3G6G", "1049", 0, "no description", 60.2], ["1050", "I7N3G6G", "1050", 0, "no description", 60.2], ["1051", "I7N3G6G", "1051", 0, "no description", 60.2], ["1052", "I7N3G6G", "1052", 0, "no description", 60.2], ["1053", "I7N3G6G", "1053", 0, "no description", 60.2], ["1054", "I7N3G6G", "1054", 0, "no description", 60.2], ["1055", "I7N3G6G", "1055", 0, "no description", 60.2], ["1056", "I7N3G6G", "1056", 0, "no description", 60.2], ["1057", "I7N3G6G", "1057", 0, "no description", 60.2], ["1058", "I7N3G6G", "1058", 0, "no description", 60.2], ["1059", "I7N3G6G", "1059", 0, "no description", 60.2], ["1060", "I7N3G6G", "1060", 0, "no description", 60.2], ["1061", "I7N3G6G", "1061", 0, "no description", 60.2], ["1062", "I7N3G6G", "1062", 0, "no description", 60.2], ["1063", "I7N3G6G", "1063", 0, "no description", 60.2], ["1064", "I7N3G6G", "1064", 0, "no description", 60.2], ["1065", "I7N3G6G", "1065", 0, "no description", 60.2], ["1066", "I7N3G6G", "1066", 0, "no description", 60.2], ["1067", "I7N3G6G", "1067", 0, "no description", 60.2], ["1068", "I7N3G6G", "1068", 0, "no description", 60.2], ["1069", "I7N3G6G", "1069", 0, "no description", 60.2], ["1070", "I7N3G6G", "1070", 0, "no description", 60.2], ["1071", "I7N3G6G", "1071", 0, "no description", 60.2], ["1072", "I7N3G6G", "1072", 0, "no description", 60.2], ["1073", "I7N3G6G", "1073", 0, "no description", 60.2], ["1074", "I7N3G6G", "1074", 0, "no description", 60.2], ["1075", "I7N3G6G", "1075", 0, "no description", 60.2], ["1076", "I7N3G6G", "1076", 0, "no description", 60.2], ["1077", "I7N3G6G", "1077", 0, "no description", 60.2], ["1078", "I7N3G6G", "1078", 0, "no description", 60.2], ["1079", "I7N3G6G", "1079", 0, "no description", 60.2], ["1080", "I7N3G6G", "1080", 0, "no description", 60.2], ["1081", "I7N3G6G", "1081", 0, "no description", 60.2], ["1082", "I7N3G6G", "1082", 0, "no description", 60.2], ["1083", "I7N3G6G", "1083", 0, "no description", 60.2], ["1084", "I7N3G6G", "1084", 0, "no description", 60.2], ["1085", "I7N3G6G", "1085", 0, "no description", 60.2], ["1086", "I7N3G6G", "1086", 0, "no description", 60.2], ["1087", "I7N3G6G", "1087", 0, "no description", 60.2], ["1088", "I7N3G6G", "1088", 0, "no description", 60.2], ["1089", "I7N3G6G", "1089", 0, "no description", 60.2], ["1090", "I7N3G6G", "1090", 0, "no description", 60.2], ["1091", "I7N3G6G", "1091", 0, "no description", 60.2], ["1092", "I7N3G6G", "1092", 0, "no description", 60.2], ["1093", "I7N3G6G", "1093", 0, "no description", 60.2], ["1094", "I7N3G6G", "1094", 0, "no description", 60.2], ["1095", "I7N3G6G", "1095", 0, "no description", 60.2], ["1096", "I7N3G6G", "1096", 0, "no description", 60.2], ["1097", "I7N3G6G", "1097", 0, "no description", 60.2], ["1098", "I7N3G6G", "1098", 0, "no description", 60.2], ["1099", "I7N3G6G", "1099", 0, "no description", 60.2], ["1100", "I7N3G6G", "1100", 0, "no description", 60.2], ["1101", "I7N3G6G", "1101", 0, "no description", 60.2], ["1102", "I7N3G6G", "1102", 0, "no description", 60.2], ["1103", "I7N3G6G", "1103", 0, "no description", 60.2], ["1104", "I7N3G6G", "1104", 0, "no description", 60.2], ["1105", "I7N3G6G", "1105", 0, "no description", 60.2], ["1106", "I7N3G6G", "1106", 0, "no description", 60.2], ["1107", "I7N3G6G", "1107", 0, "no description", 60.2], ["1108", "I7N3G6G", "1108", 0, "no description", 60.2], ["1109", "I7N3G6G", "1109", 0, "no description", 60.2], ["1110", "I7N3G6G", "1110", 0, "no description", 60.2], ["1111", "I7N3G6G", "1111", 0, "no description", 60.2], ["1112", "I7N3G6G", "1112", 0, "no description", 60.2], ["1113", "I7N3G6G", "1113", 0, "no description", 60.2], ["1114", "I7N3G6G", "1114", 0, "no description", 60.2], ["1115", "I7N3G6G", "1115", 0, "no description", 60.2], ["1116", "I7N3G6G", "1116", 0, "no description", 60.2], ["1117", "I7N3G6G", "1117", 0, "no description", 60.2], ["1118", "I7N3G6G", "1118", 0, "no description", 60.2], ["1119", "I7N3G6G", "1119", 0, "no description", 60.2], ["1120", "I7N3G6G", "1120", 0, "no description", 60.2], ["1121", "I7N3G6G", "1121", 0, "no description", 60.2], ["1122", "I7N3G6G", "1122", 0, "no description", 60.2], ["1123", "I7N3G6G", "1123", 0, "no description", 60.2], ["1124", "I7N3G6G", "1124", 0, "no description", 60.2], ["1125", "I7N3G6G", "1125", 0, "no description", 60.2], ["1126", "I7N3G6G", "1126", 0, "no description", 60.2], ["1127", "I7N3G6G", "1127", 0, "no description", 60.2], ["1128", "I7N3G6G", "1128", 0, "no description", 60.2], ["1129", "I7N3G6G", "1129", 0, "no description", 60.2], ["1130", "I7N3G6G", "1130", 0, "no description", 60.2], ["1131", "I7N3G6G", "1131", 0, "no description", 60.2], ["1132", "I7N3G6G", "1132", 0, "no description", 60.2], ["1133", "I7N3G6G", "1133", 0, "no description", 60.2], ["1134", "I7N3G6G", "1134", 0, "no description", 60.2], ["1135", "I7N3G6G", "1135", 0, "no description", 60.2], ["1136", "I7N3G6G", "1136", 0, "no description", 60.2], ["1137", "I7N3G6G", "1137", 0, "no description", 60.2], ["1138", "I7N3G6G", "1138", 0, "no description", 60.2], ["1139", "I7N3G6G", "1139", 0, "no description", 60.2], ["1140", "I7N3G6G", "1140", 0, "no description", 60.2], ["1141", "I7N3G6G", "1141", 0, "no description", 60.2], ["1142", "I7N3G6G", "1142", 0, "no description", 60.2], ["1143", "I7N3G6G", "1143", 0, "no description", 60.2], ["1144", "I7N3G6G", "1144", 0, "no description", 60.2], ["1145", "I7N3G6G", "1145", 0, "no description", 60.2], ["1146", "I7N3G6G", "1146", 0, "no description", 60.2], ["1147", "I7N3G6G", "1147", 0, "no description", 60.2], ["1148", "I7N3G6G", "1148", 0, "no description", 60.2], ["1149", "I7N3G6G", "1149", 0, "no description", 60.2], ["1150", "I7N3G6G", "1150", 0, "no description", 60.2], ["1151", "I7N3G6G", "1151", 0, "no description", 60.2], ["1152", "I7N3G6G", "1152", 0, "no description", 60.2], ["1153", "I7N3G6G", "1153", 0, "no description", 60.2], ["1154", "I7N3G6G", "1154", 0, "no description", 60.2], ["1155", "I7N3G6G", "1155", 0, "no description", 60.2], ["1156", "I7N3G6G", "1156", 0, "no description", 60.2], ["1157", "I7N3G6G", "1157", 0, "no description", 60.2], ["1158", "I7N3G6G", "1158", 0, "no description", 60.2], ["1159", "I7N3G6G", "1159", 0, "no description", 60.2], ["1160", "I7N3G6G", "1160", 0, "no description", 60.2], ["1161", "I7N3G6G", "1161", 0, "no description", 60.2], ["1162", "I7N3G6G", "1162", 0, "no description", 60.2], ["1163", "I7N3G6G", "1163", 0, "no description", 60.2], ["1164", "I7N3G6G", "1164", 0, "no description", 60.2], ["1165", "I7N3G6G", "1165", 0, "no description", 60.2], ["1166", "I7N3G6G", "1166", 0, "no description", 60.2], ["1167", "I7N3G6G", "1167", 0, "no description", 60.2], ["1168", "I7N3G6G", "1168", 0, "no description", 60.2], ["1169", "I7N3G6G", "1169", 0, "no description", 60.2], ["1170", "I7N3G6G", "1170", 0, "no description", 60.2], ["1171", "I7N3G6G", "1171", 0, "no description", 60.2], ["1172", "I7N3G6G", "1172", 0, "no description", 60.2], ["1173", "I7N3G6G", "1173", 0, "no description", 60.2], ["1174", "I7N3G6G", "1174", 0, "no description", 60.2], ["1175", "I7N3G6G", "1175", 0, "no description", 60.2], ["1176", "I7N3G6G", "1176", 0, "no description", 60.2], ["1177", "I7N3G6G", "1177", 0, "no description", 60.2], ["1178", "I7N3G6G", "1178", 0, "no description", 60.2], ["1179", "I7N3G6G", "1179", 0, "no description", 60.2], ["1180", "I7N3G6G", "1180", 0, "no description", 60.2], ["1181", "I7N3G6G", "1181", 0, "no description", 60.2], ["1182", "I7N3G6G", "1182", 0, "no description", 60.2], ["1183", "I7N3G6G", "1183", 0, "no description", 60.2], ["1184", "I7N3G6G", "1184", 0, "no description", 60.2], ["1185", "I7N3G6G", "1185", 0, "no description", 60.2], ["1186", "I7N3G6G", "1186", 0, "no description", 60.2], ["1187", "I7N3G6G", "1187", 0, "no description", 60.2], ["1188", "I7N3G6G", "1188", 0, "no description", 60.2], ["1189", "I7N3G6G", "1189", 0, "no description", 60.2], ["1190", "I7N3G6G", "1190", 0, "no description", 60.2], ["1191", "I7N3G6G", "1191", 0, "no description", 60.2], ["1192", "I7N3G6G", "1192", 0, "no description", 60.2], ["1193", "I7N3G6G", "1193", 0, "no description", 60.2], ["1194", "I7N3G6G", "1194", 0, "no description", 60.2], ["1195", "I7N3G6G", "1195", 0, "no description", 60.2], ["1196", "I7N3G6G", "1196", 0, "no description", 60.2], ["1197", "I7N3G6G", "1197", 0, "no description", 60.2], ["1198", "I7N3G6G", "1198", 0, "no description", 60.2], ["1199", "I7N3G6G", "1199", 0, "no description", 60.2], ["1200", "I7N3G6G", "1200", 0, "no description", 60.2], ["1201", "I7N3G6G", "1201", 0, "no description", 60.2], ["1202", "I7N3G6G", "1202", 0, "no description", 60.2], ["1203", "I7N3G6G", "1203", 0, "no description", 60.2], ["1204", "I7N3G6G", "1204", 0, "no description", 60.2], ["1205", "I7N3G6G", "1205", 0, "no description", 60.2], ["1206", "I7N3G6G", "1206", 0, "no description", 60.2], ["1207", "I7N3G6G", "1207", 0, "no description", 60.2], ["1208", "I7N3G6G", "1208", 0, "no description", 60.2], ["1209", "I7N3G6G", "1209", 0, "no description", 60.2], ["1210", "I7N3G6G", "1210", 0, "no description", 60.2], ["1211", "I7N3G6G", "1211", 0, "no description", 60.2], ["1212", "I7N3G6G", "1212", 0, "no description", 60.2], ["1213", "I7N3G6G", "1213", 0, "no description", 60.2], ["1214", "I7N3G6G", "1214", 0, "no description", 60.2], ["1215", "I7N3G6G", "1215", 0, "no description", 60.2], ["1216", "I7N3G6G", "1216", 0, "no description", 60.2], ["1217", "I7N3G6G", "1217", 0, "no description", 60.2], ["1218", "I7N3G6G", "1218", 0, "no description", 60.2], ["1219", "I7N3G6G", "1219", 0, "no description", 60.2], ["1220", "I7N3G6G", "1220", 0, "no description", 60.2], ["1221", "I7N3G6G", "1221", 0, "no description", 60.2], ["1222", "I7N3G6G", "1222", 0, "no description", 60.2], ["1223", "I7N3G6G", "1223", 0, "no description", 60.2], ["1224", "I7N3G6G", "1224", 0, "no description", 60.2], ["1225", "I7N3G6G", "1225", 0, "no description", 60.2], ["1226", "I7N3G6G", "1226", 0, "no description", 60.2], ["1227", "I7N3G6G", "1227", 0, "no description", 60.2], ["1228", "I7N3G6G", "1228", 0, "no description", 60.2], ["1229", "I7N3G6G", "1229", 0, "no description", 60.2], ["1230", "I7N3G6G", "1230", 0, "no description", 60.2], ["1231", "I7N3G6G", "1231", 0, "no description", 60.2], ["1232", "I7N3G6G", "1232", 0, "no description", 60.2], ["1233", "I7N3G6G", "1233", 0, "no description", 60.2], ["1234", "I7N3G6G", "1234", 0, "no description", 60.2], ["1235", "I7N3G6G", "1235", 0, "no description", 60.2], ["1236", "I7N3G6G", "1236", 0, "no description", 60.2], ["1237", "I7N3G6G", "1237", 0, "no description", 60.2], ["1238", "I7N3G6G", "1238", 0, "no description", 60.2], ["1239", "I7N3G6G", "1239", 0, "no description", 60.2], ["1240", "I7N3G6G", "1240", 0, "no description", 60.2], ["1241", "I7N3G6G", "1241", 0, "no description", 60.2], ["1242", "I7N3G6G", "1242", 0, "no description", 60.2], ["1243", "I7N3G6G", "1243", 0, "no description", 60.2], ["1244", "I7N3G6G", "1244", 0, "no description", 60.2], ["1245", "I7N3G6G", "1245", 0, "no description", 60.2], ["1246", "I7N3G6G", "1246", 0, "no description", 60.2], ["1247", "I7N3G6G", "1247", 0, "no description", 60.2], ["1248", "I7N3G6G", "1248", 0, "no description", 60.2], ["1249", "I7N3G6G", "1249", 0, "no description", 60.2], ["1250", "I7N3G6G", "1250", 0, "no description", 60.2], ["1251", "I7N3G6G", "1251", 0, "no description", 60.2], ["1252", "I7N3G6G", "1252", 0, "no description", 60.2], ["1253", "I7N3G6G", "1253", 0, "no description", 60.2], ["1254", "I7N3G6G", "1254", 0, "no description", 60.2], ["1255", "I7N3G6G", "1255", 0, "no description", 60.2], ["1256", "I7N3G6G", "1256", 0, "no description", 60.2], ["1257", "I7N3G6G", "1257", 0, "no description", 60.2], ["1258", "I7N3G6G", "1258", 0, "no description", 60.2], ["1259", "I7N3G6G", "1259", 0, "no description", 60.2], ["1260", "I7N3G6G", "1260", 0, "no description", 60.2], ["1261", "I7N3G6G", "1261", 0, "no description", 60.2], ["1262", "I7N3G6G", "1262", 0, "no description", 60.2], ["1263", "I7N3G6G", "1263", 0, "no description", 60.2], ["1264", "I7N3G6G", "1264", 0, "no description", 60.2], ["1265", "I7N3G6G", "1265", 0, "no description", 60.2], ["1266", "I7N3G6G", "1266", 0, "no description", 60.2], ["1267", "I7N3G6G", "1267", 0, "no description", 60.2], ["1268", "I7N3G6G", "1268", 0, "no description", 60.2], ["1269", "I7N3G6G", "1269", 0, "no description", 60.2], ["1270", "I7N3G6G", "1270", 0, "no description", 60.2], ["1271", "I7N3G6G", "1271", 0, "no description", 60.2], ["1272", "I7N3G6G", "1272", 0, "no description", 60.2], ["1273", "I7N3G6G", "1273", 0, "no description", 60.2], ["1274", "I7N3G6G", "1274", 0, "no description", 60.2], ["1275", "I7N3G6G", "1275", 0, "no description", 60.2], ["1276", "I7N3G6G", "1276", 0, "no description", 60.2], ["1277", "I7N3G6G", "1277", 0, "no description", 60.2], ["1278", "I7N3G6G", "1278", 0, "no description", 60.2], ["1279", "I7N3G6G", "1279", 0, "no description", 60.2], ["1280", "I7N3G6G", "1280", 0, "no description", 60.2], ["1281", "I7N3G6G", "1281", 0, "no description", 60.2], ["1282", "I7N3G6G", "1282", 0, "no description", 60.2], ["1283", "I7N3G6G", "1283", 0, "no description", 60.2], ["1284", "I7N3G6G", "1284", 0, "no description", 60.2], ["1285", "I7N3G6G", "1285", 0, "no description", 60.2], ["1286", "I7N3G6G", "1286", 0, "no description", 60.2], ["1287", "I7N3G6G", "1287", 0, "no description", 60.2], ["1288", "I7N3G6G", "1288", 0, "no description", 60.2], ["1289", "I7N3G6G", "1289", 0, "no description", 60.2], ["1290", "I7N3G6G", "1290", 0, "no description", 60.2], ["1291", "I7N3G6G", "1291", 0, "no description", 60.2], ["1292", "I7N3G6G", "1292", 0, "no description", 60.2], ["1293", "I7N3G6G", "1293", 0, "no description", 60.2], ["1294", "I7N3G6G", "1294", 0, "no description", 60.2], ["1295", "I7N3G6G", "1295", 0, "no description", 60.2], ["1296", "I7N3G6G", "1296", 0, "no description", 60.2], ["1297", "I7N3G6G", "1297", 0, "no description", 60.2], ["1298", "I7N3G6G", "1298", 0, "no description", 60.2], ["1299", "I7N3G6G", "1299", 0, "no description", 60.2], ["1300", "I7N3G6G", "1300", 0, "no description", 60.2], ["1301", "I7N3G6G", "1301", 0, "no description", 60.2], ["1302", "I7N3G6G", "1302", 0, "no description", 60.2], ["1303", "I7N3G6G", "1303", 0, "no description", 60.2], ["1304", "I7N3G6G", "1304", 0, "no description", 60.2], ["1305", "I7N3G6G", "1305", 0, "no description", 60.2], ["1306", "I7N3G6G", "1306", 0, "no description", 60.2], ["1307", "I7N3G6G", "1307", 0, "no description", 60.2], ["1308", "I7N3G6G", "1308", 0, "no description", 60.2], ["1309", "I7N3G6G", "1309", 0, "no description", 60.2], ["1310", "I7N3G6G", "1310", 0, "no description", 60.2], ["1311", "I7N3G6G", "1311", 0, "no description", 60.2], ["1312", "I7N3G6G", "1312", 0, "no description", 60.2], ["1313", "I7N3G6G", "1313", 0, "no description", 60.2], ["1314", "I7N3G6G", "1314", 0, "no description", 60.2], ["1315", "I7N3G6G", "1315", 0, "no description", 60.2], ["1316", "I7N3G6G", "1316", 0, "no description", 60.2], ["1317", "I7N3G6G", "1317", 0, "no description", 60.2], ["1318", "I7N3G6G", "1318", 0, "no description", 60.2], ["1319", "I7N3G6G", "1319", 0, "no description", 60.2], ["1320", "I7N3G6G", "1320", 0, "no description", 60.2], ["1321", "I7N3G6G", "1321", 0, "no description", 60.2], ["1322", "I7N3G6G", "1322", 0, "no description", 60.2], ["1323", "I7N3G6G", "1323", 0, "no description", 60.2], ["1324", "I7N3G6G", "1324", 0, "no description", 60.2], ["1325", "I7N3G6G", "1325", 0, "no description", 60.2], ["1326", "I7N3G6G", "1326", 0, "no description", 60.2], ["1327", "I7N3G6G", "1327", 0, "no description", 60.2], ["1328", "I7N3G6G", "1328", 0, "no description", 60.2], ["1329", "I7N3G6G", "1329", 0, "no description", 60.2], ["1330", "I7N3G6G", "1330", 0, "no description", 60.2], ["1331", "I7N3G6G", "1331", 0, "no description", 60.2], ["1332", "I7N3G6G", "1332", 0, "no description", 60.2], ["1333", "I7N3G6G", "1333", 0, "no description", 60.2], ["1334", "I7N3G6G", "1334", 0, "no description", 60.2], ["1335", "I7N3G6G", "1335", 0, "no description", 60.2], ["1336", "I7N3G6G", "1336", 0, "no description", 60.2], ["1337", "I7N3G6G", "1337", 0, "no description", 60.2], ["1338", "I7N3G6G", "1338", 0, "no description", 60.2], ["1339", "I7N3G6G", "1339", 0, "no description", 60.2], ["1340", "I7N3G6G", "1340", 0, "no description", 60.2], ["1341", "I7N3G6G", "1341", 0, "no description", 60.2], ["1342", "I7N3G6G", "1342", 0, "no description", 60.2], ["1343", "I7N3G6G", "1343", 0, "no description", 60.2], ["1344", "I7N3G6G", "1344", 0, "no description", 60.2], ["1345", "I7N3G6G", "1345", 0, "no description", 60.2], ["1346", "I7N3G6G", "1346", 0, "no description", 60.2], ["1347", "I7N3G6G", "1347", 0, "no description", 60.2], ["1348", "I7N3G6G", "1348", 0, "no description", 60.2], ["1349", "I7N3G6G", "1349", 0, "no description", 60.2], ["1350", "I7N3G6G", "1350", 0, "no description", 60.2], ["1351", "I7N3G6G", "1351", 0, "no description", 60.2], ["1352", "I7N3G6G", "1352", 0, "no description", 60.2], ["1353", "I7N3G6G", "1353", 0, "no description", 60.2], ["1354", "I7N3G6G", "1354", 0, "no description", 60.2], ["1355", "I7N3G6G", "1355", 0, "no description", 60.2], ["1356", "I7N3G6G", "1356", 0, "no description", 60.2], ["1357", "I7N3G6G", "1357", 0, "no description", 60.2], ["1358", "I7N3G6G", "1358", 0, "no description", 60.2], ["1359", "I7N3G6G", "1359", 0, "no description", 60.2], ["1360", "I7N3G6G", "1360", 0, "no description", 60.2], ["1361", "I7N3G6G", "1361", 0, "no description", 60.2], ["1362", "I7N3G6G", "1362", 0, "no description", 60.2], ["1363", "I7N3G6G", "1363", 0, "no description", 60.2], ["1364", "I7N3G6G", "1364", 0, "no description", 60.2], ["1365", "I7N3G6G", "1365", 0, "no description", 60.2], ["1366", "I7N3G6G", "1366", 0, "no description", 60.2], ["1367", "I7N3G6G", "1367", 0, "no description", 60.2], ["1368", "I7N3G6G", "1368", 0, "no description", 60.2], ["1369", "I7N3G6G", "1369", 0, "no description", 60.2], ["1370", "I7N3G6G", "1370", 0, "no description", 60.2], ["1371", "I7N3G6G", "1371", 0, "no description", 60.2], ["1372", "I7N3G6G", "1372", 0, "no description", 60.2], ["1373", "I7N3G6G", "1373", 0, "no description", 60.2], ["1374", "I7N3G6G", "1374", 0, "no description", 60.2], ["1375", "I7N3G6G", "1375", 0, "no description", 60.2], ["1376", "I7N3G6G", "1376", 0, "no description", 60.2], ["1377", "I7N3G6G", "1377", 0, "no description", 60.2], ["1378", "I7N3G6G", "1378", 0, "no description", 60.2], ["1379", "I7N3G6G", "1379", 0, "no description", 60.2], ["1380", "I7N3G6G", "1380", 0, "no description", 60.2], ["1381", "I7N3G6G", "1381", 0, "no description", 60.2], ["1382", "I7N3G6G", "1382", 0, "no description", 60.2], ["1383", "I7N3G6G", "1383", 0, "no description", 60.2], ["1384", "I7N3G6G", "1384", 0, "no description", 60.2], ["1385", "I7N3G6G", "1385", 0, "no description", 60.2], ["1386", "I7N3G6G", "1386", 0, "no description", 60.2], ["1387", "I7N3G6G", "1387", 0, "no description", 60.2], ["1388", "I7N3G6G", "1388", 0, "no description", 60.2], ["1389", "I7N3G6G", "1389", 0, "no description", 60.2], ["1390", "I7N3G6G", "1390", 0, "no description", 60.2], ["1391", "I7N3G6G", "1391", 0, "no description", 60.2], ["1392", "I7N3G6G", "1392", 0, "no description", 60.2], ["1393", "I7N3G6G", "1393", 0, "no description", 60.2], ["1394", "I7N3G6G", "1394", 0, "no description", 60.2], ["1395", "I7N3G6G", "1395", 0, "no description", 60.2], ["1396", "I7N3G6G", "1396", 0, "no description", 60.2], ["1397", "I7N3G6G", "1397", 0, "no description", 60.2], ["1398", "I7N3G6G", "1398", 0, "no description", 60.2], ["1399", "I7N3G6G", "1399", 0, "no description", 60.2], ["1400", "I7N3G6G", "1400", 0, "no description", 60.2], ["1401", "I7N3G6G", "1401", 0, "no description", 60.2], ["1402", "I7N3G6G", "1402", 0, "no description", 60.2], ["1403", "I7N3G6G", "1403", 0, "no description", 60.2], ["1404", "I7N3G6G", "1404", 0, "no description", 60.2], ["1405", "I7N3G6G", "1405", 0, "no description", 60.2], ["1406", "I7N3G6G", "1406", 0, "no description", 60.2], ["1407", "I7N3G6G", "1407", 0, "no description", 60.2], ["1408", "I7N3G6G", "1408", 0, "no description", 60.2], ["1409", "I7N3G6G", "1409", 0, "no description", 60.2], ["1410", "I7N3G6G", "1410", 0, "no description", 60.2], ["1411", "I7N3G6G", "1411", 0, "no description", 60.2], ["1412", "I7N3G6G", "1412", 0, "no description", 60.2], ["1413", "I7N3G6G", "1413", 0, "no description", 60.2], ["1414", "I7N3G6G", "1414", 0, "no description", 60.2], ["1415", "I7N3G6G", "1415", 0, "no description", 60.2], ["1416", "I7N3G6G", "1416", 0, "no description", 60.2], ["1417", "I7N3G6G", "1417", 0, "no description", 60.2], ["1418", "I7N3G6G", "1418", 0, "no description", 60.2], ["1419", "I7N3G6G", "1419", 0, "no description", 60.2], ["1420", "I7N3G6G", "1420", 0, "no description", 60.2], ["1421", "I7N3G6G", "1421", 0, "no description", 60.2], ["1422", "I7N3G6G", "1422", 0, "no description", 60.2], ["1423", "I7N3G6G", "1423", 0, "no description", 60.2], ["1424", "I7N3G6G", "1424", 0, "no description", 60.2], ["1425", "I7N3G6G", "1425", 0, "no description", 60.2], ["1426", "I7N3G6G", "1426", 0, "no description", 60.2], ["1427", "I7N3G6G", "1427", 0, "no description", 60.2], ["1428", "I7N3G6G", "1428", 0, "no description", 60.2], ["1429", "I7N3G6G", "1429", 0, "no description", 60.2], ["1430", "I7N3G6G", "1430", 0, "no description", 60.2], ["1431", "I7N3G6G", "1431", 0, "no description", 60.2], ["1432", "I7N3G6G", "1432", 0, "no description", 60.2], ["1433", "I7N3G6G", "1433", 0, "no description", 60.2], ["1434", "I7N3G6G", "1434", 0, "no description", 60.2], ["1435", "I7N3G6G", "1435", 0, "no description", 60.2], ["1436", "I7N3G6G", "1436", 0, "no description", 60.2], ["1437", "I7N3G6G", "1437", 0, "no description", 60.2], ["1438", "I7N3G6G", "1438", 0, "no description", 60.2], ["1439", "I7N3G6G", "1439", 0, "no description", 60.2], ["1440", "I7N3G6G", "1440", 0, "no description", 60.2], ["1441", "I7N3G6G", "1441", 0, "no description", 60.2], ["1442", "I7N3G6G", "1442", 0, "no description", 60.2], ["1443", "I7N3G6G", "1443", 0, "no description", 60.2], ["1444", "I7N3G6G", "1444", 0, "no description", 60.2], ["1445", "I7N3G6G", "1445", 0, "no description", 60.2], ["1446", "I7N3G6G", "1446", 0, "no description", 60.2], ["1447", "I7N3G6G", "1447", 0, "no description", 60.2], ["1448", "I7N3G6G", "1448", 0, "no description", 60.2], ["1449", "I7N3G6G", "1449", 0, "no description", 60.2], ["1450", "I7N3G6G", "1450", 0, "no description", 60.2], ["1451", "I7N3G6G", "1451", 0, "no description", 60.2], ["1452", "I7N3G6G", "1452", 0, "no description", 60.2], ["1453", "I7N3G6G", "1453", 0, "no description", 60.2], ["1454", "I7N3G6G", "1454", 0, "no description", 60.2], ["1455", "I7N3G6G", "1455", 0, "no description", 60.2], ["1456", "I7N3G6G", "1456", 0, "no description", 60.2], ["1457", "I7N3G6G", "1457", 0, "no description", 60.2], ["1458", "I7N3G6G", "1458", 0, "no description", 60.2], ["1459", "I7N3G6G", "1459", 0, "no description", 60.2], ["1460", "I7N3G6G", "1460", 0, "no description", 60.2], ["1461", "I7N3G6G", "1461", 0, "no description", 60.2], ["1462", "I7N3G6G", "1462", 0, "no description", 60.2], ["1463", "I7N3G6G", "1463", 0, "no description", 60.2], ["1464", "I7N3G6G", "1464", 0, "no description", 60.2], ["1465", "I7N3G6G", "1465", 0, "no description", 60.2], ["1466", "I7N3G6G", "1466", 0, "no description", 60.2], ["1467", "I7N3G6G", "1467", 0, "no description", 60.2], ["1468", "I7N3G6G", "1468", 0, "no description", 60.2], ["1469", "I7N3G6G", "1469", 0, "no description", 60.2], ["1470", "I7N3G6G", "1470", 0, "no description", 60.2], ["1471", "I7N3G6G", "1471", 0, "no description", 60.2], ["1472", "I7N3G6G", "1472", 0, "no description", 60.2], ["1473", "I7N3G6G", "1473", 0, "no description", 60.2], ["1474", "I7N3G6G", "1474", 0, "no description", 60.2], ["1475", "I7N3G6G", "1475", 0, "no description", 60.2], ["1476", "I7N3G6G", "1476", 0, "no description", 60.2], ["1477", "I7N3G6G", "1477", 0, "no description", 60.2], ["1478", "I7N3G6G", "1478", 0, "no description", 60.2], ["1479", "I7N3G6G", "1479", 0, "no description", 60.2], ["1480", "I7N3G6G", "1480", 0, "no description", 60.2], ["1481", "I7N3G6G", "1481", 0, "no description", 60.2], ["1482", "I7N3G6G", "1482", 0, "no description", 60.2], ["1483", "I7N3G6G", "1483", 0, "no description", 60.2], ["1484", "I7N3G6G", "1484", 0, "no description", 60.2], ["1485", "I7N3G6G", "1485", 0, "no description", 60.2], ["1486", "I7N3G6G", "1486", 0, "no description", 60.2], ["1487", "I7N3G6G", "1487", 0, "no description", 60.2], ["1488", "I7N3G6G", "1488", 0, "no description", 60.2], ["1489", "I7N3G6G", "1489", 0, "no description", 60.2], ["1490", "I7N3G6G", "1490", 0, "no description", 60.2], ["1491", "I7N3G6G", "1491", 0, "no description", 60.2], ["1492", "I7N3G6G", "1492", 0, "no description", 60.2], ["1493", "I7N3G6G", "1493", 0, "no description", 60.2], ["1494", "I7N3G6G", "1494", 0, "no description", 60.2], ["1495", "I7N3G6G", "1495", 0, "no description", 60.2], ["1496", "I7N3G6G", "1496", 0, "no description", 60.2], ["1497", "I7N3G6G", "1497", 0, "no description", 60.2], ["1498", "I7N3G6G", "1498", 0, "no description", 60.2], ["1499", "I7N3G6G", "1499", 0, "no description", 60.2], ["1500", "I7N3G6G", "1500", 0, "no description", 60.2], ["1501", "I7N3G6G", "1501", 0, "no description", 60.2], ["1502", "I7N3G6G", "1502", 0, "no description", 60.2], ["1503", "I7N3G6G", "1503", 0, "no description", 60.2], ["1504", "I7N3G6G", "1504", 0, "no description", 60.2], ["1505", "I7N3G6G", "1505", 0, "no description", 60.2], ["1506", "I7N3G6G", "1506", 0, "no description", 60.2], ["1507", "I7N3G6G", "1507", 0, "no description", 60.2], ["1508", "I7N3G6G", "1508", 0, "no description", 60.2], ["1509", "I7N3G6G", "1509", 0, "no description", 60.2], ["1510", "I7N3G6G", "1510", 0, "no description", 60.2], ["1511", "I7N3G6G", "1511", 0, "no description", 60.2], ["1512", "I7N3G6G", "1512", 0, "no description", 60.2], ["1513", "I7N3G6G", "1513", 0, "no description", 60.2], ["1514", "I7N3G6G", "1514", 0, "no description", 60.2], ["1515", "I7N3G6G", "1515", 0, "no description", 60.2], ["1516", "I7N3G6G", "1516", 0, "no description", 60.2], ["1517", "I7N3G6G", "1517", 0, "no description", 60.2], ["1518", "I7N3G6G", "1518", 0, "no description", 60.2], ["1519", "I7N3G6G", "1519", 0, "no description", 60.2], ["1520", "I7N3G6G", "1520", 0, "no description", 60.2], ["1521", "I7N3G6G", "1521", 0, "no description", 60.2], ["1522", "I7N3G6G", "1522", 0, "no description", 60.2], ["1523", "I7N3G6G", "1523", 0, "no description", 60.2], ["1524", "I7N3G6G", "1524", 0, "no description", 60.2], ["1525", "I7N3G6G", "1525", 0, "no description", 60.2], ["1526", "I7N3G6G", "1526", 0, "no description", 60.2], ["1527", "I7N3G6G", "1527", 0, "no description", 60.2], ["1528", "I7N3G6G", "1528", 0, "no description", 60.2], ["1529", "I7N3G6G", "1529", 0, "no description", 60.2], ["1530", "I7N3G6G", "1530", 0, "no description", 60.2], ["1531", "I7N3G6G", "1531", 0, "no description", 60.2], ["1532", "I7N3G6G", "1532", 0, "no description", 60.2], ["1533", "I7N3G6G", "1533", 0, "no description", 60.2], ["1534", "I7N3G6G", "1534", 0, "no description", 60.2], ["1535", "I7N3G6G", "1535", 0, "no description", 60.2], ["1536", "I7N3G6G", "1536", 0, "no description", 60.2], ["1537", "I7N3G6G", "1537", 0, "no description", 60.2], ["1538", "I7N3G6G", "1538", 0, "no description", 60.2], ["1539", "I7N3G6G", "1539", 0, "no description", 60.2], ["1540", "I7N3G6G", "1540", 0, "no description", 60.2], ["1541", "I7N3G6G", "1541", 0, "no description", 60.2], ["1542", "I7N3G6G", "1542", 0, "no description", 60.2], ["1543", "I7N3G6G", "1543", 0, "no description", 60.2], ["1544", "I7N3G6G", "1544", 0, "no description", 60.2], ["1545", "I7N3G6G", "1545", 0, "no description", 60.2], ["1546", "I7N3G6G", "1546", 0, "no description", 60.2], ["1547", "I7N3G6G", "1547", 0, "no description", 60.2], ["1548", "I7N3G6G", "1548", 0, "no description", 60.2], ["1549", "I7N3G6G", "1549", 0, "no description", 60.2], ["1550", "I7N3G6G", "1550", 0, "no description", 60.2], ["1551", "I7N3G6G", "1551", 0, "no description", 60.2], ["1552", "I7N3G6G", "1552", 0, "no description", 60.2], ["1553", "I7N3G6G", "1553", 0, "no description", 60.2], ["1554", "I7N3G6G", "1554", 0, "no description", 60.2], ["1555", "I7N3G6G", "1555", 0, "no description", 60.2], ["1556", "I7N3G6G", "1556", 0, "no description", 60.2], ["1557", "I7N3G6G", "1557", 0, "no description", 60.2], ["1558", "I7N3G6G", "1558", 0, "no description", 60.2], ["1559", "I7N3G6G", "1559", 0, "no description", 60.2], ["1560", "I7N3G6G", "1560", 0, "no description", 60.2], ["1561", "I7N3G6G", "1561", 0, "no description", 60.2], ["1562", "I7N3G6G", "1562", 0, "no description", 60.2], ["1563", "I7N3G6G", "1563", 0, "no description", 60.2], ["1564", "I7N3G6G", "1564", 0, "no description", 60.2], ["1565", "I7N3G6G", "1565", 0, "no description", 60.2], ["1566", "I7N3G6G", "1566", 0, "no description", 60.2], ["1567", "I7N3G6G", "1567", 0, "no description", 60.2], ["1568", "I7N3G6G", "1568", 0, "no description", 60.2], ["1569", "I7N3G6G", "1569", 0, "no description", 60.2], ["1570", "I7N3G6G", "1570", 0, "no description", 60.2], ["1571", "I7N3G6G", "1571", 0, "no description", 60.2], ["1572", "I7N3G6G", "1572", 0, "no description", 60.2], ["1573", "I7N3G6G", "1573", 0, "no description", 60.2], ["1574", "I7N3G6G", "1574", 0, "no description", 60.2], ["1575", "I7N3G6G", "1575", 0, "no description", 60.2], ["1576", "I7N3G6G", "1576", 0, "no description", 60.2], ["1577", "I7N3G6G", "1577", 0, "no description", 60.2], ["1578", "I7N3G6G", "1578", 0, "no description", 60.2], ["1579", "I7N3G6G", "1579", 0, "no description", 60.2], ["1580", "I7N3G6G", "1580", 0, "no description", 60.2], ["1581", "I7N3G6G", "1581", 0, "no description", 60.2], ["1582", "I7N3G6G", "1582", 0, "no description", 60.2], ["1583", "I7N3G6G", "1583", 0, "no description", 60.2], ["1584", "I7N3G6G", "1584", 0, "no description", 60.2], ["1585", "I7N3G6G", "1585", 0, "no description", 60.2], ["1586", "I7N3G6G", "1586", 0, "no description", 60.2], ["1587", "I7N3G6G", "1587", 0, "no description", 60.2], ["1588", "I7N3G6G", "1588", 0, "no description", 60.2], ["1589", "I7N3G6G", "1589", 0, "no description", 60.2], ["1590", "I7N3G6G", "1590", 0, "no description", 60.2], ["1591", "I7N3G6G", "1591", 0, "no description", 60.2], ["1592", "I7N3G6G", "1592", 0, "no description", 60.2], ["1593", "I7N3G6G", "1593", 0, "no description", 60.2], ["1594", "I7N3G6G", "1594", 0, "no description", 60.2], ["1595", "I7N3G6G", "1595", 0, "no description", 60.2], ["1596", "I7N3G6G", "1596", 0, "no description", 60.2], ["1597", "I7N3G6G", "1597", 0, "no description", 60.2], ["1598", "I7N3G6G", "1598", 0, "no description", 60.2], ["1599", "I7N3G6G", "1599", 0, "no description", 60.2], ["1600", "I7N3G6G", "1600", 0, "no description", 60.2], ["1601", "I7N3G6G", "1601", 0, "no description", 60.2], ["1602", "I7N3G6G", "1602", 0, "no description", 60.2], ["1603", "I7N3G6G", "1603", 0, "no description", 60.2], ["1604", "I7N3G6G", "1604", 0, "no description", 60.2], ["1605", "I7N3G6G", "1605", 0, "no description", 60.2], ["1606", "I7N3G6G", "1606", 0, "no description", 60.2], ["1607", "I7N3G6G", "1607", 0, "no description", 60.2], ["1608", "I7N3G6G", "1608", 0, "no description", 60.2], ["1609", "I7N3G6G", "1609", 0, "no description", 60.2], ["1610", "I7N3G6G", "1610", 0, "no description", 60.2], ["1611", "I7N3G6G", "1611", 0, "no description", 60.2], ["1612", "I7N3G6G", "1612", 0, "no description", 60.2], ["1613", "I7N3G6G", "1613", 0, "no description", 60.2], ["1614", "I7N3G6G", "1614", 0, "no description", 60.2], ["1615", "I7N3G6G", "1615", 0, "no description", 60.2], ["1616", "I7N3G6G", "1616", 0, "no description", 60.2], ["1617", "I7N3G6G", "1617", 0, "no description", 60.2], ["1618", "I7N3G6G", "1618", 0, "no description", 60.2], ["1619", "I7N3G6G", "1619", 0, "no description", 60.2], ["1620", "I7N3G6G", "1620", 0, "no description", 60.2], ["1621", "I7N3G6G", "1621", 0, "no description", 60.2], ["1622", "I7N3G6G", "1622", 0, "no description", 60.2], ["1623", "I7N3G6G", "1623", 0, "no description", 60.2], ["1624", "I7N3G6G", "1624", 0, "no description", 60.2], ["1625", "I7N3G6G", "1625", 0, "no description", 60.2], ["1626", "I7N3G6G", "1626", 0, "no description", 60.2], ["1627", "I7N3G6G", "1627", 0, "no description", 60.2], ["1628", "I7N3G6G", "1628", 0, "no description", 60.2], ["1629", "I7N3G6G", "1629", 0, "no description", 60.2], ["1630", "I7N3G6G", "1630", 0, "no description", 60.2], ["1631", "I7N3G6G", "1631", 0, "no description", 60.2], ["1632", "I7N3G6G", "1632", 0, "no description", 60.2], ["1633", "I7N3G6G", "1633", 0, "no description", 60.2], ["1634", "I7N3G6G", "1634", 0, "no description", 60.2], ["1635", "I7N3G6G", "1635", 0, "no description", 60.2], ["1636", "I7N3G6G", "1636", 0, "no description", 60.2], ["1637", "I7N3G6G", "1637", 0, "no description", 60.2], ["1638", "I7N3G6G", "1638", 0, "no description", 60.2], ["1639", "I7N3G6G", "1639", 0, "no description", 60.2], ["1640", "I7N3G6G", "1640", 0, "no description", 60.2], ["1641", "I7N3G6G", "1641", 0, "no description", 60.2], ["1642", "I7N3G6G", "1642", 0, "no description", 60.2], ["1643", "I7N3G6G", "1643", 0, "no description", 60.2], ["1644", "I7N3G6G", "1644", 0, "no description", 60.2], ["1645", "I7N3G6G", "1645", 0, "no description", 60.2], ["1646", "I7N3G6G", "1646", 0, "no description", 60.2], ["1647", "I7N3G6G", "1647", 0, "no description", 60.2], ["1648", "I7N3G6G", "1648", 0, "no description", 60.2], ["1649", "I7N3G6G", "1649", 0, "no description", 60.2], ["1650", "I7N3G6G", "1650", 0, "no description", 60.2], ["1651", "I7N3G6G", "1651", 0, "no description", 60.2], ["1652", "I7N3G6G", "1652", 0, "no description", 60.2], ["1653", "I7N3G6G", "1653", 0, "no description", 60.2], ["1654", "I7N3G6G", "1654", 0, "no description", 60.2], ["1655", "I7N3G6G", "1655", 0, "no description", 60.2], ["1656", "I7N3G6G", "1656", 0, "no description", 60.2], ["1657", "I7N3G6G", "1657", 0, "no description", 60.2], ["1658", "I7N3G6G", "1658", 0, "no description", 60.2], ["1659", "I7N3G6G", "1659", 0, "no description", 60.2], ["1660", "I7N3G6G", "1660", 0, "no description", 60.2], ["1661", "I7N3G6G", "1661", 0, "no description", 60.2], ["1662", "I7N3G6G", "1662", 0, "no description", 60.2], ["1663", "I7N3G6G", "1663", 0, "no description", 60.2], ["1664", "I7N3G6G", "1664", 0, "no description", 60.2], ["1665", "I7N3G6G", "1665", 0, "no description", 60.2], ["1666", "I7N3G6G", "1666", 0, "no description", 60.2], ["1667", "I7N3G6G", "1667", 0, "no description", 60.2], ["1668", "I7N3G6G", "1668", 0, "no description", 60.2], ["1669", "I7N3G6G", "1669", 0, "no description", 60.2], ["1670", "I7N3G6G", "1670", 0, "no description", 60.2], ["1671", "I7N3G6G", "1671", 0, "no description", 60.2], ["1672", "I7N3G6G", "1672", 0, "no description", 60.2], ["1673", "I7N3G6G", "1673", 0, "no description", 60.2], ["1674", "I7N3G6G", "1674", 0, "no description", 60.2], ["1675", "I7N3G6G", "1675", 0, "no description", 60.2], ["1676", "I7N3G6G", "1676", 0, "no description", 60.2], ["1677", "I7N3G6G", "1677", 0, "no description", 60.2], ["1678", "I7N3G6G", "1678", 0, "no description", 60.2], ["1679", "I7N3G6G", "1679", 0, "no description", 60.2], ["1680", "I7N3G6G", "1680", 0, "no description", 60.2], ["1681", "I7N3G6G", "1681", 0, "no description", 60.2], ["1682", "I7N3G6G", "1682", 0, "no description", 60.2], ["1683", "I7N3G6G", "1683", 0, "no description", 60.2], ["1684", "I7N3G6G", "1684", 0, "no description", 60.2], ["1685", "I7N3G6G", "1685", 0, "no description", 60.2], ["1686", "I7N3G6G", "1686", 0, "no description", 60.2], ["1687", "I7N3G6G", "1687", 0, "no description", 60.2], ["1688", "I7N3G6G", "1688", 0, "no description", 60.2], ["1689", "I7N3G6G", "1689", 0, "no description", 60.2], ["1690", "I7N3G6G", "1690", 0, "no description", 60.2], ["1691", "I7N3G6G", "1691", 0, "no description", 60.2], ["1692", "I7N3G6G", "1692", 0, "no description", 60.2], ["1693", "I7N3G6G", "1693", 0, "no description", 60.2], ["1694", "I7N3G6G", "1694", 0, "no description", 60.2], ["1695", "I7N3G6G", "1695", 0, "no description", 60.2], ["1696", "I7N3G6G", "1696", 0, "no description", 60.2], ["1697", "I7N3G6G", "1697", 0, "no description", 60.2], ["1698", "I7N3G6G", "1698", 0, "no description", 60.2], ["1699", "I7N3G6G", "1699", 0, "no description", 60.2], ["1700", "I7N3G6G", "1700", 0, "no description", 60.2], ["1701", "I7N3G6G", "1701", 0, "no description", 60.2], ["1702", "I7N3G6G", "1702", 0, "no description", 60.2], ["1703", "I7N3G6G", "1703", 0, "no description", 60.2], ["1704", "I7N3G6G", "1704", 0, "no description", 60.2], ["1705", "I7N3G6G", "1705", 0, "no description", 60.2], ["1706", "I7N3G6G", "1706", 0, "no description", 60.2], ["1707", "I7N3G6G", "1707", 0, "no description", 60.2], ["1708", "I7N3G6G", "1708", 0, "no description", 60.2], ["1709", "I7N3G6G", "1709", 0, "no description", 60.2], ["1710", "I7N3G6G", "1710", 0, "no description", 60.2], ["1711", "I7N3G6G", "1711", 0, "no description", 60.2], ["1712", "I7N3G6G", "1712", 0, "no description", 60.2], ["1713", "I7N3G6G", "1713", 0, "no description", 60.2], ["1714", "I7N3G6G", "1714", 0, "no description", 60.2], ["1715", "I7N3G6G", "1715", 0, "no description", 60.2], ["1716", "I7N3G6G", "1716", 0, "no description", 60.2], ["1717", "I7N3G6G", "1717", 0, "no description", 60.2], ["1718", "I7N3G6G", "1718", 0, "no description", 60.2], ["1719", "I7N3G6G", "1719", 0, "no description", 60.2], ["1720", "I7N3G6G", "1720", 0, "no description", 60.2], ["1721", "I7N3G6G", "1721", 0, "no description", 60.2], ["1722", "I7N3G6G", "1722", 0, "no description", 60.2], ["1723", "I7N3G6G", "1723", 0, "no description", 60.2], ["1724", "I7N3G6G", "1724", 0, "no description", 60.2], ["1725", "I7N3G6G", "1725", 0, "no description", 60.2], ["1726", "I7N3G6G", "1726", 0, "no description", 60.2], ["1727", "I7N3G6G", "1727", 0, "no description", 60.2], ["1728", "I7N3G6G", "1728", 0, "no description", 60.2], ["1729", "I7N3G6G", "1729", 0, "no description", 60.2], ["1730", "I7N3G6G", "1730", 0, "no description", 60.2], ["1731", "I7N3G6G", "1731", 0, "no description", 60.2], ["1732", "I7N3G6G", "1732", 0, "no description", 60.2], ["1733", "I7N3G6G", "1733", 0, "no description", 60.2], ["1734", "I7N3G6G", "1734", 0, "no description", 60.2], ["1735", "I7N3G6G", "1735", 0, "no description", 60.2], ["1736", "I7N3G6G", "1736", 0, "no description", 60.2], ["1737", "I7N3G6G", "1737", 0, "no description", 60.2], ["1738", "I7N3G6G", "1738", 0, "no description", 60.2], ["1739", "I7N3G6G", "1739", 0, "no description", 60.2], ["1740", "I7N3G6G", "1740", 0, "no description", 60.2], ["1741", "I7N3G6G", "1741", 0, "no description", 60.2], ["1742", "I7N3G6G", "1742", 0, "no description", 60.2], ["1743", "I7N3G6G", "1743", 0, "no description", 60.2], ["1744", "I7N3G6G", "1744", 0, "no description", 60.2], ["1745", "I7N3G6G", "1745", 0, "no description", 60.2], ["1746", "I7N3G6G", "1746", 0, "no description", 60.2], ["1747", "I7N3G6G", "1747", 0, "no description", 60.2], ["1748", "I7N3G6G", "1748", 0, "no description", 60.2], ["1749", "I7N3G6G", "1749", 0, "no description", 60.2], ["1750", "I7N3G6G", "1750", 0, "no description", 60.2], ["1751", "I7N3G6G", "1751", 0, "no description", 60.2], ["1752", "I7N3G6G", "1752", 0, "no description", 60.2], ["1753", "I7N3G6G", "1753", 0, "no description", 60.2], ["1754", "I7N3G6G", "1754", 0, "no description", 60.2], ["1755", "I7N3G6G", "1755", 0, "no description", 60.2], ["1756", "I7N3G6G", "1756", 0, "no description", 60.2], ["1757", "I7N3G6G", "1757", 0, "no description", 60.2], ["1758", "I7N3G6G", "1758", 0, "no description", 60.2], ["1759", "I7N3G6G", "1759", 0, "no description", 60.2], ["1760", "I7N3G6G", "1760", 0, "no description", 60.2], ["1761", "I7N3G6G", "1761", 0, "no description", 60.2], ["1762", "I7N3G6G", "1762", 0, "no description", 60.2], ["1763", "I7N3G6G", "1763", 0, "no description", 60.2], ["1764", "I7N3G6G", "1764", 0, "no description", 60.2], ["1765", "I7N3G6G", "1765", 0, "no description", 60.2], ["1766", "I7N3G6G", "1766", 0, "no description", 60.2], ["1767", "I7N3G6G", "1767", 0, "no description", 60.2], ["1768", "I7N3G6G", "1768", 0, "no description", 60.2], ["1769", "I7N3G6G", "1769", 0, "no description", 60.2], ["1770", "I7N3G6G", "1770", 0, "no description", 60.2], ["1771", "I7N3G6G", "1771", 0, "no description", 60.2], ["1772", "I7N3G6G", "1772", 0, "no description", 60.2], ["1773", "I7N3G6G", "1773", 0, "no description", 60.2], ["1774", "I7N3G6G", "1774", 0, "no description", 60.2], ["1775", "I7N3G6G", "1775", 0, "no description", 60.2], ["1776", "I7N3G6G", "1776", 0, "no description", 60.2], ["1777", "I7N3G6G", "1777", 0, "no description", 60.2], ["1778", "I7N3G6G", "1778", 0, "no description", 60.2], ["1779", "I7N3G6G", "1779", 0, "no description", 60.2], ["1780", "I7N3G6G", "1780", 0, "no description", 60.2], ["1781", "I7N3G6G", "1781", 0, "no description", 60.2], ["1782", "I7N3G6G", "1782", 0, "no description", 60.2], ["1783", "I7N3G6G", "1783", 0, "no description", 60.2], ["1784", "I7N3G6G", "1784", 0, "no description", 60.2], ["1785", "I7N3G6G", "1785", 0, "no description", 60.2], ["1786", "I7N3G6G", "1786", 0, "no description", 60.2], ["1787", "I7N3G6G", "1787", 0, "no description", 60.2], ["1788", "I7N3G6G", "1788", 0, "no description", 60.2], ["1789", "I7N3G6G", "1789", 0, "no description", 60.2], ["1790", "I7N3G6G", "1790", 0, "no description", 60.2], ["1791", "I7N3G6G", "1791", 0, "no description", 60.2], ["1792", "I7N3G6G", "1792", 0, "no description", 60.2], ["1793", "I7N3G6G", "1793", 0, "no description", 60.2], ["1794", "I7N3G6G", "1794", 0, "no description", 60.2], ["1795", "I7N3G6G", "1795", 0, "no description", 60.2], ["1796", "I7N3G6G", "1796", 0, "no description", 60.2], ["1797", "I7N3G6G", "1797", 0, "no description", 60.2], ["1798", "I7N3G6G", "1798", 0, "no description", 60.2], ["1799", "I7N3G6G", "1799", 0, "no description", 60.2], ["1800", "I7N3G6G", "1800", 0, "no description", 60.2], ["1801", "I7N3G6G", "1801", 0, "no description", 60.2], ["1802", "I7N3G6G", "1802", 0, "no description", 60.2], ["1803", "I7N3G6G", "1803", 0, "no description", 60.2], ["1804", "I7N3G6G", "1804", 0, "no description", 60.2], ["1805", "I7N3G6G", "1805", 0, "no description", 60.2], ["1806", "I7N3G6G", "1806", 0, "no description", 60.2], ["1807", "I7N3G6G", "1807", 0, "no description", 60.2], ["1808", "I7N3G6G", "1808", 0, "no description", 60.2], ["1809", "I7N3G6G", "1809", 0, "no description", 60.2], ["1810", "I7N3G6G", "1810", 0, "no description", 60.2], ["1811", "I7N3G6G", "1811", 0, "no description", 60.2], ["1812", "I7N3G6G", "1812", 0, "no description", 60.2], ["1813", "I7N3G6G", "1813", 0, "no description", 60.2], ["1814", "I7N3G6G", "1814", 0, "no description", 60.2], ["1815", "I7N3G6G", "1815", 0, "no description", 60.2], ["1816", "I7N3G6G", "1816", 0, "no description", 60.2], ["1817", "I7N3G6G", "1817", 0, "no description", 60.2], ["1818", "I7N3G6G", "1818", 0, "no description", 60.2], ["1819", "I7N3G6G", "1819", 0, "no description", 60.2], ["1820", "I7N3G6G", "1820", 0, "no description", 60.2], ["1821", "I7N3G6G", "1821", 0, "no description", 60.2], ["1822", "I7N3G6G", "1822", 0, "no description", 60.2], ["1823", "I7N3G6G", "1823", 0, "no description", 60.2], ["1824", "I7N3G6G", "1824", 0, "no description", 60.2], ["1825", "I7N3G6G", "1825", 0, "no description", 60.2], ["1826", "I7N3G6G", "1826", 0, "no description", 60.2], ["1827", "I7N3G6G", "1827", 0, "no description", 60.2], ["1828", "I7N3G6G", "1828", 0, "no description", 60.2], ["1829", "I7N3G6G", "1829", 0, "no description", 60.2], ["1830", "I7N3G6G", "1830", 0, "no description", 60.2], ["1831", "I7N3G6G", "1831", 0, "no description", 60.2], ["1832", "I7N3G6G", "1832", 0, "no description", 60.2], ["1833", "I7N3G6G", "1833", 0, "no description", 60.2], ["1834", "I7N3G6G", "1834", 0, "no description", 60.2], ["1835", "I7N3G6G", "1835", 0, "no description", 60.2], ["1836", "I7N3G6G", "1836", 0, "no description", 60.2], ["1837", "I7N3G6G", "1837", 0, "no description", 60.2], ["1838", "I7N3G6G", "1838", 0, "no description", 60.2], ["1839", "I7N3G6G", "1839", 0, "no description", 60.2], ["1840", "I7N3G6G", "1840", 0, "no description", 60.2], ["1841", "I7N3G6G", "1841", 0, "no description", 60.2], ["1842", "I7N3G6G", "1842", 0, "no description", 60.2], ["1843", "I7N3G6G", "1843", 0, "no description", 60.2], ["1844", "I7N3G6G", "1844", 0, "no description", 60.2], ["1845", "I7N3G6G", "1845", 0, "no description", 60.2], ["1846", "I7N3G6G", "1846", 0, "no description", 60.2], ["1847", "I7N3G6G", "1847", 0, "no description", 60.2], ["1848", "I7N3G6G", "1848", 0, "no description", 60.2], ["1849", "I7N3G6G", "1849", 0, "no description", 60.2], ["1850", "I7N3G6G", "1850", 0, "no description", 60.2], ["1851", "I7N3G6G", "1851", 0, "no description", 60.2], ["1852", "I7N3G6G", "1852", 0, "no description", 60.2], ["1853", "I7N3G6G", "1853", 0, "no description", 60.2], ["1854", "I7N3G6G", "1854", 0, "no description", 60.2], ["1855", "I7N3G6G", "1855", 0, "no description", 60.2], ["1856", "I7N3G6G", "1856", 0, "no description", 60.2], ["1857", "I7N3G6G", "1857", 0, "no description", 60.2], ["1858", "I7N3G6G", "1858", 0, "no description", 60.2], ["1859", "I7N3G6G", "1859", 0, "no description", 60.2], ["1860", "I7N3G6G", "1860", 0, "no description", 60.2], ["1861", "I7N3G6G", "1861", 0, "no description", 60.2], ["1862", "I7N3G6G", "1862", 0, "no description", 60.2], ["1863", "I7N3G6G", "1863", 0, "no description", 60.2], ["1864", "I7N3G6G", "1864", 0, "no description", 60.2], ["1865", "I7N3G6G", "1865", 0, "no description", 60.2], ["1866", "I7N3G6G", "1866", 0, "no description", 60.2], ["1867", "I7N3G6G", "1867", 0, "no description", 60.2], ["1868", "I7N3G6G", "1868", 0, "no description", 60.2], ["1869", "I7N3G6G", "1869", 0, "no description", 60.2], ["1870", "I7N3G6G", "1870", 0, "no description", 60.2], ["1871", "I7N3G6G", "1871", 0, "no description", 60.2], ["1872", "I7N3G6G", "1872", 0, "no description", 60.2], ["1873", "I7N3G6G", "1873", 0, "no description", 60.2], ["1874", "I7N3G6G", "1874", 0, "no description", 60.2], ["1875", "I7N3G6G", "1875", 0, "no description", 60.2], ["1876", "I7N3G6G", "1876", 0, "no description", 60.2], ["1877", "I7N3G6G", "1877", 0, "no description", 60.2], ["1878", "I7N3G6G", "1878", 0, "no description", 60.2], ["1879", "I7N3G6G", "1879", 0, "no description", 60.2], ["1880", "I7N3G6G", "1880", 0, "no description", 60.2], ["1881", "I7N3G6G", "1881", 0, "no description", 60.2], ["1882", "I7N3G6G", "1882", 0, "no description", 60.2], ["1883", "I7N3G6G", "1883", 0, "no description", 60.2], ["1884", "I7N3G6G", "1884", 0, "no description", 60.2], ["1885", "I7N3G6G", "1885", 0, "no description", 60.2], ["1886", "I7N3G6G", "1886", 0, "no description", 60.2], ["1887", "I7N3G6G", "1887", 0, "no description", 60.2], ["1888", "I7N3G6G", "1888", 0, "no description", 60.2], ["1889", "I7N3G6G", "1889", 0, "no description", 60.2], ["1890", "I7N3G6G", "1890", 0, "no description", 60.2], ["1891", "I7N3G6G", "1891", 0, "no description", 60.2], ["1892", "I7N3G6G", "1892", 0, "no description", 60.2], ["1893", "I7N3G6G", "1893", 0, "no description", 60.2], ["1894", "I7N3G6G", "1894", 0, "no description", 60.2], ["1895", "I7N3G6G", "1895", 0, "no description", 60.2], ["1896", "I7N3G6G", "1896", 0, "no description", 60.2], ["1897", "I7N3G6G", "1897", 0, "no description", 60.2], ["1898", "I7N3G6G", "1898", 0, "no description", 60.2], ["1899", "I7N3G6G", "1899", 0, "no description", 60.2], ["1900", "I7N3G6G", "1900", 0, "no description", 60.2], ["1901", "I7N3G6G", "1901", 0, "no description", 60.2], ["1902", "I7N3G6G", "1902", 0, "no description", 60.2], ["1903", "I7N3G6G", "1903", 0, "no description", 60.2], ["1904", "I7N3G6G", "1904", 0, "no description", 60.2], ["1905", "I7N3G6G", "1905", 0, "no description", 60.2], ["1906", "I7N3G6G", "1906", 0, "no description", 60.2], ["1907", "I7N3G6G", "1907", 0, "no description", 60.2], ["1908", "I7N3G6G", "1908", 0, "no description", 60.2], ["1909", "I7N3G6G", "1909", 0, "no description", 60.2], ["1910", "I7N3G6G", "1910", 0, "no description", 60.2], ["1911", "I7N3G6G", "1911", 0, "no description", 60.2], ["1912", "I7N3G6G", "1912", 0, "no description", 60.2], ["1913", "I7N3G6G", "1913", 0, "no description", 60.2], ["1914", "I7N3G6G", "1914", 0, "no description", 60.2], ["1915", "I7N3G6G", "1915", 0, "no description", 60.2], ["1916", "I7N3G6G", "1916", 0, "no description", 60.2], ["1917", "I7N3G6G", "1917", 0, "no description", 60.2], ["1918", "I7N3G6G", "1918", 0, "no description", 60.2], ["1919", "I7N3G6G", "1919", 0, "no description", 60.2], ["1920", "I7N3G6G", "1920", 0, "no description", 60.2], ["1921", "I7N3G6G", "1921", 0, "no description", 60.2], ["1922", "I7N3G6G", "1922", 0, "no description", 60.2], ["1923", "I7N3G6G", "1923", 0, "no description", 60.2], ["1924", "I7N3G6G", "1924", 0, "no description", 60.2], ["1925", "I7N3G6G", "1925", 0, "no description", 60.2], ["1926", "I7N3G6G", "1926", 0, "no description", 60.2], ["1927", "I7N3G6G", "1927", 0, "no description", 60.2], ["1928", "I7N3G6G", "1928", 0, "no description", 60.2], ["1929", "I7N3G6G", "1929", 0, "no description", 60.2], ["1930", "I7N3G6G", "1930", 0, "no description", 60.2], ["1931", "I7N3G6G", "1931", 0, "no description", 60.2], ["1932", "I7N3G6G", "1932", 0, "no description", 60.2], ["1933", "I7N3G6G", "1933", 0, "no description", 60.2], ["1934", "I7N3G6G", "1934", 0, "no description", 60.2], ["1935", "I7N3G6G", "1935", 0, "no description", 60.2], ["1936", "I7N3G6G", "1936", 0, "no description", 60.2], ["1937", "I7N3G6G", "1937", 0, "no description", 60.2], ["1938", "I7N3G6G", "1938", 0, "no description", 60.2], ["1939", "I7N3G6G", "1939", 0, "no description", 60.2], ["1940", "I7N3G6G", "1940", 0, "no description", 60.2], ["1941", "I7N3G6G", "1941", 0, "no description", 60.2], ["1942", "I7N3G6G", "1942", 0, "no description", 60.2], ["1943", "I7N3G6G", "1943", 0, "no description", 60.2], ["1944", "I7N3G6G", "1944", 0, "no description", 60.2], ["1945", "I7N3G6G", "1945", 0, "no description", 60.2], ["1946", "I7N3G6G", "1946", 0, "no description", 60.2], ["1947", "I7N3G6G", "1947", 0, "no description", 60.2], ["1948", "I7N3G6G", "1948", 0, "no description", 60.2], ["1949", "I7N3G6G", "1949", 0, "no description", 60.2], ["1950", "I7N3G6G", "1950", 0, "no description", 60.2], ["1951", "I7N3G6G", "1951", 0, "no description", 60.2], ["1952", "I7N3G6G", "1952", 0, "no description", 60.2], ["1953", "I7N3G6G", "1953", 0, "no description", 60.2], ["1954", "I7N3G6G", "1954", 0, "no description", 60.2], ["1955", "I7N3G6G", "1955", 0, "no description", 60.2], ["1956", "I7N3G6G", "1956", 0, "no description", 60.2], ["1957", "I7N3G6G", "1957", 0, "no description", 60.2], ["1958", "I7N3G6G", "1958", 0, "no description", 60.2], ["1959", "I7N3G6G", "1959", 0, "no description", 60.2], ["1960", "I7N3G6G", "1960", 0, "no description", 60.2], ["1961", "I7N3G6G", "1961", 0, "no description", 60.2], ["1962", "I7N3G6G", "1962", 0, "no description", 60.2], ["1963", "I7N3G6G", "1963", 0, "no description", 60.2], ["1964", "I7N3G6G", "1964", 0, "no description", 60.2], ["1965", "I7N3G6G", "1965", 0, "no description", 60.2], ["1966", "I7N3G6G", "1966", 0, "no description", 60.2], ["1967", "I7N3G6G", "1967", 0, "no description", 60.2], ["1968", "I7N3G6G", "1968", 0, "no description", 60.2], ["1969", "I7N3G6G", "1969", 0, "no description", 60.2], ["1970", "I7N3G6G", "1970", 0, "no description", 60.2], ["1971", "I7N3G6G", "1971", 0, "no description", 60.2], ["1972", "I7N3G6G", "1972", 0, "no description", 60.2], ["1973", "I7N3G6G", "1973", 0, "no description", 60.2], ["1974", "I7N3G6G", "1974", 0, "no description", 60.2], ["1975", "I7N3G6G", "1975", 0, "no description", 60.2], ["1976", "I7N3G6G", "1976", 0, "no description", 60.2], ["1977", "I7N3G6G", "1977", 0, "no description", 60.2], ["1978", "I7N3G6G", "1978", 0, "no description", 60.2], ["1979", "I7N3G6G", "1979", 0, "no description", 60.2], ["1980", "I7N3G6G", "1980", 0, "no description", 60.2], ["1981", "I7N3G6G", "1981", 0, "no description", 60.2], ["1982", "I7N3G6G", "1982", 0, "no description", 60.2], ["1983", "I7N3G6G", "1983", 0, "no description", 60.2], ["1984", "I7N3G6G", "1984", 0, "no description", 60.2], ["1985", "I7N3G6G", "1985", 0, "no description", 60.2], ["1986", "I7N3G6G", "1986", 0, "no description", 60.2], ["1987", "I7N3G6G", "1987", 0, "no description", 60.2], ["1988", "I7N3G6G", "1988", 0, "no description", 60.2], ["1989", "I7N3G6G", "1989", 0, "no description", 60.2], ["1990", "I7N3G6G", "1990", 0, "no description", 60.2], ["1991", "I7N3G6G", "1991", 0, "no description", 60.2], ["1992", "I7N3G6G", "1992", 0, "no description", 60.2], ["1993", "I7N3G6G", "1993", 0, "no description", 60.2], ["1994", "I7N3G6G", "1994", 0, "no description", 60.2], ["1995", "I7N3G6G", "1995", 0, "no description", 60.2], ["1996", "I7N3G6G", "1996", 0, "no description", 60.2], ["1997", "I7N3G6G", "1997", 0, "no description", 60.2], ["1998", "I7N3G6G", "1998", 0, "no description", 60.2], ["1999", "I7N3G6G", "1999", 0, "no description", 60.2], ["2000", "I7N3G6G", "2000", 0, "no description", 60.2], ["2001", "I7N3G6G", "2001", 0, "no description", 60.2], ["2002", "I7N3G6G", "2002", 0, "no description", 60.2], ["2003", "I7N3G6G", "2003", 0, "no description", 60.2], ["2004", "I7N3G6G", "2004", 0, "no description", 60.2], ["2005", "I7N3G6G", "2005", 0, "no description", 60.2], ["2006", "I7N3G6G", "2006", 0, "no description", 60.2], ["2007", "I7N3G6G", "2007", 0, "no description", 60.2], ["2008", "I7N3G6G", "2008", 0, "no description", 60.2], ["2009", "I7N3G6G", "2009", 0, "no description", 60.2], ["2010", "I7N3G6G", "2010", 0, "no description", 60.2], ["2011", "I7N3G6G", "2011", 0, "no description", 60.2], ["2012", "I7N3G6G", "2012", 0, "no description", 60.2], ["2013", "I7N3G6G", "2013", 0, "no description", 60.2], ["2014", "I7N3G6G", "2014", 0, "no description", 60.2], ["2015", "I7N3G6G", "2015", 0, "no description", 60.2], ["2016", "I7N3G6G", "2016", 0, "no description", 60.2], ["2017", "I7N3G6G", "2017", 0, "no description", 60.2], ["2018", "I7N3G6G", "2018", 0, "no description", 60.2], ["2019", "I7N3G6G", "2019", 0, "no description", 60.2], ["2020", "I7N3G6G", "2020", 0, "no description", 60.2], ["2021", "I7N3G6G", "2021", 0, "no description", 60.2], ["2022", "I7N3G6G", "2022", 0, "no description", 60.2], ["2023", "I7N3G6G", "2023", 0, "no description", 60.2], ["2024", "I7N3G6G", "2024", 0, "no description", 60.2], ["2025", "I7N3G6G", "2025", 0, "no description", 60.2], ["2026", "I7N3G6G", "2026", 0, "no description", 60.2], ["2027", "I7N3G6G", "2027", 0, "no description", 60.2], ["2028", "I7N3G6G", "2028", 0, "no description", 60.2], ["2029", "I7N3G6G", "2029", 0, "no description", 60.2], ["2030", "I7N3G6G", "2030", 0, "no description", 60.2], ["2031", "I7N3G6G", "2031", 0, "no description", 60.2], ["2032", "I7N3G6G", "2032", 0, "no description", 60.2], ["2033", "I7N3G6G", "2033", 0, "no description", 60.2], ["2034", "I7N3G6G", "2034", 0, "no description", 60.2], ["2035", "I7N3G6G", "2035", 0, "no description", 60.2], ["2036", "I7N3G6G", "2036", 0, "no description", 60.2], ["2037", "I7N3G6G", "2037", 0, "no description", 60.2], ["2038", "I7N3G6G", "2038", 0, "no description", 60.2], ["2039", "I7N3G6G", "2039", 0, "no description", 60.2], ["2040", "I7N3G6G", "2040", 0, "no description", 60.2], ["2041", "I7N3G6G", "2041", 0, "no description", 60.2], ["2042", "I7N3G6G", "2042", 0, "no description", 60.2], ["2043", "I7N3G6G", "2043", 0, "no description", 60.2], ["2044", "I7N3G6G", "2044", 0, "no description", 60.2], ["2045", "I7N3G6G", "2045", 0, "no description", 60.2], ["2046", "I7N3G6G", "2046", 0, "no description", 60.2], ["2047", "I7N3G6G", "2047", 0, "no description", 60.2], ["2048", "I7N3G6G", "2048", 0, "no description", 60.2], ["2049", "I7N3G6G", "2049", 0, "no description", 60.2], ["2050", "I7N3G6G", "2050", 0, "no description", 60.2], ["2051", "I7N3G6G", "2051", 0, "no description", 60.2], ["2052", "I7N3G6G", "2052", 0, "no description", 60.2], ["2053", "I7N3G6G", "2053", 0, "no description", 60.2], ["2054", "I7N3G6G", "2054", 0, "no description", 60.2], ["2055", "I7N3G6G", "2055", 0, "no description", 60.2], ["2056", "I7N3G6G", "2056", 0, "no description", 60.2], ["2057", "I7N3G6G", "2057", 0, "no description", 60.2], ["2058", "I7N3G6G", "2058", 0, "no description", 60.2], ["2059", "I7N3G6G", "2059", 0, "no description", 60.2], ["2060", "I7N3G6G", "2060", 0, "no description", 60.2], ["2061", "I7N3G6G", "2061", 0, "no description", 60.2], ["2062", "I7N3G6G", "2062", 0, "no description", 60.2], ["2063", "I7N3G6G", "2063", 0, "no description", 60.2], ["2064", "I7N3G6G", "2064", 0, "no description", 60.2], ["2065", "I7N3G6G", "2065", 0, "no description", 60.2], ["2066", "I7N3G6G", "2066", 0, "no description", 60.2], ["2067", "I7N3G6G", "2067", 0, "no description", 60.2], ["2068", "I7N3G6G", "2068", 0, "no description", 60.2], ["2069", "I7N3G6G", "2069", 0, "no description", 60.2], ["2070", "I7N3G6G", "2070", 0, "no description", 60.2], ["2071", "I7N3G6G", "2071", 0, "no description", 60.2], ["2072", "I7N3G6G", "2072", 0, "no description", 60.2], ["2073", "I7N3G6G", "2073", 0, "no description", 60.2], ["2074", "I7N3G6G", "2074", 0, "no description", 60.2], ["2075", "I7N3G6G", "2075", 0, "no description", 60.2], ["2076", "I7N3G6G", "2076", 0, "no description", 60.2], ["2077", "I7N3G6G", "2077", 0, "no description", 60.2], ["2078", "I7N3G6G", "2078", 0, "no description", 60.2], ["2079", "I7N3G6G", "2079", 0, "no description", 60.2], ["2080", "I7N3G6G", "2080", 0, "no description", 60.2], ["2081", "I7N3G6G", "2081", 0, "no description", 60.2], ["2082", "I7N3G6G", "2082", 0, "no description", 60.2], ["2083", "I7N3G6G", "2083", 0, "no description", 60.2], ["2084", "I7N3G6G", "2084", 0, "no description", 60.2], ["2085", "I7N3G6G", "2085", 0, "no description", 60.2], ["2086", "I7N3G6G", "2086", 0, "no description", 60.2], ["2087", "I7N3G6G", "2087", 0, "no description", 60.2], ["2088", "I7N3G6G", "2088", 0, "no description", 60.2], ["2089", "I7N3G6G", "2089", 0, "no description", 60.2], ["2090", "I7N3G6G", "2090", 0, "no description", 60.2], ["2091", "I7N3G6G", "2091", 0, "no description", 60.2], ["2092", "I7N3G6G", "2092", 0, "no description", 60.2], ["2093", "I7N3G6G", "2093", 0, "no description", 60.2], ["2094", "I7N3G6G", "2094", 0, "no description", 60.2], ["2095", "I7N3G6G", "2095", 0, "no description", 60.2], ["2096", "I7N3G6G", "2096", 0, "no description", 60.2], ["2097", "I7N3G6G", "2097", 0, "no description", 60.2], ["2098", "I7N3G6G", "2098", 0, "no description", 60.2], ["2099", "I7N3G6G", "2099", 0, "no description", 60.2], ["2100", "I7N3G6G", "2100", 0, "no description", 60.2], ["2101", "I7N3G6G", "2101", 0, "no description", 60.2], ["2102", "I7N3G6G", "2102", 0, "no description", 60.2], ["2103", "I7N3G6G", "2103", 0, "no description", 60.2], ["2104", "I7N3G6G", "2104", 0, "no description", 60.2], ["2105", "I7N3G6G", "2105", 0, "no description", 60.2], ["2106", "I7N3G6G", "2106", 0, "no description", 60.2], ["2107", "I7N3G6G", "2107", 0, "no description", 60.2], ["2108", "I7N3G6G", "2108", 0, "no description", 60.2], ["2109", "I7N3G6G", "2109", 0, "no description", 60.2], ["2110", "I7N3G6G", "2110", 0, "no description", 60.2], ["2111", "I7N3G6G", "2111", 0, "no description", 60.2], ["2112", "I7N3G6G", "2112", 0, "no description", 60.2], ["2113", "I7N3G6G", "2113", 0, "no description", 60.2], ["2114", "I7N3G6G", "2114", 0, "no description", 60.2], ["2115", "I7N3G6G", "2115", 0, "no description", 60.2], ["2116", "I7N3G6G", "2116", 0, "no description", 60.2], ["2117", "I7N3G6G", "2117", 0, "no description", 60.2], ["2118", "I7N3G6G", "2118", 0, "no description", 60.2], ["2119", "I7N3G6G", "2119", 0, "no description", 60.2], ["2120", "I7N3G6G", "2120", 0, "no description", 60.2], ["2121", "I7N3G6G", "2121", 0, "no description", 60.2], ["2122", "I7N3G6G", "2122", 0, "no description", 60.2], ["2123", "I7N3G6G", "2123", 0, "no description", 60.2], ["2124", "I7N3G6G", "2124", 0, "no description", 60.2], ["2125", "I7N3G6G", "2125", 0, "no description", 60.2], ["2126", "I7N3G6G", "2126", 0, "no description", 60.2], ["2127", "I7N3G6G", "2127", 0, "no description", 60.2], ["2128", "I7N3G6G", "2128", 0, "no description", 60.2], ["2129", "I7N3G6G", "2129", 0, "no description", 60.2], ["2130", "I7N3G6G", "2130", 0, "no description", 60.2], ["2131", "I7N3G6G", "2131", 0, "no description", 60.2], ["2132", "I7N3G6G", "2132", 0, "no description", 60.2], ["2133", "I7N3G6G", "2133", 0, "no description", 60.2], ["2134", "I7N3G6G", "2134", 0, "no description", 60.2], ["2135", "I7N3G6G", "2135", 0, "no description", 60.2], ["2136", "I7N3G6G", "2136", 0, "no description", 60.2], ["2137", "I7N3G6G", "2137", 0, "no description", 60.2], ["2138", "I7N3G6G", "2138", 0, "no description", 60.2], ["2139", "I7N3G6G", "2139", 0, "no description", 60.2], ["2140", "I7N3G6G", "2140", 0, "no description", 60.2], ["2141", "I7N3G6G", "2141", 0, "no description", 60.2], ["2142", "I7N3G6G", "2142", 0, "no description", 60.2], ["2143", "I7N3G6G", "2143", 0, "no description", 60.2], ["2144", "I7N3G6G", "2144", 0, "no description", 60.2], ["2145", "I7N3G6G", "2145", 0, "no description", 60.2], ["2146", "I7N3G6G", "2146", 0, "no description", 60.2], ["2147", "I7N3G6G", "2147", 0, "no description", 60.2], ["2148", "I7N3G6G", "2148", 0, "no description", 60.2], ["2149", "I7N3G6G", "2149", 0, "no description", 60.2], ["2150", "I7N3G6G", "2150", 0, "no description", 60.2], ["2151", "I7N3G6G", "2151", 0, "no description", 60.2], ["2152", "I7N3G6G", "2152", 0, "no description", 60.2], ["2153", "I7N3G6G", "2153", 0, "no description", 60.2], ["2154", "I7N3G6G", "2154", 0, "no description", 60.2], ["2155", "I7N3G6G", "2155", 0, "no description", 60.2], ["2156", "I7N3G6G", "2156", 0, "no description", 60.2], ["2157", "I7N3G6G", "2157", 0, "no description", 60.2], ["2158", "I7N3G6G", "2158", 0, "no description", 60.2], ["2159", "I7N3G6G", "2159", 0, "no description", 60.2], ["2160", "I7N3G6G", "2160", 0, "no description", 60.2], ["2161", "I7N3G6G", "2161", 0, "no description", 60.2], ["2162", "I7N3G6G", "2162", 0, "no description", 60.2], ["2163", "I7N3G6G", "2163", 0, "no description", 60.2], ["2164", "I7N3G6G", "2164", 0, "no description", 60.2], ["2165", "I7N3G6G", "2165", 0, "no description", 60.2], ["2166", "I7N3G6G", "2166", 0, "no description", 60.2], ["2167", "I7N3G6G", "2167", 0, "no description", 60.2], ["2168", "I7N3G6G", "2168", 0, "no description", 60.2], ["2169", "I7N3G6G", "2169", 0, "no description", 60.2], ["2170", "I7N3G6G", "2170", 0, "no description", 60.2], ["2171", "I7N3G6G", "2171", 0, "no description", 60.2], ["2172", "I7N3G6G", "2172", 0, "no description", 60.2], ["2173", "I7N3G6G", "2173", 0, "no description", 60.2], ["2174", "I7N3G6G", "2174", 0, "no description", 60.2], ["2175", "I7N3G6G", "2175", 0, "no description", 60.2], ["2176", "I7N3G6G", "2176", 0, "no description", 60.2], ["2177", "I7N3G6G", "2177", 0, "no description", 60.2], ["2178", "I7N3G6G", "2178", 0, "no description", 60.2], ["2179", "I7N3G6G", "2179", 0, "no description", 60.2], ["2180", "I7N3G6G", "2180", 0, "no description", 60.2], ["2181", "I7N3G6G", "2181", 0, "no description", 60.2], ["2182", "I7N3G6G", "2182", 0, "no description", 60.2], ["2183", "I7N3G6G", "2183", 0, "no description", 60.2], ["2184", "I7N3G6G", "2184", 0, "no description", 60.2], ["2185", "I7N3G6G", "2185", 0, "no description", 60.2], ["2186", "I7N3G6G", "2186", 0, "no description", 60.2], ["2187", "I7N3G6G", "2187", 0, "no description", 60.2], ["2188", "I7N3G6G", "2188", 0, "no description", 60.2], ["2189", "I7N3G6G", "2189", 0, "no description", 60.2], ["2190", "I7N3G6G", "2190", 0, "no description", 60.2], ["2191", "I7N3G6G", "2191", 0, "no description", 60.2], ["2192", "I7N3G6G", "2192", 0, "no description", 60.2], ["2193", "I7N3G6G", "2193", 0, "no description", 60.2], ["2194", "I7N3G6G", "2194", 0, "no description", 60.2], ["2195", "I7N3G6G", "2195", 0, "no description", 60.2], ["2196", "I7N3G6G", "2196", 0, "no description", 60.2], ["2197", "I7N3G6G", "2197", 0, "no description", 60.2], ["2198", "I7N3G6G", "2198", 0, "no description", 60.2], ["2199", "I7N3G6G", "2199", 0, "no description", 60.2], ["2200", "I7N3G6G", "2200", 0, "no description", 60.2], ["2201", "I7N3G6G", "2201", 0, "no description", 60.2], ["2202", "I7N3G6G", "2202", 0, "no description", 60.2], ["2203", "I7N3G6G", "2203", 0, "no description", 60.2], ["2204", "I7N3G6G", "2204", 0, "no description", 60.2], ["2205", "I7N3G6G", "2205", 0, "no description", 60.2], ["2206", "I7N3G6G", "2206", 0, "no description", 60.2], ["2207", "I7N3G6G", "2207", 0, "no description", 60.2], ["2208", "I7N3G6G", "2208", 0, "no description", 60.2], ["2209", "I7N3G6G", "2209", 0, "no description", 60.2], ["2210", "I7N3G6G", "2210", 0, "no description", 60.2], ["2211", "I7N3G6G", "2211", 0, "no description", 60.2], ["2212", "I7N3G6G", "2212", 0, "no description", 60.2], ["2213", "I7N3G6G", "2213", 0, "no description", 60.2], ["2214", "I7N3G6G", "2214", 0, "no description", 60.2], ["2215", "I7N3G6G", "2215", 0, "no description", 60.2], ["2216", "I7N3G6G", "2216", 0, "no description", 60.2], ["2217", "I7N3G6G", "2217", 0, "no description", 60.2], ["2218", "I7N3G6G", "2218", 0, "no description", 60.2], ["2219", "I7N3G6G", "2219", 0, "no description", 60.2], ["2220", "I7N3G6G", "2220", 0, "no description", 60.2], ["2221", "I7N3G6G", "2221", 0, "no description", 60.2], ["2222", "I7N3G6G", "2222", 0, "no description", 60.2], ["2223", "I7N3G6G", "2223", 0, "no description", 60.2], ["2224", "I7N3G6G", "2224", 0, "no description", 60.2], ["2225", "I7N3G6G", "2225", 0, "no description", 60.2], ["2226", "I7N3G6G", "2226", 0, "no description", 60.2]] \ No newline at end of file diff --git a/packages/itmat-job-executor/test/testFiles/JSONCurator_error4.json b/packages/itmat-job-executor/test/testFiles/JSONCurator_error4.json deleted file mode 100644 index 2620ae89d..000000000 --- a/packages/itmat-job-executor/test/testFiles/JSONCurator_error4.json +++ /dev/null @@ -1 +0,0 @@ -[["ID", "SubjectID", "VisitID", "Sex", "Description", "Weight"], ["1", "I7N3G6G", "1", 0, "no description", 60.2], ["2", "I7N3G6G", "2", 0, "no description", 60.2], ["3", "I7N3G6G", "3", 0, "no description", 60.2], ["4", "I7N3G6G", "4", 0, "no description", 60.2], ["5", "I7N3G6G", "5", 0, "no description", 60.2], ["6", "I7N3G6G", "6", 0, "no description", 60.2], ["7", "I7N3G6G", "7", 0, "no description", 60.2], ["8", "I7N3G6G", "8", 0, "no description", 60.2], ["9", "I7N3G6G", "9", 0, "no description", 60.2], ["10", "I7N3G6G", "10", 0, "no description", 60.2], ["11", "I7N3G6G", "11", 0, "no description", 60.2], ["12", "I7N3G6G", "12", 0, "no description", 60.2], ["13", "I7N3G6G", "13", 0, "no description", 60.2], ["14", "I7N3G6G", "14", 0, "no description", 60.2], ["15", "I7N3G6G", "15", 0, "no description", 60.2], ["16", "I7N3G6G", "16", 0, "no description", 60.2], ["17", "I7N3G6G", "17", 0, "no description", 60.2], ["18", "I7N3G6G", "18", 0, "no description", 60.2], ["19", "I7N3G6G", "19", 0, "no description", 60.2], ["20", "I7N3G6G", "20", 0, "no description", 60.2], ["21", "I7N3G6G", "21", 0, "no description", 60.2], ["22", "I7N3G6G", "22", 0, "no description", 60.2], ["23", "I7N3G6G", "23", 0, "no description", 60.2], ["24", "I7N3G6G", "24", 0, "no description", 60.2], ["25", "I7N3G6G", "25", 0, "no description", 60.2], ["26", "I7N3G6G", "26", 0, "no description", 60.2], ["27", "I7N3G6G", "27", 0, "no description", 60.2], ["28", "I7N3G6G", "28", 0, "no description", 60.2], ["29", "I7N3G6G", "29", 0, "no description", 60.2], ["30", "I7N3G6G", "30", 0, "no description", 60.2], ["31", "I7N3G6G", "31", 0, "no description", 60.2], ["32", "I7N3G6G", "32", 0, "no description", 60.2], ["33", "I7N3G6G", "33", 0, "no description", 60.2], ["34", "I7N3G6G", "34", 0, "no description", 60.2], ["35", "I7N3G6G", "35", 0, "no description", 60.2], ["36", "I7N3G6G", "36", 0, "no description", 60.2], ["37", "I7N3G6G", "37", 0, "no description", 60.2], ["38", "I7N3G6G", "38", 0, "no description", 60.2], ["39", "I7N3G6G", "39", 0, "no description", 60.2], ["40", "I7N3G6G", "40", 0, "no description", 60.2], ["41", "I7N3G6G", "41", 0, "no description", 60.2], ["42", "I7N3G6G", "42", 0, "no description", 60.2], ["43", "I7N3G6G", "43", 0, "no description", 60.2], ["44", "I7N3G6G", "44", 0, "no description", 60.2], ["45", "I7N3G6G", "45", 0, "no description", 60.2], ["46", "I7N3G6G", "46", 0, "no description", 60.2], ["47", "I7N3G6G", "47", 0, "no description", 60.2], ["48", "I7N3G6G", "48", 0, "no description", 60.2], ["49", "I7N3G6G", "49", 0, "no description", 60.2], ["50", "I7N3G6G", "50", 0, "no description", 60.2], ["51", "I7N3G6G", "51", 0, "no description", 60.2], ["52", "I7N3G6G", "52", 0, "no description", 60.2], ["53", "I7N3G6G", "53", 0, "no description", 60.2], ["54", "I7N3G6G", "54", 0, "no description", 60.2], ["55", "I7N3G6G", "55", 0, "no description", 60.2], ["56", "I7N3G6G", "56", 0, "no description", 60.2], ["57", "I7N3G6G", "57", 0, "no description", 60.2], ["58", "I7N3G6G", "58", 0, "no description", 60.2], ["59", "I7N3G6G", "59", 0, "no description", 60.2], ["60", "I7N3G6G", "60", 0, "no description", 60.2], ["61", "I7N3G6G", "61", 0, "no description", 60.2], ["62", "I7N3G6G", "62", 0, "no description", 60.2], ["63", "I7N3G6G", "63", 0, "no description", 60.2], ["64", "I7N3G6G", "64", 0, "no description", 60.2], ["65", "I7N3G6G", "65", 0, "no description", 60.2], ["66", "I7N3G6G", "66", 0, "no description", 60.2], ["67", "I7N3G6G", "67", 0, "no description", 60.2], ["68", "I7N3G6G", "68", 0, "no description", 60.2], ["69", "I7N3G6G", "69", 0, "no description", 60.2], ["70", "I7N3G6G", "70", 0, "no description", 60.2], ["71", "I7N3G6G", "71", 0, "no description", 60.2], ["72", "I7N3G6G", "72", 0, "no description", 60.2], ["73", "I7N3G6G", "73", 0, "no description", 60.2], ["74", "I7N3G6G", "74", 0, "no description", 60.2], ["75", "I7N3G6G", "75", 0, "no description", 60.2], ["76", "I7N3G6G", "76", 0, "no description", 60.2], ["77", "I7N3G6G", "77", 0, "no description", 60.2], ["78", "I7N3G6G", "78", 0, "no description", 60.2], ["79", "I7N3G6G", "79", 0, "no description", 60.2], ["80", "I7N3G6G", "80", 0, "no description", 60.2], ["81", "I7N3G6G", "81", 0, "no description", 60.2], ["82", "I7N3G6G", "82", 0, "no description", 60.2], ["83", "I7N3G6G", "83", 0, "no description", 60.2], ["84", "I7N3G6G", "84", 0, "no description", 60.2], ["85", "I7N3G6G", "85", 0, "no description", 60.2], ["86", "I7N3G6G", "86", 0, "no description", 60.2], ["87", "I7N3G6G", "87", 0, "no description", 60.2], ["88", "I7N3G6G", "88", 0, "no description", 60.2], ["89", "I7N3G6G", "89", 0, "no description", 60.2], ["90", "I7N3G6G", "90", 0, "no description", 60.2], ["91", "I7N3G6G", "91", 0, "no description", 60.2], ["92", "I7N3G6G", "92", 0, "no description", 60.2], ["93", "I7N3G6G", "93", 0, "no description", 60.2], ["94", "I7N3G6G", "94", 0, "no description", 60.2], ["95", "I7N3G6G", "95", 0, "no description", 60.2], ["96", "I7N3G6G", "96", 0, "no description", 60.2], ["97", "I7N3G6G", "97", 0, "no description", 60.2], ["98", "I7N3G6G", "98", 0, "no description", 60.2], ["99", "I7N3G6G", "99", 0, "no description", 60.2], ["100", "I7N3G6G", "100", 0, "no description", 60.2], ["101", "I7N3G6G", "101", 0, "no description", 60.2], ["102", "I7N3G6G", "102", 0, "no description", 60.2], ["103", "I7N3G6G", "103", 0, "no description", 60.2], ["104", "I7N3G6G", "104", 0, "no description", 60.2], ["105", "I7N3G6G", "105", 0, "no description", 60.2], ["106", "I7N3G6G", "106", 0, "no description", 60.2], ["107", "I7N3G6G", "107", 0, "no description", 60.2], ["108", "I7N3G6G", "108", 0, "no description", 60.2], ["109", "I7N3G6G", "109", 0, "no description", 60.2], ["110", "I7N3G6G", "110", 0, "no description", 60.2], ["111", "I7N3G6G", "111", 0, "no description", 60.2], ["112", "I7N3G6G", "112", 0, "no description", 60.2], ["113", "I7N3G6G", "113", 0, "no description", 60.2], ["114", "I7N3G6G", "114", 0, "no description", 60.2], ["115", "I7N3G6G", "115", 0, "no description", 60.2], ["116", "I7N3G6G", "116", 0, "no description", 60.2], ["117", "I7N3G6G", "117", 0, "no description", 60.2], ["118", "I7N3G6G", "118", 0, "no description", 60.2], ["119", "I7N3G6G", "119", 0, "no description", 60.2], ["120", "I7N3G6G", "120", 0, "no description", 60.2], ["121", "I7N3G6G", "121", 0, "no description", 60.2], ["122", "I7N3G6G", "122", 0, "no description", 60.2], ["123", "I7N3G6G", "123", 0, "no description", 60.2], ["124", "I7N3G6G", "124", 0, "no description", 60.2], ["125", "I7N3G6G", "125", 0, "no description", 60.2], ["126", "I7N3G6G", "126", 0, "no description", 60.2], ["127", "I7N3G6G", "127", 0, "no description", 60.2], ["128", "I7N3G6G", "128", 0, "no description", 60.2], ["129", "I7N3G6G", "129", 0, "no description", 60.2], ["130", "I7N3G6G", "130", 0, "no description", 60.2], ["131", "I7N3G6G", "131", 0, "no description", 60.2], ["132", "I7N3G6G", "132", 0, "no description", 60.2], ["133", "I7N3G6G", "133", 0, "no description", 60.2], ["134", "I7N3G6G", "134", 0, "no description", 60.2], ["135", "I7N3G6G", "135", 0, "no description", 60.2], ["136", "I7N3G6G", "136", 0, "no description", 60.2], ["137", "I7N3G6G", "137", 0, "no description", 60.2], ["138", "I7N3G6G", "138", 0, "no description", 60.2], ["139", "I7N3G6G", "139", 0, "no description", 60.2], ["140", "I7N3G6G", "140", 0, "no description", 60.2], ["141", "I7N3G6G", "141", 0, "no description", 60.2], ["142", "I7N3G6G", "142", 0, "no description", 60.2], ["143", "I7N3G6G", "143", 0, "no description", 60.2], ["144", "I7N3G6G", "144", 0, "no description", 60.2], ["145", "I7N3G6G", "145", 0, "no description", 60.2], ["146", "I7N3G6G", "146", 0, "no description", 60.2], ["147", "I7N3G6G", "147", 0, "no description", 60.2], ["148", "I7N3G6G", "148", 0, "no description", 60.2], ["149", "I7N3G6G", "149", 0, "no description", 60.2], ["150", "I7N3G6G", "150", 0, "no description", 60.2], ["151", "I7N3G6G", "151", 0, "no description", 60.2], ["152", "I7N3G6G", "152", 0, "no description", 60.2], ["153", "I7N3G6G", "153", 0, "no description", 60.2], ["154", "I7N3G6G", "154", 0, "no description", 60.2], ["155", "I7N3G6G", "155", 0, "no description", 60.2], ["156", "I7N3G6G", "156", 0, "no description", 60.2], ["157", "I7N3G6G", "157", 0, "no description", 60.2], ["158", "I7N3G6G", "158", 0, "no description", 60.2], ["159", "I7N3G6G", "159", 0, "no description", 60.2], ["160", "I7N3G6G", "160", 0, "no description", 60.2], ["161", "I7N3G6G", "161", 0, "no description", 60.2], ["162", "I7N3G6G", "162", 0, "no description", 60.2], ["163", "I7N3G6G", "163", 0, "no description", 60.2], ["164", "I7N3G6G", "164", 0, "no description", 60.2], ["165", "I7N3G6G", "165", 0, "no description", 60.2], ["166", "I7N3G6G", "166", 0, "no description", 60.2], ["167", "I7N3G6G", "167", 0, "no description", 60.2], ["168", "I7N3G6G", "168", 0, "no description", 60.2], ["169", "I7N3G6G", "169", 0, "no description", 60.2], ["170", "I7N3G6G", "170", 0, "no description", 60.2], ["171", "I7N3G6G", "171", 0, "no description", 60.2], ["172", "I7N3G6G", "172", 0, "no description", 60.2], ["173", "I7N3G6G", "173", 0, "no description", 60.2], ["174", "I7N3G6G", "174", 0, "no description", 60.2], ["175", "I7N3G6G", "175", 0, "no description", 60.2], ["176", "I7N3G6G", "176", 0, "no description", 60.2], ["177", "I7N3G6G", "177", 0, "no description", 60.2], ["178", "I7N3G6G", "178", 0, "no description", 60.2], ["179", "I7N3G6G", "179", 0, "no description", 60.2], ["180", "I7N3G6G", "180", 0, "no description", 60.2], ["181", "I7N3G6G", "181", 0, "no description", 60.2], ["182", "I7N3G6G", "182", 0, "no description", 60.2], ["183", "I7N3G6G", "183", 0, "no description", 60.2], ["184", "I7N3G6G", "184", 0, "no description", 60.2], ["185", "I7N3G6G", "185", 0, "no description", 60.2], ["186", "I7N3G6G", "186", 0, "no description", 60.2], ["187", "I7N3G6G", "187", 0, "no description", 60.2], ["188", "I7N3G6G", "188", 0, "no description", 60.2], ["189", "I7N3G6G", "189", 0, "no description", 60.2], ["190", "I7N3G6G", "190", 0, "no description", 60.2], ["191", "I7N3G6G", "191", 0, "no description", 60.2], ["192", "I7N3G6G", "192", 0, "no description", 60.2], ["193", "I7N3G6G", "193", 0, "no description", 60.2], ["194", "I7N3G6G", "194", 0, "no description", 60.2], ["195", "I7N3G6G", "195", 0, "no description", 60.2], ["196", "I7N3G6G", "196", 0, "no description", 60.2], ["197", "I7N3G6G", "197", 0, "no description", 60.2], ["198", "I7N3G6G", "198", 0, "no description", 60.2], ["199", "I7N3G6G", "199", 0, "no description", 60.2], ["200", "I7N3G6G", "200", 0, "no description", 60.2], ["201", "I7N3G6G", "201", 0, "no description", 60.2], ["202", "I7N3G6G", "202", 0, "no description", 60.2], ["203", "I7N3G6G", "203", 0, "no description", 60.2], ["204", "I7N3G6G", "204", 0, "no description", 60.2], ["205", "I7N3G6G", "205", 0, "no description", 60.2], ["206", "I7N3G6G", "206", 0, "no description", 60.2], ["207", "I7N3G6G", "207", 0, "no description", 60.2], ["208", "I7N3G6G", "208", 0, "no description", 60.2], ["209", "I7N3G6G", "209", 0, "no description", 60.2], ["210", "I7N3G6G", "210", 0, "no description", 60.2], ["211", "I7N3G6G", "211", 0, "no description", 60.2], ["212", "I7N3G6G", "212", 0, "no description", 60.2], ["213", "I7N3G6G", "213", 0, "no description", 60.2], ["214", "I7N3G6G", "214", 0, "no description", 60.2], ["215", "I7N3G6G", "215", 0, "no description", 60.2], ["216", "I7N3G6G", "216", 0, "no description", 60.2], ["217", "I7N3G6G", "217", 0, "no description", 60.2], ["218", "I7N3G6G", "218", 0, "no description", 60.2], ["219", "I7N3G6G", "219", 0, "no description", 60.2], ["220", "I7N3G6G", "220", 0, "no description", 60.2], ["221", "I7N3G6G", "221", 0, "no description", 60.2], ["222", "I7N3G6G", "222", 0, "no description", 60.2], ["223", "I7N3G6G", "223", 0, "no description", 60.2], ["224", "I7N3G6G", "224", 0, "no description", 60.2], ["225", "I7N3G6G", "225", 0, "no description", 60.2], ["226", "I7N3G6G", "226", 0, "no description", 60.2], ["227", "I7N3G6G", "227", 0, "no description", 60.2], ["228", "I7N3G6G", "228", 0, "no description", 60.2], ["229", "I7N3G6G", "229", 0, "no description", 60.2], ["230", "I7N3G6G", "230", 0, "no description", 60.2], ["231", "I7N3G6G", "231", 0, "no description", 60.2], ["232", "I7N3G6G", "232", 0, "no description", 60.2], ["233", "I7N3G6G", "233", 0, "no description", 60.2], ["234", "I7N3G6G", "234", 0, "no description", 60.2], ["235", "I7N3G6G", "235", 0, "no description", 60.2], ["236", "I7N3G6G", "236", 0, "no description", 60.2], ["237", "I7N3G6G", "237", 0, "no description", 60.2], ["238", "I7N3G6G", "238", 0, "no description", 60.2], ["239", "I7N3G6G", "239", 0, "no description", 60.2], ["240", "I7N3G6G", "240", 0, "no description", 60.2], ["241", "I7N3G6G", "241", 0, "no description", 60.2], ["242", "I7N3G6G", "242", 0, "no description", 60.2], ["243", "I7N3G6G", "243", 0, "no description", 60.2], ["244", "I7N3G6G", "244", 0, "no description", 60.2], ["245", "I7N3G6G", "245", 0, "no description", 60.2], ["246", "I7N3G6G", "246", 0, "no description", 60.2], ["247", "I7N3G6G", "247", 0, "no description", 60.2], ["248", "I7N3G6G", "248", 0, "no description", 60.2], ["249", "I7N3G6G", "249", 0, "no description", 60.2], ["250", "I7N3G6G", "250", 0, "no description", 60.2], ["251", "I7N3G6G", "251", 0, "no description", 60.2], ["252", "I7N3G6G", "252", 0, "no description", 60.2], ["253", "I7N3G6G", "253", 0, "no description", 60.2], ["254", "I7N3G6G", "254", 0, "no description", 60.2], ["255", "I7N3G6G", "255", 0, "no description", 60.2], ["256", "I7N3G6G", "256", 0, "no description", 60.2], ["257", "I7N3G6G", "257", 0, "no description", 60.2], ["258", "I7N3G6G", "258", 0, "no description", 60.2], ["259", "I7N3G6G", "259", 0, "no description", 60.2], ["260", "I7N3G6G", "260", 0, "no description", 60.2], ["261", "I7N3G6G", "261", 0, "no description", 60.2], ["262", "I7N3G6G", "262", 0, "no description", 60.2], ["263", "I7N3G6G", "263", 0, "no description", 60.2], ["264", "I7N3G6G", "264", 0, "no description", 60.2], ["265", "I7N3G6G", "265", 0, "no description", 60.2], ["266", "I7N3G6G", "266", 0, "no description", 60.2], ["267", "I7N3G6G", "267", 0, "no description", 60.2], ["268", "I7N3G6G", "268", 0, "no description", 60.2], ["269", "I7N3G6G", "269", 0, "no description", 60.2], ["270", "I7N3G6G", "270", 0, "no description", 60.2], ["271", "I7N3G6G", "271", 0, "no description", 60.2], ["272", "I7N3G6G", "272", 0, "no description", 60.2], ["273", "I7N3G6G", "273", 0, "no description", 60.2], ["274", "I7N3G6G", "274", 0, "no description", 60.2], ["275", "I7N3G6G", "275", 0, "no description", 60.2], ["276", "I7N3G6G", "276", 0, "no description", 60.2], ["277", "I7N3G6G", "277", 0, "no description", 60.2], ["278", "I7N3G6G", "278", 0, "no description", 60.2], ["279", "I7N3G6G", "279", 0, "no description", 60.2], ["280", "I7N3G6G", "280", 0, "no description", 60.2], ["281", "I7N3G6G", "281", 0, "no description", 60.2], ["282", "I7N3G6G", "282", 0, "no description", 60.2], ["283", "I7N3G6G", "283", 0, "no description", 60.2], ["284", "I7N3G6G", "284", 0, "no description", 60.2], ["285", "I7N3G6G", "285", 0, "no description", 60.2], ["286", "I7N3G6G", "286", 0, "no description", 60.2], ["287", "I7N3G6G", "287", 0, "no description", 60.2], ["288", "I7N3G6G", "288", 0, "no description", 60.2], ["289", "I7N3G6G", "289", 0, "no description", 60.2], ["290", "I7N3G6G", "290", 0, "no description", 60.2], ["291", "I7N3G6G", "291", 0, "no description", 60.2], ["292", "I7N3G6G", "292", 0, "no description", 60.2], ["293", "I7N3G6G", "293", 0, "no description", 60.2], ["294", "I7N3G6G", "294", 0, "no description", 60.2], ["295", "I7N3G6G", "295", 0, "no description", 60.2], ["296", "I7N3G6G", "296", 0, "no description", 60.2], ["297", "I7N3G6G", "297", 0, "no description", 60.2], ["298", "I7N3G6G", "298", 0, "no description", 60.2], ["299", "I7N3G6G", "299", 0, "no description", 60.2], ["300", "I7N3G6G", "300", 0, "no description", 60.2], ["301", "I7N3G6G", "301", 0, "no description", 60.2], ["302", "I7N3G6G", "302", 0, "no description", 60.2], ["303", "I7N3G6G", "303", 0, "no description", 60.2], ["304", "I7N3G6G", "304", 0, "no description", 60.2], ["305", "I7N3G6G", "305", 0, "no description", 60.2], ["306", "I7N3G6G", "306", 0, "no description", 60.2], ["307", "I7N3G6G", "307", 0, "no description", 60.2], ["308", "I7N3G6G", "308", 0, "no description", 60.2], ["309", "I7N3G6G", "309", 0, "no description", 60.2], ["310", "I7N3G6G", "310", 0, "no description", 60.2], ["311", "I7N3G6G", "311", 0, "no description", 60.2], ["312", "I7N3G6G", "312", 0, "no description", 60.2], ["313", "I7N3G6G", "313", 0, "no description", 60.2], ["314", "I7N3G6G", "314", 0, "no description", 60.2], ["315", "I7N3G6G", "315", 0, "no description", 60.2], ["316", "I7N3G6G", "316", 0, "no description", 60.2], ["317", "I7N3G6G", "317", 0, "no description", 60.2], ["318", "I7N3G6G", "318", 0, "no description", 60.2], ["319", "I7N3G6G", "319", 0, "no description", 60.2], ["320", "I7N3G6G", "320", 0, "no description", 60.2], ["321", "I7N3G6G", "321", 0, "no description", 60.2], ["322", "I7N3G6G", "322", 0, "no description", 60.2], ["323", "I7N3G6G", "323", 0, "no description", 60.2], ["324", "I7N3G6G", "324", 0, "no description", 60.2], ["325", "I7N3G6G", "325", 0, "no description", 60.2], ["326", "I7N3G6G", "326", 0, "no description", 60.2], ["327", "I7N3G6G", "327", 0, "no description", 60.2], ["328", "I7N3G6G", "328", 0, "no description", 60.2], ["329", "I7N3G6G", "329", 0, "no description", 60.2], ["330", "I7N3G6G", "330", 0, "no description", 60.2], ["331", "I7N3G6G", "331", 0, "no description", 60.2], ["332", "I7N3G6G", "332", 0, "no description", 60.2], ["333", "I7N3G6G", "333", 0, "no description", 60.2], ["334", "I7N3G6G", "334", 0, "no description", 60.2], ["335", "I7N3G6G", "335", 0, "no description", 60.2], ["336", "I7N3G6G", "336", 0, "no description", 60.2], ["337", "I7N3G6G", "337", 0, "no description", 60.2], ["338", "I7N3G6G", "338", 0, "no description", 60.2], ["339", "I7N3G6G", "339", 0, "no description", 60.2], ["340", "I7N3G6G", "340", 0, "no description", 60.2], ["341", "I7N3G6G", "341", 0, "no description", 60.2], ["342", "I7N3G6G", "342", 0, "no description", 60.2], ["343", "I7N3G6G", "343", 0, "no description", 60.2], ["344", "I7N3G6G", "344", 0, "no description", 60.2], ["345", "I7N3G6G", "345", 0, "no description", 60.2], ["346", "I7N3G6G", "346", 0, "no description", 60.2], ["347", "I7N3G6G", "347", 0, "no description", 60.2], ["348", "I7N3G6G", "348", 0, "no description", 60.2], ["349", "I7N3G6G", "349", 0, "no description", 60.2], ["350", "I7N3G6G", "350", 0, "no description", 60.2], ["351", "I7N3G6G", "351", 0, "no description", 60.2], ["352", "I7N3G6G", "352", 0, "no description", 60.2], ["353", "I7N3G6G", "353", 0, "no description", 60.2], ["354", "I7N3G6G", "354", 0, "no description", 60.2], ["355", "I7N3G6G", "355", 0, "no description", 60.2], ["356", "I7N3G6G", "356", 0, "no description", 60.2], ["357", "I7N3G6G", "357", 0, "no description", 60.2], ["358", "I7N3G6G", "358", 0, "no description", 60.2], ["359", "I7N3G6G", "359", 0, "no description", 60.2], ["360", "I7N3G6G", "360", 0, "no description", 60.2], ["361", "I7N3G6G", "361", 0, "no description", 60.2], ["362", "I7N3G6G", "362", 0, "no description", 60.2], ["363", "I7N3G6G", "363", 0, "no description", 60.2], ["364", "I7N3G6G", "364", 0, "no description", 60.2], ["365", "I7N3G6G", "365", 0, "no description", 60.2], ["366", "I7N3G6G", "366", 0, "no description", 60.2], ["367", "I7N3G6G", "367", 0, "no description", 60.2], ["368", "I7N3G6G", "368", 0, "no description", 60.2], ["369", "I7N3G6G", "369", 0, "no description", 60.2], ["370", "I7N3G6G", "370", 0, "no description", 60.2], ["371", "I7N3G6G", "371", 0, "no description", 60.2], ["372", "I7N3G6G", "372", 0, "no description", 60.2], ["373", "I7N3G6G", "373", 0, "no description", 60.2], ["374", "I7N3G6G", "374", 0, "no description", 60.2], ["375", "I7N3G6G", "375", 0, "no description", 60.2], ["376", "I7N3G6G", "376", 0, "no description", 60.2], ["377", "I7N3G6G", "377", 0, "no description", 60.2], ["378", "I7N3G6G", "378", 0, "no description", 60.2], ["379", "I7N3G6G", "379", 0, "no description", 60.2], ["380", "I7N3G6G", "380", 0, "no description", 60.2], ["381", "I7N3G6G", "381", 0, "no description", 60.2], ["382", "I7N3G6G", "382", 0, "no description", 60.2], ["383", "I7N3G6G", "383", 0, "no description", 60.2], ["384", "I7N3G6G", "384", 0, "no description", 60.2], ["385", "I7N3G6G", "385", 0, "no description", 60.2], ["386", "I7N3G6G", "386", 0, "no description", 60.2], ["387", "I7N3G6G", "387", 0, "no description", 60.2], ["388", "I7N3G6G", "388", 0, "no description", 60.2], ["389", "I7N3G6G", "389", 0, "no description", 60.2], ["390", "I7N3G6G", "390", 0, "no description", 60.2], ["391", "I7N3G6G", "391", 0, "no description", 60.2], ["392", "I7N3G6G", "392", 0, "no description", 60.2], ["393", "I7N3G6G", "393", 0, "no description", 60.2], ["394", "I7N3G6G", "394", 0, "no description", 60.2], ["395", "I7N3G6G", "395", 0, "no description", 60.2], ["396", "I7N3G6G", "396", 0, "no description", 60.2], ["397", "I7N3G6G", "397", 0, "no description", 60.2], ["398", "I7N3G6G", "398", 0, "no description", 60.2], ["399", "I7N3G6G", "399", 0, "no description", 60.2], ["400", "I7N3G6G", "400", 0, "no description", 60.2], ["401", "I7N3G6G", "401", 0, "no description", 60.2], ["402", "I7N3G6G", "402", 0, "no description", 60.2], ["403", "I7N3G6G", "403", 0, "no description", 60.2], ["404", "I7N3G6G", "404", 0, "no description", 60.2], ["405", "I7N3G6G", "405", 0, "no description", 60.2], ["406", "I7N3G6G", "406", 0, "no description", 60.2], ["407", "I7N3G6G", "407", 0, "no description", 60.2], ["408", "I7N3G6G", "408", 0, "no description", 60.2], ["409", "I7N3G6G", "409", 0, "no description", 60.2], ["410", "I7N3G6G", "410", 0, "no description", 60.2], ["411", "I7N3G6G", "411", 0, "no description", 60.2], ["412", "I7N3G6G", "412", 0, "no description", 60.2], ["413", "I7N3G6G", "413", 0, "no description", 60.2], ["414", "I7N3G6G", "414", 0, "no description", 60.2], ["415", "I7N3G6G", "415", 0, "no description", 60.2], ["416", "I7N3G6G", "416", 0, "no description", 60.2], ["417", "I7N3G6G", "417", 0, "no description", 60.2], ["418", "I7N3G6G", "418", 0, "no description", 60.2], ["419", "I7N3G6G", "419", 0, "no description", 60.2], ["420", "I7N3G6G", "420", 0, "no description", 60.2], ["421", "I7N3G6G", "421", 0, "no description", 60.2], ["422", "I7N3G6G", "422", 0, "no description", 60.2], ["423", "I7N3G6G", "423", 0, "no description", 60.2], ["424", "I7N3G6G", "424", 0, "no description", 60.2], ["425", "I7N3G6G", "425", 0, "no description", 60.2], ["426", "I7N3G6G", "426", 0, "no description", 60.2], ["427", "I7N3G6G", "427", 0, "no description", 60.2], ["428", "I7N3G6G", "428", 0, "no description", 60.2], ["429", "I7N3G6G", "429", 0, "no description", 60.2], ["430", "I7N3G6G", "430", 0, "no description", 60.2], ["431", "I7N3G6G", "431", 0, "no description", 60.2], ["432", "I7N3G6G", "432", 0, "no description", 60.2], ["433", "I7N3G6G", "433", 0, "no description", 60.2], ["434", "I7N3G6G", "434", 0, "no description", 60.2], ["435", "I7N3G6G", "435", 0, "no description", 60.2], ["436", "I7N3G6G", "436", 0, "no description", 60.2], ["437", "I7N3G6G", "437", 0, "no description", 60.2], ["438", "I7N3G6G", "438", 0, "no description", 60.2], ["439", "I7N3G6G", "439", 0, "no description", 60.2], ["440", "I7N3G6G", "440", 0, "no description", 60.2], ["441", "I7N3G6G", "441", 0, "no description", 60.2], ["442", "I7N3G6G", "442", 0, "no description", 60.2], ["443", "I7N3G6G", "443", 0, "no description", 60.2], ["444", "I7N3G6G", "444", 0, "no description", 60.2], ["445", "I7N3G6G", "445", 0, "no description", 60.2], ["446", "I7N3G6G", "446", 0, "no description", 60.2], ["447", "I7N3G6G", "447", 0, "no description", 60.2], ["448", "I7N3G6G", "448", 0, "no description", 60.2], ["449", "I7N3G6G", "449", 0, "no description", 60.2], ["450", "I7N3G6G", "450", 0, "no description", 60.2], ["451", "I7N3G6G", "451", 0, "no description", 60.2], ["452", "I7N3G6G", "452", 0, "no description", 60.2], ["453", "I7N3G6G", "453", 0, "no description", 60.2], ["454", "I7N3G6G", "454", 0, "no description", 60.2], ["455", "I7N3G6G", "455", 0, "no description", 60.2], ["456", "I7N3G6G", "456", 0, "no description", 60.2], ["457", "I7N3G6G", "457", 0, "no description", 60.2], ["458", "I7N3G6G", "458", 0, "no description", 60.2], ["459", "I7N3G6G", "459", 0, "no description", 60.2], ["460", "I7N3G6G", "460", 0, "no description", 60.2], ["461", "I7N3G6G", "461", 0, "no description", 60.2], ["462", "I7N3G6G", "462", 0, "no description", 60.2], ["463", "I7N3G6G", "463", 0, "no description", 60.2], ["464", "I7N3G6G", "464", 0, "no description", 60.2], ["465", "I7N3G6G", "465", 0, "no description", 60.2], ["466", "I7N3G6G", "466", 0, "no description", 60.2], ["467", "I7N3G6G", "467", 0, "no description", 60.2], ["468", "I7N3G6G", "468", 0, "no description", 60.2], ["469", "I7N3G6G", "469", 0, "no description", 60.2], ["470", "I7N3G6G", "470", 0, "no description", 60.2], ["471", "I7N3G6G", "471", 0, "no description", 60.2], ["472", "I7N3G6G", "472", 0, "no description", 60.2], ["473", "I7N3G6G", "473", 0, "no description", 60.2], ["474", "I7N3G6G", "474", 0, "no description", 60.2], ["475", "I7N3G6G", "475", 0, "no description", 60.2], ["476", "I7N3G6G", "476", 0, "no description", 60.2], ["477", "I7N3G6G", "477", 0, "no description", 60.2], ["478", "I7N3G6G", "478", 0, "no description", 60.2], ["479", "I7N3G6G", "479", 0, "no description", 60.2], ["480", "I7N3G6G", "480", 0, "no description", 60.2], ["481", "I7N3G6G", "481", 0, "no description", 60.2], ["482", "I7N3G6G", "482", 0, "no description", 60.2], ["483", "I7N3G6G", "483", 0, "no description", 60.2], ["484", "I7N3G6G", "484", 0, "no description", 60.2], ["485", "I7N3G6G", "485", 0, "no description", 60.2], ["486", "I7N3G6G", "486", 0, "no description", 60.2], ["487", "I7N3G6G", "487", 0, "no description", 60.2], ["488", "I7N3G6G", "488", 0, "no description", 60.2], ["489", "I7N3G6G", "489", 0, "no description", 60.2], ["490", "I7N3G6G", "490", 0, "no description", 60.2], ["491", "I7N3G6G", "491", 0, "no description", 60.2], ["492", "I7N3G6G", "492", 0, "no description", 60.2], ["493", "I7N3G6G", "493", 0, "no description", 60.2], ["494", "I7N3G6G", "494", 0, "no description", 60.2], ["495", "I7N3G6G", "495", 0, "no description", 60.2], ["496", "I7N3G6G", "496", 0, "no description", 60.2], ["497", "I7N3G6G", "497", 0, "no description", 60.2], ["498", "I7N3G6G", "498", 0, "no description", 60.2], ["499", "I7N3G6G", "499", 0, "no description", 60.2], ["500", "I7N3G6G", "500", 0, "no description", 60.2], ["501", "I7N3G6G", "501", 0, "no description", 60.2], ["502", "I7N3G6G", "502", 0, "no description", 60.2], ["503", "I7N3G6G", "503", 0, "no description", 60.2], ["504", "I7N3G6G", "504", 0, "no description", 60.2], ["505", "I7N3G6G", "505", 0, "no description", 60.2], ["506", "I7N3G6G", "506", 0, "no description", 60.2], ["507", "I7N3G6G", "507", 0, "no description", 60.2], ["508", "I7N3G6G", "508", 0, "no description", 60.2], ["509", "I7N3G6G", "509", 0, "no description", 60.2], ["510", "I7N3G6G", "510", 0, "no description", 60.2], ["511", "I7N3G6G", "511", 0, "no description", 60.2], ["512", "I7N3G6G", "512", 0, "no description", 60.2], ["513", "I7N3G6G", "513", 0, "no description", 60.2], ["514", "I7N3G6G", "514", 0, "no description", 60.2], ["515", "I7N3G6G", "515", 0, "no description", 60.2], ["516", "I7N3G6G", "516", 0, "no description", 60.2], ["517", "I7N3G6G", "517", 0, "no description", 60.2], ["518", "I7N3G6G", "518", 0, "no description", 60.2], ["519", "I7N3G6G", "519", 0, "no description", 60.2], ["520", "I7N3G6G", "520", 0, "no description", 60.2], ["521", "I7N3G6G", "521", 0, "no description", 60.2], ["522", "I7N3G6G", "522", 0, "no description", 60.2], ["523", "I7N3G6G", "523", 0, "no description", 60.2], ["524", "I7N3G6G", "524", 0, "no description", 60.2], ["525", "I7N3G6G", "525", 0, "no description", 60.2], ["526", "I7N3G6G", "526", 0, "no description", 60.2], ["527", "I7N3G6G", "527", 0, "no description", 60.2], ["528", "I7N3G6G", "528", 0, "no description", 60.2], ["529", "I7N3G6G", "529", 0, "no description", 60.2], ["530", "I7N3G6G", "530", 0, "no description", 60.2], ["531", "I7N3G6G", "531", 0, "no description", 60.2], ["532", "I7N3G6G", "532", 0, "no description", 60.2], ["533", "I7N3G6G", "533", 0, "no description", 60.2], ["534", "I7N3G6G", "534", 0, "no description", 60.2], ["535", "I7N3G6G", "535", 0, "no description", 60.2], ["536", "I7N3G6G", "536", 0, "no description", 60.2], ["537", "I7N3G6G", "537", 0, "no description", 60.2], ["538", "I7N3G6G", "538", 0, "no description", 60.2], ["539", "I7N3G6G", "539", 0, "no description", 60.2], ["540", "I7N3G6G", "540", 0, "no description", 60.2], ["541", "I7N3G6G", "541", 0, "no description", 60.2], ["542", "I7N3G6G", "542", 0, "no description", 60.2], ["543", "I7N3G6G", "543", 0, "no description", 60.2], ["544", "I7N3G6G", "544", 0, "no description", 60.2], ["545", "I7N3G6G", "545", 0, "no description", 60.2], ["546", "I7N3G6G", "546", 0, "no description", 60.2], ["547", "I7N3G6G", "547", 0, "no description", 60.2], ["548", "I7N3G6G", "548", 0, "no description", 60.2], ["549", "I7N3G6G", "549", 0, "no description", 60.2], ["550", "I7N3G6G", "550", 0, "no description", 60.2], ["551", "I7N3G6G", "551", 0, "no description", 60.2], ["552", "I7N3G6G", "552", 0, "no description", 60.2], ["553", "I7N3G6G", "553", 0, "no description", 60.2], ["554", "I7N3G6G", "554", 0, "no description", 60.2], ["555", "I7N3G6G", "555", 0, "no description", 60.2], ["556", "I7N3G6G", "556", 0, "no description", 60.2], ["557", "I7N3G6G", "557", 0, "no description", 60.2], ["558", "I7N3G6G", "558", 0, "no description", 60.2], ["559", "I7N3G6G", "559", 0, "no description", 60.2], ["560", "I7N3G6G", "560", 0, "no description", 60.2], ["561", "I7N3G6G", "561", 0, "no description", 60.2], ["562", "I7N3G6G", "562", 0, "no description", 60.2], ["563", "I7N3G6G", "563", 0, "no description", 60.2], ["564", "I7N3G6G", "564", 0, "no description", 60.2], ["565", "I7N3G6G", "565", 0, "no description", 60.2], ["566", "I7N3G6G", "566", 0, "no description", 60.2], ["567", "I7N3G6G", "567", 0, "no description", 60.2], ["568", "I7N3G6G", "568", 0, "no description", 60.2], ["569", "I7N3G6G", "569", 0, "no description", 60.2], ["570", "I7N3G6G", "570", 0, "no description", 60.2], ["571", "I7N3G6G", "571", 0, "no description", 60.2], ["572", "I7N3G6G", "572", 0, "no description", 60.2], ["573", "I7N3G6G", "573", 0, "no description", 60.2], ["574", "I7N3G6G", "574", 0, "no description", 60.2], ["575", "I7N3G6G", "575", 0, "no description", 60.2], ["576", "I7N3G6G", "576", 0, "no description", 60.2], ["577", "I7N3G6G", "577", 0, "no description", 60.2], ["578", "I7N3G6G", "578", 0, "no description", 60.2], ["579", "I7N3G6G", "579", 0, "no description", 60.2], ["580", "I7N3G6G", "580", 0, "no description", 60.2], ["581", "I7N3G6G", "581", 0, "no description", 60.2], ["582", "I7N3G6G", "582", 0, "no description", 60.2], ["583", "I7N3G6G", "583", 0, "no description", 60.2], ["584", "I7N3G6G", "584", 0, "no description", 60.2], ["585", "I7N3G6G", "585", 0, "no description", 60.2], ["586", "I7N3G6G", "586", 0, "no description", 60.2], ["587", "I7N3G6G", "587", 0, "no description", 60.2], ["588", "I7N3G6G", "588", 0, "no description", 60.2], ["589", "I7N3G6G", "589", 0, "no description", 60.2], ["590", "I7N3G6G", "590", 0, "no description", 60.2], ["591", "I7N3G6G", "591", 0, "no description", 60.2], ["592", "I7N3G6G", "592", 0, "no description", 60.2], ["593", "I7N3G6G", "593", 0, "no description", 60.2], ["594", "I7N3G6G", "594", 0, "no description", 60.2], ["595", "I7N3G6G", "595", 0, "no description", 60.2], ["596", "I7N3G6G", "596", 0, "no description", 60.2], ["597", "I7N3G6G", "597", 0, "no description", 60.2], ["598", "I7N3G6G", "598", 0, "no description", 60.2], ["599", "I7N3G6G", "599", 0, "no description", 60.2], ["600", "I7N3G6G", "600", 0, "no description", 60.2], ["601", "I7N3G6G", "601", 0, "no description", 60.2], ["602", "I7N3G6G", "602", 0, "no description", 60.2], ["603", "I7N3G6G", "603", 0, "no description", 60.2], ["604", "I7N3G6G", "604", 0, "no description", 60.2], ["605", "I7N3G6G", "605", 0, "no description", 60.2], ["606", "I7N3G6G", "606", 0, "no description", 60.2], ["607", "I7N3G6G", "607", 0, "no description", 60.2], ["608", "I7N3G6G", "608", 0, "no description", 60.2], ["609", "I7N3G6G", "609", 0, "no description", 60.2], ["610", "I7N3G6G", "610", 0, "no description", 60.2], ["611", "I7N3G6G", "611", 0, "no description", 60.2], ["612", "I7N3G6G", "612", 0, "no description", 60.2], ["613", "I7N3G6G", "613", 0, "no description", 60.2], ["614", "I7N3G6G", "614", 0, "no description", 60.2], ["615", "I7N3G6G", "615", 0, "no description", 60.2], ["616", "I7N3G6G", "616", 0, "no description", 60.2], ["617", "I7N3G6G", "617", 0, "no description", 60.2], ["618", "I7N3G6G", "618", 0, "no description", 60.2], ["619", "I7N3G6G", "619", 0, "no description", 60.2], ["620", "I7N3G6G", "620", 0, "no description", 60.2], ["621", "I7N3G6G", "621", 0, "no description", 60.2], ["622", "I7N3G6G", "622", 0, "no description", 60.2], ["623", "I7N3G6G", "623", 0, "no description", 60.2], ["624", "I7N3G6G", "624", 0, "no description", 60.2], ["625", "I7N3G6G", "625", 0, "no description", 60.2], ["626", "I7N3G6G", "626", 0, "no description", 60.2], ["627", "I7N3G6G", "627", 0, "no description", 60.2], ["628", "I7N3G6G", "628", 0, "no description", 60.2], ["629", "I7N3G6G", "629", 0, "no description", 60.2], ["630", "I7N3G6G", "630", 0, "no description", 60.2], ["631", "I7N3G6G", "631", 0, "no description", 60.2], ["632", "I7N3G6G", "632", 0, "no description", 60.2], ["633", "I7N3G6G", "633", 0, "no description", 60.2], ["634", "I7N3G6G", "634", 0, "no description", 60.2], ["635", "I7N3G6G", "635", 0, "no description", 60.2], ["636", "I7N3G6G", "636", 0, "no description", 60.2], ["637", "I7N3G6G", "637", 0, "no description", 60.2], ["638", "I7N3G6G", "638", 0, "no description", 60.2], ["639", "I7N3G6G", "639", 0, "no description", 60.2], ["640", "I7N3G6G", "640", 0, "no description", 60.2], ["641", "I7N3G6G", "641", 0, "no description", 60.2], ["642", "I7N3G6G", "642", 0, "no description", 60.2], ["643", "I7N3G6G", "643", 0, "no description", 60.2], ["644", "I7N3G6G", "644", 0, "no description", 60.2], ["645", "I7N3G6G", "645", 0, "no description", 60.2], ["646", "I7N3G6G", "646", 0, "no description", 60.2], ["647", "I7N3G6G", "647", 0, "no description", 60.2], ["648", "I7N3G6G", "648", 0, "no description", 60.2], ["649", "I7N3G6G", "649", 0, "no description", 60.2], ["650", "I7N3G6G", "650", 0, "no description", 60.2], ["651", "I7N3G6G", "651", 0, "no description", 60.2], ["652", "I7N3G6G", "652", 0, "no description", 60.2], ["653", "I7N3G6G", "653", 0, "no description", 60.2], ["654", "I7N3G6G", "654", 0, "no description", 60.2], ["655", "I7N3G6G", "655", 0, "no description", 60.2], ["656", "I7N3G6G", "656", 0, "no description", 60.2], ["657", "I7N3G6G", "657", 0, "no description", 60.2], ["658", "I7N3G6G", "658", 0, "no description", 60.2], ["659", "I7N3G6G", "659", 0, "no description", 60.2], ["660", "I7N3G6G", "660", 0, "no description", 60.2], ["661", "I7N3G6G", "661", 0, "no description", 60.2], ["662", "I7N3G6G", "662", 0, "no description", 60.2], ["663", "I7N3G6G", "663", 0, "no description", 60.2], ["664", "I7N3G6G", "664", 0, "no description", 60.2], ["665", "I7N3G6G", "665", 0, "no description", 60.2], ["666", "I7N3G6G", "666", 0, "no description", 60.2], ["667", "I7N3G6G", "667", 0, "no description", 60.2], ["668", "I7N3G6G", "668", 0, "no description", 60.2], ["669", "I7N3G6G", "669", 0, "no description", 60.2], ["670", "I7N3G6G", "670", 0, "no description", 60.2], ["671", "I7N3G6G", "671", 0, "no description", 60.2], ["672", "I7N3G6G", "672", 0, "no description", 60.2], ["673", "I7N3G6G", "673", 0, "no description", 60.2], ["674", "I7N3G6G", "674", 0, "no description", 60.2], ["675", "I7N3G6G", "675", 0, "no description", 60.2], ["676", "I7N3G6G", "676", 0, "no description", 60.2], ["677", "I7N3G6G", "677", 0, "no description", 60.2], ["678", "I7N3G6G", "678", 0, "no description", 60.2], ["679", "I7N3G6G", "679", 0, "no description", 60.2], ["680", "I7N3G6G", "680", 0, "no description", 60.2], ["681", "I7N3G6G", "681", 0, "no description", 60.2], ["682", "I7N3G6G", "682", 0, "no description", 60.2], ["683", "I7N3G6G", "683", 0, "no description", 60.2], ["684", "I7N3G6G", "684", 0, "no description", 60.2], ["685", "I7N3G6G", "685", 0, "no description", 60.2], ["686", "I7N3G6G", "686", 0, "no description", 60.2], ["687", "I7N3G6G", "687", 0, "no description", 60.2], ["688", "I7N3G6G", "688", 0, "no description", 60.2], ["689", "I7N3G6G", "689", 0, "no description", 60.2], ["690", "I7N3G6G", "690", 0, "no description", 60.2], ["691", "I7N3G6G", "691", 0, "no description", 60.2], ["692", "I7N3G6G", "692", 0, "no description", 60.2], ["693", "I7N3G6G", "693", 0, "no description", 60.2], ["694", "I7N3G6G", "694", 0, "no description", 60.2], ["695", "I7N3G6G", "695", 0, "no description", 60.2], ["696", "I7N3G6G", "696", 0, "no description", 60.2], ["697", "I7N3G6G", "697", 0, "no description", 60.2], ["698", "I7N3G6G", "698", 0, "no description", 60.2], ["699", "I7N3G6G", "699", 0, "no description", 60.2], ["700", "I7N3G6G", "700", 0, "no description", 60.2], ["701", "I7N3G6G", "701", 0, "no description", 60.2], ["702", "I7N3G6G", "702", 0, "no description", 60.2], ["703", "I7N3G6G", "703", 0, "no description", 60.2], ["704", "I7N3G6G", "704", 0, "no description", 60.2], ["705", "I7N3G6G", "705", 0, "no description", 60.2], ["706", "I7N3G6G", "706", 0, "no description", 60.2], ["707", "I7N3G6G", "707", 0, "no description", 60.2], ["708", "I7N3G6G", "708", 0, "no description", 60.2], ["709", "I7N3G6G", "709", 0, "no description", 60.2], ["710", "I7N3G6G", "710", 0, "no description", 60.2], ["711", "I7N3G6G", "711", 0, "no description", 60.2], ["712", "I7N3G6G", "712", 0, "no description", 60.2], ["713", "I7N3G6G", "713", 0, "no description", 60.2], ["714", "I7N3G6G", "714", 0, "no description", 60.2], ["715", "I7N3G6G", "715", 0, "no description", 60.2], ["716", "I7N3G6G", "716", 0, "no description", 60.2], ["717", "I7N3G6G", "717", 0, "no description", 60.2], ["718", "I7N3G6G", "718", 0, "no description", 60.2], ["719", "I7N3G6G", "719", 0, "no description", 60.2], ["720", "I7N3G6G", "720", 0, "no description", 60.2], ["721", "I7N3G6G", "721", 0, "no description", 60.2], ["722", "I7N3G6G", "722", 0, "no description", 60.2], ["723", "I7N3G6G", "723", 0, "no description", 60.2], ["724", "I7N3G6G", "724", 0, "no description", 60.2], ["725", "I7N3G6G", "725", 0, "no description", 60.2], ["726", "I7N3G6G", "726", 0, "no description", 60.2], ["727", "I7N3G6G", "727", 0, "no description", 60.2], ["728", "I7N3G6G", "728", 0, "no description", 60.2], ["729", "I7N3G6G", "729", 0, "no description", 60.2], ["730", "I7N3G6G", "730", 0, "no description", 60.2], ["731", "I7N3G6G", "731", 0, "no description", 60.2], ["732", "I7N3G6G", "732", 0, "no description", 60.2], ["733", "I7N3G6G", "733", 0, "no description", 60.2], ["734", "I7N3G6G", "734", 0, "no description", 60.2], ["735", "I7N3G6G", "735", 0, "no description", 60.2], ["736", "I7N3G6G", "736", 0, "no description", 60.2], ["737", "I7N3G6G", "737", 0, "no description", 60.2], ["738", "I7N3G6G", "738", 0, "no description", 60.2], ["739", "I7N3G6G", "739", 0, "no description", 60.2], ["740", "I7N3G6G", "740", 0, "no description", 60.2], ["741", "I7N3G6G", "741", 0, "no description", 60.2], ["742", "I7N3G6G", "742", 0, "no description", 60.2], ["743", "I7N3G6G", "743", 0, "no description", 60.2], ["744", "I7N3G6G", "744", 0, "no description", 60.2], ["745", "I7N3G6G", "745", 0, "no description", 60.2], ["746", "I7N3G6G", "746", 0, "no description", 60.2], ["747", "I7N3G6G", "747", 0, "no description", 60.2], ["748", "I7N3G6G", "748", 0, "no description", 60.2], ["749", "I7N3G6G", "749", 0, "no description", 60.2], ["750", "I7N3G6G", "750", 0, "no description", 60.2], ["751", "I7N3G6G", "751", 0, "no description", 60.2], ["752", "I7N3G6G", "752", 0, "no description", 60.2], ["753", "I7N3G6G", "753", 0, "no description", 60.2], ["754", "I7N3G6G", "754", 0, "no description", 60.2], ["755", "I7N3G6G", "755", 0, "no description", 60.2], ["756", "I7N3G6G", "756", 0, "no description", 60.2], ["757", "I7N3G6G", "757", 0, "no description", 60.2], ["758", "I7N3G6G", "758", 0, "no description", 60.2], ["759", "I7N3G6G", "759", 0, "no description", 60.2], ["760", "I7N3G6G", "760", 0, "no description", 60.2], ["761", "I7N3G6G", "761", 0, "no description", 60.2], ["762", "I7N3G6G", "762", 0, "no description", 60.2], ["763", "I7N3G6G", "763", 0, "no description", 60.2], ["764", "I7N3G6G", "764", 0, "no description", 60.2], ["765", "I7N3G6G", "765", 0, "no description", 60.2], ["766", "I7N3G6G", "766", 0, "no description", 60.2], ["767", "I7N3G6G", "767", 0, "no description", 60.2], ["768", "I7N3G6G", "768", 0, "no description", 60.2], ["769", "I7N3G6G", "769", 0, "no description", 60.2], ["770", "I7N3G6G", "770", 0, "no description", 60.2], ["771", "I7N3G6G", "771", 0, "no description", 60.2], ["772", "I7N3G6G", "772", 0, "no description", 60.2], ["773", "I7N3G6G", "773", 0, "no description", 60.2], ["774", "I7N3G6G", "774", 0, "no description", 60.2], ["775", "I7N3G6G", "775", 0, "no description", 60.2], ["776", "I7N3G6G", "776", 0, "no description", 60.2], ["777", "I7N3G6G", "777", 0, "no description", 60.2], ["778", "I7N3G6G", "778", 0, "no description", 60.2], ["779", "I7N3G6G", "779", 0, "no description", 60.2], ["780", "I7N3G6G", "780", 0, "no description", 60.2], ["781", "I7N3G6G", "781", 0, "no description", 60.2], ["782", "I7N3G6G", "782", 0, "no description", 60.2], ["783", "I7N3G6G", "783", 0, "no description", 60.2], ["784", "I7N3G6G", "784", 0, "no description", 60.2], ["785", "I7N3G6G", "785", 0, "no description", 60.2], ["786", "I7N3G6G", "786", 0, "no description", 60.2], ["787", "I7N3G6G", "787", 0, "no description", 60.2], ["788", "I7N3G6G", "788", 0, "no description", 60.2], ["789", "I7N3G6G", "789", 0, "no description", 60.2], ["790", "I7N3G6G", "790", 0, "no description", 60.2], ["791", "I7N3G6G", "791", 0, "no description", 60.2], ["792", "I7N3G6G", "792", 0, "no description", 60.2], ["793", "I7N3G6G", "793", 0, "no description", 60.2], ["794", "I7N3G6G", "794", 0, "no description", 60.2], ["795", "I7N3G6G", "795", 0, "no description", 60.2], ["796", "I7N3G6G", "796", 0, "no description", 60.2], ["797", "I7N3G6G", "797", 0, "no description", 60.2], ["798", "I7N3G6G", "798", 0, "no description", 60.2], ["799", "I7N3G6G", "799", 0, "no description", 60.2], ["800", "I7N3G6G", "800", 0, "no description", 60.2], ["801", "I7N3G6G", "801", 0, "no description", 60.2], ["802", "I7N3G6G", "802", 0, "no description", 60.2], ["803", "I7N3G6G", "803", 0, "no description", 60.2], ["804", "I7N3G6G", "804", 0, "no description", 60.2], ["805", "I7N3G6G", "805", 0, "no description", 60.2], ["806", "I7N3G6G", "806", 0, "no description", 60.2], ["807", "I7N3G6G", "807", 0, "no description", 60.2], ["808", "I7N3G6G", "808", 0, "no description", 60.2], ["809", "I7N3G6G", "809", 0, "no description", 60.2], ["810", "I7N3G6G", "810", 0, "no description", 60.2], ["811", "I7N3G6G", "811", 0, "no description", 60.2], ["812", "I7N3G6G", "812", 0, "no description", 60.2], ["813", "I7N3G6G", "813", 0, "no description", 60.2], ["814", "I7N3G6G", "814", 0, "no description", 60.2], ["815", "I7N3G6G", "815", 0, "no description", 60.2], ["816", "I7N3G6G", "816", 0, "no description", 60.2], ["817", "I7N3G6G", "817", 0, "no description", 60.2], ["818", "I7N3G6G", "818", 0, "no description", 60.2], ["819", "I7N3G6G", "819", 0, "no description", 60.2], ["820", "I7N3G6G", "820", 0, "no description", 60.2], ["821", "I7N3G6G", "821", 0, "no description", 60.2], ["822", "I7N3G6G", "822", 0, "no description", 60.2], ["823", "I7N3G6G", "823", 0, "no description", 60.2], ["824", "I7N3G6G", "824", 0, "no description", 60.2], ["825", "I7N3G6G", "825", 0, "no description", 60.2], ["826", "I7N3G6G", "826", 0, "no description", 60.2], ["827", "I7N3G6G", "827", 0, "no description", 60.2], ["828", "I7N3G6G", "828", 0, "no description", 60.2], ["829", "I7N3G6G", "829", 0, "no description", 60.2], ["830", "I7N3G6G", "830", 0, "no description", 60.2], ["831", "I7N3G6G", "831", 0, "no description", 60.2], ["832", "I7N3G6G", "832", 0, "no description", 60.2], ["833", "I7N3G6G", "833", 0, "no description", 60.2], ["834", "I7N3G6G", "834", 0, "no description", 60.2], ["835", "I7N3G6G", "835", 0, "no description", 60.2], ["836", "I7N3G6G", "836", 0, "no description", 60.2], ["837", "I7N3G6G", "837", 0, "no description", 60.2], ["838", "I7N3G6G", "838", 0, "no description", 60.2], ["839", "I7N3G6G", "839", 0, "no description", 60.2], ["840", "I7N3G6G", "840", 0, "no description", 60.2], ["841", "I7N3G6G", "841", 0, "no description", 60.2], ["842", "I7N3G6G", "842", 0, "no description", 60.2], ["843", "I7N3G6G", "843", 0, "no description", 60.2], ["844", "I7N3G6G", "844", 0, "no description", 60.2], ["845", "I7N3G6G", "845", 0, "no description", 60.2], ["846", "I7N3G6G", "846", 0, "no description", 60.2], ["847", "I7N3G6G", "847", 0, "no description", 60.2], ["848", "I7N3G6G", "848", 0, "no description", 60.2], ["849", "I7N3G6G", "849", 0, "no description", 60.2], ["850", "I7N3G6G", "850", 0, "no description", 60.2], ["851", "I7N3G6G", "851", 0, "no description", 60.2], ["852", "I7N3G6G", "852", 0, "no description", 60.2], ["853", "I7N3G6G", "853", 0, "no description", 60.2], ["854", "I7N3G6G", "854", 0, "no description", 60.2], ["855", "I7N3G6G", "855", 0, "no description", 60.2], ["856", "I7N3G6G", "856", 0, "no description", 60.2], ["857", "I7N3G6G", "857", 0, "no description", 60.2], ["858", "I7N3G6G", "858", 0, "no description", 60.2], ["859", "I7N3G6G", "859", 0, "no description", 60.2], ["860", "I7N3G6G", "860", 0, "no description", 60.2], ["861", "I7N3G6G", "861", 0, "no description", 60.2], ["862", "I7N3G6G", "862", 0, "no description", 60.2], ["863", "I7N3G6G", "863", 0, "no description", 60.2], ["864", "I7N3G6G", "864", 0, "no description", 60.2], ["865", "I7N3G6G", "865", 0, "no description", 60.2], ["866", "I7N3G6G", "866", 0, "no description", 60.2], ["867", "I7N3G6G", "867", 0, "no description", 60.2], ["868", "I7N3G6G", "868", 0, "no description", 60.2], ["869", "I7N3G6G", "869", 0, "no description", 60.2], ["870", "I7N3G6G", "870", 0, "no description", 60.2], ["871", "I7N3G6G", "871", 0, "no description", 60.2], ["872", "I7N3G6G", "872", 0, "no description", 60.2], ["873", "I7N3G6G", "873", 0, "no description", 60.2], ["874", "I7N3G6G", "874", 0, "no description", 60.2], ["875", "I7N3G6G", "875", 0, "no description", 60.2], ["876", "I7N3G6G", "876", 0, "no description", 60.2], ["877", "I7N3G6G", "877", 0, "no description", 60.2], ["878", "I7N3G6G", "878", 0, "no description", 60.2], ["879", "I7N3G6G", "879", 0, "no description", 60.2], ["880", "I7N3G6G", "880", 0, "no description", 60.2], ["881", "I7N3G6G", "881", 0, "no description", 60.2], ["882", "I7N3G6G", "882", 0, "no description", 60.2], ["883", "I7N3G6G", "883", 0, "no description", 60.2], ["884", "I7N3G6G", "884", 0, "no description", 60.2], ["885", "I7N3G6G", "885", 0, "no description", 60.2], ["886", "I7N3G6G", "886", 0, "no description", 60.2], ["887", "I7N3G6G", "887", 0, "no description", 60.2], ["888", "I7N3G6G", "888", 0, "no description", 60.2], ["889", "I7N3G6G", "889", 0, "no description", 60.2], ["890", "I7N3G6G", "890", 0, "no description", 60.2], ["891", "I7N3G6G", "891", 0, "no description", 60.2], ["892", "I7N3G6G", "892", 0, "no description", 60.2], ["893", "I7N3G6G", "893", 0, "no description", 60.2], ["894", "I7N3G6G", "894", 0, "no description", 60.2], ["895", "I7N3G6G", "895", 0, "no description", 60.2], ["896", "I7N3G6G", "896", 0, "no description", 60.2], ["897", "I7N3G6G", "897", 0, "no description", 60.2], ["898", "I7N3G6G", "898", 0, "no description", 60.2], ["899", "I7N3G6G", "899", 0, "no description", 60.2], ["900", "I7N3G6G", "900", 0, "no description", 60.2], ["901", "I7N3G6G", "901", 0, "no description", 60.2], ["902", "I7N3G6G", "902", 0, "no description", 60.2], ["903", "I7N3G6G", "903", 0, "no description", 60.2], ["904", "I7N3G6G", "904", 0, "no description", 60.2], ["905", "I7N3G6G", "905", 0, "no description", 60.2], ["906", "I7N3G6G", "906", 0, "no description", 60.2], ["907", "I7N3G6G", "907", 0, "no description", 60.2], ["908", "I7N3G6G", "908", 0, "no description", 60.2], ["909", "I7N3G6G", "909", 0, "no description", 60.2], ["910", "I7N3G6G", "910", 0, "no description", 60.2], ["911", "I7N3G6G", "911", 0, "no description", 60.2], ["912", "I7N3G6G", "912", 0, "no description", 60.2], ["913", "I7N3G6G", "913", 0, "no description", 60.2], ["914", "I7N3G6G", "914", 0, "no description", 60.2], ["915", "I7N3G6G", "915", 0, "no description", 60.2], ["916", "I7N3G6G", "916", 0, "no description", 60.2], ["917", "I7N3G6G", "917", 0, "no description", 60.2], ["918", "I7N3G6G", "918", 0, "no description", 60.2], ["919", "I7N3G6G", "919", 0, "no description", 60.2], ["920", "I7N3G6G", "920", 0, "no description", 60.2], ["921", "I7N3G6G", "921", 0, "no description", 60.2], ["922", "I7N3G6G", "922", 0, "no description", 60.2], ["923", "I7N3G6G", "923", 0, "no description", 60.2], ["924", "I7N3G6G", "924", 0, "no description", 60.2], ["925", "I7N3G6G", "925", 0, "no description", 60.2], ["926", "I7N3G6G", "926", 0, "no description", 60.2], ["927", "I7N3G6G", "927", 0, "no description", 60.2], ["928", "I7N3G6G", "928", 0, "no description", 60.2], ["929", "I7N3G6G", "929", 0, "no description", 60.2], ["930", "I7N3G6G", "930", 0, "no description", 60.2], ["931", "I7N3G6G", "931", 0, "no description", 60.2], ["932", "I7N3G6G", "932", 0, "no description", 60.2], ["933", "I7N3G6G", "933", 0, "no description", 60.2], ["934", "I7N3G6G", "934", 0, "no description", 60.2], ["935", "I7N3G6G", "935", 0, "no description", 60.2], ["936", "I7N3G6G", "936", 0, "no description", 60.2], ["937", "I7N3G6G", "937", 0, "no description", 60.2], ["938", "I7N3G6G", "938", 0, "no description", 60.2], ["939", "I7N3G6G", "939", 0, "no description", 60.2], ["940", "I7N3G6G", "940", 0, "no description", 60.2], ["941", "I7N3G6G", "941", 0, "no description", 60.2], ["942", "I7N3G6G", "942", 0, "no description", 60.2], ["943", "I7N3G6G", "943", 0, "no description", 60.2], ["944", "I7N3G6G", "944", 0, "no description", 60.2], ["945", "I7N3G6G", "945", 0, "no description", 60.2], ["946", "I7N3G6G", "946", 0, "no description", 60.2], ["947", "I7N3G6G", "947", 0, "no description", 60.2], ["948", "I7N3G6G", "948", 0, "no description", 60.2], ["949", "I7N3G6G", "949", 0, "no description", 60.2], ["950", "I7N3G6G", "950", 0, "no description", 60.2], ["951", "I7N3G6G", "951", 0, "no description", 60.2], ["952", "I7N3G6G", "952", 0, "no description", 60.2], ["953", "I7N3G6G", "953", 0, "no description", 60.2], ["954", "I7N3G6G", "954", 0, "no description", 60.2], ["955", "I7N3G6G", "955", 0, "no description", 60.2], ["956", "I7N3G6G", "956", 0, "no description", 60.2], ["957", "I7N3G6G", "957", 0, "no description", 60.2], ["958", "I7N3G6G", "958", 0, "no description", 60.2], ["959", "I7N3G6G", "959", 0, "no description", 60.2], ["960", "I7N3G6G", "960", 0, "no description", 60.2], ["961", "I7N3G6G", "961", 0, "no description", 60.2], ["962", "I7N3G6G", "962", 0, "no description", 60.2], ["963", "I7N3G6G", "963", 0, "no description", 60.2], ["964", "I7N3G6G", "964", 0, "no description", 60.2], ["965", "I7N3G6G", "965", 0, "no description", 60.2], ["966", "I7N3G6G", "966", 0, "no description", 60.2], ["967", "I7N3G6G", "967", 0, "no description", 60.2], ["968", "I7N3G6G", "968", 0, "no description", 60.2], ["969", "I7N3G6G", "969", 0, "no description", 60.2], ["970", "I7N3G6G", "970", 0, "no description", 60.2], ["971", "I7N3G6G", "971", 0, "no description", 60.2], ["972", "I7N3G6G", "972", 0, "no description", 60.2], ["973", "I7N3G6G", "973", 0, "no description", 60.2], ["974", "I7N3G6G", "974", 0, "no description", 60.2], ["975", "I7N3G6G", "975", 0, "no description", 60.2], ["976", "I7N3G6G", "976", 0, "no description", 60.2], ["977", "I7N3G6G", "977", 0, "no description", 60.2], ["978", "I7N3G6G", "978", 0, "no description", 60.2], ["979", "I7N3G6G", "979", 0, "no description", 60.2], ["980", "I7N3G6G", "980", 0, "no description", 60.2], ["981", "I7N3G6G", "981", 0, "no description", 60.2], ["982", "I7N3G6G", "982", 0, "no description", 60.2], ["983", "I7N3G6G", "983", 0, "no description", 60.2], ["984", "I7N3G6G", "984", 0, "no description", 60.2], ["985", "I7N3G6G", "985", 0, "no description", 60.2], ["986", "I7N3G6G", "986", 0, "no description", 60.2], ["987", "I7N3G6G", "987", 0, "no description", 60.2], ["988", "I7N3G6G", "988", 0, "no description", 60.2], ["989", "I7N3G6G", "989", 0, "no description", 60.2], ["990", "I7N3G6G", "990", 0, "no description", 60.2], ["991", "I7N3G6G", "991", 0, "no description", 60.2], ["992", "I7N3G6G", "992", 0, "no description", 60.2], ["993", "I7N3G6G", "993", 0, "no description", 60.2], ["994", "I7N3G6G", "994", 0, "no description", 60.2], ["995", "I7N3G6G", "995", 0, "no description", 60.2], ["996", "I7N3G6G", "996", 0, "no description", 60.2], ["997", "I7N3G6G", "997", 0, "no description", 60.2], ["998", "I7N3G6G", "998", 0, "no description", 60.2], ["999", "I7N3G6G", "999", 0, "no description", 60.2], ["1000", "I7N3G6G", "1000", 0, "no description", 60.2], ["1001", "I7N3G6G", "1001", 0, "no description", 60.2], ["1002", "I7N3G6G", "1002", 0, "no description", 60.2], ["1003", "I7N3G6G", "1003", 0, "no description", 60.2], ["1004", "I7N3G6G", "1004", 0, "no description", 60.2], ["1005", "I7N3G6G", "1005", 0, "no description", 60.2], ["1006", "I7N3G6G", "1006", 0, "no description", 60.2], ["1007", "I7N3G6G", "1007", 0, "no description", 60.2], ["1008", "I7N3G6G", "1008", 0, "no description", 60.2], ["1009", "I7N3G6G", "1009", 0, "no description", 60.2], ["1010", "I7N3G6G", "1010", 0, "no description", 60.2], ["1011", "I7N3G6G", "1011", 0, "no description", 60.2], ["1012", "I7N3G6G", "1012", 0, "no description", 60.2], ["1013", "I7N3G6G", "1013", 0, "no description", 60.2], ["1014", "I7N3G6G", "1014", 0, "no description", 60.2], ["1015", "I7N3G6G", "1015", 0, "no description", 60.2], ["1016", "I7N3G6G", "1016", 0, "no description", 60.2], ["1017", "I7N3G6G", "1017", 0, "no description", 60.2], ["1018", "I7N3G6G", "1018", 0, "no description", 60.2], ["1019", "I7N3G6G", "1019", 0, "no description", 60.2], ["1020", "I7N3G6G", "1020", 0, "no description", 60.2], ["1021", "I7N3G6G", "1021", 0, "no description", 60.2], ["1022", "I7N3G6G", "1022", 0, "no description", 60.2], ["1023", "I7N3G6G", "1023", 0, "no description", 60.2], ["1024", "I7N3G6G", "1024", 0, "no description", 60.2], ["1025", "I7N3G6G", "1025", 0, "no description", 60.2], ["1026", "I7N3G6G", "1026", 0, "no description", 60.2], ["1027", "I7N3G6G", "1027", 0, "no description", 60.2], ["1028", "I7N3G6G", "1028", 0, "no description", 60.2], ["1029", "I7N3G6G", "1029", 0, "no description", 60.2], ["1030", "I7N3G6G", "1030", 0, "no description", 60.2], ["1031", "I7N3G6G", "1031", 0, "no description", 60.2], ["1032", "I7N3G6G", "1032", 0, "no description", 60.2], ["1033", "I7N3G6G", "1033", 0, "no description", 60.2], ["1034", "I7N3G6G", "1034", 0, "no description", 60.2], ["1035", "I7N3G6G", "1035", 0, "no description", 60.2], ["1036", "I7N3G6G", "1036", 0, "no description", 60.2], ["1037", "I7N3G6G", "1037", 0, "no description", 60.2], ["1038", "I7N3G6G", "1038", 0, "no description", 60.2], ["1039", "I7N3G6G", "1039", 0, "no description", 60.2], ["1040", "I7N3G6G", "1040", 0, "no description", 60.2], ["1041", "I7N3G6G", "1041", 0, "no description", 60.2], ["1042", "I7N3G6G", "1042", 0, "no description", 60.2], ["1043", "I7N3G6G", "1043", 0, "no description", 60.2], ["1044", "I7N3G6G", "1044", 0, "no description", 60.2], ["1045", "I7N3G6G", "1045", 0, "no description", 60.2], ["1046", "I7N3G6G", "1046", 0, "no description", 60.2], ["1047", "I7N3G6G", "1047", 0, "no description", 60.2], ["1048", "I7N3G6G", "1048", 0, "no description", 60.2], ["1049", "I7N3G6G", "1049", 0, "no description", 60.2], ["1050", "I7N3G6G", "1050", 0, "no description", 60.2], ["1051", "I7N3G6G", "1051", 0, "no description", 60.2], ["1052", "I7N3G6G", "1052", 0, "no description", 60.2], ["1053", "I7N3G6G", "1053", 0, "no description", 60.2], ["1054", "I7N3G6G", "1054", 0, "no description", 60.2], ["1055", "I7N3G6G", "1055", 0, "no description", 60.2], ["1056", "I7N3G6G", "1056", 0, "no description", 60.2], ["1057", "I7N3G6G", "1057", 0, "no description", 60.2], ["1058", "I7N3G6G", "1058", 0, "no description", 60.2], ["1059", "I7N3G6G", "1059", 0, "no description", 60.2], ["1060", "I7N3G6G", "1060", 0, "no description", 60.2], ["1061", "I7N3G6G", "1061", 0, "no description", 60.2], ["1062", "I7N3G6G", "1062", 0, "no description", 60.2], ["1063", "I7N3G6G", "1063", 0, "no description", 60.2], ["1064", "I7N3G6G", "1064", 0, "no description", 60.2], ["1065", "I7N3G6G", "1065", 0, "no description", 60.2], ["1066", "I7N3G6G", "1066", 0, "no description", 60.2], ["1067", "I7N3G6G", "1067", 0, "no description", 60.2], ["1068", "I7N3G6G", "1068", 0, "no description", 60.2], ["1069", "I7N3G6G", "1069", 0, "no description", 60.2], ["1070", "I7N3G6G", "1070", 0, "no description", 60.2], ["1071", "I7N3G6G", "1071", 0, "no description", 60.2], ["1072", "I7N3G6G", "1072", 0, "no description", 60.2], ["1073", "I7N3G6G", "1073", 0, "no description", 60.2], ["1074", "I7N3G6G", "1074", 0, "no description", 60.2], ["1075", "I7N3G6G", "1075", 0, "no description", 60.2], ["1076", "I7N3G6G", "1076", 0, "no description", 60.2], ["1077", "I7N3G6G", "1077", 0, "no description", 60.2], ["1078", "I7N3G6G", "1078", 0, "no description", 60.2], ["1079", "I7N3G6G", "1079", 0, "no description", 60.2], ["1080", "I7N3G6G", "1080", 0, "no description", 60.2], ["1081", "I7N3G6G", "1081", 0, "no description", 60.2], ["1082", "I7N3G6G", "1082", 0, "no description", 60.2], ["1083", "I7N3G6G", "1083", 0, "no description", 60.2], ["1084", "I7N3G6G", "1084", 0, "no description", 60.2], ["1085", "I7N3G6G", "1085", 0, "no description", 60.2], ["1086", "I7N3G6G", "1086", 0, "no description", 60.2], ["1087", "I7N3G6G", "1087", 0, "no description", 60.2], ["1088", "I7N3G6G", "1088", 0, "no description", 60.2], ["1089", "I7N3G6G", "1089", 0, "no description", 60.2], ["1090", "I7N3G6G", "1090", 0, "no description", 60.2], ["1091", "I7N3G6G", "1091", 0, "no description", 60.2], ["1092", "I7N3G6G", "1092", 0, "no description", 60.2], ["1093", "I7N3G6G", "1093", 0, "no description", 60.2], ["1094", "I7N3G6G", "1094", 0, "no description", 60.2], ["1095", "I7N3G6G", "1095", 0, "no description", 60.2], ["1096", "I7N3G6G", "1096", 0, "no description", 60.2], ["1097", "I7N3G6G", "1097", 0, "no description", 60.2], ["1098", "I7N3G6G", "1098", 0, "no description", 60.2], ["1099", "I7N3G6G", "1099", 0, "no description", 60.2], ["1100", "I7N3G6G", "1100", 0, "no description", 60.2], ["1101", "I7N3G6G", "1101", 0, "no description", 60.2], ["1102", "I7N3G6G", "1102", 0, "no description", 60.2], ["1103", "I7N3G6G", "1103", 0, "no description", 60.2], ["1104", "I7N3G6G", "1104", 0, "no description", 60.2], ["1105", "I7N3G6G", "1105", 0, "no description", 60.2], ["1106", "I7N3G6G", "1106", 0, "no description", 60.2], ["1107", "I7N3G6G", "1107", 0, "no description", 60.2], ["1108", "I7N3G6G", "1108", 0, "no description", 60.2], ["1109", "I7N3G6G", "1109", 0, "no description", 60.2], ["1110", "I7N3G6G", "1110", 0, "no description", 60.2], ["1111", "I7N3G6G", "1111", 0, "no description", 60.2], ["1112", "I7N3G6G", "1112", 0, "no description", 60.2], ["1113", "I7N3G6G", "1113", 0, "no description", 60.2], ["1114", "I7N3G6G", "1114", 0, "no description", 60.2], ["1115", "I7N3G6G", "1115", 0, "no description", 60.2], ["1116", "I7N3G6G", "1116", 0, "no description", 60.2], ["1117", "I7N3G6G", "1117", 0, "no description", 60.2], ["1118", "I7N3G6G", "1118", 0, "no description", 60.2], ["1119", "I7N3G6G", "1119", 0, "no description", 60.2], ["1120", "I7N3G6G", "1120", 0, "no description", 60.2], ["1121", "I7N3G6G", "1121", 0, "no description", 60.2], ["1122", "I7N3G6G", "1122", 0, "no description", 60.2], ["1123", "I7N3G6G", "1123", 0, "no description", 60.2], ["1124", "I7N3G6G", "1124", 0, "no description", 60.2], ["1125", "I7N3G6G", "1125", 0, "no description", 60.2], ["1126", "I7N3G6G", "1126", 0, "no description", 60.2], ["1127", "I7N3G6G", "1127", 0, "no description", 60.2], ["1128", "I7N3G6G", "1128", 0, "no description", 60.2], ["1129", "I7N3G6G", "1129", 0, "no description", 60.2], ["1130", "I7N3G6G", "1130", 0, "no description", 60.2], ["1131", "I7N3G6G", "1131", 0, "no description", 60.2], ["1132", "I7N3G6G", "1132", 0, "no description", 60.2], ["1133", "I7N3G6G", "1133", 0, "no description", 60.2], ["1134", "I7N3G6G", "1134", 0, "no description", 60.2], ["1135", "I7N3G6G", "1135", 0, "no description", 60.2], ["1136", "I7N3G6G", "1136", 0, "no description", 60.2], ["1137", "I7N3G6G", "1137", 0, "no description", 60.2], ["1138", "I7N3G6G", "1138", 0, "no description", 60.2], ["1139", "I7N3G6G", "1139", 0, "no description", 60.2], ["1140", "I7N3G6G", "1140", 0, "no description", 60.2], ["1141", "I7N3G6G", "1141", 0, "no description", 60.2], ["1142", "I7N3G6G", "1142", 0, "no description", 60.2], ["1143", "I7N3G6G", "1143", 0, "no description", 60.2], ["1144", "I7N3G6G", "1144", 0, "no description", 60.2], ["1145", "I7N3G6G", "1145", 0, "no description", 60.2], ["1146", "I7N3G6G", "1146", 0, "no description", 60.2], ["1147", "I7N3G6G", "1147", 0, "no description", 60.2], ["1148", "I7N3G6G", "1148", 0, "no description", 60.2], ["1149", "I7N3G6G", "1149", 0, "no description", 60.2], ["1150", "I7N3G6G", "1150", 0, "no description", 60.2], ["1151", "I7N3G6G", "1151", 0, "no description", 60.2], ["1152", "I7N3G6G", "1152", 0, "no description", 60.2], ["1153", "I7N3G6G", "1153", 0, "no description", 60.2], ["1154", "I7N3G6G", "1154", 0, "no description", 60.2], ["1155", "I7N3G6G", "1155", 0, "no description", 60.2], ["1156", "I7N3G6G", "1156", 0, "no description", 60.2], ["1157", "I7N3G6G", "1157", 0, "no description", 60.2], ["1158", "I7N3G6G", "1158", 0, "no description", 60.2], ["1159", "I7N3G6G", "1159", 0, "no description", 60.2], ["1160", "I7N3G6G", "1160", 0, "no description", 60.2], ["1161", "I7N3G6G", "1161", 0, "no description", 60.2], ["1162", "I7N3G6G", "1162", 0, "no description", 60.2], ["1163", "I7N3G6G", "1163", 0, "no description", 60.2], ["1164", "I7N3G6G", "1164", 0, "no description", 60.2], ["1165", "I7N3G6G", "1165", 0, "no description", 60.2], ["1166", "I7N3G6G", "1166", 0, "no description", 60.2], ["1167", "I7N3G6G", "1167", 0, "no description", 60.2], ["1168", "I7N3G6G", "1168", 0, "no description", 60.2], ["1169", "I7N3G6G", "1169", 0, "no description", 60.2], ["1170", "I7N3G6G", "1170", 0, "no description", 60.2], ["1171", "I7N3G6G", "1171", 0, "no description", 60.2], ["1172", "I7N3G6G", "1172", 0, "no description", 60.2], ["1173", "I7N3G6G", "1173", 0, "no description", 60.2], ["1174", "I7N3G6G", "1174", 0, "no description", 60.2], ["1175", "I7N3G6G", "1175", 0, "no description", 60.2], ["1176", "I7N3G6G", "1176", 0, "no description", 60.2], ["1177", "I7N3G6G", "1177", 0, "no description", 60.2], ["1178", "I7N3G6G", "1178", 0, "no description", 60.2], ["1179", "I7N3G6G", "1179", 0, "no description", 60.2], ["1180", "I7N3G6G", "1180", 0, "no description", 60.2], ["1181", "I7N3G6G", "1181", 0, "no description", 60.2], ["1182", "I7N3G6G", "1182", 0, "no description", 60.2], ["1183", "I7N3G6G", "1183", 0, "no description", 60.2], ["1184", "I7N3G6G", "1184", 0, "no description", 60.2], ["1185", "I7N3G6G", "1185", 0, "no description", 60.2], ["1186", "I7N3G6G", "1186", 0, "no description", 60.2], ["1187", "I7N3G6G", "1187", 0, "no description", 60.2], ["1188", "I7N3G6G", "1188", 0, "no description", 60.2], ["1189", "I7N3G6G", "1189", 0, "no description", 60.2], ["1190", "I7N3G6G", "1190", 0, "no description", 60.2], ["1191", "I7N3G6G", "1191", 0, "no description", 60.2], ["1192", "I7N3G6G", "1192", 0, "no description", 60.2], ["1193", "I7N3G6G", "1193", 0, "no description", 60.2], ["1194", "I7N3G6G", "1194", 0, "no description", 60.2], ["1195", "I7N3G6G", "1195", 0, "no description", 60.2], ["1196", "I7N3G6G", "1196", 0, "no description", 60.2], ["1197", "I7N3G6G", "1197", 0, "no description", 60.2], ["1198", "I7N3G6G", "1198", 0, "no description", 60.2], ["1199", "I7N3G6G", "1199", 0, "no description", 60.2], ["1200", "I7N3G6G", "1200", 0, "no description", 60.2], ["1201", "I7N3G6G", "1201", 0, "no description", 60.2], ["1202", "I7N3G6G", "1202", 0, "no description", 60.2], ["1203", "I7N3G6G", "1203", 0, "no description", 60.2], ["1204", "I7N3G6G", "1204", 0, "no description", 60.2], ["1205", "I7N3G6G", "1205", 0, "no description", 60.2], ["1206", "I7N3G6G", "1206", 0, "no description", 60.2], ["1207", "I7N3G6G", "1207", 0, "no description", 60.2], ["1208", "I7N3G6G", "1208", 0, "no description", 60.2], ["1209", "I7N3G6G", "1209", 0, "no description", 60.2], ["1210", "I7N3G6G", "1210", 0, "no description", 60.2], ["1211", "I7N3G6G", "1211", 0, "no description", 60.2], ["1212", "I7N3G6G", "1212", 0, "no description", 60.2], ["1213", "I7N3G6G", "1213", 0, "no description", 60.2], ["1214", "I7N3G6G", "1214", 0, "no description", 60.2], ["1215", "I7N3G6G", "1215", 0, "no description", 60.2], ["1216", "I7N3G6G", "1216", 0, "no description", 60.2], ["1217", "I7N3G6G", "1217", 0, "no description", 60.2], ["1218", "I7N3G6G", "1218", 0, "no description", 60.2], ["1219", "I7N3G6G", "1219", 0, "no description", 60.2], ["1220", "I7N3G6G", "1220", 0, "no description", 60.2], ["1221", "I7N3G6G", "1221", 0, "no description", 60.2], ["1222", "I7N3G6G", "1222", 0, "no description", 60.2], ["1223", "I7N3G6G", "1223", 0, "no description", 60.2], ["1224", "I7N3G6G", "1224", 0, "no description", 60.2], ["1225", "I7N3G6G", "1225", 0, "no description", 60.2], ["1226", "I7N3G6G", "1226", 0, "no description", 60.2], ["1227", "I7N3G6G", "1227", 0, "no description", 60.2], ["1228", "I7N3G6G", "1228", 0, "no description", 60.2], ["1229", "I7N3G6G", "1229", 0, "no description", 60.2], ["1230", "I7N3G6G", "1230", 0, "no description", 60.2], ["1231", "I7N3G6G", "1231", 0, "no description", 60.2], ["1232", "I7N3G6G", "1232", 0, "no description", 60.2], ["1233", "I7N3G6G", "1233", 0, "no description", 60.2], ["1234", "I7N3G6G", "1234", 0, "no description", 60.2], ["1235", "I7N3G6G", "1235", 0, "no description", 60.2], ["1236", "I7N3G6G", "1236", 0, "no description", 60.2], ["1237", "I7N3G6G", "1237", 0, "no description", 60.2], ["1238", "I7N3G6G", "1238", 0, "no description", 60.2], ["1239", "I7N3G6G", "1239", 0, "no description", 60.2], ["1240", "I7N3G6G", "1240", 0, "no description", 60.2], ["1241", "I7N3G6G", "1241", 0, "no description", 60.2], ["1242", "I7N3G6G", "1242", 0, "no description", 60.2], ["1243", "I7N3G6G", "1243", 0, "no description", 60.2], ["1244", "I7N3G6G", "1244", 0, "no description", 60.2], ["1245", "I7N3G6G", "1245", 0, "no description", 60.2], ["1246", "I7N3G6G", "1246", 0, "no description", 60.2], ["1247", "I7N3G6G", "1247", 0, "no description", 60.2], ["1248", "I7N3G6G", "1248", 0, "no description", 60.2], ["1249", "I7N3G6G", "1249", 0, "no description", 60.2], ["1250", "I7N3G6G", "1250", 0, "no description", 60.2], ["1251", "I7N3G6G", "1251", 0, "no description", 60.2], ["1252", "I7N3G6G", "1252", 0, "no description", 60.2], ["1253", "I7N3G6G", "1253", 0, "no description", 60.2], ["1254", "I7N3G6G", "1254", 0, "no description", 60.2], ["1255", "I7N3G6G", "1255", 0, "no description", 60.2], ["1256", "I7N3G6G", "1256", 0, "no description", 60.2], ["1257", "I7N3G6G", "1257", 0, "no description", 60.2], ["1258", "I7N3G6G", "1258", 0, "no description", 60.2], ["1259", "I7N3G6G", "1259", 0, "no description", 60.2], ["1260", "I7N3G6G", "1260", 0, "no description", 60.2], ["1261", "I7N3G6G", "1261", 0, "no description", 60.2], ["1262", "I7N3G6G", "1262", 0, "no description", 60.2], ["1263", "I7N3G6G", "1263", 0, "no description", 60.2], ["1264", "I7N3G6G", "1264", 0, "no description", 60.2], ["1265", "I7N3G6G", "1265", 0, "no description", 60.2], ["1266", "I7N3G6G", "1266", 0, "no description", 60.2], ["1267", "I7N3G6G", "1267", 0, "no description", 60.2], ["1268", "I7N3G6G", "1268", 0, "no description", 60.2], ["1269", "I7N3G6G", "1269", 0, "no description", 60.2], ["1270", "I7N3G6G", "1270", 0, "no description", 60.2], ["1271", "I7N3G6G", "1271", 0, "no description", 60.2], ["1272", "I7N3G6G", "1272", 0, "no description", 60.2], ["1273", "I7N3G6G", "1273", 0, "no description", 60.2], ["1274", "I7N3G6G", "1274", 0, "no description", 60.2], ["1275", "I7N3G6G", "1275", 0, "no description", 60.2], ["1276", "I7N3G6G", "1276", 0, "no description", 60.2], ["1277", "I7N3G6G", "1277", 0, "no description", 60.2], ["1278", "I7N3G6G", "1278", 0, "no description", 60.2], ["1279", "I7N3G6G", "1279", 0, "no description", 60.2], ["1280", "I7N3G6G", "1280", 0, "no description", 60.2], ["1281", "I7N3G6G", "1281", 0, "no description", 60.2], ["1282", "I7N3G6G", "1282", 0, "no description", 60.2], ["1283", "I7N3G6G", "1283", 0, "no description", 60.2], ["1284", "I7N3G6G", "1284", 0, "no description", 60.2], ["1285", "I7N3G6G", "1285", 0, "no description", 60.2], ["1286", "I7N3G6G", "1286", 0, "no description", 60.2], ["1287", "I7N3G6G", "1287", 0, "no description", 60.2], ["1288", "I7N3G6G", "1288", 0, "no description", 60.2], ["1289", "I7N3G6G", "1289", 0, "no description", 60.2], ["1290", "I7N3G6G", "1290", 0, "no description", 60.2], ["1291", "I7N3G6G", "1291", 0, "no description", 60.2], ["1292", "I7N3G6G", "1292", 0, "no description", 60.2], ["1293", "I7N3G6G", "1293", 0, "no description", 60.2], ["1294", "I7N3G6G", "1294", 0, "no description", 60.2], ["1295", "I7N3G6G", "1295", 0, "no description", 60.2], ["1296", "I7N3G6G", "1296", 0, "no description", 60.2], ["1297", "I7N3G6G", "1297", 0, "no description", 60.2], ["1298", "I7N3G6G", "1298", 0, "no description", 60.2], ["1299", "I7N3G6G", "1299", 0, "no description", 60.2], ["1300", "I7N3G6G", "1300", 0, "no description", 60.2], ["1301", "I7N3G6G", "1301", 0, "no description", 60.2], ["1302", "I7N3G6G", "1302", 0, "no description", 60.2], ["1303", "I7N3G6G", "1303", 0, "no description", 60.2], ["1304", "I7N3G6G", "1304", 0, "no description", 60.2], ["1305", "I7N3G6G", "1305", 0, "no description", 60.2], ["1306", "I7N3G6G", "1306", 0, "no description", 60.2], ["1307", "I7N3G6G", "1307", 0, "no description", 60.2], ["1308", "I7N3G6G", "1308", 0, "no description", 60.2], ["1309", "I7N3G6G", "1309", 0, "no description", 60.2], ["1310", "I7N3G6G", "1310", 0, "no description", 60.2], ["1311", "I7N3G6G", "1311", 0, "no description", 60.2], ["1312", "I7N3G6G", "1312", 0, "no description", 60.2], ["1313", "I7N3G6G", "1313", 0, "no description", 60.2], ["1314", "I7N3G6G", "1314", 0, "no description", 60.2], ["1315", "I7N3G6G", "1315", 0, "no description", 60.2], ["1316", "I7N3G6G", "1316", 0, "no description", 60.2], ["1317", "I7N3G6G", "1317", 0, "no description", 60.2], ["1318", "I7N3G6G", "1318", 0, "no description", 60.2], ["1319", "I7N3G6G", "1319", 0, "no description", 60.2], ["1320", "I7N3G6G", "1320", 0, "no description", 60.2], ["1321", "I7N3G6G", "1321", 0, "no description", 60.2], ["1322", "I7N3G6G", "1322", 0, "no description", 60.2], ["1323", "I7N3G6G", "1323", 0, "no description", 60.2], ["1324", "I7N3G6G", "1324", 0, "no description", 60.2], ["1325", "I7N3G6G", "1325", 0, "no description", 60.2], ["1326", "I7N3G6G", "1326", 0, "no description", 60.2], ["1327", "I7N3G6G", "1327", 0, "no description", 60.2], ["1328", "I7N3G6G", "1328", 0, "no description", 60.2], ["1329", "I7N3G6G", "1329", 0, "no description", 60.2], ["1330", "I7N3G6G", "1330", 0, "no description", 60.2], ["1331", "I7N3G6G", "1331", 0, "no description", 60.2], ["1332", "I7N3G6G", "1332", 0, "no description", 60.2], ["1333", "I7N3G6G", "1333", 0, "no description", 60.2], ["1334", "I7N3G6G", "1334", 0, "no description", 60.2], ["1335", "I7N3G6G", "1335", 0, "no description", 60.2], ["1336", "I7N3G6G", "1336", 0, "no description", 60.2], ["1337", "I7N3G6G", "1337", 0, "no description", 60.2], ["1338", "I7N3G6G", "1338", 0, "no description", 60.2], ["1339", "I7N3G6G", "1339", 0, "no description", 60.2], ["1340", "I7N3G6G", "1340", 0, "no description", 60.2], ["1341", "I7N3G6G", "1341", 0, "no description", 60.2], ["1342", "I7N3G6G", "1342", 0, "no description", 60.2], ["1343", "I7N3G6G", "1343", 0, "no description", 60.2], ["1344", "I7N3G6G", "1344", 0, "no description", 60.2], ["1345", "I7N3G6G", "1345", 0, "no description", 60.2], ["1346", "I7N3G6G", "1346", 0, "no description", 60.2], ["1347", "I7N3G6G", "1347", 0, "no description", 60.2], ["1348", "I7N3G6G", "1348", 0, "no description", 60.2], ["1349", "I7N3G6G", "1349", 0, "no description", 60.2], ["1350", "I7N3G6G", "1350", 0, "no description", 60.2], ["1351", "I7N3G6G", "1351", 0, "no description", 60.2], ["1352", "I7N3G6G", "1352", 0, "no description", 60.2], ["1353", "I7N3G6G", "1353", 0, "no description", 60.2], ["1354", "I7N3G6G", "1354", 0, "no description", 60.2], ["1355", "I7N3G6G", "1355", 0, "no description", 60.2], ["1356", "I7N3G6G", "1356", 0, "no description", 60.2], ["1357", "I7N3G6G", "1357", 0, "no description", 60.2], ["1358", "I7N3G6G", "1358", 0, "no description", 60.2], ["1359", "I7N3G6G", "1359", 0, "no description", 60.2], ["1360", "I7N3G6G", "1360", 0, "no description", 60.2], ["1361", "I7N3G6G", "1361", 0, "no description", 60.2], ["1362", "I7N3G6G", "1362", 0, "no description", 60.2], ["1363", "I7N3G6G", "1363", 0, "no description", 60.2], ["1364", "I7N3G6G", "1364", 0, "no description", 60.2], ["1365", "I7N3G6G", "1365", 0, "no description", 60.2], ["1366", "I7N3G6G", "1366", 0, "no description", 60.2], ["1367", "I7N3G6G", "1367", 0, "no description", 60.2], ["1368", "I7N3G6G", "1368", 0, "no description", 60.2], ["1369", "I7N3G6G", "1369", 0, "no description", 60.2], ["1370", "I7N3G6G", "1370", 0, "no description", 60.2], ["1371", "I7N3G6G", "1371", 0, "no description", 60.2], ["1372", "I7N3G6G", "1372", 0, "no description", 60.2], ["1373", "I7N3G6G", "1373", 0, "no description", 60.2], ["1374", "I7N3G6G", "1374", 0, "no description", 60.2], ["1375", "I7N3G6G", "1375", 0, "no description", 60.2], ["1376", "I7N3G6G", "1376", 0, "no description", 60.2], ["1377", "I7N3G6G", "1377", 0, "no description", 60.2], ["1378", "I7N3G6G", "1378", 0, "no description", 60.2], ["1379", "I7N3G6G", "1379", 0, "no description", 60.2], ["1380", "I7N3G6G", "1380", 0, "no description", 60.2], ["1381", "I7N3G6G", "1381", 0, "no description", 60.2], ["1382", "I7N3G6G", "1382", 0, "no description", 60.2], ["1383", "I7N3G6G", "1383", 0, "no description", 60.2], ["1384", "I7N3G6G", "1384", 0, "no description", 60.2], ["1385", "I7N3G6G", "1385", 0, "no description", 60.2], ["1386", "I7N3G6G", "1386", 0, "no description", 60.2], ["1387", "I7N3G6G", "1387", 0, "no description", 60.2], ["1388", "I7N3G6G", "1388", 0, "no description", 60.2], ["1389", "I7N3G6G", "1389", 0, "no description", 60.2], ["1390", "I7N3G6G", "1390", 0, "no description", 60.2], ["1391", "I7N3G6G", "1391", 0, "no description", 60.2], ["1392", "I7N3G6G", "1392", 0, "no description", 60.2], ["1393", "I7N3G6G", "1393", 0, "no description", 60.2], ["1394", "I7N3G6G", "1394", 0, "no description", 60.2], ["1395", "I7N3G6G", "1395", 0, "no description", 60.2], ["1396", "I7N3G6G", "1396", 0, "no description", 60.2], ["1397", "I7N3G6G", "1397", 0, "no description", 60.2], ["1398", "I7N3G6G", "1398", 0, "no description", 60.2], ["1399", "I7N3G6G", "1399", 0, "no description", 60.2], ["1400", "I7N3G6G", "1400", 0, "no description", 60.2], ["1401", "I7N3G6G", "1401", 0, "no description", 60.2], ["1402", "I7N3G6G", "1402", 0, "no description", 60.2], ["1403", "I7N3G6G", "1403", 0, "no description", 60.2], ["1404", "I7N3G6G", "1404", 0, "no description", 60.2], ["1405", "I7N3G6G", "1405", 0, "no description", 60.2], ["1406", "I7N3G6G", "1406", 0, "no description", 60.2], ["1407", "I7N3G6G", "1407", 0, "no description", 60.2], ["1408", "I7N3G6G", "1408", 0, "no description", 60.2], ["1409", "I7N3G6G", "1409", 0, "no description", 60.2], ["1410", "I7N3G6G", "1410", 0, "no description", 60.2], ["1411", "I7N3G6G", "1411", 0, "no description", 60.2], ["1412", "I7N3G6G", "1412", 0, "no description", 60.2], ["1413", "I7N3G6G", "1413", 0, "no description", 60.2], ["1414", "I7N3G6G", "1414", 0, "no description", 60.2], ["1415", "I7N3G6G", "1415", 0, "no description", 60.2], ["1416", "I7N3G6G", "1416", 0, "no description", 60.2], ["1417", "I7N3G6G", "1417", 0, "no description", 60.2], ["1418", "I7N3G6G", "1418", 0, "no description", 60.2], ["1419", "I7N3G6G", "1419", 0, "no description", 60.2], ["1420", "I7N3G6G", "1420", 0, "no description", 60.2], ["1421", "I7N3G6G", "1421", 0, "no description", 60.2], ["1422", "I7N3G6G", "1422", 0, "no description", 60.2], ["1423", "I7N3G6G", "1423", 0, "no description", 60.2], ["1424", "I7N3G6G", "1424", 0, "no description", 60.2], ["1425", "I7N3G6G", "1425", 0, "no description", 60.2], ["1426", "I7N3G6G", "1426", 0, "no description", 60.2], ["1427", "I7N3G6G", "1427", 0, "no description", 60.2], ["1428", "I7N3G6G", "1428", 0, "no description", 60.2], ["1429", "I7N3G6G", "1429", 0, "no description", 60.2], ["1430", "I7N3G6G", "1430", 0, "no description", 60.2], ["1431", "I7N3G6G", "1431", 0, "no description", 60.2], ["1432", "I7N3G6G", "1432", 0, "no description", 60.2], ["1433", "I7N3G6G", "1433", 0, "no description", 60.2], ["1434", "I7N3G6G", "1434", 0, "no description", 60.2], ["1435", "I7N3G6G", "1435", 0, "no description", 60.2], ["1436", "I7N3G6G", "1436", 0, "no description", 60.2], ["1437", "I7N3G6G", "1437", 0, "no description", 60.2], ["1438", "I7N3G6G", "1438", 0, "no description", 60.2], ["1439", "I7N3G6G", "1439", 0, "no description", 60.2], ["1440", "I7N3G6G", "1440", 0, "no description", 60.2], ["1441", "I7N3G6G", "1441", 0, "no description", 60.2], ["1442", "I7N3G6G", "1442", 0, "no description", 60.2], ["1443", "I7N3G6G", "1443", 0, "no description", 60.2], ["1444", "I7N3G6G", "1444", 0, "no description", 60.2], ["1445", "I7N3G6G", "1445", 0, "no description", 60.2], ["1446", "I7N3G6G", "1446", 0, "no description", 60.2], ["1447", "I7N3G6G", "1447", 0, "no description", 60.2], ["1448", "I7N3G6G", "1448", 0, "no description", 60.2], ["1449", "I7N3G6G", "1449", 0, "no description", 60.2], ["1450", "I7N3G6G", "1450", 0, "no description", 60.2], ["1451", "I7N3G6G", "1451", 0, "no description", 60.2], ["1452", "I7N3G6G", "1452", 0, "no description", 60.2], ["1453", "I7N3G6G", "1453", 0, "no description", 60.2], ["1454", "I7N3G6G", "1454", 0, "no description", 60.2], ["1455", "I7N3G6G", "1455", 0, "no description", 60.2], ["1456", "I7N3G6G", "1456", 0, "no description", 60.2], ["1457", "I7N3G6G", "1457", 0, "no description", 60.2], ["1458", "I7N3G6G", "1458", 0, "no description", 60.2], ["1459", "I7N3G6G", "1459", 0, "no description", 60.2], ["1460", "I7N3G6G", "1460", 0, "no description", 60.2], ["1461", "I7N3G6G", "1461", 0, "no description", 60.2], ["1462", "I7N3G6G", "1462", 0, "no description", 60.2], ["1463", "I7N3G6G", "1463", 0, "no description", 60.2], ["1464", "I7N3G6G", "1464", 0, "no description", 60.2], ["1465", "I7N3G6G", "1465", 0, "no description", 60.2], ["1466", "I7N3G6G", "1466", 0, "no description", 60.2], ["1467", "I7N3G6G", "1467", 0, "no description", 60.2], ["1468", "I7N3G6G", "1468", 0, "no description", 60.2], ["1469", "I7N3G6G", "1469", 0, "no description", 60.2], ["1470", "I7N3G6G", "1470", 0, "no description", 60.2], ["1471", "I7N3G6G", "1471", 0, "no description", 60.2], ["1472", "I7N3G6G", "1472", 0, "no description", 60.2], ["1473", "I7N3G6G", "1473", 0, "no description", 60.2], ["1474", "I7N3G6G", "1474", 0, "no description", 60.2], ["1475", "I7N3G6G", "1475", 0, "no description", 60.2], ["1476", "I7N3G6G", "1476", 0, "no description", 60.2], ["1477", "I7N3G6G", "1477", 0, "no description", 60.2], ["1478", "I7N3G6G", "1478", 0, "no description", 60.2], ["1479", "I7N3G6G", "1479", 0, "no description", 60.2], ["1480", "I7N3G6G", "1480", 0, "no description", 60.2], ["1481", "I7N3G6G", "1481", 0, "no description", 60.2], ["1482", "I7N3G6G", "1482", 0, "no description", 60.2], ["1483", "I7N3G6G", "1483", 0, "no description", 60.2], ["1484", "I7N3G6G", "1484", 0, "no description", 60.2], ["1485", "I7N3G6G", "1485", 0, "no description", 60.2], ["1486", "I7N3G6G", "1486", 0, "no description", 60.2], ["1487", "I7N3G6G", "1487", 0, "no description", 60.2], ["1488", "I7N3G6G", "1488", 0, "no description", 60.2], ["1489", "I7N3G6G", "1489", 0, "no description", 60.2], ["1490", "I7N3G6G", "1490", 0, "no description", 60.2], ["1491", "I7N3G6G", "1491", 0, "no description", 60.2], ["1492", "I7N3G6G", "1492", 0, "no description", 60.2], ["1493", "I7N3G6G", "1493", 0, "no description", 60.2], ["1494", "I7N3G6G", "1494", 0, "no description", 60.2], ["1495", "I7N3G6G", "1495", 0, "no description", 60.2], ["1496", "I7N3G6G", "1496", 0, "no description", 60.2], ["1497", "I7N3G6G", "1497", 0, "no description", 60.2], ["1498", "I7N3G6G", "1498", 0, "no description", 60.2], ["1499", "I7N3G6G", "1499", 0, "no description", 60.2], ["1500", "I7N3G6G", "1500", 0, "no description", 60.2], ["1501", "I7N3G6G", "1501", 0, "no description", 60.2], ["1502", "I7N3G6G", "1502", 0, "no description", 60.2], ["1503", "I7N3G6G", "1503", 0, "no description", 60.2], ["1504", "I7N3G6G", "1504", 0, "no description", 60.2], ["1505", "I7N3G6G", "1505", 0, "no description", 60.2], ["1506", "I7N3G6G", "1506", 0, "no description", 60.2], ["1507", "I7N3G6G", "1507", 0, "no description", 60.2], ["1508", "I7N3G6G", "1508", 0, "no description", 60.2], ["1509", "I7N3G6G", "1509", 0, "no description", 60.2], ["1510", "I7N3G6G", "1510", 0, "no description", 60.2], ["1511", "I7N3G6G", "1511", 0, "no description", 60.2], ["1512", "I7N3G6G", "1512", 0, "no description", 60.2], ["1513", "I7N3G6G", "1513", 0, "no description", 60.2], ["1514", "I7N3G6G", "1514", 0, "no description", 60.2], ["1515", "I7N3G6G", "1515", 0, "no description", 60.2], ["1516", "I7N3G6G", "1516", 0, "no description", 60.2], ["1517", "I7N3G6G", "1517", 0, "no description", 60.2], ["1518", "I7N3G6G", "1518", 0, "no description", 60.2], ["1519", "I7N3G6G", "1519", 0, "no description", 60.2], ["1520", "I7N3G6G", "1520", 0, "no description", 60.2], ["1521", "I7N3G6G", "1521", 0, "no description", 60.2], ["1522", "I7N3G6G", "1522", 0, "no description", 60.2], ["1523", "I7N3G6G", "1523", 0, "no description", 60.2], ["1524", "I7N3G6G", "1524", 0, "no description", 60.2], ["1525", "I7N3G6G", "1525", 0, "no description", 60.2], ["1526", "I7N3G6G", "1526", 0, "no description", 60.2], ["1527", "I7N3G6G", "1527", 0, "no description", 60.2], ["1528", "I7N3G6G", "1528", 0, "no description", 60.2], ["1529", "I7N3G6G", "1529", 0, "no description", 60.2], ["1530", "I7N3G6G", "1530", 0, "no description", 60.2], ["1531", "I7N3G6G", "1531", 0, "no description", 60.2], ["1532", "I7N3G6G", "1532", 0, "no description", 60.2], ["1533", "I7N3G6G", "1533", 0, "no description", 60.2], ["1534", "I7N3G6G", "1534", 0, "no description", 60.2], ["1535", "I7N3G6G", "1535", 0, "no description", 60.2], ["1536", "I7N3G6G", "1536", 0, "no description", 60.2], ["1537", "I7N3G6G", "1537", 0, "no description", 60.2], ["1538", "I7N3G6G", "1538", 0, "no description", 60.2], ["1539", "I7N3G6G", "1539", 0, "no description", 60.2], ["1540", "I7N3G6G", "1540", 0, "no description", 60.2], ["1541", "I7N3G6G", "1541", 0, "no description", 60.2], ["1542", "I7N3G6G", "1542", 0, "no description", 60.2], ["1543", "I7N3G6G", "1543", 0, "no description", 60.2], ["1544", "I7N3G6G", "1544", 0, "no description", 60.2], ["1545", "I7N3G6G", "1545", 0, "no description", 60.2], ["1546", "I7N3G6G", "1546", 0, "no description", 60.2], ["1547", "I7N3G6G", "1547", 0, "no description", 60.2], ["1548", "I7N3G6G", "1548", 0, "no description", 60.2], ["1549", "I7N3G6G", "1549", 0, "no description", 60.2], ["1550", "I7N3G6G", "1550", 0, "no description", 60.2], ["1551", "I7N3G6G", "1551", 0, "no description", 60.2], ["1552", "I7N3G6G", "1552", 0, "no description", 60.2], ["1553", "I7N3G6G", "1553", 0, "no description", 60.2], ["1554", "I7N3G6G", "1554", 0, "no description", 60.2], ["1555", "I7N3G6G", "1555", 0, "no description", 60.2], ["1556", "I7N3G6G", "1556", 0, "no description", 60.2], ["1557", "I7N3G6G", "1557", 0, "no description", 60.2], ["1558", "I7N3G6G", "1558", 0, "no description", 60.2], ["1559", "I7N3G6G", "1559", 0, "no description", 60.2], ["1560", "I7N3G6G", "1560", 0, "no description", 60.2], ["1561", "I7N3G6G", "1561", 0, "no description", 60.2], ["1562", "I7N3G6G", "1562", 0, "no description", 60.2], ["1563", "I7N3G6G", "1563", 0, "no description", 60.2], ["1564", "I7N3G6G", "1564", 0, "no description", 60.2], ["1565", "I7N3G6G", "1565", 0, "no description", 60.2], ["1566", "I7N3G6G", "1566", 0, "no description", 60.2], ["1567", "I7N3G6G", "1567", 0, "no description", 60.2], ["1568", "I7N3G6G", "1568", 0, "no description", 60.2], ["1569", "I7N3G6G", "1569", 0, "no description", 60.2], ["1570", "I7N3G6G", "1570", 0, "no description", 60.2], ["1571", "I7N3G6G", "1571", 0, "no description", 60.2], ["1572", "I7N3G6G", "1572", 0, "no description", 60.2], ["1573", "I7N3G6G", "1573", 0, "no description", 60.2], ["1574", "I7N3G6G", "1574", 0, "no description", 60.2], ["1575", "I7N3G6G", "1575", 0, "no description", 60.2], ["1576", "I7N3G6G", "1576", 0, "no description", 60.2], ["1577", "I7N3G6G", "1577", 0, "no description", 60.2], ["1578", "I7N3G6G", "1578", 0, "no description", 60.2], ["1579", "I7N3G6G", "1579", 0, "no description", 60.2], ["1580", "I7N3G6G", "1580", 0, "no description", 60.2], ["1581", "I7N3G6G", "1581", 0, "no description", 60.2], ["1582", "I7N3G6G", "1582", 0, "no description", 60.2], ["1583", "I7N3G6G", "1583", 0, "no description", 60.2], ["1584", "I7N3G6G", "1584", 0, "no description", 60.2], ["1585", "I7N3G6G", "1585", 0, "no description", 60.2], ["1586", "I7N3G6G", "1586", 0, "no description", 60.2], ["1587", "I7N3G6G", "1587", 0, "no description", 60.2], ["1588", "I7N3G6G", "1588", 0, "no description", 60.2], ["1589", "I7N3G6G", "1589", 0, "no description", 60.2], ["1590", "I7N3G6G", "1590", 0, "no description", 60.2], ["1591", "I7N3G6G", "1591", 0, "no description", 60.2], ["1592", "I7N3G6G", "1592", 0, "no description", 60.2], ["1593", "I7N3G6G", "1593", 0, "no description", 60.2], ["1594", "I7N3G6G", "1594", 0, "no description", 60.2], ["1595", "I7N3G6G", "1595", 0, "no description", 60.2], ["1596", "I7N3G6G", "1596", 0, "no description", 60.2], ["1597", "I7N3G6G", "1597", 0, "no description", 60.2], ["1598", "I7N3G6G", "1598", 0, "no description", 60.2], ["1599", "I7N3G6G", "1599", 0, "no description", 60.2], ["1600", "I7N3G6G", "1600", 0, "no description", 60.2], ["1601", "I7N3G6G", "1601", 0, "no description", 60.2], ["1602", "I7N3G6G", "1602", 0, "no description", 60.2], ["1603", "I7N3G6G", "1603", 0, "no description", 60.2], ["1604", "I7N3G6G", "1604", 0, "no description", 60.2], ["1605", "I7N3G6G", "1605", 0, "no description", 60.2], ["1606", "I7N3G6G", "1606", 0, "no description", 60.2], ["1607", "I7N3G6G", "1607", 0, "no description", 60.2], ["1608", "I7N3G6G", "1608", 0, "no description", 60.2], ["1609", "I7N3G6G", "1609", 0, "no description", 60.2], ["1610", "I7N3G6G", "1610", 0, "no description", 60.2], ["1611", "I7N3G6G", "1611", 0, "no description", 60.2], ["1612", "I7N3G6G", "1612", 0, "no description", 60.2], ["1613", "I7N3G6G", "1613", 0, "no description", 60.2], ["1614", "I7N3G6G", "1614", 0, "no description", 60.2], ["1615", "I7N3G6G", "1615", 0, "no description", 60.2], ["1616", "I7N3G6G", "1616", 0, "no description", 60.2], ["1617", "I7N3G6G", "1617", 0, "no description", 60.2], ["1618", "I7N3G6G", "1618", 0, "no description", 60.2], ["1619", "I7N3G6G", "1619", 0, "no description", 60.2], ["1620", "I7N3G6G", "1620", 0, "no description", 60.2], ["1621", "I7N3G6G", "1621", 0, "no description", 60.2], ["1622", "I7N3G6G", "1622", 0, "no description", 60.2], ["1623", "I7N3G6G", "1623", 0, "no description", 60.2], ["1624", "I7N3G6G", "1624", 0, "no description", 60.2], ["1625", "I7N3G6G", "1625", 0, "no description", 60.2], ["1626", "I7N3G6G", "1626", 0, "no description", 60.2], ["1627", "I7N3G6G", "1627", 0, "no description", 60.2], ["1628", "I7N3G6G", "1628", 0, "no description", 60.2], ["1629", "I7N3G6G", "1629", 0, "no description", 60.2], ["1630", "I7N3G6G", "1630", 0, "no description", 60.2], ["1631", "I7N3G6G", "1631", 0, "no description", 60.2], ["1632", "I7N3G6G", "1632", 0, "no description", 60.2], ["1633", "I7N3G6G", "1633", 0, "no description", 60.2], ["1634", "I7N3G6G", "1634", 0, "no description", 60.2], ["1635", "I7N3G6G", "1635", 0, "no description", 60.2], ["1636", "I7N3G6G", "1636", 0, "no description", 60.2], ["1637", "I7N3G6G", "1637", 0, "no description", 60.2], ["1638", "I7N3G6G", "1638", 0, "no description", 60.2], ["1639", "I7N3G6G", "1639", 0, "no description", 60.2], ["1640", "I7N3G6G", "1640", 0, "no description", 60.2], ["1641", "I7N3G6G", "1641", 0, "no description", 60.2], ["1642", "I7N3G6G", "1642", 0, "no description", 60.2], ["1643", "I7N3G6G", "1643", 0, "no description", 60.2], ["1644", "I7N3G6G", "1644", 0, "no description", 60.2], ["1645", "I7N3G6G", "1645", 0, "no description", 60.2], ["1646", "I7N3G6G", "1646", 0, "no description", 60.2], ["1647", "I7N3G6G", "1647", 0, "no description", 60.2], ["1648", "I7N3G6G", "1648", 0, "no description", 60.2], ["1649", "I7N3G6G", "1649", 0, "no description", 60.2], ["1650", "I7N3G6G", "1650", 0, "no description", 60.2], ["1651", "I7N3G6G", "1651", 0, "no description", 60.2], ["1652", "I7N3G6G", "1652", 0, "no description", 60.2], ["1653", "I7N3G6G", "1653", 0, "no description", 60.2], ["1654", "I7N3G6G", "1654", 0, "no description", 60.2], ["1655", "I7N3G6G", "1655", 0, "no description", 60.2], ["1656", "I7N3G6G", "1656", 0, "no description", 60.2], ["1657", "I7N3G6G", "1657", 0, "no description", 60.2], ["1658", "I7N3G6G", "1658", 0, "no description", 60.2], ["1659", "I7N3G6G", "1659", 0, "no description", 60.2], ["1660", "I7N3G6G", "1660", 0, "no description", 60.2], ["1661", "I7N3G6G", "1661", 0, "no description", 60.2], ["1662", "I7N3G6G", "1662", 0, "no description", 60.2], ["1663", "I7N3G6G", "1663", 0, "no description", 60.2], ["1664", "I7N3G6G", "1664", 0, "no description", 60.2], ["1665", "I7N3G6G", "1665", 0, "no description", 60.2], ["1666", "I7N3G6G", "1666", 0, "no description", 60.2], ["1667", "I7N3G6G", "1667", 0, "no description", 60.2], ["1668", "I7N3G6G", "1668", 0, "no description", 60.2], ["1669", "I7N3G6G", "1669", 0, "no description", 60.2], ["1670", "I7N3G6G", "1670", 0, "no description", 60.2], ["1671", "I7N3G6G", "1671", 0, "no description", 60.2], ["1672", "I7N3G6G", "1672", 0, "no description", 60.2], ["1673", "I7N3G6G", "1673", 0, "no description", 60.2], ["1674", "I7N3G6G", "1674", 0, "no description", 60.2], ["1675", "I7N3G6G", "1675", 0, "no description", 60.2], ["1676", "I7N3G6G", "1676", 0, "no description", 60.2], ["1677", "I7N3G6G", "1677", 0, "no description", 60.2], ["1678", "I7N3G6G", "1678", 0, "no description", 60.2], ["1679", "I7N3G6G", "1679", 0, "no description", 60.2], ["1680", "I7N3G6G", "1680", 0, "no description", 60.2], ["1681", "I7N3G6G", "1681", 0, "no description", 60.2], ["1682", "I7N3G6G", "1682", 0, "no description", 60.2], ["1683", "I7N3G6G", "1683", 0, "no description", 60.2], ["1684", "I7N3G6G", "1684", 0, "no description", 60.2], ["1685", "I7N3G6G", "1685", 0, "no description", 60.2], ["1686", "I7N3G6G", "1686", 0, "no description", 60.2], ["1687", "I7N3G6G", "1687", 0, "no description", 60.2], ["1688", "I7N3G6G", "1688", 0, "no description", 60.2], ["1689", "I7N3G6G", "1689", 0, "no description", 60.2], ["1690", "I7N3G6G", "1690", 0, "no description", 60.2], ["1691", "I7N3G6G", "1691", 0, "no description", 60.2], ["1692", "I7N3G6G", "1692", 0, "no description", 60.2], ["1693", "I7N3G6G", "1693", 0, "no description", 60.2], ["1694", "I7N3G6G", "1694", 0, "no description", 60.2], ["1695", "I7N3G6G", "1695", 0, "no description", 60.2], ["1696", "I7N3G6G", "1696", 0, "no description", 60.2], ["1697", "I7N3G6G", "1697", 0, "no description", 60.2], ["1698", "I7N3G6G", "1698", 0, "no description", 60.2], ["1699", "I7N3G6G", "1699", 0, "no description", 60.2], ["1700", "I7N3G6G", "1700", 0, "no description", 60.2], ["1701", "I7N3G6G", "1701", 0, "no description", 60.2], ["1702", "I7N3G6G", "1702", 0, "no description", 60.2], ["1703", "I7N3G6G", "1703", 0, "no description", 60.2], ["1704", "I7N3G6G", "1704", 0, "no description", 60.2], ["1705", "I7N3G6G", "1705", 0, "no description", 60.2], ["1706", "I7N3G6G", "1706", 0, "no description", 60.2], ["1707", "I7N3G6G", "1707", 0, "no description", 60.2], ["1708", "I7N3G6G", "1708", 0, "no description", 60.2], ["1709", "I7N3G6G", "1709", 0, "no description", 60.2], ["1710", "I7N3G6G", "1710", 0, "no description", 60.2], ["1711", "I7N3G6G", "1711", 0, "no description", 60.2], ["1712", "I7N3G6G", "1712", 0, "no description", 60.2], ["1713", "I7N3G6G", "1713", 0, "no description", 60.2], ["1714", "I7N3G6G", "1714", 0, "no description", 60.2], ["1715", "I7N3G6G", "1715", 0, "no description", 60.2], ["1716", "I7N3G6G", "1716", 0, "no description", 60.2], ["1717", "I7N3G6G", "1717", 0, "no description", 60.2], ["1718", "I7N3G6G", "1718", 0, "no description", 60.2], ["1719", "I7N3G6G", "1719", 0, "no description", 60.2], ["1720", "I7N3G6G", "1720", 0, "no description", 60.2], ["1721", "I7N3G6G", "1721", 0, "no description", 60.2], ["1722", "I7N3G6G", "1722", 0, "no description", 60.2], ["1723", "I7N3G6G", "1723", 0, "no description", 60.2], ["1724", "I7N3G6G", "1724", 0, "no description", 60.2], ["1725", "I7N3G6G", "1725", 0, "no description", 60.2], ["1726", "I7N3G6G", "1726", 0, "no description", 60.2], ["1727", "I7N3G6G", "1727", 0, "no description", 60.2], ["1728", "I7N3G6G", "1728", 0, "no description", 60.2], ["1729", "I7N3G6G", "1729", 0, "no description", 60.2], ["1730", "I7N3G6G", "1730", 0, "no description", 60.2], ["1731", "I7N3G6G", "1731", 0, "no description", 60.2], ["1732", "I7N3G6G", "1732", 0, "no description", 60.2], ["1733", "I7N3G6G", "1733", 0, "no description", 60.2], ["1734", "I7N3G6G", "1734", 0, "no description", 60.2], ["1735", "I7N3G6G", "1735", 0, "no description", 60.2], ["1736", "I7N3G6G", "1736", 0, "no description", 60.2], ["1737", "I7N3G6G", "1737", 0, "no description", 60.2], ["1738", "I7N3G6G", "1738", 0, "no description", 60.2], ["1739", "I7N3G6G", "1739", 0, "no description", 60.2], ["1740", "I7N3G6G", "1740", 0, "no description", 60.2], ["1741", "I7N3G6G", "1741", 0, "no description", 60.2], ["1742", "I7N3G6G", "1742", 0, "no description", 60.2], ["1743", "I7N3G6G", "1743", 0, "no description", 60.2], ["1744", "I7N3G6G", "1744", 0, "no description", 60.2], ["1745", "I7N3G6G", "1745", 0, "no description", 60.2], ["1746", "I7N3G6G", "1746", 0, "no description", 60.2], ["1747", "I7N3G6G", "1747", 0, "no description", 60.2], ["1748", "I7N3G6G", "1748", 0, "no description", 60.2], ["1749", "I7N3G6G", "1749", 0, "no description", 60.2], ["1750", "I7N3G6G", "1750", 0, "no description", 60.2], ["1751", "I7N3G6G", "1751", 0, "no description", 60.2], ["1752", "I7N3G6G", "1752", 0, "no description", 60.2], ["1753", "I7N3G6G", "1753", 0, "no description", 60.2], ["1754", "I7N3G6G", "1754", 0, "no description", 60.2], ["1755", "I7N3G6G", "1755", 0, "no description", 60.2], ["1756", "I7N3G6G", "1756", 0, "no description", 60.2], ["1757", "I7N3G6G", "1757", 0, "no description", 60.2], ["1758", "I7N3G6G", "1758", 0, "no description", 60.2], ["1759", "I7N3G6G", "1759", 0, "no description", 60.2], ["1760", "I7N3G6G", "1760", 0, "no description", 60.2], ["1761", "I7N3G6G", "1761", 0, "no description", 60.2], ["1762", "I7N3G6G", "1762", 0, "no description", 60.2], ["1763", "I7N3G6G", "1763", 0, "no description", 60.2], ["1764", "I7N3G6G", "1764", 0, "no description", 60.2], ["1765", "I7N3G6G", "1765", 0, "no description", 60.2], ["1766", "I7N3G6G", "1766", 0, "no description", 60.2], ["1767", "I7N3G6G", "1767", 0, "no description", 60.2], ["1768", "I7N3G6G", "1768", 0, "no description", 60.2], ["1769", "I7N3G6G", "1769", 0, "no description", 60.2], ["1770", "I7N3G6G", "1770", 0, "no description", 60.2], ["1771", "I7N3G6G", "1771", 0, "no description", 60.2], ["1772", "I7N3G6G", "1772", 0, "no description", 60.2], ["1773", "I7N3G6G", "1773", 0, "no description", 60.2], ["1774", "I7N3G6G", "1774", 0, "no description", 60.2], ["1775", "I7N3G6G", "1775", 0, "no description", 60.2], ["1776", "I7N3G6G", "1776", 0, "no description", 60.2], ["1777", "I7N3G6G", "1777", 0, "no description", 60.2], ["1778", "I7N3G6G", "1778", 0, "no description", 60.2], ["1779", "I7N3G6G", "1779", 0, "no description", 60.2], ["1780", "I7N3G6G", "1780", 0, "no description", 60.2], ["1781", "I7N3G6G", "1781", 0, "no description", 60.2], ["1782", "I7N3G6G", "1782", 0, "no description", 60.2], ["1783", "I7N3G6G", "1783", 0, "no description", 60.2], ["1784", "I7N3G6G", "1784", 0, "no description", 60.2], ["1785", "I7N3G6G", "1785", 0, "no description", 60.2], ["1786", "I7N3G6G", "1786", 0, "no description", 60.2], ["1787", "I7N3G6G", "1787", 0, "no description", 60.2], ["1788", "I7N3G6G", "1788", 0, "no description", 60.2], ["1789", "I7N3G6G", "1789", 0, "no description", 60.2], ["1790", "I7N3G6G", "1790", 0, "no description", 60.2], ["1791", "I7N3G6G", "1791", 0, "no description", 60.2], ["1792", "I7N3G6G", "1792", 0, "no description", 60.2], ["1793", "I7N3G6G", "1793", 0, "no description", 60.2], ["1794", "I7N3G6G", "1794", 0, "no description", 60.2], ["1795", "I7N3G6G", "1795", 0, "no description", 60.2], ["1796", "I7N3G6G", "1796", 0, "no description", 60.2], ["1797", "I7N3G6G", "1797", 0, "no description", 60.2], ["1798", "I7N3G6G", "1798", 0, "no description", 60.2], ["1799", "I7N3G6G", "1799", 0, "no description", 60.2], ["1800", "I7N3G6G", "1800", 0, "no description", 60.2], ["1801", "I7N3G6G", "1801", 0, "no description", 60.2], ["1802", "I7N3G6G", "1802", 0, "no description", 60.2], ["1803", "I7N3G6G", "1803", 0, "no description", 60.2], ["1804", "I7N3G6G", "1804", 0, "no description", 60.2], ["1805", "I7N3G6G", "1805", 0, "no description", 60.2], ["1806", "I7N3G6G", "1806", 0, "no description", 60.2], ["1807", "I7N3G6G", "1807", 0, "no description", 60.2], ["1808", "I7N3G6G", "1808", 0, "no description", 60.2], ["1809", "I7N3G6G", "1809", 0, "no description", 60.2], ["1810", "I7N3G6G", "1810", 0, "no description", 60.2], ["1811", "I7N3G6G", "1811", 0, "no description", 60.2], ["1812", "I7N3G6G", "1812", 0, "no description", 60.2], ["1813", "I7N3G6G", "1813", 0, "no description", 60.2], ["1814", "I7N3G6G", "1814", 0, "no description", 60.2], ["1815", "I7N3G6G", "1815", 0, "no description", 60.2], ["1816", "I7N3G6G", "1816", 0, "no description", 60.2], ["1817", "I7N3G6G", "1817", 0, "no description", 60.2], ["1818", "I7N3G6G", "1818", 0, "no description", 60.2], ["1819", "I7N3G6G", "1819", 0, "no description", 60.2], ["1820", "I7N3G6G", "1820", 0, "no description", 60.2], ["1821", "I7N3G6G", "1821", 0, "no description", 60.2], ["1822", "I7N3G6G", "1822", 0, "no description", 60.2], ["1823", "I7N3G6G", "1823", 0, "no description", 60.2], ["1824", "I7N3G6G", "1824", 0, "no description", 60.2], ["1825", "I7N3G6G", "1825", 0, "no description", 60.2], ["1826", "I7N3G6G", "1826", 0, "no description", 60.2], ["1827", "I7N3G6G", "1827", 0, "no description", 60.2], ["1828", "I7N3G6G", "1828", 0, "no description", 60.2], ["1829", "I7N3G6G", "1829", 0, "no description", 60.2], ["1830", "I7N3G6G", "1830", 0, "no description", 60.2], ["1831", "I7N3G6G", "1831", 0, "no description", 60.2], ["1832", "I7N3G6G", "1832", 0, "no description", 60.2], ["1833", "I7N3G6G", "1833", 0, "no description", 60.2], ["1834", "I7N3G6G", "1834", 0, "no description", 60.2], ["1835", "I7N3G6G", "1835", 0, "no description", 60.2], ["1836", "I7N3G6G", "1836", 0, "no description", 60.2], ["1837", "I7N3G6G", "1837", 0, "no description", 60.2], ["1838", "I7N3G6G", "1838", 0, "no description", 60.2], ["1839", "I7N3G6G", "1839", 0, "no description", 60.2], ["1840", "I7N3G6G", "1840", 0, "no description", 60.2], ["1841", "I7N3G6G", "1841", 0, "no description", 60.2], ["1842", "I7N3G6G", "1842", 0, "no description", 60.2], ["1843", "I7N3G6G", "1843", 0, "no description", 60.2], ["1844", "I7N3G6G", "1844", 0, "no description", 60.2], ["1845", "I7N3G6G", "1845", 0, "no description", 60.2], ["1846", "I7N3G6G", "1846", 0, "no description", 60.2], ["1847", "I7N3G6G", "1847", 0, "no description", 60.2], ["1848", "I7N3G6G", "1848", 0, "no description", 60.2], ["1849", "I7N3G6G", "1849", 0, "no description", 60.2], ["1850", "I7N3G6G", "1850", 0, "no description", 60.2], ["1851", "I7N3G6G", "1851", 0, "no description", 60.2], ["1852", "I7N3G6G", "1852", 0, "no description", 60.2], ["1853", "I7N3G6G", "1853", 0, "no description", 60.2], ["1854", "I7N3G6G", "1854", 0, "no description", 60.2], ["1855", "I7N3G6G", "1855", 0, "no description", 60.2], ["1856", "I7N3G6G", "1856", 0, "no description", 60.2], ["1857", "I7N3G6G", "1857", 0, "no description", 60.2], ["1858", "I7N3G6G", "1858", 0, "no description", 60.2], ["1859", "I7N3G6G", "1859", 0, "no description", 60.2], ["1860", "I7N3G6G", "1860", 0, "no description", 60.2], ["1861", "I7N3G6G", "1861", 0, "no description", 60.2], ["1862", "I7N3G6G", "1862", 0, "no description", 60.2], ["1863", "I7N3G6G", "1863", 0, "no description", 60.2], ["1864", "I7N3G6G", "1864", 0, "no description", 60.2], ["1865", "I7N3G6G", "1865", 0, "no description", 60.2], ["1866", "I7N3G6G", "1866", 0, "no description", 60.2], ["1867", "I7N3G6G", "1867", 0, "no description", 60.2], ["1868", "I7N3G6G", "1868", 0, "no description", 60.2], ["1869", "I7N3G6G", "1869", 0, "no description", 60.2], ["1870", "I7N3G6G", "1870", 0, "no description", 60.2], ["1871", "I7N3G6G", "1871", 0, "no description", 60.2], ["1872", "I7N3G6G", "1872", 0, "no description", 60.2], ["1873", "I7N3G6G", "1873", 0, "no description", 60.2], ["1874", "I7N3G6G", "1874", 0, "no description", 60.2], ["1875", "I7N3G6G", "1875", 0, "no description", 60.2], ["1876", "I7N3G6G", "1876", 0, "no description", 60.2], ["1877", "I7N3G6G", "1877", 0, "no description", 60.2], ["1878", "I7N3G6G", "1878", 0, "no description", 60.2], ["1879", "I7N3G6G", "1879", 0, "no description", 60.2], ["1880", "I7N3G6G", "1880", 0, "no description", 60.2], ["1881", "I7N3G6G", "1881", 0, "no description", 60.2], ["1882", "I7N3G6G", "1882", 0, "no description", 60.2], ["1883", "I7N3G6G", "1883", 0, "no description", 60.2], ["1884", "I7N3G6G", "1884", 0, "no description", 60.2], ["1885", "I7N3G6G", "1885", 0, "no description", 60.2], ["1886", "I7N3G6G", "1886", 0, "no description", 60.2], ["1887", "I7N3G6G", "1887", 0, "no description", 60.2], ["1888", "I7N3G6G", "1888", 0, "no description", 60.2], ["1889", "I7N3G6G", "1889", 0, "no description", 60.2], ["1890", "I7N3G6G", "1890", 0, "no description", 60.2], ["1891", "I7N3G6G", "1891", 0, "no description", 60.2], ["1892", "I7N3G6G", "1892", 0, "no description", 60.2], ["1893", "I7N3G6G", "1893", 0, "no description", 60.2], ["1894", "I7N3G6G", "1894", 0, "no description", 60.2], ["1895", "I7N3G6G", "1895", 0, "no description"], ["1896", "I7N3G6G", "1896", 0, "no description", 60.2], ["1897", "I7N3G6G", "1897", 0], ["1898", "I7N3G6G", "1898", 0, "no description", 60.2], ["1899", "I7N3G6G", "1899", 0, "no description", 60.2], ["1900", "I7N3G6G", "1900", 0, "no description", 60.2], ["1901", "I7N3G6G", "1901", 0, "no description", 60.2], ["1902", "I7N3G6G", "1902", 0, "no description", 60.2], ["1903", "I7N3G6G", "1903", 0, "no description", 60.2], ["1904", "I7N3G6G", "1904", 0, "no description", 60.2], ["1905", "I7N3G6G", "1905", 0, "no description", 60.2], ["1906", "I7N3G6G", "1906", 0, "no description", 60.2], ["1907", "I7N3G6G", "1907", 0, "no description", 60.2], ["1908", "I7N3G6G", "1908", 0, "no description", 60.2], ["1909", "I7N3G6G", "1909", 0, "no description", 60.2], ["1910", "I7N3G6G", "1910", 0, "no description", 60.2], ["1911", "I7N3G6G", "1911", 0, "no description", 60.2], ["1912", "I7N3G6G", "1912", 0, "no description", 60.2], ["1913", "I7N3G6G", "1913", 0, "no description", 60.2], ["1914", "I7N3G6G", "1914", 0, "no description", 60.2], ["1915", "I7N3G6G", "1915", 0, "no description", 60.2], ["1916", "I7N3G6G", "1916", 0, "no description", 60.2], ["1917", "I7N3G6G", "1917", 0, "no description", 60.2], ["1918", "I7N3G6G", "1918", 0, "no description", 60.2], ["1919", "I7N3G6G", "1919", 0, "no description", 60.2], ["1920", "I7N3G6G", "1920", 0, "no description", 60.2], ["1921", "I7N3G6G", "1921", 0, "no description", 60.2], ["1922", "I7N3G6G", "1922", 0, "no description", 60.2], ["1923", "I7N3G6G", "1923", 0, "no description", 60.2], ["1924", "I7N3G6G", "1924", 0, "no description", 60.2], ["1925", "I7N3G6G", "1925", 0, "no description", 60.2], ["1926", "I7N3G6G", "1926", 0, "no description", 60.2], ["1927", "I7N3G6G", "1927", 0, "no description", 60.2], ["1928", "I7N3G6G", "1928", 0, "no description", 60.2], ["1929", "I7N3G6G", "1929", 0, "no description", 60.2], ["1930", "I7N3G6G", "1930", 0, "no description", 60.2], ["1931", "I7N3G6G", "1931", 0, "no description", 60.2], ["1932", "I7N3G6G", "1932", 0, "no description", 60.2], ["1933", "I7N3G6G", "1933", 0, "no description", 60.2], ["1934", "I7N3G6G", "1934", 0, "no description", 60.2], ["1935", "I7N3G6G", "1935", 0, "no description", 60.2], ["1936", "I7N3G6G", "1936", 0, "no description", 60.2], ["1937", "I7N3G6G", "1937", 0, "no description", 60.2], ["1938", "I7N3G6G", "1938", 0, "no description", 60.2], ["1939", "I7N3G6G", "1939", 0, "no description", 60.2], ["1940", "I7N3G6G", "1940", 0, "no description", 60.2], ["1941", "I7N3G6G", "1941", 0, "no description", 60.2], ["1942", "I7N3G6G", "1942", 0, "no description", 60.2], ["1943", "I7N3G6G", "1943", 0, "no description", 60.2], ["1944", "I7N3G6G", "1944", 0, "no description", 60.2], ["1945", "I7N3G6G", "1945", 0, "no description", 60.2], ["1946", "I7N3G6G", "1946", 0, "no description", 60.2], ["1947", "I7N3G6G", "1947", 0, "no description", 60.2], ["1948", "I7N3G6G", "1948", 0, "no description", 60.2], ["1949", "I7N3G6G", "1949", 0, "no description", 60.2], ["1950", "I7N3G6G", "1950", 0, "no description", 60.2], ["1951", "I7N3G6G", "1951", 0, "no description", 60.2], ["1952", "I7N3G6G", "1952", 0, "no description", 60.2], ["1953", "I7N3G6G", "1953", 0, "no description", 60.2], ["1954", "I7N3G6G", "1954", 0, "no description", 60.2], ["1955", "I7N3G6G", "1955", 0, "no description", 60.2], ["1956", "I7N3G6G", "1956", 0, "no description", 60.2], ["1957", "I7N3G6G", "1957", 0, "no description", 60.2], ["1958", "I7N3G6G", "1958", 0, "no description", 60.2], ["1959", "I7N3G6G", "1959", 0, "no description", 60.2], ["1960", "I7N3G6G", "1960", 0, "no description", 60.2], ["1961", "I7N3G6G", "1961", 0, "no description", 60.2], ["1962", "I7N3G6G", "1962", 0, "no description", 60.2], ["1963", "I7N3G6G", "1963", 0, "no description", 60.2], ["1964", "I7N3G6G", "1964", 0, "no description", 60.2], ["1965", "I7N3G6G", "1965", 0, "no description", 60.2], ["1966", "I7N3G6G", "1966", 0, "no description", 60.2], ["1967", "I7N3G6G", "1967", 0, "no description", 60.2], ["1968", "I7N3G6G", "1968", 0, "no description", 60.2], ["1969", "I7N3G6G", "1969", 0, "no description", 60.2], ["1970", "I7N3G6G", "1970", 0, "no description", 60.2], ["1971", "I7N3G6G", "1971", 0, "no description", 60.2], ["1972", "I7N3G6G", "1972", 0, "no description", 60.2], ["1973", "I7N3G6G", "1973", 0, "no description", 60.2], ["1974", "I7N3G6G", "1974", 0, "no description", 60.2], ["1975", "I7N3G6G", "1975", 0, "no description", 60.2], ["1976", "I7N3G6G", "1976", 0, "no description", 60.2], ["1977", "I7N3G6G", "1977", 0, "no description", 60.2], ["1978", "I7N3G6G", "1978", 0, "no description", 60.2], ["1979", "I7N3G6G", "1979", 0, "no description", 60.2], ["1980", "I7N3G6G", "1980", 0, "no description", 60.2], ["1981", "I7N3G6G", "1981", 0, "no description", 60.2], ["1982", "I7N3G6G", "1982", 0, "no description", 60.2], ["1983", "I7N3G6G", "1983", 0, "no description", 60.2], ["1984", "I7N3G6G", "1984", 0, "no description", 60.2], ["1985", "I7N3G6G", "1985", 0, "no description", 60.2], ["1986", "I7N3G6G", "1986", 0, "no description", 60.2], ["1987", "I7N3G6G", "1987", 0, "no description", 60.2], ["1988", "I7N3G6G", "1988", 0, "no description", 60.2], ["1989", "I7N3G6G", "1989", 0, "no description", 60.2], ["1990", "I7N3G6G", "1990", 0, "no description", 60.2], ["1991", "I7N3G6G", "1991", 0, "no description", 60.2], ["1992", "I7N3G6G", "1992", 0, "no description", 60.2], ["1993", "I7N3G6G", "1993", 0, "no description", 60.2], ["1994", "I7N3G6G", "1994", 0, "no description", 60.2], ["1995", "I7N3G6G", "1995", 0, "no description", 60.2], ["1996", "I7N3G6G", "1996", 0, "no description", 60.2], ["1997", "I7N3G6G", "1997", 0, "no description", 60.2], ["1998", "I7N3G6G", "1998", 0, "no description", 60.2], ["1999", "I7N3G6G", "1999", 0, "no description", 60.2], ["2000", "I7N3G6G", "2000", 0, "no description", 60.2], ["2001", "I7N3G6G", "2001", 0, "no description", 60.2], ["2002", "I7N3G6G", "2002", 0, "no description", 60.2], ["2003", "I7N3G6G", "2003", 0, "no description", 60.2], ["2004", "I7N3G6G", "2004", 0, "no description", 60.2], ["2005", "I7N3G6G", "2005", 0, "no description", 60.2], ["2006", "I7N3G6G", "2006", 0, "no description", 60.2], ["2007", "I7N3G6G", "2007", 0, "no description", 60.2], ["2008", "I7N3G6G", "2008", 0, "no description", 60.2], ["2009", "I7N3G6G", "2009", 0, "no description", 60.2], ["2010", "I7N3G6G", "2010", 0, "no description", 60.2], ["2011", "I7N3G6G", "2011", 0, "no description", 60.2], ["2012", "I7N3G6G", "2012", 0, "no description", 60.2], ["2013", "I7N3G6G", "2013", 0, "no description", 60.2], ["2014", "I7N3G6G", "2014", 0, "no description", 60.2], ["2015", "I7N3G6G", "2015", 0, "no description", 60.2], ["2016", "I7N3G6G", "2016", 0, "no description", 60.2], ["2017", "I7N3G6G", "2017", 0, "no description", 60.2], ["2018", "I7N3G6G", "2018", 0, "no description", 60.2], ["2019", "I7N3G6G", "2019", 0, "no description", 60.2], ["2020", "I7N3G6G", "2020", 0, "no description", 60.2], ["2021", "I7N3G6G", "2021", 0, "no description", 60.2], ["2022", "I7N3G6G", "2022", 0, "no description", 60.2], ["2023", "I7N3G6G", "2023", 0, "no description", 60.2], ["2024", "I7N3G6G", "2024", 0, "no description", 60.2], ["2025", "I7N3G6G", "2025", 0, "no description", 60.2], ["2026", "I7N3G6G", "2026", 0, "no description", 60.2], ["2027", "I7N3G6G", "2027", 0, "no description", 60.2], ["2028", "I7N3G6G", "2028", 0, "no description", 60.2], ["2029", "I7N3G6G", "2029", 0, "no description", 60.2], ["2030", "I7N3G6G", "2030", 0, "no description", 60.2], ["2031", "I7N3G6G", "2031", 0, "no description", 60.2], ["2032", "I7N3G6G", "2032", 0, "no description", 60.2], ["2033", "I7N3G6G", "2033", 0, "no description", 60.2], ["2034", "I7N3G6G", "2034", 0, "no description", 60.2], ["2035", "I7N3G6G", "2035", 0, "no description", 60.2], ["2036", "I7N3G6G", "2036", 0, "no description", 60.2], ["2037", "I7N3G6G", "2037", 0, "no description", 60.2], ["2038", "I7N3G6G", "2038", 0, "no description", 60.2], ["2039", "I7N3G6G", "2039", 0, "no description", 60.2], ["2040", "I7N3G6G", "2040", 0, "no description", 60.2], ["2041", "I7N3G6G", "2041", 0, "no description", 60.2], ["2042", "I7N3G6G", "2042", 0, "no description", 60.2], ["2043", "I7N3G6G", "2043", 0, "no description", 60.2], ["2044", "I7N3G6G", "2044", 0, "no description", 60.2], ["2045", "I7N3G6G", "2045", 0, "no description", 60.2], ["2046", "I7N3G6G", "2046", 0, "no description", 60.2], ["2047", "I7N3G6G", "2047", 0, "no description", 60.2], ["2048", "I7N3G6G", "2048", 0, "no description", 60.2], ["2049", "I7N3G6G", "2049", 0, "no description", 60.2], ["2050", "I7N3G6G", "2050", 0, "no description", 60.2], ["2051", "I7N3G6G", "2051", 0, "no description", 60.2], ["2052", "I7N3G6G", "2052", 0, "no description", 60.2], ["2053", "I7N3G6G", "2053", 0, "no description", 60.2], ["2054", "I7N3G6G", "2054", 0, "no description", 60.2], ["2055", "I7N3G6G", "2055", 0, "no description", 60.2], ["2056", "I7N3G6G", "2056", 0, "no description", 60.2], ["2057", "I7N3G6G", "2057", 0, "no description", 60.2], ["2058", "I7N3G6G", "2058", 0, "no description", 60.2], ["2059", "I7N3G6G", "2059", 0, "no description", 60.2], ["2060", "I7N3G6G", "2060", 0, "no description", 60.2], ["2061", "I7N3G6G", "2061", 0, "no description", 60.2], ["2062", "I7N3G6G", "2062", 0, "no description", 60.2], ["2063", "I7N3G6G", "2063", 0, "no description", 60.2], ["2064", "I7N3G6G", "2064", 0, "no description", 60.2], ["2065", "I7N3G6G", "2065", 0, "no description", 60.2], ["2066", "I7N3G6G", "2066", 0, "no description", 60.2], ["2067", "I7N3G6G", "2067", 0, "no description", 60.2], ["2068", "I7N3G6G", "2068", 0, "no description", 60.2], ["2069", "I7N3G6G", "2069", 0, "no description", 60.2], ["2070", "I7N3G6G", "2070", 0, "no description", 60.2], ["2071", "I7N3G6G", "2071", 0, "no description", 60.2], ["2072", "I7N3G6G", "2072", 0, "no description", 60.2], ["2073", "I7N3G6G", "2073", 0, "no description", 60.2], ["2074", "I7N3G6G", "2074", 0, "no description", 60.2], ["2075", "I7N3G6G", "2075", 0, "no description", 60.2], ["2076", "I7N3G6G", "2076", 0, "no description", 60.2], ["2077", "I7N3G6G", "2077", 0, "no description", 60.2], ["2078", "I7N3G6G", "2078", 0, "no description", 60.2], ["2079", "I7N3G6G", "2079", 0, "no description", 60.2], ["2080", "I7N3G6G", "2080", 0, "no description", 60.2], ["2081", "I7N3G6G", "2081", 0, "no description", 60.2], ["2082", "I7N3G6G", "2082", 0, "no description", 60.2], ["2083", "I7N3G6G", "2083", 0, "no description", 60.2], ["2084", "I7N3G6G", "2084", 0, "no description", 60.2], ["2085", "I7N3G6G", "2085", 0, "no description", 60.2], ["2086", "I7N3G6G", "2086", 0, "no description", 60.2], ["2087", "I7N3G6G", "2087", 0, "no description", 60.2], ["2088", "I7N3G6G", "2088", 0, "no description", 60.2], ["2089", "I7N3G6G", "2089", 0, "no description", 60.2], ["2090", "I7N3G6G", "2090", 0, "no description", 60.2], ["2091", "I7N3G6G", "2091", 0, "no description", 60.2], ["2092", "I7N3G6G", "2092", 0, "no description", 60.2], ["2093", "I7N3G6G", "2093", 0, "no description", 60.2], ["2094", "I7N3G6G", "2094", 0, "no description", 60.2], ["2095", "I7N3G6G", "2095", 0, "no description", 60.2], ["2096", "I7N3G6G", "2096", 0, "no description", 60.2], ["2097", "I7N3G6G", "2097", 0, "no description", 60.2], ["2098", "I7N3G6G", "2098", 0, "no description", 60.2], ["2099", "I7N3G6G", "2099", 0, "no description", 60.2], ["2100", "I7N3G6G", "2100", 0, "no description", 60.2], ["2101", "I7N3G6G", "2101", 0, "no description", 60.2], ["2102", "I7N3G6G", "2102", 0, "no description", 60.2], ["2103", "I7N3G6G", "2103", 0, "no description", 60.2], ["2104", "I7N3G6G", "2104", 0, "no description", 60.2], ["2105", "I7N3G6G", "2105", 0, "no description", 60.2], ["2106", "I7N3G6G", "2106", 0, "no description", 60.2], ["2107", "I7N3G6G", "2107", 0, "no description", 60.2], ["2108", "I7N3G6G", "2108", 0, "no description", 60.2], ["2109", "I7N3G6G", "2109", 0, "no description", 60.2], ["2110", "I7N3G6G", "2110", 0, "no description", 60.2], ["2111", "I7N3G6G", "2111", 0, "no description", 60.2], ["2112", "I7N3G6G", "2112", 0, "no description", 60.2], ["2113", "I7N3G6G", "2113", 0, "no description", 60.2], ["2114", "I7N3G6G", "2114", 0, "no description", 60.2], ["2115", "I7N3G6G", "2115", 0, "no description", 60.2], ["2116", "I7N3G6G", "2116", 0, "no description", 60.2], ["2117", "I7N3G6G", "2117", 0, "no description", 60.2], ["2118", "I7N3G6G", "2118", 0, "no description", 60.2], ["2119", "I7N3G6G", "2119", 0, "no description", 60.2], ["2120", "I7N3G6G", "2120", 0, "no description", 60.2], ["2121", "I7N3G6G", "2121", 0, "no description", 60.2], ["2122", "I7N3G6G", "2122", 0, "no description", 60.2], ["2123", "I7N3G6G", "2123", 0, "no description", 60.2], ["2124", "I7N3G6G", "2124", 0, "no description", 60.2], ["2125", "I7N3G6G", "2125", 0, "no description", 60.2], ["2126", "I7N3G6G", "2126", 0, "no description", 60.2], ["2127", "I7N3G6G", "2127", 0, "no description", 60.2], ["2128", "I7N3G6G", "2128", 0, "no description", 60.2], ["2129", "I7N3G6G", "2129", 0, "no description", 60.2], ["2130", "I7N3G6G", "2130", 0, "no description", 60.2], ["2131", "I7N3G6G", "2131", 0, "no description", 60.2], ["2132", "I7N3G6G", "2132", 0, "no description", 60.2], ["2133", "I7N3G6G", "2133", 0, "no description", 60.2], ["2134", "I7N3G6G", "2134", 0, "no description", 60.2], ["2135", "I7N3G6G", "2135", 0, "no description", 60.2], ["2136", "I7N3G6G", "2136", 0, "no description", 60.2], ["2137", "I7N3G6G", "2137", 0, "no description", 60.2], ["2138", "I7N3G6G", "2138", 0, "no description", 60.2], ["2139", "I7N3G6G", "2139", 0, "no description", 60.2], ["2140", "I7N3G6G", "2140", 0, "no description", 60.2], ["2141", "I7N3G6G", "2141", 0, "no description", 60.2], ["2142", "I7N3G6G", "2142", 0, "no description", 60.2], ["2143", "I7N3G6G", "2143", 0, "no description", 60.2], ["2144", "I7N3G6G", "2144", 0, "no description", 60.2], ["2145", "I7N3G6G", "2145", 0, "no description", 60.2], ["2146", "I7N3G6G", "2146", 0, "no description", 60.2], ["2147", "I7N3G6G", "2147", 0, "no description", 60.2], ["2148", "I7N3G6G", "2148", 0, "no description", 60.2], ["2149", "I7N3G6G", "2149", 0, "no description", 60.2], ["2150", "I7N3G6G", "2150", 0, "no description", 60.2], ["2151", "I7N3G6G", "2151", 0, "no description", 60.2], ["2152", "I7N3G6G", "2152", 0, "no description", 60.2], ["2153", "I7N3G6G", "2153", 0, "no description", 60.2], ["2154", "I7N3G6G", "2154", 0, "no description", 60.2], ["2155", "I7N3G6G", "2155", 0, "no description", 60.2], ["2156", "I7N3G6G", "2156", 0, "no description", 60.2], ["2157", "I7N3G6G", "2157", 0, "no description", 60.2], ["2158", "I7N3G6G", "2158", 0, "no description", 60.2], ["2159", "I7N3G6G", "2159", 0, "no description", 60.2], ["2160", "I7N3G6G", "2160", 0, "no description", 60.2], ["2161", "I7N3G6G", "2161", 0, "no description", 60.2], ["2162", "I7N3G6G", "2162", 0, "no description", 60.2], ["2163", "I7N3G6G", "2163", 0, "no description", 60.2], ["2164", "I7N3G6G", "2164", 0, "no description", 60.2], ["2165", "I7N3G6G", "2165", 0, "no description", 60.2], ["2166", "I7N3G6G", "2166", 0, "no description", 60.2], ["2167", "I7N3G6G", "2167", 0, "no description", 60.2], ["2168", "I7N3G6G", "2168", 0, "no description", 60.2], ["2169", "I7N3G6G", "2169", 0, "no description", 60.2], ["2170", "I7N3G6G", "2170", 0, "no description", 60.2], ["2171", "I7N3G6G", "2171", 0, "no description", 60.2], ["2172", "I7N3G6G", "2172", 0, "no description", 60.2], ["2173", "I7N3G6G", "2173", 0, "no description", 60.2], ["2174", "I7N3G6G", "2174", 0, "no description", 60.2], ["2175", "I7N3G6G", "2175", 0, "no description", 60.2], ["2176", "I7N3G6G", "2176", 0, "no description", 60.2], ["2177", "I7N3G6G", "2177", 0, "no description", 60.2], ["2178", "I7N3G6G", "2178", 0, "no description", 60.2], ["2179", "I7N3G6G", "2179", 0, "no description", 60.2], ["2180", "I7N3G6G", "2180", 0, "no description", 60.2], ["2181", "I7N3G6G", "2181", 0, "no description", 60.2], ["2182", "I7N3G6G", "2182", 0, "no description", 60.2], ["2183", "I7N3G6G", "2183", 0, "no description", 60.2], ["2184", "I7N3G6G", "2184", 0, "no description", 60.2], ["2185", "I7N3G6G", "2185", 0, "no description", 60.2], ["2186", "I7N3G6G", "2186", 0, "no description", 60.2], ["2187", "I7N3G6G", "2187", 0, "no description", 60.2], ["2188", "I7N3G6G", "2188", 0, "no description", 60.2], ["2189", "I7N3G6G", "2189", 0, "no description", 60.2], ["2190", "I7N3G6G", "2190", 0, "no description", 60.2], ["2191", "I7N3G6G", "2191", 0, "no description", 60.2], ["2192", "I7N3G6G", "2192", 0, "no description", 60.2], ["2193", "I7N3G6G", "2193", 0, "no description", 60.2], ["2194", "I7N3G6G", "2194", 0, "no description", 60.2], ["2195", "I7N3G6G", "2195", 0, "no description", 60.2], ["2196", "I7N3G6G", "2196", 0, "no description", 60.2], ["2197", "I7N3G6G", "2197", 0, "no description", 60.2], ["2198", "I7N3G6G", "2198", 0, "no description", 60.2], ["2199", "I7N3G6G", "2199", 0, "no description", 60.2], ["2200", "I7N3G6G", "2200", 0, "no description", 60.2], ["2201", "I7N3G6G", "2201", 0, "no description", 60.2], ["2202", "I7N3G6G", "2202", 0, "no description", 60.2], ["2203", "I7N3G6G", "2203", 0, "no description", 60.2], ["2204", "I7N3G6G", "2204", 0, "no description", 60.2], ["2205", "I7N3G6G", "2205", 0, "no description", 60.2], ["2206", "I7N3G6G", "2206", 0, "no description", 60.2], ["2207", "I7N3G6G", "2207", 0, "no description", 60.2], ["2208", "I7N3G6G", "2208", 0, "no description", 60.2], ["2209", "I7N3G6G", "2209", 0, "no description", 60.2], ["2210", "I7N3G6G", "2210", 0, "no description", 60.2], ["2211", "I7N3G6G", "2211", 0, "no description", 60.2], ["2212", "I7N3G6G", "2212", 0, "no description", 60.2], ["2213", "I7N3G6G", "2213", 0, "no description", 60.2], ["2214", "I7N3G6G", "2214", 0, "no description", 60.2], ["2215", "I7N3G6G", "2215", 0, "no description", 60.2], ["2216", "I7N3G6G", "2216", 0, "no description", 60.2], ["2217", "I7N3G6G", "2217", 0, "no description", 60.2], ["2218", "I7N3G6G", "2218", 0, "no description", 60.2], ["2219", "I7N3G6G", "2219", 0, "no description", 60.2], ["2220", "I7N3G6G", "2220", 0, "no description", 60.2], ["2221", "I7N3G6G", "2221", 0, "no description", 60.2], ["2222", "I7N3G6G", "2222", 0, "no description", 60.2], ["2223", "I7N3G6G", "2223", 0, "no description", 60.2], ["2224", "I7N3G6G", "2224", 0, "no description", 60.2], ["2225", "I7N3G6G", "2225", 0, "no description", 60.2], ["2226", "I7N3G6G", "2226", 0, "no description", 60.2]] \ No newline at end of file diff --git a/packages/itmat-job-executor/test/testFiles/JSONCurator_error5.json b/packages/itmat-job-executor/test/testFiles/JSONCurator_error5.json deleted file mode 100644 index 8b878f919..000000000 --- a/packages/itmat-job-executor/test/testFiles/JSONCurator_error5.json +++ /dev/null @@ -1 +0,0 @@ -[["ID", "SubjectID", "VisitID", "Sex", "Description", "Weight", "UNKNOWN"], ["1", "I7N3G6G", "1", 0, "no description", 60.2, 1], ["2", "I7N3G6G", "2", "a", "no description", 60.2, 1], ["3", "I7N3G6G", "3", 0, "no description", 60.2, 1], ["4", "I7N3G6G", "4", 0, "no description", 60.2, 1], ["5", "I7N3G6G", "5", 0, "no description", 60.2, 1], ["6", "I7N3G6G", "6", 0, "no description", 60.2, 1], ["7", "I7N3G6G", "7", 0, "no description", 60.2, 1], ["8", "I7N3G6G", "8", 0, "no description", 60.2, 1], ["9", "I7N3G6G", "9", 0, "no description", 60.2, 1], ["10", "I7N3G6G", "10", 0, "no description", 60.2, 1], ["11", "I7N3G6G", "11", 0, "no description", 60.2, 1], ["12", "I7N3G6G", "12", 0, "no description", 60.2, 1], ["13", "I7N3G6G", "13", 0, "no description", 60.2, 1], ["14", "I7N3G6G", "14", 0, "no description", 60.2, 1], ["15", "I7N3G6G", "15", 0, "no description", 60.2, 1], ["16", "I7N3G6G", "16", 0, "no description", 60.2, 1], ["17", "I7N3G6G", "17", 0, "no description", 60.2, 1], ["18", "I7N3G6G", "18", 0, "no description", 60.2, 1], ["19", "I7N3G6G", "19", 0, "no description", 60.2, 1], ["20", "I7N3G6G", "20", 0, "no description", 60.2, 1], ["21", "I7N3G6G", "21", 0, "no description", 60.2, 1], ["22", "I7N3G6G", "22", 0, "no description", 60.2, 1], ["23", "I7N3G6G", "23", 0, "no description", 60.2, 1], ["24", "I7N3G6G", "24", 0, "no description", 60.2, 1], ["25", "I7N3G6G", "25", 0, "no description", 60.2, 1], ["26", "I7N3G6G", "26", 0, "no description", 60.2, 1], ["27", "I7N3G6G", "27", 0, "no description", 60.2, 1], ["28", "I7N3G6G", "28", 0, "no description", 60.2, 1], ["29", "I7N3G6G", "29", 0, "no description", 60.2, 1], ["30", "I7N3G6G", "30", 0, "no description", 60.2, 1], ["31", "I7N3G6G", "31", 0, "no description", 60.2, 1], ["32", "I7N3G6G", "32", 0, "no description", 60.2, 1], ["33", "I7N3G6G", "33", 0, "no description", 60.2, 1], ["34", "I7N3G6G", "34", 0, "no description", 60.2, 1], ["35", "I7N3G6G", "35", 0, "no description", 60.2, 1], ["36", "I7N3G6G", "36", 0, "no description", 60.2, 1], ["37", "I7N3G6G", "37", 0, "no description", 60.2, 1], ["38", "I7N3G6G", "38", 0, "no description", 60.2, 1], ["39", "I7N3G6G", "39", 0, "no description", 60.2, 1], ["40", "I7N3G6G", "40", 0, "no description", 60.2, 1], ["41", "I7N3G6G", "41", 0, "no description", 60.2, 1], ["42", "I7N3G6G", "42", 0, "no description", 60.2, 1], ["43", "I7N3G6G", "43", 0, "no description", 60.2, 1], ["44", "I7N3G6G", "44", 0, "no description", 60.2, 1], ["45", "I7N3G6G", "45", 0, "no description", 60.2, 1], ["46", "I7N3G6G", "46", 0, "no description", 60.2, 1], ["47", "I7N3G6G", "47", 0, "no description", 60.2, 1], ["48", "I7N3G6G", "48", 0, "no description", 60.2, 1], ["49", "I7N3G6G", "49", 0, "no description", 60.2, 1], ["50", "I7N3G6G", "50", 0, "no description", 60.2, 1], ["51", "I7N3G6G", "51", 0, "no description", 60.2, 1], ["52", "I7N3G6G", "52", 0, "no description", 60.2, 1], ["53", "I7N3G6G", "53", 0, "no description", 60.2, 1], ["54", "I7N3G6G", "54", 0, "no description", 60.2, 1], ["55", "I7N3G6G", "55", 0, "no description", 60.2, 1], ["56", "I7N3G6G", "56", 0, "no description", 60.2, 1], ["57", "I7N3G6G", "57", 0, "no description", 60.2, 1], ["58", "I7N3G6G", "58", 0, "no description", 60.2, 1], ["59", "I7N3G6G", "59", 0, "no description", 60.2, 1], ["60", "I7N3G6G", "60", 0, "no description", 60.2, 1], ["61", "I7N3G6G", "61", 0, "no description", 60.2, 1], ["62", "I7N3G6G", "62", 0, "no description", 60.2, 1], ["63", "I7N3G6G", "63", 0, "no description", 60.2, 1], ["64", "I7N3G6G", "64", 0, "no description", 60.2, 1], ["65", "I7N3G6G", "65", 0, "no description", 60.2, 1], ["66", "I7N3G6G", "66", 0, "no description", 60.2, 1], ["67", "I7N3G6G", "67", 0, "no description", 60.2, 1], ["68", "I7N3G6G", "68", 0, "no description", 60.2, 1], ["69", "I7N3G6G", "69", 0, "no description", 60.2, 1], ["70", "I7N3G6G", "70", 0, "no description", 60.2, 1], ["71", "I7N3G6G", "71", 0, "no description", 60.2, 1], ["72", "I7N3G6G", "72", 0, "no description", 60.2, 1], ["73", "I7N3G6G", "73", 0, "no description", 60.2, 1], ["74", "I7N3G6G", "74", 0, "no description", 60.2, 1], ["75", "I7N3G6G", "75", 0, "no description", 60.2, 1], ["76", "I7N3G6G", "76", 0, "no description", 60.2, 1], ["77", "I7N3G6G", "77", 0, "no description", 60.2, 1], ["78", "I7N3G6G", "78", 0, "no description", 60.2, 1], ["79", "I7N3G6G", "79", 0, "no description", 60.2, 1], ["80", "I7N3G6G", "80", 0, "no description", 60.2, 1], ["81", "I7N3G6G", "81", 0, "no description", 60.2, 1], ["82", "I7N3G6G", "82", 0, "no description", 60.2, 1], ["83", "I7N3G6G", "83", 0, "no description", 60.2, 1], ["84", "I7N3G6G", "84", 0, "no description", 60.2, 1], ["85", "I7N3G6G", "85", 0, "no description", 60.2, 1], ["86", "I7N3G6G", "86", 0, "no description", 60.2, 1], ["87", "I7N3G6G", "87", 0, "no description", 60.2, 1], ["88", "I7N3G6G", "88", 0, "no description", 60.2, 1], ["89", "I7N3G6G", "89", 0, "no description", 60.2, 1], ["90", "I7N3G6G", "90", 0, "no description", 60.2, 1], ["91", "I7N3G6G", "91", 0, "no description", 60.2, 1], ["92", "I7N3G6G", "92", 0, "no description", 60.2, 1], ["93", "I7N3G6G", "93", 0, "no description", 60.2, 1], ["94", "I7N3G6G", "94", 0, "no description", 60.2, 1], ["95", "I7N3G6G", "95", 0, "no description", 60.2, 1], ["96", "I7N3G6G", "96", 0, "no description", 60.2, 1], ["97", "I7N3G6G", "97", 0, "no description", 60.2, 1], ["98", "I7N3G6G", "98", 0, "no description", 60.2, 1], ["99", "I7N3G6G", "99", 0, "no description", 60.2, 1], ["100", "I7N3G6G", "100", 0, "no description", 60.2, 1], ["101", "I7N3G6G", "101", 0, "no description", 60.2, 1], ["102", "I7N3G6G", "102", 0, "no description", 60.2, 1], ["103", "I7N3G6G", "103", 0, "no description", 60.2, 1], ["104", "I7N3G6G", "104", 0, "no description", 60.2, 1], ["105", "I7N3G6G", "105", 0, "no description", 60.2, 1], ["106", "I7N3G6G", "106", 0, "no description", 60.2, 1], ["107", "I7N3G6G", "107", 0, "no description", 60.2, 1], ["108", "I7N3G6G", "108", 0, "no description", 60.2, 1], ["109", "I7N3G6G", "109", 0, "no description", 60.2, 1], ["110", "I7N3G6G", "110", 0, "no description", 60.2, 1], ["111", "I7N3G6G", "111", 0, "no description", 60.2, 1], ["112", "I7N3G6G", "112", 0, "no description", 60.2, 1], ["113", "I7N3G6G", "113", 0, "no description", 60.2, 1], ["114", "I7N3G6G", "114", 0, "no description", 60.2, 1], ["115", "I7N3G6G", "115", 0, "no description", 60.2, 1], ["116", "I7N3G6G", "116", 0, "no description", 60.2, 1], ["117", "I7N3G6G", "117", 0, "no description", 60.2, 1], ["118", "I7N3G6G", "118", 0, "no description", 60.2, 1], ["119", "I7N3G6G", "119", 0, "no description", 60.2, 1], ["120", "I7N3G6G", "120", 0, "no description", 60.2, 1], ["121", "I7N3G6G", "121", 0, "no description", 60.2, 1], ["122", "I7N3G6G", "122", 0, "no description", 60.2, 1], ["123", "I7N3G6G", "123", 0, "no description", 60.2, 1], ["124", "I7N3G6G", "124", 0, "no description", 60.2, 1], ["125", "I7N3G6G", "125", 0, "no description", 60.2, 1], ["126", "I7N3G6G", "126", 0, "no description", 60.2, 1], ["127", "I7N3G6G", "127", 0, "no description", 60.2, 1], ["128", "I7N3G6G", "128", 0, "no description", 60.2], ["129", "I7N3G6G", "129", 0, "no description", 60.2, 1], ["130", "I7N3G6G", "130", 0, "no description", 60.2, 1], ["131", "I7N3G6G", "131", 0, "no description", 60.2, 1], ["132", "I7N3G6G", "132", 0, "no description", 60.2, 1], ["133", "I7N3G6G", "133", 0, "no description", 60.2, 1], ["134", "I7N3G6G", "134", 0, "no description", 60.2, 1], ["135", "I7N3G6G", "135", 0, "no description", 60.2, 1], ["136", "I7N3G6G", "136", 0, "no description", 60.2, 1], ["137", "I7N3G6G", "137", 0, "no description", 60.2, 1], ["138", "I7N3G6G", "138", 0, "no description", 60.2, 1], ["139", "I7N3G6G", "139", 0, "no description", 60.2, 1], ["140", "I7N3G6G", "140", 0, "no description", 60.2, 1], ["141", "I7N3G6G", "141", 0, "no description", 60.2, 1], ["142", "I7N3G6G", "142", 0, "no description", 60.2, 1], ["143", "I7N3G6G", "143", 0, "no description", 60.2, 1], ["144", "I7N3G6G", "144", 0, "no description", 60.2, 1], ["145", "I7N3G6G", "145", 0, "no description", 60.2, 1], ["146", "I7N3G6G", "146", 0, "no description", 60.2, 1], ["147", "I7N3G6G", "147", 0, "no description", 60.2, 1], ["148", "I7N3G6G", "148", 0, "no description", 60.2, 1], ["149", "I7N3G6G", "149", 0, "no description", 60.2, 1], ["150", "I7N3G6G", "150", 0, "no description", 60.2, 1], ["151", "I7N3G6G", "151", 0, "no description"], ["152", "I7N3G6G", "152", 0, "no description", 60.2, 1], ["153", "I7N3G6G", "153", 0, "no description", 60.2, 1], ["154", "I7N3G6G", "154", 0, "no description", 60.2, 1], ["155", "I7N3G6G", "155", 0, "no description", 60.2, 1], ["156", "I7N3G6G", "156", 0, "no description", 60.2, 1], ["157", "I7N3G6G", "157", 0, "no description", 60.2, 1], ["158", "I7N3G6G", "158", 0, "no description", 60.2, 1], ["159", "I7N3G6G", "159", 0, "no description", 60.2, 1], ["160", "I7N3G6G", "160", 0, "no description", 60.2, 1], ["161", "I7N3G6G", "161", 0, "no description", 60.2, 1], ["162", "I7N3G6G", "162", 0, "no description", 60.2, 1], ["163", "I7N3G6G", "163", 0, "no description", 60.2, 1], ["164", "I7N3G6G", "164", 0, "no description", 60.2, 1], ["165", "I7N3G6G", "165", 0, "no description", 60.2, 1], ["166", "I7N3G6G", "166", 0, "no description", 60.2, 1], ["167", "I7N3G6G", "167", 0, "no description", 60.2, 1], ["168", "I7N3G6G", "168", 0, "no description", 60.2, 1], ["169", "I7N3G6G", "169", 0, "no description", 60.2, 1], ["170", "I7N3G6G", "170", 0, "no description", 60.2, 1], ["171", "I7N3G6G", "171", 0, "no description", 60.2, 1], ["172", "I7N3G6G", "172", 0, "no description", 60.2, 1], ["173", "I7N3G6G", "173", 0, "no description", 60.2, 1], ["174", "I7N3G6G", "174", 0, "no description", 60.2, 1], ["175", "I7N3G6G", "175", 0, "no description", 60.2, 1], ["176", "I7N3G6G", "176", 0, "no description", 60.2, 1], ["177", "I7N3G6G", "177", 0, "no description", 60.2, 1], ["178", "I7N3G6G", "178", 0, "no description", 60.2, 1], ["179", "I7N3G6G", "179", 0, "no description", 60.2, 1], ["180", "I7N3G6G", "180", 0, "no description", 60.2, 1], ["181", "I7N3G6G", "181", 0, "no description", 60.2, 1], ["182", "I7N3G6G", "182", 0, "no description", 60.2, 1], ["183", "I7N3G6G", "183", 0, "no description", 60.2, 1], ["184", "I7N3G6G", "184", 0, "no description", 60.2, 1], ["185", "I7N3G6G", "185", 0, "no description", 60.2, 1], ["186", "I7N3G6G", "186", 0, "no description", 60.2, 1], ["187", "I7N3G6G", "187", 0, "no description", 60.2, 1], ["188", "I7N3G6G", "188", 0, "no description", 60.2, 1], ["189", "I7N3G6G", "189", 0, "no description", 60.2, 1], ["190", "I7N3G6G", "190", 0, "no description", 60.2, 1], ["191", "I7N3G6G", "191", 0, "no description", 60.2, 1], ["192", "I7N3G6G", "192", 0, "no description", 60.2, 1], ["193", "I7N3G6G", "193", 0, "no description", 60.2, 1], ["194", "I7N3G6G", "194", 0, "no description", 60.2, 1], ["195", "I7N3G6G", "195", 0, "no description", 60.2, 1], ["196", "I7N3G6G", "196", 0, "no description", 60.2, 1], ["197", "I7N3G6G", "197", 0, "no description", 60.2, 1], ["198", "I7N3G6G", "198", 0, "no description", 60.2, 1], ["199", "I7N3G6G", "199", 0, "no description", 60.2, 1], ["200", "I7N3G6G", "200", 0, "no description", 60.2, 1], ["201", "I7N3G6G", "201", 0, "no description", 60.2, 1], ["202", "I7N3G6G", "202", 0, "no description", 60.2, 1], ["203", "I7N3G6G", "203", 0, "no description", 60.2, 1], ["204", "I7N3G6G", "204", 0, "no description", 60.2, 1], ["205", "I7N3G6G", "205", 0, "no description", 60.2, 1], ["206", "I7N3G6G", "206", 0, "no description", 60.2, 1], ["207", "I7N3G6G", "207", 0, "no description", 60.2, 1], ["208", "I7N3G6G", "208", 0, "no description", 60.2, 1], ["209", "I7N3G6G", "209", 0, "no description", 60.2, 1], ["210", "I7N3G6G", "210", 0, "no description", 60.2, 1], ["211", "I7N3G6G", "211", 0, "no description", 60.2, 1], ["212", "I7N3G6G", "212", 0, "no description", 60.2, 1], ["213", "I7N3G6G", "213", 0, "no description", 60.2, 1], ["214", "I7N3G6G", "214", 0, "no description", 60.2, 1], ["215", "I7N3G6G", "215", 0, "no description", 60.2, 1], ["216", "I7N3G6G", "216", 0, "no description", 60.2, 1], ["217", "I7N3G6G", "217", 0, "no description", 60.2, 1], ["218", "I7N3G6G", "218", 0, "no description", 60.2, 1], ["219", "I7N3G6G", "219", 0, "no description", 60.2, 1], ["220", "I7N3G6G", "220", 0, "no description", 60.2, 1], ["221", "I7N3G6G", "221", 0, "no description", 60.2, 1], ["222", "I7N3G6G", "222", 0, "no description", 60.2, 1], ["223", "I7N3G6G", "223", 0, "no description", 60.2, 1], ["224", "I7N3G6G", "224", 0, "no description", 60.2, 1], ["225", "I7N3G6G", "225", 0, "no description", 60.2, 1], ["226", "I7N3G6G", "226", 0, "no description", 60.2, 1], ["227", "I7N3G6G", "227", 0, "no description", 60.2, 1], ["228", "I7N3G6G", "228", 0, "no description", 60.2, 1], ["229", "I7N3G6G", "229", 0, "no description", 60.2, 1], ["230", "I7N3G6G", "230", 0, "no description", 60.2, 1], ["231", "I7N3G6G", "231", 0, "no description", 60.2, 1], ["232", "I7N3G6G", "232", 0, "no description", 60.2, 1], ["233", "I7N3G6G", "233", 0, "no description", 60.2, 1], ["234", "I7N3G6G", "234", 0, "no description", 60.2, 1], ["235", "I7N3G6G", "235", 0, "no description", 60.2, 1], ["236", "I7N3G6G", "236", 0, "no description", 60.2, 1], ["237", "I7N3G6G", "237", 0, "no description", 60.2, 1], ["238", "I7N3G6G", "238", 0, "no description", 60.2, 1], ["239", "I7N3G6G", "239", 0, "no description", 60.2, 1], ["240", "I7N3G6G", "240", 0, "no description", 60.2, 1], ["241", "I7N3G6G", "241", 0, "no description", 60.2, 1], ["242", "I7N3G6G", "242", 0, "no description", 60.2, 1], ["243", "I7N3G6G", "243", 0, "no description", 60.2, 1], ["244", "I7N3G6G", "244", 0, "no description", 60.2, 1], ["245", "I7N3G6G", "245", 0, "no description", 60.2, 1], ["246", "I7N3G6G", "246", 0, "no description", 60.2, 1], ["247", "I7N3G6G", "247", 0, "no description", 60.2, 1], ["248", "I7N3G6G", "248", 0, "no description", 60.2, 1], ["249", "I7N3G6G", "249", 0, "no description", 60.2, 1], ["250", "I7N3G6G", "250", 0, "no description", 60.2, 1], ["251", "I7N3G6G", "251", 0, "no description", 60.2, 1], ["252", "I7N3G6G", "252", 0, "no description", 60.2, 1], ["253", "I7N3G6G", "253", 0, "no description", 60.2, 1], ["254", "I7N3G6G", "254", 0, "no description", 60.2, 1], ["255", "I7N3G6G", "255", 0, "no description", 60.2, 1], ["256", "I7N3G6G", "256", 0, "no description", 60.2, 1], ["257", "I7N3G6G", "257", 0, "no description", 60.2, 1], ["258", "I7N3G6G", "258", 0, "no description", 60.2, 1], ["259", "I7N3G6G", "259", 0, "no description", 60.2, 1], ["260", "I7N3G6G", "260", 0, "no description", 60.2, 1], ["261", "I7N3G6G", "261", 0, "no description", 60.2, 1], ["262", "I7N3G6G", "262", 0, "no description", 60.2, 1], ["263", "I7N3G6G", "263", 0, "no description", 60.2, 1], ["264", "I7N3G6G", "264", 0, "no description", 60.2, 1], ["265", "I7N3G6G", "265", 0, "no description", 60.2, 1], ["266", "I7N3G6G", "266", 0, "no description", 60.2, 1], ["267", "I7N3G6G", "267", 0, "no description", 60.2, 1], ["268", "I7N3G6G", "268", 0, "no description", 60.2, 1], ["269", "I7N3G6G", "269", 0, "no description", 60.2, 1], ["270", "I7N3G6G", "270", 0, "no description", 60.2, 1], ["271", "I7N3G6G", "271", 0, "no description", 60.2, 1], ["272", "I7N3G6G", "272", 0, "no description", 60.2, 1], ["273", "I7N3G6G", "273", 0, "no description", 60.2, 1], ["274", "I7N3G6G", "274", 0, "no description", 60.2, 1], ["275", "I7N3G6G", "275", 0, "no description", 60.2, 1], ["276", "I7N3G6G", "276", 0, "no description", 60.2, 1], ["277", "I7N3G6G", "277", 0, "no description", 60.2, 1], ["278", "I7N3G6G", "278", 0, "no description", 60.2, 1], ["279", "I7N3G6G", "279", 0, "no description", 60.2, 1], ["280", "I7N3G6G", "280", 0, "no description", 60.2, 1], ["281", "I7N3G6G", "281", 0, "no description", 60.2, 1], ["282", "I7N3G6G", "282", 0, "no description", 60.2, 1], ["283", "I7N3G6G", "283", 0, "no description", 60.2, 1], ["284", "I7N3G6G", "284", 0, "no description", 60.2, 1], ["285", "I7N3G6G", "285", 0, "no description", 60.2, 1], ["286", "I7N3G6G", "286", 0, "no description", 60.2, 1], ["287", "I7N3G6G", "287", 0, "no description", 60.2, 1], ["288", "I7N3G6G", "288", 0, "no description", 60.2, 1], ["289", "I7N3G6G", "289", 0, "no description", 60.2, 1], ["290", "I7N3G6G", "290", 0, "no description", 60.2, 1], ["291", "I7N3G6G", "291", 0, "no description", 60.2, 1], ["292", "I7N3G6G", "292", 0, "no description", 60.2, 1], ["293", "I7N3G6G", "293", 0, "no description", 60.2, 1], ["294", "I7N3G6G", "294", 0, "no description", 60.2, 1], ["295", "I7N3G6G", "295", 0, "no description", 60.2, 1], ["296", "I7N3G6G", "296", 0, "no description", 60.2, 1], ["297", "I7N3G6G", "297", 0, "no description", 60.2, 1], ["298", "I7N3G6G", "298", 0, "no description", 60.2, 1], ["299", "I7N3G6G", "299", 0, "no description", 60.2, 1], ["300", "I7N3G6G", "300", 0, "no description", 60.2, 1], ["301", "I7N3G6G", "301", 0, "no description", 60.2, 1], ["302", "I7N3G6G", "302", 0, "no description", 60.2, 1], ["303", "I7N3G6G", "303", 0, "no description", 60.2, 1], ["304", "I7N3G6G", "304", 0, "no description", 60.2, 1], ["305", "I7N3G6G", "305", 0, "no description", 60.2, 1], ["306", "I7N3G6G", "306", 0, "no description", 60.2, 1], ["307", "I7N3G6G", "307", 0, "no description", 60.2, 1], ["308", "I7N3G6G", "308", 0, "no description", 60.2, 1], ["309", "I7N3G6G", "309", 0, "no description", 60.2, 1], ["310", "I7N3G6G", "310", 0, "no description", 60.2, 1], ["311", "I7N3G6G", "311", 0, "no description", 60.2, 1], ["312", "I7N3G6G", "312", 0, "no description", 60.2, 1], ["313", "I7N3G6G", "313", 0, "no description", 60.2, 1], ["314", "I7N3G6G", "314", 0, "no description", 60.2, 1], ["315", "I7N3G6G", "315", 0, "no description", 60.2, 1], ["316", "I7N3G6G", "316", 0, "no description", 60.2, 1], ["317", "I7N3G6G", "317", 0, "no description", 60.2, 1], ["318", "I7N3G6G", "318", 0, "no description", 60.2, 1], ["319", "I7N3G6G", "319", 0, "no description", 60.2, 1], ["320", "I7N3G6G", "320", 0, "no description", 60.2, 1], ["321", "I7N3G6G", "321", 0, "no description", 60.2, 1], ["322", "I7N3G6G", "322", 0, "no description", 60.2, 1], ["323", "I7N3G6G", "323", 0, "no description", 60.2, 1], ["324", "I7N3G6G", "324", 0, "no description", 60.2, 1], ["325", "I7N3G6G", "325", 0, "no description", 60.2, 1], ["326", "I7N3G6G", "326", 0, "no description", 60.2, 1], ["327", "I7N3G6G", "327", 0, "no description", 60.2, 1], ["328", "I7N3G6G", "328", 0, "no description", 60.2, 1], ["329", "I7N3G6G", "329", 0, "no description", 60.2, 1], ["330", "I7N3G6G", "330", 0, "no description", 60.2, 1], ["331", "I7N3G6G", "331", 0, "no description", 60.2, 1], ["332", "I7N3G6G", "332", 0, "no description", 60.2, 1], ["333", "I7N3G6G", "333", 0, "no description", 60.2, 1], ["334", "I7N3G6G", "334", 0, "no description", 60.2, 1], ["335", "I7N3G6G", "335", 0, "no description", 60.2, 1], ["336", "I7N3G6G", "336", 0, "no description", 60.2, 1], ["337", "I7N3G6G", "337", 0, "no description", 60.2, 1], ["338", "I7N3G6G", "338", 0, "no description", 60.2, 1], ["339", "I7N3G6G", "339", 0, "no description", 60.2, 1], ["340", "I7N3G6G", "340", 0, "no description", 60.2, 1], ["341", "I7N3G6G", "341", 0, "no description", 60.2, 1], ["342", "I7N3G6G", "342", 0, "no description", 60.2, 1], ["343", "I7N3G6G", "343", 0, "no description", 60.2, 1], ["344", "I7N3G6G", "344", 0, "no description", 60.2, 1], ["345", "I7N3G6G", "345", 0, "no description", 60.2, 1], ["346", "I7N3G6G", "346", 0, "no description", 60.2, 1], ["347", "I7N3G6G", "347", 0, "no description", 60.2, 1], ["348", "I7N3G6G", "348", 0, "no description", 60.2, 1], ["349", "I7N3G6G", "349", 0, "no description", 60.2, 1], ["350", "I7N3G6G", "350", 0, "no description", 60.2, 1], ["351", "I7N3G6G", "351", 0, "no description", 60.2, 1], ["352", "I7N3G6G", "352", 0, "no description", 60.2, 1], ["353", "I7N3G6G", "353", 0, "no description", 60.2, 1], ["354", "I7N3G6G", "354", 0, "no description", 60.2, 1], ["355", "I7N3G6G", "355", 0, "no description", 60.2, 1], ["356", "I7N3G6G", "356", 0, "no description", 60.2, 1], ["357", "I7N3G6G", "357", 0, "no description", 60.2, 1], ["358", "I7N3G6G", "358", 0, "no description", 60.2, 1], ["359", "I7N3G6G", "359", 0, "no description", 60.2, 1], ["360", "I7N3G6G", "360", 0, "no description", 60.2, 1], ["361", "I7N3G6G", "361", 0, "no description", 60.2, 1], ["362", "I7N3G6G", "362", 0, "no description", 60.2, 1], ["363", "I7N3G6G", "363", 0, "no description", 60.2, 1], ["364", "I7N3G6G", "364", 0, "no description", 60.2, 1], ["365", "I7N3G6G", "365", 0, "no description", 60.2, 1], ["366", "I7N3G6G", "366", 0, "no description", 60.2, 1], ["367", "I7N3G6G", "367", 0, "no description", 60.2, 1], ["368", "I7N3G6G", "368", 0, "no description", 60.2, 1], ["369", "I7N3G6G", "369", 0, "no description", 60.2, 1], ["370", "I7N3G6G", "370", 0, "no description", 60.2, 1], ["371", "I7N3G6G", "371", 0, "no description", 60.2, 1], ["372", "I7N3G6G", "372", 0, "no description", 60.2, 1], ["373", "I7N3G6G", "373", 0, "no description", 60.2, 1], ["374", "I7N3G6G", "374", 0, "no description", 60.2, 1], ["375", "I7N3G6G", "375", 0, "no description", 60.2, 1], ["376", "I7N3G6G", "376", 0, "no description", 60.2, 1], ["377", "I7N3G6G", "377", 0, "no description", 60.2, 1], ["378", "I7N3G6G", "378", 0, "no description", 60.2, 1], ["379", "I7N3G6G", "379", 0, "no description", 60.2, 1], ["380", "I7N3G6G", "380", 0, "no description", 60.2, 1], ["381", "I7N3G6G", "381", 0, "no description", 60.2, 1], ["382", "I7N3G6G", "382", 0, "no description", 60.2, 1], ["383", "I7N3G6G", "383", 0, "no description", 60.2, 1], ["384", "I7N3G6G", "384", 0, "no description", 60.2, 1], ["385", "I7N3G6G", "385", 0, "no description", 60.2, 1], ["386", "I7N3G6G", "386", 0, "no description", 60.2, 1], ["387", "I7N3G6G", "387", 0, "no description", 60.2, 1], ["388", "I7N3G6G", "388", 0, "no description", 60.2, 1], ["389", "I7N3G6G", "389", 0, "no description", 60.2, 1], ["390", "I7N3G6G", "390", 0, "no description", 60.2, 1], ["391", "I7N3G6G", "391", 0, "no description", 60.2, 1], ["392", "I7N3G6G", "392", 0, "no description", 60.2, 1], ["393", "I7N3G6G", "393", 0, "no description", 60.2, 1], ["394", "I7N3G6G", "394", 0, "no description", 60.2, 1], ["395", "I7N3G6G", "395", 0, "no description", 60.2, 1], ["396", "I7N3G6G", "396", 0, "no description", 60.2, 1], ["397", "I7N3G6G", "397", 0, "no description", 60.2, 1], ["398", "I7N3G6G", "398", 0, "no description", 60.2, 1], ["399", "I7N3G6G", "399", 0, "no description", 60.2, 1], ["400", "I7N3G6G", "400", 0, "no description", 60.2, 1], ["401", "I7N3G6G", "401", 0, "no description", 60.2, 1], ["402", "I7N3G6G", "402", 0, "no description", 60.2, 1], ["403", "I7N3G6G", "403", 0, "no description", 60.2, 1], ["404", "I7N3G6G", "404", 0, "no description", 60.2, 1], ["405", "I7N3G6G", "405", 0, "no description", 60.2, 1], ["406", "I7N3G6G", "406", 0, "no description", 60.2, 1], ["407", "I7N3G6G", "407", 0, "no description", 60.2, 1], ["408", "I7N3G6G", "408", 0, "no description", 60.2, 1], ["409", "I7N3G6G", "409", 0, "no description", 60.2, 1], ["410", "I7N3G6G", "410", 0, "no description", 60.2, 1], ["411", "I7N3G6G", "411", 0, "no description", 60.2, 1], ["412", "I7N3G6G", "412", 0, "no description", 60.2, 1], ["413", "I7N3G6G", "413", 0, "no description", 60.2, 1], ["414", "I7N3G6G", "414", 0, "no description", 60.2, 1], ["415", "I7N3G6G", "415", 0, "no description", 60.2, 1], ["416", "I7N3G6G", "416", 0, "no description", 60.2, 1], ["417", "I7N3G6G", "417", 0, "no description", 60.2, 1], ["418", "I7N3G6G", "418", 0, "no description", 60.2, 1], ["419", "I7N3G6G", "419", 0, "no description", 60.2, 1], ["420", "I7N3G6G", "420", 0, "no description", 60.2, 1], ["421", "I7N3G6G", "421", 0, "no description", 60.2, 1], ["422", "I7N3G6G", "422", 0, "no description", 60.2, 1], ["423", "I7N3G6G", "423", 0, "no description", 60.2, 1], ["424", "I7N3G6G", "424", 0, "no description", 60.2, 1], ["425", "I7N3G6G", "425", 0, "no description", 60.2, 1], ["426", "I7N3G6G", "426", 0, "no description", 60.2, 1], ["427", "I7N3G6G", "427", 0, "no description", 60.2, 1], ["428", "I7N3G6G", "428", 0, "no description", 60.2, 1], ["429", "I7N3G6G", "429", 0, "no description", 60.2, 1], ["430", "I7N3G6G", "430", 0, "no description", 60.2, 1], ["431", "I7N3G6G", "431", 0, "no description", 60.2, 1], ["432", "I7N3G6G", "432", 0, "no description", 60.2, 1], ["433", "I7N3G6G", "433", 0, "no description", 60.2, 1], ["434", "I7N3G6G", "434", 0, "no description", 60.2, 1], ["435", "I7N3G6G", "435", 0, "no description", 60.2, 1], ["436", "I7N3G6G", "436", 0, "no description", 60.2, 1], ["437", "I7N3G6G", "437", 0, "no description", 60.2, 1], ["438", "I7N3G6G", "438", 0, "no description", 60.2, 1], ["439", "I7N3G6G", "439", 0, "no description", 60.2, 1], ["440", "I7N3G6G", "440", 0, "no description", 60.2, 1], ["441", "I7N3G6G", "441", 0, "no description", 60.2, 1], ["442", "I7N3G6G", "442", 0, "no description", 60.2, 1], ["443", "I7N3G6G", "443", 0, "no description", 60.2, 1], ["444", "I7N3G6G", "444", 0, "no description", 60.2, 1], ["445", "I7N3G6G", "445", 0, "no description", 60.2, 1], ["446", "I7N3G6G", "446", 0, "no description", 60.2, 1], ["447", "I7N3G6G", "447", 0, "no description", 60.2, 1], ["448", "I7N3G6G", "448", 0, "no description", 60.2, 1], ["449", "I7N3G6G", "449", 0, "no description", 60.2, 1], ["450", "I7N3G6G", "450", 0, "no description", 60.2, 1], ["451", "I7N3G6G", "451", 0, "no description", 60.2, 1], ["452", "I7N3G6G", "452", 0, "no description", 60.2, 1], ["453", "I7N3G6G", "453", 0, "no description", 60.2, 1], ["454", "I7N3G6G", "454", 0, "no description", 60.2, 1], ["455", "I7N3G6G", "455", 0, "no description", 60.2, 1], ["456", "I7N3G6G", "456", 0, "no description", 60.2, 1], ["457", "I7N3G6G", "457", 0, "no description", 60.2, 1], ["458", "I7N3G6G", "458", 0, "no description", 60.2, 1], ["459", "I7N3G6G", "459", 0, "no description", 60.2, 1], ["460", "I7N3G6G", "460", 0, "no description", 60.2, 1], ["461", "I7N3G6G", "461", 0, "no description", 60.2, 1], ["462", "I7N3G6G", "462", 0, "no description", 60.2, 1], ["463", "I7N3G6G", "463", 0, "no description", 60.2, 1], ["464", "I7N3G6G", "464", 0, "no description", 60.2, 1], ["465", "I7N3G6G", "465", 0, "no description", 60.2, 1], ["466", "I7N3G6G", "466", 0, "no description", 60.2, 1], ["467", "I7N3G6G", "467", 0, "no description", 60.2, 1], ["468", "I7N3G6G", "468", 0, "no description", 60.2, 1], ["469", "I7N3G6G", "469", 0, "no description", 60.2, 1], ["470", "I7N3G6G", "470", 0, "no description", 60.2, 1], ["471", "I7N3G6G", "471", 0, "no description", 60.2, 1], ["472", "I7N3G6G", "472", 0, "no description", 60.2, 1], ["473", "I7N3G6G", "473", 0, "no description", 60.2, 1], ["474", "I7N3G6G", "474", 0, "no description", 60.2, 1], ["475", "I7N3G6G", "475", 0, "no description", 60.2, 1], ["476", "I7N3G6G", "476", 0, "no description", 60.2, 1], ["477", "I7N3G6G", "477", 0, "no description", 60.2, 1], ["478", "I7N3G6G", "478", 0, "no description", 60.2, 1], ["479", "I7N3G6G", "479", 0, "no description", 60.2, 1], ["480", "I7N3G6G", "480", 0, "no description", 60.2, 1], ["481", "I7N3G6G", "481", 0, "no description", 60.2, 1], ["482", "I7N3G6G", "482", 0, "no description", 60.2, 1], ["483", "I7N3G6G", "483", 0, "no description", 60.2, 1], ["484", "I7N3G6G", "484", 0, "no description", 60.2, 1], ["485", "I7N3G6G", "485", 0, "no description", 60.2, 1], ["486", "I7N3G6G", "486", 0, "no description", 60.2, 1], ["487", "I7N3G6G", "487", 0, "no description", 60.2, 1], ["488", "I7N3G6G", "488", 0, "no description", 60.2, 1], ["489", "I7N3G6G", "489", 0, "no description", 60.2, 1], ["490", "I7N3G6G", "490", 0, "no description", 60.2, 1], ["491", "I7N3G6G", "491", 0, "no description", 60.2, 1], ["492", "I7N3G6G", "492", 0, "no description", 60.2, 1], ["493", "I7N3G6G", "493", 0, "no description", 60.2, 1], ["494", "I7N3G6G", "494", 0, "no description", 60.2, 1], ["495", "I7N3G6G", "495", 0, "no description", 60.2, 1], ["496", "I7N3G6G", "496", 0, "no description", 60.2, 1], ["497", "I7N3G6G", "497", 0, "no description", 60.2, 1], ["498", "I7N3G6G", "498", 0, "no description", 60.2, 1], ["499", "I7N3G6G", "499", 0, "no description", 60.2, 1], ["500", "I7N3G6G", "500", 0, "no description", 60.2, 1], ["501", "I7N3G6G", "501", 0, "no description", 60.2, 1], ["502", "I7N3G6G", "502", 0, "no description", 60.2, 1], ["503", "I7N3G6G", "503", 0, "no description", 60.2, 1], ["504", "I7N3G6G", "504", 0, "no description", 60.2, 1], ["505", "I7N3G6G", "505", 0, "no description", 60.2, 1], ["506", "I7N3G6G", "506", 0, "no description", 60.2, 1], ["507", "I7N3G6G", "507", 0, "no description", 60.2, 1], ["508", "I7N3G6G", "508", 0, "no description", 60.2, 1], ["509", "I7N3G6G", "509", 0, "no description", 60.2, 1], ["510", "I7N3G6G", "510", 0, "no description", 60.2, 1], ["511", "I7N3G6G", "511", 0, "no description", 60.2, 1], ["512", "I7N3G6G", "512", 0, "no description", 60.2, 1], ["513", "I7N3G6G", "513", 0, "no description", 60.2, 1], ["514", "I7N3G6G", "514", 0, "no description", 60.2, 1], ["515", "I7N3G6G", "515", 0, "no description", 60.2, 1], ["516", "I7N3G6G", "516", 0, "no description", 60.2, 1], ["517", "I7N3G6G", "517", 0, "no description", 60.2, 1], ["518", "I7N3G6G", "518", 0, "no description", 60.2, 1], ["519", "I7N3G6G", "519", 0, "no description", 60.2, 1], ["520", "I7N3G6G", "520", 0, "no description", 60.2, 1], ["521", "I7N3G6G", "521", 0, "no description", 60.2, 1], ["522", "I7N3G6G", "522", 0, "no description", 60.2, 1], ["523", "I7N3G6G", "523", 0, "no description", 60.2, 1], ["524", "I7N3G6G", "524", 0, "no description", 60.2, 1], ["525", "I7N3G6G", "525", 0, "no description", 60.2, 1], ["526", "I7N3G6G", "526", 0, "no description", 60.2, 1], ["527", "I7N3G6G", "527", 0, "no description", 60.2, 1], ["528", "I7N3G6G", "528", 0, "no description", 60.2, 1], ["529", "I7N3G6G", "529", 0, "no description", 60.2, 1], ["530", "I7N3G6G", "530", 0, "no description", 60.2, 1], ["531", "I7N3G6G", "531", 0, "no description", 60.2, 1], ["532", "I7N3G6G", "532", 0, "no description", 60.2, 1], ["533", "I7N3G6G", "533", 0, "no description", 60.2, 1], ["534", "I7N3G6G", "534", 0, "no description", 60.2, 1], ["535", "I7N3G6G", "535", 0, "no description", 60.2, 1], ["536", "I7N3G6G", "536", 0, "no description", 60.2, 1], ["537", "I7N3G6G", "537", 0, "no description", 60.2, 1], ["538", "I7N3G6G", "538", 0, "no description", 60.2, 1], ["539", "I7N3G6G", "539", 0, "no description", 60.2, 1], ["540", "I7N3G6G", "540", 0, "no description", 60.2, 1], ["541", "I7N3G6G", "541", 0, "no description", 60.2, 1], ["542", "I7N3G6G", "542", 0, "no description", 60.2, 1], ["543", "I7N3G6G", "543", 0, "no description", 60.2, 1], ["544", "I7N3G6G", "544", 0, "no description", 60.2, 1], ["545", "I7N3G6G", "545", 0, "no description", 60.2, 1], ["546", "I7N3G6G", "546", 0, "no description", 60.2, 1], ["547", "I7N3G6G", "547", 0, "no description", 60.2, 1], ["548", "I7N3G6G", "548", 0, "no description", 60.2, 1], ["549", "I7N3G6G", "549", 0, "no description", 60.2, 1], ["550", "I7N3G6G", "550", 0, "no description", 60.2, 1], ["551", "I7N3G6G", "551", 0, "no description", 60.2, 1], ["552", "I7N3G6G", "552", 0, "no description", 60.2, 1], ["553", "I7N3G6G", "553", 0, "no description", 60.2, 1], ["554", "I7N3G6G", "554", 0, "no description", 60.2, 1], ["555", "I7N3G6G", "555", 0, "no description", 60.2, 1], ["556", "I7N3G6G", "556", 0, "no description", 60.2, 1], ["557", "I7N3G6G", "557", 0, "no description", 60.2, 1], ["558", "I7N3G6G", "558", 0, "no description", 60.2, 1], ["559", "I7N3G6G", "559", 0, "no description", 60.2, 1], ["560", "I7N3G6G", "560", 0, "no description", 60.2, 1], ["561", "I7N3G6G", "561", 0, "no description", 60.2, 1], ["562", "I7N3G6G", "562", 0, "no description", 60.2, 1], ["563", "I7N3G6G", "563", 0, "no description", 60.2, 1], ["564", "I7N3G6G", "564", 0, "no description", 60.2, 1], ["565", "I7N3G6G", "565", 0, "no description", 60.2, 1], ["566", "I7N3G6G", "566", 0, "no description", 60.2, 1], ["567", "I7N3G6G", "567", 0, "no description", 60.2, 1], ["568", "I7N3G6G", "568", 0, "no description", 60.2, 1], ["569", "I7N3G6G", "569", 0, "no description", 60.2, 1], ["570", "I7N3G6G", "570", 0, "no description", 60.2, 1], ["571", "I7N3G6G", "571", 0, "no description", 60.2, 1], ["572", "I7N3G6G", "572", 0, "no description", 60.2, 1], ["573", "I7N3G6G", "573", 0, "no description", 60.2, 1], ["574", "I7N3G6G", "574", 0, "no description", 60.2, 1], ["575", "I7N3G6G", "575", 0, "no description", 60.2, 1], ["576", "I7N3G6G", "576", 0, "no description", 60.2, 1], ["577", "I7N3G6G", "577", 0, "no description", 60.2, 1], ["578", "I7N3G6G", "578", 0, "no description", 60.2, 1], ["579", "I7N3G6G", "579", 0, "no description", 60.2, 1], ["580", "I7N3G6G", "580", 0, "no description", 60.2, 1], ["581", "I7N3G6G", "581", 0, "no description", 60.2, 1], ["582", "I7N3G6G", "582", 0, "no description", 60.2, 1], ["583", "I7N3G6G", "583", 0, "no description", 60.2, 1], ["584", "I7N3G6G", "584", 0, "no description", 60.2, 1], ["585", "I7N3G6G", "585", 0, "no description", 60.2, 1], ["586", "I7N3G6G", "586", 0, "no description", 60.2, 1], ["587", "I7N3G6G", "587", 0, "no description", 60.2, 1], ["588", "I7N3G6G", "588", 0, "no description", 60.2, 1], ["589", "I7N3G6G", "589", 0, "no description", 60.2, 1], ["590", "I7N3G6G", "590", 0, "no description", 60.2, 1], ["591", "I7N3G6G", "591", 0, "no description", 60.2, 1], ["592", "I7N3G6G", "592", 0, "no description", 60.2, 1], ["593", "I7N3G6G", "593", 0, "no description", 60.2, 1], ["594", "I7N3G6G", "594", 0, "no description", 60.2, 1], ["595", "I7N3G6G", "595", 0, "no description", 60.2, 1], ["596", "I7N3G6G", "596", 0, "no description", 60.2, 1], ["597", "I7N3G6G", "597", 0, "no description", 60.2, 1], ["598", "I7N3G6G", "598", 0, "no description", 60.2, 1], ["599", "I7N3G6G", "599", 0, "no description", 60.2, 1], ["600", "I7N3G6G", "600", 0, "no description", 60.2, 1], ["601", "I7N3G6G", "601", 0, "no description", 60.2, 1], ["602", "I7N3G6G", "602", 0, "no description", 60.2, 1], ["603", "I7N3G6G", "603", 0, "no description", 60.2, 1], ["604", "I7N3G6G", "604", 0, "no description", 60.2, 1], ["605", "I7N3G6G", "605", 0, "no description", 60.2, 1], ["606", "I7N3G6G", "606", 0, "no description", 60.2, 1], ["607", "I7N3G6G", "607", 0, "no description", 60.2, 1], ["608", "I7N3G6G", "608", 0, "no description", 60.2, 1], ["609", "I7N3G6G", "609", 0, "no description", 60.2, 1], ["610", "I7N3G6G", "610", 0, "no description", 60.2, 1], ["611", "I7N3G6G", "611", 0, "no description", 60.2, 1], ["612", "I7N3G6G", "612", 0, "no description", 60.2, 1], ["613", "I7N3G6G", "613", 0, "no description", 60.2, 1], ["614", "I7N3G6G", "614", 0, "no description", 60.2, 1], ["615", "I7N3G6G", "615", 0, "no description", 60.2, 1], ["616", "I7N3G6G", "616", 0, "no description", 60.2, 1], ["617", "I7N3G6G", "617", 0, "no description", 60.2, 1], ["618", "I7N3G6G", "618", 0, "no description", 60.2, 1], ["619", "I7N3G6G", "619", 0, "no description", 60.2, 1], ["620", "I7N3G6G", "620", 0, "no description", 60.2, 1], ["621", "I7N3G6G", "621", 0, "no description", 60.2, 1], ["622", "I7N3G6G", "622", 0, "no description", 60.2, 1], ["623", "I7N3G6G", "623", 0, "no description", 60.2, 1], ["624", "I7N3G6G", "624", 0, "no description", 60.2, 1], ["625", "I7N3G6G", "625", 0, "no description", 60.2, 1], ["626", "I7N3G6G", "626", 0, "no description", 60.2, 1], ["627", "I7N3G6G", "627", 0, "no description", 60.2, 1], ["628", "I7N3G6G", "628", 0, "no description", 60.2, 1], ["629", "I7N3G6G", "629", 0, "no description", 60.2, 1], ["630", "I7N3G6G", "630", 0, "no description", 60.2, 1], ["631", "I7N3G6G", "631", 0, "no description", 60.2, 1], ["632", "I7N3G6G", "632", 0, "no description", 60.2, 1], ["633", "I7N3G6G", "633", 0, "no description", 60.2, 1], ["634", "I7N3G6G", "634", 0, "no description", 60.2, 1], ["635", "I7N3G6G", "635", 0, "no description", 60.2, 1], ["636", "I7N3G6G", "636", 0, "no description", 60.2, 1], ["637", "I7N3G6G", "637", 0, "no description", 60.2, 1], ["638", "I7N3G6G", "638", 0, "no description", 60.2, 1], ["639", "I7N3G6G", "639", 0, "no description", 60.2, 1], ["640", "I7N3G6G", "640", 0, "no description", 60.2, 1], ["641", "I7N3G6G", "641", 0, "no description", 60.2, 1], ["642", "I7N3G6G", "642", 0, "no description", 60.2, 1], ["643", "I7N3G6G", "643", 0, "no description", 60.2, 1], ["644", "I7N3G6G", "644", 0, "no description", 60.2, 1], ["645", "I7N3G6G", "645", 0, "no description", 60.2, 1], ["646", "I7N3G6G", "646", 0, "no description", 60.2, 1], ["647", "I7N3G6G", "647", 0, "no description", 60.2, 1], ["648", "I7N3G6G", "648", 0, "no description", 60.2, 1], ["649", "I7N3G6G", "649", 0, "no description", 60.2, 1], ["650", "I7N3G6G", "650", 0, "no description", 60.2, 1], ["651", "I7N3G6G", "651", 0, "no description", 60.2, 1], ["652", "I7N3G6G", "652", 0, "no description", 60.2, 1], ["653", "I7N3G6G", "653", 0, "no description", 60.2, 1], ["654", "I7N3G6G", "654", 0, "no description", 60.2, 1], ["655", "I7N3G6G", "655", 0, "no description", 60.2, 1], ["656", "I7N3G6G", "656", 0, "no description", 60.2, 1], ["657", "I7N3G6G", "657", 0, "no description", 60.2, 1], ["658", "I7N3G6G", "658", 0, "no description", 60.2, 1], ["659", "I7N3G6G", "659", 0, "no description", 60.2, 1], ["660", "I7N3G6G", "660", 0, "no description", 60.2, 1], ["661", "I7N3G6G", "661", 0, "no description", 60.2, 1], ["662", "I7N3G6G", "662", 0, "no description", 60.2, 1], ["663", "I7N3G6G", "663", 0, "no description", 60.2, 1], ["664", "I7N3G6G", "664", 0, "no description", 60.2, 1], ["665", "I7N3G6G", "665", 0, "no description", 60.2, 1], ["666", "I7N3G6G", "666", 0, "no description", 60.2, 1], ["667", "I7N3G6G", "667", 0, "no description", 60.2, 1], ["668", "I7N3G6G", "668", 0, "no description", 60.2, 1], ["669", "I7N3G6G", "669", 0, "no description", 60.2, 1], ["670", "I7N3G6G", "670", 0, "no description", 60.2, 1], ["671", "I7N3G6G", "671", 0, "no description", 60.2, 1], ["672", "I7N3G6G", "672", 0, "no description", 60.2, 1], ["673", "I7N3G6G", "673", 0, "no description", 60.2, 1], ["674", "I7N3G6G", "674", 0, "no description", 60.2, 1], ["675", "I7N3G6G", "675", 0, "no description", 60.2, 1], ["676", "I7N3G6G", "676", 0, "no description", 60.2, 1], ["677", "I7N3G6G", "677", 0, "no description", 60.2, 1], ["678", "I7N3G6G", "678", 0, "no description", 60.2, 1], ["679", "I7N3G6G", "679", 0, "no description", 60.2, 1], ["680", "I7N3G6G", "680", 0, "no description", 60.2, 1], ["681", "I7N3G6G", "681", 0, "no description", 60.2, 1], ["682", "I7N3G6G", "682", 0, "no description", 60.2, 1], ["683", "I7N3G6G", "683", 0, "no description", 60.2, 1], ["684", "I7N3G6G", "684", 0, "no description", 60.2, 1], ["685", "I7N3G6G", "685", 0, "no description", 60.2, 1], ["686", "I7N3G6G", "686", 0, "no description", 60.2, 1], ["687", "I7N3G6G", "687", 0, "no description", 60.2, 1], ["688", "I7N3G6G", "688", 0, "no description", 60.2, 1], ["689", "I7N3G6G", "689", 0, "no description", 60.2, 1], ["690", "I7N3G6G", "690", 0, "no description", 60.2, 1], ["691", "I7N3G6G", "691", 0, "no description", 60.2, 1], ["692", "I7N3G6G", "692", 0, "no description", 60.2, 1], ["693", "I7N3G6G", "693", 0, "no description", 60.2, 1], ["694", "I7N3G6G", "694", 0, "no description", 60.2, 1], ["695", "I7N3G6G", "695", 0, "no description", 60.2, 1], ["696", "I7N3G6G", "696", 0, "no description", 60.2, 1], ["697", "I7N3G6G", "697", 0, "no description", 60.2, 1], ["698", "I7N3G6G", "698", 0, "no description", 60.2, 1], ["699", "I7N3G6G", "699", 0, "no description", 60.2, 1], ["700", "I7N3G6G", "700", 0, "no description", 60.2, 1], ["701", "I7N3G6G", "701", 0, "no description", 60.2, 1], ["702", "I7N3G6G", "702", 0, "no description", 60.2, 1], ["703", "I7N3G6G", "703", 0, "no description", 60.2, 1], ["704", "I7N3G6G", "704", 0, "no description", 60.2, 1], ["705", "I7N3G6G", "705", 0, "no description", 60.2, 1], ["706", "I7N3G6G", "706", 0, "no description", 60.2, 1], ["707", "I7N3G6G", "707", 0, "no description", 60.2, 1], ["708", "I7N3G6G", "708", 0, "no description", 60.2, 1], ["709", "I7N3G6G", "709", 0, "no description", 60.2, 1], ["710", "I7N3G6G", "710", 0, "no description", 60.2, 1], ["711", "I7N3G6G", "711", 0, "no description", 60.2, 1], ["712", "I7N3G6G", "712", 0, "no description", 60.2, 1], ["713", "I7N3G6G", "713", 0, "no description", 60.2, 1], ["714", "I7N3G6G", "714", 0, "no description", 60.2, 1], ["715", "I7N3G6G", "715", 0, "no description", 60.2, 1], ["716", "I7N3G6G", "716", 0, "no description", 60.2, 1], ["717", "I7N3G6G", "717", 0, "no description", 60.2, 1], ["718", "I7N3G6G", "718", 0, "no description", 60.2, 1], ["719", "I7N3G6G", "719", 0, "no description", 60.2, 1], ["720", "I7N3G6G", "720", 0, "no description", 60.2, 1], ["721", "I7N3G6G", "721", 0, "no description", 60.2, 1], ["722", "I7N3G6G", "722", 0, "no description", 60.2, 1], ["723", "I7N3G6G", "723", 0, "no description", 60.2, 1], ["724", "I7N3G6G", "724", 0, "no description", 60.2, 1], ["725", "I7N3G6G", "725", 0, "no description", 60.2, 1], ["726", "I7N3G6G", "726", 0, "no description", 60.2, 1], ["727", "I7N3G6G", "727", 0, "no description", 60.2, 1], ["728", "I7N3G6G", "728", 0, "no description", 60.2, 1], ["729", "I7N3G6G", "729", 0, "no description", 60.2, 1], ["730", "I7N3G6G", "730", 0, "no description", 60.2, 1], ["731", "I7N3G6G", "731", 0, "no description", 60.2, 1], ["732", "I7N3G6G", "732", 0, "no description", 60.2, 1], ["733", "I7N3G6G", "733", 0, "no description", 60.2, 1], ["734", "I7N3G6G", "734", 0, "no description", 60.2, 1], ["735", "I7N3G6G", "735", 0, "no description", 60.2, 1], ["736", "I7N3G6G", "736", 0, "no description", 60.2, 1], ["737", "I7N3G6G", "737", 0, "no description", 60.2, 1], ["738", "I7N3G6G", "738", 0, "no description", 60.2, 1], ["739", "I7N3G6G", "739", 0, "no description", 60.2, 1], ["740", "I7N3G6G", "740", 0, "no description", 60.2, 1], ["741", "I7N3G6G", "741", 0, "no description", 60.2, 1], ["742", "I7N3G6G", "742", 0, "no description", 60.2, 1], ["743", "I7N3G6G", "743", 0, "no description", 60.2, 1], ["744", "I7N3G6G", "744", 0, "no description", 60.2, 1], ["745", "I7N3G6G", "745", 0, "no description", 60.2, 1], ["746", "I7N3G6G", "746", 0, "no description", 60.2, 1], ["747", "I7N3G6G", "747", 0, "no description", 60.2, 1], ["748", "I7N3G6G", "748", 0, "no description", 60.2, 1], ["749", "I7N3G6G", "749", 0, "no description", 60.2, 1], ["750", "I7N3G6G", "750", 0, "no description", 60.2, 1], ["751", "I7N3G6G", "751", 0, "no description", 60.2, 1], ["752", "I7N3G6G", "752", 0, "no description", 60.2, 1], ["753", "I7N3G6G", "753", 0, "no description", 60.2, 1], ["754", "I7N3G6G", "754", 0, "no description", 60.2, 1], ["755", "I7N3G6G", "755", 0, "no description", 60.2, 1], ["756", "I7N3G6G", "756", 0, "no description", 60.2, 1], ["757", "I7N3G6G", "757", 0, "no description", 60.2, 1], ["758", "I7N3G6G", "758", 0, "no description", 60.2, 1], ["759", "I7N3G6G", "759", 0, "no description", 60.2, 1], ["760", "I7N3G6G", "760", 0, "no description", 60.2, 1], ["761", "I7N3G6G", "761", 0, "no description", 60.2, 1], ["762", "I7N3G6G", "762", 0, "no description", 60.2, 1], ["763", "I7N3G6G", "763", 0, "no description", 60.2, 1], ["764", "I7N3G6G", "764", 0, "no description", 60.2, 1], ["765", "I7N3G6G", "765", 0, "no description", 60.2, 1], ["766", "I7N3G6G", "766", 0, "no description", 60.2, 1], ["767", "I7N3G6G", "767", 0, "no description", 60.2, 1], ["768", "I7N3G6G", "768", 0, "no description", 60.2, 1], ["769", "I7N3G6G", "769", 0, "no description", 60.2, 1], ["770", "I7N3G6G", "770", 0, "no description", 60.2, 1], ["771", "I7N3G6G", "771", 0, "no description", 60.2, 1], ["772", "I7N3G6G", "772", 0, "no description", 60.2, 1], ["773", "I7N3G6G", "773", 0, "no description", 60.2, 1], ["774", "I7N3G6G", "774", 0, "no description", 60.2, 1], ["775", "I7N3G6G", "775", 0, "no description", 60.2, 1], ["776", "I7N3G6G", "776", 0, "no description", 60.2, 1], ["777", "I7N3G6G", "777", 0, "no description", 60.2, 1], ["778", "I7N3G6G", "778", 0, "no description", 60.2, 1], ["779", "I7N3G6G", "779", 0, "no description", 60.2, 1], ["780", "I7N3G6G", "780", 0, "no description", 60.2, 1], ["781", "I7N3G6G", "781", 0, "no description", 60.2, 1], ["782", "I7N3G6G", "782", 0, "no description", 60.2, 1], ["783", "I7N3G6G", "783", 0, "no description", 60.2, 1], ["784", "I7N3G6G", "784", 0, "no description", 60.2, 1], ["785", "I7N3G6G", "785", 0, "no description", 60.2, 1], ["786", "I7N3G6G", "786", 0, "no description", 60.2, 1], ["787", "I7N3G6G", "787", 0, "no description", 60.2, 1], ["788", "I7N3G6G", "788", 0, "no description", 60.2, 1], ["789", "I7N3G6G", "789", 0, "no description", 60.2, 1], ["790", "I7N3G6G", "790", 0, "no description", 60.2, 1], ["791", "I7N3G6G", "791", 0, "no description", 60.2, 1], ["792", "I7N3G6G", "792", 0, "no description", 60.2, 1], ["793", "I7N3G6G", "793", 0, "no description", 60.2, 1], ["794", "I7N3G6G", "794", 0, "no description", 60.2, 1], ["795", "I7N3G6G", "795", 0, "no description", 60.2, 1], ["796", "I7N3G6G", "796", 0, "no description", 60.2, 1], ["797", "I7N3G6G", "797", 0, "no description", 60.2, 1], ["798", "I7N3G6G", "798", 0, "no description", 60.2, 1], ["799", "I7N3G6G", "799", 0, "no description", 60.2, 1], ["800", "I7N3G6G", "800", 0, "no description", 60.2, 1], ["801", "I7N3G6G", "801", 0, "no description", 60.2, 1], ["802", "I7N3G6G", "802", 0, "no description", 60.2, 1], ["803", "I7N3G6G", "803", 0, "no description", 60.2, 1], ["804", "I7N3G6G", "804", 0, "no description", 60.2, 1], ["805", "I7N3G6G", "805", 0, "no description", 60.2, 1], ["806", "I7N3G6G", "806", 0, "no description", 60.2, 1], ["807", "I7N3G6G", "807", 0, "no description", 60.2, 1], ["808", "I7N3G6G", "808", 0, "no description", 60.2, 1], ["809", "I7N3G6G", "809", 0, "no description", 60.2, 1], ["810", "I7N3G6G", "810", 0, "no description", 60.2, 1], ["811", "I7N3G6G", "811", 0, "no description", 60.2, 1], ["812", "I7N3G6G", "812", 0, "no description", 60.2, 1], ["813", "I7N3G6G", "813", 0, "no description", 60.2, 1], ["814", "I7N3G6G", "814", 0, "no description", 60.2, 1], ["815", "I7N3G6G", "815", 0, "no description", 60.2, 1], ["816", "I7N3G6G", "816", 0, "no description", 60.2, 1], ["817", "I7N3G6G", "817", 0, "no description", 60.2, 1], ["818", "I7N3G6G", "818", 0, "no description", 60.2, 1], ["819", "I7N3G6G", "819", 0, "no description", 60.2, 1], ["820", "I7N3G6G", "820", 0, "no description", 60.2, 1], ["821", "I7N3G6G", "821", 0, "no description", 60.2, 1], ["822", "I7N3G6G", "822", 0, "no description", 60.2, 1], ["823", "I7N3G6G", "823", 0, "no description", 60.2, 1], ["824", "I7N3G6G", "824", 0, "no description", 60.2, 1], ["825", "I7N3G6G", "825", 0, "no description", 60.2, 1], ["826", "I7N3G6G", "826", 0, "no description", 60.2, 1], ["827", "I7N3G6G", "827", 0, "no description", 60.2, 1], ["828", "I7N3G6G", "828", 0, "no description", 60.2, 1], ["829", "I7N3G6G", "829", 0, "no description", 60.2, 1], ["830", "I7N3G6G", "830", 0, "no description", 60.2, 1], ["831", "I7N3G6G", "831", 0, "no description", 60.2, 1], ["832", "I7N3G6G", "832", 0, "no description", 60.2, 1], ["833", "I7N3G6G", "833", 0, "no description", 60.2, 1], ["834", "I7N3G6G", "834", 0, "no description", 60.2, 1], ["835", "I7N3G6G", "835", 0, "no description", 60.2, 1], ["836", "I7N3G6G", "836", 0, "no description", 60.2, 1], ["837", "I7N3G6G", "837", 0, "no description", 60.2, 1], ["838", "I7N3G6G", "838", 0, "no description", 60.2, 1], ["839", "I7N3G6G", "839", 0, "no description", 60.2, 1], ["840", "I7N3G6G", "840", 0, "no description", 60.2, 1], ["841", "I7N3G6G", "841", 0, "no description", 60.2, 1], ["842", "I7N3G6G", "842", 0, "no description", 60.2, 1], ["843", "I7N3G6G", "843", 0, "no description", 60.2, 1], ["844", "I7N3G6G", "844", 0, "no description", 60.2, 1], ["845", "I7N3G6G", "845", 0, "no description", 60.2, 1], ["846", "I7N3G6G", "846", 0, "no description", 60.2, 1], ["847", "I7N3G6G", "847", 0, "no description", 60.2, 1], ["848", "I7N3G6G", "848", 0, "no description", 60.2, 1], ["849", "I7N3G6G", "849", 0, "no description", 60.2, 1], ["850", "I7N3G6G", "850", 0, "no description", 60.2, 1], ["851", "I7N3G6G", "851", 0, "no description", 60.2, 1], ["852", "I7N3G6G", "852", 0, "no description", 60.2, 1], ["853", "I7N3G6G", "853", 0, "no description", 60.2, 1], ["854", "I7N3G6G", "854", 0, "no description", 60.2, 1], ["855", "I7N3G6G", "855", 0, "no description", 60.2, 1], ["856", "I7N3G6G", "856", 0, "no description", 60.2, 1], ["857", "I7N3G6G", "857", 0, "no description", 60.2, 1], ["858", "I7N3G6G", "858", 0, "no description", 60.2, 1], ["859", "I7N3G6G", "859", 0, "no description", 60.2, 1], ["860", "I7N3G6G", "860", 0, "no description", 60.2, 1], ["861", "I7N3G6G", "861", 0, "no description", 60.2, 1], ["862", "I7N3G6G", "862", 0, "no description", 60.2, 1], ["863", "I7N3G6G", "863", 0, "no description", 60.2, 1], ["864", "I7N3G6G", "864", 0, "no description", 60.2, 1], ["865", "I7N3G6G", "865", 0, "no description", 60.2, 1], ["866", "I7N3G6G", "866", 0, "no description", 60.2, 1], ["867", "I7N3G6G", "867", 0, "no description", 60.2, 1], ["868", "I7N3G6G", "868", 0, "no description", 60.2, 1], ["869", "I7N3G6G", "869", 0, "no description", 60.2, 1], ["870", "I7N3G6G", "870", 0, "no description", 60.2, 1], ["871", "I7N3G6G", "871", 0, "no description", 60.2, 1], ["872", "I7N3G6G", "872", 0, "no description", 60.2, 1], ["873", "I7N3G6G", "873", 0, "no description", 60.2, 1], ["874", "I7N3G6G", "874", 0, "no description", 60.2, 1], ["875", "I7N3G6G", "875", 0, "no description", 60.2, 1], ["876", "I7N3G6G", "876", 0, "no description", 60.2, 1], ["877", "I7N3G6G", "877", 0, "no description", 60.2, 1], ["878", "I7N3G6G", "878", 0, "no description", 60.2, 1], ["879", "I7N3G6G", "879", 0, "no description", 60.2, 1], ["880", "I7N3G6G", "880", 0, "no description", 60.2, 1], ["881", "I7N3G6G", "881", 0, "no description", 60.2, 1], ["882", "I7N3G6G", "882", 0, "no description", 60.2, 1], ["883", "I7N3G6G", "883", 0, "no description", 60.2, 1], ["884", "I7N3G6G", "884", 0, "no description", 60.2, 1], ["885", "I7N3G6G", "885", 0, "no description", 60.2, 1], ["886", "I7N3G6G", "886", 0, "no description", 60.2, 1], ["887", "I7N3G6G", "887", 0, "no description", 60.2, 1], ["888", "I7N3G6G", "888", 0, "no description", 60.2, 1], ["889", "I7N3G6G", "889", 0, "no description", 60.2, 1], ["890", "I7N3G6G", "890", 0, "no description", 60.2, 1], ["891", "I7N3G6G", "891", 0, "no description", 60.2, 1], ["892", "I7N3G6G", "892", 0, "no description", 60.2, 1], ["893", "I7N3G6G", "893", 0, "no description", 60.2, 1], ["894", "I7N3G6G", "894", 0, "no description", 60.2, 1], ["895", "I7N3G6G", "895", 0, "no description", 60.2, 1], ["896", "I7N3G6G", "896", 0, "no description", 60.2, 1], ["897", "I7N3G6G", "897", 0, "no description", 60.2, 1], ["898", "I7N3G6G", "898", 0, "no description", 60.2, 1], ["899", "I7N3G6G", "899", 0, "no description", 60.2, 1], ["900", "I7N3G6G", "900", 0, "no description", 60.2, 1], ["901", "I7N3G6G", "901", 0, "no description", 60.2, 1], ["902", "I7N3G6G", "902", 0, "no description", 60.2, 1], ["903", "I7N3G6G", "903", 0, "no description", 60.2, 1], ["904", "I7N3G6G", "904", 0, "no description", 60.2, 1], ["905", "I7N3G6G", "905", 0, "no description", 60.2, 1], ["906", "I7N3G6G", "906", 0, "no description", 60.2, 1], ["907", "I7N3G6G", "907", 0, "no description", 60.2, 1], ["908", "I7N3G6G", "908", 0, "no description", 60.2, 1], ["909", "I7N3G6G", "909", 0, "no description", 60.2, 1], ["910", "I7N3G6G", "910", 0, "no description", 60.2, 1], ["911", "I7N3G6G", "911", 0, "no description", 60.2, 1], ["912", "I7N3G6G", "912", 0, "no description", 60.2, 1], ["913", "I7N3G6G", "913", 0, "no description", 60.2, 1], ["914", "I7N3G6G", "914", 0, "no description", 60.2, 1], ["915", "I7N3G6G", "915", 0, "no description", 60.2, 1], ["916", "I7N3G6G", "916", 0, "no description", 60.2, 1], ["917", "I7N3G6G", "917", 0, "no description", 60.2, 1], ["918", "I7N3G6G", "918", 0, "no description", 60.2, 1], ["919", "I7N3G6G", "919", 0, "no description", 60.2, 1], ["920", "I7N3G6G", "920", 0, "no description", 60.2, 1], ["921", "I7N3G6G", "921", 0, "no description", 60.2, 1], ["922", "I7N3G6G", "922", 0, "no description", 60.2, 1], ["923", "I7N3G6G", "923", 0, "no description", 60.2, 1], ["924", "I7N3G6G", "924", 0, "no description", 60.2, 1], ["925", "I7N3G6G", "925", 0, "no description", 60.2, 1], ["926", "I7N3G6G", "926", 0, "no description", 60.2, 1], ["927", "I7N3G6G", "927", 0, "no description", 60.2, 1], ["928", "I7N3G6G", "928", 0, "no description", 60.2, 1], ["929", "I7N3G6G", "929", 0, "no description", 60.2, 1], ["930", "I7N3G6G", "930", 0, "no description", 60.2, 1], ["931", "I7N3G6G", "931", 0, "no description", 60.2, 1], ["932", "I7N3G6G", "932", 0, "no description", 60.2, 1], ["933", "I7N3G6G", "933", 0, "no description", 60.2, 1], ["934", "I7N3G6G", "934", 0, "no description", 60.2, 1], ["935", "I7N3G6G", "935", 0, "no description", 60.2, 1], ["936", "I7N3G6G", "936", 0, "no description", 60.2, 1], ["937", "I7N3G6G", "937", 0, "no description", 60.2, 1], ["938", "I7N3G6G", "938", 0, "no description", 60.2, 1], ["939", "I7N3G6G", "939", 0, "no description", 60.2, 1], ["940", "I7N3G6G", "940", 0, "no description", 60.2, 1], ["941", "I7N3G6G", "941", 0, "no description", 60.2, 1], ["942", "I7N3G6G", "942", 0, "no description", 60.2, 1], ["943", "I7N3G6G", "943", 0, "no description", 60.2, 1], ["944", "I7N3G6G", "944", 0, "no description", 60.2, 1], ["945", "I7N3G6G", "945", 0, "no description", 60.2, 1], ["946", "I7N3G6G", "946", 0, "no description", 60.2, 1], ["947", "I7N3G6G", "947", 0, "no description", 60.2, 1], ["948", "I7N3G6G", "948", 0, "no description", 60.2, 1], ["949", "I7N3G6G", "949", 0, "no description", 60.2, 1], ["950", "I7N3G6G", "950", 0, "no description", 60.2, 1], ["951", "I7N3G6G", "951", 0, "no description", 60.2, 1], ["952", "I7N3G6G", "952", 0, "no description", 60.2, 1], ["953", "I7N3G6G", "953", 0, "no description", 60.2, 1], ["954", "I7N3G6G", "954", 0, "no description", 60.2, 1], ["955", "I7N3G6G", "955", 0, "no description", 60.2, 1], ["956", "I7N3G6G", "956", 0, "no description", 60.2, 1], ["957", "I7N3G6G", "957", 0, "no description", 60.2, 1], ["958", "I7N3G6G", "958", 0, "no description", 60.2, 1], ["959", "I7N3G6G", "959", 0, "no description", 60.2, 1], ["960", "I7N3G6G", "960", 0, "no description", 60.2, 1], ["961", "I7N3G6G", "961", 0, "no description", 60.2, 1], ["962", "I7N3G6G", "962", 0, "no description", 60.2, 1], ["963", "I7N3G6G", "963", 0, "no description", 60.2, 1], ["964", "I7N3G6G", "964", 0, "no description", 60.2, 1], ["965", "I7N3G6G", "965", 0, "no description", 60.2, 1], ["966", "I7N3G6G", "966", 0, "no description", 60.2, 1], ["967", "I7N3G6G", "967", 0, "no description", 60.2, 1], ["968", "I7N3G6G", "968", 0, "no description", 60.2, 1], ["969", "I7N3G6G", "969", 0, "no description", 60.2, 1], ["970", "I7N3G6G", "970", 0, "no description", 60.2, 1], ["971", "I7N3G6G", "971", 0, "no description", 60.2, 1], ["972", "I7N3G6G", "972", 0, "no description", 60.2, 1], ["973", "I7N3G6G", "973", 0, "no description", 60.2, 1], ["974", "I7N3G6G", "974", 0, "no description", 60.2, 1], ["975", "I7N3G6G", "975", 0, "no description", 60.2, 1], ["976", "I7N3G6G", "976", 0, "no description", 60.2, 1], ["977", "I7N3G6G", "977", 0, "no description", 60.2, 1], ["978", "I7N3G6G", "978", 0, "no description", 60.2, 1], ["979", "I7N3G6G", "979", 0, "no description", 60.2, 1], ["980", "I7N3G6G", "980", 0, "no description", 60.2, 1], ["981", "I7N3G6G", "981", 0, "no description", 60.2, 1], ["982", "I7N3G6G", "982", 0, "no description", 60.2, 1], ["983", "I7N3G6G", "983", 0, "no description", 60.2, 1], ["984", "I7N3G6G", "984", 0, "no description", 60.2, 1], ["985", "I7N3G6G", "985", 0, "no description", 60.2, 1], ["986", "I7N3G6G", "986", 0, "no description", 60.2, 1], ["987", "I7N3G6G", "987", 0, "no description", 60.2, 1], ["988", "I7N3G6G", "988", 0, "no description", 60.2, 1], ["989", "I7N3G6G", "989", 0, "no description", 60.2, 1], ["990", "I7N3G6G", "990", 0, "no description", 60.2, 1], ["991", "I7N3G6G", "991", 0, "no description", 60.2, 1], ["992", "I7N3G6G", "992", 0, "no description", 60.2, 1], ["993", "I7N3G6G", "993", 0, "no description", 60.2, 1], ["994", "I7N3G6G", "994", 0, "no description", 60.2, 1], ["995", "I7N3G6G", "995", 0, "no description", 60.2, 1], ["996", "I7N3G6G", "996", 0, "no description", 60.2, 1], ["997", "I7N3G6G", "997", 0, "no description", 60.2, 1], ["998", "I7N3G6G", "998", 0, "no description", 60.2, 1], ["999", "I7N3G6G", "999", 0, "no description", 60.2, 1], ["1000", "I7N3G6G", "1000", 0, "no description", 60.2, 1], ["1001", "I7N3G6G", "1001", 0, "no description", 60.2, 1], ["1002", "I7N3G6G", "1002", 0, "no description", 60.2, 1], ["1003", "I7N3G6G", "1003", 0, "no description", 60.2, 1], ["1004", "I7N3G6G", "1004", 0, "no description", 60.2, 1], ["1005", "I7N3G6G", "1005", 0, "no description", 60.2, 1], ["1006", "I7N3G6G", "1006", 0, "no description", 60.2, 1], ["1007", "I7N3G6G", "1007", 0, "no description", 60.2, 1], ["1008", "I7N3G6G", "1008", 0, "no description", 60.2, 1], ["1009", "I7N3G6G", "1009", 0, "no description", 60.2, 1], ["1010", "I7N3G6G", "1010", 0, "no description", 60.2, 1], ["1011", "I7N3G6G", "1011", 0, "no description", 60.2, 1], ["1012", "I7N3G6G", "1012", 0, "no description", 60.2, 1], ["1013", "I7N3G6G", "1013", 0, "no description", 60.2, 1], ["1014", "I7N3G6G", "1014", 0, "no description", 60.2, 1], ["1015", "I7N3G6G", "1015", 0, "no description", 60.2, 1], ["1016", "I7N3G6G", "1016", 0, "no description", 60.2, 1], ["1017", "I7N3G6G", "1017", 0, "no description", 60.2, 1], ["1018", "I7N3G6G", "1018", 0, "no description", 60.2, 1], ["1019", "I7N3G6G", "1019", 0, "no description", 60.2, 1], ["1020", "I7N3G6G", "1020", 0, "no description", 60.2, 1], ["1021", "I7N3G6G", "1021", 0, "no description", 60.2, 1], ["1022", "I7N3G6G", "1022", 0, "no description", 60.2, 1], ["1023", "I7N3G6G", "1023", 0, "no description", 60.2, 1], ["1024", "I7N3G6G", "1024", 0, "no description", 60.2, 1], ["1025", "I7N3G6G", "1025", 0, "no description", 60.2, 1], ["1026", "I7N3G6G", "1026", 0, "no description", 60.2, 1], ["1027", "I7N3G6G", "1027", 0, "no description", 60.2, 1], ["1028", "I7N3G6G", "1028", 0, "no description", 60.2, 1], ["1029", "I7N3G6G", "1029", 0, "no description", 60.2, 1], ["1030", "I7N3G6G", "1030", 0, "no description", 60.2, 1], ["1031", "I7N3G6G", "1031", 0, "no description", 60.2, 1], ["1032", "I7N3G6G", "1032", 0, "no description", 60.2, 1], ["1033", "I7N3G6G", "1033", 0, "no description", 60.2, 1], ["1034", "I7N3G6G", "1034", 0, "no description", 60.2, 1], ["1035", "I7N3G6G", "1035", 0, "no description", 60.2, 1], ["1036", "I7N3G6G", "1036", 0, "no description", 60.2, 1], ["1037", "I7N3G6G", "1037", 0, "no description", 60.2, 1], ["1038", "I7N3G6G", "1038", 0, "no description", 60.2, 1], ["1039", "I7N3G6G", "1039", 0, "no description", 60.2, 1], ["1040", "I7N3G6G", "1040", 0, "no description", 60.2, 1], ["1041", "I7N3G6G", "1041", 0, "no description", 60.2, 1], ["1042", "I7N3G6G", "1042", 0, "no description", 60.2, 1], ["1043", "I7N3G6G", "1043", 0, "no description", 60.2, 1], ["1044", "I7N3G6G", "1044", 0, "no description", 60.2, 1], ["1045", "I7N3G6G", "1045", 0, "no description", 60.2, 1], ["1046", "I7N3G6G", "1046", 0, "no description", 60.2, 1], ["1047", "I7N3G6G", "1047", 0, "no description", 60.2, 1], ["1048", "I7N3G6G", "1048", 0, "no description", 60.2, 1], ["1049", "I7N3G6G", "1049", 0, "no description", 60.2, 1], ["1050", "I7N3G6G", "1050", 0, "no description", 60.2, 1], ["1051", "I7N3G6G", "1051", 0, "no description", 60.2, 1], ["1052", "I7N3G6G", "1052", 0, "no description", 60.2, 1], ["1053", "I7N3G6G", "1053", 0, "no description", 60.2, 1], ["1054", "I7N3G6G", "1054", 0, "no description", 60.2, 1], ["1055", "I7N3G6G", "1055", 0, "no description", 60.2, 1], ["1056", "I7N3G6G", "1056", 0, "no description", 60.2, 1], ["1057", "I7N3G6G", "1057", 0, "no description", 60.2, 1], ["1058", "I7N3G6G", "1058", 0, "no description", 60.2, 1], ["1059", "I7N3G6G", "1059", 0, "no description", 60.2, 1], ["1060", "I7N3G6G", "1060", 0, "no description", 60.2, 1], ["1061", "I7N3G6G", "1061", 0, "no description", 60.2, 1], ["1062", "I7N3G6G", "1062", 0, "no description", 60.2, 1], ["1063", "I7N3G6G", "1063", 0, "no description", 60.2, 1], ["1064", "I7N3G6G", "1064", 0, "no description", 60.2, 1], ["1065", "I7N3G6G", "1065", 0, "no description", 60.2, 1], ["1066", "I7N3G6G", "1066", 0, "no description", 60.2, 1], ["1067", "I7N3G6G", "1067", 0, "no description", 60.2, 1], ["1068", "I7N3G6G", "1068", 0, "no description", 60.2, 1], ["1069", "I7N3G6G", "1069", 0, "no description", 60.2, 1], ["1070", "I7N3G6G", "1070", 0, "no description", 60.2, 1], ["1071", "I7N3G6G", "1071", 0, "no description", 60.2, 1], ["1072", "I7N3G6G", "1072", 0, "no description", 60.2, 1], ["1073", "I7N3G6G", "1073", 0, "no description", 60.2, 1], ["1074", "I7N3G6G", "1074", 0, "no description", 60.2, 1], ["1075", "I7N3G6G", "1075", 0, "no description", 60.2, 1], ["1076", "I7N3G6G", "1076", 0, "no description", 60.2, 1], ["1077", "I7N3G6G", "1077", 0, "no description", 60.2, 1], ["1078", "I7N3G6G", "1078", 0, "no description", 60.2, 1], ["1079", "I7N3G6G", "1079", 0, "no description", 60.2, 1], ["1080", "I7N3G6G", "1080", 0, "no description", 60.2, 1], ["1081", "I7N3G6G", "1081", 0, "no description", 60.2, 1], ["1082", "I7N3G6G", "1082", 0, "no description", 60.2, 1], ["1083", "I7N3G6G", "1083", 0, "no description", 60.2, 1], ["1084", "I7N3G6G", "1084", 0, "no description", 60.2, 1], ["1085", "I7N3G6G", "1085", 0, "no description", 60.2, 1], ["1086", "I7N3G6G", "1086", 0, "no description", 60.2, 1], ["1087", "I7N3G6G", "1087", 0, "no description", 60.2, 1], ["1088", "I7N3G6G", "1088", 0, "no description", 60.2, 1], ["1089", "I7N3G6G", "1089", 0, "no description", 60.2, 1], ["1090", "I7N3G6G", "1090", 0, "no description", 60.2, 1], ["1091", "I7N3G6G", "1091", 0, "no description", 60.2, 1], ["1092", "I7N3G6G", "1092", 0, "no description", 60.2, 1], ["1093", "I7N3G6G", "1093", 0, "no description", 60.2, 1], ["1094", "I7N3G6G", "1094", 0, "no description", 60.2, 1], ["1095", "I7N3G6G", "1095", 0, "no description", 60.2, 1], ["1096", "I7N3G6G", "1096", 0, "no description", 60.2, 1], ["1097", "I7N3G6G", "1097", 0, "no description", 60.2, 1], ["1098", "I7N3G6G", "1098", 0, "no description", 60.2, 1], ["1099", "I7N3G6G", "1099", 0, "no description", 60.2, 1], ["1100", "I7N3G6G", "1100", 0, "no description", 60.2, 1], ["1101", "I7N3G6G", "1101", 0, "no description", 60.2, 1], ["1102", "I7N3G6G", "1102", 0, "no description", 60.2, 1], ["1103", "I7N3G6G", "1103", 0, "no description", 60.2, 1], ["1104", "I7N3G6G", "1104", 0, "no description", 60.2, 1], ["1105", "I7N3G6G", "1105", 0, "no description", 60.2, 1], ["1106", "I7N3G6G", "1106", 0, "no description", 60.2, 1], ["1107", "I7N3G6G", "1107", 0, "no description", 60.2, 1], ["1108", "I7N3G6G", "1108", 0, "no description", 60.2, 1], ["1109", "I7N3G6G", "1109", 0, "no description", 60.2, 1], ["1110", "I7N3G6G", "1110", 0, "no description", 60.2, 1], ["1111", "I7N3G6G", "1111", 0, "no description", 60.2, 1], ["1112", "I7N3G6G", "1112", 0, "no description", 60.2, 1], ["1113", "I7N3G6G", "1113", 0, "no description", 60.2, 1], ["1114", "I7N3G6G", "1114", 0, "no description", 60.2, 1], ["1115", "I7N3G6G", "1115", 0, "no description", 60.2, 1], ["1116", "I7N3G6G", "1116", 0, "no description", 60.2, 1], ["1117", "I7N3G6G", "1117", 0, "no description", 60.2, 1], ["1118", "I7N3G6G", "1118", 0, "no description", 60.2, 1], ["1119", "I7N3G6G", "1119", 0, "no description", 60.2, 1], ["1120", "I7N3G6G", "1120", 0, "no description", 60.2, 1], ["1121", "I7N3G6G", "1121", 0, "no description", 60.2, 1], ["1122", "I7N3G6G", "1122", 0, "no description", 60.2, 1], ["1123", "I7N3G6G", "1123", 0, "no description", 60.2, 1], ["1124", "I7N3G6G", "1124", 0, "no description", 60.2, 1], ["1125", "I7N3G6G", "1125", 0, "no description", 60.2, 1], ["1126", "I7N3G6G", "1126", 0, "no description", 60.2, 1], ["1127", "I7N3G6G", "1127", 0, "no description", 60.2, 1], ["1128", "I7N3G6G", "1128", 0, "no description", 60.2, 1], ["1129", "I7N3G6G", "1129", 0, "no description", 60.2, 1], ["1130", "I7N3G6G", "1130", 0, "no description", 60.2, 1], ["1131", "I7N3G6G", "1131", 0, "no description", 60.2, 1], ["1132", "I7N3G6G", "1132", 0, "no description", 60.2, 1], ["1133", "I7N3G6G", "1133", 0, "no description", 60.2, 1], ["1134", "I7N3G6G", "1134", 0, "no description", 60.2, 1], ["1135", "I7N3G6G", "1135", 0, "no description", 60.2, 1], ["1136", "I7N3G6G", "1136", 0, "no description", 60.2, 1], ["1137", "I7N3G6G", "1137", 0, "no description", 60.2, 1], ["1138", "I7N3G6G", "1138", 0, "no description", 60.2, 1], ["1139", "I7N3G6G", "1139", 0, "no description", 60.2, 1], ["1140", "I7N3G6G", "1140", 0, "no description", 60.2, 1], ["1141", "I7N3G6G", "1141", 0, "no description", 60.2, 1], ["1142", "I7N3G6G", "1142", 0, "no description", 60.2, 1], ["1143", "I7N3G6G", "1143", 0, "no description", 60.2, 1], ["1144", "I7N3G6G", "1144", 0, "no description", 60.2, 1], ["1145", "I7N3G6G", "1145", 0, "no description", 60.2, 1], ["1146", "I7N3G6G", "1146", 0, "no description", 60.2, 1], ["1147", "I7N3G6G", "1147", 0, "no description", 60.2, 1], ["1148", "I7N3G6G", "1148", 0, "no description", 60.2, 1], ["1149", "I7N3G6G", "1149", 0, "no description", 60.2, 1], ["1150", "I7N3G6G", "1150", 0, "no description", 60.2, 1], ["1151", "I7N3G6G", "1151", 0, "no description", 60.2, 1], ["1152", "I7N3G6G", "1152", 0, "no description", 60.2, 1], ["1153", "I7N3G6G", "1153", 0, "no description", 60.2, 1], ["1154", "I7N3G6G", "1154", 0, "no description", 60.2, 1], ["1155", "I7N3G6G", "1155", 0, "no description", 60.2, 1], ["1156", "I7N3G6G", "1156", 0, "no description", 60.2, 1], ["1157", "I7N3G6G", "1157", 0, "no description", 60.2, 1], ["1158", "I7N3G6G", "1158", 0, "no description", 60.2, 1], ["1159", "I7N3G6G", "1159", 0, "no description", 60.2, 1], ["1160", "I7N3G6G", "1160", 0, "no description", 60.2, 1], ["1161", "I7N3G6G", "1161", 0, "no description", 60.2, 1], ["1162", "I7N3G6G", "1162", 0, "no description", 60.2, 1], ["1163", "I7N3G6G", "1163", 0, "no description", 60.2, 1], ["1164", "I7N3G6G", "1164", 0, "no description", 60.2, 1], ["1165", "I7N3G6G", "1165", 0, "no description", 60.2, 1], ["1166", "I7N3G6G", "1166", 0, "no description", 60.2, 1], ["1167", "I7N3G6G", "1167", 0, "no description", 60.2, 1], ["1168", "I7N3G6G", "1168", 0, "no description", 60.2, 1], ["1169", "I7N3G6G", "1169", 0, "no description", 60.2, 1], ["1170", "I7N3G6G", "1170", 0, "no description", 60.2, 1], ["1171", "I7N3G6G", "1171", 0, "no description", 60.2, 1], ["1172", "I7N3G6G", "1172", 0, "no description", 60.2, 1], ["1173", "I7N3G6G", "1173", 0, "no description", 60.2, 1], ["1174", "I7N3G6G", "1174", 0, "no description", 60.2, 1], ["1175", "I7N3G6G", "1175", 0, "no description", 60.2, 1], ["1176", "I7N3G6G", "1176", 0, "no description", 60.2, 1], ["1177", "I7N3G6G", "1177", 0, "no description", 60.2, 1], ["1178", "I7N3G6G", "1178", 0, "no description", 60.2, 1], ["1179", "I7N3G6G", "1179", 0, "no description", 60.2, 1], ["1180", "I7N3G6G", "1180", 0, "no description", 60.2, 1], ["1181", "I7N3G6G", "1181", 0, "no description", 60.2, 1], ["1182", "I7N3G6G", "1182", 0, "no description", 60.2, 1], ["1183", "I7N3G6G", "1183", 0, "no description", 60.2, 1], ["1184", "I7N3G6G", "1184", 0, "no description", 60.2, 1], ["1185", "I7N3G6G", "1185", 0, "no description", 60.2, 1], ["1186", "I7N3G6G", "1186", 0, "no description", 60.2, 1], ["1187", "I7N3G6G", "1187", 0, "no description", 60.2, 1], ["1188", "I7N3G6G", "1188", 0, "no description", 60.2, 1], ["1189", "I7N3G6G", "1189", 0, "no description", 60.2, 1], ["1190", "I7N3G6G", "1190", 0, "no description", 60.2, 1], ["1191", "I7N3G6G", "1191", 0, "no description", 60.2, 1], ["1192", "I7N3G6G", "1192", 0, "no description", 60.2, 1], ["1193", "I7N3G6G", "1193", 0, "no description", 60.2, 1], ["1194", "I7N3G6G", "1194", 0, "no description", 60.2, 1], ["1195", "I7N3G6G", "1195", 0, "no description", 60.2, 1], ["1196", "I7N3G6G", "1196", 0, "no description", 60.2, 1], ["1197", "I7N3G6G", "1197", 0, "no description", 60.2, 1], ["1198", "I7N3G6G", "1198", 0, "no description", 60.2, 1], ["1199", "I7N3G6G", "1199", 0, "no description", 60.2, 1], ["1200", "I7N3G6G", "1200", 0, "no description", 60.2, 1], ["1201", "I7N3G6G", "1201", 0, "no description", 60.2, 1], ["1202", "I7N3G6G", "1202", 0, "no description", 60.2, 1], ["1203", "I7N3G6G", "1203", 0, "no description", 60.2, 1], ["1204", "I7N3G6G", "1204", 0, "no description", 60.2, 1], ["1205", "I7N3G6G", "1205", 0, "no description", 60.2, 1], ["1206", "I7N3G6G", "1206", 0, "no description", 60.2, 1], ["1207", "I7N3G6G", "1207", 0, "no description", 60.2, 1], ["1208", "I7N3G6G", "1208", 0, "no description", 60.2, 1], ["1209", "I7N3G6G", "1209", 0, "no description", 60.2, 1], ["1210", "I7N3G6G", "1210", 0, "no description", 60.2, 1], ["1211", "I7N3G6G", "1211", 0, "no description", 60.2, 1], ["1212", "I7N3G6G", "1212", 0, "no description", 60.2, 1], ["1213", "I7N3G6G", "1213", 0, "no description", 60.2, 1], ["1214", "I7N3G6G", "1214", 0, "no description", 60.2, 1], ["1215", "I7N3G6G", "1215", 0, "no description", 60.2, 1], ["1216", "I7N3G6G", "1216", 0, "no description", 60.2, 1], ["1217", "I7N3G6G", "1217", 0, "no description", 60.2, 1], ["1218", "I7N3G6G", "1218", 0, "no description", 60.2, 1], ["1219", "I7N3G6G", "1219", 0, "no description", 60.2, 1], ["1220", "I7N3G6G", "1220", 0, "no description", 60.2, 1], ["1221", "I7N3G6G", "1221", 0, "no description", 60.2, 1], ["1222", "I7N3G6G", "1222", 0, "no description", 60.2, 1], ["1223", "I7N3G6G", "1223", 0, "no description", 60.2, 1], ["1224", "I7N3G6G", "1224", 0, "no description", 60.2, 1], ["1225", "I7N3G6G", "1225", 0, "no description", 60.2, 1], ["1226", "I7N3G6G", "1226", 0, "no description", 60.2, 1], ["1227", "I7N3G6G", "1227", 0, "no description", 60.2, 1], ["1228", "I7N3G6G", "1228", 0, "no description", 60.2, 1], ["1229", "I7N3G6G", "1229", 0, "no description", 60.2, 1], ["1230", "I7N3G6G", "1230", 0, "no description", 60.2, 1], ["1231", "I7N3G6G", "1231", 0, "no description", 60.2, 1], ["1232", "I7N3G6G", "1232", 0, "no description", 60.2, 1], ["1233", "I7N3G6G", "1233", 0, "no description", 60.2, 1], ["1234", "I7N3G6G", "1234", 0, "no description", 60.2, 1], ["1235", "I7N3G6G", "1235", 0, "no description", 60.2, 1], ["1236", "I7N3G6G", "1236", 0, "no description", 60.2, 1], ["1237", "I7N3G6G", "1237", 0, "no description", 60.2, 1], ["1238", "I7N3G6G", "1238", 0, "no description", 60.2, 1], ["1239", "I7N3G6G", "1239", 0, "no description", 60.2, 1], ["1240", "I7N3G6G", "1240", 0, "no description", 60.2, 1], ["1241", "I7N3G6G", "1241", 0, "no description", 60.2, 1], ["1242", "I7N3G6G", "1242", 0, "no description", 60.2, 1], ["1243", "I7N3G6G", "1243", 0, "no description", 60.2, 1], ["1244", "I7N3G6G", "1244", 0, "no description", 60.2, 1], ["1245", "I7N3G6G", "1245", 0, "no description", 60.2, 1], ["1246", "I7N3G6G", "1246", 0, "no description", 60.2, 1], ["1247", "I7N3G6G", "1247", 0, "no description", 60.2, 1], ["1248", "I7N3G6G", "1248", 0, "no description", 60.2, 1], ["1249", "I7N3G6G", "1249", 0, "no description", 60.2, 1], ["1250", "I7N3G6G", "1250", 0, "no description", 60.2, 1], ["1251", "I7N3G6G", "1251", 0, "no description", 60.2, 1], ["1252", "I7N3G6G", "1252", 0, "no description", 60.2, 1], ["1253", "I7N3G6G", "1253", 0, "no description", 60.2, 1], ["1254", "I7N3G6G", "1254", 0, "no description", 60.2, 1], ["1255", "I7N3G6G", "1255", 0, "no description", 60.2, 1], ["1256", "I7N3G6G", "1256", 0, "no description", 60.2, 1], ["1257", "I7N3G6G", "1257", 0, "no description", 60.2, 1], ["1258", "I7N3G6G", "1258", 0, "no description", 60.2, 1], ["1259", "I7N3G6G", "1259", 0, "no description", 60.2, 1], ["1260", "I7N3G6G", "1260", 0, "no description", 60.2, 1], ["1261", "I7N3G6G", "1261", 0, "no description", 60.2, 1], ["1262", "I7N3G6G", "1262", 0, "no description", 60.2, 1], ["1263", "I7N3G6G", "1263", 0, "no description", 60.2, 1], ["1264", "I7N3G6G", "1264", 0, "no description", 60.2, 1], ["1265", "I7N3G6G", "1265", 0, "no description", 60.2, 1], ["1266", "I7N3G6G", "1266", 0, "no description", 60.2, 1], ["1267", "I7N3G6G", "1267", 0, "no description", 60.2, 1], ["1268", "I7N3G6G", "1268", 0, "no description", 60.2, 1], ["1269", "I7N3G6G", "1269", 0, "no description", 60.2, 1], ["1270", "I7N3G6G", "1270", 0, "no description", 60.2, 1], ["1271", "I7N3G6G", "1271", 0, "no description", 60.2, 1], ["1272", "I7N3G6G", "1272", 0, "no description", 60.2, 1], ["1273", "I7N3G6G", "1273", 0, "no description", 60.2, 1], ["1274", "I7N3G6G", "1274", 0, "no description", 60.2, 1], ["1275", "I7N3G6G", "1275", 0, "no description", 60.2, 1], ["1276", "I7N3G6G", "1276", 0, "no description", 60.2, 1], ["1277", "I7N3G6G", "1277", 0, "no description", 60.2, 1], ["1278", "I7N3G6G", "1278", 0, "no description", 60.2, 1], ["1279", "I7N3G6G", "1279", 0, "no description", 60.2, 1], ["1280", "I7N3G6G", "1280", 0, "no description", 60.2, 1], ["1281", "I7N3G6G", "1281", 0, "no description", 60.2, 1], ["1282", "I7N3G6G", "1282", 0, "no description", 60.2, 1], ["1283", "I7N3G6G", "1283", 0, "no description", 60.2, 1], ["1284", "I7N3G6G", "1284", 0, "no description", 60.2, 1], ["1285", "I7N3G6G", "1285", 0, "no description", 60.2, 1], ["1286", "I7N3G6G", "1286", 0, "no description", 60.2, 1], ["1287", "I7N3G6G", "1287", 0, "no description", 60.2, 1], ["1288", "I7N3G6G", "1288", 0, "no description", 60.2, 1], ["1289", "I7N3G6G", "1289", 0, "no description", 60.2, 1], ["1290", "I7N3G6G", "1290", 0, "no description", 60.2, 1], ["1291", "I7N3G6G", "1291", 0, "no description", 60.2, 1], ["1292", "I7N3G6G", "1292", 0, "no description", 60.2, 1], ["1293", "I7N3G6G", "1293", 0, "no description", 60.2, 1], ["1294", "I7N3G6G", "1294", 0, "no description", 60.2, 1], ["1295", "I7N3G6G", "1295", 0, "no description", 60.2, 1], ["1296", "I7N3G6G", "1296", 0, "no description", 60.2, 1], ["1297", "I7N3G6G", "1297", 0, "no description", 60.2, 1], ["1298", "I7N3G6G", "1298", 0, "no description", 60.2, 1], ["1299", "I7N3G6G", "1299", 0, "no description", 60.2, 1], ["1300", "I7N3G6G", "1300", 0, "no description", 60.2, 1], ["1301", "I7N3G6G", "1301", 0, "no description", 60.2, 1], ["1302", "I7N3G6G", "1302", 0, "no description", 60.2, 1], ["1303", "I7N3G6G", "1303", 0, "no description", 60.2, 1], ["1304", "I7N3G6G", "1304", 0, "no description", 60.2, 1], ["1305", "I7N3G6G", "1305", 0, "no description", 60.2, 1], ["1306", "I7N3G6G", "1306", 0, "no description", 60.2, 1], ["1307", "I7N3G6G", "1307", 0, "no description", 60.2, 1], ["1308", "I7N3G6G", "1308", 0, "no description", 60.2, 1], ["1309", "I7N3G6G", "1309", 0, "no description", 60.2, 1], ["1310", "I7N3G6G", "1310", 0, "no description", 60.2, 1], ["1311", "I7N3G6G", "1311", 0, "no description", 60.2, 1], ["1312", "I7N3G6G", "1312", 0, "no description", 60.2, 1], ["1313", "I7N3G6G", "1313", 0, "no description", 60.2, 1], ["1314", "I7N3G6G", "1314", 0, "no description", 60.2, 1], ["1315", "I7N3G6G", "1315", 0, "no description", 60.2, 1], ["1316", "I7N3G6G", "1316", 0, "no description", 60.2, 1], ["1317", "I7N3G6G", "1317", 0, "no description", 60.2, 1], ["1318", "I7N3G6G", "1318", 0, "no description", 60.2, 1], ["1319", "I7N3G6G", "1319", 0, "no description", 60.2, 1], ["1320", "I7N3G6G", "1320", 0, "no description", 60.2, 1], ["1321", "I7N3G6G", "1321", 0, "no description", 60.2, 1], ["1322", "I7N3G6G", "1322", 0, "no description", 60.2, 1], ["1323", "I7N3G6G", "1323", 0, "no description", 60.2, 1], ["1324", "I7N3G6G", "1324", 0, "no description", 60.2, 1], ["1325", "I7N3G6G", "1325", 0, "no description", 60.2, 1], ["1326", "I7N3G6G", "1326", 0, "no description", 60.2, 1], ["1327", "I7N3G6G", "1327", 0, "no description", 60.2, 1], ["1328", "I7N3G6G", "1328", 0, "no description", 60.2, 1], ["1329", "I7N3G6G", "1329", 0, "no description", 60.2, 1], ["1330", "I7N3G6G", "1330", 0, "no description", 60.2, 1], ["1331", "I7N3G6G", "1331", 0, "no description", 60.2, 1], ["1332", "I7N3G6G", "1332", 0, "no description", 60.2, 1], ["1333", "I7N3G6G", "1333", 0, "no description", 60.2, 1], ["1334", "I7N3G6G", "1334", 0, "no description", 60.2, 1], ["1335", "I7N3G6G", "1335", 0, "no description", 60.2, 1], ["1336", "I7N3G6G", "1336", 0, "no description", 60.2, 1], ["1337", "I7N3G6G", "1337", 0, "no description", 60.2, 1], ["1338", "I7N3G6G", "1338", 0, "no description", 60.2, 1], ["1339", "I7N3G6G", "1339", 0, "no description", 60.2, 1], ["1340", "I7N3G6G", "1340", 0, "no description", 60.2, 1], ["1341", "I7N3G6G", "1341", 0, "no description", 60.2, 1], ["1342", "I7N3G6G", "1342", 0, "no description", 60.2, 1], ["1343", "I7N3G6G", "1343", 0, "no description", 60.2, 1], ["1344", "I7N3G6G", "1344", 0, "no description", 60.2, 1], ["1345", "I7N3G6G", "1345", 0, "no description", 60.2, 1], ["1346", "I7N3G6G", "1346", 0, "no description", 60.2, 1], ["1347", "I7N3G6G", "1347", 0, "no description", 60.2, 1], ["1348", "I7N3G6G", "1348", 0, "no description", 60.2, 1], ["1349", "I7N3G6G", "1349", 0, "no description", 60.2, 1], ["1350", "I7N3G6G", "1350", 0, "no description", 60.2, 1], ["1351", "I7N3G6G", "1351", 0, "no description", 60.2, 1], ["1352", "I7N3G6G", "1352", 0, "no description", 60.2, 1], ["1353", "I7N3G6G", "1353", 0, "no description", 60.2, 1], ["1354", "I7N3G6G", "1354", 0, "no description", 60.2, 1], ["1355", "I7N3G6G", "1355", 0, "no description", 60.2, 1], ["1356", "I7N3G6G", "1356", 0, "no description", 60.2, 1], ["1357", "I7N3G6G", "1357", 0, "no description", 60.2, 1], ["1358", "I7N3G6G", "1358", 0, "no description", 60.2, 1], ["1359", "I7N3G6G", "1359", 0, "no description", 60.2, 1], ["1360", "I7N3G6G", "1360", 0, "no description", 60.2, 1], ["1361", "I7N3G6G", "1361", 0, "no description", 60.2, 1], ["1362", "I7N3G6G", "1362", 0, "no description", 60.2, 1], ["1363", "I7N3G6G", "1363", 0, "no description", 60.2, 1], ["1364", "I7N3G6G", "1364", 0, "no description", 60.2, 1], ["1365", "I7N3G6G", "1365", 0, "no description", 60.2, 1], ["1366", "I7N3G6G", "1366", 0, "no description", 60.2, 1], ["1367", "I7N3G6G", "1367", 0, "no description", 60.2, 1], ["1368", "I7N3G6G", "1368", 0, "no description", 60.2, 1], ["1369", "I7N3G6G", "1369", 0, "no description", 60.2, 1], ["1370", "I7N3G6G", "1370", 0, "no description", 60.2, 1], ["1371", "I7N3G6G", "1371", 0, "no description", 60.2, 1], ["1372", "I7N3G6G", "1372", 0, "no description", 60.2, 1], ["1373", "I7N3G6G", "1373", 0, "no description", 60.2, 1], ["1374", "I7N3G6G", "1374", 0, "no description", 60.2, 1], ["1375", "I7N3G6G", "1375", 0, "no description", 60.2, 1], ["1376", "I7N3G6G", "1376", 0, "no description", 60.2, 1], ["1377", "I7N3G6G", "1377", 0, "no description", 60.2, 1], ["1378", "I7N3G6G", "1378", 0, "no description", 60.2, 1], ["1379", "I7N3G6G", "1379", 0, "no description", 60.2, 1], ["1380", "I7N3G6G", "1380", 0, "no description", 60.2, 1], ["1381", "I7N3G6G", "1381", 0, "no description", 60.2, 1], ["1382", "I7N3G6G", "1382", 0, "no description", 60.2, 1], ["1383", "I7N3G6G", "1383", 0, "no description", 60.2, 1], ["1384", "I7N3G6G", "1384", 0, "no description", 60.2, 1], ["1385", "I7N3G6G", "1385", 0, "no description", 60.2, 1], ["1386", "I7N3G6G", "1386", 0, "no description", 60.2, 1], ["1387", "I7N3G6G", "1387", 0, "no description", 60.2, 1], ["1388", "I7N3G6G", "1388", 0, "no description", 60.2, 1], ["1389", "I7N3G6G", "1389", 0, "no description", 60.2, 1], ["1390", "I7N3G6G", "1390", 0, "no description", 60.2, 1], ["1391", "I7N3G6G", "1391", 0, "no description", 60.2, 1], ["1392", "I7N3G6G", "1392", 0, "no description", 60.2, 1], ["1393", "I7N3G6G", "1393", 0, "no description", 60.2, 1], ["1394", "I7N3G6G", "1394", 0, "no description", 60.2, 1], ["1395", "I7N3G6G", "1395", 0, "no description", 60.2, 1], ["1396", "I7N3G6G", "1396", 0, "no description", 60.2, 1], ["1397", "I7N3G6G", "1397", 0, "no description", 60.2, 1], ["1398", "I7N3G6G", "1398", 0, "no description", 60.2, 1], ["1399", "I7N3G6G", "1399", 0, "no description", 60.2, 1], ["1400", "I7N3G6G", "1400", 0, "no description", 60.2, 1], ["1401", "I7N3G6G", "1401", 0, "no description", 60.2, 1], ["1402", "I7N3G6G", "1402", 0, "no description", 60.2, 1], ["1403", "I7N3G6G", "1403", 0, "no description", 60.2, 1], ["1404", "I7N3G6G", "1404", 0, "no description", 60.2, 1], ["1405", "I7N3G6G", "1405", 0, "no description", 60.2, 1], ["1406", "I7N3G6G", "1406", 0, "no description", 60.2, 1], ["1407", "I7N3G6G", "1407", 0, "no description", 60.2, 1], ["1408", "I7N3G6G", "1408", 0, "no description", 60.2, 1], ["1409", "I7N3G6G", "1409", 0, "no description", 60.2, 1], ["1410", "I7N3G6G", "1410", 0, "no description", 60.2, 1], ["1411", "I7N3G6G", "1411", 0, "no description", 60.2, 1], ["1412", "I7N3G6G", "1412", 0, "no description", 60.2, 1], ["1413", "I7N3G6G", "1413", 0, "no description", 60.2, 1], ["1414", "I7N3G6G", "1414", 0, "no description", 60.2, 1], ["1415", "I7N3G6G", "1415", 0, "no description", 60.2, 1], ["1416", "I7N3G6G", "1416", 0, "no description", 60.2, 1], ["1417", "I7N3G6G", "1417", 0, "no description", 60.2, 1], ["1418", "I7N3G6G", "1418", 0, "no description", 60.2, 1], ["1419", "I7N3G6G", "1419", 0, "no description", 60.2, 1], ["1420", "I7N3G6G", "1420", 0, "no description", 60.2, 1], ["1421", "I7N3G6G", "1421", 0, "no description", 60.2, 1], ["1422", "I7N3G6G", "1422", 0, "no description", 60.2, 1], ["1423", "I7N3G6G", "1423", 0, "no description", 60.2, 1], ["1424", "I7N3G6G", "1424", 0, "no description", 60.2, 1], ["1425", "I7N3G6G", "1425", 0, "no description", 60.2, 1], ["1426", "I7N3G6G", "1426", 0, "no description", 60.2, 1], ["1427", "I7N3G6G", "1427", 0, "no description", 60.2, 1], ["1428", "I7N3G6G", "1428", 0, "no description", 60.2, 1], ["1429", "I7N3G6G", "1429", 0, "no description", 60.2, 1], ["1430", "I7N3G6G", "1430", 0, "no description", 60.2, 1], ["1431", "I7N3G6G", "1431", 0, "no description", 60.2, 1], ["1432", "I7N3G6G", "1432", 0, "no description", 60.2, 1], ["1433", "I7N3G6G", "1433", 0, "no description", 60.2, 1], ["1434", "I7N3G6G", "1434", 0, "no description", 60.2, 1], ["1435", "I7N3G6G", "1435", 0, "no description", 60.2, 1], ["1436", "I7N3G6G", "1436", 0, "no description", 60.2, 1], ["1437", "I7N3G6G", "1437", 0, "no description", 60.2, 1], ["1438", "I7N3G6G", "1438", 0, "no description", 60.2, 1], ["1439", "I7N3G6G", "1439", 0, "no description", 60.2, 1], ["1440", "I7N3G6G", "1440", 0, "no description", 60.2, 1], ["1441", "I7N3G6G", "1441", 0, "no description", 60.2, 1], ["1442", "I7N3G6G", "1442", 0, "no description", 60.2, 1], ["1443", "I7N3G6G", "1443", 0, "no description", 60.2, 1], ["1444", "I7N3G6G", "1444", 0, "no description", 60.2, 1], ["1445", "I7N3G6G", "1445", 0, "no description", 60.2, 1], ["1446", "I7N3G6G", "1446", 0, "no description", 60.2, 1], ["1447", "I7N3G6G", "1447", 0, "no description", 60.2, 1], ["1448", "I7N3G6G", "1448", 0, "no description", 60.2, 1], ["1449", "I7N3G6G", "1449", 0, "no description", 60.2, 1], ["1450", "I7N3G6G", "1450", 0, "no description", 60.2, 1], ["1451", "I7N3G6G", "1451", 0, "no description", 60.2, 1], ["1452", "I7N3G6G", "1452", 0, "no description", 60.2, 1], ["1453", "I7N3G6G", "1453", 0, "no description", 60.2, 1], ["1454", "I7N3G6G", "1454", 0, "no description", 60.2, 1], ["1455", "I7N3G6G", "1455", 0, "no description", 60.2, 1], ["1456", "I7N3G6G", "1456", 0, "no description", 60.2, 1], ["1457", "I7N3G6G", "1457", 0, "no description", 60.2, 1], ["1458", "I7N3G6G", "1458", 0, "no description", 60.2, 1], ["1459", "I7N3G6G", "1459", 0, "no description", 60.2, 1], ["1460", "I7N3G6G", "1460", 0, "no description", 60.2, 1], ["1461", "I7N3G6G", "1461", 0, "no description", 60.2, 1], ["1462", "I7N3G6G", "1462", 0, "no description", 60.2, 1], ["1463", "I7N3G6G", "1463", 0, "no description", 60.2, 1], ["1464", "I7N3G6G", "1464", 0, "no description", 60.2, 1], ["1465", "I7N3G6G", "1465", 0, "no description", 60.2, 1], ["1466", "I7N3G6G", "1466", 0, "no description", 60.2, 1], ["1467", "I7N3G6G", "1467", 0, "no description", 60.2, 1], ["1468", "I7N3G6G", "1468", 0, "no description", 60.2, 1], ["1469", "I7N3G6G", "1469", 0, "no description", 60.2, 1], ["1470", "I7N3G6G", "1470", 0, "no description", 60.2, 1], ["1471", "I7N3G6G", "1471", 0, "no description", 60.2, 1], ["1472", "I7N3G6G", "1472", 0, "no description", 60.2, 1], ["1473", "I7N3G6G", "1473", 0, "no description", 60.2, 1], ["1474", "I7N3G6G", "1474", 0, "no description", 60.2, 1], ["1475", "I7N3G6G", "1475", 0, "no description", 60.2, 1], ["1476", "I7N3G6G", "1476", 0, "no description", 60.2, 1], ["1477", "I7N3G6G", "1477", 0, "no description", 60.2, 1], ["1478", "I7N3G6G", "1478", 0, "no description", 60.2, 1], ["1479", "I7N3G6G", "1479", 0, "no description", 60.2, 1], ["1480", "I7N3G6G", "1480", 0, "no description", 60.2, 1], ["1481", "I7N3G6G", "1481", 0, "no description", 60.2, 1], ["1482", "I7N3G6G", "1482", 0, "no description", 60.2, 1], ["1483", "I7N3G6G", "1483", 0, "no description", 60.2, 1], ["1484", "I7N3G6G", "1484", 0, "no description", 60.2, 1], ["1485", "I7N3G6G", "1485", 0, "no description", 60.2, 1], ["1486", "I7N3G6G", "1486", 0, "no description", 60.2, 1], ["1487", "I7N3G6G", "1487", 0, "no description", 60.2, 1], ["1488", "I7N3G6G", "1488", 0, "no description", 60.2, 1], ["1489", "I7N3G6G", "1489", 0, "no description", 60.2, 1], ["1490", "I7N3G6G", "1490", 0, "no description", 60.2, 1], ["1491", "I7N3G6G", "1491", 0, "no description", 60.2, 1], ["1492", "I7N3G6G", "1492", 0, "no description", 60.2, 1], ["1493", "I7N3G6G", "1493", 0, "no description", 60.2, 1], ["1494", "I7N3G6G", "1494", 0, "no description", 60.2, 1], ["1495", "I7N3G6G", "1495", 0, "no description", 60.2, 1], ["1496", "I7N3G6G", "1496", 0, "no description", 60.2, 1], ["1497", "I7N3G6G", "1497", 0, "no description", 60.2, 1], ["1498", "I7N3G6G", "1498", 0, "no description", 60.2, 1], ["1499", "I7N3G6G", "1499", 0, "no description", 60.2, 1], ["1500", "I7N3G6G", "1500", 0, "no description", 60.2, 1], ["1501", "I7N3G6G", "1501", 0, "no description", 60.2, 1], ["1502", "I7N3G6G", "1502", 0, "no description", 60.2, 1], ["1503", "I7N3G6G", "1503", 0, "no description", 60.2, 1], ["1504", "I7N3G6G", "1504", 0, "no description", 60.2, 1], ["1505", "I7N3G6G", "1505", 0, "no description", 60.2, 1], ["1506", "I7N3G6G", "1506", 0, "no description", 60.2, 1], ["1507", "I7N3G6G", "1507", 0, "no description", 60.2, 1], ["1508", "I7N3G6G", "1508", 0, "no description", 60.2, 1], ["1509", "I7N3G6G", "1509", 0, "no description", 60.2, 1], ["1510", "I7N3G6G", "1510", 0, "no description", 60.2, 1], ["1511", "I7N3G6G", "1511", 0, "no description", 60.2, 1], ["1512", "I7N3G6G", "1512", 0, "no description", 60.2, 1], ["1513", "I7N3G6G", "1513", 0, "no description", 60.2, 1], ["1514", "I7N3G6G", "1514", 0, "no description", 60.2, 1], ["1515", "I7N3G6G", "1515", 0, "no description", 60.2, 1], ["1516", "I7N3G6G", "1516", 0, "no description", 60.2, 1], ["1517", "I7N3G6G", "1517", 0, "no description", 60.2, 1], ["1518", "I7N3G6G", "1518", 0, "no description", 60.2, 1], ["1519", "I7N3G6G", "1519", 0, "no description", 60.2, 1], ["1520", "I7N3G6G", "1520", 0, "no description", 60.2, 1], ["1521", "I7N3G6G", "1521", 0, "no description", 60.2, 1], ["1522", "I7N3G6G", "1522", 0, "no description", 60.2, 1], ["1523", "I7N3G6G", "1523", 0, "no description", 60.2, 1], ["1524", "I7N3G6G", "1524", 0, "no description", 60.2, 1], ["1525", "I7N3G6G", "1525", 0, "no description", 60.2, 1], ["1526", "I7N3G6G", "1526", 0, "no description", 60.2, 1], ["1527", "I7N3G6G", "1527", 0, "no description", 60.2, 1], ["1528", "I7N3G6G", "1528", 0, "no description", 60.2, 1], ["1529", "I7N3G6G", "1529", 0, "no description", 60.2, 1], ["1530", "I7N3G6G", "1530", 0, "no description", 60.2, 1], ["1531", "I7N3G6G", "1531", 0, "no description", 60.2, 1], ["1532", "I7N3G6G", "1532", 0, "no description", 60.2, 1], ["1533", "I7N3G6G", "1533", 0, "no description", 60.2, 1], ["1534", "I7N3G6G", "1534", 0, "no description", 60.2, 1], ["1535", "I7N3G6G", "1535", 0, "no description", 60.2, 1], ["1536", "I7N3G6G", "1536", 0, "no description", 60.2, 1], ["1537", "I7N3G6G", "1537", 0, "no description", 60.2, 1], ["1538", "I7N3G6G", "1538", 0, "no description", 60.2, 1], ["1539", "I7N3G6G", "1539", 0, "no description", 60.2, 1], ["1540", "I7N3G6G", "1540", 0, "no description", 60.2, 1], ["1541", "I7N3G6G", "1541", 0, "no description", 60.2, 1], ["1542", "I7N3G6G", "1542", 0, "no description", 60.2, 1], ["1543", "I7N3G6G", "1543", 0, "no description", 60.2, 1], ["1544", "I7N3G6G", "1544", 0, "no description", 60.2, 1], ["1545", "I7N3G6G", "1545", 0, "no description", 60.2, 1], ["1546", "I7N3G6G", "1546", 0, "no description", 60.2, 1], ["1547", "I7N3G6G", "1547", 0, "no description", 60.2, 1], ["1548", "I7N3G6G", "1548", 0, "no description", 60.2, 1], ["1549", "I7N3G6G", "1549", 0, "no description", 60.2, 1], ["1550", "I7N3G6G", "1550", 0, "no description", 60.2, 1], ["1551", "I7N3G6G", "1551", 0, "no description", 60.2, 1], ["1552", "I7N3G6G", "1552", 0, "no description", 60.2, 1], ["1553", "I7N3G6G", "1553", 0, "no description", 60.2, 1], ["1554", "I7N3G6G", "1554", 0, "no description", 60.2, 1], ["1555", "I7N3G6G", "1555", 0, "no description", 60.2, 1], ["1556", "I7N3G6G", "1556", 0, "no description", 60.2, 1], ["1557", "I7N3G6G", "1557", 0, "no description", 60.2, 1], ["1558", "I7N3G6G", "1558", 0, "no description", 60.2, 1], ["1559", "I7N3G6G", "1559", 0, "no description", 60.2, 1], ["1560", "I7N3G6G", "1560", 0, "no description", 60.2, 1], ["1561", "I7N3G6G", "1561", 0, "no description", 60.2, 1], ["1562", "I7N3G6G", "1562", 0, "no description", 60.2, 1], ["1563", "I7N3G6G", "1563", 0, "no description", 60.2, 1], ["1564", "I7N3G6G", "1564", 0, "no description", 60.2, 1], ["1565", "I7N3G6G", "1565", 0, "no description", 60.2, 1], ["1566", "I7N3G6G", "1566", 0, "no description", 60.2, 1], ["1567", "I7N3G6G", "1567", 0, "no description", 60.2, 1], ["1568", "I7N3G6G", "1568", 0, "no description", 60.2, 1], ["1569", "I7N3G6G", "1569", 0, "no description", 60.2, 1], ["1570", "I7N3G6G", "1570", 0, "no description", 60.2, 1], ["1571", "I7N3G6G", "1571", 0, "no description", 60.2, 1], ["1572", "I7N3G6G", "1572", 0, "no description", 60.2, 1], ["1573", "I7N3G6G", "1573", 0, "no description", 60.2, 1], ["1574", "I7N3G6G", "1574", 0, "no description", 60.2, 1], ["1575", "I7N3G6G", "1575", 0, "no description", 60.2, 1], ["1576", "I7N3G6G", "1576", 0, "no description", 60.2, 1], ["1577", "I7N3G6G", "1577", 0, "no description", 60.2, 1], ["1578", "I7N3G6G", "1578", 0, "no description", 60.2, 1], ["1579", "I7N3G6G", "1579", 0, "no description", 60.2, 1], ["1580", "I7N3G6G", "1580", 0, "no description", 60.2, 1], ["1581", "I7N3G6G", "1581", 0, "no description", 60.2, 1], ["1582", "I7N3G6G", "1582", 0, "no description", 60.2, 1], ["1583", "I7N3G6G", "1583", 0, "no description", 60.2, 1], ["1584", "I7N3G6G", "1584", 0, "no description", 60.2, 1], ["1585", "I7N3G6G", "1585", 0, "no description", 60.2, 1], ["1586", "I7N3G6G", "1586", 0, "no description", 60.2, 1], ["1587", "I7N3G6G", "1587", 0, "no description", 60.2, 1], ["1588", "I7N3G6G", "1588", 0, "no description", 60.2, 1], ["1589", "I7N3G6G", "1589", 0, "no description", 60.2, 1], ["1590", "I7N3G6G", "1590", 0, "no description", 60.2, 1], ["1591", "I7N3G6G", "1591", 0, "no description", 60.2, 1], ["1592", "I7N3G6G", "1592", 0, "no description", 60.2, 1], ["1593", "I7N3G6G", "1593", 0, "no description", 60.2, 1], ["1594", "I7N3G6G", "1594", 0, "no description", 60.2, 1], ["1595", "I7N3G6G", "1595", 0, "no description", 60.2, 1], ["1596", "I7N3G6G", "1596", 0, "no description", 60.2, 1], ["1597", "I7N3G6G", "1597", 0, "no description", 60.2, 1], ["1598", "I7N3G6G", "1598", 0, "no description", 60.2, 1], ["1599", "I7N3G6G", "1599", 0, "no description", 60.2, 1], ["1600", "I7N3G6G", "1600", 0, "no description", 60.2, 1], ["1601", "I7N3G6G", "1601", 0, "no description", 60.2, 1], ["1602", "I7N3G6G", "1602", 0, "no description", 60.2, 1], ["1603", "I7N3G6G", "1603", 0, "no description", 60.2, 1], ["1604", "I7N3G6G", "1604", 0, "no description", 60.2, 1], ["1605", "I7N3G6G", "1605", 0, "no description", 60.2, 1], ["1606", "I7N3G6G", "1606", 0, "no description", 60.2, 1], ["1607", "I7N3G6G", "1607", 0, "no description", 60.2, 1], ["1608", "I7N3G6G", "1608", 0, "no description", 60.2, 1], ["1609", "I7N3G6G", "1609", 0, "no description", 60.2, 1], ["1610", "I7N3G6G", "1610", 0, "no description", 60.2, 1], ["1611", "I7N3G6G", "1611", 0, "no description", 60.2, 1], ["1612", "I7N3G6G", "1612", 0, "no description", 60.2, 1], ["1613", "I7N3G6G", "1613", 0, "no description", 60.2, 1], ["1614", "I7N3G6G", "1614", 0, "no description", 60.2, 1], ["1615", "I7N3G6G", "1615", 0, "no description", 60.2, 1], ["1616", "I7N3G6G", "1616", 0, "no description", 60.2, 1], ["1617", "I7N3G6G", "1617", 0, "no description", 60.2, 1], ["1618", "I7N3G6G", "1618", 0, "no description", 60.2, 1], ["1619", "I7N3G6G", "1619", 0, "no description", 60.2, 1], ["1620", "I7N3G6G", "1620", 0, "no description", 60.2, 1], ["1621", "I7N3G6G", "1621", 0, "no description", 60.2, 1], ["1622", "I7N3G6G", "1622", 0, "no description", 60.2, 1], ["1623", "I7N3G6G", "1623", 0, "no description", 60.2, 1], ["1624", "I7N3G6G", "1624", 0, "no description", 60.2, 1], ["1625", "I7N3G6G", "1625", 0, "no description", 60.2, 1], ["1626", "I7N3G6G", "1626", 0, "no description", 60.2, 1], ["1627", "I7N3G6G", "1627", 0, "no description", 60.2, 1], ["1628", "I7N3G6G", "1628", 0, "no description", 60.2, 1], ["1629", "I7N3G6G", "1629", 0, "no description", 60.2, 1], ["1630", "I7N3G6G", "1630", 0, "no description", 60.2, 1], ["1631", "I7N3G6G", "1631", 0, "no description", 60.2, 1], ["1632", "I7N3G6G", "1632", 0, "no description", 60.2, 1], ["1633", "I7N3G6G", "1633", 0, "no description", 60.2, 1], ["1634", "I7N3G6G", "1634", 0, "no description", 60.2, 1], ["1635", "I7N3G6G", "1635", 0, "no description", 60.2, 1], ["1636", "I7N3G6G", "1636", 0, "no description", 60.2, 1], ["1637", "I7N3G6G", "1637", 0, "no description", 60.2, 1], ["1638", "I7N3G6G", "1638", 0, "no description", 60.2, 1], ["1639", "I7N3G6G", "1639", 0, "no description", 60.2, 1], ["1640", "I7N3G6G", "1640", 0, "no description", 60.2, 1], ["1641", "I7N3G6G", "1641", 0, "no description", 60.2, 1], ["1642", "I7N3G6G", "1642", 0, "no description", 60.2, 1], ["1643", "I7N3G6G", "1643", 0, "no description", 60.2, 1], ["1644", "I7N3G6G", "1644", 0, "no description", 60.2, 1], ["1645", "I7N3G6G", "1645", 0, "no description", 60.2, 1], ["1646", "I7N3G6G", "1646", 0, "no description", 60.2, 1], ["1647", "I7N3G6G", "1647", 0, "no description", 60.2, 1], ["1648", "I7N3G6G", "1648", 0, "no description", 60.2, 1], ["1649", "I7N3G6G", "1649", 0, "no description", 60.2, 1], ["1650", "I7N3G6G", "1650", 0, "no description", 60.2, 1], ["1651", "I7N3G6G", "1651", 0, "no description", 60.2, 1], ["1652", "I7N3G6G", "1652", 0, "no description", 60.2, 1], ["1653", "I7N3G6G", "1653", 0, "no description", 60.2, 1], ["1654", "I7N3G6G", "1654", 0, "no description", 60.2, 1], ["1655", "I7N3G6G", "1655", 0, "no description", 60.2, 1], ["1656", "I7N3G6G", "1656", 0, "no description", 60.2, 1], ["1657", "I7N3G6G", "1657", 0, "no description", 60.2, 1], ["1658", "I7N3G6G", "1658", 0, "no description", 60.2, 1], ["1659", "I7N3G6G", "1659", 0, "no description", 60.2, 1], ["1660", "I7N3G6G", "1660", 0, "no description", 60.2, 1], ["1661", "I7N3G6G", "1661", 0, "no description", 60.2, 1], ["1662", "I7N3G6G", "1662", 0, "no description", 60.2, 1], ["1663", "I7N3G6G", "1663", 0, "no description", 60.2, 1], ["1664", "I7N3G6G", "1664", 0, "no description", 60.2, 1], ["1665", "I7N3G6G", "1665", 0, "no description", 60.2, 1], ["1666", "I7N3G6G", "1666", 0, "no description", 60.2, 1], ["1667", "I7N3G6G", "1667", 0, "no description", 60.2, 1], ["1668", "I7N3G6G", "1668", 0, "no description", 60.2, 1], ["1669", "I7N3G6G", "1669", 0, "no description", 60.2, 1], ["1670", "I7N3G6G", "1670", 0, "no description", 60.2, 1], ["1671", "I7N3G6G", "1671", 0, "no description", 60.2, 1], ["1672", "I7N3G6G", "1672", 0, "no description", 60.2, 1], ["1673", "I7N3G6G", "1673", 0, "no description", 60.2, 1], ["1674", "I7N3G6G", "1674", 0, "no description", 60.2, 1], ["1675", "I7N3G6G", "1675", 0, "no description", 60.2, 1], ["1676", "I7N3G6G", "1676", 0, "no description", 60.2, 1], ["1677", "I7N3G6G", "1677", 0, "no description", 60.2, 1], ["1678", "I7N3G6G", "1678", 0, "no description", 60.2, 1], ["1679", "I7N3G6G", "1679", 0, "no description", 60.2, 1], ["1680", "I7N3G6G", "1680", 0, "no description", 60.2, 1], ["1681", "I7N3G6G", "1681", 0, "no description", 60.2, 1], ["1682", "I7N3G6G", "1682", 0, "no description", 60.2, 1], ["1683", "I7N3G6G", "1683", 0, "no description", 60.2, 1], ["1684", "I7N3G6G", "1684", 0, "no description", 60.2, 1], ["1685", "I7N3G6G", "1685", 0, "no description", 60.2, 1], ["1686", "I7N3G6G", "1686", 0, "no description", 60.2, 1], ["1687", "I7N3G6G", "1687", 0, "no description", 60.2, 1], ["1688", "I7N3G6G", "1688", 0, "no description", 60.2, 1], ["1689", "I7N3G6G", "1689", 0, "no description", 60.2, 1], ["1690", "I7N3G6G", "1690", 0, "no description", 60.2, 1], ["1691", "I7N3G6G", "1691", 0, "no description", 60.2, 1], ["1692", "I7N3G6G", "1692", 0, "no description", 60.2, 1], ["1693", "I7N3G6G", "1693", 0, "no description", 60.2, 1], ["1694", "I7N3G6G", "1694", 0, "no description", 60.2, 1], ["1695", "I7N3G6G", "1695", 0, "no description", 60.2, 1], ["1696", "I7N3G6G", "1696", 0, "no description", 60.2, 1], ["1697", "I7N3G6G", "1697", 0, "no description", 60.2, 1], ["1698", "I7N3G6G", "1698", 0, "no description", 60.2, 1], ["1699", "I7N3G6G", "1699", 0, "no description", 60.2, 1], ["1700", "I7N3G6G", "1700", 0, "no description", 60.2, 1], ["1701", "I7N3G6G", "1701", 0, "no description", 60.2, 1], ["1702", "I7N3G6G", "1702", 0, "no description", 60.2, 1], ["1703", "I7N3G6G", "1703", 0, "no description", 60.2, 1], ["1704", "I7N3G6G", "1704", 0, "no description", 60.2, 1], ["1705", "I7N3G6G", "1705", 0, "no description", 60.2, 1], ["1706", "I7N3G6G", "1706", 0, "no description", 60.2, 1], ["1707", "I7N3G6G", "1707", 0, "no description", 60.2, 1], ["1708", "I7N3G6G", "1708", 0, "no description", 60.2, 1], ["1709", "I7N3G6G", "1709", 0, "no description", 60.2, 1], ["1710", "I7N3G6G", "1710", 0, "no description", 60.2, 1], ["1711", "I7N3G6G", "1711", 0, "no description", 60.2, 1], ["1712", "I7N3G6G", "1712", 0, "no description", 60.2, 1], ["1713", "I7N3G6G", "1713", 0, "no description", 60.2, 1], ["1714", "I7N3G6G", "1714", 0, "no description", 60.2, 1], ["1715", "I7N3G6G", "1715", 0, "no description", 60.2, 1], ["1716", "I7N3G6G", "1716", 0, "no description", 60.2, 1], ["1717", "I7N3G6G", "1717", 0, "no description", 60.2, 1], ["1718", "I7N3G6G", "1718", 0, "no description", 60.2, 1], ["1719", "I7N3G6G", "1719", 0, "no description", 60.2, 1], ["1720", "I7N3G6G", "1720", 0, "no description", 60.2, 1], ["1721", "I7N3G6G", "1721", 0, "no description", 60.2, 1], ["1722", "I7N3G6G", "1722", 0, "no description", 60.2, 1], ["1723", "I7N3G6G", "1723", 0, "no description", 60.2, 1], ["1724", "I7N3G6G", "1724", 0, "no description", 60.2, 1], ["1725", "I7N3G6G", "1725", 0, "no description", 60.2, 1], ["1726", "I7N3G6G", "1726", 0, "no description", 60.2, 1], ["1727", "I7N3G6G", "1727", 0, "no description", 60.2, 1], ["1728", "I7N3G6G", "1728", 0, "no description", 60.2, 1], ["1729", "I7N3G6G", "1729", 0, "no description", 60.2, 1], ["1730", "I7N3G6G", "1730", 0, "no description", 60.2, 1], ["1731", "I7N3G6G", "1731", 0, "no description", 60.2, 1], ["1732", "I7N3G6G", "1732", 0, "no description", 60.2, 1], ["1733", "I7N3G6G", "1733", 0, "no description", 60.2, 1], ["1734", "I7N3G6G", "1734", 0, "no description", 60.2, 1], ["1735", "I7N3G6G", "1735", 0, "no description", 60.2, 1], ["1736", "I7N3G6G", "1736", 0, "no description", 60.2, 1], ["1737", "I7N3G6G", "1737", 0, "no description", 60.2, 1], ["1738", "I7N3G6G", "1738", 0, "no description", 60.2, 1], ["1739", "I7N3G6G", "1739", 0, "no description", 60.2, 1], ["1740", "I7N3G6G", "1740", 0, "no description", 60.2, 1], ["1741", "I7N3G6G", "1741", 0, "no description", 60.2, 1], ["1742", "I7N3G6G", "1742", 0, "no description", 60.2, 1], ["1743", "I7N3G6G", "1743", 0, "no description", 60.2, 1], ["1744", "I7N3G6G", "1744", 0, "no description", 60.2, 1], ["1745", "I7N3G6G", "1745", 0, "no description", 60.2, 1], ["1746", "I7N3G6G", "1746", 0, "no description", 60.2, 1], ["1747", "I7N3G6G", "1747", 0, "no description", 60.2, 1], ["1748", "I7N3G6G", "1748", 0, "no description", 60.2, 1], ["1749", "I7N3G6G", "1749", 0, "no description", 60.2, 1], ["1750", "I7N3G6G", "1750", 0, "no description", 60.2, 1], ["1751", "I7N3G6G", "1751", 0, "no description", 60.2, 1], ["1752", "I7N3G6G", "1752", 0, "no description", 60.2, 1], ["1753", "I7N3G6G", "1753", 0, "no description", 60.2, 1], ["1754", "I7N3G6G", "1754", 0, "no description", 60.2, 1], ["1755", "I7N3G6G", "1755", 0, "no description", 60.2, 1], ["1756", "I7N3G6G", "1756", 0, "no description", 60.2, 1], ["1757", "I7N3G6G", "1757", 0, "no description", 60.2, 1], ["1758", "I7N3G6G", "1758", 0, "no description", 60.2, 1], ["1759", "I7N3G6G", "1759", 0, "no description", 60.2, 1], ["1760", "I7N3G6G", "1760", 0, "no description", 60.2, 1], ["1761", "I7N3G6G", "1761", 0, "no description", 60.2, 1], ["1762", "I7N3G6G", "1762", 0, "no description", 60.2, 1], ["1763", "I7N3G6G", "1763", 0, "no description", 60.2, 1], ["1764", "I7N3G6G", "1764", 0, "no description", 60.2, 1], ["1765", "I7N3G6G", "1765", 0, "no description", 60.2, 1], ["1766", "I7N3G6G", "1766", 0, "no description", 60.2, 1], ["1767", "I7N3G6G", "1767", 0, "no description", 60.2, 1], ["1768", "I7N3G6G", "1768", 0, "no description", 60.2, 1], ["1769", "I7N3G6G", "1769", 0, "no description", 60.2, 1], ["1770", "I7N3G6G", "1770", 0, "no description", 60.2, 1], ["1771", "I7N3G6G", "1771", 0, "no description", 60.2, 1], ["1772", "I7N3G6G", "1772", 0, "no description", 60.2, 1], ["1773", "I7N3G6G", "1773", 0, "no description", 60.2, 1], ["1774", "I7N3G6G", "1774", 0, "no description", 60.2, 1], ["1775", "I7N3G6G", "1775", 0, "no description", 60.2, 1], ["1776", "I7N3G6G", "1776", 0, "no description", 60.2, 1], ["1777", "I7N3G6G", "1777", 0, "no description", 60.2, 1], ["1778", "I7N3G6G", "1778", 0, "no description", 60.2, 1], ["1779", "I7N3G6G", "1779", 0, "no description", 60.2, 1], ["1780", "I7N3G6G", "1780", 0, "no description", 60.2, 1], ["1781", "I7N3G6G", "1781", 0, "no description", 60.2, 1], ["1782", "I7N3G6G", "1782", 0, "no description", 60.2, 1], ["1783", "I7N3G6G", "1783", 0, "no description", 60.2, 1], ["1784", "I7N3G6G", "1784", 0, "no description", 60.2, 1], ["1785", "I7N3G6G", "1785", 0, "no description", 60.2, 1], ["1786", "I7N3G6G", "1786", 0, "no description", 60.2, 1], ["1787", "I7N3G6G", "1787", 0, "no description", 60.2, 1], ["1788", "I7N3G6G", "1788", 0, "no description", 60.2, 1], ["1789", "I7N3G6G", "1789", 0, "no description", 60.2, 1], ["1790", "I7N3G6G", "1790", 0, "no description", 60.2, 1], ["1791", "I7N3G6G", "1791", 0, "no description", 60.2, 1], ["1792", "I7N3G6G", "1792", 0, "no description", 60.2, 1], ["1793", "I7N3G6G", "1793", 0, "no description", 60.2, 1], ["1794", "I7N3G6G", "1794", 0, "no description", 60.2, 1], ["1795", "I7N3G6G", "1795", 0, "no description", 60.2, 1], ["1796", "I7N3G6G", "1796", 0, "no description", 60.2, 1], ["1797", "I7N3G6G", "1797", 0, "no description", 60.2, 1], ["1798", "I7N3G6G", "1798", 0, "no description", 60.2, 1], ["1799", "I7N3G6G", "1799", 0, "no description", 60.2, 1], ["1800", "I7N3G6G", "1800", 0, "no description", 60.2, 1], ["1801", "I7N3G6G", "1801", 0, "no description", 60.2, 1], ["1802", "I7N3G6G", "1802", 0, "no description", 60.2, 1], ["1803", "I7N3G6G", "1803", 0, "no description", 60.2, 1], ["1804", "I7N3G6G", "1804", 0, "no description", 60.2, 1], ["1805", "I7N3G6G", "1805", 0, "no description", 60.2, 1], ["1806", "I7N3G6G", "1806", 0, "no description", 60.2, 1], ["1807", "I7N3G6G", "1807", 0, "no description", 60.2, 1], ["1808", "I7N3G6G", "1808", 0, "no description", 60.2, 1], ["1809", "I7N3G6G", "1809", 0, "no description", 60.2, 1], ["1810", "I7N3G6G", "1810", 0, "no description", 60.2, 1], ["1811", "I7N3G6G", "1811", 0, "no description", 60.2, 1], ["1812", "I7N3G6G", "1812", 0, "no description", 60.2, 1], ["1813", "I7N3G6G", "1813", 0, "no description", 60.2, 1], ["1814", "I7N3G6G", "1814", 0, "no description", 60.2, 1], ["1815", "I7N3G6G", "1815", 0, "no description", 60.2, 1], ["1816", "I7N3G6G", "1816", 0, "no description", 60.2, 1], ["1817", "I7N3G6G", "1817", 0, "no description", 60.2, 1], ["1818", "I7N3G6G", "1818", 0, "no description", 60.2, 1], ["1819", "I7N3G6G", "1819", 0, "no description", 60.2, 1], ["1820", "I7N3G6G", "1820", 0, "no description", 60.2, 1], ["1821", "I7N3G6G", "1821", 0, "no description", 60.2, 1], ["1822", "I7N3G6G", "1822", 0, "no description", 60.2, 1], ["1823", "I7N3G6G", "1823", 0, "no description", 60.2, 1], ["1824", "I7N3G6G", "1824", 0, "no description", 60.2, 1], ["1825", "I7N3G6G", "1825", 0, "no description", 60.2, 1], ["1826", "I7N3G6G", "1826", 0, "no description", 60.2, 1], ["1827", "I7N3G6G", "1827", 0, "no description", 60.2, 1], ["1828", "I7N3G6G", "1828", 0, "no description", 60.2, 1], ["1829", "I7N3G6G", "1829", 0, "no description", 60.2, 1], ["1830", "I7N3G6G", "1830", 0, "no description", 60.2, 1], ["1831", "I7N3G6G", "1831", 0, "no description", 60.2, 1], ["1832", "I7N3G6G", "1832", 0, "no description", 60.2, 1], ["1833", "I7N3G6G", "1833", 0, "no description", 60.2, 1], ["1834", "I7N3G6G", "1834", 0, "no description", 60.2, 1], ["1835", "I7N3G6G", "1835", 0, "no description", 60.2, 1], ["1836", "I7N3G6G", "1836", 0, "no description", 60.2, 1], ["1837", "I7N3G6G", "1837", 0, "no description", 60.2, 1], ["1838", "I7N3G6G", "1838", 0, "no description", 60.2, 1], ["1839", "I7N3G6G", "1839", 0, "no description", 60.2, 1], ["1840", "I7N3G6G", "1840", 0, "no description", 60.2, 1], ["1841", "I7N3G6G", "1841", 0, "no description", 60.2, 1], ["1842", "I7N3G6G", "1842", 0, "no description", 60.2, 1], ["1843", "I7N3G6G", "1843", 0, "no description", 60.2, 1], ["1844", "I7N3G6G", "1844", 0, "no description", 60.2, 1], ["1845", "I7N3G6G", "1845", 0, "no description", 60.2, 1], ["1846", "I7N3G6G", "1846", 0, "no description", 60.2, 1], ["1847", "I7N3G6G", "1847", 0, "no description", 60.2, 1], ["1848", "I7N3G6G", "1848", 0, "no description", 60.2, 1], ["1849", "I7N3G6G", "1849", 0, "no description", 60.2, 1], ["1850", "I7N3G6G", "1850", 0, "no description", 60.2, 1], ["1851", "I7N3G6G", "1851", 0, "no description", 60.2, 1], ["1852", "I7N3G6G", "1852", 0, "no description", 60.2, 1], ["1853", "I7N3G6G", "1853", 0, "no description", 60.2, 1], ["1854", "I7N3G6G", "1854", 0, "no description", 60.2, 1], ["1855", "I7N3G6G", "1855", 0, "no description", 60.2, 1], ["1856", "I7N3G6G", "1856", 0, "no description", 60.2, 1], ["1857", "I7N3G6G", "1857", 0, "no description", 60.2, 1], ["1858", "I7N3G6G", "1858", 0, "no description", 60.2, 1], ["1859", "I7N3G6G", "1859", 0, "no description", 60.2, 1], ["1860", "I7N3G6G", "1860", 0, "no description", 60.2, 1], ["1861", "I7N3G6G", "1861", 0, "no description", 60.2, 1], ["1862", "I7N3G6G", "1862", 0, "no description", 60.2, 1], ["1863", "I7N3G6G", "1863", 0, "no description", 60.2, 1], ["1864", "I7N3G6G", "1864", 0, "no description", 60.2, 1], ["1865", "I7N3G6G", "1865", 0, "no description", 60.2, 1], ["1866", "I7N3G6G", "1866", 0, "no description", 60.2, 1], ["1867", "I7N3G6G", "1867", 0, "no description", 60.2, 1], ["1868", "I7N3G6G", "1868", 0, "no description", 60.2, 1], ["1869", "I7N3G6G", "1869", 0, "no description", 60.2, 1], ["1870", "I7N3G6G", "1870", 0, "no description", 60.2, 1], ["1871", "I7N3G6G", "1871", 0, "no description", 60.2, 1], ["1872", "I7N3G6G", "1872", 0, "no description", 60.2, 1], ["1873", "I7N3G6G", "1873", 0, "no description", 60.2, 1], ["1874", "I7N3G6G", "1874", 0, "no description", 60.2, 1], ["1875", "I7N3G6G", "1875", 0, "no description", 60.2, 1], ["1876", "I7N3G6G", "1876", 0, "no description", 60.2, 1], ["1877", "I7N3G6G", "1877", 0, "no description", 60.2, 1], ["1878", "I7N3G6G", "1878", 0, "no description", 60.2, 1], ["1879", "I7N3G6G", "1879", 0, "no description", 60.2, 1], ["1880", "I7N3G6G", "1880", 0, "no description", 60.2, 1], ["1881", "I7N3G6G", "1881", 0, "no description", 60.2, 1], ["1882", "I7N3G6G", "1882", 0, "no description", 60.2, 1], ["1883", "I7N3G6G", "1883", 0, "no description", 60.2, 1], ["1884", "I7N3G6G", "1884", 0, "no description", 60.2, 1], ["1885", "I7N3G6G", "1885", 0, "no description", 60.2, 1], ["1886", "I7N3G6G", "1886", 0, "no description", 60.2, 1], ["1887", "I7N3G6G", "1887", 0, "no description", 60.2, 1], ["1888", "I7N3G6G", "1888", 0, "no description", 60.2, 1], ["1889", "I7N3G6G", "1889", 0, "no description", 60.2, 1], ["1890", "I7N3G6G", "1890", 0, "no description", 60.2, 1], ["1891", "I7N3G6G", "1891", 0, "no description", 60.2, 1], ["1892", "I7N3G6G", "1892", 0, "no description", 60.2, 1], ["1893", "I7N3G6G", "1893", 0, "no description", 60.2, 1], ["1894", "I7N3G6G", "1894", 0, "no description", 60.2, 1], ["1895", "I7N3G6G", "1895", 0, "no description", 60.2, 1], ["1896", "I7N3G6G", "1896", 0, "no description", 60.2, 1], ["1897", "I7N3G6G", "1897", 0, "no description", 60.2, 1], ["1898", "I7N3G6G", "1898", 0, "no description", 60.2, 1], ["1899", "I7N3G6G", "1899", 0, "no description", 60.2, 1], ["1900", "I7N3G6G", "1900", 0, "no description", 60.2, 1], ["1901", "I7N3G6G", "1901", 0, "no description", 60.2, 1], ["1902", "I7N3G6G", "1902", 0, "no description", 60.2, 1], ["1903", "I7N3G6G", "1903", 0, "no description", 60.2, 1], ["1904", "I7N3G6G", "1904", 0, "no description", 60.2, 1], ["1905", "I7N3G6G", "1905", 0, "no description", 60.2, 1], ["1906", "I7N3G6G", "1906", 0, "no description", 60.2, 1], ["1907", "I7N3G6G", "1907", 0, "no description", 60.2, 1], ["1908", "I7N3G6G", "1908", 0, "no description", 60.2, 1], ["1909", "I7N3G6G", "1909", 0, "no description", 60.2, 1], ["1910", "I7N3G6G", "1910", 0, "no description", 60.2, 1], ["1911", "I7N3G6G", "1911", 0, "no description", 60.2, 1], ["1912", "I7N3G6G", "1912", 0, "no description", 60.2, 1], ["1913", "I7N3G6G", "1913", 0, "no description", 60.2, 1], ["1914", "I7N3G6G", "1914", 0, "no description", 60.2, 1], ["1915", "I7N3G6G", "1915", 0, "no description", 60.2, 1], ["1916", "I7N3G6G", "1916", 0, "no description", 60.2, 1], ["1917", "I7N3G6G", "1917", 0, "no description", 60.2, 1], ["1918", "I7N3G6G", "1918", 0, "no description", 60.2, 1], ["1919", "I7N3G6G", "1919", 0, "no description", 60.2, 1], ["1920", "I7N3G6G", "1920", 0, "no description", 60.2, 1], ["1921", "I7N3G6G", "1921", 0, "no description", 60.2, 1], ["1922", "I7N3G6G", "1922", 0, "no description", 60.2, 1], ["1923", "I7N3G6G", "1923", 0, "no description", 60.2, 1], ["1924", "I7N3G6G", "1924", 0, "no description", 60.2, 1], ["1925", "I7N3G6G", "1925", 0, "no description", 60.2, 1], ["1926", "I7N3G6G", "1926", 0, "no description", 60.2, 1], ["1927", "I7N3G6G", "1927", 0, "no description", 60.2, 1], ["1928", "I7N3G6G", "1928", 0, "no description", 60.2, 1], ["1929", "I7N3G6G", "1929", 0, "no description", 60.2, 1], ["1930", "I7N3G6G", "1930", 0, "no description", 60.2, 1], ["1931", "I7N3G6G", "1931", 0, "no description", 60.2, 1], ["1932", "I7N3G6G", "1932", 0, "no description", 60.2, 1], ["1933", "I7N3G6G", "1933", 0, "no description", 60.2, 1], ["1934", "I7N3G6G", "1934", 0, "no description", 60.2, 1], ["1935", "I7N3G6G", "1935", 0, "no description", 60.2, 1], ["1936", "I7N3G6G", "1936", 0, "no description", 60.2, 1], ["1937", "I7N3G6G", "1937", 0, "no description", 60.2, 1], ["1938", "I7N3G6G", "1938", 0, "no description", 60.2, 1], ["1939", "I7N3G6G", "1939", 0, "no description", 60.2, 1], ["1940", "I7N3G6G", "1940", 0, "no description", 60.2, 1], ["1941", "I7N3G6G", "1941", 0, "no description", 60.2, 1], ["1942", "I7N3G6G", "1942", 0, "no description", 60.2, 1], ["1943", "I7N3G6G", "1943", 0, "no description", 60.2, 1], ["1944", "I7N3G6G", "1944", 0, "no description", 60.2, 1], ["1945", "I7N3G6G", "1945", 0, "no description", 60.2, 1], ["1946", "I7N3G6G", "1946", 0, "no description", 60.2, 1], ["1947", "I7N3G6G", "1947", 0, "no description", 60.2, 1], ["1948", "I7N3G6G", "1948", 0, "no description", 60.2, 1], ["1949", "I7N3G6G", "1949", 0, "no description", 60.2, 1], ["1950", "I7N3G6G", "1950", 0, "no description", 60.2, 1], ["1951", "I7N3G6G", "1951", 0, "no description", 60.2, 1], ["1952", "I7N3G6G", "1952", 0, "no description", 60.2, 1], ["1953", "I7N3G6G", "1953", 0, "no description", 60.2, 1], ["1954", "I7N3G6G", "1954", 0, "no description", 60.2, 1], ["1955", "I7N3G6G", "1955", 0, "no description", 60.2, 1], ["1956", "I7N3G6G", "1956", 0, "no description", 60.2, 1], ["1957", "I7N3G6G", "1957", 0, "no description", 60.2, 1], ["1958", "I7N3G6G", "1958", 0, "no description", 60.2, 1], ["1959", "I7N3G6G", "1959", 0, "no description", 60.2, 1], ["1960", "I7N3G6G", "1960", 0, "no description", 60.2, 1], ["1961", "I7N3G6G", "1961", 0, "no description", 60.2, 1], ["1962", "I7N3G6G", "1962", 0, "no description", 60.2, 1], ["1963", "I7N3G6G", "1963", 0, "no description", 60.2, 1], ["1964", "I7N3G6G", "1964", 0, "no description", 60.2, 1], ["1965", "I7N3G6G", "1965", 0, "no description", 60.2, 1], ["1966", "I7N3G6G", "1966", 0, "no description", 60.2, 1], ["1967", "I7N3G6G", "1967", 0, "no description", 60.2, 1], ["1968", "I7N3G6G", "1968", 0, "no description", 60.2, 1], ["1969", "I7N3G6G", "1969", 0, "no description", 60.2, 1], ["1970", "I7N3G6G", "1970", 0, "no description", 60.2, 1], ["1971", "I7N3G6G", "1971", 0, "no description", 60.2, 1], ["1972", "I7N3G6G", "1972", 0, "no description", 60.2, 1], ["1973", "I7N3G6G", "1973", 0, "no description", 60.2, 1], ["1974", "I7N3G6G", "1974", 0, "no description", 60.2, 1], ["1975", "I7N3G6G", "1975", 0, "no description", 60.2, 1], ["1976", "I7N3G6G", "1976", 0, "no description", 60.2, 1], ["1977", "I7N3G6G", "1977", 0, "no description", 60.2, 1], ["1978", "I7N3G6G", "1978", 0, "no description", 60.2, 1], ["1979", "I7N3G6G", "1979", 0, "no description", 60.2, 1], ["1980", "I7N3G6G", "1980", 0, "no description", 60.2, 1], ["1981", "I7N3G6G", "1981", 0, "no description", 60.2, 1], ["1982", "I7N3G6G", "1982", 0, "no description", 60.2, 1], ["1983", "I7N3G6G", "1983", 0, "no description", 60.2, 1], ["1984", "I7N3G6G", "1984", 0, "no description", 60.2, 1], ["1985", "I7N3G6G", "1985", 0, "no description", 60.2, 1], ["1986", "I7N3G6G", "1986", 0, "no description", 60.2, 1], ["1987", "I7N3G6G", "1987", 0, "no description", 60.2, 1], ["1988", "I7N3G6G", "1988", 0, "no description", 60.2, 1], ["1989", "I7N3G6G", "1989", 0, "no description", 60.2, 1], ["1990", "I7N3G6G", "1990", 0, "no description", 60.2, 1], ["1991", "I7N3G6G", "1991", 0, "no description", 60.2, 1], ["1992", "I7N3G6G", "1992", 0, "no description", 60.2, 1], ["1993", "I7N3G6G", "1993", 0, "no description", 60.2, 1], ["1994", "I7N3G6G", "1994", 0, "no description", 60.2, 1], ["1995", "I7N3G6G", "1995", 0, "no description", 60.2, 1], ["1996", "I7N3G6G", "1996", 0, "no description", 60.2, 1], ["1997", "I7N3G6G", "1997", 0, "no description", 60.2, 1], ["1998", "I7N3G6G", "1998", 0, "no description", 60.2, 1], ["1999", "I7N3G6G", "1999", 0, "no description", 60.2, 1], ["2000", "I7N3G6G", "2000", 0, "no description", 60.2, 1], ["2001", "I7N3G6G", "2001", 0, "no description", 60.2], ["2002", "I7N3G6G", "2002", 0, "no description", 60.2, 1], ["2003", "I7N3G6G", "2003", 0, "no description"], ["2004", "I7N3G6G", "2004", 0, "no description", 60.2, 1], ["2005", "I7N3G6G", "2005", 0, "no description", 60.2, 1], ["2006", "I7N3G6G", "2006", 0, "no description", 60.2, 1], ["2007", "I7N3G6G", "2007", 0, "no description", 60.2, 1], ["2008", "I7N3G6G", "2008", 0, "no description", 60.2, 1], ["2009", "I7N3G6G", "2009", 0, "no description", 60.2, 1], ["2010", "I7N3G6G", "2010", 0, "no description", 60.2, 1], ["2011", "I7N3G6G", "2011", 0, "no description", 60.2, 1], ["2012", "I7N3G6G", "2012", 0, "no description", 60.2, 1], ["2013", "I7N3G6G", "2013", 0, "no description", 60.2, 1], ["2014", "I7N3G6G", "2014", 0, "no description", 60.2, 1], ["2015", "I7N3G6G", "2015", 0, "no description", 60.2, 1], ["2016", "I7N3G6G", "2016", 0, "no description", 60.2, 1], ["2017", "I7N3G6G", "2017", 0, "no description", 60.2, 1], ["2018", "I7N3G6G", "2018", 0, "no description", 60.2, 1], ["2019", "I7N3G6G", "2019", 0, "no description", 60.2, 1], ["2020", "I7N3G6G", "2020", 0, "no description", 60.2, 1], ["2021", "I7N3G6G", "2021", 0, "no description", 60.2, 1], ["2022", "I7N3G6G", "2022", 0, "no description", 60.2, 1], ["2023", "I7N3G6G", "2023", 0, "no description", 60.2, 1], ["2024", "I7N3G6G", "2024", 0, "no description", 60.2, 1], ["2025", "I7N3G6G", "2025", 0, "no description", 60.2, 1], ["2026", "I7N3G6G", "2026", 0, "no description", 60.2, 1], ["2027", "I7N3G6G", "2027", 0, "no description", 60.2, 1], ["2028", "I7N3G6G", "2028", 0, "no description", 60.2, 1], ["2029", "I7N3G6G", "2029", 0, "no description", 60.2, 1], ["2030", "I7N3G6G", "2030", 0, "no description", 60.2, 1], ["2031", "I7N3G6G", "2031", 0, "no description", 60.2, 1], ["2032", "I7N3G6G", "2032", 0, "no description", 60.2, 1], ["2033", "I7N3G6G", "2033", 0, "no description", 60.2, 1], ["2034", "I7N3G6G", "2034", 0, "no description", 60.2, 1], ["2035", "I7N3G6G", "2035", 0, "no description", 60.2, 1], ["2036", "I7N3G6G", "2036", 0, "no description", 60.2, 1], ["2037", "I7N3G6G", "2037", 0, "no description", 60.2, 1], ["2038", "I7N3G6G", "2038", 0, "no description", 60.2, 1], ["2039", "I7N3G6G", "2039", 0, "no description", 60.2, 1], ["2040", "I7N3G6G", "2040", 0, "no description", 60.2, 1], ["2041", "I7N3G6G", "2041", 0, "no description", 60.2, 1], ["2042", "I7N3G6G", "2042", 0, "no description", 60.2, 1], ["2043", "I7N3G6G", "2043", 0, "no description", 60.2, 1], ["2044", "I7N3G6G", "2044", 0, "no description", 60.2, 1], ["2045", "I7N3G6G", "2045", 0, "no description", 60.2, 1], ["2046", "I7N3G6G", "2046", 0, "no description", 60.2, 1], ["2047", "I7N3G6G", "2047", 0, "no description", 60.2, 1], ["2048", "I7N3G6G", "2048", 0, "no description", 60.2, 1], ["2049", "I7N3G6G", "2049", 0, "no description", 60.2, 1], ["2050", "I7N3G6G", "2050", 0, "no description", 60.2, 1], ["2051", "I7N3G6G", "2051", 0, "no description", 60.2, 1], ["2052", "I7N3G6G", "2052", 0, "no description", 60.2, 1], ["2053", "I7N3G6G", "2053", 0, "no description", 60.2, 1], ["2054", "I7N3G6G", "2054", 0, "no description", 60.2, 1], ["2055", "I7N3G6G", "2055", 0, "no description", 60.2, 1], ["2056", "I7N3G6G", "2056", 0, "no description", 60.2, 1], ["2057", "I7N3G6G", "2057", 0, "no description", 60.2, 1], ["2058", "I7N3G6G", "2058", 0, "no description", 60.2, 1], ["2059", "I7N3G6G", "2059", 0, "no description", 60.2, 1], ["2060", "I7N3G6G", "2060", 0, "no description", 60.2, 1], ["2061", "I7N3G6G", "2061", 0, "no description", 60.2, 1], ["2062", "I7N3G6G", "2062", 0, "no description", 60.2, 1], ["2063", "I7N3G6G", "2063", 0, "no description", 60.2, 1], ["2064", "I7N3G6G", "2064", 0, "no description", 60.2, 1], ["2065", "I7N3G6G", "2065", 0, "no description", 60.2, 1], ["2066", "I7N3G6G", "2066", 0, "no description", 60.2, 1], ["2067", "I7N3G6G", "2067", 0, "no description", 60.2, 1], ["2068", "I7N3G6G", "2068", 0, "no description", 60.2, 1], ["2069", "I7N3G6G", "2069", 0, "no description", 60.2, 1], ["2070", "I7N3G6G", "2070", 0, "no description", 60.2, 1], ["2071", "I7N3G6G", "2071", 0, "no description", 60.2, 1], ["2072", "I7N3G6G", "2072", 0, "no description", 60.2, 1], ["2073", "I7N3G6G", "2073", 0, "no description", 60.2, 1], ["2074", "I7N3G6G", "2074", 0, "no description", 60.2, 1], ["2075", "I7N3G6G", "2075", 0, "no description", 60.2, 1], ["2076", "I7N3G6G", "2076", 0, "no description", 60.2, 1], ["2077", "I7N3G6G", "2077", 0, "no description", 60.2, 1], ["2078", "I7N3G6G", "2078", 0, "no description", 60.2, 1], ["2079", "I7N3G6G", "2079", 0, "no description", 60.2, 1], ["2080", "I7N3G6G", "2080", 0, "no description", 60.2, 1], ["2081", "I7N3G6G", "2081", 0, "no description", 60.2, 1], ["2082", "I7N3G6G", "2082", 0, "no description", 60.2, 1], ["2083", "I7N3G6G", "2083", 0, "no description", 60.2, 1], ["2084", "I7N3G6G", "2084", 0, "no description", 60.2, 1], ["2085", "I7N3G6G", "2085", 0, "no description", 60.2, 1], ["2086", "I7N3G6G", "2086", 0, "no description", 60.2, 1], ["2087", "I7N3G6G", "2087", 0, "no description", 60.2, 1], ["2088", "I7N3G6G", "2088", 0, "no description", 60.2, 1], ["2089", "I7N3G6G", "2089", 0, "no description", 60.2, 1], ["2090", "I7N3G6G", "2090", 0, "no description", 60.2, 1], ["2091", "I7N3G6G", "2091", 0, "no description", 60.2, 1], ["2092", "I7N3G6G", "2092", 0, "no description", 60.2, 1], ["2093", "I7N3G6G", "2093", 0, "no description", 60.2, 1], ["2094", "I7N3G6G", "2094", 0, "no description", 60.2, 1], ["2095", "I7N3G6G", "2095", 0, "no description", 60.2, 1], ["2096", "I7N3G6G", "2096", 0, "no description", 60.2, 1], ["2097", "I7N3G6G", "2097", 0, "no description", 60.2, 1], ["2098", "I7N3G6G", "2098", 0, "no description", 60.2, 1], ["2099", "I7N3G6G", "2099", 0, "no description", 60.2, 1], ["2100", "I7N3G6G", "2100", 0, "no description", 60.2, 1], ["2101", "I7N3G6G", "2101", 0, "no description", 60.2, 1], ["2102", "I7N3G6G", "2102", 0, "no description", 60.2, 1], ["2103", "I7N3G6G", "2103", 0, "no description", 60.2, 1], ["2104", "I7N3G6G", "2104", 0, "no description", 60.2, 1], ["2105", "I7N3G6G", "2105", 0, "no description", 60.2, 1], ["2106", "I7N3G6G", "2106", 0, "no description", 60.2, 1], ["2107", "I7N3G6G", "2107", 0, "no description", 60.2, 1], ["2108", "I7N3G6G", "2108", 0, "no description", 60.2, 1], ["2109", "I7N3G6G", "2109", 0, "no description", 60.2, 1], ["2110", "I7N3G6G", "2110", 0, "no description", 60.2, 1], ["2111", "I7N3G6G", "2111", 0, "no description", 60.2, 1], ["2112", "I7N3G6G", "2112", 0, "no description", 60.2, 1], ["2113", "I7N3G6G", "2113", 0, "no description", 60.2, 1], ["2114", "I7N3G6G", "2114", 0, "no description", 60.2, 1], ["2115", "I7N3G6G", "2115", 0, "no description", 60.2, 1], ["2116", "I7N3G6G", "2116", 0, "no description", 60.2, 1], ["2117", "I7N3G6G", "2117", 0, "no description", 60.2, 1], ["2118", "I7N3G6G", "2118", 0, "no description", 60.2, 1], ["2119", "I7N3G6G", "2119", 0, "no description", 60.2, 1], ["2120", "I7N3G6G", "2120", 0, "no description", 60.2, 1], ["2121", "I7N3G6G", "2121", 0, "no description", 60.2, 1], ["2122", "I7N3G6G", "2122", 0, "no description", 60.2, 1], ["2123", "I7N3G6G", "2123", 0, "no description", 60.2, 1], ["2124", "I7N3G6G", "2124", 0, "no description", 60.2, 1], ["2125", "I7N3G6G", "2125", 0, "no description", 60.2, 1], ["2126", "I7N3G6G", "2126", 0, "no description", 60.2, 1], ["2127", "I7N3G6G", "2127", 0, "no description", 60.2, 1], ["2128", "I7N3G6G", "2128", 0, "no description", 60.2, 1], ["2129", "I7N3G6G", "2129", 0, "no description", 60.2, 1], ["2130", "I7N3G6G", "2130", 0, "no description", 60.2, 1], ["2131", "I7N3G6G", "2131", 0, "no description", 60.2, 1], ["2132", "I7N3G6G", "2132", 0, "no description", 60.2, 1], ["2133", "I7N3G6G", "2133", 0, "no description", 60.2, 1], ["2134", "I7N3G6G", "2134", 0, "no description", 60.2, 1], ["2135", "I7N3G6G", "2135", 0, "no description", 60.2, 1], ["2136", "I7N3G6G", "2136", 0, "no description", 60.2, 1], ["2137", "I7N3G6G", "2137", 0, "no description", 60.2, 1], ["2138", "I7N3G6G", "2138", 0, "no description", 60.2, 1], ["2139", "I7N3G6G", "2139", 0, "no description", 60.2, 1], ["2140", "I7N3G6G", "2140", 0, "no description", 60.2, 1], ["2141", "I7N3G6G", "2141", 0, "no description", 60.2, 1], ["2142", "I7N3G6G", "2142", 0, "no description", 60.2, 1], ["2143", "I7N3G6G", "2143", 0, "no description", 60.2, 1], ["2144", "I7N3G6G", "2144", 0, "no description", 60.2, 1], ["2145", "I7N3G6G", "2145", 0, "no description", 60.2, 1], ["2146", "I7N3G6G", "2146", 0, "no description", 60.2, 1], ["2147", "I7N3G6G", "2147", 0, "no description", 60.2, 1], ["2148", "I7N3G6G", "2148", 0, "no description", 60.2, 1], ["2149", "I7N3G6G", "2149", 0, "no description", 60.2, 1], ["2150", "I7N3G6G", "2150", 0, "no description", 60.2, 1], ["2151", "I7N3G6G", "2151", 0, "no description", 60.2, 1], ["2152", "I7N3G6G", "2152", 0, "no description", 60.2, 1], ["2153", "I7N3G6G", "2153", 0, "no description", 60.2, 1], ["2154", "I7N3G6G", "2154", 0, "no description", 60.2, 1], ["2155", "I7N3G6G", "2155", 0, "no description", 60.2, 1], ["2156", "I7N3G6G", "2156", 0, "no description", 60.2, 1], ["2157", "I7N3G6G", "2157", 0, "no description", 60.2, 1], ["2158", "I7N3G6G", "2158", 0, "no description", 60.2, 1], ["2159", "I7N3G6G", "2159", 0, "no description", 60.2, 1], ["2160", "I7N3G6G", "2160", 0, "no description", 60.2, 1], ["2161", "I7N3G6G", "2161", 0, "no description", 60.2, 1], ["2162", "I7N3G6G", "2162", 0, "no description", 60.2, 1], ["2163", "I7N3G6G", "2163", 0, "no description", 60.2, 1], ["2164", "I7N3G6G", "2164", 0, "no description", 60.2, 1], ["2165", "I7N3G6G", "2165", 0, "no description", 60.2, 1], ["2166", "I7N3G6G", "2166", 0, "no description", 60.2, 1], ["2167", "I7N3G6G", "2167", 0, "no description", 60.2, 1], ["2168", "I7N3G6G", "2168", 0, "no description", 60.2, 1], ["2169", "I7N3G6G", "2169", 0, "no description", 60.2, 1], ["2170", "I7N3G6G", "2170", 0, "no description", 60.2, 1], ["2171", "I7N3G6G", "2171", 0, "no description", 60.2, 1], ["2172", "I7N3G6G", "2172", 0, "no description", 60.2, 1], ["2173", "I7N3G6G", "2173", 0, "no description", 60.2, 1], ["2174", "I7N3G6G", "2174", 0, "no description", 60.2, 1], ["2175", "I7N3G6G", "2175", 0, "no description", 60.2, 1], ["2176", "I7N3G6G", "2176", 0, "no description", 60.2, 1], ["2177", "I7N3G6G", "2177", 0, "no description", 60.2, 1], ["2178", "I7N3G6G", "2178", 0, "no description", 60.2, 1], ["2179", "I7N3G6G", "2179", 0, "no description", 60.2, 1], ["2180", "I7N3G6G", "2180", 0, "no description", 60.2, 1], ["2181", "I7N3G6G", "2181", 0, "no description", 60.2, 1], ["2182", "I7N3G6G", "2182", 0, "no description", 60.2, 1], ["2183", "I7N3G6G", "2183", 0, "no description", 60.2, 1], ["2184", "I7N3G6G", "2184", 0, "no description", 60.2, 1], ["2185", "I7N3G6G", "2185", 0, "no description", 60.2, 1], ["2186", "I7N3G6G", "2186", 0, "no description", 60.2, 1], ["2187", "I7N3G6G", "2187", 0, "no description", 60.2, 1], ["2188", "I7N3G6G", "2188", 0, "no description", 60.2, 1], ["2189", "I7N3G6G", "2189", 0, "no description", 60.2, 1], ["2190", "I7N3G6G", "2190", 0, "no description", 60.2, 1], ["2191", "I7N3G6G", "2191", 0, "no description", 60.2, 1], ["2192", "I7N3G6G", "2192", 0, "no description", 60.2, 1], ["2193", "I7N3G6G", "2193", 0, "no description", 60.2, 1], ["2194", "I7N3G6G", "2194", 0, "no description", 60.2, 1], ["2195", "I7N3G6G", "2195", 0, "no description", 60.2, 1], ["2196", "I7N3G6G", "2196", 0, "no description", 60.2, 1], ["2197", "I7N3G6G", "2197", 0, "no description", 60.2, 1], ["2198", "I7N3G6G", "2198", 0, "no description", 60.2, 1], ["2199", "I7N3G6G", "2199", 0, "no description", 60.2, 1], ["2200", "I7N3G6G", "2200", 0, "no description", 60.2, 1], ["2201", "I7N3G6G", "2201", 0, "no description", 60.2, 1], ["2202", "I7N3G6G", "2202", 0, "no description", 60.2, 1], ["2203", "I7N3G6G", "2203", 0, "no description", 60.2, 1], ["2204", "I7N3G6G", "2204", 0, "no description", 60.2, 1], ["2205", "I7N3G6G", "2205", 0, "no description", 60.2, 1], ["2206", "I7N3G6G", "2206", 0, "no description", 60.2, 1], ["2207", "I7N3G6G", "2207", 0, "no description", 60.2, 1], ["2208", "I7N3G6G", "2208", 0, "no description", 60.2, 1], ["2209", "I7N3G6G", "2209", 0, "no description", 60.2, 1], ["2210", "I7N3G6G", "2210", 0, "no description", 60.2, 1], ["2211", "I7N3G6G", "2211", 0, "no description", 60.2, 1], ["2212", "I7N3G6G", "2212", 0, "no description", 60.2, 1], ["2213", "I7N3G6G", "2213", 0, "no description", 60.2, 1], ["2214", "I7N3G6G", "2214", 0, "no description", 60.2, 1], ["2215", "I7N3G6G", "2215", 0, "no description", 60.2, 1], ["2216", "I7N3G6G", "2216", 0, "no description", 60.2, 1], ["2217", "I7N3G6G", "2217", 0, "no description", 60.2, 1], ["2218", "I7N3G6G", "2218", 0, "no description", 60.2, 1], ["2219", "I7N3G6G", "2219", 0, "no description", 60.2, 1], ["2220", "I7N3G6G", "2220", 0, "no description", 60.2, 1], ["2221", "I7N3G6G", "2221", 0, "no description", 60.2, 1], ["2222", "I7N3G6G", "2222", 0, "no description", 60.2, 1], ["2223", "I7N3G6G", "2223", 0, "no description", 60.2, 1], ["2224", "I7N3G6G", "2224", 0, "no description", 60.2, 1], ["2225", "I7N3G6G", "2225", 0, "no description", 60.2, 1], ["2226", "I7N3G6G", "2226", 0, "no description", 60.2, 1]] \ No newline at end of file diff --git a/packages/itmat-job-executor/test/unit_tests/csvcurator.test.ts b/packages/itmat-job-executor/test/unit_tests/csvcurator.test.ts deleted file mode 100644 index a646b6d17..000000000 --- a/packages/itmat-job-executor/test/unit_tests/csvcurator.test.ts +++ /dev/null @@ -1,549 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { IJobEntryForDataCuration, enumValueType } from '@itmat-broker/itmat-types'; -import { processDataRow, processHeader, CSVCurator } from '../../src/curation/CSVCurator'; -import { stub } from './_stubHelper'; - -describe('Unit tests for processHeader function', () => { - const fieldsList = [ - { - fieldId: 1, - fieldName: 'SubjectID', - possibleValues: [], - unit: '', - comments: '', - dataType: enumValueType.STRING - }, - { - fieldId: 2, - fieldName: 'VisitID', - possibleValues: [], - unit: '', - comments: '', - dataType: enumValueType.STRING - }, - { - fieldId: 31, - fieldName: 'Sex', - possibleValues: [{ code: '1', description: 'male' }, { code: '0', description: 'female' }], - unit: '', - comments: '', - dataType: enumValueType.CATEGORICAL - }, - { - fieldId: 32, - fieldName: 'Description', - possibleValues: [], - unit: '', - comments: '', - dataType: enumValueType.STRING - }, - { - fieldId: 33, - fieldName: 'Weight', - possibleValues: [], - unit: 'Kg', - comments: '', - dataType: enumValueType.DECIMAL - } - ]; - - it('processHeader function', () => { - const exampleheader = ['ID', 'SubjectID', 'VisitID', 'Sex', 'Description', 'Weight']; - const { parsedHeader, error, subjectIdIndex, visitIdIndex } = processHeader(exampleheader, fieldsList); - expect(parsedHeader.length).toBe(5); - expect(parsedHeader).toEqual(fieldsList); - expect(error).toBeUndefined(); - expect(subjectIdIndex).toBe(1); - expect(visitIdIndex).toBe(2); - }); - - it('processHeader function without SubjectID or VisitID', () => { - const exampleheader = ['ID', 'SubjectID', 'Sex', 'Description', 'Weight']; - const { parsedHeader, error, subjectIdIndex, visitIdIndex } = processHeader(exampleheader, fieldsList); - expect(parsedHeader.length).toBe(4); - expect(parsedHeader[0]).toEqual(fieldsList[0]); - expect(parsedHeader[1]).toEqual(fieldsList[2]); - expect(parsedHeader[2]).toEqual(fieldsList[3]); - expect(parsedHeader[3]).toEqual(fieldsList[4]); - expect(error).toBeDefined(); - if (!error) - return; - expect(error.length).toBe(1); - expect(error[0]).toBe('SubjectID or VisitID not found.'); - expect(subjectIdIndex).toBe(1); - expect(visitIdIndex).toBe(0); - }); - - it('processHeader function weeds out duplicates', () => { - const exampleheader = ['ID', 'SubjectID', 'VisitID', 'Sex', 'Description', 'Weight', 'Weight']; - const { error, parsedHeader } = processHeader(exampleheader, fieldsList); - expect(parsedHeader.length).toBe(6); - expect(parsedHeader[0]).toEqual(fieldsList[0]); - expect(parsedHeader[1]).toEqual(fieldsList[1]); - expect(parsedHeader[2]).toEqual(fieldsList[2]); - expect(parsedHeader[3]).toEqual(fieldsList[3]); - expect(parsedHeader[4]).toEqual(fieldsList[4]); - expect(parsedHeader[5]).toEqual({ - dataType: 'dul', - fieldId: undefined, - fieldName: 'Weight' - }); - expect(error).toBeDefined(); - if (!error) - return; - expect(error.length).toBe(1); - expect(error[0]).toBe('Line 1 column 7: Duplicate field.'); - }); - - it('processHeader function weeds with unregistered fields', () => { - const exampleheader = ['ID', 'SubjectID', 'VisitID', 'Sex', 'Description', 'Weight', 'UNKNOWN']; - const { error, parsedHeader } = processHeader(exampleheader, fieldsList); - expect(parsedHeader.length).toBe(6); - expect(parsedHeader[0]).toEqual(fieldsList[0]); - expect(parsedHeader[1]).toEqual(fieldsList[1]); - expect(parsedHeader[2]).toEqual(fieldsList[2]); - expect(parsedHeader[3]).toEqual(fieldsList[3]); - expect(parsedHeader[4]).toEqual(fieldsList[4]); - expect(parsedHeader[5]).toEqual({ fieldName: 'UNKNOWN', dataType: 'unk', fieldId: undefined }); - expect(error).toBeDefined(); - if (!error) - return; - expect(error[0]).toBe('Line 1 column 7: Unknown field.'); - }); -}); - -describe('Unit tests for processDataRow function', () => { - const fieldsList = [ - { - fieldId: 1, - fieldName: 'SubjectID', - possibleValues: [], - unit: '', - comments: '', - dataType: enumValueType.STRING - }, - { - fieldId: 2, - fieldName: 'VisitID', - possibleValues: [], - unit: '', - comments: '', - dataType: enumValueType.STRING - }, - { - fieldId: 31, - fieldName: 'Sex', - possibleValues: [{ code: '1', description: 'male' }, { code: '0', description: 'female' }], - unit: '', - comments: '', - dataType: enumValueType.CATEGORICAL - }, - { - fieldId: 32, - fieldName: 'Description', - possibleValues: [], - unit: '', - comments: '', - dataType: enumValueType.STRING - }, - { - fieldId: 33, - fieldName: 'Weight', - possibleValues: [], - unit: 'Kg', - comments: '', - dataType: enumValueType.DECIMAL - } - ]; - - const job = stub({ // subset of the IJobEntry interface - id: 'mockJobId', - studyId: 'mockStudyId' - }); - const { subjectIdIndex, visitIdIndex, parsedHeader } = processHeader(['ID', 'SubjectID', 'VisitID', 'Sex', 'Description', 'Weight'], fieldsList); - const templateParams = { - subjectIdIndex: subjectIdIndex, - visitIdIndex: visitIdIndex, - lineNum: 22, - row: [], - parsedHeader: parsedHeader, - job - }; - - it('processDataRow function correctly parse data row', () => { - const { error, dataEntry } = processDataRow(stub({ ...templateParams, row: ['0', 'I7N3G6G', '1', '1', 'description', 60.2] })); - expect(error).toBeUndefined(); - expect(dataEntry).toEqual({ - m_subjectId: 'I7N3G6G', - m_visitId: '1', - m_studyId: 'mockStudyId', - m_versionId: null, - 31: '1', - 32: 'description', - 33: 60.2, - deleted: null - }); - }); - - it('processDataRow function weeds out datatype mismatch', () => { - const { error, dataEntry } = processDataRow(stub({ ...templateParams, row: ['0', 'I7N3G6G', '1', 'a', 'description', 'b'] })); - expect(error).toBeDefined(); - if (!error) - return; - expect(error).toHaveLength(2); - expect(error[0]).toBe('Line 22 column 4: Cannot parse \'a\' as categorical, value is illegal.'); - expect(error[1]).toBe('Line 22 column 6: Cannot parse \'b\' as decimal.'); - expect(dataEntry).toEqual({ - m_subjectId: 'I7N3G6G', - m_visitId: '1', - m_studyId: 'mockStudyId', - m_versionId: null, - 32: 'description', - deleted: null - }); - }); - - it('processDataRow function deals with missing value by skipping', () => { - const { error, dataEntry } = processDataRow(stub({ ...templateParams, row: ['0', 'I7N3G6G', '1', '1', 'description', ''] })); - expect(error).toBeUndefined(); - expect(dataEntry).toEqual({ - m_subjectId: 'I7N3G6G', - m_visitId: '1', - m_studyId: 'mockStudyId', - m_versionId: null, - 31: '1', - 32: 'description', - deleted: null - }); - }); - - it('processDataRow function deals with missing subject id correctly', () => { - const { error, dataEntry } = processDataRow(stub({ ...templateParams, row: ['0', '', '1', '1', 'description', 60.2] })); - expect(error).toBeDefined(); - if (!error) - return; - expect(error).toHaveLength(1); - expect(error[0]).toBe('No subject id provided.'); - expect(dataEntry).toEqual({ - m_visitId: '1', - m_studyId: 'mockStudyId', - m_versionId: null, - 31: '1', - 32: 'description', - 33: 60.2, - deleted: null - }); - }); - - it('processDataRow function deals with missing visit id correctly', () => { - const { error, dataEntry } = processDataRow(stub({ ...templateParams, row: ['0', 'I7N3G6G', '', '1', 'description', 60.2] })); - expect(error).toBeDefined(); - if (!error) - return; - expect(error).toHaveLength(1); - expect(error[0]).toBe('No visit id provided.'); - expect(dataEntry).toEqual({ - m_subjectId: 'I7N3G6G', - m_studyId: 'mockStudyId', - m_versionId: null, - 31: '1', - 32: 'description', - 33: 60.2, - deleted: null - }); - }); -}); - -describe('CSVCuratorClass', () => { - const fieldsList = [ - { - fieldId: 1, - fieldName: 'SubjectID', - possibleValues: [], - unit: '', - comments: '', - dataType: enumValueType.STRING - }, - { - fieldId: 2, - fieldName: 'VisitID', - possibleValues: [], - unit: '', - comments: '', - dataType: enumValueType.STRING - }, - { - fieldId: 31, - fieldName: 'Sex', - possibleValues: [{ code: '1', description: 'male' }, { code: '0', description: 'female' }], - unit: '', - comments: '', - dataType: enumValueType.CATEGORICAL - }, - { - fieldId: 32, - fieldName: 'Description', - possibleValues: [], - unit: '', - comments: '', - dataType: enumValueType.STRING - }, - { - fieldId: 33, - fieldName: 'Weight', - possibleValues: [], - unit: 'Kg', - comments: '', - dataType: enumValueType.DECIMAL - } - ]; - - // should stop uploading when error occurs - function BulkInsert(this: any) { - this._insertArray = []; - this._executeCalled = []; // array of length of _insertArray when execute() is called - this.insert = (object: any) => { this._insertArray.push(object); }; - this._foundobj = null; - this.find = (object: { [x: string]: any; }) => { - let tag; - for (const each of this._insertArray) { - tag = true; - for (const field of Object.keys(object)) { - if (JSON.stringify(object[field]) !== JSON.stringify(each[field])) { - tag = false; - break; - } - } - if (tag === true) { - this._foundobj = each; - break; - } - } - return this; - }; - this.upsert = () => { return this; }; - this.execute = () => new Promise((resolve) => { - setTimeout(() => { - this._executeCalled.push(this._insertArray.length); - resolve(); - }, 10); - }); - this.updateOne = (object: { [x: string]: { [x: string]: any; }; }) => { - if (this._foundobj === null) { - this._insertArray.push(object['$set']); - } - for (let i = 0; i < this._insertArray.length; i++) { - if (JSON.stringify(this._insertArray[i]) === JSON.stringify(this._foundobj)) { - for (const field of Object.keys(object['$set'])) { - this._insertArray[i][field] = object['$set'][field]; - } - } - } - this._foundobj = null; - }; - } - - function MongoStub(this: any) { - this._bulkinsert = new (BulkInsert as any)(); - this.initializeUnorderedBulkOp = () => this._bulkinsert; - } - - it('test mongostub update new', async () => { - const bulkinsert = (new (MongoStub as any)()).initializeUnorderedBulkOp(); - bulkinsert.insert({ a: 1 }); - bulkinsert.insert({ b: 2 }); - return bulkinsert.execute().then(() => { - bulkinsert.find({ c: 3 }).upsert().updateOne({ $set: { c: 4 } }); - return bulkinsert.execute(); - }).then(() => { - expect(bulkinsert._insertArray).toEqual([{ a: 1 }, { b: 2 }, { c: 4 }]); - expect(bulkinsert._executeCalled).toEqual([2, 3]); - }); - }, 10000); - - it('test mongostub update existing', async () => { - const bulkinsert = (new (MongoStub as any)()).initializeUnorderedBulkOp(); - bulkinsert.insert({ a: 1 }); - bulkinsert.insert({ b: 2 }); - return bulkinsert.execute().then(() => { - bulkinsert.find({ b: 2 }).upsert().updateOne({ $set: { d: 4 } }); - return bulkinsert.execute(); - }).then(() => { - expect(bulkinsert._insertArray).toEqual([{ a: 1 }, { b: 2, d: 4 }]); - expect(bulkinsert._executeCalled).toEqual([2, 2]); - }); - }, 10000); - - it('csvcurator uploads csv file okay', async () => { - const readStream = fs.createReadStream(path.join(__dirname, '../testFiles/CSVCurator.tsv')); - const mongoStub = new (MongoStub as any)(); - const jobEntry = stub({ // subset of the IJobEntry interface - id: 'mockJobId', - studyId: 'mockStudyId' - }); - const csvcurator = new CSVCurator( - mongoStub, - readStream, - undefined, - jobEntry, - fieldsList - ); - const errors = await csvcurator.processIncomingStreamAndUploadToMongo(); - expect(errors).toEqual([]); - expect(mongoStub._bulkinsert._insertArray).toHaveLength(2226); - expect(mongoStub._bulkinsert._executeCalled).toEqual([1000, 2000, 2226]); - expect(mongoStub._bulkinsert._insertArray[0]).toEqual({ - m_subjectId: 'I7N3G6G', - m_visitId: '1', - m_studyId: 'mockStudyId', - m_versionId: null, - 31: '0', - 32: 'no description', - 33: 60.2, - deleted: null - }); - - }); - - it('csvcurator catches non-existing headers', async () => { - const readStream = fs.createReadStream(path.join(__dirname, '../testFiles/CSVCurator_error1.tsv')); - const mongoStub = new (MongoStub as any)(); - const jobEntry = stub({ // subset of the IJobEntry interface - id: 'mockJobId', - studyId: 'mockStudyId' - }); - const csvcurator = new CSVCurator( - mongoStub, - readStream, - undefined, - jobEntry, - fieldsList - ); - const errors = await csvcurator.processIncomingStreamAndUploadToMongo(); - expect(errors.length).toBe(1); - expect(errors[0]).toBe('Line 1 column 7: Unknown field.'); - expect(mongoStub._bulkinsert._insertArray).toHaveLength(0); - expect(mongoStub._bulkinsert._executeCalled).toEqual([]); - - }); - - it('csvcurator catches duplicate header', async () => { - const readStream = fs.createReadStream(path.join(__dirname, '../testFiles/CSVCurator_error2.tsv')); - const mongoStub = new (MongoStub as any)(); - const jobEntry = stub({ // subset of the IJobEntry interface - id: 'mockJobId', - studyId: 'mockStudyId' - }); - const csvcurator = new CSVCurator( - mongoStub, - readStream, - undefined, - jobEntry, - fieldsList - ); - const errors = await csvcurator.processIncomingStreamAndUploadToMongo(); - expect(errors[0]).toBe('Line 1 column 6: Duplicate field.'); - expect(mongoStub._bulkinsert._insertArray).toHaveLength(0); - expect(mongoStub._bulkinsert._executeCalled).toEqual([]); - - }); - - it('csvcurator catches uneven field before watermark', async () => { - const readStream = fs.createReadStream(path.join(__dirname, '../testFiles/CSVCurator_error3.tsv')); - const mongoStub = new (MongoStub as any)(); - const jobEntry = stub({ // subset of the IJobEntry interface - id: 'mockJobId', - studyId: 'mockStudyId' - }); - const csvcurator = new CSVCurator( - mongoStub, - readStream, - undefined, - jobEntry, - fieldsList - ); - - const errors = await csvcurator.processIncomingStreamAndUploadToMongo(); - expect(errors).toEqual([ - 'Line 18: Uneven field Number; expected 6 fields but got 5', - 'Line 28: Uneven field Number; expected 6 fields but got 5' - ]); - expect(mongoStub._bulkinsert._insertArray).toHaveLength(16); - expect(mongoStub._bulkinsert._executeCalled).toEqual([]); - expect(mongoStub._bulkinsert._insertArray[0]).toEqual({ - m_subjectId: 'I7N3G6G', - m_visitId: '1', - m_studyId: 'mockStudyId', - m_versionId: null, - 31: '0', - 32: 'no description', - 33: 60.2, - deleted: null - }); - - }); - - it('csvcurator catches uneven field after watermark', async () => { - const readStream = fs.createReadStream(path.join(__dirname, '../testFiles/CSVCurator_error4.tsv')); - const mongoStub = new (MongoStub as any)(); - const jobEntry = stub({ // subset of the IJobEntry interface - id: 'mockJobId', - studyId: 'mockStudyId' - }); - const csvcurator = new CSVCurator( - mongoStub, - readStream, - undefined, - jobEntry, - fieldsList - ); - - const errors = await csvcurator.processIncomingStreamAndUploadToMongo(); - expect(errors).toEqual([ - 'Line 1530: Uneven field Number; expected 6 fields but got 4', - 'Line 1836: Uneven field Number; expected 6 fields but got 5' - ]); - expect(mongoStub._bulkinsert._insertArray).toHaveLength(1528); - expect(mongoStub._bulkinsert._executeCalled).toEqual([1000]); - expect(mongoStub._bulkinsert._insertArray[0]).toEqual({ - m_subjectId: 'I7N3G6G', - m_visitId: '1', - m_studyId: 'mockStudyId', - m_versionId: null, - 31: '0', - 32: 'no description', - 33: 60.2, - deleted: null - }); - - }); - - it('csvcurator catches mixed errors', async () => { - const readStream = fs.createReadStream(path.join(__dirname, '../testFiles/CSVCurator_error5.tsv')); - const mongoStub = new (MongoStub as any)(); - const jobEntry = stub({ // subset of the IJobEntry interface - id: 'mockJobId', - studyId: 'mockStudyId' - }); - const csvcurator = new CSVCurator( - mongoStub, - readStream, - undefined, - jobEntry, - fieldsList - ); - - const errors = await csvcurator.processIncomingStreamAndUploadToMongo(); - expect(errors).toEqual([ - 'Line 1 column 7: Unknown field.', - 'Line 8 column 4: Cannot parse \'a\' as categorical, value is illegal.', - 'Line 31: Uneven field Number; expected 7 fields but got 6', - 'Line 32: Uneven field Number; expected 7 fields but got 6', - 'Line 1531: Uneven field Number; expected 7 fields but got 3', - 'Line 1532: Uneven field Number; expected 7 fields but got 4' - ]); - expect(mongoStub._bulkinsert._insertArray).toHaveLength(0); - expect(mongoStub._bulkinsert._executeCalled).toEqual([]); - }); -}); diff --git a/packages/itmat-job-executor/test/unit_tests/fieldcurator.test.ts b/packages/itmat-job-executor/test/unit_tests/fieldcurator.test.ts deleted file mode 100644 index 419c6dd90..000000000 --- a/packages/itmat-job-executor/test/unit_tests/fieldcurator.test.ts +++ /dev/null @@ -1,365 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { IJobEntryForFieldCuration, IJobEntry, enumValueType } from '@itmat-broker/itmat-types'; -import { processFieldRow, FieldCurator } from '../../src/curation/FieldCurator'; -import { stub } from './_stubHelper'; - -describe('Unit tests for processFieldRow function', () => { - const job = stub({ // subset of the IJobEntry interface - id: 'mockJobId', - studyId: 'mockStudyId', - data: { - tag: 'testFieldTree' - } - }); - const templateParams = { - lineNum: 22, - row: [], - job, - fieldTreeId: 'mockFieldTreeId', - codes: {} - }; - - it('processFieldRow function correctly parse data row', () => { - const { error, dataEntry } = processFieldRow(stub({ - ...templateParams, row: [ - '564', 'IDEAFAST', 'Participants', '', '', '', 'SubjectID', 'Subject ID', '', '', '', '', '', '', '', '', '', 'nvarchar', '', 'False', '', '9', '', 'True', 'False', 'True', '', 'False', '', '', '', 'False', '' - ] - })); - expect(error).toBeUndefined(); - expect(dataEntry.id).toBeDefined(); - expect(typeof dataEntry.id).toBe('string'); - expect(dataEntry.fieldName).toBe('SubjectID'); - expect(dataEntry.dataType).toBe(enumValueType.STRING); - expect(dataEntry.possibleValues).toEqual([]); - expect(dataEntry.unit).toBe(''); - expect(dataEntry.comments).toBe(''); - expect(dataEntry.dateDeleted).toBe(null); - expect(typeof dataEntry.dateAdded).toBe('string'); - }); - - it('processFieldRow function detects necessary fields that are empty', () => { - const { error, dataEntry } = processFieldRow(stub({ - ...templateParams, row: [ - '', 'IDEAFAST', 'Participants', '', '', '', '', 'Subject ID', '', '', '', '', '', '', '', '', '', 'nvarchar', '', 'False', '', '9', '', 'True', 'False', 'True', '', 'False', '', '', '', 'False', '' - ] - })); - expect(error).toBeDefined(); - if (!error) - return; - expect(error).toHaveLength(2); - expect(error[0]).toBe('Line 22 column 1: FieldID cannot be empty.'); - expect(error[1]).toBe('Line 22 column 7: Field Name cannot be empty.'); - expect(dataEntry).toEqual({}); - }); - - it('processFieldRow function detects uneven fields', () => { - const { error, dataEntry } = processFieldRow(stub({ - ...templateParams, row: [ - '564', 'IDEAFAST', 'Participants', '', '', '', 'SubjectID', 'Subject ID', '', '', '', '', '', '', '', '', '', 'nvarchar', '', 'False', '', '9', '', 'True', 'False', 'True', 'False', '', '', '', 'False' - ] - })); - expect(error).toBeDefined(); - if (!error) - return; - expect(error).toHaveLength(1); - expect(error[0]).toBe('Line 22: Uneven field Number; expected 33 fields but got 31.'); - expect(dataEntry).toEqual({}); - }); - - // it('processFieldRow function requires possibleValues if valueType is "C"', () => { - // const { error, dataEntry } = processFieldRow(stub({ - // ...templateParams, row: [ - // '42', 'Gender', 'c', '', '', 'Demographic>Baseline', '1', '1', '1', '1', 'Sex / Gender' - // ] - // })); - // expect(error).toBeDefined(); - // expect(error).toHaveLength(1); - // expect(error[0]).toBe('Line 22 column 4: "Possible values" cannot be empty if value type is categorical.'); - // expect(dataEntry).toEqual({}); - // }); - - it('processFieldRow function catches unparsable entries for supposed number', () => { - const { error, dataEntry } = processFieldRow(stub({ - ...templateParams, row: [ - '564A', 'IDEAFAST', 'Participants', '', '', '', 'SubjectID', 'Subject ID', '', '', '', '', '', '', '', '', '', 'nvarchar', '', 'False', '', '9A', '', 'True', 'False', 'True', '', 'False', '', '', '', 'False', '' - ] - })); - expect(error).toBeDefined(); - if (!error) - return; - expect(error).toHaveLength(2); - expect(error[0]).toBe('Line 22 column 1: Cannot parse field ID as number.'); - expect(error[1]).toBe('Line 22 column 22: Cannot parse length of characters as number.'); - expect(dataEntry).toEqual({}); - }); - - it('processFieldRow function catches invalid value type', () => { - const { error, dataEntry } = processFieldRow(stub({ - ...templateParams, row: [ - '564', 'IDEAFAST', 'Participants', '', '', '', 'SubjectID', 'Subject ID', '', '', '', '', '', '', '', '', '', 'str', '', 'False', '', '9', '', 'True', 'False', 'True', '', 'False', '', '', '', 'False', '' - ] - })); - expect(error).toBeDefined(); - if (!error) - return; - expect(error).toHaveLength(1); - expect(error[0]).toBe('Line 22 column 18: Invalid value type "Participants": use "int" for integers, "decimal()" for decimals, "nvarchar/varchar" for characters/strings, "datetime" for date time, "bit" for bit.'); - expect(dataEntry).toEqual({}); - }); -}); - -describe('FieldCuratorClass', () => { - // should stop uploading when error occurs - function BulkInsert(this: any) { - this._insertArray = []; - this._executeCalled = []; // array of length of _insertArray when execute() is called - this.insert = (object: any) => { this._insertArray.push(object); }; - this.execute = () => new Promise((resolve) => { - setTimeout(() => { - this._executeCalled.push(this._insertArray.length); - resolve(); - }, 10); - }); - } - - function MongoStub(this: any) { - this._bulkinsert = new (BulkInsert as any)(); - this.initializeUnorderedBulkOp = () => this._bulkinsert; - } - - it('test mongostub', async () => { - const bulkinsert = (new (MongoStub as any)()).initializeUnorderedBulkOp(); - bulkinsert.insert({}); - bulkinsert.insert({}); - return bulkinsert.execute().then(() => { - bulkinsert.insert({}); - return bulkinsert.execute(); - }).then(() => { - expect(bulkinsert._insertArray).toEqual([{}, {}, {}]); - expect(bulkinsert._executeCalled).toEqual([2, 3]); - }); - }); - - it('fieldcurator uploads csv file < 1000 fields okay', async () => { - const readStream = fs.createReadStream(path.join(__dirname, '../testFiles/FieldCurator.tsv')); - const mongoStub = new (MongoStub as any)(); - const jobEntry = stub({ // subset of the IJobEntry interface - id: 'mockJobId', - studyId: 'mockStudyId', - data: { - tag: 'mockTag' - } - }); - const fieldcurator = new FieldCurator( - mongoStub, - readStream, - undefined, - jobEntry, - {} - ); - const errors = await fieldcurator.processIncomingStreamAndUploadToMongo(); - expect(errors).toEqual([]); - expect(mongoStub._bulkinsert._insertArray).toHaveLength(26); - expect(mongoStub._bulkinsert._executeCalled).toEqual([26]); - expect(mongoStub._bulkinsert._insertArray[0].id).toBeDefined(); - expect(typeof mongoStub._bulkinsert._insertArray[0].id).toBe('string'); - expect(mongoStub._bulkinsert._insertArray[0].fieldName).toBe('SubjectID'); - expect(mongoStub._bulkinsert._insertArray[0].dataType).toBe(enumValueType.STRING); - expect(mongoStub._bulkinsert._insertArray[0].possibleValues).toEqual([]); - expect(mongoStub._bulkinsert._insertArray[0].unit).toBe(''); - expect(mongoStub._bulkinsert._insertArray[0].comments).toBe(''); - expect(mongoStub._bulkinsert._insertArray[0].dateDeleted).toBe(null); - expect(typeof mongoStub._bulkinsert._insertArray[0].dateAdded).toBe('string'); - }); - - it('fieldcurator uploads csv file > 1000 fields okay', async () => { - const readStream = fs.createReadStream(path.join(__dirname, '../testFiles/FieldCurator_1000.tsv')); - const mongoStub = new (MongoStub as any)(); - const jobEntry = stub({ // subset of the IJobEntry interface - id: 'mockJobId', - studyId: 'mockStudyId', - data: { - tag: 'mockTag' - } - }); - const fieldcurator = new FieldCurator( - mongoStub, - readStream, - undefined, - jobEntry, - {} - ); - const errors = await fieldcurator.processIncomingStreamAndUploadToMongo(); - expect(errors).toEqual([]); - expect(mongoStub._bulkinsert._insertArray).toHaveLength(1226); - expect(mongoStub._bulkinsert._executeCalled).toEqual([1000, 1226]); - expect(mongoStub._bulkinsert._insertArray[0].id).toBeDefined(); - expect(typeof mongoStub._bulkinsert._insertArray[0].id).toBe('string'); - expect(mongoStub._bulkinsert._insertArray[0].fieldName).toBe('SubjectID'); - expect(mongoStub._bulkinsert._insertArray[0].dataType).toBe(enumValueType.STRING); - expect(mongoStub._bulkinsert._insertArray[0].possibleValues).toEqual([]); - expect(mongoStub._bulkinsert._insertArray[0].unit).toBe(''); - expect(mongoStub._bulkinsert._insertArray[0].comments).toBe(''); - expect(mongoStub._bulkinsert._insertArray[0].dateDeleted).toBe(null); - expect(typeof mongoStub._bulkinsert._insertArray[0].dateAdded).toBe('string'); - }); - - it('fieldcurator catches duplicate fieldId before first watermark', async () => { - const readStream = fs.createReadStream(path.join(__dirname, '../testFiles/FieldCurator_error1.tsv')); - const mongoStub = new (MongoStub as any)(); - const jobEntry = stub({ // subset of the IJobEntry interface - id: 'mockJobId', - studyId: 'mockStudyId', - data: { - tag: 'mockTag' - } - }); - const fieldcurator = new FieldCurator( - mongoStub, - readStream, - undefined, - jobEntry, - {} - ); - const errors = await fieldcurator.processIncomingStreamAndUploadToMongo(); - expect(errors).toEqual(['Data Error: There is duplicate field id.']); - expect(mongoStub._bulkinsert._insertArray).toHaveLength(1226); - expect(mongoStub._bulkinsert._executeCalled).toEqual([1000]); - expect(mongoStub._bulkinsert._insertArray[0].id).toBeDefined(); - expect(typeof mongoStub._bulkinsert._insertArray[0].id).toBe('string'); - expect(mongoStub._bulkinsert._insertArray[0].fieldName).toBe('SubjectID'); - expect(mongoStub._bulkinsert._insertArray[0].dataType).toBe(enumValueType.STRING); - expect(mongoStub._bulkinsert._insertArray[0].possibleValues).toEqual([]); - expect(mongoStub._bulkinsert._insertArray[0].unit).toBe(''); - expect(mongoStub._bulkinsert._insertArray[0].comments).toBe(''); - expect(mongoStub._bulkinsert._insertArray[0].dateDeleted).toBe(null); - expect(typeof mongoStub._bulkinsert._insertArray[0].dateAdded).toBe('string'); - }); - - it('fieldcurator catches duplicate fieldId after first watermark', async () => { - const readStream = fs.createReadStream(path.join(__dirname, '../testFiles/FieldCurator_error2.tsv')); - const mongoStub = new (MongoStub as any)(); - const jobEntry = stub({ // subset of the IJobEntry interface - id: 'mockJobId', - studyId: 'mockStudyId', - data: { - tag: 'mockTag' - } - }); - const fieldcurator = new FieldCurator( - mongoStub, - readStream, - undefined, - jobEntry, - {} - ); - const errors = await fieldcurator.processIncomingStreamAndUploadToMongo(); - expect(errors).toEqual(['Data Error: There is duplicate field id.']); - expect(mongoStub._bulkinsert._insertArray).toHaveLength(1226); - expect(mongoStub._bulkinsert._executeCalled).toEqual([1000]); - expect(mongoStub._bulkinsert._insertArray[0].id).toBeDefined(); - expect(typeof mongoStub._bulkinsert._insertArray[0].id).toBe('string'); - expect(mongoStub._bulkinsert._insertArray[0].fieldName).toBe('SubjectID'); - expect(mongoStub._bulkinsert._insertArray[0].dataType).toBe(enumValueType.STRING); - expect(mongoStub._bulkinsert._insertArray[0].possibleValues).toEqual([]); - expect(mongoStub._bulkinsert._insertArray[0].unit).toBe(''); - expect(mongoStub._bulkinsert._insertArray[0].comments).toBe(''); - expect(mongoStub._bulkinsert._insertArray[0].dateDeleted).toBe(null); - expect(typeof mongoStub._bulkinsert._insertArray[0].dateAdded).toBe('string'); - }); - - it('fieldcurator catches uneven field before watermark', async () => { - const readStream = fs.createReadStream(path.join(__dirname, '../testFiles/FieldCurator_error3.tsv')); - const mongoStub = new (MongoStub as any)(); - const jobEntry = stub({ // subset of the IJobEntry interface - id: 'mockJobId', - studyId: 'mockStudyId', - data: { - tag: 'mockTag' - } - }); - const fieldcurator = new FieldCurator( - mongoStub, - readStream, - undefined, - jobEntry, - {} - ); - const errors = await fieldcurator.processIncomingStreamAndUploadToMongo(); - expect(errors).toEqual(['Line 24: Uneven field Number; expected 33 fields but got 31.']); - expect(mongoStub._bulkinsert._insertArray).toHaveLength(22); - expect(mongoStub._bulkinsert._executeCalled).toEqual([]); - expect(mongoStub._bulkinsert._insertArray[0].id).toBeDefined(); - expect(typeof mongoStub._bulkinsert._insertArray[0].id).toBe('string'); - expect(mongoStub._bulkinsert._insertArray[0].fieldName).toBe('SubjectID'); - expect(mongoStub._bulkinsert._insertArray[0].dataType).toBe(enumValueType.STRING); - expect(mongoStub._bulkinsert._insertArray[0].possibleValues).toEqual([]); - expect(mongoStub._bulkinsert._insertArray[0].unit).toBe(''); - expect(mongoStub._bulkinsert._insertArray[0].comments).toBe(''); - expect(mongoStub._bulkinsert._insertArray[0].dateDeleted).toBe(null); - expect(typeof mongoStub._bulkinsert._insertArray[0].dateAdded).toBe('string'); - }); - - it('fieldcurator catches uneven field after watermark', async () => { - const readStream = fs.createReadStream(path.join(__dirname, '../testFiles/FieldCurator_error4.tsv')); - const mongoStub = new (MongoStub as any)(); - const jobEntry = stub({ // subset of the IJobEntry interface - id: 'mockJobId', - studyId: 'mockStudyId', - data: { - tag: 'mockTag' - } - }); - const fieldcurator = new FieldCurator( - mongoStub, - readStream, - undefined, - jobEntry, - {} - ); - const errors = await fieldcurator.processIncomingStreamAndUploadToMongo(); - expect(errors).toEqual(['Line 1121: Uneven field Number; expected 33 fields but got 32.']); - expect(mongoStub._bulkinsert._insertArray).toHaveLength(1119); - expect(mongoStub._bulkinsert._executeCalled).toEqual([1000]); - expect(mongoStub._bulkinsert._insertArray[0].id).toBeDefined(); - expect(typeof mongoStub._bulkinsert._insertArray[0].id).toBe('string'); - expect(mongoStub._bulkinsert._insertArray[0].fieldName).toBe('SubjectID'); - expect(mongoStub._bulkinsert._insertArray[0].dataType).toBe(enumValueType.STRING); - expect(mongoStub._bulkinsert._insertArray[0].possibleValues).toEqual([]); - expect(mongoStub._bulkinsert._insertArray[0].unit).toBe(''); - expect(mongoStub._bulkinsert._insertArray[0].comments).toBe(''); - expect(mongoStub._bulkinsert._insertArray[0].dateDeleted).toBe(null); - expect(typeof mongoStub._bulkinsert._insertArray[0].dateAdded).toBe('string'); - }); - - it('fieldcurator catches mixed errors', async () => { - const readStream = fs.createReadStream(path.join(__dirname, '../testFiles/FieldCurator_error5.tsv')); - const mongoStub = new (MongoStub as any)(); - const jobEntry = stub>({ // subset of the IJobEntry interface - id: 'mockJobId', - studyId: 'mockStudyId', - data: { - versionTag: 'testData' - } - }); - const fieldcurator = new FieldCurator( - mongoStub, - readStream, - undefined, - jobEntry, - {} - ); - - const errors = await fieldcurator.processIncomingStreamAndUploadToMongo(); - expect(errors).toEqual([ - 'Line 10 column 7: Field Name cannot be empty.', - 'Line 69: Uneven field Number; expected 33 fields but got 32.', - 'Line 331 column 18: Invalid value type "Participants": use "int" for integers, "decimal()" for decimals, "nvarchar/varchar" for characters/strings, "datetime" for date time, "bit" for bit.', - 'Line 919 column 1: Cannot parse field ID as number.', - 'Data Error: There is duplicate field id.' - ]); - expect(mongoStub._bulkinsert._insertArray).toHaveLength(8); - expect(mongoStub._bulkinsert._executeCalled).toEqual([]); - }); -}); diff --git a/packages/itmat-job-executor/test/unit_tests/jsoncurator.test.ts b/packages/itmat-job-executor/test/unit_tests/jsoncurator.test.ts deleted file mode 100644 index 745b146a4..000000000 --- a/packages/itmat-job-executor/test/unit_tests/jsoncurator.test.ts +++ /dev/null @@ -1,535 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { IJobEntryForDataCuration, enumValueType } from '@itmat-broker/itmat-types'; -import { processJSONHeader, processEachSubject, JSONCurator } from '../../src/curation/JSONCurator'; -import { stub } from './_stubHelper'; - -describe('Unit tests for processJSONHeader function', () => { - const fieldsList = [ - { - fieldId: 1, - fieldName: 'SubjectID', - possibleValues: [], - unit: '', - comments: '', - dataType: enumValueType.STRING - }, - { - fieldId: 2, - fieldName: 'VisitID', - possibleValues: [], - unit: '', - comments: '', - dataType: enumValueType.STRING - }, - { - fieldId: 31, - fieldName: 'Sex', - possibleValues: [{ code: '1', description: 'male' }, { code: '0', description: 'female' }], - unit: '', - comments: '', - dataType: enumValueType.CATEGORICAL - }, - { - fieldId: 32, - fieldName: 'Description', - possibleValues: [], - unit: '', - comments: '', - dataType: enumValueType.STRING - }, - { - fieldId: 33, - fieldName: 'Weight', - possibleValues: [], - unit: 'Kg', - comments: '', - dataType: enumValueType.DECIMAL - } - ]; - - it('processJSONHeader function', () => { - const exampleheader = ['ID', 'SubjectID', 'VisitID', 'Sex', 'Description', 'Weight']; - const { parsedHeader, error, subjectIdIndex, visitIdIndex } = processJSONHeader(exampleheader, fieldsList); - expect(parsedHeader.length).toBe(5); - expect(parsedHeader).toEqual(fieldsList); - expect(error).toBeUndefined(); - expect(subjectIdIndex).toBe(1); - expect(visitIdIndex).toBe(2); - }); - - it('processJSONHeader function without SubjectID or VisitID', () => { - const exampleheader = ['ID', 'SubjectID', 'Sex', 'Description', 'Weight']; - const { parsedHeader, error, subjectIdIndex, visitIdIndex } = processJSONHeader(exampleheader, fieldsList); - expect(parsedHeader.length).toBe(4); - expect(parsedHeader[0]).toEqual(fieldsList[0]); - expect(parsedHeader[1]).toEqual(fieldsList[2]); - expect(parsedHeader[2]).toEqual(fieldsList[3]); - expect(parsedHeader[3]).toEqual(fieldsList[4]); - expect(error).toBeDefined(); - if (!error) - return; - expect(error.length).toBe(1); - expect(error[0]).toBe('SubjectID or VisitID not found.'); - expect(subjectIdIndex).toBe(1); - expect(visitIdIndex).toBe(0); - }); - - it('processJSONHeader function weeds out duplicates', () => { - const exampleheader = ['ID', 'SubjectID', 'VisitID', 'Sex', 'Description', 'Weight', 'Weight']; - const { error, parsedHeader } = processJSONHeader(exampleheader, fieldsList); - expect(parsedHeader.length).toBe(6); - expect(parsedHeader[0]).toEqual(fieldsList[0]); - expect(parsedHeader[1]).toEqual(fieldsList[1]); - expect(parsedHeader[2]).toEqual(fieldsList[2]); - expect(parsedHeader[3]).toEqual(fieldsList[3]); - expect(parsedHeader[4]).toEqual(fieldsList[4]); - expect(parsedHeader[5]).toEqual({ - dataType: 'dul', - fieldId: undefined, - fieldName: 'Weight' - }); - expect(error).toBeDefined(); - if (!error) - return; - expect(error.length).toBe(1); - expect(error[0]).toBe('Object 1 column 7: Duplicate field.'); - }); - - it('processJSONHeader function weeds with unregistered fields', () => { - const exampleheader = ['ID', 'SubjectID', 'VisitID', 'Sex', 'Description', 'Weight', 'UNKNOWN']; - const { error, parsedHeader } = processJSONHeader(exampleheader, fieldsList); - expect(parsedHeader.length).toBe(6); - expect(parsedHeader[0]).toEqual(fieldsList[0]); - expect(parsedHeader[1]).toEqual(fieldsList[1]); - expect(parsedHeader[2]).toEqual(fieldsList[2]); - expect(parsedHeader[3]).toEqual(fieldsList[3]); - expect(parsedHeader[4]).toEqual(fieldsList[4]); - expect(parsedHeader[5]).toEqual({ fieldName: 'UNKNOWN', dataType: 'unk', fieldId: undefined }); - expect(error).toBeDefined(); - if (!error) - return; - expect(error[0]).toBe('Object 1 column 7: Unknown field.'); - }); -}); - -describe('Unit tests for processEachSubject function', () => { - const fieldsList = [ - { - fieldId: 1, - fieldName: 'SubjectID', - possibleValues: [], - unit: '', - comments: '', - dataType: enumValueType.STRING - }, - { - fieldId: 2, - fieldName: 'VisitID', - possibleValues: [], - unit: '', - comments: '', - dataType: enumValueType.STRING - }, - { - fieldId: 31, - fieldName: 'Sex', - possibleValues: [{ code: '1', description: 'male' }, { code: '0', description: 'female' }], - unit: '', - comments: '', - dataType: enumValueType.CATEGORICAL - }, - { - fieldId: 32, - fieldName: 'Description', - possibleValues: [], - unit: '', - comments: '', - dataType: enumValueType.STRING - }, - { - fieldId: 33, - fieldName: 'Weight', - possibleValues: [], - unit: 'Kg', - comments: '', - dataType: enumValueType.DECIMAL - } - ]; - - const job = stub({ // subset of the IJobEntry interface - id: 'mockJobId', - studyId: 'mockStudyId' - }); - const { subjectIdIndex, visitIdIndex, parsedHeader } = processJSONHeader(['ID', 'SubjectID', 'VisitID', 'Sex', 'Description', 'Weight'], fieldsList); - const templateParams = { - subjectIdIndex: subjectIdIndex, - visitIdIndex: visitIdIndex, - objectNum: 22, - row: [], - parsedHeader: parsedHeader, - job - }; - - it('processEachSubject function correctly parse data row', () => { - const { error, dataEntry } = processEachSubject(stub({ ...templateParams, subject: ['0', 'I7N3G6G', '1', 0, 'description', 60.2] })); - expect(error).toBeUndefined(); - expect(dataEntry).toEqual({ - m_subjectId: 'I7N3G6G', - m_visitId: '1', - m_studyId: 'mockStudyId', - 31: '0', - 32: 'description', - 33: 60.2, - deleted: null - }); - }); - - it('processEachSubject function weeds out datatype mismatch', () => { - const { error, dataEntry } = processEachSubject(stub({ ...templateParams, subject: ['0', 'I7N3G6G', '1', 'a', 'description', 'b'] })); - expect(error).toBeDefined(); - if (!error) - return; - expect(error).toHaveLength(2); - expect(error[0]).toBe('Object 22 column 4: Cannot parse \'a\' as categorical, value is illegal.'); - expect(error[1]).toBe('Object 22 column 6: Cannot parse \'b\' as decimal.'); - expect(dataEntry).toEqual({ - m_subjectId: 'I7N3G6G', - m_visitId: '1', - m_studyId: 'mockStudyId', - 32: 'description', - deleted: null - }); - }); - - it('processEachSubject function deals with missing value by skipping', () => { - const { error, dataEntry } = processEachSubject(stub({ ...templateParams, subject: ['0', 'I7N3G6G', '1', 0, 'description', ''] })); - expect(error).toBeUndefined(); - expect(dataEntry).toEqual({ - m_subjectId: 'I7N3G6G', - m_visitId: '1', - m_studyId: 'mockStudyId', - 31: '0', - 32: 'description', - deleted: null - }); - }); - - it('processEachSubject function deals with missing subject id correctly', () => { - const { error, dataEntry } = processEachSubject(stub({ ...templateParams, subject: ['0', '', '1', 0, 'description', 60.2] })); - expect(error).toBeDefined(); - if (!error) - return; - expect(error).toHaveLength(1); - expect(error[0]).toBe('No subject id provided.'); - expect(dataEntry).toEqual({ - m_visitId: '1', - m_studyId: 'mockStudyId', - 31: '0', - 32: 'description', - 33: 60.2, - deleted: null - }); - }); - - it('processEachSubject function deals with missing visit id correctly', () => { - const { error, dataEntry } = processEachSubject(stub({ ...templateParams, subject: ['0', 'I7N3G6G', '', 0, 'description', 60.2] })); - expect(error).toBeDefined(); - if (!error) - return; - expect(error).toHaveLength(1); - expect(error[0]).toBe('No visit id provided.'); - expect(dataEntry).toEqual({ - m_subjectId: 'I7N3G6G', - m_studyId: 'mockStudyId', - 31: '0', - 32: 'description', - 33: 60.2, - deleted: null - }); - }); -}); - -describe('JSONCuratorClass', () => { - const fieldsList = [ - { - fieldId: 1, - fieldName: 'SubjectID', - possibleValues: [], - unit: '', - comments: '', - dataType: enumValueType.STRING - }, - { - fieldId: 2, - fieldName: 'VisitID', - possibleValues: [], - unit: '', - comments: '', - dataType: enumValueType.STRING - }, - { - fieldId: 31, - fieldName: 'Sex', - possibleValues: [{ code: '1', description: 'male' }, { code: '0', description: 'female' }], - unit: '', - comments: '', - dataType: enumValueType.CATEGORICAL - }, - { - fieldId: 32, - fieldName: 'Description', - possibleValues: [], - unit: '', - comments: '', - dataType: enumValueType.STRING - }, - { - fieldId: 33, - fieldName: 'Weight', - possibleValues: [], - unit: 'Kg', - comments: '', - dataType: enumValueType.DECIMAL - } - ]; - // should stop uploading when error occurs - function BulkInsert(this: any) { - this._insertArray = []; - this._executeCalled = []; // array of length of _insertArray when execute() is called - this.insert = (object: any) => { this._insertArray.push(object); }; - this._foundobj = null; - this.find = (object: { [x: string]: any; }) => { - let tag; - for (const each of this._insertArray) { - tag = true; - for (const field of Object.keys(object)) { - if (JSON.stringify(object[field]) !== JSON.stringify(each[field])) { - tag = false; - break; - } - } - if (tag === true) { - this._foundobj = each; - break; - } - } - return this; - }; - this.upsert = () => { return this; }; - this.execute = () => new Promise((resolve) => { - setTimeout(() => { - this._executeCalled.push(this._insertArray.length); - resolve(); - }, 10); - }); - this.updateOne = (object: { [x: string]: { [x: string]: any; }; }) => { - if (this._foundobj === null) { - this._insertArray.push(object['$set']); - } - for (let i = 0; i < this._insertArray.length; i++) { - if (JSON.stringify(this._insertArray[i]) === JSON.stringify(this._foundobj)) { - for (const field of Object.keys(object['$set'])) { - this._insertArray[i][field] = object['$set'][field]; - } - } - } - this._foundobj = null; - }; - } - - function MongoStub(this: any) { - this._bulkinsert = new (BulkInsert as any)(); - this.initializeUnorderedBulkOp = () => this._bulkinsert; - } - - it('test mongostub update new', async () => { - const bulkinsert = (new (MongoStub as any)()).initializeUnorderedBulkOp(); - bulkinsert.insert({ a: 1 }); - bulkinsert.insert({ b: 2 }); - return bulkinsert.execute().then(() => { - bulkinsert.find({ c: 3 }).upsert().updateOne({ $set: { c: 4 } }); - return bulkinsert.execute(); - }).then(() => { - expect(bulkinsert._insertArray).toEqual([{ a: 1 }, { b: 2 }, { c: 4 }]); - expect(bulkinsert._executeCalled).toEqual([2, 3]); - }); - }, 10000); - - it('test mongostub update existing', async () => { - const bulkinsert = (new (MongoStub as any)()).initializeUnorderedBulkOp(); - bulkinsert.insert({ a: 1 }); - bulkinsert.insert({ b: 2 }); - return bulkinsert.execute().then(() => { - bulkinsert.find({ b: 2 }).upsert().updateOne({ $set: { d: 4 } }); - return bulkinsert.execute(); - }).then(() => { - expect(bulkinsert._insertArray).toEqual([{ a: 1 }, { b: 2, d: 4 }]); - expect(bulkinsert._executeCalled).toEqual([2, 2]); - }); - }, 10000); - - it('jsoncurator uploads json file okay', async () => { - const readStream = fs.createReadStream(path.join(__dirname, '../testFiles/JSONCurator.json')); - const mongoStub = new (MongoStub as any)(); - const jobEntry = stub({ // subset of the IJobEntry interface - id: 'mockJobId', - studyId: 'mockStudyId' - }); - const jsoncurator = new JSONCurator( - mongoStub, - readStream, - jobEntry, - fieldsList - ); - const errors = await jsoncurator.processIncomingStreamAndUploadToMongo(); - expect(errors).toEqual([]); - expect(mongoStub._bulkinsert._insertArray).toHaveLength(2226); - expect(mongoStub._bulkinsert._executeCalled).toEqual([1000, 2000, 2226]); - expect(mongoStub._bulkinsert._insertArray[0]).toEqual({ - m_subjectId: 'I7N3G6G', - m_visitId: '1', - m_studyId: 'mockStudyId', - 31: '0', - 32: 'no description', - 33: 60.2, - deleted: null - }); - - }); - - it('jsoncurator catches non-esisting headers', async () => { - const readStream = fs.createReadStream(path.join(__dirname, '../testFiles/JSONCurator_error1.json')); - const mongoStub = new (MongoStub as any)(); - const jobEntry = stub({ // subset of the IJobEntry interface - id: 'mockJobId', - studyId: 'mockStudyId' - }); - const jsoncurator = new JSONCurator( - mongoStub, - readStream, - jobEntry, - fieldsList - ); - const errors = await jsoncurator.processIncomingStreamAndUploadToMongo(); - expect(errors).toEqual([ - 'Object 1 column 7: Unknown field.' - ]); - expect(mongoStub._bulkinsert._insertArray).toHaveLength(0); // nothing gets uploaded if errors are caught in header - expect(mongoStub._bulkinsert._executeCalled).toEqual([]); - - }); - - it('jsoncurator catches duplicate header', async () => { - const readStream = fs.createReadStream(path.join(__dirname, '../testFiles/JSONCurator_error2.json')); - const mongoStub = new (MongoStub as any)(); - const jobEntry = stub({ // subset of the IJobEntry interface - id: 'mockJobId', - studyId: 'mockStudyId' - }); - const jsoncurator = new JSONCurator( - mongoStub, - readStream, - jobEntry, - fieldsList - ); - const errors = await jsoncurator.processIncomingStreamAndUploadToMongo(); - expect(errors[0]).toBe('Object 1 column 6: Duplicate field.'); - expect(mongoStub._bulkinsert._insertArray).toHaveLength(0); - expect(mongoStub._bulkinsert._executeCalled).toEqual([]); - - }); - - it('jsoncurator catches uneven field before watermark', async () => { - const readStream = fs.createReadStream(path.join(__dirname, '../testFiles/JSONCurator_error3.json')); - const mongoStub = new (MongoStub as any)(); - const jobEntry = stub({ // subset of the IJobEntry interface - id: 'mockJobId', - studyId: 'mockStudyId' - }); - const jsoncurator = new JSONCurator( - mongoStub, - readStream, - jobEntry, - fieldsList - ); - - const errors = await jsoncurator.processIncomingStreamAndUploadToMongo(); - expect(errors).toEqual([ - 'Object 29: Uneven field Number; expected 6 fields but got 5', - 'Object 31: Uneven field Number; expected 6 fields but got 4' - ]); - expect(mongoStub._bulkinsert._insertArray).toHaveLength(26); - expect(mongoStub._bulkinsert._executeCalled).toEqual([]); - expect(mongoStub._bulkinsert._insertArray[0]).toEqual({ - m_subjectId: 'I7N3G6G', - m_visitId: '1', - m_studyId: 'mockStudyId', - 31: '0', - 32: 'no description', - 33: 60.2, - deleted: null - }); - - }); - - it('jsoncurator catches uneven field after watermark', async () => { - const readStream = fs.createReadStream(path.join(__dirname, '../testFiles/JSONCurator_error4.json')); - const mongoStub = new (MongoStub as any)(); - const jobEntry = stub({ // subset of the IJobEntry interface - id: 'mockJobId', - studyId: 'mockStudyId' - }); - const jsoncurator = new JSONCurator( - mongoStub, - readStream, - jobEntry, - fieldsList - ); - - const errors = await jsoncurator.processIncomingStreamAndUploadToMongo(); - expect(errors).toEqual([ - 'Object 1897: Uneven field Number; expected 6 fields but got 5', - 'Object 1899: Uneven field Number; expected 6 fields but got 4' - ]); - expect(mongoStub._bulkinsert._insertArray).toHaveLength(1894); - expect(mongoStub._bulkinsert._executeCalled).toEqual([1000]); - expect(mongoStub._bulkinsert._insertArray[0]).toEqual({ - m_subjectId: 'I7N3G6G', - m_visitId: '1', - m_studyId: 'mockStudyId', - 31: '0', - 32: 'no description', - 33: 60.2, - deleted: null - }); - - }); - - it('jsoncurator catches mixed errors', async () => { - const readStream = fs.createReadStream(path.join(__dirname, '../testFiles/JSONCurator_error5.json')); - const mongoStub = new (MongoStub as any)(); - const jobEntry = stub({ // subset of the IJobEntry interface - id: 'mockJobId', - studyId: 'mockStudyId' - }); - const jsoncurator = new JSONCurator( - mongoStub, - readStream, - jobEntry, - fieldsList - ); - - const errors = await jsoncurator.processIncomingStreamAndUploadToMongo(); - expect(errors).toEqual([ - 'Object 1 column 7: Unknown field.', - 'Object 4 column 4: Cannot parse \'a\' as categorical, value is illegal.', - 'Object 130: Uneven field Number; expected 7 fields but got 6', - 'Object 153: Uneven field Number; expected 7 fields but got 5', - 'Object 2003: Uneven field Number; expected 7 fields but got 6', - 'Object 2005: Uneven field Number; expected 7 fields but got 5' - ]); - expect(mongoStub._bulkinsert._insertArray).toHaveLength(0); - expect(mongoStub._bulkinsert._executeCalled).toEqual([]); - }); -}); diff --git a/packages/itmat-job-executor/tsconfig.app.json b/packages/itmat-job-executor/tsconfig.app.json index 411502e7b..d480fe1da 100644 --- a/packages/itmat-job-executor/tsconfig.app.json +++ b/packages/itmat-job-executor/tsconfig.app.json @@ -2,7 +2,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", + "module": "ES2022", "types": [ "node", "express", diff --git a/packages/itmat-job-executor/tsconfig.json b/packages/itmat-job-executor/tsconfig.json index 7f9a7d526..e02193374 100644 --- a/packages/itmat-job-executor/tsconfig.json +++ b/packages/itmat-job-executor/tsconfig.json @@ -1,5 +1,15 @@ { "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "ES2022", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noImplicitAny": false, + "noFallthroughCasesInSwitch": true + }, "files": [], "include": [], "references": [ diff --git a/packages/itmat-job-executor/tsconfig.spec.json b/packages/itmat-job-executor/tsconfig.spec.json index 5fde83bbc..0e0652379 100644 --- a/packages/itmat-job-executor/tsconfig.spec.json +++ b/packages/itmat-job-executor/tsconfig.spec.json @@ -2,7 +2,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", + "module": "ES2022", "types": [ "jest", "node", diff --git a/packages/itmat-job-executor/webpack.config.js b/packages/itmat-job-executor/webpack.config.cjs similarity index 100% rename from packages/itmat-job-executor/webpack.config.js rename to packages/itmat-job-executor/webpack.config.cjs diff --git a/packages/itmat-models/README.md b/packages/itmat-models/README.md deleted file mode 100644 index 64504a4e0..000000000 --- a/packages/itmat-models/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# itmat-models - -This library was generated with [Nx](https://nx.dev). - -## Building - -Run `nx build itmat-models` to build the library. - -## Running unit tests - -Run `nx test itmat-models` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/packages/itmat-models/src/graphql/appUsers.ts b/packages/itmat-models/src/graphql/appUsers.ts deleted file mode 100644 index 6d892affe..000000000 --- a/packages/itmat-models/src/graphql/appUsers.ts +++ /dev/null @@ -1,80 +0,0 @@ -import gql from 'graphql-tag'; -import { USER_FRAGMENT } from './user'; - -export const GET_USERS = gql` - query getUsers($fetchDetailsAdminOnly: Boolean!, $fetchAccessPrivileges: Boolean!, $userId: String) { - getUsers (userId: $userId) { - id - username @include (if: $fetchDetailsAdminOnly) - type - firstname - lastname - email @include (if: $fetchDetailsAdminOnly) - emailNotificationsActivated @include (if: $fetchDetailsAdminOnly) - emailNotificationsStatus @include (if: $fetchDetailsAdminOnly) - organisation - createdAt @include (if: $fetchDetailsAdminOnly) - expiredAt @include (if: $fetchDetailsAdminOnly) - description @include (if: $fetchDetailsAdminOnly) - access @include (if: $fetchAccessPrivileges) { - id - projects { - id - name - studyId - } - studies { - id - name - } - } - metadata @include (if: $fetchDetailsAdminOnly) - } - } -`; - -export const EDIT_USER = gql` - mutation EditUser( - $id: String! - $username: String - $type: USERTYPE - $firstname: String - $lastname: String - $email: String - $emailNotificationsActivated: Boolean - $emailNotificationsStatus: JSON - $password: String - $description: String - $organisation: String - $expiredAt: Float - $metadata: JSON - ) { - editUser(user: { - id: $id - username: $username - password: $password - firstname: $firstname - lastname: $lastname - emailNotificationsActivated: $emailNotificationsActivated - emailNotificationsStatus: $emailNotificationsStatus - email: $email - description: $description - organisation: $organisation - type: $type - expiredAt: $expiredAt - metadata: $metadata - }) { - ...ALL_FOR_USER - } - } - ${USER_FRAGMENT} -`; - -export const DELETE_USER = gql` - mutation DeleteUser($userId: String!) { - deleteUser(userId: $userId) { - id - successful - } - } -`; diff --git a/packages/itmat-models/src/graphql/curation.ts b/packages/itmat-models/src/graphql/curation.ts deleted file mode 100644 index 85d840ee9..000000000 --- a/packages/itmat-models/src/graphql/curation.ts +++ /dev/null @@ -1,45 +0,0 @@ -import gql from 'graphql-tag'; - -export const JOB_FRAGMENT = gql` - fragment ALL_FOR_JOB on Job { - id - studyId - projectId - jobType - requester - requestTime - receivedFiles - status - error - cancelled - cancelledTime - data - } -`; - -export const CREATE_DATA_CURATION_JOB = gql` - mutation createDataCurationJob($file: [String]!, $studyId: String!) { - createDataCurationJob(file: $file, studyId: $studyId) { - ...ALL_FOR_JOB - } - } - ${JOB_FRAGMENT} -`; - -export const CREATE_FIELD_CURATION_JOB = gql` - mutation createFieldCurationJob($file: String!, $studyId: String!, $tag: String!) { - createFieldCurationJob(file: $file, studyId: $studyId, tag: $tag) { - ...ALL_FOR_JOB - } - } - ${JOB_FRAGMENT} -`; - -export const CREATE_QUERY_CURATION_JOB = gql` - mutation createQueryCurationJob($queryId: [String], $studyId: String, $projectId: String) { - createQueryCurationJob(queryId: $queryId, studyId: $studyId, projectId: $projectId) { - ...ALL_FOR_JOB - } - } - ${JOB_FRAGMENT} -`; diff --git a/packages/itmat-models/src/graphql/fields.ts b/packages/itmat-models/src/graphql/fields.ts deleted file mode 100644 index 1285ca626..000000000 --- a/packages/itmat-models/src/graphql/fields.ts +++ /dev/null @@ -1,61 +0,0 @@ -import gql from 'graphql-tag'; - -export const FIELD_FRAGMENT = gql` - fragment ALL_FOR_FIELD on Field { - id - studyId - fieldId - fieldName - tableName - dataType - dataVersion - possibleValues { - id - code - description - } - metadata - unit - comments - dateAdded - dateDeleted - } -`; - -export const GET_STUDY_FIELDS = gql` - query getStudyFields($studyId: String!, $projectId: String, $versionId: String) { - getStudyFields(studyId: $studyId, projectId: $projectId, versionId: $versionId) { - ...ALL_FOR_FIELD - } - } - ${FIELD_FRAGMENT} -`; - -export const CREATE_NEW_FIELD = gql` - mutation createNewField($studyId: String!, $fieldInput: [FieldInput]!) { - createNewField(studyId: $studyId, fieldInput: $fieldInput) { - successful - id - code - description - } - } -`; - -export const EDIT_FIELD = gql` - mutation editField($studyId: String!, $fieldInput: FieldInput!) { - editField(studyId: $studyId, fieldInput: $fieldInput) { - ...ALL_FOR_FIELD - } - } - ${FIELD_FRAGMENT} -`; - -export const DELETE_FIELD = gql` - mutation deleteField($studyId: String!, $fieldId: String!) { - deleteField(studyId: $studyId, fieldId: $fieldId) { - ...ALL_FOR_FIELD - } - } - ${FIELD_FRAGMENT} -`; diff --git a/packages/itmat-models/src/graphql/files.ts b/packages/itmat-models/src/graphql/files.ts deleted file mode 100644 index 4d321b2e5..000000000 --- a/packages/itmat-models/src/graphql/files.ts +++ /dev/null @@ -1,27 +0,0 @@ -import gql from 'graphql-tag'; - -export const UPLOAD_FILE = gql` - mutation uploadFile($studyId: String!, $file: Upload!, $description: String!, $fileLength: BigInt, $hash: String) { - uploadFile(studyId: $studyId, description: $description, file: $file, fileLength: $fileLength, hash: $hash) { - id - uri - fileName - studyId - projectId - fileSize - description - uploadTime - uploadedBy - hash - metadata - } - } -`; - -export const DELETE_FILE = gql` - mutation deleteFile($fileId: String!) { - deleteFile(fileId: $fileId) { - successful - } - } -`; diff --git a/packages/itmat-models/src/graphql/index.ts b/packages/itmat-models/src/graphql/index.ts deleted file mode 100644 index d21de8c89..000000000 --- a/packages/itmat-models/src/graphql/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as appUsers from './appUsers'; -import * as organisation from './organisation'; -import * as pubkey from './pubkey'; -import * as curation from './curation'; -import * as fields from './fields'; -import * as files from './files'; -import * as log from './log'; -import * as permission from './permission'; -import * as projects from './projects'; -import * as query from './query'; -import * as study from './study'; -import * as user from './user'; -import * as subscription from './subscription'; -import * as standardization from './standardization'; -export * from './appUsers'; -export * from './organisation'; -export * from './pubkey'; -export * from './curation'; -export * from './fields'; -export * from './files'; -export * from './log'; -export * from './permission'; -export * from './projects'; -export * from './query'; -export * from './study'; -export * from './user'; -export * from './subscription'; -export * from './standardization'; -export const GQLRequests = { ...subscription, ...appUsers, ...organisation, ...pubkey, ...curation, ...fields, ...files, ...log, ...permission, ...projects, ...query, ...study, ...user, ...standardization }; diff --git a/packages/itmat-models/src/graphql/log.ts b/packages/itmat-models/src/graphql/log.ts deleted file mode 100644 index b967ce786..000000000 --- a/packages/itmat-models/src/graphql/log.ts +++ /dev/null @@ -1,30 +0,0 @@ -import gql from 'graphql-tag'; - -export const GET_LOGS = gql` - query getLogs( - $requesterName: String, - $requesterType: USERTYPE, - $logType: LOG_TYPE, - $actionType: LOG_ACTION, - $status: LOG_STATUS - ) { - getLogs ( - requesterName: $requesterName, - requesterType: $requesterType, - logType: $logType, - actionType: $actionType, - status: $status - ) { - id, - requesterName, - requesterType, - userAgent, - logType, - actionType, - actionData, - time, - status, - error - } - } -`; diff --git a/packages/itmat-models/src/graphql/organisation.ts b/packages/itmat-models/src/graphql/organisation.ts deleted file mode 100644 index 6f5c610d9..000000000 --- a/packages/itmat-models/src/graphql/organisation.ts +++ /dev/null @@ -1,44 +0,0 @@ -import gql from 'graphql-tag'; - -export const GET_ORGANISATIONS = gql` - query getOrganisations($organisationId: String) { - getOrganisations(organisationId: $organisationId) { - id - name - shortname - containOrg - deleted - metadata { - siteIDMarker - } - } - } -`; - -export const CREATE_ORGANISATION = gql` - mutation createOrganisation($name: String!, $shortname: String, $containOrg: String, $metadata: OrganisationMetadataInput) { - createOrganisation(name: $name, shortname: $shortname, containOrg: $containOrg, metadata: $metadata) { - id - name - shortname - containOrg - metadata { - siteIDMarker - } - } - } -`; - -export const DELETE_ORGANISATION = gql` - mutation deleteOrganisation($id: String!) { - deleteOrganisation(id: $id) { - id - name - shortname - containOrg - metadata { - siteIDMarker - } - } - } -`; diff --git a/packages/itmat-models/src/graphql/permission.ts b/packages/itmat-models/src/graphql/permission.ts deleted file mode 100644 index 658c6eb02..000000000 --- a/packages/itmat-models/src/graphql/permission.ts +++ /dev/null @@ -1,76 +0,0 @@ -import gql from 'graphql-tag'; - -export const GET_GRANTED_PERMISSIONS = gql` - query getGrantedPermissions($studyId: String, $projectId: String) { - getGrantedPermissions(studyId: $studyId, projectId: $projectId) { - studies { - studyId - permissions - } - projects { - projectId - permissions - } - } - } -`; - -export const EDIT_ROLE = gql` - mutation editRole( - $roleId: String!, - $name: String, - $description: String, - $permissionChanges: JSON, - $userChanges: StringArrayChangesInput - ) { - editRole(roleId: $roleId, name: $name, description: $description, permissionChanges: $permissionChanges, userChanges: $userChanges) { - id - name - studyId - projectId - description - permissions - users { - id - organisation - firstname - lastname - } - description - } - } -`; - -export const ADD_NEW_ROLE = gql` - mutation addRole( - $studyId: String! - $projectId: String, - $roleName: String! - ) { - addRole ( - studyId: $studyId, - projectId: $projectId, - roleName: $roleName, - ) { - id - name - permissions - studyId - projectId - users { - id - firstname - lastname - organisation - } - } - } -`; - -export const REMOVE_ROLE = gql` - mutation removeRole($roleId: String!) { - removeRole(roleId: $roleId) { - successful - } - } -`; diff --git a/packages/itmat-models/src/graphql/projects.ts b/packages/itmat-models/src/graphql/projects.ts deleted file mode 100644 index 30ee5d10f..000000000 --- a/packages/itmat-models/src/graphql/projects.ts +++ /dev/null @@ -1,65 +0,0 @@ -import gql from 'graphql-tag'; -import { JOB_FRAGMENT } from './curation'; -import { FIELD_FRAGMENT } from './fields'; - - -export const GET_PROJECT = gql` - query getProject($projectId: String!, $admin: Boolean!) { - getProject(projectId: $projectId) { - id - studyId - name - dataVersion { - id - version - contentId - tag - updateDate - } - summary - jobs { - ...ALL_FOR_JOB - } - roles @include(if: $admin) { - id - name - permissions - projectId - studyId - description - users { - id - firstname - lastname - organisation - username - } - } - iCanEdit - fields { - ...ALL_FOR_FIELD - } - files { - id - fileName - studyId - projectId - fileSize - description - uploadTime - uploadedBy - } - } - } - ${JOB_FRAGMENT} - ${FIELD_FRAGMENT} -`; - -export const GET_PROJECT_PATIENT_MAPPING = gql` - query getProject($projectId: String!) { - getProject(projectId: $projectId) { - id - patientMapping - } - } -`; diff --git a/packages/itmat-models/src/graphql/pubkey.ts b/packages/itmat-models/src/graphql/pubkey.ts deleted file mode 100644 index cc5d6c000..000000000 --- a/packages/itmat-models/src/graphql/pubkey.ts +++ /dev/null @@ -1,70 +0,0 @@ -import gql from 'graphql-tag'; - -export const GET_PUBKEYS = gql` - query getPubkeys($pubkeyId: String, $associatedUserId: String) { - getPubkeys(pubkeyId: $pubkeyId, associatedUserId: $associatedUserId) { - id - pubkey - associatedUserId - jwtPubkey - refreshCounter - deleted - } - } -`; - -export const REGISTER_PUBKEY = gql` - mutation registerPubkey($pubkey: String!, $signature: String!, $associatedUserId: String) { - registerPubkey(pubkey: $pubkey, signature: $signature, associatedUserId: $associatedUserId) { - id - pubkey - jwtPubkey - jwtSeckey - associatedUserId - } - } -`; - -export const LINK_USER_PUBKEY = gql` - mutation linkUserPubkey( - $pubkey: String! - $signature: String! - $associatedUserId: String! - $pubkeyId: String! - ) { - linkUserPubkey( - pubkeyId: $pubkeyId - pubkey: $pubkey - associatedUserId: $associatedUserId - signature: $signature - ) { - sucessful - } - } -`; - -export const ISSUE_ACCESS_TOKEN = gql` - mutation issueAccessToken($pubkey: String!, $signature: String!) { - issueAccessToken(pubkey: $pubkey, signature: $signature) { - accessToken - } - } -`; - -export const KEYPAIRGEN_SIGNATURE = gql` - mutation keyPairGenwSignature { - keyPairGenwSignature { - privateKey - publicKey - signature - } - } -`; - -export const RSA_SIGNER = gql` - mutation rsaSigner($privateKey: String!, $message: String) { - rsaSigner(privateKey: $privateKey, message: $message) { - signature - } - } -`; diff --git a/packages/itmat-models/src/graphql/query.ts b/packages/itmat-models/src/graphql/query.ts deleted file mode 100644 index a1b5ea0b2..000000000 --- a/packages/itmat-models/src/graphql/query.ts +++ /dev/null @@ -1,50 +0,0 @@ -import gql from 'graphql-tag'; - -export const GET_QUERY = gql` - query getQueries($studyId: String, $projectId: String) { - getQueries(studyId: $studyId, projectId: $projectId) { - id - queryString - studyId - projectId - requester - status - error - cancelled - cancelledTime - queryResult - data_requested - cohort - new_fields - } - } -`; - - -export const CREATE_QUERY = gql` - mutation createQuery($query: QueryObjInput!) { - createQuery(query: $query) { - id - queryString - studyId - projectId - requester - status - error - cancelled - cancelledTime - queryResult - data_requested - cohort - new_fields - } - } -`; - -export const GET_QUERY_BY_ID = gql` - query getQueryById($queryId: String!) { - getQueryById(queryId: $queryId) { - queryResult - } - } -`; diff --git a/packages/itmat-models/src/graphql/standardization.ts b/packages/itmat-models/src/graphql/standardization.ts deleted file mode 100644 index c9e216697..000000000 --- a/packages/itmat-models/src/graphql/standardization.ts +++ /dev/null @@ -1,59 +0,0 @@ -import gql from 'graphql-tag'; - -export const GET_STANDARDIZATION = gql` - query getStandardization($studyId: String, $projectId: String, $type: String, $versionId: String,) { - getStandardization(studyId: $studyId, projectId: $projectId, type: $type, versionId: $versionId) { - id - studyId - type - field - path - joinByKeys - uploadedAt - stdRules { - id - entry - source - parameter - filters - } - dataVersion - metadata - deleted - } - } -`; - -export const CREATE_STANDARDIZATION = gql` - mutation createStandardization($studyId: String!, $standardization: StandardizationInput!) { - createStandardization(studyId: $studyId, standardization: $standardization) { - id - studyId - type - field - path - joinByKeys - uploadedAt - stdRules { - id - entry - source - parameter - filters - } - metadata - deleted - } - } -`; - -export const DELETE_STANDARDIZATION = gql` - mutation deleteStandardization($studyId: String!, $field: [String]!, $type: String!) { - deleteStandardization(studyId: $studyId, field: $field, type: $type) { - id - successful - code - description - } - } -`; diff --git a/packages/itmat-models/src/graphql/study.ts b/packages/itmat-models/src/graphql/study.ts deleted file mode 100644 index 160b03820..000000000 --- a/packages/itmat-models/src/graphql/study.ts +++ /dev/null @@ -1,221 +0,0 @@ -import gql from 'graphql-tag'; -import { JOB_FRAGMENT } from './curation'; - -export const DELETE_STUDY = gql` - mutation deleteStudy($studyId: String!) { - deleteStudy(studyId: $studyId) { - id - successful - } - } -`; - -export const GET_STUDY = gql` - query getStudy($studyId: String!) { - getStudy(studyId: $studyId) { - id - name - createdBy - description - type - jobs { - ...ALL_FOR_JOB - } - projects { - id - studyId - name - } - roles { - id - name - permissions - projectId - studyId - description - users { - id - firstname - lastname - organisation - username - } - } - files { - id - fileName - studyId - projectId - fileSize - description - uploadTime - uploadedBy - hash - metadata - } - subjects - visits - numOfRecords - currentDataVersion - dataVersions { - id - version - tag - updateDate - contentId - } - } - } - ${JOB_FRAGMENT} -`; - -export const GET_DATA_RECORDS = gql` - query getDataRecords($studyId: String!, $queryString: JSON, $versionId: String, $projectId: String) { - getDataRecords(studyId: $studyId, queryString: $queryString, versionId: $versionId, projectId: $projectId) - } -`; - -export const GET_ONTOLOGY_TREE = gql` - query getOntologyTree($studyId: String!, $projectId: String, $treeName: String, $versionId: String) { - getOntologyTree(studyId: $studyId, projectId: $projectId, treeName: $treeName, versionId: $versionId) { - id - name - routes { - id - path - name - field - visitRange - } - metadata - } - } -`; - -export const CHECK_DATA_COMPLETE = gql` - query checkDataComplete($studyId: String!) { - checkDataComplete(studyId: $studyId) { - subjectId - visitId - fieldId - error - } - } -`; - -export const CREATE_STUDY = gql` - mutation createStudy($name: String!, $description: String, $type: STUDYTYPE!){ - createStudy(name: $name, description: $description, type: $type) { - id - name - description - type - } - } -`; - -export const EDIT_STUDY = gql` - mutation editStudy($studyId: String!, $description: String) { - editStudy(studyId: $studyId, description: $description) { - id - name - description - type - } - } -`; - -export const CREATE_NEW_DATA_VERSION = gql` - mutation createNewDataVersion($studyId: String!, $dataVersion: String!, $tag: String){ - createNewDataVersion(studyId: $studyId, dataVersion: $dataVersion, tag: $tag) { - id - version - tag - updateDate - contentId - } - } -`; - -export const UPLOAD_DATA_IN_ARRAY = gql` - mutation uploadDataInArray($studyId: String!, $data: [DataClip]) { - uploadDataInArray(studyId: $studyId, data: $data) { - successful - id - code - description - } - } -`; - -export const DELETE_DATA_RECORDS = gql` - mutation deleteDataRecords($studyId: String!, $subjectIds: [String], $visitIds: [String], $fieldIds: [String]) { - deleteDataRecords(studyId: $studyId, subjectIds: $subjectIds, visitIds: $visitIds, fieldIds: $fieldIds) { - successful - id - code - description - } - } -`; - -export const CREATE_ONTOLOGY_TREE = gql` - mutation createOntologyTree($studyId: String!, $ontologyTree: OntologyTreeInput!) { - createOntologyTree(studyId: $studyId, ontologyTree: $ontologyTree) { - id - name - routes { - id - path - name - field - visitRange - } - metadata - } - } -`; - -export const DELETE_ONTOLOGY_TREE = gql` - mutation deleteOntologyTree($studyId: String!, $treeName: String!) { - deleteOntologyTree(studyId: $studyId, treeName: $treeName) { - id - successful - } - } -`; - -export const CREATE_PROJECT = gql` - mutation createProject($studyId: String!, $projectName: String!) { - createProject(studyId: $studyId, projectName: $projectName) { - id - studyId - name - } - } -`; - -export const DELETE_PROJECT = gql` - mutation deleteProject($projectId: String!) { - deleteProject(projectId: $projectId) { - id - successful - } - } -`; - -export const SET_DATAVERSION_AS_CURRENT = gql` - mutation setDataversionAsCurrent($studyId: String!, $dataVersionId: String!) { - setDataversionAsCurrent(studyId: $studyId, dataVersionId: $dataVersionId) { - id - currentDataVersion - dataVersions { - id - version - tag - updateDate - contentId - } - } - } -`; diff --git a/packages/itmat-models/src/graphql/subscription.ts b/packages/itmat-models/src/graphql/subscription.ts deleted file mode 100644 index d70719f8a..000000000 --- a/packages/itmat-models/src/graphql/subscription.ts +++ /dev/null @@ -1,12 +0,0 @@ -import gql from 'graphql-tag'; - - -export const SUBSCRIBE_TO_JOB_STATUS = gql` - subscription subscribeToJobStatusChange($studyId: String!) { - subscribeToJobStatusChange(studyId: $studyId) { - jobId - newStatus - errors - } - } -`; diff --git a/packages/itmat-models/src/graphql/user.ts b/packages/itmat-models/src/graphql/user.ts deleted file mode 100644 index 7f8c023c4..000000000 --- a/packages/itmat-models/src/graphql/user.ts +++ /dev/null @@ -1,159 +0,0 @@ -import gql from 'graphql-tag'; - -export const USER_FRAGMENT = gql` - fragment ALL_FOR_USER on User { - id - username - type - firstname - lastname - email - organisation - description - emailNotificationsActivated - emailNotificationsStatus - access { - id - projects { - id - name - studyId - } - studies { - id - name - type - } - }, - metadata, - createdAt, - expiredAt - } -`; - -export const LOGIN = gql` -mutation login($username: String!, $password: String!, $totp: String!, $requestexpirydate: Boolean) { - login(username: $username, password: $password, totp: $totp, requestexpirydate: $requestexpirydate) { - ...ALL_FOR_USER - } -} -${USER_FRAGMENT} -`; - -export const WHO_AM_I = gql` - query { - whoAmI { - ...ALL_FOR_USER - } - } - ${USER_FRAGMENT} -`; - -export const LOGOUT = gql` - mutation { - logout{ - successful - id - } - } -`; - -export const REQUEST_USERNAME_OR_RESET_PASSWORD = gql` - mutation requestUsernameOrResetPassword( - $forgotUsername: Boolean!, - $forgotPassword: Boolean!, - $email: String, - $username: String - ) { - requestUsernameOrResetPassword( - forgotUsername: $forgotUsername, - forgotPassword: $forgotPassword, - email: $email, - username: $username - ) { - successful - } - } -`; - -export const RESET_PASSWORD = gql` - mutation resetPassword( - $encryptedEmail: String!, - $token: String!, - $newPassword: String! - ) { - resetPassword( - encryptedEmail: $encryptedEmail, - token: $token, - newPassword: $newPassword - ) { - successful - } - } -`; - -export const VALIDATE_RESET_PASSWORD = gql` - query validateResetPassword( - $encryptedEmail: String!, - $token: String! - ) { - validateResetPassword( - encryptedEmail: $encryptedEmail, - token: $token - ) { - successful - } - } -`; - -export const CREATE_USER = gql` - mutation CreateUser( - $username: String! - $password: String! - $firstname: String! - $lastname: String! - $description: String - $organisation: String! - $emailNotificationsActivated: Boolean - $email: String! - $type: USERTYPE - $metadata: JSON - ){ - createUser(user: { - username: $username - password: $password - firstname: $firstname - lastname: $lastname - description: $description - organisation: $organisation - emailNotificationsActivated: $emailNotificationsActivated - email: $email - type: $type - metadata: $metadata - }) { - successful - } - } -`; - -export const RECOVER_SESSION_EXPIRE_TIME = gql` - query recoverSessionExpireTime { - recoverSessionExpireTime { - successful - } - } -`; - -export const REQUEST_EXPIRY_DATE = gql` - mutation requestExpiryDate( - $username: String, - $email: String - ) { - requestExpiryDate( - username: $username, - email: $email - ) { - successful - } - } -`; diff --git a/packages/itmat-models/src/index.ts b/packages/itmat-models/src/index.ts deleted file mode 100644 index 104d0485d..000000000 --- a/packages/itmat-models/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './graphql'; \ No newline at end of file diff --git a/packages/itmat-setup/.babelrc b/packages/itmat-setup/.babelrc index 171d92f23..b34c8a529 100644 --- a/packages/itmat-setup/.babelrc +++ b/packages/itmat-setup/.babelrc @@ -7,4 +7,4 @@ } ] ] -} \ No newline at end of file +} diff --git a/packages/itmat-setup/README.md b/packages/itmat-setup/README.md index f2c25f67f..c80c2231b 100644 --- a/packages/itmat-setup/README.md +++ b/packages/itmat-setup/README.md @@ -2,13 +2,10 @@ This library was generated with [Nx](https://nx.dev). - ## Running unit tests Run `nx test itmat-setup` to execute the unit tests via [Jest](https://jestjs.io). - ## Running lint Run `nx lint itmat-setup` to execute the lint via [ESLint](https://eslint.org/). - diff --git a/packages/itmat-setup/jest.config.ts b/packages/itmat-setup/jest.config.ts index 920434922..ada68558a 100644 --- a/packages/itmat-setup/jest.config.ts +++ b/packages/itmat-setup/jest.config.ts @@ -1,7 +1,7 @@ /* eslint-disable */ export default { displayName: 'itmat-setup', - preset: '../../jest.preset.js', + preset: '../../jest.preset.cjs', globals: { 'ts-jest': { tsconfig: '/tsconfig.spec.json', diff --git a/packages/itmat-setup/project.json b/packages/itmat-setup/project.json index fd6171efa..65eb45bc5 100644 --- a/packages/itmat-setup/project.json +++ b/packages/itmat-setup/project.json @@ -3,6 +3,7 @@ "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "packages/itmat-setup/src", "projectType": "library", + "tags": [], "targets": { "build": { "executor": "@nx/js:tsc", @@ -45,6 +46,5 @@ "command": "node dist/packages/itmat-setup/src/runSetup.js" } } - }, - "tags": [] + } } \ No newline at end of file diff --git a/packages/itmat-setup/src/databaseSetup/collectionsAndIndexes.ts b/packages/itmat-setup/src/databaseSetup/collectionsAndIndexes.ts index eba685d33..9d287245e 100644 --- a/packages/itmat-setup/src/databaseSetup/collectionsAndIndexes.ts +++ b/packages/itmat-setup/src/databaseSetup/collectionsAndIndexes.ts @@ -2,6 +2,7 @@ import * as mongo from 'mongodb'; import { v4 as uuid } from 'uuid'; import { seedUsers } from './seed/users'; import { seedOrganisations } from './seed/organisations'; +import { seedConfigs } from './seed/config'; const collections = { jobs_collection: { name: 'JOB_COLLECTION', @@ -13,22 +14,15 @@ const collections = { name: 'USER_COLLECTION', indexes: [ { key: { id: 1 }, unique: true }, - { key: { username: 1, deleted: 1 }, unique: true }, - { key: { email: 1, deleted: 1 }, unique: true } + { key: { 'username': 1, 'life.deletedTime': 1 }, unique: true }, + { key: { 'email': 1, 'life.deletedTime': 1 }, unique: true } ] }, studies_collection: { name: 'STUDY_COLLECTION', indexes: [ { key: { id: 1 }, unique: true }, - { key: { name: 1, deleted: 1 }, unique: true } - ] - }, - projects_collection: { - name: 'PROJECT_COLLECTION', - indexes: [ - { key: { id: 1 }, unique: true }, - { key: { name: 1, studyId: 1, deleted: 1 }, unique: true } + { key: { 'name': 1, 'life.deletedTime': 1 }, unique: true } ] }, queries_collection: { @@ -47,20 +41,20 @@ const collections = { name: 'DATA_COLLECTION', indexes: [ { key: { id: 1 }, unique: true }, - { key: { m_studyId: 1, m_versionId: 1, m_subjectId: 1, m_visitId: 1, m_fieldId: 1, uploadedAt: -1 }, unique: true } + { key: { 'studyId': 1, 'fieldId': 1, 'life.createdTime': 1, 'life.deletedTime': 1, 'properties': 1 }, unique: true } ] }, roles_collection: { name: 'ROLE_COLLECTION', indexes: [ - { key: { id: 1 }, unique: true }, - { key: { name: 1, studyId: 1, projectId: 1, deleted: 1 }, unique: true } + { key: { id: 1 }, unique: true } ] }, field_dictionary_collection: { name: 'FIELD_COLLECTION', indexes: [ - { key: { id: 1 }, unique: true } + { key: { id: 1 }, unique: true }, + { key: { 'studyId': 1, 'fieldId': 1, 'life.createdTime': 1, 'life.deletedTime': 1, 'properties': 1, 'dataVersion': 1 }, unique: true } ] }, files_collection: { @@ -94,14 +88,74 @@ const collections = { indexes: [ { key: { id: 1 }, unique: true } ] + }, + configs_collection: { + name: 'CONFIG_COLLECTION', + indexes: [ + { key: { id: 1 }, unique: true }, + { key: { type: 1, key: 1 }, unique: true } + ] + }, + ontologies_collection: { + name: 'ONTOLOGY_COLLECTION', + indexes: [ + { key: { id: 1 }, unique: true } + ] + }, + drives_collection: { + name: 'DRIVE_COLLECTION', + indexes: [ + { key: { id: 1 }, unique: true } + ] + }, + colddata_collection: { + name: 'COLDDATA_COLLECTION', + indexes: [ + { key: { id: 1 }, unique: true }, + { key: { 'studyId': 1, 'fieldId': 1, 'life.createdTime': 1, 'life.deletedTime': 1, 'properties': 1 }, unique: true } + ] + }, + cache_collection: { + name: 'CACHE_COLLECTION', + indexes: [ + { key: { id: 1 }, unique: true }, + { key: { 'keyHash': 1, 'type': 1, 'life.createdTime': 1 }, unique: true } + ] + }, + domains_collection: { + name: 'DOMAIN_COLLECTION', + indexes: [ + { key: { id: 1 }, unique: true }, + { key: { domainPath: 1 }, unique: true } + ] + }, + docs_collection: { + name: 'DOC_COLLECTION', + indexes: [ + { key: { id: 1 }, unique: true } + ] + }, + webauthn_collection: { + name: 'WEBAUTHN_COLLECTION', + indexes: [ + { key: { id: 1 }, unique: true }, + { key: { userId: 1 }, unique: false } + ] + }, + instance_collection: { + name: 'INSTANCE_COLLECTION', + indexes: [ + { key: { id: 1 }, unique: true }, + { key: { userId: 1 }, unique: false } + ] } }; export async function setupDatabase(mongostr: string, databaseName: string): Promise { + console.log('Setting up database'); const conn = await mongo.MongoClient.connect(mongostr); const db = conn.db(databaseName); const existingCollections = (await db.listCollections({}).toArray()).map((el) => el.name); - /* creating collections and indexes */ for (const each of Object.keys(collections) as Array) { if (existingCollections.includes(collections[each].name)) { @@ -122,5 +176,8 @@ export async function setupDatabase(mongostr: string, databaseName: string): Pro /* insert seed organisations */ await db.collection(collections.organisations_collection.name).insertMany(seedOrganisations); + /* Insert necessary configs */ + await db.collection(collections.configs_collection.name).insertMany(seedConfigs); + await conn.close(); } diff --git a/packages/itmat-setup/src/databaseSetup/seed/config.ts b/packages/itmat-setup/src/databaseSetup/seed/config.ts new file mode 100644 index 000000000..c384c7d74 --- /dev/null +++ b/packages/itmat-setup/src/databaseSetup/seed/config.ts @@ -0,0 +1,169 @@ +enum enumInstanceType { + SMALL = 'small', + MEDIUM ='medium', + LARGE = 'large' +} + +export const seedConfigs = [{ + id: 'root_admin_user_config_protected', + type: 'USERCONFIG', + key: 'replaced_at_runtime2', + properties: { + id: 'root_admin_user_config', + life: { + createdTime: Date.now(), + createdUser: 'SYSTEM', + deletedTime: null, + deletedUser: null + }, + metadata: {}, + defaultUserExpiredDays: 90, + defaultMaximumFileSize: 100 * 1024 * 1024, // 100 MB + defaultMaximumFileRepoSize: 500 * 1024 * 1024, // 500 MB + defaultMaximumRepoSize: 10 * 1024 * 1024 * 1024, // 10GB + defaultFileBucketId: 'user', + defaultMaximumQPS: 500, + + // LXD instances + defaultLXDflavor: [enumInstanceType.SMALL, enumInstanceType.MEDIUM], + defaultLXDMaximumInstances: 3, // number + defaultLXDMaximumInstanceCPUCores: 3 * 8, // number + defaultLXDMaximumInstanceDiskSize: 3 * 40 * 1024 * 1024 * 1024, + defaultLXDMaximumInstanceMemory: 3 * 32 * 1024 * 1024 * 1024, + // set to 360 hours + defaultLXDMaximumInstanceLife: 360 * 60 * 60 * 1000 + } +}, { + id: 'root_standard_user_config_protected', + type: 'USERCONFIG', + key: 'replaced_at_runtime1', + properties: { + id: 'root_standard_user_config', + life: { + createdTime: Date.now(), + createdUser: 'SYSTEM', + deletedTime: null, + deletedUser: null + }, + metadata: {}, + defaultUserExpiredDays: 90, + defaultMaximumFileSize: 100 * 1024 * 1024, // 100 MB + defaultMaximumFileRepoSize: 500 * 1024 * 1024, // 500 MB + defaultMaximumRepoSize: 10 * 1024 * 1024 * 1024, // 10GB + defaultFileBucketId: 'user', + defaultMaximumQPS: 500, + + // LXD instances + defaultLXDflavor: [enumInstanceType.SMALL, enumInstanceType.MEDIUM], + defaultLXDMaximumInstances: 3, // number + defaultLXDMaximumInstanceCPUCores: 3 * 8, // number + defaultLXDMaximumInstanceDiskSize: 3 * 40 * 1024 * 1024 * 1024, + defaultLXDMaximumInstanceMemory: 3 * 16 * 1024 * 1024 * 1024, + // set to 360 hours + defaultLXDMaximumInstanceLife: 360 * 60 * 60 * 1000 + } +}, { + id: 'root_system_config_protected', + type: 'SYSTEMCONFIG', + key: null, + properties: { + id: 'root_system_config', + life: { + createdTime: Date.now(), + createdUser: 'SYSTEM', + deletedTime: null, + deletedUser: null + }, + metadata: {}, + defaultBackgroundColor: '#FFFFFF', + defaultMaximumFileSize: 1 * 1024 * 1024 * 1024, // 1 GB + defaultFileBucketId: 'system', + defaultProfileFileBucketId: 'profile', + defaultProfileBucketId: 'profile', + logoLink: null, + logoSize: ['24px', '24px'], + archiveAddress: '', + defaultEventTimeConsumptionBar: [50, 100], + defaultUserExpireDays: 90, + defaultLXDFlavor: { + [enumInstanceType.SMALL]: { cpuLimit: 4, memoryLimit: 16 * 1024 * 1024 * 1024, diskLimit: 20 * 1024 * 1024 * 1024 }, + [enumInstanceType.MEDIUM]: { cpuLimit: 8, memoryLimit: 32 * 1024 * 1024 * 1024, diskLimit: 40 * 1024 * 1024 * 1024 }, + [enumInstanceType.LARGE]: { cpuLimit: 16, memoryLimit: 64 * 1024 * 1024 * 1024, diskLimit: 60 * 1024 * 1024 * 1024 } + } + }, + life: { + createdTime: Date.now(), + createdUser: 'SYSTEM', + deletedTime: null, + deletedUser: null + }, + metadata: {} +}, { + id: 'root_doc_config_protected', + type: 'DOCCONFIG', + key: null, + properties: { + id: 'root_doc_config', + life: { + createdTime: Date.now(), + createdUser: 'SYSTEM', + deletedTime: null, + deletedUser: null + }, + metadata: {}, + defaultFileBucketId: 'doc', + defaultMaximumFileSize: 100 * 1024 * 1024 // 100 MB + }, + life: { + createdTime: Date.now(), + createdUser: 'SYSTEM', + deletedTime: null, + deletedUser: null + }, + metadata: {} +}, { + id: 'root_cache_config_protected', + type: 'CACHECONFIG', + key: null, + properties: { + id: 'root_cache_config', + life: { + createdTime: Date.now(), + createdUser: 'SYSTEM', + deletedTime: null, + deletedUser: null + }, + metadata: {}, + defaultFileBucketId: 'cache', + defaultMaximumFileSize: 100 * 1024 * 1024 // 100 MB + }, + life: { + createdTime: Date.now(), + createdUser: 'SYSTEM', + deletedTime: null, + deletedUser: null + }, + metadata: {} +}, { + id: 'root_domain_config_protected', + type: 'DOMAINCONFIG', + key: null, + properties: { + id: 'root_domain_config', + life: { + createdTime: Date.now(), + createdUser: 'SYSTEM', + deletedTime: null, + deletedUser: null + }, + metadata: {}, + defaultFileBucketId: 'domain' + }, + life: { + createdTime: Date.now(), + createdUser: 'SYSTEM', + deletedTime: null, + deletedUser: null + }, + metadata: {} +}]; \ No newline at end of file diff --git a/packages/itmat-setup/src/databaseSetup/seed/organisations.ts b/packages/itmat-setup/src/databaseSetup/seed/organisations.ts index 71562762c..2a103a83d 100644 --- a/packages/itmat-setup/src/databaseSetup/seed/organisations.ts +++ b/packages/itmat-setup/src/databaseSetup/seed/organisations.ts @@ -2,15 +2,26 @@ export const seedOrganisations = [{ id: 'organisation_system', name: 'System', shortname: 'Sys', - containOrg: '', - deleted: null + life: { + createdTime: 1591134065000, + createdUser: 'admin', + deletedTime: null, + deletedUser: null + }, + metadata: { + siteIDMarker: 'I' + } }, { id: 'organisation_user', name: 'user', shortname: 'Use', - containOrg: '', - metadata: { - siteIDMarker: 'I' + life: { + createdTime: 1591134065000, + createdUser: 'admin', + deletedTime: null, + deletedUser: null }, - deleted: null + metadata: { + siteIDMarker: 'Z' + } }]; diff --git a/packages/itmat-setup/src/databaseSetup/seed/users.ts b/packages/itmat-setup/src/databaseSetup/seed/users.ts index 25f7d5b13..f3f1286fa 100644 --- a/packages/itmat-setup/src/databaseSetup/seed/users.ts +++ b/packages/itmat-setup/src/databaseSetup/seed/users.ts @@ -11,10 +11,15 @@ export const seedUsers = [{ description: 'I am an admin user.', emailNotificationsActivated: true, emailNotificationsStatus: { expiringNotification: false }, - deleted: null, id: 'replaced_at_runtime2', - createdAt: 1591134065000, - expiredAt: 1991134065000 + expiredAt: 1991134065000, + life: { + createdTime: 1591134065000, + createdUser: 'admin', + deletedTime: null, + deletedUser: null + }, + metadata: {} }, { username: 'standardUser', @@ -29,8 +34,14 @@ export const seedUsers = [{ emailNotificationsActivated: true, emailNotificationsStatus: { expiringNotification: false }, organisation: 'organisation_system', - deleted: null, id: 'replaced_at_runtime1', createdAt: 1591134065000, - expiredAt: 1991134065000 + expiredAt: 1991134065000, + life: { + createdTime: 1591134065000, + createdUser: 'admin', + deletedTime: null, + deletedUser: null + }, + metadata: {} }]; diff --git a/packages/itmat-setup/src/runSetup.ts b/packages/itmat-setup/src/runSetup.ts index 2fd56703d..6b859e543 100644 --- a/packages/itmat-setup/src/runSetup.ts +++ b/packages/itmat-setup/src/runSetup.ts @@ -6,7 +6,7 @@ let config; try { const content = fs.readFileSync('./config.json', 'utf-8'); config = JSON.parse(content); -} catch (e) { +} catch (__unused__exception) { console.error(chalk.red('Cannot parse configuration file. Using default configuration.')); config = { database: { diff --git a/packages/itmat-setup/tsconfig.json b/packages/itmat-setup/tsconfig.json index bc0dc3f79..e7911c37f 100644 --- a/packages/itmat-setup/tsconfig.json +++ b/packages/itmat-setup/tsconfig.json @@ -13,6 +13,7 @@ "compilerOptions": { "forceConsistentCasingInFileNames": true, "strict": true, + "noImplicitAny": false, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true } diff --git a/packages/itmat-setup/tsconfig.lib.json b/packages/itmat-setup/tsconfig.lib.json index 9de746f94..d4f7225a9 100644 --- a/packages/itmat-setup/tsconfig.lib.json +++ b/packages/itmat-setup/tsconfig.lib.json @@ -1,7 +1,7 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "module": "commonjs", + "module": "ES2022", "outDir": "../../dist/out-tsc", "declaration": true, "types": [ diff --git a/packages/itmat-setup/tsconfig.spec.json b/packages/itmat-setup/tsconfig.spec.json index b004638c1..46f886035 100644 --- a/packages/itmat-setup/tsconfig.spec.json +++ b/packages/itmat-setup/tsconfig.spec.json @@ -2,7 +2,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", + "module": "ES2022", "types": [ "jest", "node" diff --git a/packages/itmat-types/.lib.swcrc b/packages/itmat-types/.lib.swcrc index 5442653a0..0b7862885 100644 --- a/packages/itmat-types/.lib.swcrc +++ b/packages/itmat-types/.lib.swcrc @@ -1,6 +1,6 @@ { "jsc": { - "target": "es2017", + "target": "ES2022", "parser": { "syntax": "typescript", "decorators": true, @@ -28,4 +28,4 @@ "./**/jest-setup.ts$", ".*.js$" ] -} \ No newline at end of file +} diff --git a/packages/itmat-types/jest.config.ts b/packages/itmat-types/jest.config.ts index 0349860f8..d8e464cba 100644 --- a/packages/itmat-types/jest.config.ts +++ b/packages/itmat-types/jest.config.ts @@ -8,7 +8,7 @@ const { exclude: _, ...swcJestConfig } = JSON.parse( ); export default { displayName: 'itmat-types', - preset: '../../jest.preset.js', + preset: '../../jest.preset.cjs', transform: { '^.+\\.[tj]s$': ['@swc/jest', swcJestConfig], }, diff --git a/packages/itmat-types/project.json b/packages/itmat-types/project.json index 3ef1e4c08..e8a60a991 100644 --- a/packages/itmat-types/project.json +++ b/packages/itmat-types/project.json @@ -3,6 +3,7 @@ "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "packages/itmat-types/src", "projectType": "library", + "tags": [], "targets": { "build": { "executor": "@nx/esbuild:esbuild", @@ -16,7 +17,6 @@ "assets": [ "packages/itmat-types/*.md" ], - "generatePackageJson": true, "thirdParty": true } }, @@ -35,6 +35,5 @@ "jestConfig": "packages/itmat-types/jest.config.ts" } } - }, - "tags": [] + } } \ No newline at end of file diff --git a/packages/itmat-types/src/types/base.ts b/packages/itmat-types/src/types/base.ts new file mode 100644 index 000000000..8351692f9 --- /dev/null +++ b/packages/itmat-types/src/types/base.ts @@ -0,0 +1,12 @@ +export interface ILifeCircle { + createdTime: number; + createdUser: string; + deletedTime: number | null; + deletedUser: string | null; +} + +export interface IBase { + id: string; + life: ILifeCircle; + metadata: Record; +} \ No newline at end of file diff --git a/packages/itmat-types/src/types/cache.ts b/packages/itmat-types/src/types/cache.ts new file mode 100644 index 000000000..7fcd9a551 --- /dev/null +++ b/packages/itmat-types/src/types/cache.ts @@ -0,0 +1,19 @@ +import { IBase } from './base'; + +/** We store large data as json in minio as a cache. */ +export interface ICache extends IBase { + keyHash: string; + keys: Record, + uri: string; + status: enumCacheStatus; + type: enumCacheType +} + +export enum enumCacheType { + API = 'API', +} + +export enum enumCacheStatus { + OUTDATED = 'OUTDATED', + INUSE = 'INUSE' +} \ No newline at end of file diff --git a/packages/itmat-types/src/types/common.ts b/packages/itmat-types/src/types/common.ts new file mode 100644 index 000000000..d7a8f3a52 --- /dev/null +++ b/packages/itmat-types/src/types/common.ts @@ -0,0 +1,10 @@ +export interface IGenericResponse { + successful: boolean; + id?: string; + code?: string; + description?: string; +} + +export enum enumReservedKeys { + STUDY_LEVEL_DATA = 'reserved_study_level_data' +} \ No newline at end of file diff --git a/packages/itmat-types/src/types/config.ts b/packages/itmat-types/src/types/config.ts new file mode 100644 index 000000000..6813f60b5 --- /dev/null +++ b/packages/itmat-types/src/types/config.ts @@ -0,0 +1,241 @@ +import { IBase, ILifeCircle } from './base'; +import { v4 as uuid } from 'uuid'; +import { enumReservedUsers } from './user'; +import { IJobSchedulerConfig, enumJobSchedulerStrategy } from './job'; +import { enumInstanceType } from './instance'; + +export interface IConfig extends IBase { + type: enumConfigType; + key: string | null; // studyid for study; userid for user; null for system + properties: ISystemConfig | IStudyConfig | IUserConfig | IDocConfig | ICacheConfig | IDomainConfig; +} + +export enum enumConfigType { + SYSTEMCONFIG = 'SYSTEMCONFIG', + STUDYCONFIG = 'STUDYCONFIG', + USERCONFIG = 'USERCONFIG', + DOCCONFIG = 'DOCCONFIG', + CACHECONFIG = 'CACHECONFIG', + DOMAINCONFIG = 'DOMAINCONFIG' +} + +export interface ISystemConfig extends IBase { + defaultBackgroundColor: string; // hex code + defaultMaximumFileSize: number; + defaultFileBucketId: string; + defaultProfileBucketId: string; + logoLink: string | null; // TODO: fetch file from database; + logoSize: string[]; // width * height + archiveAddress: string; + defaultEventTimeConsumptionBar: number[]; + defaultUserExpireDays: number; + jobSchedulerConfig: IJobSchedulerConfig; + defaultLXDFlavor: { + [key in enumInstanceType]: { + cpuLimit: number; + memoryLimit: number; + diskLimit: number; + } + }; +} + +export enum enumStudyBlockColumnValueType { + STRING = 'STRING', + TIME = 'TIME' +} + +export interface IStudyFileBlock { + title: string; + fieldIds: string[]; + defaultFileColumns: Array<{ title: string, property: string, type: enumStudyBlockColumnValueType }>; + defaultFileColumnsPropertyColor?: string; +} + +export interface IStudyConfig extends IBase { + defaultStudyProfile: string | null; + defaultMaximumFileSize: number; + defaultRepresentationForMissingValue: string; + defaultFileBlocks: IStudyFileBlock[]; + defaultVersioningKeys: string[]; // data clips with same values of such keys will be considered as the same values with different versions +} + +export interface IUserConfig extends IBase { + defaultUserExpiredDays: number + defaultMaximumFileSize: number; + defaultMaximumFileRepoSize: number; + defaultMaximumRepoSize: number; + defaultFileBucketId: string; + defaultMaximumQPS: number; + + // LXD instances + defaultLXDMaximumInstances: number; + defaultLXDMaximumInstanceCPUCores: number; + defaultLXDMaximumInstanceDiskSize: number; + defaultLXDMaximumInstanceMemory: number; + defaultLXDMaximumInstanceLife: number; + defaultLXDflavor: enumInstanceType[]; // e.g., ['small', 'medium'] +} + +export interface IOrganisationConfig extends IBase { + defaultFileBucketId: string; +} + +export interface ICacheConfig extends IBase { + defaultFileBucketId: string; + defaultMaximumFileSize: number; +} + +export interface IDocConfig extends IBase { + defaultFileBucketId: string; + defaultMaximumFileSize: number; +} + +export interface IDomainConfig extends IBase { + defaultMaximumFileSize: number + defaultFileBucketId: string +} +export interface IDefaultSettings extends IBase { + systemConfig: ISystemConfig; + studyConfig: IStudyConfig; + userConfig: IUserConfig; + docConfig: IDocConfig; +} + +// default settings +export class DefaultSettings implements IDefaultSettings { + public readonly id: string = uuid(); + public readonly life: ILifeCircle = { + createdTime: Date.now(), + createdUser: enumReservedUsers.SYSTEM, + deletedTime: null, + deletedUser: null + }; + public readonly metadata: Record = {}; + + public readonly systemConfig: ISystemConfig = { + id: uuid(), + life: { + createdTime: Date.now(), + createdUser: enumReservedUsers.SYSTEM, + deletedTime: null, + deletedUser: null + }, + metadata: {}, + defaultBackgroundColor: '#FFFFFF', + defaultMaximumFileSize: 1 * 1024 * 1024 * 1024, // 1 GB + defaultFileBucketId: 'system', + defaultProfileBucketId: 'profile', + logoLink: null, + logoSize: ['24px', '24px'], + archiveAddress: '', + defaultEventTimeConsumptionBar: [50, 100], + defaultUserExpireDays: 90, + jobSchedulerConfig: { + strategy: enumJobSchedulerStrategy.FIFO, + usePriority: true, + // for errored jobs + reExecuteFailedJobs: true, + failedJobDelayTime: 10 * 60 * 1000, // unit timestamps + maxAttempts: 10 // the number of attempts should be stored in history + }, + defaultLXDFlavor: { + [enumInstanceType.SMALL]: { cpuLimit: 4, memoryLimit: 16 * 1024 * 1024 * 1024, diskLimit: 40 * 1024 * 1024 * 1024 }, + [enumInstanceType.MEDIUM]: { cpuLimit: 8, memoryLimit: 32 * 1024 * 1024 * 1024, diskLimit: 60 * 1024 * 1024 * 1024 }, + [enumInstanceType.LARGE]: { cpuLimit: 16, memoryLimit: 64 * 1024 * 1024 * 1024, diskLimit: 80 * 1024 * 1024 * 1024 } + } + }; + + public readonly studyConfig: IStudyConfig = { + id: uuid(), + life: { + createdTime: Date.now(), + createdUser: enumReservedUsers.SYSTEM, + deletedTime: null, + deletedUser: null + }, + metadata: {}, + defaultStudyProfile: null, + defaultMaximumFileSize: 8 * 1024 * 1024 * 1024, // 8 GB, + defaultRepresentationForMissingValue: '99999', + defaultFileBlocks: [], + defaultVersioningKeys: [] + }; + + public readonly userConfig: IUserConfig = { + id: uuid(), + life: { + createdTime: Date.now(), + createdUser: enumReservedUsers.SYSTEM, + deletedTime: null, + deletedUser: null + }, + metadata: {}, + defaultUserExpiredDays: 90, + defaultMaximumFileSize: 100 * 1024 * 1024, // 100 MB + defaultMaximumFileRepoSize: 500 * 1024 * 1024, // 500 MB + defaultMaximumRepoSize: 10 * 1024 * 1024 * 1024, // 10GB + defaultFileBucketId: 'user', + defaultMaximumQPS: 500, + // LXD instances + defaultLXDflavor: [enumInstanceType.SMALL, enumInstanceType.MEDIUM], + defaultLXDMaximumInstances: 3, // number + defaultLXDMaximumInstanceCPUCores: 3 * 8, // number + defaultLXDMaximumInstanceDiskSize: 3 * 40 * 1024 * 1024 * 1024, + defaultLXDMaximumInstanceMemory: 3 * 16 * 1024 * 1024 * 1024, + // set to 360 hours + defaultLXDMaximumInstanceLife: 360 * 60 * 60 * 1000 // set to suitable hours + }; + + public readonly docConfig: IDocConfig = { + id: uuid(), + life: { + createdTime: Date.now(), + createdUser: enumReservedUsers.SYSTEM, + deletedTime: null, + deletedUser: null + }, + metadata: {}, + defaultFileBucketId: 'doc', + defaultMaximumFileSize: 100 * 1024 * 1024 // 100 MB + }; + + public readonly organisationConfig: IOrganisationConfig = { + id: uuid(), + life: { + createdTime: Date.now(), + createdUser: enumReservedUsers.SYSTEM, + deletedTime: null, + deletedUser: null + }, + metadata: {}, + defaultFileBucketId: 'organisation' + }; + + public readonly cacheConfig: ICacheConfig = { + id: uuid(), + life: { + createdTime: Date.now(), + createdUser: enumReservedUsers.SYSTEM, + deletedTime: null, + deletedUser: null + }, + metadata: {}, + defaultFileBucketId: 'cache', + defaultMaximumFileSize: 100 * 1024 * 1024 // 100 MB + }; + + public readonly domainConfig: IDomainConfig = { + id: uuid(), + life: { + createdTime: Date.now(), + createdUser: enumReservedUsers.SYSTEM, + deletedTime: null, + deletedUser: null + }, + metadata: {}, + defaultFileBucketId: 'domain', + defaultMaximumFileSize: 10 * 1024 * 1024 // 100 MB + }; +} + +export const defaultSettings = Object.freeze(new DefaultSettings()); diff --git a/packages/itmat-types/src/types/coreErrors.ts b/packages/itmat-types/src/types/coreErrors.ts new file mode 100644 index 000000000..dc0864a50 --- /dev/null +++ b/packages/itmat-types/src/types/coreErrors.ts @@ -0,0 +1,43 @@ +import { TRPCError } from '@trpc/server'; + +export enum enumCoreErrors { + DATABASE_ERROR = 'DATABASE_ERROR', + NOT_LOGGED_IN = 'NOT_LOGGED_IN', + CLIENT_MALFORMED_INPUT = 'CLIENT_MALFORMED_INPUT', + CLIENT_ACTION_ON_NON_EXISTENT_ENTRY = 'CLIENT_ACTION_ON_NON_EXISTENT_ENTRY', + AUTHENTICATION_ERROR = 'AUTHENTICATION_ERROR', + NO_PERMISSION_ERROR = 'NO_PERMISSION_ERROR', + FILE_STREAM_ERROR = 'FILE_STREAM_ERROR', + OBJ_STORE_ERROR = 'OBJ_STORE_ERROR', + UNQUALIFIED_ERROR = 'UNQUALIFIED_ERROR', + NOT_IMPLEMENTED = 'NOT_IMPLEMENTED', + POLLING_ERROR = 'POLLING_ERROR' // error type for polling errors +} + +export enum enumRequestErrorCodes { + BAD_REQUEST = 'BAD_REQUEST', + UNAUTHORIZED = 'UNAUTHORIZED', + FORBIDDEN = 'FORBIDDEN', + NOT_FOUND = 'NOT_FOUND', + TIMEOUT = 'TIMEOUT', + CONFLICT = 'CONFLICT', + PRECONDITION_FAILED = 'PRECONDITION_FAILED', + PAYLOAD_TOO_LARGE = 'PAYLOAD_TOO_LARGE', + METHOD_NOT_SUPPORTED = 'METHOD_NOT_SUPPORTED', + UNPROCESSABLE_CONTENT = 'UNPROCESSABLE_CONTENT', + TOO_MANY_REQUESTS = 'TOO_MANY_REQUESTS', + CLIENT_CLOSED_REQUEST = 'CLIENT_CLOSED_REQUEST', + INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR' +} + +/** + * This is a unified error class for all core errors. +*/ +export class CoreError extends TRPCError { + errorCode: enumCoreErrors; + constructor(errorCode: enumCoreErrors, message: string, httpErrorCode: (typeof TRPCError.prototype)['code'] = 'BAD_REQUEST') { + super({ message: message, code: httpErrorCode }); + this.errorCode = errorCode; + Object.setPrototypeOf(this, new.target.prototype); + } +} diff --git a/packages/itmat-types/src/types/data.ts b/packages/itmat-types/src/types/data.ts index d4e125f8f..937aa40a0 100644 --- a/packages/itmat-types/src/types/data.ts +++ b/packages/itmat-types/src/types/data.ts @@ -1,19 +1,51 @@ -export interface IDataEntry { - id: string; - m_studyId: string; - m_subjectId: string; // patient Id - m_visitId: string; // visit Id - m_versionId: string | null; // data version Id - m_fieldId: string; - metadata?: any; - value: any; - uploadedBy?: string; - uploadedAt: number; -} +import { IBase } from './base'; -export interface IFieldDescriptionObject { +export interface IData extends IBase { + studyId: string; fieldId: string; - timepoint: number; - measurement: number; - datatype: 'c' | 'i' | 'd' | 'b' | 't'; + dataVersion: string | null; + value: unknown; + properties: Record; +} + +export interface IGroupedData { + [key: string]: { + [key: string]: { + [key: string]: unknown + } + } +} + +export enum enumDataTransformationOperation { + GROUP = 'GROUP', + AFFINE = 'AFFINE', + LEAVEONE = 'LEAVEONE', + CONCAT = 'CONCAT', + DECONCAT = 'DECONCAT', + JOIN = 'JOIN', + DEGROUP = 'DEGROUP', + FILTER = 'FILTER', + FLATTEN = 'FLATTEN', + COUNT = 'COUNT' } + +export interface IDataSetSummary { + numberOfDataRecords: number; + numberOfDataAdds: number; + numberOfDataDeletes: number; + + numberOfVersionedRecords: number; + numberOfVersionedAdds: number; + numberOfVersionedDeletes: number; + + numberOfUnversionedRecords: number; + numberOfUnversionedAdds: number; + numberOfUnversionedDeletes: number; + + numberOfFields: number; + numberOfVersionedFields: number; + numberOfUnversionedFields: number; + + dataByUploaders: Array<{ userId: string, count: number }>; + dataByUsers: Array<{ userId: string, count: number }>; +} \ No newline at end of file diff --git a/packages/itmat-types/src/types/domain.ts b/packages/itmat-types/src/types/domain.ts new file mode 100644 index 000000000..0dac10ca5 --- /dev/null +++ b/packages/itmat-types/src/types/domain.ts @@ -0,0 +1,8 @@ +import { IBase } from './base'; + +export interface IDomain extends IBase { + domainPath: string; + name: string; + logo?: string; // file id + color?: string; +} \ No newline at end of file diff --git a/packages/itmat-types/src/types/drive.ts b/packages/itmat-types/src/types/drive.ts new file mode 100644 index 000000000..c7b0e5466 --- /dev/null +++ b/packages/itmat-types/src/types/drive.ts @@ -0,0 +1,30 @@ +import { IBase } from './base'; + +/** + * The node of the drive. It can be a folder or a file. + */ +export interface IDrive extends IBase { + managerId: string; + path: string[]; + restricted: boolean; // whether to allow delete on this node. User own folders are protected. + name: string; // folder name or file name + description?: string; + fileId: string | null; // null for folder + type: enumDriveNodeTypes; + parent: string | null; // null for root node + children: string[]; // ids of the file nodes + sharedUsers: IDrivePermission[]; // ids of shared users +} + +// this permission is different form data permissions +export interface IDrivePermission { + iid: string; // userId or userGroupId + read: boolean; + write: boolean; + delete: boolean; +} + +export enum enumDriveNodeTypes { + FOLDER = 'FOLDER', + FILE = 'FILE' +} diff --git a/packages/itmat-types/src/types/field.ts b/packages/itmat-types/src/types/field.ts index a85c8ab68..fae5fb401 100644 --- a/packages/itmat-types/src/types/field.ts +++ b/packages/itmat-types/src/types/field.ts @@ -1,37 +1,39 @@ -export interface IFieldEntry { - id: string; +import { IBase } from './base'; +import { IValueVerifier } from './utils'; + +export interface IField extends IBase { studyId: string; - fieldId: string; fieldName: string; - tableName?: string; - dataType: enumValueType; - possibleValues?: IValueDescription[]; + fieldId: string; + description?: string; + dataType: enumDataTypes; + categoricalOptions?: ICategoricalOption[]; unit?: string; comments?: string; - metadata?: any; - dateAdded: string; - dateDeleted: string | null; dataVersion: string | null; + verifier?: IValueVerifier[][]; + properties?: IFieldProperty[]; // mostly used for file data } -export interface IValueDescription { - id: string; - code: string; - description: string +export interface IFieldProperty { + name: string; + verifier?: IValueVerifier[][]; + description?: string; + required: boolean; } -export enum enumItemType { - IMAGE = 'I', - CLINICAL = 'C' +export enum enumDataTypes { + INTEGER = 'INTEGER', + DECIMAL = 'DECIMAL', + STRING = 'STRING', + BOOLEAN = 'BOOLEAN', + DATETIME = 'DATETIME', + FILE = 'FILE', + JSON = 'JSON', + CATEGORICAL = 'CATEGORICAL' } -export enum enumValueType { - INTEGER = 'int', - DECIMAL = 'dec', - STRING = 'str', - BOOLEAN = 'bool', - DATETIME = 'date', - FILE = 'file', - JSON = 'json', - CATEGORICAL = 'cat' +export interface ICategoricalOption extends IBase { + code: string; + description: string; } diff --git a/packages/itmat-types/src/types/file.ts b/packages/itmat-types/src/types/file.ts index fad6ac3eb..6a3ec8587 100644 --- a/packages/itmat-types/src/types/file.ts +++ b/packages/itmat-types/src/types/file.ts @@ -1,14 +1,60 @@ -export interface IFile { - id: string; +import { Readable } from 'stream'; +import { IBase } from './base'; + +export interface IFile extends IBase { + studyId: string | null; // null for system and user file + userId: string | null; // null for system and study file fileName: string; - studyId: string; - projectId?: string; - fileSize?: string; - description: string; - uploadTime: string; - uploadedBy: string; // userId + fileSize: number; + description?: string; + properties: Record; uri: string; - deleted: number | null; hash: string; - metadata: Record + fileType: enumFileTypes; + fileCategory: enumFileCategories; + sharedUsers?: string[]; +} + + + +export enum enumFileTypes { + CSV = 'CSV', + ZIP = 'ZIP', + RAR = 'RAR', + UNKNOWN = 'UNKNOWN', + MARKDOWN = 'MARKDOWN', + TXT = 'TXT', + JSON = 'JSON', + PDF = 'PDF', + DOCX = 'DOCX', + XLSX = 'XLSX', + XLS = 'XLS', + MAT = 'MAT', + // images + JPG = 'JPG', + JPEG = 'JPEG', + PNG = 'PNG', + WEBP = 'WEBP', + GIF = 'GIF', + // videos + MP4 = 'MP4', + AVI = 'AVI', + // others + FEATHER = 'FEATHER' +} + +export enum enumFileCategories { + STUDY_DATA_FILE = 'STUDY_DATA_FILE', + USER_DRIVE_FILE = 'USER_REPO_FILE', + DOC_FILE = 'DOC_FILE', + CACHE = 'CACHE', + PROFILE_FILE = 'DOMAIN_PROFILE_FILE', + DOMAIN_FILE = 'DOMAIN_FILE' +} + +export interface FileUpload { + createReadStream: () => Readable; + filename: string; + mimetype: string; + encoding: string; } diff --git a/packages/itmat-types/src/types/index.ts b/packages/itmat-types/src/types/index.ts index 10479bc67..322047ef3 100644 --- a/packages/itmat-types/src/types/index.ts +++ b/packages/itmat-types/src/types/index.ts @@ -9,6 +9,19 @@ import * as Organisation from './organisation'; import * as Pubkey from './pubkey'; import * as Data from './data'; import * as Standardization from './standardization'; +import * as Common from './common'; +import * as Base from './base'; +import * as CoreErrors from './coreErrors'; +import * as Config from './config'; +import * as ZodSchema from './zod'; +import * as Utils from './utils'; +import * as Drive from './drive'; +import * as Permission from './permission'; +import * as Cache from './cache'; +import * as Domain from './domain'; +import * as WebAuthn from './webauthn'; +import * as Instance from './instance'; +import * as Lxd from './lxd'; export * from './field'; export * from './file'; @@ -21,4 +34,18 @@ export * from './organisation'; export * from './pubkey'; export * from './data'; export * from './standardization'; -export const Types = { File, Job, Log, User, Organisation, Pubkey, Study, Query, Field, Data, Standardization }; +export * from './common'; +export * from './base'; +export * from './coreErrors'; +export * from './config'; +export * from './zod'; +export * from './utils'; +export * from './drive'; +export * from './permission'; +export * from './cache'; +export * from './domain'; +export * from './webauthn'; +export * from './instance'; +export * from './lxd'; + +export const Types = { File, Job, Log, User, Organisation, Pubkey, Study, Query, Field, Data, Standardization, Common, Base, CoreErrors, Config, ZodSchema, Utils, Drive, Permission, Cache, Domain, WebAuthn, Instance, Lxd}; diff --git a/packages/itmat-types/src/types/instance.ts b/packages/itmat-types/src/types/instance.ts new file mode 100644 index 000000000..90c5f8610 --- /dev/null +++ b/packages/itmat-types/src/types/instance.ts @@ -0,0 +1,60 @@ +import { IBase } from './base'; +import { IUser } from './user'; +import { LXDInstanceState, LXDInstanceTypeEnum} from './lxd'; + +export interface IInstance extends IBase{ + id: string; + name: string; + userId: IUser['id']; + username: IUser['username']; + status: enumInstanceStatus; + type: LXDInstanceTypeEnum; // virtual-machine' | 'container'; instance type VM or container + appType: enumAppType; // the application type, jupyter or matlab + createAt: number; // instance creation time + lifeSpan: number; // instance's life span, miliseconds + instanceToken: string; // instance cert token + project?: string |'default'; // the lxd project of the this instance + webDavToken?: string; // webDav cert token + config: Record; + lxdState?: LXDInstanceState; // Optional field for LXD instance state details + hostMapPort?: number; // Host map port number for the instance +} + +export enum enumInstanceStatus { + PENDING = 'PENDING', // first creation will be pending + FAILED = 'FAILED', + STARTING = 'STARTING', + STOPPING = 'STOPPING', + RUNNING = 'RUNNING', + STOPPED = 'STOPPED', + DELETED = 'DELETED', +} + +export enum enumAppType { + JUPYTER = 'Jupyter', + MATLAB = 'Matlab', + DESKTOP = 'Desktop' +} + + +// operation for instance +export enum enumOpeType { + CREATE = 'create', + UPDATE = 'update', + STOP = 'stop', + START = 'start', + DELETE = 'delete' +} + +//monitor operation for instances +export enum enumMonitorType { + STATE = 'state', + SYNC_DELETION = 'sync-deletion', +} + +export enum enumInstanceType { + SMALL = 'small', + MEDIUM ='medium', + LARGE = 'large' +} + diff --git a/packages/itmat-types/src/types/job.ts b/packages/itmat-types/src/types/job.ts index 667c34ff4..f6c187177 100644 --- a/packages/itmat-types/src/types/job.ts +++ b/packages/itmat-types/src/types/job.ts @@ -1,23 +1,87 @@ -import type { ObjectId } from 'mongodb'; +import { IBase } from './base'; +import type * as mongodb from 'mongodb'; -export interface IJobEntry { - _id?: ObjectId; - jobType: string; - id: string; - projectId?: string; +export interface IJobData { + queryId: string[]; + projectId: string; studyId: string; - requester: string; - requestTime: number; - receivedFiles: string[]; - status: string; - error: null | Record; - cancelled: boolean; - cancelledTime?: number; - claimedBy?: string; - lastClaimed?: number; - data?: dataobj; -} - -export type IJobEntryForDataCuration = IJobEntry; -export type IJobEntryForFieldCuration = IJobEntry<{ tag: string }>; -export type IJobEntryForQueryCuration = IJobEntry<{ queryId: string[], projectId: string, studyId: string }>; +} + +export interface IJob extends IBase { + name: string; + nextExecutionTime: number; // when creating jobs, set it to now for immediate executed jobs or a further time; do not be confused if this time is older than the current time. + period: number | null; // null for oneoff jobs + type: enumJobType; + executor: IExecutor | null; + data: IJobData | JSON | null; + parameters: JSON | null; + priority: number; + history: IJobHistory[]; // by default we will only keep the latest history + counter: number; + status: enumJobStatus; +} + +export interface IJobHistory { + time: number; + status: enumJobHistoryStatus; + errors: string[]; +} + +export enum enumJobType { + DMPAPI = 'DMPAPI', + AE = 'AE', + SYSTEMPROCESS = 'SYSTEMPROCESS', + LXD = 'LXD', + LXD_MONITOR = 'LXD_MONITOR' +} + +export enum enumJobStatus { + PENDING = 'PENDING', // oneoff jobs will always be PENDING + CANCELLED = 'CANCELLED', + FINISHED = 'FINISHED', // period should be null, + INUSE = 'INUSE', // for lxd containers + ERROR = 'ERROR' +} + +export enum enumJobHistoryStatus { + SUCCESS = 'SUCCESS', + FAILED = 'FAILED' +} + +export interface IExecutor { + id: string; + path: string; // for DMPAPI, use the trpc router path + type: string | null; +} + + +// Job Executor +export interface IJobPollerConfig { + identity: string; // a string identifying the server; this is just to keep track in mongo + jobType?: string; // if undefined, matches all jobs + jobCollection: mongodb.Collection; // collection to poll + pollingInterval: number; // in ms + action: (document: IJob) => Promise;// gets called every time there is new document, + jobSchedulerConfig: IJobSchedulerConfig +} + +// define the action return type +export interface IJobActionReturn { + successful: boolean; + result?: unknown; + error?: unknown; +} + +export interface IJobSchedulerConfig { + strategy: enumJobSchedulerStrategy, + usePriority: boolean, + // for errored jobs + reExecuteFailedJobs: boolean, + failedJobDelayTime: number, // unit timestamps + maxAttempts: number, // the number of attempts should be stored in history + jobCollection?: mongodb.Collection; // collection to poll +} + +export enum enumJobSchedulerStrategy { + FIFO = 'FIFO' +} \ No newline at end of file diff --git a/packages/itmat-types/src/types/log.ts b/packages/itmat-types/src/types/log.ts index bc41c6ca1..812b45ebb 100644 --- a/packages/itmat-types/src/types/log.ts +++ b/packages/itmat-types/src/types/log.ts @@ -1,102 +1,33 @@ -import { userTypes } from './user'; +import { IBase } from './base'; + +export interface ILog extends IBase { + requester: string; + type: enumEventType; + apiResolver: enumAPIResolver | null, // null for system logs + event: string; // we do not use enum here to reduce conflicts of both API backends + parameters?: Record; + status: enumEventStatus; + errors?: string; + timeConsumed: number | null; +} -export interface ILogEntry { - id: string, - requesterName: string, - requesterType: userTypes, - userAgent: USER_AGENT, - logType: LOG_TYPE, - actionType: LOG_ACTION, - actionData: any, - time: number, - status: LOG_STATUS, - errors: string | null +export enum enumAPIResolver { + 'tRPC' = 'tRPC', + 'FILE' = 'FILE' } -export enum USER_AGENT { +export enum enumUserAgent { MOZILLA = 'MOZILLA', OTHER = 'OTHER' } -export enum LOG_TYPE { - SYSTEM_LOG = 'SYSTEM_LOG', - REQUEST_LOG = 'REQUEST_LOG' -} - -export enum LOG_ACTION { - // SYSTEM - startSERVER = 'START_SERVER', - stopSERVER = 'STOP_SERVER', - - // USER - getUsers = 'GET_USERS', - EditUser = 'EDIT_USER', - DeleteUser = 'DELETE_USER', - CreateUser = 'CREATE_USER', - login = 'LOGIN_USER', - whoAmI = 'WHO_AM_I', - logout = 'LOGOUT', - requestUsernameOrResetPassword = 'REQUEST_USERNAME_OR_RESET_PASSWORD', - resetPassword = 'RESET_PASSWORD', - - // KEY - registerPubkey = 'REGISTER_PUBKEY', - issueAccessToken = 'ISSUE_ACCESS_TOKEN', - keyPairGenwSignature = 'KEYPAIRGEN_SIGNATURE', - rsaSigner = 'RSA_SIGNER', - linkUserPubkey = 'LINK_USER_PUBKEY', - - // ORGANISATION - createOrganisation = 'CREATE_ORGANISATION', - deleteOrganisation = 'DELETE_ORGANISATION', - - // PROJECT - getProject = 'GET_PROJECT', - // GET_PROJECT_PATIENT_MAPPING = 'GET_PROJECT_PATIENT_MAPPING', - createProject = 'CREATE_PROJECT', - deleteProject = 'DELETE_PROJECT', - setDataversionAsCurrent = 'SET_DATAVERSION_AS_CURRENT', - subscribeToJobStatusChange = 'SUBSCRIBE_TO_JOB_STATUS', - - // STUDY | DATASET - deleteStudy = 'DELETE_STUDY', - getStudy = 'GET_STUDY', - getStudyFields = 'GET_STUDY_FIELDS', - createStudy = 'CREATE_STUDY', - editStudy = 'EDIT_STUDY', - createFieldCurationJob = 'CREATE_FIELD_CREATION_JOB', - createDataCurationJob = 'CREATE_DATA_CURATION_JOB', - getDataRecords = 'GET_DATA_RECORDS', - getOntologyTree = 'GET_ONTOLOGY_TREE', - checkDataComplete = 'CHECK_DATA_COMPLETE', - createNewDataVersion = 'CREATE_NEW_DATA_VERSION', - uploadDataInArray = 'UPLOAD_DATA_IN_ARRAY', - deleteDataRecords = 'DELETE_DATA_RECORDS', - createNewField = 'CREATE_NEW_FIELD', - editField = 'EDIT_FIELD', - deleteField = 'DELETE_FIELD', - addOntologyField = 'ADD_ONTOLOGY_FIELD', - deleteOntologyField = 'DELETE_ONTOLOGY_FIELD', - - // STUDY & PROJECT - editRole = 'EDIT_ROLE', - addRole = 'ADD_NEW_ROLE', - removeRole = 'REMOVE_ROLE', - - // FILE - uploadFile = 'UPLOAD_FILE', - DOWNLOAD_FILE = 'DOWNLOAD_FILE', - deleteFile = 'DELETE_FILE', - - //QUERY - getQueries = 'GET_QUERY', - createQuery = 'CREATE_QUERY', - getQueryById = 'GET_QUERY_BY_ID', - createQueryCurationJob = 'CREATE_QUERY_CURATION_JOB' - +export enum enumEventType { + SYSTEM_LOG = 'SYSTEMLOG', + API_LOG = 'APILOG' } -export enum LOG_STATUS { +export enum enumEventStatus { SUCCESS = 'SUCCESS', - FAIL = 'FAIL' + FAIL = 'FAIL', + UNKNOWN = 'UNKNOWN' } diff --git a/packages/itmat-types/src/types/lxd.ts b/packages/itmat-types/src/types/lxd.ts new file mode 100644 index 000000000..1c1b66409 --- /dev/null +++ b/packages/itmat-types/src/types/lxd.ts @@ -0,0 +1,422 @@ +/** + * LXD related types + */ + +interface LxdInstanceUsageProp { + usage: number; +} + +interface LxdInstanceMemory { + swap_usage: number; + swap_usage_peak: number; + usage: number; + usage_peak: number; +} + +interface LxdInstanceNetworkAddress { + address: string; + family: string; + netmask: string; + scope: string; +} + +interface LxdInstanceNetworkCounters { + bytes_received: number; + bytes_sent: number; + errors_received: number; + errors_sent: number; + packets_dropped_inbound: number; + packets_dropped_outbound: number; + packets_received: number; + packets_sent: number; +} + +interface LxdInstanceNetwork { + addresses: LxdInstanceNetworkAddress[]; + counters: LxdInstanceNetworkCounters; + host_name: string; + hwaddr: string; + mtu: number; + state: 'up' | 'down'; + type: string; +} + +export interface LXDInstanceState { + cpu: LxdInstanceUsageProp; + disk: { + root: LxdInstanceUsageProp; + } & Record; + memory: LxdInstanceMemory; + network?: Record; + pid: number; + processes: number; + status: string; + hostIp?: string; +} + +export enum LXDInstanceTypeEnum { + CONTAINER = 'container', + VIRTUAL_MACHINE = 'virtual-machine', +} + +export interface LXDInstanceType { + name: string; + description: string; + status: string; + statusCode: number; + profiles: string[]; + type: LXDInstanceTypeEnum; + architecture: string; + creationDate: string; + lastUsedDate: string; + username: string; + cpuLimit: string; + memoryLimit: string; + key: string; +} + +export interface LxdConfiguration { + name: string; + architecture: string; + config?: { + 'limits.cpu': string, + 'limits.memory': string, + 'user.username': string, // store username to instance config + 'user.user-data': string // set the cloud-init user-data + }; + source: { + type: string; + alias: string; + }; + profiles?: string[]; + type?: string; + project?: string; +} + +// resources.d.ts +export interface LxdResources { + cpu: Cpu; + gpu: Gpu; + memory: Memory; + network: Network; + pci: Pci; + storage: Storage; + system: System; + usb: Usb; + } + +export interface Cpu { + architecture: string; + sockets?: CpuSockets[] | null; + total: number; + } + +export interface CpuSockets { + cache?: Cache[] | null; + cores?: Cores[] | null; + frequency: number; + frequency_minimum: number; + frequency_turbo: number; + name: string; + socket: number; + vendor: string; + } + +export interface Cache { + level: number; + size: number; + type: string; + } + +export interface Cores { + core: number; + die: number; + frequency: number; + threads?: Threads[] | null; + } + +export interface Threads { + id: number; + isolated: boolean; + numa_node: number; + online: boolean; + thread: number; + } + +export interface Gpu { + cards?: GpuCards[] | null; + total: number; + } + +export interface GpuCards { + driver: string; + driver_version: string; + drm: Drm; + mdev?: null; + numa_node: number; + nvidia: Nvidia; + pci_address: string; + product: string; + product_id: string; + sriov: Sriov; + usb_address: string; + vendor: string; + vendor_id: string; + } + +export interface Drm { + card_device: string; + card_name: string; + control_device: string; + control_name: string; + id: number; + render_device: string; + render_name: string; + } + +export interface Nvidia { + architecture: string; + brand: string; + card_device: string; + card_name: string; + cuda_version: string; + model: string; + nvrm_version: string; + uuid: string; + } + +export interface Sriov { + current_vfs: number; + maximum_vfs: number; + vfs?: null; + } + +export interface Memory { + hugepages_size: number; + hugepages_total: number; + hugepages_used: number; + nodes?: null; + total: number; + used: number; + } + +export interface Network { + cards?: NetworkCards[] | null; + total: number; + } + +export interface NetworkCards { + driver: string; + driver_version: string; + firmware_version: string; + numa_node: number; + pci_address: string; + ports?: Ports[] | null; + product: string; + product_id: string; + sriov: Sriov; + usb_address: string; + vendor: string; + vendor_id: string; + } + +export interface Ports { + address: string; + auto_negotiation: boolean; + id: string; + infiniband: Infiniband; + link_detected: boolean; + link_duplex: string; + link_speed: number; + port: number; + port_type: string; + protocol: string; + supported_modes?: string[] | null; + supported_ports?: string[] | null; + transceiver_type: string; + } + +export interface Infiniband { + issm_device: string; + issm_name: string; + mad_device: string; + mad_name: string; + verb_device: string; + verb_name: string; + } + +export interface Pci { + devices?: PciDevices[] | null; + total: number; + } + +export interface PciDevices { + driver: string; + driver_version: string; + iommu_group: number; + numa_node: number; + pci_address: string; + product: string; + product_id: string; + vendor: string; + vendor_id: string; + vpd: Vpd; + } + +export interface Vpd { + entries: string; + product_name: string; + } + +export interface Storage { + disks?: Disks[] | null; + total: number; + } + +export interface Disks { + block_size: number; + device: string; + device_id: string; + device_path: string; + firmware_version: string; + id: string; + model: string; + numa_node: number; + partitions?: Partitions[] | null; + pci_address: string; + read_only: boolean; + removable: boolean; + rpm: number; + serial: string; + size: number; + type: string; + usb_address: string; + wwn: string; + } + +export interface Partitions { + device: string; + id: string; + partition: number; + read_only: boolean; + size: number; + } + +export interface System { + chassis: Chassis; + family: string; + firmware: Firmware; + motherboard: Motherboard; + product: string; + serial: string; + sku: string; + type: string; + uuid: string; + vendor: string; + version: string; + } + +export interface Chassis { + serial: string; + type: string; + vendor: string; + version: string; + } + +export interface Firmware { + date: string; + vendor: string; + version: string; + } + +export interface Motherboard { + product: string; + serial: string; + vendor: string; + version: string; + } + +export interface Usb { + devices?: UsbDevices[] | null; + total: number; + } + +export interface UsbDevices { + bus_address: number; + device_address: number; + interfaces?: Interfaces[] | null; + product: string; + product_id: string; + speed: number; + vendor: string; + vendor_id: string; + } + +export interface Interfaces { + class: string; + class_id: number; + driver: string; + driver_version: string; + number: number; + subclass: string; + subclass_id: number; + } + + +export interface LxdOperationMetadata { + command: string[]; + environment: { + HOME: string; + LANG: string; + PATH: string; + TERM: string; + USER: string; + }; + fds: { + [key: string]: string; + }; + interactive: boolean; +} + +export interface LxdOperation { + metadata: { + class: string; + created_at: string; + description: string; + err: string; + id: string; + location: string; + may_cancel: boolean; + metadata: LxdOperationMetadata; + resources: { + containers: string[]; + instances: string[]; + }; + status: string; + status_code: number; + updated_at: string; + }; + operation: string; + status: string; + status_code: number; + type: string; +} + +export interface LxdErrorResponse { + error: string; + error_code: number; + type: string; +} + +export type LxdGetInstanceConsoleResponse = { + operationId: string; + operationSecrets: { [key: string]: string }; +} | { + error: boolean; + data: string; +}; +export interface InstanceInfo { + metadata?: { + running_operation?: string; + }; +} diff --git a/packages/itmat-types/src/types/organisation.ts b/packages/itmat-types/src/types/organisation.ts index a89d63c11..c2f833abe 100644 --- a/packages/itmat-types/src/types/organisation.ts +++ b/packages/itmat-types/src/types/organisation.ts @@ -1,12 +1,7 @@ -export type OrganisationMetadata = { - siteIDMarker?: string; -} +import { IBase } from './base'; -export interface IOrganisation { - id: string; +export interface IOrganisation extends IBase { name: string; - shortname: string | null; - containOrg: string | null; - deleted: number | null; - metadata: OrganisationMetadata; -} + shortname?: string; + profile?: string; +} \ No newline at end of file diff --git a/packages/itmat-types/src/types/permission.ts b/packages/itmat-types/src/types/permission.ts new file mode 100644 index 000000000..cef6b1587 --- /dev/null +++ b/packages/itmat-types/src/types/permission.ts @@ -0,0 +1,54 @@ +import { IBase } from './base'; + +// we use three bit to denote read, write and delele, saved as number + +// we use mongodb which is a collection of json documents +// this permission accepts json input and decide the permissions +// we provide three permissions: read, write and delete +export interface IDocumentLevelPermission { + reads: string[]; + writes: string[]; + deletes: string[]; +} + +// study assistant can have full control of user management +// study manager in addition can manage the study data and study itself, e.g., editing study metadata +// study user only have access to data filtered by dataPermissions +export enum enumStudyRoles { + STUDY_MANAGER = 'STUDY_MANAGER', + STUDY_ASSISTANT = 'STUDY_ASSISTANT', + STUDY_USER = 'STUDY_USER' +} + +export interface IDataPermission { + fields: string[]; // regular expression + dataProperties: { + [property: string]: string[] // regular expression + }, + includeUnVersioned: boolean, + permission: number +} + +export interface IRole extends IBase { + studyId: string; + name: string; + description?: string; + + // data permissions for studyId + dataPermissions: IDataPermission[]; // or + studyRole: enumStudyRoles; + users: string[]; +} + +// read - write - delete +export const permissionString: Record = { + READ: [4, 5, 6, 7], + WRITE: [6, 7], + DELETE: [7] +}; + +export enum enumDataAtomicPermissions { + READ = 'READ', + WRITE = 'WRITE', + DELETE = 'DELETE' +} \ No newline at end of file diff --git a/packages/itmat-types/src/types/pubkey.ts b/packages/itmat-types/src/types/pubkey.ts index 77f6bf1ad..70b49e88f 100644 --- a/packages/itmat-types/src/types/pubkey.ts +++ b/packages/itmat-types/src/types/pubkey.ts @@ -1,11 +1,14 @@ -export interface IPubkey { - id: string; +import { IBase } from './base'; + +export interface IPubkey extends IBase { pubkey: string; + hashedPrivateKey?: string; jwtPubkey: string; jwtSeckey: string; refreshCounter: number; associatedUserId: string | null; - deleted: number | null; + challenge: string | null; + lastUsedTime: number | null; } export type AccessToken = { diff --git a/packages/itmat-types/src/types/query.ts b/packages/itmat-types/src/types/query.ts index c7a07ff00..2b8d925a1 100644 --- a/packages/itmat-types/src/types/query.ts +++ b/packages/itmat-types/src/types/query.ts @@ -1,6 +1,5 @@ export type IQueryEntry = { id: string; - queryString: any; studyId: string; projectId?: string; requester: string; @@ -14,6 +13,14 @@ export type IQueryEntry = { data_requested: string[]; cohort: ICohortSelection[][]; new_fields: INewFieldSelection[]; + queryString: IQueryString; +} + +export interface IQueryString { + data_requested?: string[]; + cohort: ICohortSelection[][]; + new_fields: INewFieldSelection[]; + table_requested?: string; } // we do not reuse the cohort types directly to avoid conflicts diff --git a/packages/itmat-types/src/types/standardization.ts b/packages/itmat-types/src/types/standardization.ts index 219b4b3bb..079ee30df 100644 --- a/packages/itmat-types/src/types/standardization.ts +++ b/packages/itmat-types/src/types/standardization.ts @@ -1,5 +1,6 @@ -export interface IStandardization { - id: string, +import { IBase } from './base'; + +export interface IStandardization extends IBase { studyId: string, type: string, field: string[], @@ -9,9 +10,6 @@ export interface IStandardization { // records with same path will be joined together joinByKeys?: string[], dataVersion: string | null, - uploadedAt: number, - metadata?: JSON, - deleted: number | null, } export enum StandardizationRuleSource { @@ -22,11 +20,27 @@ export enum StandardizationRuleSource { inc = 'inc' } +export enum StandardizationFilterOptions { + convert = 'convert', + delete = 'delete' +} + +export interface StandardizationFilterOptionParameters { + source: 'value' | 'data'; + parameter: string; +} + +interface StandardizationRuleFilter { + [key: string]: Array +} + + + export interface IStandardizationRule { id: string, entry: string, source: StandardizationRuleSource, parameter: string[], // further processings for a value: delete, convert, etc. - filters?: any + filters?: StandardizationRuleFilter } diff --git a/packages/itmat-types/src/types/study.ts b/packages/itmat-types/src/types/study.ts index 131b2d471..975b99228 100644 --- a/packages/itmat-types/src/types/study.ts +++ b/packages/itmat-types/src/types/study.ts @@ -1,82 +1,18 @@ -import { IUser } from './user'; -import { FileUpload } from 'graphql-upload-minimal'; +import { IBase } from './base'; -export enum studyType { - SENSOR = 'SENSOR', - CLINICAL = 'CLINICAL', - ANY = 'ANY' -} - -export interface IStudy { - id: string; +export interface IStudy extends IBase { name: string; - createdBy: string; - lastModified: number; - deleted: number | null; currentDataVersion: number; // index; dataVersions[currentDataVersion] gives current version; // -1 if no data dataVersions: IStudyDataVersion[]; - description: string; - ontologyTrees?: IOntologyTree[]; - type: studyType; - metadata: Record + description?: string; + profile?: string; + isPublic: boolean; } -export interface IStudyDataVersion { - id: string; // uuid - contentId: string; // same contentId = same data +export interface IStudyDataVersion extends IBase { + id: string; version: string; tag?: string; - updateDate: string; -} - -export enum atomicOperation { - READ = 'READ', - WRITE = 'WRITE' -} - -export enum IPermissionManagementOptions { - own = 'own', - role = 'role', - job = 'job', - query = 'query', - ontologyTrees = 'ontologyTrees' -} - -interface IRoleBase { - id: string; - projectId?: string; - studyId: string; - name: string; - permissions: { - data?: { - subjectIds?: string[]; - visitIds?: string[]; - fieldIds?: string[]; - uploaders?: string[]; // only works for downloading data; for file data, it will check IFile instead of data clip - hasVersioned?: boolean; - operations?: atomicOperation[]; - filters?: any[] - }, - manage?: { - [IPermissionManagementOptions.own]?: atomicOperation[]; - [IPermissionManagementOptions.role]?: atomicOperation[]; - [IPermissionManagementOptions.job]?: atomicOperation[]; - [IPermissionManagementOptions.query]?: atomicOperation[]; - [IPermissionManagementOptions.ontologyTrees]: atomicOperation[]; - } - }; - description: string; - createdBy: string; - deleted: number | null; - metadata: Record -} - -export interface IRole extends IRoleBase { - users: string[]; -} - -export interface IRoleQL extends IRoleBase { - users: IUser[]; } export interface IProject { @@ -85,22 +21,17 @@ export interface IProject { createdBy: string; name: string; dataVersion?: IStudyDataVersion | null; - summary?: any; + summary?: { + subjects: string[]; + visits: string[]; + standardizationTypes: string[] + }; patientMapping: { [originalId: string]: string }; lastModified: number; deleted: number | null; metadata: Record } -export interface IDataClip { - fieldId: string; - value?: string; - subjectId: string; - visitId: string; - file?: FileUpload; - metadata?: JSON -} - export interface ISubjectDataRecordSummary { subjectId: string, visitId: string, @@ -113,7 +44,7 @@ export interface IOntologyTree { name: string, dataVersion: string | null, routes?: IOntologyRoute[], - metadata?: JSON, + metadata?: Record, deleted: number } diff --git a/packages/itmat-types/src/types/user.ts b/packages/itmat-types/src/types/user.ts index c6307fb34..0ffc75932 100644 --- a/packages/itmat-types/src/types/user.ts +++ b/packages/itmat-types/src/types/user.ts @@ -1,25 +1,31 @@ -export enum userTypes { +import { IBase } from './base'; + +export enum enumUserTypes { ADMIN = 'ADMIN', STANDARD = 'STANDARD', + SYSTEM = 'SYSTEM', + GUEST = 'GUEST' +} + +export enum enumReservedUsers { SYSTEM = 'SYSTEM' } -export interface IUserWithoutToken { - id: string; +export interface IUserWithoutToken extends IBase { username: string; email: string; firstname: string; lastname: string; organisation: string; - type: userTypes; - description: string; + type: enumUserTypes; emailNotificationsActivated: boolean; - emailNotificationsStatus: any; - deleted: number | null; - createdAt: number; - expiredAt: number; + emailNotificationsStatus: { + expiringNotification: boolean + }; resetPasswordRequests: IResetPasswordRequest[]; - metadata?: any + profile?: string; + expiredAt: number; + description?: string; } export interface IResetPasswordRequest { diff --git a/packages/itmat-types/src/types/utils.ts b/packages/itmat-types/src/types/utils.ts new file mode 100644 index 000000000..0c033ab42 --- /dev/null +++ b/packages/itmat-types/src/types/utils.ts @@ -0,0 +1,47 @@ +// Value verifier +export interface IValueVerifier { + formula: IAST; + condition: enumConditionOps; + value: string | number; + parameters: Record; +} + +export enum enumConditionOps { + NUMERICALEQUAL = 'numerical:=', + NUMERICALNOTEQUAL = 'numerical:!=', + NUMERICALLESSTHAN = 'numerical:<', + NUMERICALGREATERTHAN = 'numerical:>', + NUMERICALNOTLESSTHAN = 'numerical:>=', + NUMERICALNOTGREATERTHAN = 'numerical:<=', + STRINGREGEXMATCH = 'string:=regex=', + STRINGEQUAL = 'string:=', + GENERALISNULL = 'general:=null', + GENERALISNOTNULL = 'general:!=null' +} + +export interface IAST { + type: enumASTNodeTypes; + operator: enumMathOps | null, + value: string | number | null; + parameters: Record; + children: IAST[] | null; // null for lead node; OPERATION type should not be a lead node. +} + +export enum enumASTNodeTypes { + OPERATION = 'OPERATION', + VARIABLE = 'VARIABLE', + SELF = 'SELF', // the input value + VALUE = 'VALUE', + MAP = 'MAP' +} + +export enum enumMathOps { + NUMERICALADD = 'numerical:+', + NUMERICALMINUS = 'numerical:-', + NUMERICALMULTIPLY = 'numerical:*', + NUMERICALDIVIDE = 'numerical:/', + NUMERICALPOW = 'numerical:^', + STRINGCONCAT = 'string:+', + STRINGSUBSTR = 'string:substr', + TYPECONVERSION = 'string:=>' +} \ No newline at end of file diff --git a/packages/itmat-types/src/types/webauthn.ts b/packages/itmat-types/src/types/webauthn.ts new file mode 100644 index 000000000..ab0b8cfd1 --- /dev/null +++ b/packages/itmat-types/src/types/webauthn.ts @@ -0,0 +1,26 @@ + +/* +* Each authenticator must also be associated to a user so that you can generate a list of +* authenticator credential IDs to pass into `generateAuthenticationOptions()`, from which one is +* expected to generate an authentication response. +*/ + +import type { AuthenticatorDevice as OriginalAuthenticatorDevice} from '@simplewebauthn/types'; +import { IUser } from './user'; +import { IBase } from './base'; + +// overwrite the AuthenticatorDevice type in simplewebauthn +export type AuthenticatorDevice = Omit & { + id: string; // the ID of the authenticator credential + name?: string; // the name of the authenticator credential + origin?: string; // the origin of the authenticator credential +}; + +// include the credential +export interface IWebAuthn extends IBase { + userId: IUser['id']; + username: IUser['username']; + devices: AuthenticatorDevice[]; + challenge: Uint8Array; + challengeTimestamp: number; +} diff --git a/packages/itmat-types/src/types/zod.ts b/packages/itmat-types/src/types/zod.ts new file mode 100644 index 000000000..9c5cb05fc --- /dev/null +++ b/packages/itmat-types/src/types/zod.ts @@ -0,0 +1,14 @@ +import { Readable } from 'stream'; +import { z } from 'zod'; + +const readStreamRefinement = z.instanceof(Readable).refine( + (val): val is Readable => val instanceof Readable +); + +export const FileUploadSchema = z.object({ + createReadStream: z.function().returns(readStreamRefinement), + filename: z.string(), + mimetype: z.string(), + encoding: z.string(), + fieldName: z.string() +}); \ No newline at end of file diff --git a/packages/itmat-types/tsconfig.json b/packages/itmat-types/tsconfig.json index da97c912e..781e6aac1 100644 --- a/packages/itmat-types/tsconfig.json +++ b/packages/itmat-types/tsconfig.json @@ -1,12 +1,13 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "module": "commonjs", + "module": "ES2022", "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, + "noImplicitAny": false, "noFallthroughCasesInSwitch": true }, "files": [], diff --git a/packages/itmat-types/tsconfig.spec.json b/packages/itmat-types/tsconfig.spec.json index e85ec32ef..c744cb73a 100644 --- a/packages/itmat-types/tsconfig.spec.json +++ b/packages/itmat-types/tsconfig.spec.json @@ -2,7 +2,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", + "module": "ES2022", "types": [ "jest", "node" diff --git a/packages/itmat-ui-react-e2e/cypress.config.ts b/packages/itmat-ui-react-e2e/cypress.config.ts deleted file mode 100644 index 8307a625c..000000000 --- a/packages/itmat-ui-react-e2e/cypress.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { defineConfig } from 'cypress'; -import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset'; - -export default defineConfig({ - e2e: nxE2EPreset(__dirname) -}); diff --git a/packages/itmat-ui-react-e2e/project.json b/packages/itmat-ui-react-e2e/project.json deleted file mode 100644 index 09fa9f398..000000000 --- a/packages/itmat-ui-react-e2e/project.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "itmat-ui-react-e2e", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "sourceRoot": "packages/itmat-ui-react-e2e/src", - "projectType": "application", - "targets": { - "e2e": { - "executor": "@nx/cypress:cypress", - "options": { - "cypressConfig": "packages/itmat-ui-react-e2e/cypress.config.ts", - "devServerTarget": "itmat-ui-react:serve:development", - "testingType": "e2e" - }, - "configurations": { - "production": { - "devServerTarget": "itmat-ui-react:serve:production" - } - } - }, - "lint": { - "executor": "@nx/eslint:lint", - "outputs": [ - "{options.outputFile}" - ] - } - }, - "tags": [], - "implicitDependencies": [ - "itmat-ui-react" - ] -} \ No newline at end of file diff --git a/packages/itmat-ui-react-e2e/src/e2e/app.cy.ts b/packages/itmat-ui-react-e2e/src/e2e/app.cy.ts deleted file mode 100644 index 8b015c6b8..000000000 --- a/packages/itmat-ui-react-e2e/src/e2e/app.cy.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { getGreeting } from '../support/app.po'; - -describe('itmat-ui-react', () => { - beforeEach(() => cy.visit('/')); - - it('should display welcome message', () => { - // Custom command example, see `../support/commands.ts` file - cy.login('my-email@something.com', 'myPassword'); - - // Function helper example, see `../support/app.po.ts` file - getGreeting().contains('Welcome itmat-ui-react'); - }); -}); diff --git a/packages/itmat-ui-react-e2e/src/fixtures/example.json b/packages/itmat-ui-react-e2e/src/fixtures/example.json deleted file mode 100644 index a0f14054f..000000000 --- a/packages/itmat-ui-react-e2e/src/fixtures/example.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "Using fixtures to represent data", - "email": "hello@cypress.io" -} \ No newline at end of file diff --git a/packages/itmat-ui-react-e2e/src/support/app.po.ts b/packages/itmat-ui-react-e2e/src/support/app.po.ts deleted file mode 100644 index 329342469..000000000 --- a/packages/itmat-ui-react-e2e/src/support/app.po.ts +++ /dev/null @@ -1 +0,0 @@ -export const getGreeting = () => cy.get('h1'); diff --git a/packages/itmat-ui-react-e2e/src/support/commands.ts b/packages/itmat-ui-react-e2e/src/support/commands.ts deleted file mode 100644 index 4200179ba..000000000 --- a/packages/itmat-ui-react-e2e/src/support/commands.ts +++ /dev/null @@ -1,33 +0,0 @@ -// *********************************************** -// This example commands.js shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** - -// eslint-disable-next-line @typescript-eslint/no-namespace -declare namespace Cypress { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - interface Chainable { - login(email: string, password: string): void; - } -} -// -// -- This is a parent command -- -Cypress.Commands.add('login', (email, password) => { - console.log('Custom command example: Login', email, password); -}); -// -// -- This is a child command -- -// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This will overwrite an existing command -- -// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/packages/itmat-ui-react-e2e/src/support/e2e.ts b/packages/itmat-ui-react-e2e/src/support/e2e.ts deleted file mode 100644 index 3d469a6b6..000000000 --- a/packages/itmat-ui-react-e2e/src/support/e2e.ts +++ /dev/null @@ -1,17 +0,0 @@ -// *********************************************************** -// This example support/index.js is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** - -// Import commands.js using ES2015 syntax: -import './commands'; diff --git a/packages/itmat-ui-react-e2e/tsconfig.json b/packages/itmat-ui-react-e2e/tsconfig.json deleted file mode 100644 index 73e9cc535..000000000 --- a/packages/itmat-ui-react-e2e/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "sourceMap": false, - "outDir": "../../dist/out-tsc", - "allowJs": true, - "types": [ - "cypress", - "node" - ] - }, - "include": [ - "src/**/*.ts", - "src/**/*.js", - "cypress.config.ts" - ] -} \ No newline at end of file diff --git a/packages/itmat-ui-react/.babelrc b/packages/itmat-ui-react/.babelrc index 9d74c5df9..870a13e32 100644 --- a/packages/itmat-ui-react/.babelrc +++ b/packages/itmat-ui-react/.babelrc @@ -8,4 +8,4 @@ ] ], "plugins": [] -} \ No newline at end of file +} diff --git a/packages/itmat-ui-react/express-user.d.ts b/packages/itmat-ui-react/express-user.d.ts new file mode 100644 index 000000000..1321e900d --- /dev/null +++ b/packages/itmat-ui-react/express-user.d.ts @@ -0,0 +1,16 @@ +import type { IUserWithoutToken } from '@itmat-broker/itmat-types'; + +declare global { + + namespace Express { + + type User = IUserWithoutToken + + interface Request { + user?: User; + headers: IncomingHttpHeaders; + login(user: User, done: (err: unknown) => void): void; + logout(done: (err: unknown) => void): void; + } + } +} diff --git a/packages/itmat-ui-react/jest.config.ts b/packages/itmat-ui-react/jest.config.ts index 08eb2d553..51f1d4eba 100644 --- a/packages/itmat-ui-react/jest.config.ts +++ b/packages/itmat-ui-react/jest.config.ts @@ -1,7 +1,7 @@ /* eslint-disable */ export default { displayName: 'itmat-ui-react', - preset: '../../jest.preset.js', + preset: '../../jest.preset.cjs', transform: { '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', '^.+\\.[tj]sx?$': ['@swc/jest', { jsc: { transform: { react: { runtime: 'automatic' } } } }] diff --git a/packages/itmat-ui-react/project.json b/packages/itmat-ui-react/project.json index 719483b56..e390f4bb6 100644 --- a/packages/itmat-ui-react/project.json +++ b/packages/itmat-ui-react/project.json @@ -3,6 +3,7 @@ "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "packages/itmat-ui-react/src", "projectType": "application", + "tags": [], "targets": { "build": { "executor": "@nx/webpack:webpack", @@ -26,7 +27,7 @@ "packages/itmat-ui-react/src/styles.css" ], "scripts": [], - "webpackConfig": "packages/itmat-ui-react/webpack.config.js" + "webpackConfig": "packages/itmat-ui-react/webpack.config.cjs" }, "configurations": { "development": { @@ -84,6 +85,5 @@ "jestConfig": "packages/itmat-ui-react/jest.config.ts" } } - }, - "tags": [] + } } \ No newline at end of file diff --git a/packages/itmat-ui-react/proxy.conf.js b/packages/itmat-ui-react/proxy.conf.js index c6a27f8a0..4baedea8f 100644 --- a/packages/itmat-ui-react/proxy.conf.js +++ b/packages/itmat-ui-react/proxy.conf.js @@ -1,42 +1,32 @@ const API_SERVER = 'http://localhost:3333'; -module.exports = { - '/graphql': { +module.exports = [ + { + context: ['/trpc'], target: API_SERVER, secure: false, changeOrigin: true }, - '/file': { - target: API_SERVER, - secure: false, - changeOrigin: true - }, - '/pun': { + { + context: ['/webdav'], target: API_SERVER, secure: false, changeOrigin: true, - autoRewrite: true, - ws: true + autoRewrite: true }, - '/node': { + { + context: ['/file'], target: API_SERVER, secure: false, - changeOrigin: true, - autoRewrite: true, - ws: true - }, - '/rnode': { - target: API_SERVER, - secure: false, - changeOrigin: true, - autoRewrite: true, - ws: true + changeOrigin: true }, - '/public': { + { + context: ['/rtc', '/jupyter', '/matlab'], target: API_SERVER, secure: false, changeOrigin: true, autoRewrite: true, - ws: true + ws: true, + xforward: true } -}; \ No newline at end of file +]; diff --git a/packages/itmat-ui-react/src/Fence.tsx b/packages/itmat-ui-react/src/Fence.tsx index 27b6a9022..a11ab293a 100644 --- a/packages/itmat-ui-react/src/Fence.tsx +++ b/packages/itmat-ui-react/src/Fence.tsx @@ -1,5 +1,4 @@ -import { FunctionComponent } from 'react'; -import { Query } from '@apollo/client/react/components'; +import { FunctionComponent, useState, useEffect } from 'react'; import { Routes, Route } from 'react-router-dom'; import { LoginBox } from './components/login/login'; import { MainMenuBar } from './components/scaffold/mainMenuBar'; @@ -8,42 +7,71 @@ import css from './components/scaffold/scaffold.module.css'; import { ResetPasswordPage } from './components/login/resetPasswordPage'; import { RequestResetPassword } from './components/login/requestResetPasswordPage'; import { RegisterNewUser } from './components/login/register'; -import { WHO_AM_I, RECOVER_SESSION_EXPIRE_TIME } from '@itmat-broker/itmat-models'; import LoadSpinner from './components/reusable/loadSpinner'; import { StatusBar } from './components/scaffold/statusBar'; -import { useQuery } from '@apollo/client/react/hooks'; +import { trpc } from './utils/trpc'; + +import { WebAuthnRegistrationComponent } from './utils/dmpWebauthn/webauthn.register'; +import { WebAuthnAuthenticationComponent } from './utils/dmpWebauthn/webauthn.authenticate'; +import { DeviceNicknameComponent } from './utils/dmpWebauthn/webuathn.nickname'; +import { useAuth } from './utils/dmpWebauthn/webauthn.context'; + export const Fence: FunctionComponent = () => { + const { + isUserLogin, + setIsUserLogin + } = useAuth(); + + const whoAmI = trpc.user.whoAmI.useQuery(); + const recoverSession = trpc.user.recoverSessionExpireTime.useQuery(undefined, { + refetchInterval: 30 * 60 * 1000 + }); + + const [component, setComponent] = useState(null); + + const isAnyLoading = whoAmI.isLoading; + + const hasError = whoAmI.isError || recoverSession.isError; + const errorMessage = whoAmI.error?.message ?? ''; + + useEffect(() => { + if (whoAmI.data !== null && whoAmI.data !== undefined) { + setIsUserLogin(true); + } else { + setIsUserLogin(false); + } + }, [whoAmI.data]); + + useEffect(() => { + if (isAnyLoading) { + setComponent(); + } else if (hasError) { + setComponent(

Error: {errorMessage}

); + } else if (isUserLogin) { + setComponent( +
+ + + +
+ ); + } else { + setComponent(); + } + }, [isAnyLoading, hasError, isUserLogin, errorMessage]); - const { loading, error, data } = useQuery(WHO_AM_I); - let component: JSX.Element | null = null; - if (loading) - component = ; - else if (error) - component =

- Error - {' '} - {error.message} -

; - else if (data.whoAmI !== null && data.whoAmI !== undefined && data.whoAmI.username !== null) { - component =
- query={RECOVER_SESSION_EXPIRE_TIME} pollInterval={30 * 60 * 1000 /* 30 minutes */}> - {() => { - return null; - }} - - - - -
; - } else - component = ; - return - } /> - } /> - } /> - - ; + return ( + + } /> + } /> + } /> + } /> + } /> + } /> + + + ); }; export default Fence; diff --git a/packages/itmat-ui-react/src/Providers.tsx b/packages/itmat-ui-react/src/Providers.tsx index 2f510720c..e3315a658 100644 --- a/packages/itmat-ui-react/src/Providers.tsx +++ b/packages/itmat-ui-react/src/Providers.tsx @@ -1,17 +1,47 @@ -import { FunctionComponent, PropsWithChildren } from 'react'; -import { ApolloProvider } from '@apollo/client'; +import { FunctionComponent, PropsWithChildren, useState } from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; import { HelmetProvider } from 'react-helmet-async'; -import { client } from './components/apolloClient'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { httpLink } from '@trpc/client'; +import { trpc } from './utils/trpc'; +import { AuthProvider } from './utils/dmpWebauthn/webauthn.context'; -const Providers: FunctionComponent> = ({ children }) => ( - - - - {children} - - - -); +const Providers: FunctionComponent> = ({ children }) => { + const [queryClient] = useState(() => new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false + } + } + })); + const [trpcClient] = useState(() => + trpc.createClient({ + links: [ + httpLink({ + url: `${window.location.origin}/trpc`, + async headers() { + return { + authorization: document.cookie + }; + } + }) + ] + }) + ); + + return ( + + + + + + {children} + + + + + + ); +}; export default Providers; diff --git a/packages/itmat-ui-react/src/assets/manifest.json b/packages/itmat-ui-react/src/assets/manifest.json index 7cfdbf51c..065e90933 100644 --- a/packages/itmat-ui-react/src/assets/manifest.json +++ b/packages/itmat-ui-react/src/assets/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "IDEAFast", - "name": "IDEAFast Data Management Portal", + "short_name": "DMP", + "name": "Data Science Institute Data Management Portal", "icons": [ { "src": "favicon.ico", @@ -12,4 +12,4 @@ "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" -} \ No newline at end of file +} diff --git a/packages/itmat-ui-react/src/components/apolloClient.ts b/packages/itmat-ui-react/src/components/apolloClient.ts deleted file mode 100644 index 4c66dbb0a..000000000 --- a/packages/itmat-ui-react/src/components/apolloClient.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { InMemoryCache } from '@apollo/client/cache'; -import { ApolloClient, from, ApolloLink } from '@apollo/client'; -import { onError } from '@apollo/client/link/error'; -import createUploadLink from 'apollo-upload-client/createUploadLink.mjs'; -import axios from 'axios'; - -const customFetch = (uri, options): any => { - options.url = uri; - options.data = options.body; - if (options.body instanceof FormData) { - try { - const currentOperation = JSON.parse((options.body as FormData).get('operations') as string); - if (currentOperation.operationName === 'uploadFile') { - const description = JSON.parse(currentOperation.variables.description); - options.onUploadProgress = (progressEvent) => { - (window as any).onUploadProgressHackMap?.[`UP_${description.participantId}_${description.deviceId}_${description.startDate}_${description.endDate}`]?.(progressEvent); - }; - options.onDownloadProgress = (progressEvent) => { - (window as any).onUploadProgressHackMap?.[`DOWN_${description.participantId}_${description.deviceId}_${description.startDate}_${description.endDate}`]?.(progressEvent); - }; - } - } - catch (__unused__exception) { - // Do nothing - } - } - // return fetch(uri, options); - return axios(options).then(response => ({ - headers: response.headers, - ok: true, - redirected: false, - status: response.status, - statusText: response.statusText, - trailer: Promise.resolve(response.headers), - type: response.headers?.['Content-Type'], - body: response.data, - url: uri, - text: () => { - return Promise.resolve(JSON.stringify(response.data)); - } - })); -}; - -const uploadLink = createUploadLink({ - uri: `${window.location.origin}/graphql`, - credentials: 'include', - fetch: customFetch -}) as any as ApolloLink; - -const cache = new InMemoryCache({ - dataIdFromObject: (object) => { - switch (object.__typename) { - case 'OrganisationMetadata': - return `${object.siteIDMarker}`; - default: - return `${object.__typename || 'undefined_typeName'}___${object.id || 'undefined_id'}`; - - } - } -}); - -export const client = new ApolloClient({ - link: from([ - onError(({ response, graphQLErrors, networkError }) => { - if (graphQLErrors) { - if ((response as any).errors[0].message === 'NOT_LOGGED_IN') { - window.location.reload(); - } - graphQLErrors.map((error) => - console.error('[GraphQL error]', error.message ? error.message : error) - ); - } - if (networkError) { - console.error('[Network error]:', networkError.message ? networkError.message : networkError); - } - }), - uploadLink - ]), - cache -}); diff --git a/packages/itmat-ui-react/src/components/datasetDetail/index.tsx b/packages/itmat-ui-react/src/components/datasetDetail/index.tsx index f1c00ad81..dec3dd21a 100644 --- a/packages/itmat-ui-react/src/components/datasetDetail/index.tsx +++ b/packages/itmat-ui-react/src/components/datasetDetail/index.tsx @@ -1,72 +1,59 @@ import { FunctionComponent } from 'react'; -import { Query } from '@apollo/client/react/components'; import { NavLink, Route, Routes, useParams, Navigate } from 'react-router-dom'; -import { GET_STUDY, WHO_AM_I } from '@itmat-broker/itmat-models'; -import { userTypes } from '@itmat-broker/itmat-types'; +import { enumUserTypes } from '@itmat-broker/itmat-types'; import LoadSpinner from '../reusable/loadSpinner'; import css from './projectPage.module.css'; -import { DashboardTabContent, DataManagementTabContentFetch, ProjectsTabContent, AdminTabContent, FieldManagementTabContentFetch } from './tabContent'; +import { DashboardTabContent, AdminTabContent } from './tabContent'; import { FileRepositoryTabContent } from './tabContent/files/fileTab'; +import { trpc } from '../../utils/trpc'; +import { ConfigTabContent } from './tabContent/config/configTab'; export const DatasetDetailPage: FunctionComponent = () => { const { studyId } = useParams(); if (!studyId) return ; - return ( - - query={GET_STUDY} - variables={{ studyId }} - errorPolicy='ignore' // quick fix ; TO_DO change to split graphql requests coupled with UI - > - {({ loading, error, data }) => { - if (loading) { return ; } - if (error) { return

Error {JSON.stringify(error)}

; } - if (!data || !data.getStudy) { return
Oops! Cannot find this dataset.
; } - return
-
-

{data.getStudy.name.toUpperCase()}

-
- query={WHO_AM_I}> - {({ loading, error, data: sessionData }) => { - if (loading) return ; - if (error) return

{error.toString()}

; - if (sessionData.whoAmI.type === userTypes.ADMIN) { - return ( - <> - isActive ? css.active : undefined}>DASHBOARD - isActive ? css.active : undefined}>DATA STANDARDIZATION - isActive ? css.active : undefined}>DATA MANAGEMENT - isActive ? css.active : undefined}>FILES REPOSITORY - isActive ? css.active : undefined}>ADMINISTRATION - isActive ? css.active : undefined}>PROJECTS - - ); - } else { - return ( - <> - isActive ? css.active : undefined}>FILES REPOSITORY - isActive ? css.active : undefined}>DATA MANAGEMENT - isActive ? css.active : undefined}>PROJECTS - - ); - } - }} - -
-
-
- - } /> - } /> - } /> - } /> - } /> - } /> - } /> - -
-
; - }} - - ); + const getStudy = trpc.study.getStudies.useQuery({ studyId }); + const whoAmI = trpc.user.whoAmI.useQuery(); + + if (getStudy.isLoading) { + return ; + } + if (getStudy.isError || whoAmI.isError) { + return <> + An error occured. + ; + } + if (getStudy.data.length === 0) { + return

There is no dataset or you have not been granted access to any. Please contact your data custodian.

; + } + return
+
+

{getStudy.data[0].name.toUpperCase()}

+
+ { + whoAmI.isLoading ? : + whoAmI.data?.type === enumUserTypes.ADMIN ? ( + <> + isActive ? css.active : undefined}>CONFIG + isActive ? css.active : undefined}>DASHBOARD + isActive ? css.active : undefined}>FILES REPOSITORY + isActive ? css.active : undefined}>ADMINISTRATION + + ) : ( + isActive ? css.active : undefined}>FILES REPOSITORY + ) + } +
+
+
+ + } /> + } /> + } /> + } /> + } /> + +
+
; + }; diff --git a/packages/itmat-ui-react/src/components/datasetDetail/projectPage.module.css b/packages/itmat-ui-react/src/components/datasetDetail/projectPage.module.css index 21bd3c36e..cdeacd350 100644 --- a/packages/itmat-ui-react/src/components/datasetDetail/projectPage.module.css +++ b/packages/itmat-ui-react/src/components/datasetDetail/projectPage.module.css @@ -1,7 +1,7 @@ .page_container { grid-template-columns: 100%; grid-template-rows: 60px calc(100% - 60px); - grid-template-areas: + grid-template-areas: "ariane" "content"; display: grid; @@ -45,9 +45,9 @@ } .tabs a:hover { - -webkit-box-shadow:inset 0px -2px 0px 0px var(--color-primary-color); - -moz-box-shadow:inset 0px -3px 0px 0px var(--color-primary-color); - box-shadow:inset 0px -3px 0px 0px var(--color-primary-color); + -webkit-box-shadow: inset 0px -2px 0px 0px var(--color-primary-color); + -moz-box-shadow: inset 0px -3px 0px 0px var(--color-primary-color); + box-shadow: inset 0px -3px 0px 0px var(--color-primary-color); color: var(--color-button-active); } @@ -59,4 +59,4 @@ overflow: auto; grid-area: content; position: relative; -} \ No newline at end of file +} diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/admin/adminTab.tsx b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/admin/adminTab.tsx index 7ffe949a3..f3e6aaf36 100644 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/admin/adminTab.tsx +++ b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/admin/adminTab.tsx @@ -1,64 +1,58 @@ import { FunctionComponent, useState } from 'react'; -import { useQuery } from '@apollo/client/react/hooks'; import { Subsection } from '../../../reusable'; import LoadSpinner from '../../../reusable/loadSpinner'; import css from './tabContent.module.css'; import { RoleControlSection } from '../../../reusable/roleControlSection/roleControlSection'; -import { GET_STUDY, DELETE_STUDY, WHO_AM_I } from '@itmat-broker/itmat-models'; -import { Mutation, Query } from '@apollo/client/react/components'; import { Navigate, useParams } from 'react-router-dom'; -import { Button } from 'antd'; - +import { Button, message } from 'antd'; +import { trpc } from './../../../../utils/trpc'; +import { StudyVisibility } from '../../../reusable/studyVisibility/studyVisibility'; export const AdminTabContent: FunctionComponent = () => { - const { studyId } = useParams(); const [deleteButtonShown, setDeleteButtonShown] = useState(false); - const { data, loading } = useQuery(GET_STUDY, { variables: { studyId } }); + const getStudy = trpc.study.getStudies.useQuery({ studyId }); + const deleteStudy = trpc.study.deleteStudy.useMutation({ + onSuccess: () => { + void message.success('Study deleted successfully'); + }, + onError: () => { + void message.error('Failed to delete study'); + } + }); + + if (getStudy.isLoading) { + return ; + } + + if (getStudy.isError) { + return

An error occured

; + } - if (!studyId) + if (!getStudy.data[0]) { return null; - if (loading) - return ; + } + return (
- - - + +
+
+

Be careful to check all related projects and files before deleting this dataset!

- query={GET_STUDY} variables={{ studyId }}> - {({ loading, data, error }) => { - if (loading) { return ; } - if (error) { return

{error.toString()}

; } - - return - mutation={DELETE_STUDY} - refetchQueries={[ - { query: WHO_AM_I, variables: { fetchDetailsAdminOnly: false, fetchAccessPrivileges: false } } - ]} - > - - {(deleteStudy, { loading, error, data: StudyDeletedData }) => { - if (StudyDeletedData && StudyDeletedData.deleteStudy && StudyDeletedData.deleteStudy.successful) { - return ; - } - if (error) return

{error.message}

; - if (loading) - return ; - return ( - <> - {!deleteButtonShown ? : <>    } - - ); - }} - - ; - }} - - + {(() => { + if (!deleteStudy.isLoading && !deleteStudy.isError && deleteStudy.data) { + return ; + } + return !deleteButtonShown ? : + <> + + + ; + })()}
diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/admin/tabContent.module.css b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/admin/tabContent.module.css index c1830a9fa..0af02956a 100644 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/admin/tabContent.module.css +++ b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/admin/tabContent.module.css @@ -3,7 +3,7 @@ grid-template-columns: 60% 40%; grid-template-rows: 100%; height: 100%; - grid-template-areas: "main sub" + grid-template-areas: "main sub"; } .main_page { diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/config/configContent.module.css b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/config/configContent.module.css new file mode 100644 index 000000000..3fc1eb85d --- /dev/null +++ b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/config/configContent.module.css @@ -0,0 +1,46 @@ +.tab_page_wrapper { + padding: 2rem; + height: 100%; + overflow: auto; +} + +.page_container { + grid-template-columns: 100% 0%; + grid-template-rows: 100%; + display: grid; + height: 100%; +} + +.overview-header { + display: flex; + align-items: center; + /* This will vertically center align the icon and the title */ + /* If there is still a misalignment, you may adjust by adding 'align-self' to the children */ + font-size: 20px; + font-weight: 500; + overflow: auto; +} + +.overview-icon { + background-color: #000; + /* Or any color you prefer */ + width: 4px; + /* Thickness of the line */ + height: 24px; + /* Height of the line, adjust as needed */ + margin-right: 16px; + /* Space between the line and the text */ + /* Adjust 'margin-top' if the line is not aligned with the text */ +} + +.overview-title { + /* Style for your title */ + font-size: 24px; + font-weight: bold; +} + +.overview-content { + /* Style for your content */ + text-indent: 30px; + /* Adjust the indent as needed */ +} diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/config/configTab.tsx b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/config/configTab.tsx new file mode 100644 index 000000000..73080f17a --- /dev/null +++ b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/config/configTab.tsx @@ -0,0 +1,228 @@ +import { enumConfigType, enumStudyBlockColumnValueType, IStudy } from '@itmat-broker/itmat-types'; +import { FunctionComponent } from 'react'; +import css from './configContent.module.css'; +import { Button, Form, Input, List, message, Select, Table } from 'antd'; +import { trpc } from '../../../../utils/trpc'; +import LoadSpinner from '../../../reusable/loadSpinner'; +import { MinusOutlined, PlusOutlined } from '@ant-design/icons'; +import Column from 'antd/es/table/Column'; + +export const ConfigTabContent: FunctionComponent<{ study: IStudy }> = ({ study }) => { + const whoAmI = trpc.user.whoAmI.useQuery(); + const getStudyConfig = trpc.config.getConfig.useQuery({ + configType: enumConfigType.STUDYCONFIG, + key: study.id, + useDefault: true + }); + const getStudyFields = trpc.data.getStudyFields.useQuery({ studyId: study.id }); + const editConfig = trpc.config.editConfig.useMutation({ + onSuccess: () => { + void message.success('Config updated successfully.'); + }, + onError: (error) => { + void message.error('An error occured: ' + error); + } + }); + + const [form] = Form.useForm(); + + if (whoAmI.isLoading || getStudyConfig.isLoading || getStudyFields.isLoading) { + return ; + } + + if (whoAmI.isError || getStudyConfig.isError || getStudyFields.isError) { + return

An error occured.

; + } + + const secondLevelColumns = [{ + title: 'Title', + dataIndex: 'title', + key: 'title', + render: (_, record, index) => ( + + + + ) + }, { + title: 'Property', + dataIndex: 'property', + key: 'property', + render: (_, record, index) => ( + + + + ) + }, { + title: 'Type', + dataIndex: 'type', + key: 'type', + render: (_, record, index) => ( + + + + ) + }]; + + const firstLevelColumns = [{ + title: 'Title', + dataIndex: 'title', + key: 'title', + width: '10%', + render: (_, record, index) => ( + + + + ) + }, { + title: 'Field IDs', + dataIndex: 'fieldIds', + key: 'fieldIds', + width: '40%', + render: (_, record, index) => ( + + + + ) + }, { + title: 'Default File Columns', + dataIndex: 'defaultFileColumns', + key: 'defaultFileColumns', + width: '50%', + render: (_, record, index) => ( + + {(fields, { add, remove }) => ( + ( + + )} + > + {secondLevelColumns.map((col) => ( + ( + + {col.render(value, row, secondIndex)} + + )} + /> + ))} + ( +
+ )} +
+ ) + }]; + + return
+
+ +
+
+
+
Config
+
+
+
+ +
+
+ } + > + +
+ + + + + {(fileBlocks, { add, remove }) => ( + ( + + )} + > + {firstLevelColumns.map((col) => ( + ( + + {col.render(value, row, index)} + + )} + width={col.width} // Set width here + /> + ))} + ( +
+ )} +
+
+
+
+
; +
; +}; diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/dashboard/dashBoardTab.tsx b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/dashboard/dashBoardTab.tsx index e47320e34..10b13d665 100644 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/dashboard/dashBoardTab.tsx +++ b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/dashboard/dashBoardTab.tsx @@ -1,13 +1,345 @@ import { FunctionComponent } from 'react'; -import { IJobEntry } from '@itmat-broker/itmat-types'; -import { Subsection } from '../../../reusable/subsection/subsection'; -import { JobSection } from './jobs'; import css from './tabContent.module.css'; +import { trpc } from '../../../../utils/trpc'; +import { Button, Card, Col, Divider, Form, Input, List, Row, Statistic, Tooltip, message } from 'antd'; +import dayjs from 'dayjs'; +import { IDataSetSummary, IStudy } from '@itmat-broker/itmat-types'; +import { ResponsivePieCanvas } from '@nivo/pie'; +import { ResponsiveCalendar } from '@nivo/calendar'; + +export const DashboardTabContent: FunctionComponent<{ study: IStudy }> = ({ study }) => { + const getStudyDataSummary = trpc.data.getStudyDataSummary.useQuery({ studyId: study.id, useCache: false }); + + if (getStudyDataSummary.isLoading) { + return
+ Loading... +
; + } + if (getStudyDataSummary.isError) { + return
+ An error occured. +
; + } -export const DashboardTabContent: FunctionComponent<{ studyId: string; jobs: Array> }> = ({ studyId, jobs }) => { return
- - - +
+ +
+
+
+
Meta
+
+
+
+
+
+ } + > + + + +
+ +
+
+
+
Data Versioning
+
+
+
+
+
+ } + > + + + + + ; ; }; + +export const MetaBlock: FunctionComponent<{ study: IStudy, dataSummary: IDataSetSummary }> = ({ study, dataSummary }) => { + const getStudyRoles = trpc.role.getStudyRoles.useQuery({ studyId: study.id }); + const getUsers = trpc.user.getUsers.useQuery({}); + + if (getStudyRoles.isLoading || getUsers.isLoading) { + return
+ Loading... +
; + } + + if (getStudyRoles.isError || getUsers.isError) { + return
+ An error occured. +
; + } + + const totalUploaders = dataSummary.dataByUploaders.reduce((a, c) => a + c.count, 0); + + const dataByUploaders: { id: string, label: string, value: number }[] = [{ + id: 'Others', + label: 'Others', + value: 0 + }]; + dataSummary.dataByUploaders.sort((a, b) => b.count - a.count).forEach((el, index) => { + if (index < 10) { + const user = getUsers.data.find(user => user.id === el.userId); + dataByUploaders.push({ + id: user ? `${user.firstname} ${user.lastname}` : 'Unknown', + label: user ? `${user.firstname} ${user.lastname}` : 'Unknown', + value: el.count / totalUploaders + }); + } else { + dataByUploaders[0].value += el.count / totalUploaders; + } + }); + dataByUploaders.forEach(el => el.value = parseFloat((el.value * 100).toFixed(2))); + + const dataByUsers: { id: string, label: string, value: number }[] = [{ + id: 'Others', + label: 'Others', + value: 0 + }]; + + const totalUsers = dataSummary.dataByUsers.reduce((a, c) => a + c.count, 0); + dataSummary.dataByUsers.sort((a, b) => b.count - a.count).forEach((el, index) => { + if (index < 10) { + const user = getUsers.data.find(user => user.id === el.userId); + dataByUsers.push({ + id: user ? `${user.firstname} ${user.lastname}` : 'Unknown', + label: user ? `${user.firstname} ${user.lastname}` : 'Unknown', + value: el.count / totalUsers + }); + } else { + dataByUsers[0].value += el.count / totalUsers; + } + }); + dataByUsers.forEach(el => el.value = parseFloat((el.value * 100).toFixed(2))); + + + return
+
+ + +

{study.description}

+

{`Created On ${(new Date(study.life.createdTime)).toLocaleDateString()}`}

+
+ + + + el.users).reduce((a, c) => { + a = a.concat(c); + return a; + }, []))).length} /> + + + + + + + +

+
+ +
+ +
Sources
+
+ + +
+ +
Usage
+
+ +

+
; +}; + + +export const DataVersionBlock: FunctionComponent<{ study: IStudy, dataSummary: IDataSetSummary }> = ({ study, dataSummary }) => { + const [form] = Form.useForm(); + const createStudyDataVersion = trpc.study.createDataVersion.useMutation({ + onSuccess: () => { + void message.success('Data version created successfully'); + }, + onError: () => { + void message.error('Failed to create data version: '); + } + }); + + const calendarDate = study.dataVersions.map(el => { + return { + day: dayjs.unix(el.life.createdTime / 1000).format('YYYY-MM-DD'), + value: 1, + info: el.version + }; + }); + return
+
+ { + const version = calendarDate.find(el => el.day === day); + return {version ? version.info : 'No data version'}; + }} + /> +
+
+ Data Summaries +
+ + Versioned Data
} value={dataSummary.numberOfVersionedRecords} /> + + + Versioned Fields
} value={dataSummary.numberOfVersionedFields} /> + +
+
+ + Unversioned Data} value={dataSummary.numberOfUnversionedRecords} /> + + + Unversioned Fields} value={dataSummary.numberOfUnversionedFields} /> + +
+ Create New Data Version +
+ + + + + + + + + +
+ + ; +}; diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/dashboard/jobs.tsx b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/dashboard/jobs.tsx index da5da29f4..d11f1e7dc 100644 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/dashboard/jobs.tsx +++ b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/dashboard/jobs.tsx @@ -1,123 +1,6 @@ import { FunctionComponent } from 'react'; -import { InfoCircleOutlined } from '@ant-design/icons'; -import css from './tabContent.module.css'; -import { useSubscription } from '@apollo/client/react/hooks'; -import type { IJobEntry } from '@itmat-broker/itmat-types'; -import { GET_STUDY, SUBSCRIBE_TO_JOB_STATUS } from '@itmat-broker/itmat-models'; -import { Table, Button } from 'antd'; -const STATUSES: { [status: string]: any } = { - finished: () => Finished, - // error: (errors: string[]) => <>Errored, - error: (errors: string[]) => - Errored - -
-
-
    - {errors.map((el, ind) =>
  • {el}
  • )} -
-
-
- , - QUEUED: () => Queued, - PROCESSING: () => Processing, - CANCELLED: () => Cancelled -}; - -// const JOBTYPES: { [type: string]: any } = { -// DATA_UPLOAD_CSV: Data upload, -// DATA_UPLOAD_JSON: Data upload json, -// FIELD_INFO_UPLOAD: Field annotation upload -// }; - -export const JobSection: FunctionComponent<{ studyId: string; jobs: Array> }> = ({ studyId, jobs }) => { - useSubscription( - SUBSCRIBE_TO_JOB_STATUS, - { - variables: { studyId }, onSubscriptionData: ({ client: store, subscriptionData }) => { - if (subscriptionData.data.subscribeToJobStatusChange !== null) { - const olddata: any = store.readQuery({ query: GET_STUDY, variables: { studyId } }); - const oldjobs = olddata.getStudy.jobs; - const newjobs = oldjobs.map((el: any) => { - if (el.id === subscriptionData.data.subscribeToJobStatusChange.jobId) { - el.status = subscriptionData.data.subscribeToJobStatusChange.newStatus; - if (el.status === 'error') { - el.error = subscriptionData.data.subscribeToJobStatusChange.errors; - } - } - return el; - }); - const tmp = { ...olddata.getStudy, jobs: newjobs }; - store.writeQuery({ query: GET_STUDY, variables: { studyId }, data: { getStudy: tmp } }); - } - } - } - ); - const columns = [ - { - title: 'Created At', - dataIndex: 'requestTime', - key: 'requestTime', - render: (__unused__value, record) => { - return new Date(record.requestTime).toUTCString(); - } - }, - { - title: 'Job Type', - dataIndex: 'jobType', - key: 'jobType', - render: (__unused__value, record) => { - return record.jobType; - } - }, - { - title: 'Parameters', - dataIndex: 'parameters', - key: 'parameters', - render: (__unused__value, record) => { - return record.receivedFiles; - } - }, - { - title: 'Status', - dataIndex: 'status', - key: 'status', - render: (__unused__value, record) => { - return record.cancelled ? STATUSES.CANCELLED() : (STATUSES[record.status] || (() => null))(record.error); - } - }, - { - title: 'Cancel', - dataIndex: 'cancel', - key: 'cancel', - render: () => { - return (); - } - } - ]; - - return (
- rec.id} - columns={columns} - dataSource={jobs} - size='small' - pagination={ - { - defaultPageSize: 50, - showSizeChanger: true, - pageSizeOptions: ['20', '50', '100', '200'], - defaultCurrent: 1, - showQuickJumper: true - } - } - > -
-
); +export const JobSection: FunctionComponent = () => { + return null; }; diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/dashboard/tabContent.module.css b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/dashboard/tabContent.module.css index 023f92d39..621be9194 100644 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/dashboard/tabContent.module.css +++ b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/dashboard/tabContent.module.css @@ -4,70 +4,63 @@ overflow: auto; } -.finishedStatus_td { - background: var(--color-status-finish); - color: black; -} - -.errorStatus_td { - background: var(--color-status-error); -} - -.processingStatus_td { - background: var(--color-status-processing); - color: black; -} - -.queuedStatus_td { - background: var(--color-status-queued); +.page_container { + grid-template-columns: 100% 0%; + grid-template-rows: 100%; + display: grid; + height: 100%; } -.cancelledStatus_td { - background: var(--color-status-cancelled); +.overview-header { + display: flex; + align-items: center; + /* This will vertically center align the icon and the title */ + /* If there is still a misalignment, you may adjust by adding 'align-self' to the children */ + font-size: 20px; + font-weight: 500; + overflow: auto; } -.job_table td:nth-child(3) { - text-align: center; +.overview-icon { + background-color: #000; + /* Or any color you prefer */ + width: 4px; + /* Thickness of the line */ + height: 24px; + /* Height of the line, adjust as needed */ + margin-right: 16px; + /* Space between the line and the text */ + /* Adjust 'margin-top' if the line is not aligned with the text */ } -.cancelButton { +.overview-title { + /* Style for your title */ + font-size: 24px; font-weight: bold; - cursor: pointer; - color: #5a5a5a; } -.cancelButton:hover { - color: white; +.overview-content { + /* Style for your content */ + text-indent: 30px; + /* Adjust the indent as needed */ } -.error_wrapper { +.pie-chart-container { + height: 400px; + /* or any other appropriate height */ + width: 100%; position: relative; + /* Ensure the canvas doesn't overflow its container */ } -.errorStatus_td > svg { - height: 1rem; - width: 1rem; - cursor: context-menu -} - -.error_wrapper > div { - display: none; - width: 35rem; - max-height: 20rem; - top: -1rem; - overflow: auto; - padding: 1rem 0 0 1.7rem; - background: var(--color-popup); - box-shadow: 6px 6px 3px rgba(0, 0, 0, 0.4); - border: 1px solid var(--color-popup-border); - left: 5rem; +.pie-chart-title { position: absolute; + top: 50%; + left: 40%; + transform: translate(-50%, -50%); + font-size: 24px; + font-weight: bold; + color: #333; + pointer-events: none; + /* Ensure the title doesn't interfere with mouse events */ } - -.errorStatus_td > svg:hover + .error_wrapper > div { - display: unset; -} - -.error_wrapper > div:hover { - display: unset; -} \ No newline at end of file diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/data/dataSummary.tsx b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/data/dataSummary.tsx deleted file mode 100644 index 36f58cad1..000000000 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/data/dataSummary.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { FunctionComponent } from 'react'; -// import { Query } from '@apollo/client/react/components'; -import { GET_STUDY } from '@itmat-broker/itmat-models'; -import { IStudyDataVersion } from '@itmat-broker/itmat-types'; -import css from './tabContent.module.css'; -import { useQuery } from '@apollo/client/react/hooks'; -import LoadSpinner from '../../../reusable/loadSpinner'; -import { Tooltip } from 'antd'; - -export const DataSummaryVisual: FunctionComponent<{ studyId: string; selectedVersion: number; currentVersion: number; versions: IStudyDataVersion[] }> = ({ studyId, currentVersion, selectedVersion, versions }) => { - const { data: getStudyData, loading: getStudyLoading } = useQuery(GET_STUDY, { variables: { studyId } }); - if (getStudyLoading) { return ; } - - return <> - {selectedVersion === currentVersion ? null : <>Warning: You are not looking at the current version of the data.


} -
- - - - - -
- - ; -}; - -////////////////////////// COMPONENTS WITHIN THE PAGE////////////////////////////////////// -const NumberOfPatients: FunctionComponent<{ data: any }> = ({ data }) => { - return
-
-

Number of subjects

- - - {`${data.getStudy.subjects[0].length}/${data.getStudy.subjects[1].length}`} - - -
-
; -}; - -const NumberOfRecords: FunctionComponent<{ data: any }> = ({ data }) => { - return
-
-

Number of records

- - - {`${data.getStudy.numOfRecords[0]}/${data.getStudy.numOfRecords[1]}`} - - -
-
; -}; - -const NewestVersionOfData: FunctionComponent<{ version: string, tag: string }> = ({ version, tag }) => { - return
-

Current Data version

- {`${version} : ${tag}`} -
-
; -}; - -const NumberOfVisits: FunctionComponent<{ data: any }> = ({ data }) => { - return
-
-

Number of visits

- - - {`${data.getStudy.visits[0].length}/${data.getStudy.visits[1].length}`} - - -
-
; -}; - -const DateOfUpload: FunctionComponent<{ date: string | number /* UNIX timestamp */ }> = ({ date }) => { - return
-

Data were uploaded on

- {date ? (new Date(parseInt(date as any))).toLocaleString() : 'n/a'} -
; -}; - -/////////////////////////////////////////////////////////////////////////////////// - diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/data/dataTab.tsx b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/data/dataTab.tsx deleted file mode 100644 index 5ece8795e..000000000 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/data/dataTab.tsx +++ /dev/null @@ -1,236 +0,0 @@ -import { Fragment, FunctionComponent, useState } from 'react'; -import { generateCascader } from '../../../../utils/tools'; -import { useQuery, useMutation } from '@apollo/client/react/hooks'; -import { Query } from '@apollo/client/react/components'; -import { GET_STUDY, GET_STUDY_FIELDS, GET_DATA_RECORDS, CREATE_NEW_DATA_VERSION, SET_DATAVERSION_AS_CURRENT, WHO_AM_I, GET_ONTOLOGY_TREE } from '@itmat-broker/itmat-models'; -import { userTypes, IOntologyRoute } from '@itmat-broker/itmat-types'; -import LoadSpinner from '../../../reusable/loadSpinner'; -import { Subsection, SubsectionWithComment } from '../../../reusable/subsection/subsection'; -import { DataSummaryVisual } from './dataSummary'; -import { FieldListSection } from '../../../reusable/fieldList/fieldList'; -import css from './tabContent.module.css'; -import { Button, Form, Input, Modal, Table, Tooltip, Select, Cascader } from 'antd'; -import { useParams } from 'react-router-dom'; -const { Option } = Select; - -export const DataManagementTabContentFetch: FunctionComponent = () => { - const { studyId } = useParams(); - const { loading: getStudyLoading, error: getStudyError, data: getStudyData } = useQuery(GET_STUDY, { variables: { studyId: studyId } }); - const { loading: getStudyFieldsLoading, error: getStudyFieldsError, data: getStudyFieldsData } = useQuery(GET_STUDY_FIELDS, { variables: { studyId: studyId } }); - const { loading: getDataRecordsLoading, error: getDataRecordsError, data: getDataRecordsData } = useQuery(GET_DATA_RECORDS, { - variables: { - studyId: studyId, versionId: null, queryString: { - data_requested: null, - new_fields: null, - cohort: null, - format: 'raw' - } - } - }); - const { loading: getOntologyTreeLoading, error: getOntologyTreeError, data: getOntologyTreeData } = useQuery(GET_ONTOLOGY_TREE, { - variables: { - studyId: studyId, - treeId: null - } - }); - const { loading: whoAmILoading, error: whoAmIError, data: whoAmIData } = useQuery(WHO_AM_I); - const [createNewDataVersion] = useMutation(CREATE_NEW_DATA_VERSION); - const [setDataVersion, { loading }] = useMutation(SET_DATAVERSION_AS_CURRENT); - const [selectedPath, setSelectedPath] = useState([]); - const [selectedVersion, setSelectedVersion] = useState(getStudyData.getStudy.currentDataVersion); - const [isModalOn, setIsModalOn] = useState(false); - const [treeId, setTreeId] = useState(''); - if (getStudyLoading || getStudyFieldsLoading || getDataRecordsLoading || whoAmILoading || getOntologyTreeLoading) { - return ; - } - if (!studyId || getStudyError || getStudyFieldsError || getDataRecordsError || whoAmIError || getOntologyTreeError) { - return

- An error occured, please contact your administrator -

; - } - const showSaveVersionButton = true; - // build the table columns for unversioned data - const columns = [ - { - title: 'Subject ID', - dataIndex: 'subjectId', - key: 'subjectId', - render: (__unused__value, record) => { - return record.subjectId; - } - }, - { - title: 'Visit ID', - dataIndex: 'visitId', - key: 'visitId', - render: (__unused__value, record) => { - return record.visitId.join('\t'); - } - } - ]; - - const versions: any = []; - for (const item of getStudyData.getStudy.dataVersions) { - versions[item['version']] = item['id']; - } - - //construct the cascader - const fieldPathOptions: any = []; - getOntologyTreeData.getOntologyTree.filter(el => el.id === treeId)[0]?.routes.forEach(el => { - generateCascader(el, fieldPathOptions, false); - }); - const routes: IOntologyRoute[] = getOntologyTreeData.getOntologyTree.filter(es => es.id === treeId)[0]?.routes?.filter(es => { - return JSON.stringify(es.path) === JSON.stringify(selectedPath); - }) || []; - const fields: string[] = routes.length !== 0 ? routes.map(es => JSON.stringify(es.field)) : []; - - return
- {getStudyData.getStudy.currentDataVersion !== -1 ? -
- {getStudyData.getStudy.dataVersions.length >= 1 ? <> - { - getStudyData.getStudy.dataVersions.map((el, ind) => - -
{ setSelectedVersion(ind); }} - className={css.data_version_cube + (ind === selectedVersion ? (ind === getStudyData.getStudy.currentDataVersion ? ` ${css.data_version_cube_current}` : ` ${css.data_version_cube_selected_not_current}`) : '')}> - { - {el.version} - } -
- {ind === getStudyData.getStudy.dataVersions.length - 1 ? null : âŸļ} -
- ) - } - - {showSaveVersionButton && (selectedVersion !== getStudyData.getStudy.currentDataVersion) ? - - : null - }
- : null} -
: null} -
- - - setSelectedPath(value)} - placeholder={'Please select'} - /> - }> - { - if (!routes) { - return false; - } - if (fields.includes(JSON.stringify(['$' + el.fieldId.toString()]))) { - return true; - } - return false; - })]} - verbal={true} - > - {/* */} -

- -
-
- -
- {whoAmIData.whoAmI.type === userTypes.ADMIN ? - - setIsModalOn(false)} - onCancel={() => setIsModalOn(false)} - > - query={GET_DATA_RECORDS} variables={{ - studyId: studyId, versionId: null, queryString: { - data_requested: null, - new_fields: null, - cohort: null - } - }}> - {({ data, loading, error }) => { - if (loading) { return ; } - if (error) { return

{JSON.stringify(error)}

; } - if (!data) { return

Not executed.

; } - const parsedData = getDataRecordsData.getDataRecords.data; - const groupedData: any = {}; - for (const key in parsedData) { - if (!(key in groupedData)) { - groupedData[key] = []; - } - groupedData[key].push(Object.keys(parsedData[key])); - } - return ( rec.id} - pagination={false} - columns={columns} - dataSource={Object.keys(groupedData).map((el) => { - return { - id: el, - subjectId: el, - visitId: groupedData[el] - }; - })} - size='middle' - >
); - }} - -
- -
{ - createNewDataVersion({ - variables: { - ...variables, - withUnversionedData: variables.withUnversionedData === 'true' ? true : false, - studyId: studyId - } - }); - }}> - - - - - - - - - -
-
: null} - < Subsection title='Data Summary'> - - -
-
; -}; diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/data/fieldListSelection.tsx b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/data/fieldListSelection.tsx deleted file mode 100644 index ce5f745c0..000000000 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/data/fieldListSelection.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { FunctionComponent, useState } from 'react'; -import { Query } from '@apollo/client/react/components'; -import { useQuery } from '@apollo/client/react/hooks'; -import { GET_STUDY_FIELDS, GET_STUDY } from '@itmat-broker/itmat-models'; -import { IStudyDataVersion } from '@itmat-broker/itmat-types'; -import { FieldListSection } from '../../../reusable/fieldList/fieldList'; -import LoadSpinner from '../../../reusable/loadSpinner'; -// number of patients -// newest version of data - date / tag -// download data -// data curation pipeline -// upload new sets of data - -export const FieldListSelectionSection: FunctionComponent<{ studyId: string; selectedVersion: number; currentVersion: number; versions: IStudyDataVersion[] }> = ({ studyId, currentVersion, selectedVersion }) => { - const { loading: getStudyLoading, error: getStudyError, data: getStudyData } = useQuery(GET_STUDY, { variables: { studyId: studyId } }); - const { loading: getStudyFieldsLoading, error: getStudyFieldsError, data: getStudyFieldsData } = useQuery(GET_STUDY_FIELDS, { variables: { studyId: studyId } }); - if (getStudyLoading || getStudyFieldsLoading) { - return ; - } - if (getStudyError || getStudyFieldsError) { - return

- An error occured, please contact your administrator -

; - } - // leave as placeholder; - // const { fieldTrees } = versions[selectedVersion]; - const fieldTrees = getStudyFieldsData.getStudyFields; - - return <> - {selectedVersion === currentVersion ? null : <>Warning: You are not looking at the current version of the data.


} - - { - fieldTrees.length === 0 ? -

There is no field annotations uploaded for this data version yet.

- : - - } - ; -}; - -const FieldListSelectionState: FunctionComponent<{ studyId: string; fieldTreeIds: string[], studyData: any }> = ({ studyId, fieldTreeIds, studyData }) => { - const [selectedTree, setSelectedTree] = useState(fieldTreeIds[0]); - - return <> -

- query={GET_STUDY_FIELDS} variables={{ studyId, fieldTreeId: selectedTree }}> - {({ data, loading, error }) => { - if (loading) { return ; } - if (error) { return

{JSON.stringify(error)}

; } - if (!data || !data.getStudyFields || data.getStudyFields.length === 0) { return

There is no field annotations uploaded for this tag.

; } - return ; - }} - - ; - -}; diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/data/fieldTab.tsx b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/data/fieldTab.tsx deleted file mode 100644 index bdd2e2b65..000000000 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/data/fieldTab.tsx +++ /dev/null @@ -1,239 +0,0 @@ -import { FunctionComponent, useEffect, useState } from 'react'; -import { useQuery, useMutation } from '@apollo/client/react/hooks'; -import { CREATE_STANDARDIZATION, GET_STANDARDIZATION, GET_ONTOLOGY_TREE } from '@itmat-broker/itmat-models'; -import { IStandardization } from '@itmat-broker/itmat-types'; -import { DeleteOutlined, FrownTwoTone, SmileTwoTone, PlusOutlined } from '@ant-design/icons'; -import LoadSpinner from '../../../reusable/loadSpinner'; -import { generateCascader } from '../../../../utils/tools'; -import css from './tabContent.module.css'; -import { Button, Input, Select, notification, Space, Form, Cascader } from 'antd'; -const { Option } = Select; - -export const FieldManagementTabContentFetch: FunctionComponent<{ studyId: string }> = ({ studyId }) => { - const { loading: getStandardizationLoading, error: getStandardizationError, data: getStandardizationData } = useQuery(GET_STANDARDIZATION, { variables: { studyId: studyId } }); - const { loading: getOntologyTreeLoading, error: getOntologyTreeError, data: getOntologyTreeData } = useQuery(GET_ONTOLOGY_TREE, { - variables: { - studyId: studyId, - treeId: null - } - }); - const [createStandardization, { - loading: editFieldLoading - }] = useMutation(CREATE_STANDARDIZATION, { - onCompleted: () => { - notification.open({ - message: 'Success', - description: - `Std for ${selectedStd.field} has been updated.`, - icon: - }); - }, - onError: () => { - notification.open({ - message: 'Failed', - description: - `Field ${selectedStd.field.toString()} failed to update.`, - icon: - }); - } - }); - - const [editMode, setEditMode] = useState(false); - const [format, setFormat] = useState(undefined); - const [selectedStd, setSelectedStd] = useState({ - id: '', - studyId: studyId, - type: format || '', - field: [], - path: [], - stdRules: [], - joinByKeys: [], - deleted: null - }); - const [form] = Form.useForm(); - useEffect(() => { - form.setFieldsValue(selectedStd); - }, [form, selectedStd]); - if (getStandardizationLoading || getOntologyTreeLoading || editFieldLoading) { - return ; - } - if (getStandardizationError || getOntologyTreeError) { - return

- An error occured, please contact your administrator -

; - } - const availableFormats: string[] = Array.from(new Set(getStandardizationData.getStandardization.map(el => el.type))); - const fieldPathOptions: any = []; - getOntologyTreeData.getOntologyTree.filter(el => el.name === format)[0]?.routes.forEach(el => { - generateCascader(el, fieldPathOptions, true); - }); - return
- - - { - const searchedRoute: any = getOntologyTreeData.getOntologyTree - .filter(el => el.name === format)[0].routes?.filter(el => JSON.stringify(el.path.concat(el.name)) === JSON.stringify(value))[0]; - if (searchedRoute === undefined) { - return; - } - const searchedStd: IStandardization | undefined = getStandardizationData.getStandardization.filter(el => { - return JSON.stringify(el.field) === JSON.stringify(searchedRoute.field); - })[0]; - if (searchedStd === undefined) { - return; - } - setSelectedStd({ - ...searchedStd, - field: searchedStd.field, - path: searchedStd.path - }); - }} - /> - -

- -
{ - const stdRules: any[] = []; - [...value.stdRules].forEach(el => { - delete el.__typename; - delete el.id; - stdRules.push(el); - }); - createStandardization({ - variables: { - studyId: studyId, - standardization: { - type: selectedStd.type, - field: value.field, - path: value.path, - joinByKeys: value.joinByKeys, - stdRules: stdRules - } - } - }); - }} - > - - - - - - - - - - - - - - remove(name)} /> - - ))} - - - - - )} - -
- { - editMode ? - - - : null - } - - ; -}; diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/data/tabContent.module.css b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/data/tabContent.module.css deleted file mode 100644 index 30e61b3b1..000000000 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/data/tabContent.module.css +++ /dev/null @@ -1,149 +0,0 @@ -.tab_page_wrapper { - padding: 2rem; -} - -.scaffold_wrapper { - height: 100%; -} - -.scaffold_wrapper>div { - height: 100%; - overflow: auto; -} - -.field_management_section { - display: grid; - grid-template-columns: 100%; - grid-template-rows: auto; - grid-template-areas: - "a a" - "left right" - "c d" -} - -.left_panel { - grid-area: leftPanel; -} - -.top_panel { - grid-area: versions; - background: var(--color-dataversions-background); - padding: 1rem 1.5rem 0rem 1.5rem; -} - -.right_panel { - grid-area: rightPanel; - background: var(--color-data-background); -} - -.add_new_fields_section { - background: var(--color-pop-up); - border-radius: 0.2rem; - padding: 1rem; - margin-bottom: 1rem; -} - -.add_new_fields_section h5 { - display: inline-block; - cursor: pointer; - font-size: 1rem; - padding: 0.5rem; - border-bottom: 1px solid var(--color-primary-color) -} - -.add_new_fields_section .selected_tab { - background-color: var(--color-primary-color); -} - -.data_summary_section { - display: grid; - grid-template-columns: 30% 5% 30% 5% 30%; - /* grid-template-rows: 150px 150px; */ - grid-template-areas: "patients empty1 records empty2 unversioned" - "version empty1 visits empty2 empty3" - "date date date empty5 dummy" - "filename filename filename filename filename"; - height: 100%; -} - -.data_summary_section>div { - background-color: var(--color-ariane-tab-active-background); - padding: 1rem; - margin-bottom: 2rem; - overflow: hidden; -} - -.data_summary_section>div>div { - height: 100%; - width: 100%; - overflow: hidden; -} - - -.top_panel h5 { - font-size: 1rem; - margin-right: 25rem; - display: inline-block; -} - -.number_highlight { - font-size: 1.7rem; - color: var(--color-primary-color); - font-weight: bold; -} - -.data_version_cube { - display: inline-block; - margin-bottom: 1rem; - padding: 0.7rem; - background-color: rgba(228, 228, 228, 0.1); - cursor: pointer; - /* box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.3); */ -} - -.data_version_cube:hover { - -webkit-box-shadow: inset 0px -2px 0px 0px var(--color-primary-color); - -moz-box-shadow: inset 0px -2px 0px 0px var(--color-primary-color); - box-shadow: inset 0px -2px 0px 0px var(--color-primary-color); -} - -.data_version_cube_current { - background-color: var(--color-status-finish); - /* color: white; */ -} - -.data_version_cube_selected_not_current { - background-color: #c1c1c1; - color: black; -} - -.arrow { - font-weight: bold; - color: #d2d2d2; -} - -.versioning_section_button { - margin-left: 3rem; - width: unset; - display: inline-block; - font-weight: normal; - -} - -.switchButton { - display: inline-block; - width: 3rem; - transform: scale(0.7) -} - -.switchButton:hover { - background: rgb(90, 90, 90) -} - -.infocircle { - height: 1rem; -} - -.ant-table table { - font-size: 16px; -} diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/data/uploadNewData.tsx b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/data/uploadNewData.tsx deleted file mode 100644 index 239c4d5d3..000000000 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/data/uploadNewData.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { FunctionComponent, useState } from 'react'; -import { Mutation, Query } from '@apollo/client/react/components'; -import { NavLink } from 'react-router-dom'; -import { CREATE_DATA_CURATION_JOB, GET_STUDY } from '@itmat-broker/itmat-models'; -import { IFile } from '@itmat-broker/itmat-types'; -import LoadSpinner from '../../../reusable/loadSpinner'; -import { Select, Button } from 'antd'; -const { Option } = Select; - -export const UploadNewData: FunctionComponent<{ studyId: string; cancelButton?: () => void }> = ({ studyId, cancelButton }) => { - return
-

To upload a new version of the dataset, please make sure you have uploaded the data file to the file repository.

-

- - query={GET_STUDY} variables={{ studyId }}> - {({ loading, data, error }) => { - if (loading) { return ; } - if (error) { return

{error.toString()}

; } - if (!data.getStudy || data.getStudy.files === undefined || data.getStudy.files.length === 0) { - return null; - } - return ; - }} - - -
; -}; - -const UploadNewDataForm: FunctionComponent<{ studyId: string; files: IFile[]; cancelButton?: () => void }> = ({ cancelButton, files, studyId }) => { - const [error, setError] = useState(''); - const [successfullySaved, setSuccessfullySaved] = useState(false); - const [selectedFile, setSelectedFile] = useState([]); // files.length > 0 because of checks above - const handleCancel = cancelButton === undefined ? (() => setSelectedFile([])) : cancelButton; - - const filteredFiles = files.filter(el => !( - el.fileName.indexOf('VariablesList') >= 0 - || el.fileName.indexOf('Site') >= 0 - || el.fileName.indexOf('Codes') >= 0 - || el.fileName.indexOf('SubjectGroup') >= 0 - || el.fileName.indexOf('Tables') >= 0 - || el.fileName.indexOf('Visits') >= 0 - )); - - return <> - Data file: -

- - - mutation={CREATE_DATA_CURATION_JOB} - onCompleted={() => setSuccessfullySaved(true)} - // update={(store, { data: { createDataCurationJob } }) => { - // // Read the data from our cache for this query. - // const data: any = store.readQuery({ query: GET_STUDY, variables: { studyId } }); - // // Add our comment from the mutation to the end. - // const newjobs = data.getStudy.jobs.concat(createDataCurationJob); - // data.getStudy.jobs = newjobs; - // // Write our data back to the cache. - // store.writeQuery({ query: GET_STUDY, variables: { studyId }, data }); - // }} - > - {(createCurationJob, { loading, error }) => { - if (loading) { return ; } - if (error) { return ; } - return ; - }} - - - - {error ?
{error}
: null} - {successfullySaved ?
Job created and queued.
: null} - ; - -}; - -// const CreateNewDataVersion: FunctionComponent<{ studyId: string; dataVersion: string; tag: string }> = ({ studyId, dataVersion, tag }) => { - -// }; diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/data/uploadNewFields.tsx b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/data/uploadNewFields.tsx deleted file mode 100644 index fb571a319..000000000 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/data/uploadNewFields.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { createRef, FunctionComponent, useState } from 'react'; -import { Mutation, Query } from '@apollo/client/react/components'; -import { NavLink } from 'react-router-dom'; -import { CREATE_FIELD_CURATION_JOB, GET_STUDY } from '@itmat-broker/itmat-models'; -import { IFile } from '@itmat-broker/itmat-types'; -import LoadSpinner from '../../../reusable/loadSpinner'; -import css from './tabContent.module.css'; -import { Button, Select } from 'antd'; -const { Option } = Select; - -export const UploadNewFields: FunctionComponent<{ studyId: string }> = ({ studyId }) => { - const [expanded, setExpanded] = useState(false); - const [error, setError] = useState(''); - const [uploadFileTabSelected, setUploadFileTabSelected] = useState(true); - const fileRef = createRef(); - - if (!expanded) { - return ; - } - - return
-

Please note that you will need to upload an entire set of annotations and not just the delta.

-

You can either upload a new annotation files or select one previously uploaded file:

-
setUploadFileTabSelected(true)} className={uploadFileTabSelected ? css.selected_tab : ''}>Upload a new file
setUploadFileTabSelected(false)} className={!uploadFileTabSelected ? css.selected_tab : ''}>Select from existing file
- { - uploadFileTabSelected ? - <> -
- - mutation={CREATE_FIELD_CURATION_JOB}> - {(createCurationJob, { loading }) => { - if (loading) { return ; } - return ( - - ); - }} - - - {error ?
{error}
: null} - - : - - } -
; -}; - - -const UploadFieldBySelectingFileFormFetch: FunctionComponent<{ studyId: string; cancel: (__unused__expanded: boolean) => void }> = ({ studyId, cancel }) => { - return query={GET_STUDY} variables={{ studyId }}> - {({ loading, data, error }) => { - if (loading) return ; - if (error) return

{error.toString()}

; - if (!data.getStudy || data.getStudy.files === undefined || data.getStudy.files.length === 0) { - return

No file has been uploaded to this dataset yet. You can do this in the file repository

; - } - return ; - }} - ; -}; - -const UploadFieldBySelectingFileForm: FunctionComponent<{ studyId: string; files: IFile[]; cancel: (__unused__expanded: boolean) => void }> = ({ cancel, studyId, files }) => { - const [error, setError] = useState(''); - const [successfullySaved, setSuccessfullySaved] = useState(false); - const [selectedFile, setSelectedFile] = useState(''); // files.length > 0 because of checks above - const [tag, setTag] = useState(''); - - return
- -

- - { setTag(e.target.value); setError(''); setSuccessfullySaved(false); }} placeholder='e.g main tree' type='text' />

- mutation={CREATE_FIELD_CURATION_JOB} onCompleted={() => setSuccessfullySaved(true)}> - {(createCurationJob, { loading }) => { - if (loading) { return ; } - return ; - }} - - - - {error ?
{error}
: null} - {successfullySaved ?
Job created and queued.
: null} -
; -}; diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/files/tabContent.module.css b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/files/fileRepo.module.css similarity index 54% rename from packages/itmat-ui-react/src/components/datasetDetail/tabContent/files/tabContent.module.css rename to packages/itmat-ui-react/src/components/datasetDetail/tabContent/files/fileRepo.module.css index 7f22438d9..66797881f 100644 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/files/tabContent.module.css +++ b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/files/fileRepo.module.css @@ -9,7 +9,7 @@ grid-template-columns: 65% 35%; grid-template-rows: 100%; height: 100%; - grid-template-areas: "leftPanel rightPanel" + grid-template-areas: "leftPanel rightPanel"; } .left_panel { @@ -52,7 +52,7 @@ color: white; font-size: 1rem; padding: 0.5rem; - border-bottom: 1px solid var(--color-primary-color) + border-bottom: 1px solid var(--color-primary-color); } .add_new_fields_section .selected_tab { @@ -60,7 +60,7 @@ } .drop_overlay::after { - content: ''; + content: ""; position: absolute; border: 0.5rem dashed #555; z-index: 10000; @@ -70,6 +70,45 @@ left: 0.5rem; } -.editable_row [class*=ant-form-item-explain] { +.editable_row [class*="ant-form-item-explain"] { display: none; } + +.custom-text-hover { + cursor: pointer; + transition: all 0.3s ease; +} + +.overview-header { + display: flex; + align-items: center; + /* This will vertically center align the icon and the title */ + /* If there is still a misalignment, you may adjust by adding 'align-self' to the children */ + font-size: 20px; + font-weight: 500; + overflow: auto; +} + +.overview-icon { + background-color: #000; + /* Or any color you prefer */ + width: 4px; + /* Thickness of the line */ + height: 24px; + /* Height of the line, adjust as needed */ + margin-right: 16px; + /* Space between the line and the text */ + /* Adjust 'margin-top' if the line is not aligned with the text */ +} + +.overview-title { + /* Style for your title */ + font-size: 24px; + font-weight: bold; +} + +.overview-content { + /* Style for your content */ + text-indent: 30px; + /* Adjust the indent as needed */ +} diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/files/fileTab.tsx b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/files/fileTab.tsx index 7773cce54..3abaf3d10 100644 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/files/fileTab.tsx +++ b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/files/fileTab.tsx @@ -1,671 +1,807 @@ -import { FunctionComponent, useState, useEffect, useRef, useContext, Fragment, HTMLAttributes, createContext, ReactNode } from 'react'; -import { Button, Upload, notification, Tag, Table, Form, Input, InputRef, DatePicker, Space, Modal } from 'antd'; -import { RcFile } from 'antd/es/upload'; -import { UploadOutlined, DeleteOutlined } from '@ant-design/icons'; -import { Query } from '@apollo/client/react/components'; -import { useApolloClient, useMutation, useQuery } from '@apollo/client/react/hooks'; -import { useDropzone } from 'react-dropzone'; -import { GET_STUDY, UPLOAD_FILE, GET_ORGANISATIONS, GET_USERS, EDIT_STUDY, WHO_AM_I } from '@itmat-broker/itmat-models'; -import { IFile, userTypes, deviceTypes } from '@itmat-broker/itmat-types'; -import { FileList, formatBytes } from '../../../reusable/fileList/fileList'; +import React, { FunctionComponent, useState } from 'react'; +import { Progress, Button, Table, List, Modal, Upload, Form, Select, Input, notification, message, Typography, Tooltip } from 'antd'; +import { CloudDownloadOutlined, InboxOutlined, NumberOutlined } from '@ant-design/icons'; +import { enumConfigType, IStudyConfig, IStudy, IField, enumDataTypes, IStudyFileBlock, enumUserTypes, IUserWithoutToken, deviceTypes, enumStudyBlockColumnValueType, IFile } from '@itmat-broker/itmat-types'; import LoadSpinner from '../../../reusable/loadSpinner'; -import { Subsection, SubsectionWithComment } from '../../../reusable/subsection/subsection'; -import css from './tabContent.module.css'; -import { ApolloError } from '@apollo/client/errors'; -import { validate } from '@ideafast/idgen'; -import dayjs, { Dayjs } from 'dayjs'; -import { v4 as uuid } from 'uuid'; - -type StudyFile = RcFile & { - uuid: string; - participantId?: string; - deviceId?: string; - startDate?: Dayjs; - endDate?: Dayjs; - tup?: string; -} - -const { RangePicker } = DatePicker; -let progressReports: any[] = []; +import css from './fileRepo.module.css'; +import { trpc } from '../../../../utils/trpc'; +import { formatBytes, stringCompareFunc, tableColumnRender } from '../../../../utils/tools'; +import { UploadChangeParam } from 'antd/lib/upload'; +import { RcFile, UploadFile } from 'antd/lib/upload/interface'; +import axios from 'axios'; +import dayjs from 'dayjs'; +import Highlighter from 'react-highlight-words'; +import { ResponsiveLine } from '@nivo/line'; +import { ResponsiveBar } from '@nivo/bar'; +import { useQueryClient } from '@tanstack/react-query'; +const { Option } = Select; + +export const FileRepositoryTabContent: FunctionComponent<{ study: IStudy }> = ({ study }) => { + const whoAmI = trpc.user.whoAmI.useQuery(); + const getStudyConfig = trpc.config.getConfig.useQuery({ configType: enumConfigType.STUDYCONFIG, key: study.id, useDefault: true }); + const getStudyFields = trpc.data.getStudyFields.useQuery({ studyId: study.id }); + + if (whoAmI.isLoading || getStudyConfig.isLoading || getStudyFields.isLoading) { + return <> +
Loading...
+
+ +
+ ; + } -export const FileRepositoryTabContent: FunctionComponent<{ studyId: string }> = ({ studyId }) => { + if (whoAmI.isError || getStudyConfig.isError || getStudyFields.isError) { + const errorMessage = ( +
+

An error occurred:

+ {whoAmI.isError &&

{`whoAmI error: ${whoAmI.error.message}`}

} + {getStudyConfig.isError &&

{`getStudyConfig error: ${getStudyConfig.error.message}`}

} + {getStudyFields.isError &&

{`getStudyFields error: ${getStudyFields.error.message}`}

} +
+ ); - const [uploadMovement, setUploadMovement] = useState(0); - const [isDropOverlayShowing, setisDropOverlayShowing] = useState(false); - const [fileList, setFileList] = useState([]); - const [isUploading, setIsUploading] = useState(false); - const store = useApolloClient(); - const { loading: getOrgsLoading, error: getOrgsError, data: getOrgsData } = useQuery(GET_ORGANISATIONS); - const { loading: getStudyLoading, error: getStudyError, data: getStudyData } = useQuery(GET_STUDY, { variables: { studyId: studyId } }); - const { loading: getUsersLoading, error: getUsersError, data: getUsersData } = useQuery(GET_USERS, { variables: { fetchDetailsAdminOnly: false, fetchAccessPrivileges: false } }); - const { loading: whoAmILoading, error: whoAmIError, data: whoAmIData } = useQuery(WHO_AM_I); - const [searchTerm, setSearchTerm] = useState(''); - const [isEditingDescription, setIsEditingDescription] = useState(false); - const [datasetDescription, setDatasetDescription] = useState(''); - const [isFileSummaryShown, setIsFileSummaryShown] = useState(false); - const [editStudy] = useMutation(EDIT_STUDY, { - onCompleted: () => { window.location.reload(); }, - onError: () => { return; } - }); - const [uploadFile] = useMutation(UPLOAD_FILE, { - onCompleted: ({ uploadFile }) => { - const cachedata = store.readQuery({ - query: GET_STUDY, - variables: { studyId } - }) as any; - if (!cachedata) - return; - const newcachedata = { - ...cachedata.getStudy, - files: [...cachedata.getStudy.files, uploadFile] - }; - store.writeQuery({ - query: GET_STUDY, - variables: { studyId }, - data: { getStudy: newcachedata } - }); - }, - onError: (error: ApolloError) => { - notification.error({ - message: 'Upload error!', - description: error.message ?? 'Unknown Error Occurred!', - placement: 'topRight', - duration: 0 - }); + return
{errorMessage}
; + } + return
+ +
+
+
Description
+
+
+ } + > + + + {study.description} + + + + { + (getStudyConfig.data ? (getStudyConfig.data.properties as IStudyConfig).defaultFileBlocks : []).map((block, index) => ( + + )) } + ; +}; + +export const UploadFileComponent: FunctionComponent<{ study: IStudy, fields: IField[], fieldIds: string[], setIsUploading: (isUploading: boolean) => void, setProgress: (progress: number) => void }> = ({ study, fields, fieldIds, setIsUploading, setProgress }) => { + const queryClient = useQueryClient(); + const [__unused__api, contextHolder] = notification.useNotification(); + const [isShowPanel, setIsShowPanel] = React.useState(false); + const [fileList, setFileList] = useState([]); + const [fileProperties, setFileProperties] = useState({ + fieldId: '' }); + const getCurrentDomain = trpc.domain.getCurrentDomain.useQuery(); + const [form] = Form.useForm(); + let selectedField = fields.filter(el => el.fieldId === fileProperties.fieldId)[0]; - const onDropLocal = (acceptedFiles: Blob[]) => { - fileFilter(acceptedFiles.map(file => { - return file as StudyFile; - })); - }; + if (getCurrentDomain.isLoading) { + return ; + } - const onDragEnter = () => setisDropOverlayShowing(true); - const onDragOver = () => setisDropOverlayShowing(true); - const onDragLeave = () => setisDropOverlayShowing(false); - const onDropAccepted = () => setisDropOverlayShowing(false); - const onDropRejected = () => setisDropOverlayShowing(false); - - const { getRootProps, getInputProps } = useDropzone({ - noClick: true, - preventDropOnDocument: true, - noKeyboard: true, - onDrop: onDropLocal, - onDragEnter, - onDragOver, - onDragLeave, - onDropAccepted, - onDropRejected - }); + if (getCurrentDomain.isError) { + return
An error occured.
; + } - const removeFile = (record: StudyFile): void => { - setFileList(fileList => { - const index = fileList.findIndex(file => file.uuid === record.uuid); - const newFileList = [...fileList]; - newFileList.splice(index, 1); - return newFileList; - }); - }; + const handleUploadFile = async (variables: Record) => { + try { + setIsShowPanel(false); + setIsUploading(true); + const formData = new FormData(); - const fileFilter = (files: StudyFile[]) => { - files.forEach((file) => { - const matcher = /(.{1})(.{6})-(.{3})(.{6})-(\d{8})-(\d{8})\.(.*)/; - const particules = file.name.match(matcher); - if (particules?.length === 8) { - if (Object.keys(sites).includes(particules[1].toUpperCase()) - && validate(particules[2].toUpperCase())) - file.participantId = `${particules[1].toUpperCase()}${particules[2].toUpperCase()}`; - if (Object.keys(deviceTypes).includes(particules[3].toUpperCase()) - && validate(particules[4].toUpperCase())) - file.deviceId = `${particules[3].toUpperCase()}${particules[4].toUpperCase()}`; - const startDate = dayjs(particules[5], 'YYYYMMDD'); - const endDate = dayjs(particules[6], 'YYYYMMDD'); - if (startDate.isSame(endDate) || startDate.isBefore(endDate)) { - if (startDate.isValid()) - file.startDate = startDate; - if (endDate.isValid() && (endDate.isSame(dayjs()) || endDate.isBefore(dayjs()))) - file.endDate = endDate; - } + // Append file + if (fileList.length > 0) { + formData.append('file', fileList[0]); } - progressReports[`UP_${file.participantId}_${file.deviceId}_${file.startDate?.valueOf()}_${file.endDate?.valueOf()}`] = undefined; - file.uuid = uuid(); - fileList.push(file); - }); - setFileList([...fileList]); - }; - const validFile = fileList.filter((file) => file.deviceId && file.participantId && file.startDate && file.endDate); - const uploadHandler = () => { - - const uploads: Promise[] = []; - setIsUploading(true); - validFile.forEach(file => { - const description = { - participantId: file.participantId?.trim().toUpperCase(), - deviceId: file.deviceId?.trim().toUpperCase(), - startDate: file.startDate?.valueOf(), - endDate: file.endDate?.valueOf(), - tup: file.tup?.trim().toUpperCase() - }; - const uploadMapHackName = `UP_${description.participantId}_${description.deviceId}_${description.startDate}_${description.endDate}`; - if (!(window as any).onUploadProgressHackMap) - (window as any).onUploadProgressHackMap = {}; - (window as any).onUploadProgressHackMap[uploadMapHackName] = (progressEvent) => { - setUploadMovement(Math.random); - progressReports = { - ...progressReports, - [uploadMapHackName]: progressEvent - }; - }; - uploads.push(uploadFile({ - variables: { - file, - studyId, - description: JSON.stringify(description), - fileLength: file.size - } - }).then(result => { - if (!result.data) { - // Any accompanying error should already be displayed by `uploadFile` - console.error(result.errors, result.extensions?.code); - return; - } - delete (window as any).onUploadProgressHackMap[uploadMapHackName]; - delete progressReports[uploadMapHackName]; - removeFile(file); - notification.success({ - message: 'Upload succeeded!', - description: `File ${result.data.uploadFile.fileName} was successfully uploaded!`, - placement: 'topRight' - }); - }).catch(error => { - delete (window as any).onUploadProgressHackMap[uploadMapHackName]; - delete progressReports[uploadMapHackName]; - notification.error({ - message: 'Upload error!', - description: error?.message ?? error ?? 'Unknown Error Occurred!', - placement: 'topRight', - duration: 0 - }); + + // Append additional fields + formData.append('fieldId', String(variables.fieldId)); + formData.append('studyId', String(variables.studyId)); + formData.append('properties', JSON.stringify({ + ...variables, + FileName: fileList[0]?.name || 'unknown' })); - }); + // Axios request + const response = await axios.post('/trpc/data.uploadStudyFileData', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + }, + onUploadProgress: (progressEvent) => { + if (progressEvent.total) { + const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total); + setProgress(percentCompleted); + } + } + }); + if (response?.data?.result?.data?.id) { + const queryKey = [['data', 'getStudyFiles'], { + input: { + studyId: study.id, + fieldIds: fieldIds, + useCache: false, + readable: true + }, type: 'query' + }]; + const cache: IFile[] = queryClient.getQueryData(queryKey) ?? []; + const newCache = [...cache, response.data.result.data]; + queryClient.setQueryData(queryKey, newCache); + void queryClient.invalidateQueries(['data', 'getStudyFiles', { + input: { + studyId: study.id, + fieldIds: fieldIds, + useCache: false, + readable: true + } + }]); + setIsUploading(false); + setIsShowPanel(false); + void message.success('File has been uploaded.'); + } - Promise.all(uploads).then(() => { + } catch (error) { + // Check if the error is an AxiosError and handle it accordingly + if (axios.isAxiosError(error)) { + void message.error(String(JSON.parse(error.request?.response).error.message)); + } else { + // Handle non-Axios errors + void message.error('An unexpected error occurred.'); + } + } finally { setIsUploading(false); - }); - }; - - const uploaderProps = { - onRemove: (file) => { - const target = fileList.indexOf(file); - setFileList(fileList.splice(0, target).concat(fileList.splice(target + 1))); - }, - beforeUpload: (file) => { - fileFilter([file]); - return true; - }, - fileList: fileList.map(file => ({ - ...file, - originFileObj: file - })), - multiple: true, - showUploadList: false - }; + setProgress(0); + } - const handleSave = record => { - setFileList(fileList => { - const index = fileList.findIndex(file => file.uuid === record.uuid); - const newFileList = [...fileList]; - const newFile = fileList[index]; - newFile.participantId = record.participantId; - newFile.deviceId = record.deviceId; - newFile.startDate = record.startDate; - newFile.endDate = record.endDate; - newFile.tup = record.tup; - newFileList.splice(index, 1, newFile); - return newFileList; - }); }; + return (
+ {contextHolder} + + { + setIsShowPanel(false); + setFileList([...[]]); + }} + onOk={() => { + void handleUploadFile({ + ...form.getFieldsValue(), + studyId: study.id + }); + setFileList([]); + form.resetFields(); + }} + > + { + return false; + }} + onChange={(info: UploadChangeParam) => { + // Filter out any items that do not have originFileObj + const validFiles: RcFile[] = info.fileList + .map(item => item.originFileObj) + .filter((file): file is RcFile => !!file); + // We set a special case for IDEA-FAST project because the validator could not be merged into the new rules + if (getCurrentDomain.data?.name === 'IDEA-FAST' && study.description?.startsWith('IDEA-FAST')) { + try { + if (validFiles.length !== 1) { + return; + } + const fileName = validFiles[0].name; + const matcher = /(.{1})(.{6})-(.{3})(.{6})-(\d{8})-(\d{8})\.(.*)/; + const particules = fileName.match(matcher); + const properties: Record = {}; + if (!particules) { + return; + } + if (particules?.length === 8) { + properties.subjectId = `${particules[1].toUpperCase()}${particules[2].toUpperCase()}`; + properties.deviceId = `${particules[3].toUpperCase()}${particules[4].toUpperCase()}`; + const startDate = dayjs(particules[5], 'YYYYMMDD'); + const endDate = dayjs(particules[6], 'YYYYMMDD'); + if (startDate.isSame(endDate) || startDate.isBefore(endDate)) { + if (startDate.isValid()) + properties.startDate = startDate.format('YYYYMMDD'); + if (endDate.isValid() && (endDate.isSame(dayjs()) || endDate.isBefore(dayjs()))) + properties.endDate = endDate.format('YYYYMMDD'); + } + } + const fieldId = `Device_${deviceTypes[particules[3]].replace(/ /g, '_')}`; + form.setFieldsValue({ + fieldId: fieldId, + subjectId: properties.subjectId, + deviceId: properties.deviceId, + startDate: properties.startDate, + endDate: properties.endDate + }); + setFileProperties({ + fieldId: fieldId + }); + selectedField = fields.filter(el => el.fieldId === fieldId)[0]; + } catch { + void message.error('Failed to upload file. Please check the file name format.'); + } + } + setFileList(validFiles); + }} + onRemove={() => setFileList([])} + > +

+ +

+

+ Drag files here or click to select files +

+
+
+ + + + { + (selectedField && selectedField.properties) ? selectedField.properties.filter(el => el.name !== 'FileName').map(el => + + + + ) + : null + } +
+
+
); +}; - const fileDetailsColumns = [ - { - title: 'File name', - dataIndex: 'name', - key: 'fileName', - sorter: (a, b) => a.fileName.localeCompare(b.fileName), - render: (value, record) => { - const progress = progressReports[`UP_${record.participantId}_${record.deviceId}_${record.startDate?.valueOf()}_${record.endDate?.valueOf()}`]; - if (progress) - return {Math.round(1000 * (progress.loaded - 1) / progress.total) / 10}%; - return value; - } - }, - { - title: 'Participant ID', - dataIndex: 'participantId', - key: 'pid', - editable: true, - width: '10rem' - }, - { - title: 'Site', - dataIndex: 'siteId', - key: 'site', - render: (__unused__value, record) => record.participantId ? sites[record.participantId.substr(0, 1)] : null - }, - { - title: 'Device ID', - dataIndex: 'deviceId', - key: 'did', - editable: true, - width: '12rem' - }, - { - title: 'Device type', - dataIndex: 'deviceType', - key: 'stype', - render: (__unused__value, record) => record.deviceId ? deviceTypes[record.deviceId.substr(0, 3)] : null - }, - { - title: 'Period', - dataIndex: 'period', - key: 'period', - editable: true, - width: '24rem' - }, - { - title: 'TUP', - dataIndex: 'tup', - key: 'tup', - editable: true, - width: '24rem' - }, - { - key: 'delete', - render: (__unused__value, record) => - }] - .map(col => { - if (!col.editable) { - return col; - } - return { - ...col, - onCell: record => ({ - record: { - ...record, - period: [record.startDate, record.endDate] - }, - editable: col.editable, - dataIndex: col.dataIndex, - title: col.title, - handleSave - }) - }; - }); - - if (getOrgsLoading || getStudyLoading || getUsersLoading || whoAmILoading) - return ; - - if (getOrgsError || getStudyError || getUsersError || whoAmIError) - return
- An error occured, please contact your administrator -
; - - const userIdNameMapping = getUsersData.getUsers.reduce((a, b) => { a[b['id']] = b['firstname'].concat(' ').concat(b['lastname']); return a; }, {}); - - const sites = getOrgsData.getOrganisations.filter(org => org.metadata?.siteIDMarker).reduce((prev, current) => ({ - ...prev, - [current.metadata.siteIDMarker]: current.shortname ?? current.name - }), {}); - - function dataSourceFilter(files: IFile[]) { - const studyLevelFiles: IFile[] = []; - const subjectLevelFiles: IFile[] = []; - for (const file of files) { - if (Object.keys(JSON.parse(file.description)).length !== 0) { - if (file !== null && file !== undefined && - (!searchTerm - || (JSON.parse(file.description).participantId).toUpperCase().indexOf(searchTerm) > -1 - || sites[JSON.parse(file.description).participantId[0]].toUpperCase().indexOf(searchTerm) > -1 - || JSON.parse(file.description).deviceId.toUpperCase().indexOf(searchTerm) > -1 - || deviceTypes[JSON.parse(file.description).deviceId.substr(0, 3)].toUpperCase().indexOf(searchTerm) > -1 - || (!userIdNameMapping[file.uploadedBy] || userIdNameMapping[file.uploadedBy].toUpperCase().indexOf(searchTerm) > -1))) { - subjectLevelFiles.push(file); +export const FileBlock: FunctionComponent<{ user: IUserWithoutToken, fields: IField[], study: IStudy, block: IStudyFileBlock }> = ({ user, fields, study, block }) => { + const [isUploading, setIsUploading] = useState(false); + const queryClient = useQueryClient(); + const [progress, setProgress] = useState(0); + const [searchedKeyword, setSearchedKeyword] = useState(undefined); + const [isModalOn, setIsModalOn] = useState(false); + const getStudyFiles = trpc.data.getStudyFiles.useQuery({ studyId: study.id, fieldIds: block.fieldIds, readable: true, useCache: false }); + const deleteFile = trpc.data.deleteFile.useMutation({ + onSuccess: (data) => { + const queryKey = [['data', 'getStudyFiles'], { + input: { + studyId: study.id, + fieldIds: fields.map(el => el.fieldId), + useCache: false, + readable: true + }, type: 'query' + }]; + const cache: IFile[] = queryClient.getQueryData(queryKey) ?? []; + const newCache = cache.filter(el => el.id !== data.id); + queryClient.setQueryData(queryKey, newCache); + void queryClient.invalidateQueries(['data', 'getStudyFiles', { + input: { + studyId: study.id, + fieldIds: fields.map(el => el.fieldId), + useCache: false, + readable: true } - } else { - studyLevelFiles.push(file); - } + }]); + void message.success('File has been deleted.'); + }, + onError: () => { + void message.error('Failed to delete file.'); } - return [subjectLevelFiles, studyLevelFiles]; + }); + if (getStudyFiles.isLoading) { + return ; + } + if (getStudyFiles.isError) { + return
An error occured.
; } - const sortedFiles = dataSourceFilter(getStudyData.getStudy.files).sort((a, b) => parseInt((b as any).uploadTime) - parseInt((a as any).uploadTime)); - const numberOfFiles = sortedFiles[0].length; - const sizeOfFiles = sortedFiles[0].reduce((a, b) => a + (parseInt(b['fileSize'] as any) || 0), 0); - const participantOfFiles = sortedFiles[0].reduce(function (values, v) { - if (!values.set[JSON.parse(v['description'])['participantId']]) { - (values as any).set[JSON.parse(v['description'])['participantId']] = 1; - values.count++; - } - return values; - }, { set: {}, count: 0 }).count; - // format the file structure - const fileSummary: any[] = []; - const categoryColumns: any[] = []; - if (sortedFiles[0].length > 0) { - const availableSites: string[] = Array.from(new Set(sortedFiles[0].map(el => JSON.parse(el.description).participantId[0]).sort())); - const availableDeviceTypes: string[] = Array.from(new Set(sortedFiles[0].map(el => JSON.parse(el.description).deviceId.substr(0, 3)).sort())); - categoryColumns.push( - { - title: 'Site', - dataIndex: 'site', - key: 'site', - render: (__unused__value, record) => sites[record.site] ? sites[record.site].concat(' (').concat(record.site).concat(')') : record.site + const columns = generateTableColumns(block, searchedKeyword); + if (user.type === enumUserTypes.ADMIN) { + columns.push({ + title: '', + dataIndex: 'delete', + key: 'delete', + render: (__unused__value, record) => { + return ; } - ); - for (const deviceType of availableDeviceTypes) { - categoryColumns.push({ - title: deviceType, - dataIndex: deviceType, - key: deviceType, - render: (__unused__value, record) => record[deviceType] ? record[deviceType].toString() : deviceTypes[record[deviceType]] - }); - } - categoryColumns.push({ - title: 'Total', - dataIndex: 'Total', - key: 'total', - render: (__unused__value, record) => record.total }); - for (const site of availableSites) { - const tmpData: any = { - site: site - }; - for (const deviceType of availableDeviceTypes) { - tmpData[deviceType] = sortedFiles[0].filter(el => (JSON.parse(el.description).participantId[0] === site - && JSON.parse(el.description).deviceId.substr(0, 3) === deviceType)).length; - } - tmpData.total = sortedFiles[0].filter(el => JSON.parse(el.description).participantId[0] === site).length; - fileSummary.push(tmpData); - } - const tmpData: any = { - site: 'Total', - total: sortedFiles[0].length - }; - for (const deviceType of availableDeviceTypes) { - tmpData[deviceType] = sortedFiles[0].filter(el => JSON.parse(el.description).deviceId.substr(0, 3) === deviceType).length; - } - fileSummary.push(tmpData); } - return
} className={`${css.scaffold_wrapper} ${isDropOverlayShowing ? css.drop_overlay : ''}`}> - - {fileList.length > 0 - ? -
- - - - -
-
- rec.uuid} - rowClassName={() => css.editable_row} - pagination={false} - columns={fileDetailsColumns} - dataSource={fileList} - size='small' - components={{ body: { row: EditableRow, cell: EditableCell } }} /> - -     - - - - :
- - {isEditingDescription ? - <> - - - : - - } - : null - }> - <>{ - (isEditingDescription && whoAmIData.whoAmI.type === userTypes.ADMIN) ? { setDatasetDescription(e.target.value); }}> - : - (getStudyData.getStudy.description === null || getStudyData.getStudy.description === '') ? 'No descriptions.' : getStudyData.getStudy.description - } -
-
-
- - query={GET_STUDY} variables={{ studyId }}> - {({ loading, data, error }) => { - if (loading || error) - return <>To upload files you can click on the button below or drag and drop files directly from your hard drive.; - return <>To upload files to {data.getStudy.name} you can click on the button below or drag and drop files directly from your hard drive.; - }} - -
- If the file name is of the form XAAAAAA-DDDBBBBBB-00000000-00000000.EXTwe will extract metadata automatically. If not, you will be prompted to enter the relevant information.

- - - -
-
-
-
- - Total Files: {numberOfFiles} - Total Size: {formatBytes(sizeOfFiles)} - Total Participants: {participantOfFiles} - - } - > - setIsFileSummaryShown(false)} onOk={() => setIsFileSummaryShown(false)} width={'60%'}> -
Number of files associated to sites and device types.

-

- - {Object.keys(deviceTypes).sort().map((el, index) => {`${el}: ${deviceTypes[el]}`})} - - - setSearchTerm(value?.toUpperCase())} /> - - -
-
- - + const filteredFiles = getStudyFiles.data?.filter(el => { + if (!searchedKeyword) { + return true; + } else { + const keyword = searchedKeyword.toLowerCase(); + if ( + el.fileName?.toLowerCase().includes(keyword) || + el.life?.createdUser?.toLowerCase().includes(keyword) || + Object.keys(el.properties ?? {}).some(key => String(el.properties?.[key]).toLowerCase().includes(keyword)) + ) { + return true; + } + return false; } - ; -}; + }).sort((a, b) => (b.life?.createdTime ?? 0) - (a.life?.createdTime ?? 0)) ?? []; + + const summaryByMonth = (filteredFiles) => { + // Create a map to hold the counts per month + const fileCounts: { [key: string]: number } = {}; + + // Iterate over each file and extract the createdTime + filteredFiles.forEach(file => { + if (file.life?.createdTime) { + // Convert createdTime (Unix timestamp) to a JavaScript Date object + const createdDate = new Date(file.life.createdTime); + + // Format the date as 'YYYY-MM' for the month + const month = `${createdDate.getFullYear()}-${String(createdDate.getMonth() + 1).padStart(2, '0')}`; + + // Increment the count for the corresponding month + if (fileCounts[month]) { + fileCounts[month]++; + } else { + fileCounts[month] = 1; + } + } + }); -const EditableContext = createContext({}); + // Convert the map into an array of {x: month, y: count} objects + const result = Object.keys(fileCounts).map(month => ({ + x: month, + y: fileCounts[month] + })); -type EditableRowProps = { - index: number; -} + // Sort the result array by the 'x' (month) value + result.sort((a, b) => { + const dateA = new Date(a.x + '-01'); // Adding '-01' to treat it as a full date (YYYY-MM-DD) + const dateB = new Date(b.x + '-01'); + return dateA.getTime() - dateB.getTime(); + }); + // Return in the expected format + return [{ + id: 'Month', + color: 'hsl(127, 70%, 50%)', + data: result + }]; + }; -const EditableRow: FunctionComponent = ({ index, ...props }) => { - const [form] = Form.useForm(); + const summaryByUser = (filteredFiles) => { + // Create a map to hold the counts per user + const userCounts: { [key: string]: number } = {}; - useEffect(() => { - form.validateFields(); - }); + // Iterate over each file and extract the createdUser + filteredFiles.forEach(file => { + const createdUser = file.life?.createdUser || 'Unknown User'; // Fallback to 'Unknown User' if createdUser is null/undefined - return ( -
- -
- - - ); -}; + // Increment the count for the corresponding user + if (userCounts[createdUser]) { + userCounts[createdUser]++; + } else { + userCounts[createdUser] = 1; + } + }); -interface EditableCellProps { - editable: boolean; - children: ReactNode; - dataIndex: string; - record: StudyFile; - handleSave: (__unused__record: StudyFile) => void; -} + // Convert the map into an array of {x: user, y: count} objects + const result = Object.keys(userCounts).map(user => ({ + user: user.split(' ').map(el => (el[0] ?? '').toUpperCase()).join(''), + userFullName: user, + files: userCounts[user] + })); -const EditableCell: FunctionComponent = ({ - editable, - children, - dataIndex, - record, - handleSave, - ...restProps -}) => { - const [editing, setEditing] = useState(false); - const inputRef = useRef(null); - const rangeRef = useRef(null); - const form = useContext(EditableContext); - const { loading: getOrgsLoading, error: getOrgsError, data: getOrgsData } = useQuery(GET_ORGANISATIONS); - - useEffect(() => { - if (editable && !editing) { - form.setFieldsValue(record); - setEditing(true); - } - }, [editable, editing, form, record]); + // Sort the result array by the 'y' (count) value in descending order + result.sort((a, b) => a.user.localeCompare(b.user)); - const save = async () => { - try { - const values = await form.validateFields(); - handleSave({ ...record, ...values }); - } catch (errInfo) { - // console.error(errInfo); - } + // Return in the expected format + return result; }; - if (getOrgsLoading) - return ; - - if (getOrgsError) - return
- An error occured, please contact your administrator: {getOrgsError.message} -
; - - const sites = getOrgsData.getOrganisations.filter(org => org.metadata?.siteIDMarker).reduce((prev, current) => ({ - ...prev, - [current.metadata.siteIDMarker]: current.name - }), {}); - - let childNode = children; - - if (editing) { - if (dataIndex === 'period') { - childNode = <> - }]} - > - - - }]} - > - - - }, - ({ getFieldValue }) => ({ - validator() { - if (getFieldValue('startDate') && getFieldValue('endDate')) - return Promise.resolve(); - return Promise.reject('Missing dates'); + return +
+
+
{block.title}
+
+
+ el.dataType === enumDataTypes.FILE)} fieldIds={block.fieldIds} setIsUploading={setIsUploading} setProgress={setProgress} /> +
+ + } + > + +
+
+ setSearchedKeyword(e.target.value)} + /> +
+
+ { + isUploading ? ( +
+ + {progress >= 99 ? 'Finishing' : 'Uploading'} +
+ ) : null + } +
+
+ + {`Files: ${filteredFiles.length}`} + + + {`Size: ${formatBytes(filteredFiles.reduce((acc, curr) => acc + (Number(curr?.fileSize) || 0), 0))}`} + + +
+
+ setIsModalOn(false)} + footer={null} + width='80%' + > +

Progress by Month

+
+ - { - return dayjs().isBefore(currentDate); - }} onCalendarChange={(dates) => { - if (dates === null) - return; - form.setFieldsValue({ startDate: dates[0] }); - form.setFieldsValue({ endDate: dates[1] }); - }} onBlur={save} /> - - ; - } else { - childNode = , validator: (__unused__rule, value) => { - if (dataIndex === 'participantId') { - if (!Object.keys(sites).includes(value?.[0])) - throw new Error('Invalid site marker'); - if (value.length === 7) { - if (!validate(value?.substr(1).toUpperCase())) - throw new Error('Invalid participant ID'); - return Promise.resolve(); + ]} + /> +
+

Progress by Uploaders

+
+ { + return { + tickSize: 10, + tickPadding: 0, + tickRotation: -90, + legend: 'User', + legendPosition: 'middle', + legendOffset: 32, + truncateTickAt: 0 + }; + })()} + axisLeft={{ + tickSize: 5, + tickPadding: 5, + tickRotation: 0, + legend: 'Files', + legendPosition: 'middle', + legendOffset: -40, + truncateTickAt: 0 + }} + labelSkipWidth={12} + labelSkipHeight={12} + labelTextColor={{ + from: 'color', + modifiers: [ + [ + 'darker', + 1.6 + ] + ] + }} + legends={[ + { + dataFrom: 'keys', + anchor: 'bottom-right', + direction: 'column', + justify: false, + translateX: 120, + translateY: 0, + itemsSpacing: 2, + itemWidth: 100, + itemHeight: 20, + itemDirection: 'left-to-right', + itemOpacity: 0.85, + symbolSize: 20, + effects: [ + { + on: 'hover', + style: { + itemOpacity: 1 + } + } + ] + } + ]} + tooltip={(d) => `${d.data.userFullName}: ${d.data.files} files`} + role="application" + ariaLabel="Nivo bar chart demo" + barAriaLabel={e => e.id + ': ' + e.formattedValue + ' in country: ' + e.indexValue} + /> +

+

Statistics by Group

+
+ - ; + +
+
+
+ +
+
+ + + ; +}; + +type CustomColumnType = { + title: React.ReactNode; + dataIndex: string; + key: string; + sorter?: (a, b) => number; + render?: (value, record) => React.ReactNode; +}; + +function generateTableColumns(block: IStudyFileBlock, searchedKeyword: string | undefined) { + const columns: CustomColumnType[] = [{ + title: 'File Name', + dataIndex: 'fileName', + key: 'fileName', + sorter: (a, b) => { return stringCompareFunc(a.fileName, b.fileName); }, + render: (__unused__value, record) => { + if (searchedKeyword) + return ; + else + return record.fileName; + } + }, { + title: 'File Size', + dataIndex: 'fileSize', + key: 'fileSize', + sorter: (a, b) => { return a.fileSize - b.fileSize; }, + render: (__unused__value, record) => { + return formatBytes(record.fileSize); } + }, { + title: 'Uploaded Time', + dataIndex: 'uploadedTime', + key: 'uploadedTime', + sorter: (a, b) => { return (new Date(a.life.createdTime)).valueOf() - (new Date(b.life.createdTime)).valueOf(); }, + render: (__unused__value, record) => { + return (new Date(record.life.createdTime)).toLocaleDateString(); + } + }, { + title: 'Uploaded By', + dataIndex: 'uploadedBy', + key: 'uploadedBy', + render: (__unused__value, record) => { + if (searchedKeyword) + return ; + else + return record.life.createdUser; + } + }]; + for (const bcolumn of block.defaultFileColumns) { + columns.push({ + title: {bcolumn.title}, + dataIndex: bcolumn.property, + key: bcolumn.property, + sorter: (a, b) => { + if (bcolumn.type === enumStudyBlockColumnValueType.TIME) { + return (new Date(tableColumnRender(a, bcolumn))).valueOf() - (new Date(tableColumnRender(b, bcolumn))).valueOf(); + } + return stringCompareFunc(tableColumnRender(a, bcolumn), tableColumnRender(b, bcolumn)); + }, + render: (__unused__value, record) => { + const formattedText = tableColumnRender(record, bcolumn); + if (searchedKeyword) + return ; + else + return formattedText; + } + }); } + columns.push({ + title: '', + dataIndex: 'hash', + render: (__unused__value, record) => ( + + + + ), + // width: '8rem', + key: 'delete' + }); + columns.push({ + title: '', + dataIndex: 'download', + key: 'download', + render: (__unused__value, record) => { + return ; + } + }); + + return columns; +} - return ; -}; diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/index.tsx b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/index.tsx index c65001421..e9caa694e 100644 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/index.tsx +++ b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/index.tsx @@ -1,5 +1,2 @@ export { AdminTabContent } from './admin/adminTab'; -export { DataManagementTabContentFetch } from './data/dataTab'; -export { FieldManagementTabContentFetch } from './data/fieldTab'; export { DashboardTabContent } from './dashboard/dashBoardTab'; -export { ProjectsTabContent } from './projects/projectTab'; diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/detailSections/deleteProjectSection.tsx b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/detailSections/deleteProjectSection.tsx deleted file mode 100644 index bf9b6a4c7..000000000 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/detailSections/deleteProjectSection.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { FunctionComponent, useState } from 'react'; -import { Mutation } from '@apollo/client/react/components'; -import { Navigate } from 'react-router'; -import { WHO_AM_I, DELETE_PROJECT, GET_STUDY } from '@itmat-broker/itmat-models'; -import { IProject } from '@itmat-broker/itmat-types'; - -export const DeleteProjectSection: FunctionComponent<{ studyId: string; projectId: string; projectName: string }> = ({ studyId, projectId, projectName }) => { - const [isExpanded, setIsExpanded] = useState(false); - const [inputText, setInput] = useState(''); - const [error, setError] = useState(''); - const [deleted, setDeleted] = useState(false); - - if (!isExpanded) { - return { setIsExpanded(true); setInput(''); }}>Click to delete; - } - - if (deleted) { - return ; - } - - return <> -

Warning! This is irreversible! If you really want to delete this project, please type the name of the project ({projectName}) below to confirm.

- { setInput(e.target.value); setError(''); }} />

- - mutation={DELETE_PROJECT} - update={(store) => { - // Read the data from our cache for this query. - const data: any = store.readQuery({ query: GET_STUDY, variables: { studyId, admin: true } }); - // Add our comment from the mutation to the end. - const newProjects = data.getStudy.projects.filter((el: IProject) => el.id !== projectId); - data.getStudy.projects = newProjects; - // Write our data back to the cache. - store.writeQuery({ query: GET_STUDY, variables: { studyId, admin: true }, data }); - - // Read the data from our cache for this query. - const whoAmI: any = store.readQuery({ query: WHO_AM_I }); - // Add our comment from the mutation to the end. - // const newWhoAmIProjects = whoAmI.whoAmI.access.projects.filter((el: IProject) => el.id !== projectId); - whoAmI.whoAmI.access.projects = newProjects; - // Write our data back to the cache. - store.writeQuery({ query: WHO_AM_I, data: whoAmI }); - }} - onCompleted={() => setDeleted(true)} - > - {(deleteProject, { loading }) => - loading ? - : - - } - -
- {error ?
{error}
: null} - ; -}; diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/detailSections/fieldList.tsx b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/detailSections/fieldList.tsx deleted file mode 100644 index 8208511f0..000000000 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/detailSections/fieldList.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { FunctionComponent } from 'react'; -import { useQuery } from '@apollo/client/react/hooks'; -import { GET_STUDY } from '@itmat-broker/itmat-models'; -import LoadSpinner from '../../../../reusable/loadSpinner'; - -export const GrantedFieldListSection: FunctionComponent<{ originalCheckedList: string[]; studyId: string; projectId: string }> = ({ studyId }) => { - const { loading, data, error } = useQuery(GET_STUDY, { variables: { studyId } }); - if (loading) { return ; } - if (error) { return

{error.toString()}

; } - const { getStudy } = data; - - if (!getStudy || !getStudy.dataVersions || getStudy.dataVersions.length === 0) { - return

No data has been uploaded.

; - } - return null; -}; diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/detailSections/fileList.tsx b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/detailSections/fileList.tsx deleted file mode 100644 index 9e72533b9..000000000 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/detailSections/fileList.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { FunctionComponent } from 'react'; - -export const GrantedFileListSelection: FunctionComponent = () => { - return null; -}; diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/detailSections/patientIdMapping.tsx b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/detailSections/patientIdMapping.tsx deleted file mode 100644 index c8b59e1ad..000000000 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/detailSections/patientIdMapping.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { FunctionComponent, useState } from 'react'; -import { Query } from '@apollo/client/react/components'; -import { GET_PROJECT_PATIENT_MAPPING } from '@itmat-broker/itmat-models'; -import LoadSpinner from '../../../../reusable/loadSpinner'; -import { Button } from 'antd'; - -export const PatientIdMappingSection: FunctionComponent<{ projectId: string }> = ({ projectId }) => { - const [clickedFetch, setClickedFetch] = useState(false); - const [currentProjectId, setCurrentProjectId] = useState(projectId); - - if (projectId !== currentProjectId) { - setClickedFetch(false); - setCurrentProjectId(projectId); - } - - if (!clickedFetch) { return ; } - return query={GET_PROJECT_PATIENT_MAPPING} variables={{ projectId }}> - {({ data, loading, error }) => { - if (loading) { return ; } - if (error) { return

{error.toString()}

; } - if (!data || !data.getProject || !data.getProject.patientMapping) { return

'Cannot fetch data'

; } - return ; - }} - ; -}; diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/detailSections/projectDetail.module.css b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/detailSections/projectDetail.module.css deleted file mode 100644 index ee02ae6d0..000000000 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/detailSections/projectDetail.module.css +++ /dev/null @@ -1,48 +0,0 @@ -.project_detail_scaffold { - display: grid; - height: 100%; - width: 100%; - grid-template-columns: 80% 20%; - grid-template-rows: 50px calc(100% - 50px); - grid-template-areas: "ariane ariane" - "leftpanel rightpanel"; -} - -.project_detail_left { - height: 100%; - overflow: auto; - padding: 1.2rem; - grid-area: leftpanel; -} - -.project_detail_right { - height: 100%; - overflow: auto; - background: var(--color-data-background); - padding: 1.2rem; - grid-area: rightpanel; -} - -.project_detail_title { - height: 100%; - overflow: hidden; - grid-area: ariane; - line-height: 50px; - font-size: 1.5rem; - background: var(--color-dataversions-background); -} - -/* backbutton to project list */ -.project_detail_title>a>div { - display: inline-block; - background: var(--color-ariane-tab-active-background); - width: 50px; - height: 50px; - text-align: center; - margin-right: 1rem; -} - -/* backbutton to project list */ -.project_detail_title>a>div:hover { - background: var(--color-scroll-bar-active); -} diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/detailSections/projectDetail.tsx b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/detailSections/projectDetail.tsx deleted file mode 100644 index aa3c161fe..000000000 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/detailSections/projectDetail.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { FunctionComponent } from 'react'; -import { Query } from '@apollo/client/react/components'; -import { GET_PROJECT, WHO_AM_I } from '@itmat-broker/itmat-models'; -import { Subsection } from '../../../../reusable'; -import LoadSpinner from '../../../../reusable/loadSpinner'; -import { RoleControlSection } from '../../../../reusable/roleControlSection/roleControlSection'; -import { DeleteProjectSection } from './deleteProjectSection'; -import { PatientIdMappingSection } from './patientIdMapping'; -import css from './projectDetail.module.css'; -import { NavLink, useParams } from 'react-router-dom'; -import { useQuery } from '@apollo/client/react/hooks'; -import { userTypes } from '@itmat-broker/itmat-types'; - -export const ProjectDetail: FunctionComponent = () => { - const { projectId, studyId } = useParams(); - const { loading: whoamiloading, error: whoamierror, data: whoamidata } = useQuery(WHO_AM_I); - if (whoamiloading) { return

Loading..

; } - if (whoamierror) { return

ERROR: please try again.

; } - - if (!projectId || !studyId) - return null; - return - query={GET_PROJECT} - variables={{ projectId, admin: whoamidata.whoAmI.type === userTypes.ADMIN }} - > - {({ data, loading, error }) => { - if (loading) { return ; } - if (error) { return

{error.toString()}

; } - if (!data || !data.getProject) { return

Cannot find this project! Please contact admin.

; } - - return
-
-
⬅
{data.getProject.name} -
-
- - - - - - - - - -
-
-
-
; - }} - ; -}; diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/projectListSection.tsx b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/projectListSection.tsx deleted file mode 100644 index a6d39ef28..000000000 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/projectListSection.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { FunctionComponent, useState } from 'react'; -import { Mutation } from '@apollo/client/react/components'; -import { NavLink, Navigate } from 'react-router-dom'; -import { CREATE_PROJECT } from '@itmat-broker/itmat-models'; -import css from './tabContent.module.css'; -import { Button, Input } from 'antd'; - -export const ProjectListSection: FunctionComponent<{ studyId: string; projectList: Array<{ id: string; name: string }> }> = ({ studyId, projectList }) => { - return
- {projectList.map((el) => )} -
; -}; - -const OneProject: FunctionComponent<{ studyId: string; id: string; name: string }> = ({ id, name, studyId }) => { - return (<> -
- ); -}; - - - -export const AddNewProject: FunctionComponent<{ studyId: string }> = ({ studyId }) => { - const [projectName, setProjectName] = useState(''); - const [error, setError] = useState(''); - - return
- Project Name: - { setError(''); setProjectName(e.target.value); }} type='text' placeholder='Enter name' />

- mutation={CREATE_PROJECT} - // update={(store, { data: { createProject } }) => { - // // Read the data from our cache for this query. - // const data: any = store.readQuery({ query: GET_STUDY, variables: { studyId, admin: true } }); - // // Add our comment from the mutation to the end. - // const newProjects = data.getStudy.projects.concat(createProject); - // data.getStudy.projects = newProjects; - // // Write our data back to the cache. - // store.writeQuery({ query: GET_STUDY, variables: { studyId, admin: true }, data }); - - // // Read the data from our cache for this query. - // const whoAmI: any = store.readQuery({ query: WHO_AM_I }); - // // Add our comment from the mutation to the end. - // // const newWhoAmIProjects = whoAmI.whoAmI.access.projects.concat(createProject); - // whoAmI.whoAmI.access.projects = newProjects; - // // Write our data back to the cache. - // store.writeQuery({ query: WHO_AM_I, data: whoAmI }); - // }} - > - {(addNewProject, { loading, data }) => - <> - {data ? : null} - { - loading ? - : - - } - - } - - { - error ?
{error}
: null - } -
; -}; diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/projectTab.tsx b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/projectTab.tsx deleted file mode 100644 index 149d6648c..000000000 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/projectTab.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { FunctionComponent } from 'react'; -import { useQuery } from '@apollo/client/react/hooks'; -import { Route, Routes, useParams, Navigate } from 'react-router-dom'; -import { Subsection } from '../../../reusable/subsection/subsection'; -import { ProjectDetail } from './detailSections/projectDetail'; -import { ProjectListSection, AddNewProject } from './projectListSection'; -import LoadSpinner from '../../../reusable/loadSpinner'; -import { WHO_AM_I } from '@itmat-broker/itmat-models'; -import { userTypes } from '@itmat-broker/itmat-types'; -import css from './tabContent.module.css'; - -export const ProjectsTabContent: FunctionComponent<{ projectList: { id: string; name: string }[] }> = ({ projectList }) => { - const { studyId } = useParams(); - const { loading: whoAmILoading, error: whoAmIError, data: whoAmIData } = useQuery(WHO_AM_I); - if (whoAmILoading) { - return ; - } - if (!studyId || whoAmIError) - return ; - if (whoAmIData.whoAmI.type === userTypes.ADMIN) { - return - } /> - -
- - - -
-
- - - -
- } /> - } /> -
; - } else - return ; -}; diff --git a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/tabContent.module.css b/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/tabContent.module.css deleted file mode 100644 index e13bda742..000000000 --- a/packages/itmat-ui-react/src/components/datasetDetail/tabContent/projects/tabContent.module.css +++ /dev/null @@ -1,30 +0,0 @@ -.tab_page_wrapper { - display: grid; - grid-template-columns: 50% 50%; - grid-template-rows: 100%; - height: 100%; - overflow: auto; -} - -.tab_page_wrapper > div { - padding: 2rem; - height: 100%; -} -/* right panel*/ .tab_page_wrapper > div:nth-child(2) { - background: var(--color-additional-panel-background); -} - -.add_new_project_tr input { - background-color: #484848; - border: none; - margin: 0px; -} - -.project_badge { - height: 3.5rem; - background: var(--color-pop-up); - color: unset; - font-size: 1.2rem; - margin: 0 0 1rem 0; - overflow: hidden; -} \ No newline at end of file diff --git a/packages/itmat-ui-react/src/components/datasetList/addNewDataSet.tsx b/packages/itmat-ui-react/src/components/datasetList/addNewDataSet.tsx index af8eccece..becb71d39 100644 --- a/packages/itmat-ui-react/src/components/datasetList/addNewDataSet.tsx +++ b/packages/itmat-ui-react/src/components/datasetList/addNewDataSet.tsx @@ -1,84 +1,70 @@ import { FunctionComponent, useState } from 'react'; -import { Query } from '@apollo/client/react/components'; -import { useMutation } from '@apollo/client/react/hooks'; -import { WHO_AM_I, CREATE_STUDY } from '@itmat-broker/itmat-models'; -import { userTypes, studyType } from '@itmat-broker/itmat-types'; -import { Button, Form, Input, Alert, Select } from 'antd'; +import { Button, Form, Input, Alert, message } from 'antd'; import { PlusOutlined } from '@ant-design/icons'; import { Subsection } from '../reusable'; -const { Option } = Select; +import { trpc } from '../../utils/trpc'; +import LoadSpinner from '../reusable/loadSpinner'; export const AddNewDataSet: FunctionComponent = () => { - - const [showMore, setShowMore] = useState(false); - const [createStudy, { - data: createStudyData, - loading: createStudyLoading, - error: createStudyError - }] = useMutation(CREATE_STUDY, { - onCompleted: () => { setShowMore(false); }, - refetchQueries: [{ query: WHO_AM_I }], - onError: () => { return; } + const whoAmI = trpc.user.whoAmI.useQuery(); + const createStudy = trpc.study.createStudy.useMutation({ + onSuccess: () => { + void message.success('Study created successfully'); + }, + onError: () => { + void message.error('Failed to create study'); + }, + onSettled() { + setShowMore(false); + } }); + const [showMore, setShowMore] = useState(false); + + if (whoAmI.isLoading) { + return ; + } + + if (whoAmI.isError) { + return

An error occured.

; + } return ( - query={WHO_AM_I}> - {({ loading, error, data }) => { - if (loading) { return

Loading...

; } - if (error) { return

Error {error.name}: {error.message}

; } - if (data.whoAmI && data.whoAmI.type && data.whoAmI.type === userTypes.ADMIN) { - return ( - !showMore ? - - : -
- -
createStudy({ variables })}> - - - - - - - - - - {createStudyError ? ( - <> - error.message).join()} /> -
- - ) : null} - {createStudyData?.successful ? ( - <> - -
- - ) : null} - - -     - - - -
-
- ); - } else { - return null; - } - }} - + !showMore ? + + : +
+ +
{ createStudy.mutate({ ...variables }); }}> + + + + + + + {createStudy.isError ? ( + <> + +
+ + ) : null} + {createStudy.data?.id ? ( + <> + +
+ + ) : null} + + +     + + + +
+
); + }; diff --git a/packages/itmat-ui-react/src/components/datasetList/datasetList.tsx b/packages/itmat-ui-react/src/components/datasetList/datasetList.tsx index 0a9704780..3de03497c 100644 --- a/packages/itmat-ui-react/src/components/datasetList/datasetList.tsx +++ b/packages/itmat-ui-react/src/components/datasetList/datasetList.tsx @@ -1,30 +1,28 @@ import { FunctionComponent } from 'react'; -import { Query } from '@apollo/client/react/components'; import { useNavigate } from 'react-router-dom'; import { IStudy } from '@itmat-broker/itmat-types'; -import { WHO_AM_I } from '@itmat-broker/itmat-models'; import { Button, Table } from 'antd'; import { ContainerOutlined } from '@ant-design/icons'; import LoadSpinner from '../reusable/loadSpinner'; +import { trpc } from '../../utils/trpc'; export const DatasetList: FunctionComponent = () => { - return ( - - query={WHO_AM_I}> - {({ loading, error, data }) => { - if (loading) { return ; } - if (error) { return

Error {error.name}: {error.message}

; } - if (data.whoAmI && data.whoAmI.access && data.whoAmI.access.studies) { - const datasets = data.whoAmI.access.studies; - if (datasets.length > 0) { - return ; - } - } - return

There is no dataset or you have not been granted access to any. Please contact your data custodian.

; - } - } - - ); + const getStudies = trpc.study.getStudies.useQuery({}); + + if (getStudies.isLoading) { + return ; + } + if (getStudies.isError) { + return <> + An error occured. + ; + } + + if (getStudies.data.length > 0) { + return ; + } else { + return

There is no dataset or you have not been granted access to any. Please contact your data custodian.

; + } }; const PickDatasetSection: FunctionComponent<{ datasets: IStudy[] }> = ({ datasets }) => { @@ -60,7 +58,7 @@ const PickDatasetSection: FunctionComponent<{ datasets: IStudy[] }> = ({ dataset rowKey={(rec) => rec.id} pagination={false} columns={columns} - dataSource={datasets} + dataSource={datasets.sort((a, b) => a.name.localeCompare(b.name))} size='small' />

diff --git a/packages/itmat-ui-react/src/components/datasetList/datasetPage.module.css b/packages/itmat-ui-react/src/components/datasetList/datasetPage.module.css index 59a9a96a3..2060aa7be 100644 --- a/packages/itmat-ui-react/src/components/datasetList/datasetPage.module.css +++ b/packages/itmat-ui-react/src/components/datasetList/datasetPage.module.css @@ -1,12 +1,11 @@ .page_container { grid-template-columns: 50% 50%; grid-template-rows: 100%; - grid-template-areas: - "projectListPanel -"; + grid-template-areas: "projectListPanel -"; display: grid; height: 100%; } .project_list + div { background: var(--color-additional-panel-background); -} \ No newline at end of file +} diff --git a/packages/itmat-ui-react/src/components/domain/domains.module.css b/packages/itmat-ui-react/src/components/domain/domains.module.css new file mode 100644 index 000000000..b4e78057c --- /dev/null +++ b/packages/itmat-ui-react/src/components/domain/domains.module.css @@ -0,0 +1,91 @@ +.page_container { + grid-template-columns: 100% 0%; + grid-template-rows: 100%; + display: grid; + height: 100%; + grid-template-areas: "userList"; +} + +.col-item { + margin-bottom: 16px; +} + +.card-container { + position: relative; + display: flex; + max-width: 100%; + flex-direction: column; + height: 100%; + width: 100%; + box-sizing: border-box; + padding: 8px; +} + +.card-image { + object-fit: contain; + width: 100%; + height: 70%; +} + +.card-meta { + position: absolute; + bottom: 20px; + width: 100%; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.card-meta:hover { + cursor: pointer; +} + +.row-container { + width: 100%; + /* Add this line */ + box-sizing: border-box; + /* Add this line */ +} + +.col-item { + padding: 0 8px; + /* Half of gutter width */ + margin-bottom: 16px; + box-sizing: border-box; + /* Add this line */ +} + +.overview-header { + display: flex; + align-items: center; + /* This will vertically center align the icon and the title */ + /* If there is still a misalignment, you may adjust by adding 'align-self' to the children */ + font-size: 20px; + font-weight: 500; + overflow: auto; +} + +.overview-icon { + background-color: #000; + /* Or any color you prefer */ + width: 4px; + /* Thickness of the line */ + height: 24px; + /* Height of the line, adjust as needed */ + margin-right: 16px; + /* Space between the line and the text */ + /* Adjust 'margin-top' if the line is not aligned with the text */ +} + +.overview-title { + /* Style for your title */ + font-size: 24px; + font-weight: bold; +} + +.overview-content { + /* Style for your content */ + text-indent: 30px; + /* Adjust the indent as needed */ +} diff --git a/packages/itmat-ui-react/src/components/domain/domains.tsx b/packages/itmat-ui-react/src/components/domain/domains.tsx new file mode 100644 index 000000000..569c5fbb8 --- /dev/null +++ b/packages/itmat-ui-react/src/components/domain/domains.tsx @@ -0,0 +1,207 @@ +import { FunctionComponent, useState } from 'react'; +import css from './domains.module.css'; +import { trpc } from '../../utils/trpc'; +import { List, Table, Image, ColorPicker, Button, Modal, Form, Input, Upload, notification, message } from 'antd'; +import LoadSpinner from '../reusable/loadSpinner'; +import { RcFile, UploadFile } from 'antd/es/upload'; +import { DeleteOutlined, PlusOutlined, SmileOutlined } from '@ant-design/icons'; +import { convertFileListToApiFormat, getBase64 } from '../../utils/tools'; +import ImgCrop from 'antd-img-crop'; +import axios from 'axios'; + + +export const DomainSection: FunctionComponent = () => { + const getDomains = trpc.domain.getDomains.useQuery({}); + const [api, contextHolder] = notification.useNotification(); + const deleteDomain = trpc.domain.deleteDomain.useMutation({ + onSuccess: (() => { + api.open({ + message: 'Domain has been deleted', + description: '', + duration: 10, + icon: + }); + }) + }); + if (getDomains.isLoading) { + return ; + } + if (getDomains.isError) { + return

+ An error occured, please contact your administrator +

; + } + + const columns = [{ + title: 'Name', + dataIndex: 'name', + key: 'name', + ellipsis: true, + render: (__unused__value, record) => { + return
{record.name}
; + } + }, { + title: 'Path', + dataIndex: 'path', + key: 'path', + ellipsis: true, + render: (__unused__value, record) => { + return
{record.domainPath}
; + } + }, { + title: 'Logo', + dataIndex: 'logo', + key: 'logo', + render: (__unused__value, record) => { + return
; + } + }, { + title: 'Color', + dataIndex: 'color', + key: 'color', + ellipsis: true, + render: (__unused__value, record) => { + return
; + } + }, { + render: (__unused__value, record) => { + return deleteDomain.mutate({ domainId: record.id })} />; + } + }]; + + return (
+ {contextHolder} + +
+
+
+
List of Domains
+
+
+
+ +
+
+ } + > +
{childNode}
+ +
+ +
); +}; + +export const DomainCreation: FunctionComponent = () => { + const [isModalOn, setIsModalOn] = useState(false); + const [domainColor, setDomainColor] = useState('#FFFFFF'); + const [form] = Form.useForm(); + const [fileList, setFileList] = useState([]); + + const handleCreateDomain = async (variables: Record) => { + try { + const files = await convertFileListToApiFormat(fileList, 'logo'); + const formData = new FormData(); + if (files.length > 0) { + files.forEach(file => { + formData.append('logo', file.stream, file.originalname); + }); + } + + Object.entries(variables).forEach(([key, value]) => { + variables[key] && formData.append(key, String(value)); + }); + + const response = await axios.post('/trpc/domain.createDomain', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + + if (response?.data?.result?.data?.id) { + void message.success('Domain created successfully'); + } + + } catch { + void message.error('Error creating domain'); + } + }; + + const beforeUpload = (file: RcFile): boolean => { + const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png'; + if (!isJpgOrPng) { + void message.error('You can only upload JPG/PNG file!'); + } + setFileList([file]); + return isJpgOrPng; + }; + + const handlePreview = async (file: UploadFile) => { + if (!file.url && !file.preview) { + file.preview = await getBase64(file.originFileObj as RcFile); + } + }; + + return (
+ + void handleCreateDomain(form.getFieldsValue())} + onCancel={() => setIsModalOn(false)} + > +
+ + + + + + + + + void handlePreview} + fileList={fileList} + > + {fileList.length >= 1 ? null : ( +
+ +
Upload
+
+ )} +
+
+
+ + setDomainColor(hex)} /> + +
+
+
); +}; diff --git a/packages/itmat-ui-react/src/components/domain/index.tsx b/packages/itmat-ui-react/src/components/domain/index.tsx new file mode 100644 index 000000000..bb33fabd4 --- /dev/null +++ b/packages/itmat-ui-react/src/components/domain/index.tsx @@ -0,0 +1,18 @@ +import { FunctionComponent } from 'react'; +import { DomainSection } from './domains'; +import css from './domains.module.css'; + +export const DomainPage: FunctionComponent = () => { + return ( +
+
+
+ Domains +
+
+ +
+
+
+ ); +}; diff --git a/packages/itmat-ui-react/src/components/drive/drive.module.css b/packages/itmat-ui-react/src/components/drive/drive.module.css new file mode 100644 index 000000000..4d95fda8b --- /dev/null +++ b/packages/itmat-ui-react/src/components/drive/drive.module.css @@ -0,0 +1,186 @@ +.page_container { + height: 100%; + position: relative; + overflow-y: scroll; + display: grid; + max-width: 90vw; + justify-content: space-between; + grid-template-areas: + "FILE FILE" + "SHARED SHARED"; + grid-template-columns: 40% 60%; + grid-template-rows: 750px 1fr; + overflow: auto; + padding: 2rem; +} + +.file_wrapper { + grid-area: FILE; + padding: 20px; + background-color: white; +} + +/* Assuming this is named FileManager.module.css */ + +/* Styles for the table cell with the ellipsis */ +/* Cell container */ +.ellipsisStyle { + display: flex; + align-items: center; + position: relative; + overflow: hidden; + /* Ensure the cell doesn't expand */ +} + +/* Text and icon container */ +.textAndIcon { + flex: 1; + display: flex; + align-items: center; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.textAndIcon span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Icons container */ +.cellIcons { + display: flex; + align-items: center; + position: absolute; + right: 0; + /* Reserve space for icons to prevent cell expansion */ + width: 60px; + /* Adjust based on your icon size */ + justify-content: flex-end; + opacity: 0; + transition: opacity 0.3s ease; +} + +/* Show icons on hover */ +.ellipsisStyle:hover .cellIcons { + opacity: 1; +} + +/* Individual icons */ +.cellIcon { + margin-left: 8px; +} + +/* Styles for the action element in the table cell */ +.cellAction { + position: absolute; + right: 16px; + /* Adjust the spacing as needed */ + top: 50%; + transform: translateY(-50%); + display: none; +} + +/* Hover state for the ellipsis action to be visible */ +.ellipsisStyle:hover .cellAction { + display: block; +} + +/* Styles for the folder and file names */ +.customTextHover { + cursor: pointer; + transition: all 0.3s ease; +} + +/* Hover effects for the folder and file names */ +.customTextHover:hover { + font-weight: bold; + text-decoration: underline; +} + +/* Styles for the dropdown enu */ +.dropdownMenu { + cursor: pointer; + font-size: 24px; + /* Adjust the font size as desired */ + display: inline-block; + line-height: 1; + /* Ensure the icon is vertically centered */ +} + +.shared_container { + width: 100%; + grid-area: SHARED; + padding: 20px; +} + +.ellipsis-style { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + max-width: 150px; + /* Adjust this width based on your requirement */ + display: block; +} + +.group_wrapper { + grid-area: GROUP; + padding: 20px; + background-color: white; +} + +.overview-header { + display: flex; + align-items: center; + /* This will vertically center align the icon and the title */ + /* If there is still a misalignment, you may adjust by adding 'align-self' to the children */ + font-size: 20px; + font-weight: 500; + overflow: auto; +} + +.overview-icon { + background-color: #000; + /* Or any color you prefer */ + width: 4px; + /* Thickness of the line */ + height: 24px; + /* Height of the line, adjust as needed */ + margin-right: 16px; + /* Space between the line and the text */ + /* Adjust 'margin-top' if the line is not aligned with the text */ +} + +.overview-title { + /* Style for your title */ + font-size: 24px; + font-weight: bold; +} + +.overview-content { + /* Style for your content */ + text-indent: 30px; + /* Adjust the indent as needed */ +} + +.key_wrapper { + grid-area: KEY; + padding: 20px; + background-color: white; +} + +.ant-table table { + font-size: 10px; +} + +.breadcrumbContainer { + display: flex; + /* This will align the breadcrumb items inline */ + align-items: center; + /* This will vertically align them in the middle */ + flex-wrap: wrap; + /* This allows the breadcrumb items to wrap */ + text-align: left; + /* Align text to the left */ +} diff --git a/packages/itmat-ui-react/src/components/drive/file.tsx b/packages/itmat-ui-react/src/components/drive/file.tsx new file mode 100644 index 000000000..9a3bdabbb --- /dev/null +++ b/packages/itmat-ui-react/src/components/drive/file.tsx @@ -0,0 +1,970 @@ +import React, { FunctionComponent, useEffect } from 'react'; +import css from './drive.module.css'; +import { Input, Button, Row, Col, Table, Menu, notification, Modal, Tag, Upload, Tooltip, Form, Checkbox, List, Dropdown, Spin, Select, UploadFile } from 'antd'; +import { IDrive, IDrivePermission, IUserWithoutToken, enumDriveNodeTypes } from '@itmat-broker/itmat-types'; +import { FileTwoTone, FolderTwoTone, MailOutlined, MinusOutlined, MoreOutlined, PlusOutlined, ShareAltOutlined, UploadOutlined, UsergroupAddOutlined } from '@ant-design/icons'; +import { trpc } from '../../utils/trpc'; +import { useQueryClient } from '@tanstack/react-query'; +import { convertFileListToApiFormat, formatBytes } from '../../utils/tools'; +import axios, { AxiosError } from 'axios'; +import { RcFile } from 'antd/es/upload'; +import ClipLoader from 'react-spinners/ClipLoader'; + +const { Column } = Table; +const { Option } = Select; + +export const MyFile: FunctionComponent = () => { + const whoAmI = trpc.user.whoAmI.useQuery(); + const getUsers = trpc.user.getUsers.useQuery({}); + const getDrives = trpc.drive.getDrives.useQuery({}); + + const [isInitialize, setIsInitialize] = React.useState(true); + const queryClient = useQueryClient(); + const [currentLocationPath, setCurrentLocationPath] = React.useState([]); + const [isCreatingFolder, setIsCreatingFolder] = React.useState(false); + const [isSharingDrive, setIsSharingDrive] = React.useState(false); + const [currentNodeId, setCurrentNodeId] = React.useState(null); + const [isRecursiveUploading, setIsRecursiveUploading] = React.useState(false); + const [uploadingFolderFileList, setUploadingFolderFileList] = React.useState([]); + const [isUploading, setIsUploading] = React.useState(false); + + useEffect(() => { + if (isInitialize && getDrives?.data) { + setCurrentLocationPath([getDrives.data?.[whoAmI.data.id].filter(el => el.parent === null)[0].id]); + setIsInitialize(false); + } + }, [getDrives.data, isInitialize, whoAmI.data.id]); + + useEffect(() => { + if (isRecursiveUploading && uploadingFolderFileList.length > 0) { + const uploadAllFiles = async () => { + const tPaths: string[][] = []; + const files = await convertFileListToApiFormat(uploadingFolderFileList, 'files'); + const formData = new FormData(); + if (files.length > 0) { + for (let i = 0; i < files.length; i++) { + const pathFromCurrent: string[] = uploadingFolderFileList[i].webkitRelativePath.split('/'); + if (pathFromCurrent[pathFromCurrent.length - 1].startsWith('.')) { + continue; + } + formData.append('files', files[i].stream, files[i].originalname); + tPaths.push(pathFromCurrent); + } + } + + formData.append('parentId', currentLocationPath[currentLocationPath.length - 1]); + formData.append('paths', JSON.stringify(tPaths)); + setIsUploading(true); + const response = await axios.post('/trpc/drive.createRecursiveDrives', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + if (response?.data?.result?.data?.length) { + const queryKey = [['drive', 'getDrives'], { input: {}, type: 'query' }]; + // log each query's key and data + const cache = queryClient.getQueryData(queryKey) ?? []; + const newCache = Array.from(new Map([...cache[whoAmI.data.id], ...response.data.result.data].map(item => [item.id, item])).values()); + const newQueryCache = { + ...cache, + [whoAmI.data.id]: newCache + }; + queryClient.setQueryData(queryKey, newQueryCache); + void queryClient.invalidateQueries(['drive', 'getDrives', { input: {} }]); + notification.success({ + message: 'Directory has been uploaded.', + description: '', + placement: 'topRight', + duration: 0 + }); + setIsCreatingFolder(false); + } else { + notification.error({ + message: 'Direcroty upload error!', + description: 'Unknown Error Occurred!', + placement: 'topRight', + duration: 0 + }); + } + setIsUploading(false); + setIsRecursiveUploading(false); + }; + try { + void uploadAllFiles(); + } catch (error) { + if (error instanceof AxiosError) { + notification.error({ + message: 'Upload error!', + description: `${error.response?.data?.error?.message ?? 'An unknown error occurred.'}`, + placement: 'topRight', + duration: 0 + }); + } else { + notification.error({ + message: 'Upload error!', + description: 'An unknown error occurred.', + placement: 'topRight', + duration: 0 + }); + } + } + } + }, [currentLocationPath, isRecursiveUploading, queryClient, setUploadingFolderFileList, uploadingFolderFileList, whoAmI.data.id]); + + const createDriveFolder = trpc.drive.createDriveFolder.useMutation({ + onSuccess: (data) => { + const queryKey = [['drive', 'getDrives'], { input: {}, type: 'query' }]; + // log each query's key and data + const cache = queryClient.getQueryData(queryKey) ?? []; + const newCache = [...cache[whoAmI.data.id], data]; + const newQueryCache = { + ...cache, + [whoAmI.data.id]: newCache + }; + queryClient.setQueryData(queryKey, newQueryCache); + void queryClient.invalidateQueries(['drive', 'getDrives', { input: {} }]); + + notification.success({ + message: `Folder ${data.name} has been created.`, + description: '', + placement: 'topRight', + duration: 0 + }); + setIsCreatingFolder(false); + }, + onError(error) { + if (error instanceof AxiosError) { + notification.error({ + message: 'Upload error!', + description: `${error.response?.data?.error?.message ?? 'An unknown error occurred.'}`, + placement: 'topRight', + duration: 0 + }); + } else { + notification.error({ + message: 'Upload error!', + description: 'An unknown error occurred.', + placement: 'topRight', + duration: 0 + }); + } + } + }); + + const deleteDrive = trpc.drive.deleteDrive.useMutation({ + onSuccess: (data) => { + const queryKey = [['drive', 'getDrives'], { input: {}, type: 'query' }]; + // log each query's key and data + const cache = queryClient.getQueryData(queryKey) ?? []; + const newCache = cache[whoAmI.data.id].filter(el => el.id !== data.id); + const newQueryCache = { + ...cache, + [whoAmI.data.id]: newCache + }; + queryClient.setQueryData(queryKey, newQueryCache); + }, + onError(error) { + notification.error({ + message: 'Delete error!', + description: error.message || 'Unknown Error Occurred!', + placement: 'topRight', + duration: 0 + }); + } + }); + + const shareDriveViaEmail = trpc.drive.shareDriveToUserViaEmail.useMutation({ + onSuccess: () => { + setIsSharingDrive(false); + notification.success({ + message: 'Drive shared successfully!', + description: '', + placement: 'topRight', + duration: 0 + }); + }, + onError(error) { + setIsSharingDrive(false); + notification.error({ + message: 'Upload error!', + description: error.message || 'Unknown Error Occurred!', + placement: 'topRight', + duration: 0 + }); + } + }); + const fileTableColumns = [ + { + title: 'Name', + dataIndex: 'value', + key: 'value', + width: '40%', + render: (value, record) => { + const icon = record.type === enumDriveNodeTypes.FILE ? : ; + const content = ( + { + if (record.type === enumDriveNodeTypes.FOLDER) { + setCurrentLocationPath([...currentLocationPath, record.id]); + } + }}> + {icon} + +  {record.name} + + + ); + + return ( +
+
+ {content} +
+
+ {/* Share Icon with Tooltip */} + + { + setIsSharingDrive(true); + setCurrentNodeId(record.id); + e.stopPropagation(); // Prevent triggering Dropdown + }} + /> + + + {/* More Icon (Ellipsis) with Dropdown and Tooltip */} + { + if (e.key === 'download') { + const a = document.createElement('a'); + a.href = `/file/${record.fileId}`; + a.setAttribute('download', record.name || 'download'); // Optional: provide a default file name + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + } else if (e.key === 'delete') { + // Handle delete logic here + } + }}> + { + record.type === enumDriveNodeTypes.FILE && + + Download + + } + { + deleteDrive.mutate({ driveId: record.id }); + }}> + Delete + + {/* Add more Menu.Item components as needed */} + + } trigger={['click']}> + + e.stopPropagation()}> + + + + +
+
+ ); + } + } + , + { + title: 'Modified', + dataIndex: 'modified', + key: 'modified', + width: '20%', + sorter: (a, b) => a.life.createdTime - b.life.createdTime, + render: (__unused__value, record) => {new Date(record.life.createdTime).toLocaleDateString()} + }, + { + title: 'File Size', + dataIndex: 'fileSize', + key: 'fileSize', + width: '20%', + render: (__unused__value, record) => {formatBytes(record.metadata.fileSize)} + }, + { + title: 'Sharing', + dataIndex: 'sharedUsers', + key: 'sharedUsers', + width: '20%', + render: (__unused__value, record) => { + if (record.sharedUsers.length >= 1) { + return ( +
{ + setIsSharingDrive(true); + setCurrentNodeId(record.id); + }} // Attach the click event handler + > + Shared +
+ ); + } else { + return ( +
{ + setIsSharingDrive(true); + setCurrentNodeId(record.id); + }} // Attach the click event handler even if it's private + > + Private +
+ ); + } + } + } + ]; + + const handleUploadFile = async (fileList: UploadFile[], variables: Record) => { + try { + const files = await convertFileListToApiFormat(fileList, 'file'); + setIsUploading(true); + const formData = new FormData(); + + if (files.length > 0) { + files.forEach(file => { + formData.append('file', file.stream, file.originalname); + }); + } + + // Append other variables to formData + Object.entries(variables).forEach(([key, value]) => { + variables[key] && formData.append(key, String(value)); + }); + + // Perform the request via axios + const response = await axios.post('/trpc/drive.createDriveFile', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + + if (response?.data?.result?.data?.id) { + const queryKey = [['drive', 'getDrives'], { input: { userId: whoAmI?.data?.id }, type: 'query' }]; + // log each query's key and data + const cache = queryClient.getQueryData(queryKey) ?? []; + const newCache = [...cache[whoAmI.data.id], response.data.result.data]; + const newQueryCache = { + ...cache, + [whoAmI.data.id]: newCache + }; + queryClient.setQueryData(queryKey, newQueryCache); + notification.success({ + message: `File ${response.data.result.data.name} has been uploaded.`, + description: '', + placement: 'topRight', + duration: 0 + }); + setIsUploading(false); + } + } catch (error) { + if (error instanceof AxiosError) { + notification.error({ + message: 'Upload error!', + description: `${error.response?.data?.error?.message ?? 'An unknown error occurred.'}`, + placement: 'topRight', + duration: 0 + }); + } else { + notification.error({ + message: 'Upload error!', + description: 'An unknown error occurred.', + placement: 'topRight', + duration: 0 + }); + } + setIsUploading(false); + } finally { + setIsUploading(false); + } + }; + + return ( + <> +
+ { + isUploading && + } + +
+
My files
+
+ } + > + + + + + + { + if (e.key === 'newFolder') { + setIsCreatingFolder(true); + } + // Add more conditions for other menu items here + }}> + New Folder + } trigger={['click']}> + + + + + { + // if (e.key === 'files') { + // setIsUploadingFile(true); + // } + }}> + + { + return void handleUploadFile([file], { + parentId: currentLocationPath[currentLocationPath.length - 1] + + }); + }} + > + {'Files'} + + + + { + setUploadingFolderFileList(fileList); + setIsRecursiveUploading(true); + }} + > + {' Directory'} + + + {/* Other Option 2 */} + {/* Add more Menu.Item components as needed */} + } trigger={['click']}> + + + + + + + + + + { + isUploading ? ( +
+ + Uploading...Please wait +
+ ) : null + } + + +
+
+ + +
+ { + + currentLocationPath.map((el, index) => { + const isLast = index === currentLocationPath.length - 1; + const folderName = getDrives?.data?.[whoAmI.data.id]?.find(es => es.id === el)?.name || 'Unknown'; + return ( + + setCurrentLocationPath(currentLocationPath.slice(0, index + 1))} + > + {folderName} + + {!isLast && /} + + ); + }) + + } +
+
+ + el.parent === currentLocationPath[currentLocationPath.length - 1])} + /> + el.id === currentNodeId)[0]} users={getUsers.data ?? []} /> + + + +
+ +
+ + ); +}; + +export const CreateFolder: FunctionComponent<{ isModalShown, setIsModalShown, uploadFunc, uploadVariables }> = ({ isModalShown, setIsModalShown, uploadFunc, uploadVariables }) => { + const [folderName, setFolderName] = React.useState(null); + const [description, setDescription] = React.useState(undefined); + return ( + { + uploadFunc.mutate({ + folderName: folderName, + parentId: uploadVariables.parentNodeId, + description: description ?? undefined + }); + }} + onCancel={() => setIsModalShown(false)} + > + setFolderName(event.target.value)} placeholder='Folder Name' required={true}> +

+ setDescription(event.target.value)} placeholder='Description'> +
+ ); +}; + +export const ShareFileModal: FunctionComponent<{ isModalShown: boolean, setIsModalShown, shareFunc, shareVariables: Record, currentDrive?: IDrive, users: IUserWithoutToken[] }> = ({ isModalShown, setIsModalShown, shareFunc, currentDrive, users }) => { + const [form] = Form.useForm(); + if (!currentDrive) { + return null; + } + const tmp = currentDrive.sharedUsers.map(el => { + const user = users.filter(ek => ek.id === el.iid)[0]; + const tmp: Partial & { user?: string, email?: string } = { ...el }; + if (!user) { + tmp.user = 'NA'; + tmp.email = 'NA'; + } else { + tmp.user = `${user.firstname} ${user.lastname}`; + tmp.email = user.email; + } + return tmp; + }); + const reformattedSharedUsers = { + ...currentDrive, + sharedUsers: tmp + }; + return ( + setIsModalShown(false)} + footer={null} + > +
+ + {(sharedUsers, { add, remove }) => { + return ; + }} + + + + + +
+ ); +}; + +export const EditPermissionTable: FunctionComponent<{ sharedUsers, add, remove, users: IUserWithoutToken[] }> = ({ sharedUsers, add, remove }) => { + const columns = [{ + title: 'User', + dataIndex: 'user', + key: 'user', + minWidth: 100 // Example minimum width + // ... other properties + }, { + title: 'Email', + dataIndex: 'email', + key: 'email', + minWidth: 200 // Example minimum width + // ... other properties + // ... other columns remain unchanged + }, { + title: 'Read', + dataIndex: 'read', + key: 'read', + render: (__unused__value, record) => { + return ; + } + }, { + title: 'Write', + dataIndex: 'write', + key: 'write', + render: (__unused__value, record) => { + return ; + } + }, { + title: 'Delete', + dataIndex: 'delete', + key: 'delete', + render: (__unused__value, record) => { + return ; + } + }]; + return ( +
{ + return ( + + + + ); + }} + > + { + columns.slice(0, 2).map(el => + { + return ( + + + + ); + }} + /> + ) + } + { + columns.slice(2, 5).map(el => + { + return ( + + + + ); + }} + /> + ) + } + { + return ( +
+ ); +}; + +export const SharedFiles: FunctionComponent<{ users: IUserWithoutToken[], sharedUserFiles: Record, self: IUserWithoutToken, deleteDriveFunc }> = ({ users, sharedUserFiles, self, deleteDriveFunc }) => { + const [currentSharedUser, setCurrentSharedUser] = React.useState(null); + const [currentLocationPath, setCurrentLocationPath] = React.useState(['shared']); + const sharedUsers: string[] = Object.keys(sharedUserFiles).filter(el => el !== self.id); + const rootDrive: IDrive = { + id: 'shared', + managerId: self.id, + path: ['shared'], + restricted: true, + name: 'Shared', + fileId: null, + type: enumDriveNodeTypes.FOLDER, + parent: null, + children: [], + sharedUsers: [], // ids of shared users + life: { + createdTime: Date.now(), + createdUser: self.id, + deletedTime: null, + deletedUser: null + }, + metadata: {} + }; + + const reformattedFiles: IDrive[] = []; + for (const key of Object.keys(sharedUserFiles)) { + if (key === self.id) { + continue; + } + const files = sharedUserFiles[key]; + const root = sharedUserFiles[key].filter(el => el.parent === null)[0]; + // if (root) { + // root.parent = rootDrive.id; + // rootDrive.children.push(root.id); + // } + for (const file of files) { + const newFile = { + ...file, + path: [rootDrive.id].concat(file.path) + }; + if (newFile.parent === root?.id) { + newFile.parent = rootDrive.id; + rootDrive.children.push(newFile.id); + } + reformattedFiles.push(newFile); + } + } + reformattedFiles.push(rootDrive); + + const fileTableColumns = [ + { + title: 'Name', + dataIndex: 'value', + key: 'value', + width: '40%', + render: (__unused__value, record) => { + const icon = record.type === enumDriveNodeTypes.FILE ? : ; + const content = ( + { + if (record.type === enumDriveNodeTypes.FOLDER) { + setCurrentLocationPath([...currentLocationPath, record.id]); + } + }}> + {icon} + +  {record.name} + + + ); + + return ( +
+
+ {content} +
+
+ {/* Share Icon with Tooltip */} + { + if (e.key === 'download') { + const a = document.createElement('a'); + a.href = `/file/${record.fileId}`; + a.setAttribute('download', record.name || 'download'); // Optional: provide a default file name + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + } else if (e.key === 'delete') { + // Handle delete logic hereƒ + } + }}> + { + record.type === enumDriveNodeTypes.FILE && + + Download + + } + { + record.sharedUsers.some(el => el.delete) ? + { + deleteDriveFunc.mutate({ driveId: record.id }); + }}> + Delete + + : null + } + + } trigger={['click']}> + + e.stopPropagation()}> + + + + +
+
+ ); + } + }, + { + title: 'Modified', + dataIndex: 'modified', + key: 'modified', + width: '20%', + render: (__unused__value, record) => {new Date(record.life.createdTime).toUTCString()} + }, + { + title: 'File Size', + dataIndex: 'fileSize', + key: 'fileSize', + width: '20%', + render: (__unused__value, record) => {formatBytes(record.metadata.fileSize)} + }, + { + title: 'Sharing', + dataIndex: 'sharedBy', + key: 'sharedBy', + width: '20%', + render: (__unused__value, record) => { + const user = users.filter(el => el.id === record.managerId)[0]; + + // Define styles + const sharedByStyle = { + fontWeight: 'bold', + color: '#555' // Example: dark gray color + }; + + const userNameStyle = { + color: '#007bff', // Example: blue color + fontWeight: 'normal' + }; + + return ( + + Shared By + {' '} + {user ? `${user.firstname} ${user.lastname}` : 'NA'} + + ); + } + } + ]; + const items = sharedUsers.map((el, index) => { + const user = users.filter(es => es.id === el)[0]; + return { + id: el, + key: el, + icon: , + // children: [], + label: user ? `${user.firstname} ${user.lastname}` : `Firstname_${index} Lastname_${index}` + }; + }); + return ( + +
+
+
+
Shared Files
+
+
+
+ +
+
+ } + > + +
+
+ { + currentLocationPath.map((el, index) => { + const tag = { + setCurrentLocationPath(currentLocationPath.slice(0, index + 1)); + }}>{reformattedFiles.filter(es => es.id === el)[0].name}; + if (index < currentLocationPath.length - 1) { + return [tag, /]; + } else { + return tag; + } + }) + } + { + return el.managerId === currentSharedUser; + }).filter(el => el.parent === currentLocationPath[currentLocationPath.length - 1]) : []} + // dataSource={reformattedFiles.filter(el => el.parent === currentLocationPath[currentLocationPath.length - 1])} + /> + + + + + ); +}; + +const LoadingIcon = () => { + return ( +
+ +
+ ); +}; diff --git a/packages/itmat-ui-react/src/components/drive/index.tsx b/packages/itmat-ui-react/src/components/drive/index.tsx new file mode 100644 index 000000000..29849a5d7 --- /dev/null +++ b/packages/itmat-ui-react/src/components/drive/index.tsx @@ -0,0 +1,11 @@ +import { FunctionComponent } from 'react'; +import { MyFile } from '../drive/file'; +import css from './drive.module.css'; + +export const DrivePage: FunctionComponent = () => { + return ( +
+ +
+ ); +}; diff --git a/packages/itmat-ui-react/src/components/instance/index.tsx b/packages/itmat-ui-react/src/components/instance/index.tsx new file mode 100644 index 000000000..a636c5915 --- /dev/null +++ b/packages/itmat-ui-react/src/components/instance/index.tsx @@ -0,0 +1,8 @@ +import { FunctionComponent } from 'react'; +import { InstanceSection } from './instance'; // Assuming InstanceSection is the component we're about to create + +export const InstancePage: FunctionComponent = () => { + return ( + + ); +}; diff --git a/packages/itmat-ui-react/src/components/instance/instance.module.css b/packages/itmat-ui-react/src/components/instance/instance.module.css new file mode 100644 index 000000000..e7d52bc5d --- /dev/null +++ b/packages/itmat-ui-react/src/components/instance/instance.module.css @@ -0,0 +1,25 @@ +.page_container { + /* padding: 2rem; */ + padding: 20px; /* Adjust as needed */ + overflow: auto; /* Ensures that scrolling is possible */ + height: calc(100vh - 100px); +} + +.createButton { + margin-bottom: 1rem; +} + +.table { + margin-top: 1rem; +} + +/* Style for cards, adjust as needed */ +.cardContainer { + max-height: 300px; /* Example height */ + overflow: auto; /* Allows scrolling within the card */ + margin-bottom: 20px; /* Adds space between cards */ +} + +.marginBottom { + margin-bottom: 20px; /* Adjust the value as needed */ +} diff --git a/packages/itmat-ui-react/src/components/instance/instance.tsx b/packages/itmat-ui-react/src/components/instance/instance.tsx new file mode 100644 index 000000000..0e2bda587 --- /dev/null +++ b/packages/itmat-ui-react/src/components/instance/instance.tsx @@ -0,0 +1,481 @@ +import React, { FunctionComponent, useState, useEffect} from 'react'; +import { Button, message, Modal, Form, Select, Card, Tag, Progress, Space, Row, Col , Tooltip} from 'antd'; +import { trpc } from '../../utils/trpc'; +import css from './instance.module.css'; +import { enumAppType,enumInstanceType, enumInstanceStatus, LXDInstanceTypeEnum, enumOpeType, IUserConfig} from '@itmat-broker/itmat-types'; + +const { Option } = Select; + +type CreateInstanceFormValues = { + name: string; + type: LXDInstanceTypeEnum; + appType: enumAppType; + instanceType: enumInstanceType; + lifeSpan: number; + project: string; + cpuLimit: number; + memoryLimit: string; + diskLimit: string; +}; + +type FlavorDetails = { + cpuLimit: number; + memoryLimit: string; + diskLimit: string; +}; + + +export const InstanceSection: FunctionComponent = () => { + + const [selectedInstanceTypeDetails, setSelectedInstanceTypeDetails] = useState(''); + const [isConnectingToJupyter, setIsConnectingToJupyter] = useState(false); + + const [isConnectingToVNC, setIsConnectingToVNC] = useState(false); + + const [isAtOrOverQuota, setIsAtOrOverQuota] = useState(false); + + // quota (user) and flavor (system) + const { data: quotaAndFlavors } = trpc.instance.getQuotaAndFlavors.useQuery<{ + userQuota: IUserConfig; + userFlavors: { [key: string]: FlavorDetails }; + }>(); + + const getInstances = trpc.instance.getInstances.useQuery(undefined, { + refetchInterval: 2 * 60 * 1000, + refetchIntervalInBackground: true, // Continue refetching when tab is in background + refetchOnWindowFocus: true, // Refetch when window regains focus + refetchOnReconnect: true, // Refetch when reconnecting + retry: 3, // Number of retry attempts + onError: (error) => { + console.error('Failed to fetch instances:', error); + } + }); + + + // Recalculate `isAtOrOverQuota` whenever quota or instances data changes + useEffect(() => { + if (getInstances.data && quotaAndFlavors) { + const runningInstancesCount = getInstances.data.length; + const maxInstancesAllowed = quotaAndFlavors.userQuota.defaultLXDMaximumInstances || 0; + setIsAtOrOverQuota(runningInstancesCount >= maxInstancesAllowed); + } + }, [getInstances.data, quotaAndFlavors]); + + + const createInstance = trpc.instance.createInstance.useMutation({ + onSuccess: async () => { + void message.success('Instance created successfully.'); + await getInstances.refetch(); + }, + onError: (error) => { + void message.error(`Failed to create instance: ${error.message}`); + } + }); + const deleteInstance = trpc.instance.deleteInstance.useMutation({ + onSuccess: async () => { + void message.success('Instance deleted successfully.'); + await getInstances.refetch(); + }, + onError: (error) => { + void message.error(`Failed to delete instance: ${error.message}`); + } + }); + const startStopInstance = trpc.instance.startStopInstance.useMutation({ + onSuccess: async () => { + void message.success('Instance status changed successfully.'); + await getInstances.refetch(); + }, + onError: (error) => { + void message.error(`Failed to change instance status: ${error.message}`); + } + }); + const restartInstance = trpc.instance.restartInstance.useMutation({ + onSuccess: async () => { + void message.success('Instance restarted successfully.'); + await getInstances.refetch(); // refetch the instances to update the list + }, + onError: (error) => { + void message.error(`Failed to restart instance: ${error.message}`); + } + }); + + const [isModalOpen, setIsModalOpen] = useState(false); + + const [createForm] = Form.useForm(); + + const extendInstanceLifespan = trpc.instance.extendInstanceLifespan.useMutation({ + onSuccess: async () => { + void message.success('Instance lifespan extended successfully.'); + await getInstances.refetch(); + }, + onError: (error) => { + void message.error(`Failed to extend instance lifespan: ${error.message}`); + } + }); + + // Define the initial form values including the default instanceType + const initialFormValues: Partial = { + instanceType: enumInstanceType.SMALL, + lifeSpan: quotaAndFlavors?.userQuota?.defaultLXDMaximumInstanceLife ?? 360 * 60 * 60 * 1000 // Default to 360 hours if no user quota + }; + + useEffect(() => { + if (quotaAndFlavors?.userFlavors && quotaAndFlavors.userFlavors[enumInstanceType.SMALL]) { + + const { cpuLimit, memoryLimit, diskLimit } = quotaAndFlavors.userFlavors[enumInstanceType.SMALL]; + setSelectedInstanceTypeDetails(`${cpuLimit} CPU, ${memoryLimit} memory, ${diskLimit} disk`); + } + }, [quotaAndFlavors]); + + + const handleCreateInstance = (values: CreateInstanceFormValues) => { + + const generatedName = `${values.appType}-${Date.now()}`; + const determinedType = values.appType === enumAppType.DESKTOP ? LXDInstanceTypeEnum.VIRTUAL_MACHINE: LXDInstanceTypeEnum.CONTAINER; + // get the cpu, memorylimit, disk limit from the backend server + // Get the flavor details from the selected type + const flavorDetails = quotaAndFlavors?.userFlavors[values.instanceType]; + if (!flavorDetails) { + void message.error('Invalid instance type selected.'); + return; + } + // Use user-defined maximum instance life or default value + const effectiveLifeSpan = values.lifeSpan ?? (quotaAndFlavors?.userQuota?.defaultLXDMaximumInstanceLife || 360 * 60 * 60 * 1000); + + createInstance.mutate({ + name: generatedName, + type: determinedType, + appType: values.appType, + lifeSpan: effectiveLifeSpan, + cpuLimit: flavorDetails.cpuLimit, + memoryLimit: flavorDetails.memoryLimit, + diskLimit: flavorDetails.diskLimit + }); + setIsModalOpen(false); + createForm.resetFields(); + }; + + const handleRestartInstance = async (values: { instance_id: string, lifeSpan: number }) => { + restartInstance.mutate({ + instanceId:values.instance_id, + lifeSpan: values.lifeSpan + }); + + }; + + const connectToJupyterHandler = async (instance_id: string) => { + setIsConnectingToJupyter(true); // Indicate that connection attempt is in progress + + try { + // // Construct the Jupyter proxy URL directly + const baseUrl = new URL(window.location.href); + const jupyterProxyUrl = `${baseUrl.origin}/jupyter/${instance_id}`; + + // Open the Jupyter service in a new tab + window.open(jupyterProxyUrl, '_blank'); + // Construct the Jupyter proxy URL for WebSocket connection + + + } catch (error: unknown) { + if (error instanceof Error) { + void message.error(error.message || 'Failed to connect to Jupyter. Please try again.'); + } else { + void message.error('Failed to connect to Jupyter. Please try again.'); + } + } finally { + setIsConnectingToJupyter(false); // Reset the connection attempt status + } + }; + + + // instanceActions.tsx + const connectToVNCHandler = async (instance_id: string) => { + setIsConnectingToVNC(true); + + try { + const baseUrl = new URL(window.location.href); + const vncProxyUrl = `${baseUrl.origin}/matlab/${instance_id}/vnc_auto.html?path=matlab/${instance_id}/websockify`; + + // Open VNC viewer in new tab + window.open(vncProxyUrl, '_blank'); + } catch (error: unknown) { + if (error instanceof Error) { + void message.error(error.message || 'Failed to connect to VNC viewer'); + } else { + void message.error('Failed to connect to VNC viewer'); + } + } finally { + setIsConnectingToVNC(false); + } + }; + + const handleDeleteInstance = (instance) => { + Modal.confirm({ + title: ( + + Confirm Deletion + + ), + content: ( +
+

+ Warning: You are about to delete the instance "{instance.name}". +

+

+ + All data inside this instance will be permanently removed. + +

+

This action cannot be undone. Please proceed with caution.

+
+ ), + okText: 'Yes, delete it', + okType: 'danger', + cancelText: 'No, cancel', + onOk: () => { + deleteInstance.mutate({ instanceId: instance.id }); + } + }); + }; + + if (getInstances.isLoading) { + return
Loading instances...
; + } + + if (getInstances.isError) { + return
Error loading instances: {getInstances.error.message}
; + } + + const getStatusTagColor = (status: enumInstanceStatus) => { + switch (status) { + case enumInstanceStatus.PENDING: + return '#ffecb3'; + case enumInstanceStatus.RUNNING: + return '#87d068'; + case enumInstanceStatus.STOPPING: + return '#ffd8bf'; + case enumInstanceStatus.STOPPED: + return '#d9d9d9'; + case enumInstanceStatus.DELETED: + return '#f50'; + default: + return 'default'; + } + }; + + // sorting function + const sortedInstances = [...getInstances.data].sort((a, b) => { + if (a.status === enumInstanceStatus.RUNNING && b.status !== enumInstanceStatus.RUNNING) { + return -1; + } + if (b.status === enumInstanceStatus.RUNNING && a.status !== enumInstanceStatus.RUNNING) { + return 1; + } + if (a.status === enumInstanceStatus.FAILED && b.status !== enumInstanceStatus.FAILED) { + return 1; + } + if (b.status === enumInstanceStatus.FAILED && a.status !== enumInstanceStatus.FAILED) { + return -1; + } + // Within the same status, sort by creation time, most recent first + return new Date(b.createAt).getTime() - new Date(a.createAt).getTime(); + }); + + + return ( +
+
+
+ {} +

My Instances

+ + + +
+ +
+
+ + {sortedInstances.map((instance) => ( + {instance.name}} + extra={{instance.status}} + className={css.cardContainer} + > + +
+

Application Type: {instance.appType}

+

Created At: {new Date(instance.createAt).toLocaleString()}

+

+ Life Span: {(Number(instance.lifeSpan) / 1000 / 3600).toFixed(2)} (hours) + {/* Add Extend button inline with lifespan for instances with less than 7 days remaining */} + {instance.lifeSpan > 0 && instance.lifeSpan < 7 * 24 * 60 * 60 * 1000 && ( + + + + )} +

+

+ CPU: {instance.config && typeof instance.config['limits.cpu'] === 'string' ? instance.config['limits.cpu'] : 'N/A'} Cores, + Memory: {instance.config && typeof instance.config['limits.memory'] === 'string' ? instance.config['limits.memory'] : 'N/A'} +

+ + + {instance.status === enumInstanceStatus.RUNNING && ( + + ( + + CPU: {('cpuUsage' in instance.metadata) ? instance.metadata.cpuUsage as number : 0}% + + )} + size="default" + style={{ width: '150px' }} // Adjust width + /> + ( + + Memory: {('memoryUsage' in instance.metadata) ? Math.round(instance.metadata.memoryUsage as number) : 0}% + + )} + size="default" + style={{ width: '150px' }} // Adjust width + /> + + )} + + + {/* Conditionally render Launch/Stop button based on status */} + + {instance.status === enumInstanceStatus.STOPPED && instance.lifeSpan > 0 && ( + + )} + {instance.status === enumInstanceStatus.RUNNING && ( + // change the danger to warning color + + )} + {/* Only show Delete button for STOPPED status */} + {(instance.status === enumInstanceStatus.STOPPED || instance.status === enumInstanceStatus.FAILED) && ( + + )} + {/*Restart button to reset the instance with new lifespan */} + {instance.status === enumInstanceStatus.STOPPED && instance.lifeSpan <= 0 && ( + + )} + {/** Remove the extend button from here since we moved it next to the lifespan */} + {/** console connection button, only show for RUNNING status */} + {instance.appType === enumAppType.MATLAB && instance.status === enumInstanceStatus.RUNNING && ( + // set the button color to green + + )} + {instance.appType === enumAppType.JUPYTER && instance.status === enumInstanceStatus.RUNNING && ( + )} + + + ))} + setIsModalOpen(false)} onOk={() => createForm.submit()}> +
+ + + + + + + {selectedInstanceTypeDetails && ( +
{selectedInstanceTypeDetails}
+ )} + +
+ + ); +}; \ No newline at end of file diff --git a/packages/itmat-ui-react/src/components/log/index.tsx b/packages/itmat-ui-react/src/components/log/index.tsx index 1d3c20bad..099774271 100644 --- a/packages/itmat-ui-react/src/components/log/index.tsx +++ b/packages/itmat-ui-react/src/components/log/index.tsx @@ -1,5 +1,5 @@ import { FunctionComponent } from 'react'; -import { LogListSection } from './logList'; +import { LogSection } from './logList'; import css from './logList.module.css'; export const LogPage: FunctionComponent = () => { @@ -10,7 +10,7 @@ export const LogPage: FunctionComponent = () => { Log History
- +
diff --git a/packages/itmat-ui-react/src/components/log/logList.module.css b/packages/itmat-ui-react/src/components/log/logList.module.css index d1cb47e2d..120ae1dc0 100644 --- a/packages/itmat-ui-react/src/components/log/logList.module.css +++ b/packages/itmat-ui-react/src/components/log/logList.module.css @@ -3,85 +3,51 @@ grid-template-rows: 100%; display: grid; height: 100%; - grid-template-areas: "userList" } -.log_list_section { - grid-area: logList; +.timeBar { + height: 10px; + border-radius: 5px; + transition: width 0.3s; } -.log_list > table:first-of-type thead:first-of-type tr:first-of-type { - background: var(--color-background); +.chartWrapper { + width: 100%; + height: 100px; + position: relative; + overflow: hidden; } -.button_clicked button { - background-color: red; +.overview-header { + display: flex; + align-items: center; + /* This will vertically center align the icon and the title */ + /* If there is still a misalignment, you may adjust by adding 'align-self' to the children */ + font-size: 20px; + font-weight: 500; + overflow: auto; } -.submit_cancel_button_wrapper { - display: flex; - width: 100%; +.overview-icon { + background-color: #000; + /* Or any color you prefer */ + width: 4px; + /* Thickness of the line */ + height: 24px; + /* Height of the line, adjust as needed */ + margin-right: 16px; + /* Space between the line and the text */ + /* Adjust 'margin-top' if the line is not aligned with the text */ } -/* The switch - the box around the slider */ -.switch { - position: relative; - display: inline-block; - width: 40px; - height: 20px; - } - - /* Hide default HTML checkbox */ - .switch input { - opacity: 0; - width: 0; - height: 0; - } - - /* The slider */ - .slider { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: #ccc; - -webkit-transition: .4s; - transition: .4s; - } - - .slider:before { - position: absolute; - content: ""; - height: 20px; - width: 20px; - left: 0px; - bottom: 0px; - background-color: white; - -webkit-transition: .4s; - transition: .4s; - } - - input:checked + .slider { - background-color: #2196F3; - } - - input:focus + .slider { - box-shadow: 0 0 1px #2196F3; - } - - input:checked + .slider:before { - -webkit-transform: translateX(20px); - -ms-transform: translateX(20px); - transform: translateX(20px); - } - - /* Rounded sliders */ - .slider.round { - border-radius: 34px; - } - - .slider.round:before { - border-radius: 50%; - } \ No newline at end of file +.overview-title { + /* Style for your title */ + font-size: 24px; + font-weight: bold; +} + +.overview-content { + /* Style for your content */ + text-indent: 30px; + /* Adjust the indent as needed */ +} diff --git a/packages/itmat-ui-react/src/components/log/logList.tsx b/packages/itmat-ui-react/src/components/log/logList.tsx index 8cf8f3b54..9f1ca82f6 100644 --- a/packages/itmat-ui-react/src/components/log/logList.tsx +++ b/packages/itmat-ui-react/src/components/log/logList.tsx @@ -1,298 +1,330 @@ -import { FunctionComponent, useState } from 'react'; -import { GET_LOGS } from '@itmat-broker/itmat-models'; -import { userTypes, LOG_ACTION, LOG_TYPE, LOG_STATUS, USER_AGENT, ILogEntry } from '@itmat-broker/itmat-types'; -import { Query } from '@apollo/client/react/components'; +import { FunctionComponent } from 'react'; +import { List, Progress, Table, Tooltip } from 'antd'; +import { trpc } from '../../utils/trpc'; import LoadSpinner from '../reusable/loadSpinner'; -import { Table, Input, Button, Checkbox, Descriptions, DatePicker, Modal, Row, Col } from 'antd'; -import Highlighter from 'react-highlight-words'; +import { ISystemConfig, IUserWithoutToken, enumConfigType } from '@itmat-broker/itmat-types'; +import css from './logList.module.css'; +import { ResponsiveTimeRange } from '@nivo/calendar'; import dayjs from 'dayjs'; -export const LogListSection: FunctionComponent = () => { +type DailyCount = { + dayIndex: number; + count: number; +}; - return ( - - query={GET_LOGS} - variables={{}} - > - {({ loading, error, data }) => { - if (loading) { return ; } - if (error) { - return ( -

- Error {error.message} -

- ); - } - const logList: ILogEntry[] = data.getLogs; - return ( - - ); - }} - - ); +type EventStatistics = { + event: string; + count: number; + dailyCounts: DailyCount[]; + avgTimeConsumed: number; + stdDevTimeConsumed: number; }; -const LogList: FunctionComponent<{ list: ILogEntry[] }> = ({ list }) => { - const [searchTerm, setSearchTerm] = useState(''); - const initInputs = { - requesterName: '', - requesterType: [], - userAgent: [], - logType: [], - actionType: [], - time: '', - status: [], - dateRange: ['', ''] - }; - const [inputs, setInputs]: [{ [key: string]: any }, any] = useState(initInputs); - const [verbose, setVerbose] = useState(false); - const [verboseInfo, setVerboseInfo] = useState(({ actionData: JSON.stringify({}) })); - const [advancedSearch, setAdvancedSearch] = useState(false); - const dateFormat = 'YYYY-MM-DD'; - function formatActionData(data: any) { - const obj = JSON.parse(data.actionData); - const keys = Object.keys(obj); - const keysMap = keys.map((el) => <>{el}
); - const valuesMap = keys.map((el) => <>{JSON.stringify((obj[el] || 'NA'))}
); - return { keyList: keysMap, valueList: valuesMap }; - } - /* time offset, when filter by time, adjust all date to utc time */ - const timeOffset = dayjs().utcOffset() /* minutes */ * 60 /* seconds */ * 1000 /* to UNIX millisec */; +type RequesterStatistics = { + requester: string; + count: number; + dailyCounts: DailyCount[]; + avgTimeConsumed: number; + stdDevTimeConsumed: number; +}; - const inputControl = (property: string) => ({ - value: inputs[property], - onChange: (e: any) => { - setInputs({ ...inputs, [property]: property === 'requesterName' ? e.target.value : e }); - } - }); +type LogsSummary = { + eventStatistics: EventStatistics[]; + requesterStatistics: RequesterStatistics[]; +}; - const checkboxControl = (property: string) => ({ - value: inputs[property], - onChange: (e: any) => { - setInputs({ ...inputs, [property]: e }); - } - }); - function dataSourceFilter(logList: ILogEntry[]) { - return logList.filter((log) => { - // convert the contest of log to be string (except for value), as null could not be parsed - const logCopy: any = { ...log }; - Object.keys(logCopy).forEach(item => { - logCopy[item] = (logCopy[item] || '').toString(); - }); - return (searchTerm === '' || (logCopy.requesterName.toUpperCase().search(searchTerm) > -1 || logCopy.requesterType.toUpperCase().search(searchTerm) > -1 - || logCopy.logType.toUpperCase().search(searchTerm) > -1 || logCopy.actionType.toUpperCase().search(searchTerm) > -1 - || logCopy.status.toUpperCase().search(searchTerm) > -1 || logCopy.userAgent.toUpperCase().search(searchTerm) > -1 - || new Date(logCopy.time).toUTCString().toUpperCase().search(searchTerm) > -1 || logCopy.status.toUpperCase().search(searchTerm) > -1)) - && (inputs.requesterName === '' || logCopy.requesterName.toLowerCase().indexOf(inputs.requesterName.toLowerCase()) !== -1) - && (inputs.requesterType.length === 0 || inputs.requesterType.includes(logCopy.requesterType)) - && (inputs.userAgent.length === 0 || inputs.userAgent.includes(logCopy.userAgent)) - && (inputs.logType.length === 0 || inputs.logType.includes(logCopy.logType)) - && (inputs.actionType.length === 0 || inputs.actionType.includes(logCopy.actionType)) - && (inputs.status.length === 0 || inputs.status.includes(logCopy.status)) - && (inputs.dateRange[0] === '' || (dayjs(inputs.dateRange[0].startOf('day')).valueOf() - timeOffset) < logCopy.time) - && (inputs.dateRange[1] === '' || (dayjs(inputs.dateRange[1].startOf('day')).valueOf() + 24 * 60 * 60 * 1000 /* ONE DAY IN MILLSEC */ - timeOffset) > logCopy.time); - }); +export const LogSection: FunctionComponent = () => { + const getLogs = trpc.log.getLogs.useQuery({ indexRange: [0, 100] }); + const getUsers = trpc.user.getUsers.useQuery({}); + const getSystemConfig = trpc.config.getConfig.useQuery({ configType: enumConfigType.SYSTEMCONFIG, key: null, useDefault: true }); + const getLogsSummary = trpc.log.getLogsSummary.useQuery(); + if (getLogs.isLoading || getUsers.isLoading || getSystemConfig.isLoading || getLogsSummary.isLoading) { + return <> +
Loading...
+
+ +
+ ; } - const columns = [ - { - title: 'Requester Name', - dataIndex: 'requesterName', - key: 'requesterName', - render: (__unused__value, record) => { - if (searchTerm) - return ; - else - return record.requesterName; - } - }, - { - title: 'Requester Type', - dataIndex: 'requesterType', - key: 'requesterType', - render: (__unused__value, record) => { - if (searchTerm) - return ; - else - return record.requesterType; - } - }, - { - title: 'Request Type', - dataIndex: 'logType', - key: 'logType', - render: (__unused__value, record) => { - if (searchTerm) - return ; - else - return record.logType; - } - }, - { - title: 'Request From', - dataIndex: 'userAgent', - key: 'userAgent', - render: (__unused__value, record) => { - if (searchTerm) - return ; - else - return record.userAgent; - } - }, - { - title: 'API Type', - dataIndex: 'actionType', - key: 'actionType', - render: (__unused__value, record) => { - if (searchTerm) - return ; - else - return record.actionType; - } - }, - { - title: 'Time', - dataIndex: 'time', - key: 'time', - render: (__unused__value, record) => { - return new Date(record.time).toUTCString(); + if (getLogs.isError || getUsers.isError || getSystemConfig.isError || getLogsSummary.isError) { + return <>An error occurred.; + } + + return
+ +
+
+
+
Logs
+
+
+
+
+
} - }, - { - title: 'Status', - dataIndex: 'status', - key: 'status', - render: (__unused__value, record) => { - if (searchTerm) - return ; - else - return record.status; + > + +
+ +
+ +
+
+
+
Statistics
+
+
+
+
+ } + > + +
+ + +
+ + + ; +}; + +const generateEventStatisticsColumns = (barThreshold: number[]) => { + return [{ + title: 'Event', + dataIndex: 'event', + key: 'event', + width: '20%', + render: (__unused__value, record) => { + return record.event; } - ]; + }, { + title: 'Number Calls', + dataIndex: 'count', + key: 'count', + width: '20%', + sorter: (a, b) => a.count - b.count, + render: (__unused__value, record) => { + return record.count; + } + }, { + title: 'Avg Time Consuming/ms', + dataIndex: 'avgTimeConsumed', + key: 'avgTimeConsumed', + width: '20%', + sorter: (a, b) => a.avgTimeConsumed - b.avgTimeConsumed, + render: (__unused__value, record) => { + const value = Math.round(record.avgTimeConsumed * 100) / 100; + return + + ; + } + }, { + title: 'Activity', + dataIndex: 'activity', + key: 'activity', + width: '40%', + render: (__unused__value, record) => { + return
+ { + return { + day: dayjs().add(rel.dayIndex, 'day').format('YYYY-MM-DD'), + value: rel.count + }; + })} + from={dayjs().subtract(40, 'day').format('YYYY-MM-DD')} + to={dayjs().format('YYYY-MM-DD')} + emptyColor="#eeeeee" + colors={['#61cdbb', '#97e3d5', '#e8c1a0', '#f47560']} + margin={{ top: 40, right: 40, bottom: 100, left: 40 }} + dayBorderWidth={2} + dayBorderColor="#ffffff" + /> +
; + } + }]; +}; - const detailColumns = [ - { - title: 'Field', - dataIndex: 'keyList', - key: 'keyList', - render: (__unused__value, record) => { - return record.keyList; - } - }, - { - title: 'Value', - dataIndex: 'valueList', - key: 'valueList', - render: (__unused__value, record) => { - return record.valueList; +const generateRequesterStatisticsColumns = (barThreshold: number[], users: IUserWithoutToken[]) => { + return [{ + title: 'Requester', + dataIndex: 'requester', + key: 'requester', + width: '20%', + render: (__unused__value, record) => { + const user = users.filter(el => el.id === record.requester)[0]; + if (user) { + return `${user.firstname} ${user.lastname}`; + } else { + return 'NA'; } } - ]; + }, { + title: 'Number Calls', + dataIndex: 'count', + key: 'count', + width: '20%', + sorter: (a, b) => a.count - b.count, + render: (__unused__value, record) => { + return record.count; + } + }, { + title: 'Avg Time Consuming/ms', + dataIndex: 'avgTimeConsumed', + key: 'avgTimeConsumed', + width: '20%', + sorter: (a, b) => a.avgTimeConsumed - b.avgTimeConsumed, + render: (__unused__value, record) => { + const value = Math.round(record.avgTimeConsumed * 100) / 100; + return + + ; + } + }, { + title: 'Activity', + dataIndex: 'activity', + key: 'activity', + width: '40%', + render: (__unused__value, record) => { + return
+ { + return { + day: dayjs().add(rel.dayIndex, 'day').format('YYYY-MM-DD'), + value: rel.count + }; + })} + from={dayjs().subtract(40, 'day').format('YYYY-MM-DD')} + to={dayjs().format('YYYY-MM-DD')} + emptyColor="#eeeeee" + colors={['#61cdbb', '#97e3d5', '#e8c1a0', '#f47560']} + margin={{ top: 40, right: 40, bottom: 100, left: 40 }} + dayBorderWidth={2} + dayBorderColor="#ffffff" + /> +
; + } + }]; +}; - return <> - setSearchTerm(value.toUpperCase())} />    -     - - { setAdvancedSearch(false); }} - onCancel={() => { setAdvancedSearch(false); }} - okText='OK' - cancelText='Reset' - width={'50%'} - > - - - - - - - - - - - - {Object.keys(LOG_ACTION).map(el => {el})} - - - - - - - -
rec.id} - onRow={(record: ILogEntry) => ({ - onClick: () => { - setVerbose(true); - setVerboseInfo(record); - }, - style: { - cursor: 'pointer' - } - })} - pagination={ - { - defaultPageSize: 10, - showSizeChanger: true, - pageSizeOptions: ['10', '20', '50', '100'], - defaultCurrent: 1, - showQuickJumper: true - } - } - columns={columns} - dataSource={dataSourceFilter(list)} - size='small' - > -
- { setVerbose(false); }} - onCancel={() => { setVerbose(false); }} - > - -
-
- ; +const generateLogColumns = (users: IUserWithoutToken[], barThreshold: number[]) => { + return [{ + title: 'Caller', + dataIndex: 'caller', + key: 'caller', + render: (__unused__value, record) => { + const user = users.filter(el => el.id === record.requester)[0]; + return user ? `${user.firstname} ${user.lastname}` : 'NA'; + } + }, { + title: 'type', + dataIndex: 'type', + key: 'type', + render: (__unused__value, record) => { + return record.type; + } + }, { + title: 'Resolver', + dataIndex: 'apiResolver', + key: 'apiResolver', + render: (__unused__value, record) => { + return record.apiResolver; + } + }, { + title: 'Event', + dataIndex: 'event', + key: 'event', + render: (__unused__value, record) => { + return record.event; + } + }, { + title: 'Status', + dataIndex: 'status', + key: 'status', + render: (__unused__value, record) => { + return record.status; + } + }, { + title: 'Time Elapsed/ms', + dataIndex: 'timeConsumed', + key: 'timeConsumed', + sorter: (a, b) => a.timeConsumed - b.timeConsumed, + render: (__unused__value, record) => { + // return ; + const percent = Math.min(100, (record.timeConsumed / barThreshold[1]) * 100); + return ( + + + + ); + } + }, { + title: 'Execution Time', + dataIndex: 'time', + key: 'time', + sorter: (a, b) => a.life.createdTime ?? 0 - b.life.createdTime ?? 0, + render: (__unused__value, record) => { + return new Date(record.life.createdTime).toUTCString(); + } + }]; +}; + +const greenRGB = [0, 255, 0]; // RGB for green +const blueRGB = [0, 0, 255]; // RGB for blue +const redRGB = [255, 0, 0]; // RGB for red + +const lerp = (start, end, t) => { + return start + t * (end - start); +}; + +const getColor = (value, threshold) => { + let startColor, endColor, t; + + if (value < threshold[0]) { + startColor = greenRGB; + endColor = blueRGB; + t = value / threshold[0]; // Normalize between 0 and 1 + } else if (value <= threshold[1]) { + startColor = blueRGB; + endColor = redRGB; + t = (value - threshold[0]) / (threshold[1] - threshold[0]); // Normalize between 0 and 1 + } else { + return `rgb(${redRGB.join(',')})`; // If it's over b, just return red + } + + const r = Math.round(lerp(startColor[0], endColor[0], t)); + const g = Math.round(lerp(startColor[1], endColor[1], t)); + const b = Math.round(lerp(startColor[2], endColor[2], t)); + + return `rgb(${r},${g},${b})`; }; diff --git a/packages/itmat-ui-react/src/components/login/login.module.css b/packages/itmat-ui-react/src/components/login/login.module.css index ad010154f..5da657838 100644 --- a/packages/itmat-ui-react/src/components/login/login.module.css +++ b/packages/itmat-ui-react/src/components/login/login.module.css @@ -14,7 +14,7 @@ padding-bottom: 4rem; } -.login_box [class*=ant-select] { +.login_box [class*="ant-select"] { text-align: left; } diff --git a/packages/itmat-ui-react/src/components/login/login.tsx b/packages/itmat-ui-react/src/components/login/login.tsx index 37941ca09..ca365fcc0 100644 --- a/packages/itmat-ui-react/src/components/login/login.tsx +++ b/packages/itmat-ui-react/src/components/login/login.tsx @@ -1,79 +1,124 @@ import { FunctionComponent } from 'react'; -import { Mutation } from '@apollo/client/react/components'; -import { LOGIN, WHO_AM_I } from '@itmat-broker/itmat-models'; -import { NavLink } from 'react-router-dom'; +import { NavLink, useNavigate} from 'react-router-dom'; import css from './login.module.css'; -import { Input, Form, Button, Alert, Checkbox } from 'antd'; +import { Input, Form, Button, Alert, Checkbox, message, Image, Divider} from 'antd'; +import { trpc } from '../../utils/trpc'; +import LoadSpinner from '../reusable/loadSpinner'; +import { useAuth } from '../../utils/dmpWebauthn/webauthn.context'; +import { UserOutlined, KeyOutlined} from '@ant-design/icons'; export const LoginBox: FunctionComponent = () => { + const login = trpc.user.login.useMutation({ + onSuccess: () => { + window.location.reload(); + }, + onError: () => { + void message.error('Failed to log in.'); + } + }); + + const getCurrentSubPath = trpc.domain.getCurrentSubPath.useQuery(); + const getCurrentDomain = trpc.domain.getCurrentDomain.useQuery(); + + const navigate = useNavigate(); + const { isWebauthAvailable, credentials } = useAuth(); // Accessing webauthn availability and credentials + + if (getCurrentSubPath.isLoading || getCurrentDomain.isLoading) { + return <> +
Loading...
+
+ +
+ ; + } + if (getCurrentSubPath.isError || getCurrentDomain.isError) { + return <> + An error occured. + ; + } + + const handleAuthLogin = () => { + navigate('/authenticate_webauthn'); + }; return ( +
+
+ +

Welcome

+
+
+
{ login.mutate({ ...variables, requestexpirydate: false }); }}> + + + + + + + + + + {login.isError ? ( + <> + +
+ + ) : null} + {(login.error?.message === 'Account Expired. Please request a new expiry date!') ? ( + <> + + Tick the box to request a new expiry date! + + + + + + ) : + + + + } - - mutation={LOGIN} - update={(cache, { data: { login } }) => { - cache.writeQuery({ - query: WHO_AM_I, - data: { whoAmI: login } - }); - }} - onError={() => { return; }} - > - {(login, { loading, error }) => { - return ( -
-
- IDEA-FAST Logo -

Welcome

-
-
- login({ variables })}> - - - - - - - - - - {error ? ( - <> - error.message).join()} /> -
- - ) : null} - {(error?.message === 'Account Expired. Please request a new expiry date!') ? ( - <> - - Tick the box to request a new expiry date! - - - - - - ) : - - - - } + +
- + {/* Conditionally render the "Login with Authenticator" button */} + {(isWebauthAvailable && credentials && credentials.length > 0) && ( + <> + Or +
-
- ); - }} - + Login with an authenticator + + + )} +
+
+
+ Forgot username or password
+ Do not have an account? Please register
+ v{process.env.NX_REACT_APP_VERSION} - {process.env.NX_REACT_APP_COMMIT} ({process.env.NX_REACT_APP_BRANCH}) +
+
); }; diff --git a/packages/itmat-ui-react/src/components/login/register.tsx b/packages/itmat-ui-react/src/components/login/register.tsx index 46692bf2f..165069414 100644 --- a/packages/itmat-ui-react/src/components/login/register.tsx +++ b/packages/itmat-ui-react/src/components/login/register.tsx @@ -1,29 +1,59 @@ import { FunctionComponent, useState } from 'react'; -import { useQuery, useMutation } from '@apollo/client/react/hooks'; import { NavLink, useNavigate } from 'react-router-dom'; -import { GET_ORGANISATIONS, CREATE_USER } from '@itmat-broker/itmat-models'; import { IOrganisation } from '@itmat-broker/itmat-types'; -import { Input, Form, Button, Alert, Checkbox, Select } from 'antd'; +import { Input, Form, Button, Alert, Checkbox, Select, Image, message } from 'antd'; import css from './login.module.css'; +import { trpc } from '../../utils/trpc'; +import LoadSpinner from '../reusable/loadSpinner'; export const RegisterNewUser: FunctionComponent = () => { const navigate = useNavigate(); const [completedCreation, setCompletedCreation] = useState(false); - const [createUser, { loading, error }] = useMutation(CREATE_USER, - { onCompleted: () => setCompletedCreation(true) } - ); + const createUser = trpc.user.createUser.useMutation({ + onSuccess: () => { + void addUserToPublicStudies(createUser.variables?.username); + setCompletedCreation(true); + }, + onError: () => { + return; + } + }); + const addGuestUser = trpc.role.addGuestUser.useMutation({ + onSuccess: () => { + void message.success('Role created successfully'); + }, + onError: () => { + return; + } + }); + + const getCurrentSubPath = trpc.domain.getCurrentSubPath.useQuery(); + const getCurrentDomain = trpc.domain.getCurrentDomain.useQuery(); // Get list of organisations from server - const { loading: getorgsloading, error: getorgserror, data: getorgsdata } = useQuery(GET_ORGANISATIONS); - if (getorgsloading) { return

Loading..

; } - if (getorgserror) { return

ERROR: please try again.

; } - const orgList: IOrganisation[] = getorgsdata.getOrganisations; + const getOrganisations = trpc.organisation.getOrganisations.useQuery({}); + + if (getCurrentSubPath.isLoading || getCurrentDomain.isLoading || getOrganisations.isLoading) { + return <> +
Loading...
+
+ +
+ ; + } + if (getCurrentSubPath.isError || getCurrentDomain.isError || getOrganisations.isError) { + return <> + An error occured. + ; + } + + const orgList: IOrganisation[] = getOrganisations.data; if (completedCreation) { return (
- IDEA-FAST Logo +

Registration Successful!


@@ -40,14 +70,20 @@ export const RegisterNewUser: FunctionComponent = () => { ); } + const addUserToPublicStudies = async (username?: string) => { + if (username) { + addGuestUser.mutate({ username }); + } + }; + return (
- IDEA-FAST Logo +

Register an Account


-
createUser({ variables })}> + { createUser.mutate(variables); }}> @@ -57,7 +93,7 @@ export const RegisterNewUser: FunctionComponent = () => { ({ - validator(rule, value) { + async validator(rule, value) { if (!value || getFieldValue('password') === value) { return Promise.resolve(); } @@ -86,10 +122,9 @@ export const RegisterNewUser: FunctionComponent = () => { Subscribe to email notifications - {error ? ( + {createUser.isError ? ( <> - error.message).join()} /> - +
) : null} @@ -100,7 +135,7 @@ export const RegisterNewUser: FunctionComponent = () => { Cancel     -
diff --git a/packages/itmat-ui-react/src/components/login/requestResetPasswordPage.tsx b/packages/itmat-ui-react/src/components/login/requestResetPasswordPage.tsx index e58735493..b21f150e8 100644 --- a/packages/itmat-ui-react/src/components/login/requestResetPasswordPage.tsx +++ b/packages/itmat-ui-react/src/components/login/requestResetPasswordPage.tsx @@ -1,9 +1,9 @@ import { FunctionComponent, useState } from 'react'; -import { Mutation } from '@apollo/client/react/components'; import { NavLink, useNavigate } from 'react-router-dom'; -import { REQUEST_USERNAME_OR_RESET_PASSWORD } from '@itmat-broker/itmat-models'; import css from './login.module.css'; import { Input, Form, Button, Alert } from 'antd'; +import LoadSpinner from '../reusable/loadSpinner'; +import { trpc } from '../../utils/trpc'; export const RequestResetPassword: FunctionComponent = () => { @@ -11,11 +11,36 @@ export const RequestResetPassword: FunctionComponent = () => { const [forgotUsername, setForgotUsername] = useState(false); const [requestCompleted, setRequestCompleted] = useState(false); + const getCurrentSubPath = trpc.domain.getCurrentSubPath.useQuery(); + const getCurrentDomain = trpc.domain.getCurrentDomain.useQuery(); + const requestPasswordReset = trpc.user.requestUsernameOrResetPassword.useMutation({ + onSuccess: () => { + setRequestCompleted(true); + }, + onError: () => { + return; + } + }); + + if (getCurrentSubPath.isLoading || getCurrentDomain.isLoading) { + return <> +
Loading...
+
+ +
+ ; + } + if (getCurrentSubPath.isError || getCurrentDomain.isError) { + return <> + An error occured. + ; + } + if (requestCompleted) { return (
- IDEA-FAST Logo +

Done!


{`A link for password reset ${forgotUsername ? 'together with your username ' : ''}has been sent to your email.`}

@@ -32,77 +57,68 @@ export const RequestResetPassword: FunctionComponent = () => { } + return ( - - mutation={REQUEST_USERNAME_OR_RESET_PASSWORD} - onError={() => { return; }} - onCompleted={() => setRequestCompleted(true)} - > - {(requestPasswordReset, { loading, error }) => { - return ( -
-
- IDEA-FAST Logo -

Forgot your {forgotUsername ? 'username' : 'password'}?

-
-
- requestPasswordReset({ - variables: { - ...variables, - forgotUsername, - forgotPassword: true - } - })}> - {forgotUsername ? - - - - : - - - - } - {error ? ( - <> - error.message).join()} /> -
- - ) : null} - - - {forgotUsername - ? <> -     - - - : <> -     - - } -     - - - -
-
-
-
- Do not have an account? Please register
- v{process.env.NX_REACT_APP_VERSION} - {process.env.NX_REACT_APP_COMMIT} ({process.env.NX_REACT_APP_BRANCH}) -
-
- ); - }} - +
+
+ +

Forgot your {forgotUsername ? 'username' : 'password'}?

+
+
+
{ + requestPasswordReset.mutate({ + ...variables, + forgotUsername, + forgotPassword: true + }); + }}> + {forgotUsername ? + + + + : + + + + } + {requestPasswordReset.isError ? ( + <> + +
+ + ) : null} + + + {forgotUsername + ? <> +     + + + : <> +     + + } +     + + + +
+
+
+
+ Do not have an account? Please register
+ v{process.env.NX_REACT_APP_VERSION} - {process.env.NX_REACT_APP_COMMIT} ({process.env.NX_REACT_APP_BRANCH}) +
+
); }; diff --git a/packages/itmat-ui-react/src/components/login/resetPasswordPage.tsx b/packages/itmat-ui-react/src/components/login/resetPasswordPage.tsx index 963fed73f..f62446f1f 100644 --- a/packages/itmat-ui-react/src/components/login/resetPasswordPage.tsx +++ b/packages/itmat-ui-react/src/components/login/resetPasswordPage.tsx @@ -1,10 +1,8 @@ import { FunctionComponent, useState } from 'react'; -import { Mutation } from '@apollo/client/react/components'; -import { useQuery } from '@apollo/client/react/hooks'; -import { VALIDATE_RESET_PASSWORD, RESET_PASSWORD } from '@itmat-broker/itmat-models'; import { NavLink, useNavigate, useParams } from 'react-router-dom'; import css from '../login/login.module.css'; import { Input, Form, Button, Alert } from 'antd'; +import { trpc } from '../../utils/trpc'; import LoadSpinner from '../reusable/loadSpinner'; export const ResetPasswordPage: FunctionComponent = () => { @@ -12,14 +10,17 @@ export const ResetPasswordPage: FunctionComponent = () => { const { encryptedEmail, token } = useParams(); const navigate = useNavigate(); const [passwordSuccessfullyChanged, setPasswordSuccessfullyChanged] = useState(false); - const { loading, error } = useQuery(VALIDATE_RESET_PASSWORD, { - variables: { - encryptedEmail: encryptedEmail, - token: token + const validateResetPassword = trpc.user.validateResetPassword.useQuery({ + encryptedEmail: encryptedEmail || '', + token: token || '' + }); + const resetPasswordMutation = trpc.user.resetPassword.useMutation({ + onSuccess: () => { + setPasswordSuccessfullyChanged(true); } }); - if (loading) { return ; } - if (error) { + if (validateResetPassword.isLoading) { return ; } + if (validateResetPassword.isError) { return

The link is invalid. Please make a new request.

@@ -59,73 +60,60 @@ export const ResetPasswordPage: FunctionComponent = () => { } return ( - - mutation={RESET_PASSWORD} - onCompleted={() => { - setPasswordSuccessfullyChanged(true); - }} - onError={() => { return; }} - > - {(resetPassword, { loading, error }) => { - return ( - -
-
- IDEA-FAST Logo -

Reset your password

-
-
-
resetPassword({ - variables: { - ...variables, - encryptedEmail, - token +
+
+ IDEA-FAST Logo +

Reset your password

+
+
+ { + resetPasswordMutation.mutateAsync({ + ...variables, + encryptedEmail: encryptedEmail || '', + token: token || '' + }).catch(() => { return; }); + }}> + + + + ({ + async validator(_rule: unknown, value: string) { + if (!value || getFieldValue('newPassword') === value) { + return Promise.resolve(); } - })}> - - - - ({ - validator(rule, value) { - if (!value || getFieldValue('newPassword') === value) { - return Promise.resolve(); - } - return Promise.reject('The two passwords that you entered do not match!'); - } - }) - ]} > - - - {error ? ( - <> - error.message).join()} /> -
- - ) : null} - - -     - - - -
-
-
-
- Do not have an account? Please register
- v{process.env.NX_REACT_APP_VERSION} - {process.env.NX_REACT_APP_COMMIT} ({process.env.NX_REACT_APP_BRANCH}) -
-
- ); - }} - + return Promise.reject('The two passwords that you entered do not match!'); + } + }) + ]} > + + + {resetPasswordMutation.isError ? ( + <> + +
+ + ) : null} + + +     + + + +
+
+
+
+ Do not have an account? Please register
+ v{process.env.NX_REACT_APP_VERSION} - {process.env.NX_REACT_APP_COMMIT} ({process.env.NX_REACT_APP_BRANCH}) +
+
); }; diff --git a/packages/itmat-ui-react/src/components/lxd/index.tsx b/packages/itmat-ui-react/src/components/lxd/index.tsx new file mode 100644 index 000000000..4708ae788 --- /dev/null +++ b/packages/itmat-ui-react/src/components/lxd/index.tsx @@ -0,0 +1,19 @@ +import React, { FunctionComponent } from 'react'; +import css from './lxd.module.css'; +import LXDInstanceList from './lxd.instance.list'; +// import LXDCommandExecutor from './lxd.instance.terminal'; + +export const LXDPage: FunctionComponent = () => { + return ( +
+
+
+ LXD Management +
+
+ +
+
+
+ ); +}; diff --git a/packages/itmat-ui-react/src/components/lxd/instanceOptions.tsx b/packages/itmat-ui-react/src/components/lxd/instanceOptions.tsx new file mode 100644 index 000000000..785c49d4e --- /dev/null +++ b/packages/itmat-ui-react/src/components/lxd/instanceOptions.tsx @@ -0,0 +1,62 @@ +export const instanceCreationTypes = [ + { + label: 'Container', + value: 'container' + }, + { + label: 'VM', + value: 'virtual-machine' + } +]; + +export const optionTrueFalse = [ + { + label: 'Select option', + value: '', + disabled: true + }, + { + label: 'true', + value: 'true' + }, + { + label: 'false', + value: 'false' + } +]; + +export const optionAllowDeny = [ + { + label: 'Select option', + value: '', + disabled: true + }, + { + label: 'Allow', + value: 'true' + }, + { + label: 'Deny', + value: 'false' + } +]; + +export const optionYesNo = [ + { + label: 'Select option', + value: '', + disabled: true + }, + { + label: 'Yes', + value: 'true' + }, + { + label: 'No', + value: 'false' + } +]; + +export const diskPriorities = [...Array(11).keys()].map((i) => { + return { label: i.toString(), value: i }; +}); diff --git a/packages/itmat-ui-react/src/components/lxd/instanceTable.tsx b/packages/itmat-ui-react/src/components/lxd/instanceTable.tsx new file mode 100644 index 000000000..fd67300c0 --- /dev/null +++ b/packages/itmat-ui-react/src/components/lxd/instanceTable.tsx @@ -0,0 +1,26 @@ +export const STATUS = 'Status'; +export const NAME = 'Name'; +export const TYPE = 'Type'; +export const DESCRIPTION = 'Description'; +export const IPV4 = 'IPv4'; +export const IPV6 = 'IPv6'; +export const SNAPSHOTS = 'Snapshots'; +export const ACTIONS = 'Actions'; + +export const COLUMN_WIDTHS: Record = { + [NAME]: 170, + [TYPE]: 130, + [DESCRIPTION]: 150, + [SNAPSHOTS]: 110, + [STATUS]: 160, + [ACTIONS]: 210 +}; + +export const SIZE_HIDEABLE_COLUMNS = [ + SNAPSHOTS, + DESCRIPTION, + TYPE, + STATUS +]; + +export const CREATION_SPAN_COLUMNS = [TYPE, DESCRIPTION, IPV4, IPV6, SNAPSHOTS]; diff --git a/packages/itmat-ui-react/src/components/lxd/lxd.Instance.statusIcon.tsx b/packages/itmat-ui-react/src/components/lxd/lxd.Instance.statusIcon.tsx new file mode 100644 index 000000000..130774cea --- /dev/null +++ b/packages/itmat-ui-react/src/components/lxd/lxd.Instance.statusIcon.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { Tooltip } from 'antd'; +import { + CheckCircleOutlined, + SyncOutlined, // This icon can spin + PauseCircleOutlined, + ExclamationCircleOutlined, + StopOutlined, + QuestionCircleOutlined +} from '@ant-design/icons'; +import css from './lxd.module.css'; + +const iconStyle = { fontSize: '16px' }; + +const statusToIcon = { + Running: , + Stopped: , + Starting: , + Stopping: , + Error: , + default: +}; + +const InstanceStatusIcon = ({ status }) => { + const IconComponent = statusToIcon[status] || statusToIcon.default; + return ( + + + {IconComponent} + + + ); +}; + +export default InstanceStatusIcon; diff --git a/packages/itmat-ui-react/src/components/lxd/lxd.instance.list.tsx b/packages/itmat-ui-react/src/components/lxd/lxd.instance.list.tsx new file mode 100644 index 000000000..79a617c29 --- /dev/null +++ b/packages/itmat-ui-react/src/components/lxd/lxd.instance.list.tsx @@ -0,0 +1,325 @@ +import React, { useEffect, useState} from 'react'; +import { Table, Button, message, Spin, Modal, Space, Form, InputNumber, Input } from 'antd'; +import { DeleteOutlined } from '@ant-design/icons'; +// Additional imports +import { PoweroffOutlined, PlayCircleOutlined, ExclamationCircleOutlined } from '@ant-design/icons'; +import { LXDInstanceType, enumOpeType} from '@itmat-broker/itmat-types'; + +// import { instanceCreationTypes } from './instanceOptions'; +import InstanceStatusIcon from '././lxd.Instance.statusIcon'; +import { trpc } from '../../utils/trpc'; +import { formatCPUInfo, formatMemoryInfo, formatStorageInfo, formatGPUInfo} from './util/formatUtils'; + +const { confirm } = Modal; + +const LXDInstanceList = () => { + const [instances, setInstances] = useState([]); + const [loading, setLoading] = useState(true); + const [selectedInstance, setSelectedInstance] = useState(null); + const [isInstanceDetailsModalVisible, setIsInstanceDetailsModalVisible] = useState(false); + + const [isUpdateConfigModalOpen, setIsUpdateConfigModalOpen] = useState(false); + const [editingInstance, setEditingInstance] = useState(null); + + const [updateConfigForm] = Form.useForm(); + + const [systemStats, setSystemStats] = useState({ + cpu: '', + memory: '', + storage: '', + gpu: '' + + }); + + const updateInstanceConfig = trpc.instance.editInstance.useMutation({ + onSuccess: () => { + void message.success('Instance configuration updated successfully.'); + }, + onError: (error) => { + void message.error(`Failed to update instance configuration: ${error.message}`); + } + }); + + const getInstances = trpc.lxd.getInstances.useQuery(undefined, { + refetchInterval: 60 * 1000 // Refetch every 60 seconds + }); + const getResources = trpc.lxd.getResources.useQuery(); + + + // Function to refresh the list of instances + const refreshInstancesList = async () => { + try { + await getInstances.refetch(); + } catch (error) { + console.error('Failed to refresh instances list:', error); + } + }; + + const startStopInstance = trpc.lxd.startStopInstance.useMutation({ + onSuccess: async () => { + void message.success('Operation successful'); + await refreshInstancesList(); + }, + onError: (error) => { + void message.error(`Failed operation: ${error.message}`); + } + }); + + const deleteInstance = trpc.lxd.deleteInstance.useMutation({ + onSuccess: async () => { + void message.success('Instance deleted successfully'); + await refreshInstancesList(); + }, + onError: (error) => { + void message.error(`Failed to delete instance: ${error.message}`); + } + }); + + const handleStartStop = (instanceName: string , action: enumOpeType.START | enumOpeType.STOP, project: string) => { + startStopInstance.mutate({ instanceName, action, project}); + }; + + const handleDelete = (instanceName: string, project: string) => { + confirm({ + title: 'Are you sure you want to delete this instance?', + icon: , + content: ( +
+

Instance: {instanceName}

+

This action cannot be undone. The instance will be permanently deleted.

+
+ ), + okText: 'Delete', + okType: 'danger', + cancelText: 'Cancel', + onOk() { + deleteInstance.mutate({ instanceName, project}); + } + }); + }; + + const openUpdateConfigModal = (instance: LXDInstanceType) => { + setEditingInstance(instance); + // Pre-populate the form with existing values + updateConfigForm.setFieldsValue({ + cpuLimit: parseInt(instance.cpuLimit as string) || 1, + memoryLimit: parseInt(instance.memoryLimit as string) || 1 + }); + setIsUpdateConfigModalOpen(true); + }; + + // Function to show the modal with the instance details + const showInstanceDetails = (instance: LXDInstanceType) => { + setSelectedInstance(instance); + setIsInstanceDetailsModalVisible(true); + }; + + + useEffect(() => { + if (!getInstances.isLoading && getInstances.data) { + const instancesList = getInstances.data.data as LXDInstanceType[]; + const formattedInstances = instancesList.map(instance => ({ + ...instance, // Spread all existing properties to cover all required fields + key: instance.name // Add 'key' if it's additional and not already included in the instance object + })); + setInstances(formattedInstances); + setLoading(false); + } + + if (getInstances.isError) { + void message.error('Failed to load instance state: ' + getInstances.error.message); + setLoading(false); + } + }, [getInstances.isLoading, getInstances.data, getInstances.isError, getInstances.error?.message]); + + + useEffect(() => { + if (!getResources.isLoading && getResources.data && typeof getResources.data.data !== 'string') { + + const data = getResources.data.data; + setSystemStats({ + cpu: formatCPUInfo(data.cpu), + memory: formatMemoryInfo(data.memory), + storage: formatStorageInfo(data.storage), + gpu: formatGPUInfo(data.gpu) + }); + } + + if (getResources.isError) { + void message.error('Error fetching system information'); // getResources?.error?.message + } + }, [getResources.isLoading, getResources.data, getResources.isError]); + + + const columns = [ + { + title: 'Name', + dataIndex: 'name', + key: 'name', + render: (text, record) => ( + + ) + }, + + // username + { + title: 'Username', + dataIndex: 'username', + key: 'username', + render: text => {text} + }, + { + title: 'Status', + dataIndex: 'status', + key: 'status', + render: (status) => + }, + { + title: 'Actions', + key: 'actions', + render: (_, record) => ( + + + + + + {/* Other actions can be added here */} + + ) + } + + ]; + + const handleUpdateInstanceConfig = async (values: { cpuLimit: number; memoryLimit: number; }) => { + if (!editingInstance) return; // Guard clause in case no instance is selected + + try { + await updateInstanceConfig.mutateAsync({ + instanceName: editingInstance.key, + updates: { + cpuLimit: values.cpuLimit, + memoryLimit: `${values.memoryLimit}GB` + }, + instanceId: '' + }); + + setIsUpdateConfigModalOpen(false); + await refreshInstancesList(); // Refresh the list to show updated values + + } catch (error) { + void message.error(`Failed to update instance configuration: ${error instanceof Error ? error.message : String(error)}`); + } + }; + + return ( +
+ +
+

System Resources

+

CPU: {systemStats.cpu}

+

Memory: {systemStats.memory}

+

Storage: {systemStats.storage}

+

GPU: {systemStats.gpu}

+
+ {loading ? : } + setIsInstanceDetailsModalVisible(false)} + footer={null} + > + {selectedInstance && ( +
+

Name: {selectedInstance.name}

+

Status: {selectedInstance.status}

+

Type: {selectedInstance.type}

+

Architecture: {selectedInstance.architecture}

+

Creation Date: {selectedInstance.creationDate}

+

Last Used Date: {selectedInstance.lastUsedDate}

+

Username: {selectedInstance.username}

+

CPU Limit: {selectedInstance.cpuLimit}

+

Memory Limit: {selectedInstance.memoryLimit}

+

Profiles: {selectedInstance.profiles?.join(', ')}

+
+ )} +
+ setIsUpdateConfigModalOpen(false)} + onOk={() => updateConfigForm.submit()} + > +
{ + void handleUpdateInstanceConfig(values); + }} + > + {/* CPU and Memory Form Items */} + + + + ({ + async validator() { + if (typeof getFieldValue('memoryLimit') === 'number') { + return Promise.resolve(); + } + return Promise.reject(new Error('Please input the memory limit!')); + } + }) + ]} + > + + + + + + + + +
+ + ); +}; + +export default LXDInstanceList; diff --git a/packages/itmat-ui-react/src/components/lxd/lxd.module.css b/packages/itmat-ui-react/src/components/lxd/lxd.module.css new file mode 100644 index 000000000..035a8e6f1 --- /dev/null +++ b/packages/itmat-ui-react/src/components/lxd/lxd.module.css @@ -0,0 +1,171 @@ +.page_container { + display: grid; + grid-template-columns: 100%; + grid-template-rows: auto; + height: 100%; + grid-template-areas: "lxdManagementArea"; + /* Additional styles as needed */ +} + +.lxdSection { + grid-area: lxdManagementArea; + padding: 20px; + background-color: #f4f4f4; + border-radius: 8px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + overflow: auto; +} + +.pageAriane { + font-size: 1.5rem; + font-weight: bold; + margin-bottom: 20px; + color: #333; +} + +.pagePanel { + background-color: white; + padding: 20px; + border-radius: 8px; + box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.1); + /* Ensure that the panel can scroll if content overflows */ + overflow: auto; +} + +/* InstanceStatusIcon.module.css */ +.icon_pinning { + animation: spin 1s infinite linear; +} + +@keyframes spin { + 100% { + transform: rotate(360deg); + } +} + +.pTerminalFullscreen { + width: 100%; + height: calc(100vh - 108px); + /* Adjust the height based on your header/footer sizes */ + overflow: hidden; + display: flex; + flex-direction: column; +} + +.pTerminal { + height: 100%; + width: 100%; + flex-grow: 1; + /* Allow terminal to fill the space */ +} + +.terminalInput { + width: 100%; + padding: 10px; + font-size: 16px; + box-sizing: border-box; + /* Include padding in the width */ +} + +.modalOverrides { + width: fit-content !important; + height: fit-content !important; + object-fit: contain !important; + margin: auto !important; + max-width: 100%; + max-height: 100%; + margin: 5vh auto; /* Center the modal vertically with 5% top and bottom margin */ + overflow: auto; /* Add scrollbars if the content is larger than the modal */ +} + +.spiceArea { + min-width: 500px; + min-height: 300px; + max-width: 80vw; + min-height: 50vh; + width: fit-content !important; + height: fit-content !important; + display: block; + background-color: black; +} + +.spiceScreen { + width: 100%; + height: 100%; + /* Ensure the spice screen fills the space */ +} + +.spiceScreen canvas { + max-width: 100%; + max-height: 100%; +} + +/* .consoleContainer { + width: 100%; + height: 100%; + padding: 16px; + background-color: #1e1e1e; + color: white; + box-sizing: border-box; + } */ + +html, +body, +#root { + height: 100%; + margin: 0; + padding: 0; +} + +.consoleContainer { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + background-color: #000; +} + +.p_terminal { + flex-grow: 1; + width: 100%; + height: 100%; +} + +.pTerminalFullscreen { + width: 100%; + height: calc(100vh - 108px); /* Adjust based on your header/footer sizes */ + overflow: hidden; + display: flex; + flex-direction: column; +} + +.p-terminal { + font-family: "Courier New, monospace"; /* Same font as Terminal configuration */ + font-size: 13px; /* Same as terminal options */ + line-height: 1.3; + width: 100%; + height: 100%; + overflow: hidden; /* Prevents scrollbars */ +} + +/* console-modal CSS */ + +.console-modal { + width: 100%; + height: 100%; + padding: 0; + box-sizing: border-box; +} + +/* Fullscreen adjustments */ +.fullscreen-terminal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + width: 100vw; + height: 100vh; + z-index: 9999; + background-color: #000; /* Optional: set background for full screen */ +} diff --git a/packages/itmat-ui-react/src/components/lxd/util/formatUtils.ts b/packages/itmat-ui-react/src/components/lxd/util/formatUtils.ts new file mode 100644 index 000000000..d5349305b --- /dev/null +++ b/packages/itmat-ui-react/src/components/lxd/util/formatUtils.ts @@ -0,0 +1,36 @@ +// src/utils/formatUtils.ts +import { Cpu, Memory, Storage, Gpu} from '@itmat-broker/itmat-types'; + +// Format CPU information +export const formatCPUInfo = (cpu: Cpu) => { + if (!cpu.sockets) return 'CPU information not available'; + const coreCount = cpu.sockets.reduce((acc, socket) => acc + (socket.cores ? socket.cores.length : 0), 0); + return `CPU: ${cpu.total} Socket(s), ${coreCount} Core(s) Total, Architecture: ${cpu.architecture}`; +}; + +// Format Memory information +export const formatMemoryInfo = (memory: Memory) => { + return `Total Memory: ${formatBytes(memory.total)}, Used: ${formatBytes(memory.used)}, Free: ${formatBytes(memory.total - memory.used)}`; +}; + +// Format Storage information +export const formatStorageInfo = (storage: Storage) => { + if (!storage.disks) return 'Storage information not available'; + const totalSize = storage.disks.reduce((acc, disk) => acc + disk.size, 0); + return `Storage: ${storage.total} Disk(s), Total Size: ${formatBytes(totalSize)}`; +}; + +export const formatGPUInfo = (gpu: Gpu) => { + return `GPU: ${gpu.total} Card(s)`; +}; + + +// Helper function to format bytes into a more readable format +export function formatBytes(bytes: number, decimals = 2) { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; +} diff --git a/packages/itmat-ui-react/src/components/lxd/util/updateMaxHeight.tsx b/packages/itmat-ui-react/src/components/lxd/util/updateMaxHeight.tsx new file mode 100644 index 000000000..1c6f64a8b --- /dev/null +++ b/packages/itmat-ui-react/src/components/lxd/util/updateMaxHeight.tsx @@ -0,0 +1,23 @@ +type HeightProperty = 'height' | 'max-height' | 'min-height'; + +export const updateMaxHeight = ( + targetClass: string, + bottomClass?: string, + additionalOffset = 0, + targetProperty: HeightProperty = 'height' +) => { + const elements = document.getElementsByClassName(targetClass); + const belowElements = bottomClass + ? document.getElementsByClassName(bottomClass) + : null; + if (elements.length !== 1 || (belowElements && belowElements.length !== 1)) { + return; + } + const above = elements[0].getBoundingClientRect().top + 1; + const below = belowElements + ? belowElements[0].getBoundingClientRect().height + 1 + : 0; + const offset = Math.ceil(above + below + additionalOffset); + const style = `${targetProperty}: calc(100vh - ${offset}px)`; + elements[0].setAttribute('style', style); +}; diff --git a/packages/itmat-ui-react/src/components/organisation/index.tsx b/packages/itmat-ui-react/src/components/organisation/index.tsx new file mode 100644 index 000000000..40eb9f024 --- /dev/null +++ b/packages/itmat-ui-react/src/components/organisation/index.tsx @@ -0,0 +1,18 @@ +import { FunctionComponent } from 'react'; +import css from './organisations.module.css'; +import { OrganisationListSection } from './organisationList'; + +export const OrganisationPage: FunctionComponent = () => { + return ( +
+
+
+ Organisations +
+
+ +
+
+
+ ); +}; diff --git a/packages/itmat-ui-react/src/components/organisation/organisationList.tsx b/packages/itmat-ui-react/src/components/organisation/organisationList.tsx new file mode 100644 index 000000000..d069b328a --- /dev/null +++ b/packages/itmat-ui-react/src/components/organisation/organisationList.tsx @@ -0,0 +1,204 @@ +import { FunctionComponent, useState } from 'react'; +import { notification, Image, List, Table, Form, message, Button, Modal, Input } from 'antd'; +import { trpc } from '../../utils/trpc'; +import LoadSpinner from '../reusable/loadSpinner'; +import dayjs from 'dayjs'; +import weekday from 'dayjs/plugin/weekday'; +import { DeleteOutlined, PlusOutlined, SmileOutlined } from '@ant-design/icons'; +import css from './organisations.module.css'; +import Upload, { RcFile, UploadFile } from 'antd/es/upload'; +import { convertFileListToApiFormat, getBase64 } from '../../utils/tools'; +import axios from 'axios'; +import ImgCrop from 'antd-img-crop'; + +dayjs.extend(weekday); + +export const OrganisationListSection: FunctionComponent = () => { + const getOrganisations = trpc.organisation.getOrganisations.useQuery({}); + const [api, contextHolder] = notification.useNotification(); + const deleteOrganisation = trpc.organisation.deleteOrganisation.useMutation({ + onSuccess: (() => { + api.open({ + message: 'Organisation has been deleted', + description: '', + duration: 10, + icon: + }); + }), + onError: ((error) => { + api.open({ + message: 'Error deleting organisation', + description: error.message, + duration: 10, + icon: + }); + }) + }); + + if (getOrganisations.isLoading) { + return ; + } + + if (getOrganisations.isError) { + return

+ An error occured, please contact your administrator +

; + } + + const columns = [{ + title: 'Name', + dataIndex: 'name', + key: 'name', + ellipsis: true, + render: (__unused__value, record) => { + return
{record.name}
; + } + }, { + title: 'Shortname', + dataIndex: 'shortname', + key: 'shortname', + ellipsis: true, + render: (__unused__value, record) => { + return
{record.shortname}
; + } + }, { + title: 'Logo', + dataIndex: 'logo', + key: 'logo', + render: (__unused__value, record) => { + return
; + } + }, { + render: (__unused__value, record) => { + return deleteOrganisation.mutate({ organisationId: record.id })} />; + } + }]; + + return (
+ {contextHolder} + +
+
+
+
List of Organisations
+
+
+
+ +
+
+ } + > +
+ +
+ +
); +}; + +export const OrganisationCreation: FunctionComponent = () => { + const [isModalOn, setIsModalOn] = useState(false); + const [form] = Form.useForm(); + const [fileList, setFileList] = useState([]); + + const handleCreateDomain = async (variables: Record) => { + try { + const files = await convertFileListToApiFormat(fileList, 'profile'); + const formData = new FormData(); + if (files.length > 0) { + files.forEach(file => { + formData.append('profile', file.stream, file.originalname); + }); + } + + Object.entries(variables).forEach(([key, value]) => { + variables[key] && formData.append(key, String(value)); + }); + + const response = await axios.post('/trpc/organisation.createOrganisation', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + + if (response?.data?.result?.data?.id) { + void message.success('Domain created successfully'); + } + + } catch { + void message.error('Error creating domain'); + } + }; + + const beforeUpload = (file: RcFile): boolean => { + const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png'; + if (!isJpgOrPng) { + void message.error('You can only upload JPG/PNG file!'); + } + setFileList([file]); + return isJpgOrPng; + }; + + const handlePreview = async (file: UploadFile) => { + if (!file.url && !file.preview) { + file.preview = await getBase64(file.originFileObj as RcFile); + } + }; + + return (
+ + void handleCreateDomain(form.getFieldsValue())} + onCancel={() => setIsModalOn(false)} + > +
+ + + + + + + + + void handlePreview} + fileList={fileList} + > + {fileList.length >= 1 ? null : ( +
+ +
Upload
+
+ )} +
+
+
+
+
+
); +}; diff --git a/packages/itmat-ui-react/src/components/organisation/organisations.module.css b/packages/itmat-ui-react/src/components/organisation/organisations.module.css new file mode 100644 index 000000000..b4e78057c --- /dev/null +++ b/packages/itmat-ui-react/src/components/organisation/organisations.module.css @@ -0,0 +1,91 @@ +.page_container { + grid-template-columns: 100% 0%; + grid-template-rows: 100%; + display: grid; + height: 100%; + grid-template-areas: "userList"; +} + +.col-item { + margin-bottom: 16px; +} + +.card-container { + position: relative; + display: flex; + max-width: 100%; + flex-direction: column; + height: 100%; + width: 100%; + box-sizing: border-box; + padding: 8px; +} + +.card-image { + object-fit: contain; + width: 100%; + height: 70%; +} + +.card-meta { + position: absolute; + bottom: 20px; + width: 100%; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.card-meta:hover { + cursor: pointer; +} + +.row-container { + width: 100%; + /* Add this line */ + box-sizing: border-box; + /* Add this line */ +} + +.col-item { + padding: 0 8px; + /* Half of gutter width */ + margin-bottom: 16px; + box-sizing: border-box; + /* Add this line */ +} + +.overview-header { + display: flex; + align-items: center; + /* This will vertically center align the icon and the title */ + /* If there is still a misalignment, you may adjust by adding 'align-self' to the children */ + font-size: 20px; + font-weight: 500; + overflow: auto; +} + +.overview-icon { + background-color: #000; + /* Or any color you prefer */ + width: 4px; + /* Thickness of the line */ + height: 24px; + /* Height of the line, adjust as needed */ + margin-right: 16px; + /* Space between the line and the text */ + /* Adjust 'margin-top' if the line is not aligned with the text */ +} + +.overview-title { + /* Style for your title */ + font-size: 24px; + font-weight: bold; +} + +.overview-content { + /* Style for your content */ + text-indent: 30px; + /* Adjust the indent as needed */ +} diff --git a/packages/itmat-ui-react/src/components/profile/index.tsx b/packages/itmat-ui-react/src/components/profile/index.tsx index 5b0c5dcf1..7c9e3d403 100644 --- a/packages/itmat-ui-react/src/components/profile/index.tsx +++ b/packages/itmat-ui-react/src/components/profile/index.tsx @@ -1,18 +1,15 @@ import { FunctionComponent } from 'react'; import { ProfileManagementSection } from './profile'; import css from './profile.module.css'; +import { MyKeys } from './keys'; +import { MyWebauthn } from './webauthn'; export const ProfilePage: FunctionComponent = () => { return (
-
-
- My account -
-
- -
-
+ + +
); }; diff --git a/packages/itmat-ui-react/src/components/profile/keys.tsx b/packages/itmat-ui-react/src/components/profile/keys.tsx new file mode 100644 index 000000000..db6567e0f --- /dev/null +++ b/packages/itmat-ui-react/src/components/profile/keys.tsx @@ -0,0 +1,223 @@ +import React, { FunctionComponent, useState } from 'react'; +import LoadSpinner from '../reusable/loadSpinner'; +// import { ProjectSection } from '../users/projectSection'; +import { Button, List, Table, message, Modal, Popconfirm } from 'antd'; +import { CopyOutlined } from '@ant-design/icons'; +import css from './profile.module.css'; +import { trpc } from '../../utils/trpc'; +import copy from 'copy-to-clipboard'; +import { Key } from '../../utils/dmpCrypto/dmp.key'; +import { useQueryClient } from '@tanstack/react-query'; +import { IPubkey } from '@itmat-broker/itmat-types'; + +export const MyKeys: FunctionComponent = () => { + const whoAmI = trpc.user.whoAmI.useQuery(); + const getUserKeys = trpc.user.getUserKeys.useQuery({ userId: whoAmI.data.id }); + const queryClient = useQueryClient(); + const deletePubkey = trpc.user.deletePubkey.useMutation({ + onSuccess: (data) => { + void message.success('Key deleted.'); + const queryKey = [['user', 'getUserKeys'], { input: { userId: whoAmI.data.id }, type: 'query' }]; + const cache: IPubkey[] = queryClient.getQueryData(queryKey) ?? []; + const newCache = cache.filter((el) => el.id !== data.id); + queryClient.setQueryData(queryKey, newCache); + }, + onError: () => { + void message.error('Failed to delete this key.'); + } + }); + if (whoAmI.isLoading || getUserKeys.isLoading) { + return <> +
Loading...
+
+ +
+ ; + } + if (whoAmI.isError || getUserKeys.isError) { + return <> + An error occured. + ; + } + + const columns = [{ + title: 'Pubkey', + dataIndex: 'pubkey', + key: 'value', + render: (__unused__value, record) => { + const pubkey = record.pubkey ?? ''; + // Remove headers and footers from the public key + const keyBody = pubkey.replace('-----BEGIN PUBLIC KEY-----', '') + .replace('-----END PUBLIC KEY-----', '') + .trim(); // Remove extra whitespace + + // Extract the first few characters of the key body + const displayKey = keyBody.substring(0, 26) + '...'; // Adjust number of characters as needed + return ( +
+ {displayKey} +
+ ); + } + }, { + title: 'Created At', + dataIndex: 'createdAt', + key: 'value', + render: (__unused__value, record) => { + return (new Date(record.life.createdTime)).toDateString(); + } + }, { + title: '', + dataIndex: 'delete', + key: 'delete', + render: (_, record) => { + return deletePubkey.mutate({ + associatedUserId: whoAmI.data.id, + keyId: record.id + })} + > + + ; + } + }]; + return (
+ +
+
+
+
My Keys
+
+
+
+ +
+
+ } + > + + + + + + ); +}; + +const KeyGeneration: React.FunctionComponent<{ userId: string }> = ({ userId }) => { + const [isKeyGenOpen, setIsKeyGenOpen] = useState(false); + const [completedKeypairGen, setcompletedKeypairGen] = useState(false); + const [exportedKeyPair, setExportedKeyPair] = useState({ privateKey: '', publicKey: '' }); + const queryClient = useQueryClient(); + const registerPubkey = trpc.user.registerPubkey.useMutation({ + onSuccess: (data) => { + void message.success('Key registered.'); + const queryKey = [['user', 'getUserKeys'], { input: { userId: userId }, type: 'query' }]; + const cache: IPubkey[] = queryClient.getQueryData(queryKey) ?? []; + const newCache = [...cache, data]; + queryClient.setQueryData(queryKey, newCache); + }, + onError: () => { + void message.error('Failed to register this key.'); + } + }); + + const [downloadLink, setDownloadLink] = useState(''); + // function for generating file and set download link + const makeTextFile = (filecontent: string) => { + const data = new Blob([filecontent], { type: 'text/plain' }); + // Avoid memory leaks + if (downloadLink !== '') window.URL.revokeObjectURL(downloadLink); + // Update the download link state + setDownloadLink(window.URL.createObjectURL(data)); + }; + + const hashedPrivateKey = async (privateKey) => { + const encoder = new TextEncoder(); + const privateKeyBuffer = encoder.encode(privateKey); + + // Compute the SHA-256 hash + const hashBuffer = await crypto.subtle.digest('SHA-256', privateKeyBuffer); + + // Convert the hash to a hex string + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join(''); + return hashHex; + }; + + return ( +
+ + { + void (async () => { + await registerPubkey.mutate({ + pubkey: exportedKeyPair.publicKey, + hashedPrivateKey: await hashedPrivateKey(exportedKeyPair.privateKey), + associatedUserId: userId + }); + })(); + setIsKeyGenOpen(false); + }} + okText={completedKeypairGen ? 'Register this key pair' : 'Ok'} + onCancel={() => { + setIsKeyGenOpen(false); + }} + > +

+ { + completedKeypairGen ? + : null + } +
+
+ ); +}; + +export const cryptoInBrowser = { + keyGen: async function () { + return Key.createRSAKey(); + }, + signGen: async function (message: string, signKey: CryptoKey) { + return Key.signwtRSAKey(message, signKey); + } +}; diff --git a/packages/itmat-ui-react/src/components/profile/profile.module.css b/packages/itmat-ui-react/src/components/profile/profile.module.css index b1e5ae753..e855f5563 100644 --- a/packages/itmat-ui-react/src/components/profile/profile.module.css +++ b/packages/itmat-ui-react/src/components/profile/profile.module.css @@ -1,62 +1,160 @@ .page_container { - grid-template-columns: 100% 0%; - grid-template-rows: 100%; - display: grid; height: 100%; - grid-template-areas: "userList" + position: relative; + overflow-y: scroll; + display: grid; + justify-content: space-between; + grid-template-areas: + "LEFT RIGHT" + "FILE FILE" + "SHARED SHARED" + "GROUP GROUP" + "KEY KEY"; + grid-template-columns: 40% 60%; + grid-template-rows: 600px 1fr; + padding: 2rem; +} + +.profile_left { + grid-area: LEFT; + padding: 20px; + background-color: white; +} + +.profile_summary_wrapper { + text-align: center; + border: 3px solid white; +} + +.profile_summary_profile { + text-align: center; } -.user_list_section { - grid-area: userList; +.profile_summary_statistics { + text-align: center; } -.user_detail { - grid-area: userDetail; - background: var(--color-additional-panel-background); +.profile_summary_statistics_value { + font-weight: bold; + font-size: 32px; } -.user_list > table:first-of-type thead:first-of-type tr:first-of-type { - background: var(--color-background); +.profile_summary_statistics_tag { + font-size: 16px; + font-family: monospace; } -.create_new_user button { - display: inline-block; - width: 40%; +.profile_summary_description { + font-size: 16px; + font-family: "Brush Script MT"; } -.button_clicked button { - background-color: red; +.profile_right { + grid-area: RIGHT; + padding: 20px; + background-color: white; } -.submit_cancel_button_wrapper { - display: flex; - width: 100%; +.profile_edit_submit { + float: right; } -.submit_cancel_button_wrapper a { - width: 50%; - margin-right: 0.5rem; +.profile_edit_normal_title { + font-size: 15px; } -.submit_cancel_button_wrapper button { - width: 100%; - margin: 0px; +.profile_edit_special_title { + font-size: 25px; + font-weight: bold; } -.really_delete_button { - background-color: red; - cursor: pointer; - margin-right: 1rem; +.file_wrapper { + grid-area: FILE; + padding: 20px; + background-color: white; } -.request_sent_button { - cursor: unset; - background: var(--color-status-finish); - color: var(--color-font-color); +.custom-text-hover { + cursor: pointer; + transition: all 0.3s ease; } -.request_sent_button:hover { - cursor: unset; - background: var(--color-status-finish); - color: var(--color-font-color); -} \ No newline at end of file +.custom-text-hover:hover { + font-weight: bold; + text-decoration: underline; +} + +.shared_container { + display: flex; + /* Enable flexbox */ + flex-direction: row; + /* Align children in a row */ + width: 100%; + /* Full width of the container */ + grid-area: SHARED; +} + +.shared_container > div:first-child { + width: 20%; + /* Width of the first div */ +} + +.shared_container > div:last-child { + width: 80%; + /* Width of the second div */ +} + +.ellipsis-style { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + max-width: 150px; + /* Adjust this width based on your requirement */ + display: block; +} + +.group_wrapper { + grid-area: GROUP; + padding: 20px; + background-color: white; +} + +.overview-header { + display: flex; + align-items: center; + /* This will vertically center align the icon and the title */ + /* If there is still a misalignment, you may adjust by adding 'align-self' to the children */ + font-size: 20px; + font-weight: 500; + overflow: auto; +} + +.overview-icon { + background-color: #000; + /* Or any color you prefer */ + width: 4px; + /* Thickness of the line */ + height: 24px; + /* Height of the line, adjust as needed */ + margin-right: 16px; + /* Space between the line and the text */ + /* Adjust 'margin-top' if the line is not aligned with the text */ +} + +.overview-title { + /* Style for your title */ + font-size: 24px; + font-weight: bold; +} + +.overview-content { + /* Style for your content */ + text-indent: 30px; + /* Adjust the indent as needed */ +} + +.key_wrapper { + grid-area: KEY; + padding: 20px; + background-color: white; +} diff --git a/packages/itmat-ui-react/src/components/profile/profile.tsx b/packages/itmat-ui-react/src/components/profile/profile.tsx index f79efc5dc..468a17f65 100644 --- a/packages/itmat-ui-react/src/components/profile/profile.tsx +++ b/packages/itmat-ui-react/src/components/profile/profile.tsx @@ -1,22 +1,30 @@ -import { ChangeEvent, FunctionComponent, useState } from 'react'; -import { Mutation } from '@apollo/client/react/components'; -import { useQuery, useMutation } from '@apollo/client/react/hooks'; -import { IUserWithoutToken, IPubkey, IOrganisation } from '@itmat-broker/itmat-types'; -import { WHO_AM_I, EDIT_USER, REQUEST_USERNAME_OR_RESET_PASSWORD, REGISTER_PUBKEY, GET_PUBKEYS, ISSUE_ACCESS_TOKEN, GET_ORGANISATIONS, REQUEST_EXPIRY_DATE } from '@itmat-broker/itmat-models'; -import { Subsection } from '../reusable'; +import { ChangeEvent, FunctionComponent, useEffect, useState } from 'react'; +import { IOrganisation, IUserWithoutToken, enumUserTypes } from '@itmat-broker/itmat-types'; import LoadSpinner from '../reusable/loadSpinner'; -import { ProjectSection } from '../users/projectSection'; -import { Form, Input, Select, DatePicker, Button, Alert, Checkbox } from 'antd'; -import dayjs from 'dayjs'; -import { WarningOutlined, PauseCircleOutlined } from '@ant-design/icons'; +import { Form, Input, Button, Image as AntdImage, Typography, Row, Col, Divider, message, Select } from 'antd'; import { Key } from '../../utils/dmpCrypto/dmp.key'; +import css from './profile.module.css'; +import { trpc } from '../../utils/trpc'; +import { createUserIcon } from '../../utils/image'; +const { Title } = Typography; +const { TextArea } = Input; -export const ProfileManagementSection: FunctionComponent = () => { - const { loading: whoamiloading, error: whoamierror, data: whoamidata } = useQuery(WHO_AM_I); - const [requestExpiryDate] = useMutation(REQUEST_EXPIRY_DATE, { onCompleted: () => { setRequestExpiryDateSent(true); } }); - const [requestExpiryDateSent, setRequestExpiryDateSent] = useState(false); - if (whoamiloading) { +export const ProfileManagementSection: FunctionComponent = () => { + const whoAmI = trpc.user.whoAmI.useQuery(); + const getOrganisations = trpc.organisation.getOrganisations.useQuery({}); + const getUserRoles = trpc.role.getUserRoles.useQuery({ userId: whoAmI?.data.id }); + const getStudies = trpc.study.getStudies.useQuery({}); + const getUserKeys = trpc.user.getUserKeys.useQuery({ userId: whoAmI?.data.id }); + const requestExpiryDate = trpc.user.requestExpiryDate.useMutation({ + onSuccess: () => { + void message.success('Request sent'); + }, + onError: () => { + void message.error('Error sending request'); + } + }); + if (whoAmI.isLoading || getOrganisations.isLoading || getUserRoles.isLoading || getStudies.isLoading || getUserKeys.isLoading) { return <>
Loading...
@@ -24,231 +32,165 @@ export const ProfileManagementSection: FunctionComponent = () => {
; } - if (whoamierror) { + if (whoAmI.isError || getOrganisations.isError || getUserRoles.isError || getStudies.isError || getUserKeys.isError) { return <> -
Loading...
-
- -
+ An error occured. ; } - - const user: IUserWithoutToken = whoamidata.whoAmI; - return ( - <> -
{whoamidata.whoAmI.username}
-
- { - dayjs().add(2, 'week').valueOf() - dayjs(user.expiredAt).valueOf() > 0 - ? dayjs().valueOf() - dayjs(user.expiredAt).valueOf() > 0 - ? <> - Your account has expired. -
-
-
- : <> - Your account is close to expiring! -
-
- -
-
- : null - } - {requestExpiryDateSent ? ( - <> - -
- - ) : null} - - - -
-
- - -
-
- - -
-
- - -
-
-
- - -
-
-
- - -
+ return (<> +
+
+
+ { + whoAmI.data ? + : + } +
+
+ +
{getStudies.data.length ?? 'NA'} + + {(() => { + const remaining: number = whoAmI.data.expiredAt - Date.now(); + if (whoAmI.data.type === enumUserTypes.ADMIN) { + return 'Infinite'; + } + return Math.floor(Math.max(0, remaining) / 1000 / 60 / 60 / 24) + ' days'; + })()} + + {getUserKeys.data.length ?? 'NA'} +
- - + + Datasets + + Expired In + + Public keys + + +
+
+ {getOrganisations.data.filter(el => el.id === whoAmI.data.organisation)?.[0]?.name ?? 'NA'} +
+
+
+ {whoAmI.data.description} +

+
+ +
- - ); + + +
+ +
+ ); }; -export const EditUserForm: FunctionComponent<{ user: (IUserWithoutToken & { access?: { id: string, projects: { id: string, name: string, studyId: string }[], studies: { id: string, name: string }[] } }) }> = ({ user }) => { - const [savedSuccessfully, setSavedSuccessfully] = useState(false); - const [requestResetPassword] = useMutation(REQUEST_USERNAME_OR_RESET_PASSWORD, { onCompleted: () => { setRequestResetPasswordSent(true); } }); - const [requestResetPasswordSent, setRequestResetPasswordSent] = useState(false); - const { loading: getorgsloading, error: getorgserror, data: getorgsdata } = useQuery(GET_ORGANISATIONS); - - function formatSubmitObj(variables) { - const final = { - id: user.id, - emailNotificationsActivated: variables.emailNotificationsActivated - }; - return final; +export const ProfileEditForm: FunctionComponent<{ user: IUserWithoutToken, organisations: IOrganisation[] }> = ({ user, organisations }) => { + const editUser = trpc.user.editUser.useMutation({ + onSuccess: () => { + void message.success('Profile updated successfully'); + }, + onError: () => { + void message.error('Error updating profile'); + } + }); + const [form] = Form.useForm(); + useEffect(() => { + if (user) { + form.resetFields(); // Reset the form fields when user data changes + } + }, [user, form]); + if (!user) { + return null; } - const disabledDate = (current) => { - return current && (current < dayjs().endOf('day') || current > dayjs().add(3, 'month')); + const handleSubmit = async (variables: Record, user: { id: string }) => { + await editUser.mutateAsync({ userId: user.id, ...variables }); }; - if (getorgsloading) { return

Loading..

; } - if (getorgserror) { return

ERROR: please try again.

; } - const orgList: IOrganisation[] = getorgsdata.getOrganisations; - - return ( - - mutation={EDIT_USER} - onCompleted={() => setSavedSuccessfully(true)} - > - {(submit, { loading, error }) => -
org.id === user.organisation)?.name - }} layout='vertical' onFinish={(variables) => submit({ variables: formatSubmitObj(variables) })}> - - - - - - - - - - - - - - - - - - - - - - - - - - Email Notification - - {error ? ( - <> - error.message).join()} /> -
- - ) : null} - {savedSuccessfully ? ( - <> - -
- - ) : null} - {requestResetPasswordSent ? ( - <> - -
- - ) : null} - - - - - - } - - - ); -}; - -/* show date and time separately */ -export const showTimeFunc = { - showDate: function (timestamps: number) { - return new Date(timestamps).toISOString().substring(0, 10); - }, - showTime: function (timestamps: number) { - return new Date(timestamps).toISOString().substring(11, 19); - } -}; + const onFinish = (variables) => { + void handleSubmit(variables, user); + }; -/* More time control due to different behaviors in chrome and firefox, also correct errors of summer/winter time offset */ -export const changeTimeFunc = { - changeDate: function (inputs: any, value: any) { - /* When in summer time, there is non-zero timezoneoffset which should be considered */ - const offsetTime = new Date(inputs.expiredAt - new Date(inputs.expiredAt).getTimezoneOffset() * 60 * 1000); - let newDate; - const recordTime = offsetTime.toISOString().substring(11, 19); - /* If the input date is invalid, the shown date will keep the original one */ - if (isNaN(new Date(value + 'T' + recordTime).valueOf()) || (new Date(value + 'T' + recordTime).valueOf() < 0)) { - newDate = new Date(inputs.expiredAt); - } else { - newDate = new Date(value + 'T' + recordTime); - } - return { ...inputs, expiredAt: newDate.valueOf() }; - }, - changeTime: function (inputs: any, value: any) { - const recordedDate = new Date(inputs.expiredAt).toISOString().substring(0, 10); - /* When in summer time, there is non-zero timezoneoffset which should be considered */ - return { ...inputs, expiredAt: new Date(recordedDate + 'T' + value).valueOf() - new Date(inputs.expiredAt).getTimezoneOffset() * 60 * 1000 }; - } + return (
void onFinish(variables)} + > + +
BASIC INFO
+
+ + +
+
FIRST NAME

+ + + + + +
LAST NAME

+ + + + + + + +
USER NAME

+ + + + + +
PASSWORD

+ + + + + + + +
Email

+ + + + + +
Organisation

+ + + + + + + +
Description
+ + +
- @@ -542,81 +279,3 @@ export const RsaSigner: FunctionComponent = () => { }; -export const TokenManagement: FunctionComponent<{ userId: string }> = ({ userId }) => { - const [completedTokenGen, setcompletedTokenGen] = useState(false); - //const [accessTokenGen, setaccessTokenGen] = useState(''); - const [tokenGen, { data: tokendata, loading, error }] = useMutation(ISSUE_ACCESS_TOKEN, { - onCompleted: () => { - setcompletedTokenGen(true); - } - }); - - const { loading: getPubkeysloading, error: getPubkeyserror, data: getPubkeysdata } = useQuery(GET_PUBKEYS, { - variables: { - associatedUserId: userId - } - }); - if (getPubkeysloading) { - return <> -
Loading...
-
- -
- ; - } - if (getPubkeyserror) { - return <> -
Loading...
-
- -
- ; - } - - const ipubkey: IPubkey = getPubkeysdata?.getPubkeys?.[0]; - if ((ipubkey === null) || (ipubkey === undefined)) { - return

You need to register a public-key for generating access token.

; - } - - if (completedTokenGen) { - return ( -
-

The access token is successfully generated!

-
-

Securely keep this token as an authentication key when interacting with APIs

-