diff --git a/api/src/engine/connectors/exareme/converters.ts b/api/src/engine/connectors/exareme/converters.ts index 72e62044aa2241b824b46d376ff8c14c31b35779..5a779207338a6d115adcad5142ff1e8a81967953 100644 --- a/api/src/engine/connectors/exareme/converters.ts +++ b/api/src/engine/connectors/exareme/converters.ts @@ -4,6 +4,12 @@ import { Variable } from 'src/engine/models/variable.model'; import { Hierarchy } from './interfaces/hierarchy.interface'; import { VariableEntity } from './interfaces/variable-entity.interface'; import { Entity } from './interfaces/entity.interface'; +import { TransientCreateInput } from 'src/engine/models/transient/transient-create.input'; +import { TransientDataResult } from './interfaces/transient/transient-data-result.interface'; +import { Transient } from 'src/engine/models/transient/transient.model'; +import { MetaData } from 'src/engine/models/result/common/metadata.model'; +import { TableResult } from 'src/engine/models/result/table-result.model'; +import { Dictionary } from 'src/common/interfaces/utilities.interface'; export const dataToGroup = (data: Hierarchy): Group => { return { @@ -33,3 +39,88 @@ export const dataToVariable = (data: VariableEntity): Variable => { groups: [], }; }; + +export const transientInputToData = (data: TransientCreateInput) => { + return { + algorithm: { + parameters: [ + { + name: 'dataset', + value: data.datasets.join(','), + }, + { + name: 'y', + value: data.variables.join(','), + }, + { + name: 'filter', + value: data.filter, + }, + { + name: 'pathology', + value: data.domain, + }, + ], + type: 'string', + name: 'DESCRIPTIVE_STATS', + }, + name: 'Descriptive statistics', + }; +}; + +const dictToTable = (dict: Dictionary<string[]>, rows: number): string[][] => { + const keys = Object.keys(dict); + + return keys.map((key) => { + const row = Array.from(Array(rows).keys()) + .map((i) => dict[key][i]) + .map((val) => val ?? ''); + row.unshift(key); + return row; + }); +}; + +export const dataToTransient = (data: TransientDataResult): Transient => { + const result = data.result[0]; + const tables = Object.keys(result.data.single).map((varKey): TableResult => { + const variable = result.data.single[varKey]; + const domains: MetaData[] = []; + const rows: Dictionary<string[]> = {}; + + let count = 0; + + Object.keys(variable).map((domainKey) => { + domains.push({ name: domainKey, type: 'string' }); + const data = variable[domainKey]; + + [ + [varKey, 'num_total'], + ['datapoints', 'num_datapoints'], + ['nulls', 'num_nulls'], + ].forEach((keys) => { + if (!rows[keys[0]]) rows[keys[0]] = []; + rows[keys[0]][count] = data[keys[1]]; + }); + + const properties = variable[domainKey].data; + + Object.keys(properties).forEach((propKey) => { + if (!rows[propKey]) rows[propKey] = []; + rows[propKey][count] = properties[propKey].toString(); + }); + + count++; + }); + + return { + data: dictToTable(rows, count), + metadatas: domains, + name: varKey, + }; + }); + + return { + title: data.name, + result: tables, + }; +}; diff --git a/api/src/engine/connectors/exareme/interfaces/transient/transient-data-result.interface.ts b/api/src/engine/connectors/exareme/interfaces/transient/transient-data-result.interface.ts new file mode 100644 index 0000000000000000000000000000000000000000..37a62f96b071c594696f01bd3b27d75d2bc25f67 --- /dev/null +++ b/api/src/engine/connectors/exareme/interfaces/transient/transient-data-result.interface.ts @@ -0,0 +1,42 @@ +export interface NumericalData { + [key: string]: number; +} + +export interface CategoricalData { + [key: string]: { + count: number; + percentage: number; + }; +} + +export interface TransientDataResult { + name: string; + result: [ + { + data: { + single: { + [variable: string]: { + [dataset: string]: { + data: NumericalData | CategoricalData; + num_datapoints: number; + num_total: number; + num_nulls: number; + }; + }; + }; + model: { + [dataset: string]: { + data: { + [variable: string]: { + [key: string]: number; + }; + }; + num_datapoints: number; + num_total: number; + num_nulls: number; + }; + }; + }; + }, + ]; +} diff --git a/api/src/engine/connectors/exareme/main.connector.ts b/api/src/engine/connectors/exareme/main.connector.ts index 30a7a6e283885e97c3dd7ef4321dd61a04d21c3c..a4ecf13e8be50a1e06aafa1a83cd714f916d3254 100644 --- a/api/src/engine/connectors/exareme/main.connector.ts +++ b/api/src/engine/connectors/exareme/main.connector.ts @@ -8,9 +8,16 @@ import { Group } from 'src/engine/models/group.model'; import { TransientCreateInput } from 'src/engine/models/transient/transient-create.input'; import { Transient } from 'src/engine/models/transient/transient.model'; import { Variable } from 'src/engine/models/variable.model'; -import { dataToCategory, dataToGroup, dataToVariable } from './converters'; +import { + dataToCategory, + dataToGroup, + dataToTransient, + dataToVariable, + transientInputToData, +} from './converters'; import { Hierarchy } from './interfaces/hierarchy.interface'; import { Pathology } from './interfaces/pathology.interface'; +import { TransientDataResult } from './interfaces/transient/transient-data-result.interface'; export default class ExaremeService implements IEngineService { constructor( @@ -18,11 +25,157 @@ export default class ExaremeService implements IEngineService { private readonly httpService: HttpService, ) {} - createTransient(data: TransientCreateInput): Transient | Promise<Transient> { - return { - id: 'test', - label: 'test', - }; + async createTransient(data: TransientCreateInput): Promise<Transient> { + const form = transientInputToData(data); + + const path = this.options.baseurl + 'experiments/transient'; + + const result = { + name: 'Descriptive statistics', + result: [ + { + data: { + single: { + 'Left inferior temporal gyrus': { + ppmi: { + data: { + std: 1.2048783713787277, + max: 15.0815, + min: 7.6335, + mean: 11.38076218487395, + }, + num_datapoints: 714, + num_total: 714, + num_nulls: 0, + }, + edsd: { + data: { + std: 1.3274694970555183, + max: 14.593, + min: 5.4301, + mean: 10.647539816933637, + }, + num_datapoints: 437, + num_total: 474, + num_nulls: 37, + }, + 'desd-synthdata': { + data: { + std: 1.3479276642860987, + max: 14.593, + min: 5.4301, + mean: 10.685619565217392, + }, + num_datapoints: 920, + num_total: 1000, + num_nulls: 80, + }, + }, + 'Left posterior insula': { + ppmi: { + data: { + std: 0.25046887396228024, + max: 3.0882, + min: 1.7073, + mean: 2.358402521008403, + }, + num_datapoints: 714, + num_total: 714, + num_nulls: 0, + }, + edsd: { + data: { + std: 0.2716090949138581, + max: 3.1971, + min: 1.2675, + mean: 2.2726512585812357, + }, + num_datapoints: 437, + num_total: 474, + num_nulls: 37, + }, + 'desd-synthdata': { + data: { + std: 0.2619310561946756, + max: 3.1971, + min: 1.2675, + mean: 2.27014597826087, + }, + num_datapoints: 920, + num_total: 1000, + num_nulls: 80, + }, + }, + }, + model: { + ppmi: { + num_datapoints: 714, + data: { + 'Left inferior temporal gyrus': { + std: 1.2048783713787277, + max: 15.0815, + min: 7.6335, + mean: 11.38076218487395, + }, + 'Left posterior insula': { + std: 0.25046887396228024, + max: 3.0882, + min: 1.7073, + mean: 2.358402521008403, + }, + }, + num_total: 714, + num_nulls: 0, + }, + edsd: { + num_datapoints: 437, + data: { + 'Left inferior temporal gyrus': { + std: 1.3274694970555183, + max: 14.593, + min: 5.4301, + mean: 10.647539816933637, + }, + 'Left posterior insula': { + std: 0.2716090949138581, + max: 3.1971, + min: 1.2675, + mean: 2.2726512585812357, + }, + }, + num_total: 474, + num_nulls: 37, + }, + 'desd-synthdata': { + num_datapoints: 920, + data: { + 'Left inferior temporal gyrus': { + std: 1.3479276642860987, + max: 14.593, + min: 5.4301, + mean: 10.685619565217392, + }, + 'Left posterior insula': { + std: 0.2619310561946756, + max: 3.1971, + min: 1.2675, + mean: 2.27014597826087, + }, + }, + num_total: 1000, + num_nulls: 80, + }, + }, + }, + }, + ], + } as TransientDataResult; + + /*= await firstValueFrom( + this.httpService.post<TransientDataResult>(path, form), + );*/ + + return dataToTransient(result); } async getDomains(ids: string[]): Promise<Domain[]> { @@ -57,10 +210,6 @@ export default class ExaremeService implements IEngineService { } } - demo(): string { - return 'exareme'; - } - getActiveUser(): Observable<string> { const path = this.options.baseurl + 'activeUser'; diff --git a/api/src/engine/engine.controller.ts b/api/src/engine/engine.controller.ts index a495a2c317efa7911dc3f2e43915c88f9fb13830..c54f0ce0fa44732ed4fc614caf5ff0509d6e0068 100644 --- a/api/src/engine/engine.controller.ts +++ b/api/src/engine/engine.controller.ts @@ -19,11 +19,6 @@ export class EngineController { @Inject(ENGINE_SERVICE) private readonly engineService: IEngineService, ) {} - @Get('/test') - getTest(): string { - return this.engineService.demo(); - } - @Get('/algorithms') getAlgorithms(@Req() request: Request): Observable<string> { return this.engineService.getAlgorithms(request); diff --git a/api/src/engine/engine.interfaces.ts b/api/src/engine/engine.interfaces.ts index ec9bcd7d2ba113d292f0b68ff3bce599f4007f6c..323e65e85368da3785ba7ab8daad0a067560ca9e 100644 --- a/api/src/engine/engine.interfaces.ts +++ b/api/src/engine/engine.interfaces.ts @@ -10,8 +10,6 @@ export interface IEngineOptions { } export interface IEngineService { - demo(): string; - getDomains(ids: string[]): Domain[] | Promise<Domain[]>; getAlgorithms(request: Request): Observable<string>; diff --git a/api/src/engine/models/entity.model.ts b/api/src/engine/models/entity.model.ts index 70fc95022847f7e5f52916b3fefb6cc8b5a700a1..32183fd7e970410a1ee3f479cef4d691d56fb7a7 100644 --- a/api/src/engine/models/entity.model.ts +++ b/api/src/engine/models/entity.model.ts @@ -1,10 +1,11 @@ -import { Field, ObjectType } from '@nestjs/graphql'; +import { Field, InputType, ObjectType } from '@nestjs/graphql'; +@InputType() @ObjectType() export class Entity { @Field() id: string; @Field({ nullable: true }) - label: string; + label?: string; } diff --git a/api/src/engine/models/result/common/metadata.model.ts b/api/src/engine/models/result/common/metadata.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..0c35ddd5ea8464f733f22b62ade44a1eacc3abe5 --- /dev/null +++ b/api/src/engine/models/result/common/metadata.model.ts @@ -0,0 +1,10 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +@ObjectType() +export class MetaData { + @Field() + name: string; + + @Field() + type: string; +} diff --git a/api/src/engine/models/result/table-result.model.ts b/api/src/engine/models/result/table-result.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..3ad5cc749406ca469fdda9b68e564d29a7656e0a --- /dev/null +++ b/api/src/engine/models/result/table-result.model.ts @@ -0,0 +1,14 @@ +import { Field, ObjectType } from '@nestjs/graphql'; +import { MetaData } from './common/metadata.model'; + +@ObjectType() +export class TableResult { + @Field() + name: string; + + @Field(() => [[String]]) + data: string[][]; + + @Field(() => [MetaData]) + metadatas: MetaData[]; +} diff --git a/api/src/engine/models/transient/transient-create.input.ts b/api/src/engine/models/transient/transient-create.input.ts index d4b012c25106bc9633c7043d3c05932a19eb5a67..0a2827e2bcb55928db0f1df2af9a69b7e341189a 100644 --- a/api/src/engine/models/transient/transient-create.input.ts +++ b/api/src/engine/models/transient/transient-create.input.ts @@ -1,7 +1,17 @@ import { Field, InputType } from '@nestjs/graphql'; +import { Extrafield } from '../utility/extrafield.input'; @InputType() -export class TransientCreateInput { +export class TransientCreateInput extends Extrafield { + @Field(() => [String]) + datasets: string[]; + + @Field(() => [String]) + variables: string[]; + + @Field(() => String, { nullable: true }) + filter: string; + @Field() - name: string; + domain: string; } diff --git a/api/src/engine/models/transient/transient.model.ts b/api/src/engine/models/transient/transient.model.ts index 4831c0fff16c45f4247038ddfa60c61a3e0ee105..54d8e10ec9a1d6f4c98d9bb027afdc4a81bb1be0 100644 --- a/api/src/engine/models/transient/transient.model.ts +++ b/api/src/engine/models/transient/transient.model.ts @@ -1,5 +1,11 @@ -import { ObjectType } from '@nestjs/graphql'; -import { Entity } from '../entity.model'; +import { Field, ObjectType } from '@nestjs/graphql'; +import { TableResult } from '../result/table-result.model'; @ObjectType() -export class Transient extends Entity {} +export class Transient { + @Field() + title: string; + + @Field(() => [TableResult]) + result: TableResult[]; +} diff --git a/api/src/engine/models/utility/extrafield.input.ts b/api/src/engine/models/utility/extrafield.input.ts new file mode 100644 index 0000000000000000000000000000000000000000..22f917222d400c70427ea7ec89abda45f2471155 --- /dev/null +++ b/api/src/engine/models/utility/extrafield.input.ts @@ -0,0 +1,12 @@ +import { Field, InputType } from '@nestjs/graphql'; + +/** + * This class is used to add a common extra field to + * an input class in order to provide specific + * information for a connector + */ +@InputType() +export class Extrafield { + @Field({ nullable: true }) + extradata: string; +} diff --git a/api/src/schema.gql b/api/src/schema.gql index 5657a0dff1bb60212db3b62b0c2ff9bd60ac3ee0..e0d4db8774b413f182643f998a0b3142e43b4712 100644 --- a/api/src/schema.gql +++ b/api/src/schema.gql @@ -34,9 +34,20 @@ type Domain { rootGroup: Group! } +type MetaData { + name: String! + type: String! +} + +type TableResult { + name: String! + data: [[String!]!]! + metadatas: [MetaData!]! +} + type Transient { - id: String! - label: String + title: String! + result: [TableResult!]! } type Query { @@ -48,5 +59,9 @@ type Mutation { } input TransientCreateInput { - name: String! + extradata: String + datasets: [String!]! + variables: [String!]! + filter: String + domain: String! }