diff --git a/api/src/engine/connectors/datashield/datashield.connector.ts b/api/src/engine/connectors/datashield/datashield.connector.ts index d09e8f8dfbfa9995ad94384b79b9248bf17f0263..05c7acdca7f1f8067c901ccafa8aa2e1d03c318e 100644 --- a/api/src/engine/connectors/datashield/datashield.connector.ts +++ b/api/src/engine/connectors/datashield/datashield.connector.ts @@ -319,21 +319,27 @@ export default class DataShieldConnector implements Connector { const coVariable = experiment.variables.length > 0 ? experiment.variables[0] : undefined; + const expToInput = { + coVariable, + variables: experiment.coVariables, + algorithm: { + id: experiment.algorithm.name, + }, + datasets: experiment.datasets, + }; + + experiment.algorithm.parameters.forEach((param) => { + if (!expToInput.algorithm[param.name]) { + // FIXME: the parameter should be added in a specific key entry (e.g. expToInput.algorithm.parameters') + // Should be fixed inside the Datashield API + expToInput.algorithm[param.name] = param.value; + } + }); + const result = await firstValueFrom( - this.httpService.post( - path.href, - { - coVariable, - variables: experiment.coVariables, - algorithm: { - id: experiment.algorithm.name, - }, - datasets: experiment.datasets, - }, - { - headers: { cookie, 'Content-Type': 'application/json' }, - }, - ), + this.httpService.post(path.href, expToInput, { + headers: { cookie, 'Content-Type': 'application/json' }, + }), ); handlers(experiment, result.data, vars); diff --git a/api/src/engine/connectors/datashield/handlers/algorithms/linear-regression.handler.ts b/api/src/engine/connectors/datashield/handlers/algorithms/linear-regression.handler.ts index 623ca2190b5db5cf08c78e98f51ba76dcac0761e..96d2a3a0970e576c4ed6d47229afd645867697b4 100644 --- a/api/src/engine/connectors/datashield/handlers/algorithms/linear-regression.handler.ts +++ b/api/src/engine/connectors/datashield/handlers/algorithms/linear-regression.handler.ts @@ -23,6 +23,8 @@ const properties = [ 'high0.95CI', ]; +const summaryProps = ['iter', 'Nvalid', 'Ntotal', 'df']; + export default class LinearRegressionHandler extends BaseHandler { canHandle(algorithm: string, data: any): boolean { return ( @@ -30,6 +32,19 @@ export default class LinearRegressionHandler extends BaseHandler { ); } + getSummaryTable(data: any): TableResult { + const summaryTable: TableResult = { + name: 'Summary', + headers: [ + { name: 'Name', type: 'string' }, + { name: 'Value', type: 'string' }, + ], + data: summaryProps.map((prop) => [lookupDict[prop], data[prop]]), + }; + + return summaryTable; + } + private getTableResult(data: any, vars: Variable[]): TableResult { return { name: 'Results', @@ -50,9 +65,11 @@ export default class LinearRegressionHandler extends BaseHandler { return this.next?.handle(experiment, data, vars); const tableResult = this.getTableResult(data, vars); + const summaryResult = this.getSummaryTable(data); + + if (summaryResult) experiment.results.push(summaryResult); + if (tableResult) experiment.results.push(tableResult); - if (tableResult) { - experiment.results.push(tableResult); - } + this.next?.handle(experiment, data, vars); } } diff --git a/api/src/engine/connectors/datashield/handlers/algorithms/logistic-regression.handler.spec.ts b/api/src/engine/connectors/datashield/handlers/algorithms/logistic-regression.handler.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..b6e9dfb0273359a141a01662711b353f360ad1bc --- /dev/null +++ b/api/src/engine/connectors/datashield/handlers/algorithms/logistic-regression.handler.spec.ts @@ -0,0 +1,90 @@ +import { Experiment } from '../../../../models/experiment/experiment.model'; +import LogisticRegressionHandler from './logistic-regression.handler'; + +const data = { + Nvalid: 214, + Nmissing: 0, + Ntotal: 214, + 'disclosure.risk': [[0], [0]], + errorMessage: [['No errors'], ['No errors']], + nsubs: 214, + iter: 6, + formula: + 'race ~ Urea.nitrogen..Mass.volume..in.Serum.or.Plasma + Albumin..Mass.volume..in.Serum.or.Plasma', + coefficients: [ + { + Estimate: -4.53, + 'Std. Error': 3.4497, + 'z-value': -1.3132, + 'p-value': 0.1891, + 'low0.95CI.LP': -11.2914, + 'high0.95CI.LP': 2.2313, + P_OR: 0.0107, + 'low0.95CI.P_OR': 0, + 'high0.95CI.P_OR': 0.903, + _row: '(Intercept)', + }, + { + Estimate: 0.0598, + 'Std. Error': 0.0663, + 'z-value': 0.9023, + 'p-value': 0.3669, + 'low0.95CI.LP': -0.0701, + 'high0.95CI.LP': 0.1898, + P_OR: 1.0617, + 'low0.95CI.P_OR': 0.9323, + 'high0.95CI.P_OR': 1.209, + _row: 'Urea.nitrogen..Mass.volume..in.Serum.or.Plasma', + }, + { + Estimate: 0.4569, + 'Std. Error': 0.7518, + 'z-value': 0.6078, + 'p-value': 0.5433, + 'low0.95CI.LP': -1.0166, + 'high0.95CI.LP': 1.9304, + P_OR: 1.5792, + 'low0.95CI.P_OR': 0.3618, + 'high0.95CI.P_OR': 6.8926, + _row: 'Albumin..Mass.volume..in.Serum.or.Plasma', + }, + ], + dev: 172.4603, + df: 211, + 'output.information': + 'SEE TOP OF OUTPUT FOR INFORMATION ON MISSING DATA AND ERROR MESSAGES', +}; + +const createExperiment = (): Experiment => ({ + id: 'dummy-id', + name: 'Testing purpose', + algorithm: { + name: 'logistic-regression', + parameters: [ + { + name: 'pos-level', + value: 'White', + }, + ], + }, + datasets: ['sophia.db'], + domain: 'dementia', + variables: ['race'], + coVariables: [ + 'Urea.nitrogen..Mass.volume..in.Serum.or.Plasma', + 'Albumin..Mass.volume..in.Serum.or.Plasma', + ], + results: [], +}); + +describe('Logistic Regression Handler', () => { + describe('Normal usage', () => { + it('should return the correct results', () => { + const experiment = createExperiment(); + const handler = new LogisticRegressionHandler(); + handler.handle(experiment, data, []); + + expect(experiment.results).toHaveLength(1); + }); + }); +}); diff --git a/api/src/engine/connectors/datashield/handlers/algorithms/logistic-regression.handler.ts b/api/src/engine/connectors/datashield/handlers/algorithms/logistic-regression.handler.ts new file mode 100644 index 0000000000000000000000000000000000000000..bd0771deeaad1e497fe93fb5549fc98887aa5f84 --- /dev/null +++ b/api/src/engine/connectors/datashield/handlers/algorithms/logistic-regression.handler.ts @@ -0,0 +1,83 @@ +import { Experiment } from '../../../../models/experiment/experiment.model'; +import { TableResult } from '../../../../models/result/table-result.model'; +import { Variable } from '../../../../models/variable.model'; +import BaseHandler from '../base.handler'; + +const lookupDict = { + Estimate: 'Estimate', + 'Std. Error': 'Std. Error', + 'z-value': 'Z value', + 'p-value': 'P value', + 'low0.95CI.LP': 'Low 95% CI', + 'high0.95CI': 'High 95% CI', + P_OR: 'P OR', + 'low0.95CI.P_OR': 'Low 95% CI P_OR', + 'high0.95CI.P_OR': 'High 95% CI P OR', + _row: '', + iter: 'Iteration(s)', + Nvalid: 'Valid observations', + Ntotal: 'Total observations', + df: 'Degrees of freedom', +}; + +const properties = [ + '_row', + 'Estimate', + 'Std. Error', + 'z-value', + 'p-value', + 'low0.95CI.LP', + 'high0.95CI.LP', + 'P_OR', + 'low0.95CI.P_OR', + 'high0.95CI.P_OR', +]; + +const summaryProps = ['iter', 'Nvalid', 'Ntotal', 'df']; + +export default class LogisticRegressionHandler extends BaseHandler { + canHandle(algorithm: string, data: any): boolean { + return ( + algorithm.toLowerCase() === 'logistic-regression' && data['coefficients'] + ); + } + + getSummaryTable(data: any): TableResult { + return { + name: 'Summary', + headers: [ + { name: 'Name', type: 'string' }, + { name: 'Value', type: 'string' }, + ], + data: summaryProps.map((prop) => [lookupDict[prop], data[prop]]), + }; + } + + getTableResult(data: unknown, vars: Variable[]): TableResult { + return { + name: 'Results', + headers: properties.map((name) => ({ + name: lookupDict[name], + type: 'string', + })), + data: data['coefficients'].map((row: any) => { + const variable = vars.find((v) => v.id === row['_row']); + if (variable) row['_row'] = variable.label ?? variable.id; + return properties.map((name) => row[name]); + }), + }; + } + + handle(experiment: Experiment, data: unknown, vars: Variable[]): void { + if (!this.canHandle(experiment.algorithm.name, data)) + return this.next?.handle(experiment, data, vars); + + const tableResult = this.getTableResult(data, vars); + const summaryTable = this.getSummaryTable(data); + + if (tableResult) experiment.results.push(tableResult); + if (summaryTable) experiment.results.push(summaryTable); + + this.next?.handle(experiment, data, vars); + } +}